From 0e557200f66d2761a2ac681630d57e8050e4c21f Mon Sep 17 00:00:00 2001 From: Nirav Uchat Date: Thu, 4 Feb 2021 10:37:36 -0800 Subject: [PATCH] added header based proxy support --- apidef/api_definitions.go | 4 +- gateway/handler_success.go | 78 ++++++++++++++++++++++++++++++++++++++ gateway/reverse_proxy.go | 14 +++++++ 3 files changed, 95 insertions(+), 1 deletion(-) diff --git a/apidef/api_definitions.go b/apidef/api_definitions.go index 80762ab6bce..1b1eaa94b7a 100644 --- a/apidef/api_definitions.go +++ b/apidef/api_definitions.go @@ -508,7 +508,9 @@ type RequestSigningMeta struct { } type ProxyConfig struct { - PreserveHostHeader bool `bson:"preserve_host_header" json:"preserve_host_header"` + PreserveHostHeader bool `bson:"preserve_host_header" json:"preserve_host_header"` + //Cisco - enable host re-write from header + UpdateHostHeader string `bson:"update_host_header" json:"update_host_header"` ListenPath string `bson:"listen_path" json:"listen_path"` TargetURL string `bson:"target_url" json:"target_url"` DisableStripSlash bool `bson:"disable_strip_slash" json:"disable_strip_slash"` diff --git a/gateway/handler_success.go b/gateway/handler_success.go index 9551906bd31..fe3fce531d6 100644 --- a/gateway/handler_success.go +++ b/gateway/handler_success.go @@ -2,10 +2,15 @@ package gateway import ( "bytes" + "crypto/tls" "encoding/base64" "io" "io/ioutil" + "net" "net/http" + "net/http/httputil" + "net/textproto" + "net/url" "runtime/pprof" "strconv" "strings" @@ -304,6 +309,52 @@ func (s *SuccessHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) *http log.Debug("Started proxy") defer s.Base().UpdateRequestSession(r) + log.Debug("Check update_host_header") + if len(s.Spec.Proxy.UpdateHostHeader) > 0 { + //Normalize header + log.Debug("Detected UpdateHostHeader ", s.Spec.Proxy.UpdateHostHeader) + header := textproto.CanonicalMIMEHeaderKey(s.Spec.Proxy.UpdateHostHeader) + log.Debug("CanonicalMIMEHeaderKey form ", s.Spec.Proxy.UpdateHostHeader) + updateHost, ok := r.Header[header] + if ok { + //Create Reverse proxy + director := func(req *http.Request) { + req.Header.Del(header) + log.Debug("Updating upstream host", updateHost[0]) + + //Set host + req.URL.Host = updateHost[0] + + targetUrl, _ := url.Parse(s.Spec.Proxy.TargetURL) + + //Set Scheme + switch targetUrl.Scheme { + case "http": + req.URL.Scheme = "http" + case "https": + req.URL.Scheme = "https" + case "ws": + req.URL.Scheme = "http" + case "wss": + req.URL.Scheme = "https" + } + + //Reset the rawquery assuming URLRewrite may have reset the path + if origURL := ctxGetOrigRequestURL(r); origURL != nil { + req.URL.RawQuery = origURL.RawQuery + } + } + + proxy := &httputil.ReverseProxy{Director: director} + proxy.Transport = defaultProxyTransport(30) + + log.Debug("Start update_host_header proxy") + proxy.ServeHTTP(w, r) + log.Debug("Done update_host_header proxy") + return nil + } + } + versionDef := s.Spec.VersionDefinition if !s.Spec.VersionData.NotVersioned && versionDef.Location == "url" && versionDef.StripPath { part := s.Spec.getVersionFromRequest(r) @@ -341,6 +392,33 @@ func (s *SuccessHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) *http return nil } +func defaultProxyTransport(dialerTimeout float64) http.RoundTripper { + timeout := 30.0 + if dialerTimeout > 0 { + log.Debug("Setting timeout for outbound request to: ", dialerTimeout) + timeout = dialerTimeout + } + + dialer := &net.Dialer{ + Timeout: time.Duration(float64(timeout) * float64(time.Second)), + KeepAlive: 30 * time.Second, + DualStack: true, + } + dialContextFunc := dialer.DialContext + if dnsCacheManager.IsCacheEnabled() { + dialContextFunc = dnsCacheManager.WrapDialer(dialer) + } + + return &http.Transport{ + DialContext: dialContextFunc, + MaxIdleConns: config.Global().MaxIdleConns, + MaxIdleConnsPerHost: config.Global().MaxIdleConnsPerHost, // default is 100 + ResponseHeaderTimeout: time.Duration(dialerTimeout) * time.Second, + TLSHandshakeTimeout: 10 * time.Second, + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + } +} + // ServeHTTPWithCache will store the request details in the analytics store if necessary and proxy the request to it's // final destination, this is invoked by the ProxyHandler or right at the start of a request chain if the URL // Spec states the path is Ignored Itwill also return a response object for the cache diff --git a/gateway/reverse_proxy.go b/gateway/reverse_proxy.go index 214c6429e7e..60695f20c42 100644 --- a/gateway/reverse_proxy.go +++ b/gateway/reverse_proxy.go @@ -23,6 +23,7 @@ import ( "net" "net/http" "net/http/httptest" + "net/textproto" "net/url" "strconv" "strings" @@ -279,6 +280,19 @@ func TykNewSingleHostReverseProxy(target *url.URL, spec *APISpec, logger *logrus req.Host = targetToUse.Host } + //Cisco change + if len(spec.Proxy.UpdateHostHeader) > 0 { + //Normalize header + log.Debug("Detected UpdateHostHeader ", spec.Proxy.UpdateHostHeader) + header := textproto.CanonicalMIMEHeaderKey(spec.Proxy.UpdateHostHeader) + log.Debug("CanonicalMIMEHeaderKey form ", spec.Proxy.UpdateHostHeader) + updateHost, ok := req.Header[header] + if ok { + log.Debug("Updating upstream host", updateHost[0]) + req.Host = updateHost[0] + } + } + if targetQuery == "" || req.URL.RawQuery == "" { req.URL.RawQuery = targetQuery + req.URL.RawQuery } else {