package util import ( "fmt" "github.com/sirupsen/logrus" "net/http" "net/http/httputil" "net/url" "strings" ) type ProxyServiceResolver interface { Service(host string) string } func NewProxy(target string) (*httputil.ReverseProxy, error) { targetURL, err := url.Parse(target) if err != nil { return nil, err } proxy := httputil.NewSingleHostReverseProxy(targetURL) director := proxy.Director proxy.Director = func(req *http.Request) { director(req) logrus.Infof("-> %v", req.URL.String()) req.Header.Set("X-Proxy", "zrok") } proxy.ModifyResponse = func(resp *http.Response) error { return nil } proxy.ErrorHandler = func(w http.ResponseWriter, r *http.Request, err error) { logrus.Errorf("error proxying: %v", err) } return proxy, nil } func NewServiceProxy(p ProxyServiceResolver) (*httputil.ReverseProxy, error) { proxy := hostTargetReverseProxy(p) director := proxy.Director proxy.Director = func(req *http.Request) { director(req) logrus.Infof("-> %v", req.URL.String()) req.Header.Set("X-Proxy", "zrok") } proxy.ModifyResponse = func(resp *http.Response) error { return nil } proxy.ErrorHandler = func(w http.ResponseWriter, r *http.Request, err error) { logrus.Errorf("error proxying: %v", err) } return proxy, nil } type proxyHandler struct { proxy *httputil.ReverseProxy } func NewProxyHandler(proxy *httputil.ReverseProxy) *proxyHandler { return &proxyHandler{proxy} } func (self *proxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { self.proxy.ServeHTTP(w, r) } func hostTargetReverseProxy(r ProxyServiceResolver) *httputil.ReverseProxy { director := func(req *http.Request) { targetSvc := r.Service(req.Host) if target, err := url.Parse(fmt.Sprintf("http://%v", targetSvc)); err == nil { targetQuery := target.RawQuery req.URL.Scheme = target.Scheme req.URL.Host = target.Host req.URL.Path, req.URL.RawPath = joinURLPath(target, req.URL) if targetQuery == "" || req.URL.RawQuery == "" { req.URL.RawQuery = targetQuery + req.URL.RawQuery } else { req.URL.RawQuery = targetQuery + "&" + req.URL.RawQuery } if _, ok := req.Header["User-Agent"]; !ok { // explicitly disable User-Agent so it's not set to default value req.Header.Set("User-Agent", "") } } } return &httputil.ReverseProxy{Director: director} } func joinURLPath(a, b *url.URL) (path, rawpath string) { if a.RawPath == "" && b.RawPath == "" { return singleJoiningSlash(a.Path, b.Path), "" } // Same as singleJoiningSlash, but uses EscapedPath to determine // whether a slash should be added apath := a.EscapedPath() bpath := b.EscapedPath() aslash := strings.HasSuffix(apath, "/") bslash := strings.HasPrefix(bpath, "/") switch { case aslash && bslash: return a.Path + b.Path[1:], apath + bpath[1:] case !aslash && !bslash: return a.Path + "/" + b.Path, apath + "/" + bpath } return a.Path + b.Path, apath + bpath } func singleJoiningSlash(a, b string) string { aslash := strings.HasSuffix(a, "/") bslash := strings.HasPrefix(b, "/") switch { case aslash && bslash: return a + b[1:] case !aslash && !bslash: return a + "/" + b } return a + b }