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)
 	}