|
| 1 | +package reverseproxy |
| 2 | + |
| 3 | +import ( |
| 4 | + "net/http" |
| 5 | + "net/http/httputil" |
| 6 | + "net/url" |
| 7 | + "strings" |
| 8 | +) |
| 9 | + |
| 10 | +// Build initializes and returns a new ReverseProxy instance suitable for SSL proxying |
| 11 | +func Build(toURL *url.URL) *httputil.ReverseProxy { |
| 12 | + localProxy := &httputil.ReverseProxy{} |
| 13 | + addProxyHeaders := func(req *http.Request) { |
| 14 | + req.Header.Set(http.CanonicalHeaderKey("X-Forwarded-Proto"), "https") |
| 15 | + req.Header.Set(http.CanonicalHeaderKey("X-Forwarded-Port"), "443") // TODO: inherit another port if needed |
| 16 | + } |
| 17 | + localProxy.Director = newDirector(toURL, addProxyHeaders) |
| 18 | + |
| 19 | + return localProxy |
| 20 | +} |
| 21 | + |
| 22 | +// newDirector creates a base director that should be exactly what http.NewSingleHostReverseProxy() creates, but allows |
| 23 | +// for the caller to supply and extraDirector function to decorate to request to the downstream server |
| 24 | +func newDirector(target *url.URL, extraDirector func(*http.Request)) func(*http.Request) { |
| 25 | + targetQuery := target.RawQuery |
| 26 | + return func(req *http.Request) { |
| 27 | + req.URL.Scheme = target.Scheme |
| 28 | + req.URL.Host = target.Host |
| 29 | + req.URL.Path = singleJoiningSlash(target.Path, req.URL.Path) |
| 30 | + if targetQuery == "" || req.URL.RawQuery == "" { |
| 31 | + req.URL.RawQuery = targetQuery + req.URL.RawQuery |
| 32 | + } else { |
| 33 | + req.URL.RawQuery = targetQuery + "&" + req.URL.RawQuery |
| 34 | + } |
| 35 | + if _, ok := req.Header["User-Agent"]; !ok { |
| 36 | + // explicitly disable User-Agent so it's not set to default value |
| 37 | + req.Header.Set("User-Agent", "") |
| 38 | + } |
| 39 | + |
| 40 | + if extraDirector != nil { |
| 41 | + extraDirector(req) |
| 42 | + } |
| 43 | + } |
| 44 | +} |
| 45 | + |
| 46 | +// singleJoiningSlash is a utility function that adds a single slash to a URL where appropriate, copied from |
| 47 | +// the httputil package |
| 48 | +// TODO: add test to ensure behavior does not diverge from httputil's implementation, as per Rob Pike's proverbs |
| 49 | +func singleJoiningSlash(a, b string) string { |
| 50 | + aslash := strings.HasSuffix(a, "/") |
| 51 | + bslash := strings.HasPrefix(b, "/") |
| 52 | + switch { |
| 53 | + case aslash && bslash: |
| 54 | + return a + b[1:] |
| 55 | + case !aslash && !bslash: |
| 56 | + return a + "/" + b |
| 57 | + } |
| 58 | + return a + b |
| 59 | +} |
0 commit comments