comparison controllers/externalwfs.go @ 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 bd292a554b6e
children ad0e47c1fedf
comparison
equal deleted inserted replaced
343:5b03f420957d 344:e98033e3683a
1 package controllers 1 package controllers
2 2
3 import ( 3 import (
4 "compress/gzip"
4 "encoding/xml" 5 "encoding/xml"
5 "fmt" 6 "fmt"
6 "io" 7 "io"
7 "log" 8 "log"
9 "net"
8 "net/http" 10 "net/http"
9 "strings" 11 "strings"
10 12
11 "github.com/gorilla/mux" 13 "github.com/gorilla/mux"
12 "golang.org/x/net/html/charset" 14 "golang.org/x/net/html/charset"
13 15
14 "gemma.intevation.de/gemma/config" 16 "gemma.intevation.de/gemma/config"
15 ) 17 )
18
19 func copyHeader(dst, src http.Header) {
20 for k, vv := range src {
21 log.Printf("%s: %v\n", k, vv)
22 for _, v := range vv {
23 dst.Add(k, v)
24 }
25 }
26 }
27
28 func cloneHeader(h http.Header) http.Header {
29 h2 := make(http.Header, len(h))
30 for k, vv := range h {
31 log.Printf("clone: %s: %v\n", k, vv)
32 vv2 := make([]string, len(vv))
33 copy(vv2, vv)
34 h2[k] = vv2
35 }
36 return h2
37 }
38
39 // Hop-by-hop headers. These are removed when sent to the backend.
40 // http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html
41 var hopHeaders = []string{
42 "Connection",
43 "Proxy-Connection", // non-standard but still sent by libcurl and rejected by e.g. google
44 "Keep-Alive",
45 "Proxy-Authenticate",
46 "Proxy-Authorization",
47 "Te", // canonicalized version of "TE"
48 "Trailer", // not Trailers per URL above; http://www.rfc-editor.org/errata_search.php?eid=4522
49 "Transfer-Encoding",
50 "Upgrade",
51 }
52
53 // removeConnectionHeaders removes hop-by-hop headers listed in the "Connection" header of h.
54 // See RFC 2616, section 14.10.
55 func removeConnectionHeaders(h http.Header) {
56 if c := h.Get("Connection"); c != "" {
57 for _, f := range strings.Split(c, ",") {
58 if f = strings.TrimSpace(f); f != "" {
59 h.Del(f)
60 }
61 }
62 }
63 }
64
65 func isXML(h http.Header) bool {
66 for _, t := range h["Content-Type"] {
67 t = strings.ToLower(t)
68 if strings.Contains(t, "text/xml") ||
69 strings.Contains(t, "application/xml") {
70 return true
71 }
72 }
73 return false
74 }
16 75
17 func externalWFSProxy(rw http.ResponseWriter, req *http.Request) { 76 func externalWFSProxy(rw http.ResponseWriter, req *http.Request) {
18 77
19 external := config.ExternalWFSs() 78 external := config.ExternalWFSs()
20 if external == nil || len(external) == 0 { 79 if external == nil || len(external) == 0 {
55 return 114 return
56 } 115 }
57 116
58 log.Printf("%v\n", prefix) 117 log.Printf("%v\n", prefix)
59 url := prefix + "?" + req.URL.RawQuery 118 url := prefix + "?" + req.URL.RawQuery
119 log.Printf("%v\n", url)
60 120
61 remoteReq, err := http.NewRequest(req.Method, url, req.Body) 121 remoteReq, err := http.NewRequest(req.Method, url, req.Body)
62 if err != nil { 122 if err != nil {
63 http.Error(rw, fmt.Sprintf("error: %v", err), http.StatusBadRequest) 123 http.Error(rw, fmt.Sprintf("error: %v", err), http.StatusBadRequest)
64 return 124 return
65 } 125 }
66 126
67 client := &http.Client{} 127 remoteReq.Header = cloneHeader(req.Header)
68 resp, err := client.Do(remoteReq) 128 removeConnectionHeaders(remoteReq.Header)
129
130 // Remove hop-by-hop headers to the backend. Especially
131 // important is "Connection" because we want a persistent
132 // connection, regardless of what the client sent to us.
133 for _, h := range hopHeaders {
134 if remoteReq.Header.Get(h) != "" {
135 remoteReq.Header.Del(h)
136 }
137 }
138
139 if clientIP, _, err := net.SplitHostPort(req.RemoteAddr); err == nil {
140 // If we aren't the first proxy retain prior
141 // X-Forwarded-For information as a comma+space
142 // separated list and fold multiple headers into one.
143 if prior, ok := remoteReq.Header["X-Forwarded-For"]; ok {
144 clientIP = strings.Join(prior, ", ") + ", " + clientIP
145 }
146 remoteReq.Header.Set("X-Forwarded-For", clientIP)
147 log.Printf("X-Forwarded-For: %s\n", clientIP)
148 }
149
150 log.Printf("req: %v\n", remoteReq)
151
152 resp, err := http.DefaultTransport.RoundTrip(remoteReq)
153 //client := &http.Client{}
154 //resp, err := client.Do(remoteReq)
69 if err != nil { 155 if err != nil {
70 http.Error(rw, fmt.Sprintf("error: %v", err), http.StatusBadRequest) 156 http.Error(rw, fmt.Sprintf("error: %v", err), http.StatusBadRequest)
71 return 157 return
72 } 158 }
73 159
74 var transcode bool 160 log.Printf("%v\n", resp.Header)
75 161
76 for k := range resp.Header { 162 xml := isXML(resp.Header)
77 v := resp.Header.Get(k) 163 log.Printf("is xml: %t\n", xml)
78 rw.Header().Set(k, v) 164
79 if strings.TrimSpace(strings.ToLower(k)) == "content-type" && 165 gzipped := strings.Contains(resp.Header.Get("Content-Encoding"), "gzip")
80 strings.Contains(strings.ToLower(v), "application/xml") { 166 if gzipped {
81 transcode = true 167 resp.Header.Del("Content-Encoding")
82 } 168 }
83 } 169
170 removeConnectionHeaders(resp.Header)
171 copyHeader(rw.Header(), resp.Header)
172
173 rw.WriteHeader(resp.StatusCode)
84 174
85 defer resp.Body.Close() 175 defer resp.Body.Close()
86 176
87 if transcode { 177 if xml {
88 to := useHTTPS(req) + "://" + req.Host 178 to := useHTTPS(req) + "://" + req.Host
89 if !strings.HasPrefix(req.URL.Path, "/") { 179 if !strings.HasPrefix(req.URL.Path, "/") {
90 to += "/" 180 to += "/"
91 } 181 }
92 to += req.URL.Path 182 to += req.URL.Path
183 var r io.Reader = resp.Body
184 if gzipped {
185 var err error
186 r, err = gzip.NewReader(resp.Body)
187 if err != nil {
188 log.Printf("gzip error: %v\n", err)
189 http.Error(rw, fmt.Sprintf("error: %v", err), http.StatusBadGateway)
190 return
191 }
192 } else {
193 r = resp.Body
194 }
93 log.Printf("rewrite %s to: %s\n", prefix, to) 195 log.Printf("rewrite %s to: %s\n", prefix, to)
94 err = rewrite(rw, resp.Body, prefix, to) 196 err = rewrite(rw, r, prefix, to)
95 } else { 197 } else {
198 log.Printf("no rewrite")
96 _, err = io.Copy(rw, resp.Body) 199 _, err = io.Copy(rw, resp.Body)
97 } 200 }
98 201
99 if err != nil { 202 if err != nil {
100 log.Printf("copy error: %v\n", err) 203 log.Printf("copy error: %v\n", err)