Mercurial > gemma
view controllers/externalwfs.go @ 345:b97b3172c61a
Add staging feature to more tables
Added tables currently only have limited visibility for waterway_user
but not yet policies for write access.
author | Tom Gottfried <tom@intevation.de> |
---|---|
date | Mon, 06 Aug 2018 15:19:05 +0200 |
parents | e98033e3683a |
children | ad0e47c1fedf |
line wrap: on
line source
package controllers import ( "compress/gzip" "encoding/xml" "fmt" "io" "log" "net" "net/http" "strings" "github.com/gorilla/mux" "golang.org/x/net/html/charset" "gemma.intevation.de/gemma/config" ) func copyHeader(dst, src http.Header) { for k, vv := range src { log.Printf("%s: %v\n", k, vv) for _, v := range vv { dst.Add(k, v) } } } func cloneHeader(h http.Header) http.Header { h2 := make(http.Header, len(h)) for k, vv := range h { log.Printf("clone: %s: %v\n", k, vv) vv2 := make([]string, len(vv)) copy(vv2, vv) h2[k] = vv2 } return h2 } // Hop-by-hop headers. These are removed when sent to the backend. // http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html var hopHeaders = []string{ "Connection", "Proxy-Connection", // non-standard but still sent by libcurl and rejected by e.g. google "Keep-Alive", "Proxy-Authenticate", "Proxy-Authorization", "Te", // canonicalized version of "TE" "Trailer", // not Trailers per URL above; http://www.rfc-editor.org/errata_search.php?eid=4522 "Transfer-Encoding", "Upgrade", } // removeConnectionHeaders removes hop-by-hop headers listed in the "Connection" header of h. // See RFC 2616, section 14.10. func removeConnectionHeaders(h http.Header) { if c := h.Get("Connection"); c != "" { for _, f := range strings.Split(c, ",") { if f = strings.TrimSpace(f); f != "" { h.Del(f) } } } } func isXML(h http.Header) bool { for _, t := range h["Content-Type"] { t = strings.ToLower(t) if strings.Contains(t, "text/xml") || strings.Contains(t, "application/xml") { return true } } return false } 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 log.Printf("%v\n", url) remoteReq, err := http.NewRequest(req.Method, url, req.Body) if err != nil { http.Error(rw, fmt.Sprintf("error: %v", err), http.StatusBadRequest) return } remoteReq.Header = cloneHeader(req.Header) removeConnectionHeaders(remoteReq.Header) // Remove hop-by-hop headers to the backend. Especially // important is "Connection" because we want a persistent // connection, regardless of what the client sent to us. for _, h := range hopHeaders { if remoteReq.Header.Get(h) != "" { remoteReq.Header.Del(h) } } if clientIP, _, err := net.SplitHostPort(req.RemoteAddr); err == nil { // If we aren't the first proxy retain prior // X-Forwarded-For information as a comma+space // separated list and fold multiple headers into one. if prior, ok := remoteReq.Header["X-Forwarded-For"]; ok { clientIP = strings.Join(prior, ", ") + ", " + clientIP } remoteReq.Header.Set("X-Forwarded-For", clientIP) log.Printf("X-Forwarded-For: %s\n", clientIP) } log.Printf("req: %v\n", remoteReq) resp, err := http.DefaultTransport.RoundTrip(remoteReq) //client := &http.Client{} //resp, err := client.Do(remoteReq) if err != nil { http.Error(rw, fmt.Sprintf("error: %v", err), http.StatusBadRequest) return } log.Printf("%v\n", resp.Header) xml := isXML(resp.Header) log.Printf("is xml: %t\n", xml) gzipped := strings.Contains(resp.Header.Get("Content-Encoding"), "gzip") if gzipped { resp.Header.Del("Content-Encoding") } removeConnectionHeaders(resp.Header) copyHeader(rw.Header(), resp.Header) rw.WriteHeader(resp.StatusCode) defer resp.Body.Close() if xml { to := useHTTPS(req) + "://" + req.Host if !strings.HasPrefix(req.URL.Path, "/") { to += "/" } to += req.URL.Path var r io.Reader = resp.Body if gzipped { var err error r, err = gzip.NewReader(resp.Body) if err != nil { log.Printf("gzip error: %v\n", err) http.Error(rw, fmt.Sprintf("error: %v", err), http.StatusBadGateway) return } } else { r = resp.Body } log.Printf("rewrite %s to: %s\n", prefix, to) err = rewrite(rw, r, prefix, to) } else { log.Printf("no rewrite") _, 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 } }