# HG changeset patch # User Sascha L. Teichmann # Date 1533311931 -7200 # Node ID bd292a554b6e72bed889fa4562a525467a3f4901 # Parent 154e0f5bff0a3761647e6ede400bc09ac42c0451 Made gemma a WFS proxy. diff -r 154e0f5bff0a -r bd292a554b6e 3rdpartylibs.sh --- 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 diff -r 154e0f5bff0a -r bd292a554b6e config/config.go --- 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", diff -r 154e0f5bff0a -r bd292a554b6e controllers/externalwfs.go --- /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 + } +} diff -r 154e0f5bff0a -r bd292a554b6e controllers/routes.go --- 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) - }