Skip to content

Instantly share code, notes, and snippets.

@tjamet
Last active November 10, 2022 10:15
Show Gist options
  • Save tjamet/8a3d9e3019045855137deadcf3318245 to your computer and use it in GitHub Desktop.
Save tjamet/8a3d9e3019045855137deadcf3318245 to your computer and use it in GitHub Desktop.
http handler factory to proxy requests in go
func newProxyHandler(client *http.Client, backend *url.URL) http.Handler {
return http.HandlerFunc(func(ow http.ResponseWriter, r *http.Request) {
w := &loggedResponseWriter{ResponseWriter: ow}
defer func() {
log.Printf("%s %s %d %d Bytes", r.Method, r.URL.Path, w.code, w.size)
}()
req, err := http.NewRequest(r.Method, fmt.Sprintf("%s://%s", backend.Scheme, backend.Host), r.Body)
if err != nil {
log.Println("failed to call backend:", err.Error())
w.WriteHeader(http.StatusInternalServerError)
return
}
req.Host = r.Host
// r.RequestURI contains the full value of GET http://host/path?query HTTP/1.1
// When sending the request, request.URL.RequestURI() is used to fill GET <what> HTTP/1.1
// Although this does not work when using proxy:
// https://github.com/golang/go/blob/c0547476f342665514904cf2581a62135d2366c3/src/net/http/request.go#L524
// This behaviour is achieved by providing only Opaque:
// https://github.com/golang/go/blob/c0547476f342665514904cf2581a62135d2366c3/src/net/url/url.go#L1002
req.URL.Opaque = r.URL.RequestURI()
// Implement the X-Forwarded headers as defined in
// https://docs.aws.amazon.com/elasticloadbalancing/latest/classic/x-forwarded-headers.html
reqURL := url.URL{Host: r.RemoteAddr}
req.Header.Set("X-Forwarded-Port", reqURL.Port())
if r.TLS == nil {
req.Header.Set("X-Forwarded-Proto", "http")
} else {
req.Header.Set("X-Forwarded-Proto", "https")
}
req.Header["X-Forwarded-For"] = []string{}
// Forward all original request headers
for key, value := range r.Header {
req.Header[key] = value
}
req.Header["X-Forwarded-For"] = append(req.Header["X-Forwarded-For"], reqURL.Hostname())
resp, err := client.Do(req)
if err != nil {
log.Println("failed to write response:", err.Error())
w.WriteHeader(http.StatusInternalServerError)
} else {
// Write the response back with all headers
for key, value := range resp.Header {
w.Header()[key] = value
}
w.WriteHeader(resp.StatusCode)
_, err = io.Copy(w, resp.Body)
if err != nil {
log.Println("failed to write response:", err.Error())
}
}
})
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment