Mercurial > gemma
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) |