changeset 335:bd292a554b6e

Made gemma a WFS proxy.
author Sascha L. Teichmann <sascha.teichmann@intevation.de>
date Fri, 03 Aug 2018 17:58:51 +0200
parents 154e0f5bff0a
children 9d69eb2f0af3
files 3rdpartylibs.sh config/config.go controllers/externalwfs.go controllers/routes.go
diffstat 4 files changed, 210 insertions(+), 1 deletions(-) [+]
line wrap: on
line diff
--- a/3rdpartylibs.sh	Fri Aug 03 16:04:14 2018 +0200
+++ b/3rdpartylibs.sh	Fri Aug 03 17:58:51 2018 +0200
@@ -7,3 +7,4 @@
 go get -u -v github.com/gorilla/mux
 go get -u -v gopkg.in/gomail.v2
 go get -u -v github.com/rs/cors
+go get -u -v golang.org/x/net/html/charset
--- a/config/config.go	Fri Aug 03 16:04:14 2018 +0200
+++ b/config/config.go	Fri Aug 03 17:58:51 2018 +0200
@@ -34,6 +34,8 @@
 
 func AllowedOrigins() []string { return viper.GetStringSlice("allowed-origins") }
 
+func ExternalWFSs() map[string]interface{} { return viper.GetStringMap("external-wfs") }
+
 var RootCmd = &cobra.Command{
 	Use:   "gemma",
 	Short: "gemma is a server for waterway monitoring and management",
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/controllers/externalwfs.go	Fri Aug 03 17:58:51 2018 +0200
@@ -0,0 +1,198 @@
+package controllers
+
+import (
+	"encoding/xml"
+	"fmt"
+	"io"
+	"log"
+	"net/http"
+	"strings"
+
+	"github.com/gorilla/mux"
+	"golang.org/x/net/html/charset"
+
+	"gemma.intevation.de/gemma/config"
+)
+
+func externalWFSProxy(rw http.ResponseWriter, req *http.Request) {
+
+	external := config.ExternalWFSs()
+	if external == nil || len(external) == 0 {
+		http.NotFound(rw, req)
+		return
+	}
+	wfs := mux.Vars(req)["wfs"]
+
+	alias, found := external[wfs]
+	if !found {
+		http.NotFound(rw, req)
+		return
+	}
+	data, ok := alias.(map[string]interface{})
+	if !ok {
+		log.Printf("error: badly configured external wfs %s\n", wfs)
+		http.Error(rw,
+			http.StatusText(http.StatusInternalServerError),
+			http.StatusInternalServerError)
+		return
+	}
+
+	urlS, found := data["url"]
+	if !found {
+		log.Printf("error: missinf url fore xternal wfs %s\n", wfs)
+		http.Error(rw,
+			http.StatusText(http.StatusInternalServerError),
+			http.StatusInternalServerError)
+		return
+	}
+
+	prefix, ok := urlS.(string)
+	if !ok {
+		log.Printf("error: badly configured url for external wfs %s\n", wfs)
+		http.Error(rw,
+			http.StatusText(http.StatusInternalServerError),
+			http.StatusInternalServerError)
+		return
+	}
+
+	log.Printf("%v\n", prefix)
+	url := prefix + "?" + req.URL.RawQuery
+
+	remoteReq, err := http.NewRequest(req.Method, url, req.Body)
+	if err != nil {
+		http.Error(rw, fmt.Sprintf("error: %v", err), http.StatusBadRequest)
+		return
+	}
+
+	client := &http.Client{}
+	resp, err := client.Do(remoteReq)
+	if err != nil {
+		http.Error(rw, fmt.Sprintf("error: %v", err), http.StatusBadRequest)
+		return
+	}
+
+	var transcode bool
+
+	for k := range resp.Header {
+		v := resp.Header.Get(k)
+		rw.Header().Set(k, v)
+		if strings.TrimSpace(strings.ToLower(k)) == "content-type" &&
+			strings.Contains(strings.ToLower(v), "application/xml") {
+			transcode = true
+		}
+	}
+
+	defer resp.Body.Close()
+
+	if transcode {
+		to := useHTTPS(req) + "://" + req.Host
+		if !strings.HasPrefix(req.URL.Path, "/") {
+			to += "/"
+		}
+		to += req.URL.Path
+		log.Printf("rewrite %s to: %s\n", prefix, to)
+		err = rewrite(rw, resp.Body, prefix, to)
+	} else {
+		_, err = io.Copy(rw, resp.Body)
+	}
+
+	if err != nil {
+		log.Printf("copy error: %v\n", err)
+	}
+}
+
+func rewrite(w io.Writer, r io.Reader, from, to string) error {
+
+	decoder := xml.NewDecoder(r)
+	decoder.CharsetReader = charset.NewReaderLabel
+
+	encoder := xml.NewEncoder(w)
+
+	replace := func(s string) string {
+		return strings.Replace(s, from, to, -1)
+	}
+
+	var ns nsdef
+
+tokens:
+	for {
+		tok, err := decoder.Token()
+		switch {
+		case tok == nil && err == io.EOF:
+			break tokens
+		case err != nil:
+			return err
+		}
+
+		switch t := tok.(type) {
+		case xml.StartElement:
+			ns = ns.push()
+			t = t.Copy()
+
+			attr := make([]xml.Attr, len(t.Attr))
+
+			//var lns string
+			for i, at := range t.Attr {
+				switch {
+				case at.Name.Space == "xmlns":
+					ns.define(at.Value, at.Name.Local)
+					attr[i] = xml.Attr{Name: at.Name, Value: at.Value}
+				default:
+					attr[i] = xml.Attr{Name: at.Name, Value: replace(at.Value)}
+				}
+			}
+			if s := ns.lookup(t.Name.Space); s != "" {
+				t.Name.Space = ""
+				t.Name.Local = s + ":" + t.Name.Local
+			}
+			t.Attr = attr
+			tok = t
+
+		case xml.CharData:
+			tok = xml.CharData(replace(string(t)))
+
+		case xml.EndElement:
+			//log.Printf("lookup %s -> %s\n", t.Name.Space, ns.lookup(t.Name.Space))
+			if s := ns.lookup(t.Name.Space); s != "" {
+				t.Name.Space = ""
+				t.Name.Local = s + ":" + t.Name.Local
+				tok = t
+			}
+			ns = ns.pop()
+		}
+		if err := encoder.EncodeToken(tok); err != nil {
+			return err
+		}
+	}
+
+	return encoder.Flush()
+}
+
+type nsdef []map[string]string
+
+func (n nsdef) lookup(ns string) string {
+	for i := len(n) - 1; i >= 0; i-- {
+		if s := n[i][ns]; s != "" {
+			return s
+		}
+	}
+	return ""
+}
+
+func (n nsdef) push() nsdef {
+	return append(n, make(map[string]string))
+}
+
+func (n nsdef) pop() nsdef {
+	if l := len(n); l > 0 {
+		n[l-1] = nil
+		n = n[:l-1]
+	}
+	return n
+}
+
+func (n nsdef) define(ns, s string) {
+	if n != nil {
+		n[len(n)-1][ns] = s
+	}
+}
--- a/controllers/routes.go	Fri Aug 03 16:04:14 2018 +0200
+++ b/controllers/routes.go	Fri Aug 03 17:58:51 2018 +0200
@@ -17,6 +17,7 @@
 		all      = auth.EnsureRole("sys_admin", "waterway_admin", "waterway_user")
 	)
 
+	// User management.
 	api.Handle("/users", all(&JSONHandler{
 		Handle: listUsers,
 	})).Methods(http.MethodGet)
@@ -39,6 +40,7 @@
 		Handle: deleteUser,
 	})).Methods(http.MethodDelete)
 
+	// Password resets.
 	api.Handle("/users/passwordreset", &JSONHandler{
 		Input:  func() interface{} { return new(PWResetUser) },
 		Handle: passwordResetRequest,
@@ -48,11 +50,17 @@
 		Handle: passwordReset,
 	}).Methods(http.MethodGet)
 
+	// Proxy for external WFSs.
+	api.HandleFunc("/externalwfs/{wfs}", externalWFSProxy).
+		Methods(
+			http.MethodGet, http.MethodPost,
+			http.MethodPut, http.MethodDelete)
+
+	// Token handling: Login/Logout.
 	api.HandleFunc("/login", login).
 		Methods(http.MethodGet, http.MethodPost)
 	api.Handle("/logout", auth.SessionMiddleware(http.HandlerFunc(logout))).
 		Methods(http.MethodGet, http.MethodPost)
 	api.Handle("/renew", auth.SessionMiddleware(http.HandlerFunc(renew))).
 		Methods(http.MethodGet, http.MethodPost)
-
 }