Mercurial > gemma
changeset 344:e98033e3683a
Be more precise with HTTP headers in WFS proxy.
author | Sascha L. Teichmann <sascha.teichmann@intevation.de> |
---|---|
date | Mon, 06 Aug 2018 14:52:04 +0200 |
parents | 5b03f420957d |
children | b97b3172c61a |
files | controllers/externalwfs.go |
diffstat | 1 files changed, 115 insertions(+), 12 deletions(-) [+] |
line wrap: on
line diff
--- a/controllers/externalwfs.go Mon Aug 06 13:25:18 2018 +0200 +++ b/controllers/externalwfs.go Mon Aug 06 14:52:04 2018 +0200 @@ -1,10 +1,12 @@ package controllers import ( + "compress/gzip" "encoding/xml" "fmt" "io" "log" + "net" "net/http" "strings" @@ -14,6 +16,63 @@ "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() @@ -57,6 +116,7 @@ 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 { @@ -64,35 +124,78 @@ return } - client := &http.Client{} - resp, err := client.Do(remoteReq) + 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 } - var transcode bool + log.Printf("%v\n", resp.Header) + + xml := isXML(resp.Header) + log.Printf("is xml: %t\n", xml) - 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 - } + 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 transcode { + 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, resp.Body, prefix, to) + err = rewrite(rw, r, prefix, to) } else { + log.Printf("no rewrite") _, err = io.Copy(rw, resp.Body) }