From bc1702e6cf582988737a00aaec3b56a17171fd89 Mon Sep 17 00:00:00 2001 From: yusing Date: Wed, 8 Jan 2025 12:04:11 +0800 Subject: [PATCH] refactoring: moved reverse_proxy to separate package to avoid import cycle --- internal/docker/idlewatcher/waker.go | 8 +-- internal/net/http/header_utils.go | 46 ++++++++++++ internal/net/http/logger.go | 5 -- internal/net/http/middleware/middleware.go | 5 +- .../http/middleware/set_upstream_headers.go | 3 +- internal/net/http/middleware/test_utils.go | 4 +- .../{ => reverseproxy}/reverse_proxy_mod.go | 72 +++++-------------- internal/route/http.go | 5 +- 8 files changed, 76 insertions(+), 72 deletions(-) delete mode 100644 internal/net/http/logger.go rename internal/net/http/{ => reverseproxy}/reverse_proxy_mod.go (89%) diff --git a/internal/docker/idlewatcher/waker.go b/internal/docker/idlewatcher/waker.go index 6439e65..c2c34c8 100644 --- a/internal/docker/idlewatcher/waker.go +++ b/internal/docker/idlewatcher/waker.go @@ -8,7 +8,7 @@ import ( "github.com/yusing/go-proxy/internal/docker/idlewatcher/types" E "github.com/yusing/go-proxy/internal/error" "github.com/yusing/go-proxy/internal/metrics" - gphttp "github.com/yusing/go-proxy/internal/net/http" + "github.com/yusing/go-proxy/internal/net/http/reverseproxy" net "github.com/yusing/go-proxy/internal/net/types" route "github.com/yusing/go-proxy/internal/route/types" "github.com/yusing/go-proxy/internal/task" @@ -22,7 +22,7 @@ type ( waker struct { _ U.NoCopy - rp *gphttp.ReverseProxy + rp *reverseproxy.ReverseProxy stream net.Stream hc health.HealthChecker metric *metrics.Gauge @@ -38,7 +38,7 @@ const ( // TODO: support stream -func newWaker(parent task.Parent, entry route.Entry, rp *gphttp.ReverseProxy, stream net.Stream) (Waker, E.Error) { +func newWaker(parent task.Parent, entry route.Entry, rp *reverseproxy.ReverseProxy, stream net.Stream) (Waker, E.Error) { hcCfg := entry.RawEntry().HealthCheck hcCfg.Timeout = idleWakerCheckTimeout @@ -71,7 +71,7 @@ func newWaker(parent task.Parent, entry route.Entry, rp *gphttp.ReverseProxy, st } // lifetime should follow route provider. -func NewHTTPWaker(parent task.Parent, entry route.Entry, rp *gphttp.ReverseProxy) (Waker, E.Error) { +func NewHTTPWaker(parent task.Parent, entry route.Entry, rp *reverseproxy.ReverseProxy) (Waker, E.Error) { return newWaker(parent, entry, rp, nil) } diff --git a/internal/net/http/header_utils.go b/internal/net/http/header_utils.go index f086c07..db8c78f 100644 --- a/internal/net/http/header_utils.go +++ b/internal/net/http/header_utils.go @@ -2,6 +2,10 @@ package http import ( "net/http" + "net/textproto" + + "github.com/yusing/go-proxy/internal/utils/strutils" + "golang.org/x/net/http/httpguts" ) const ( @@ -22,6 +26,48 @@ const ( HeaderContentLength = "Content-Length" ) +// Hop-by-hop headers. These are removed when sent to the backend. +// As of RFC 7230, hop-by-hop headers are required to appear in the +// Connection header field. These are the headers defined by the +// obsoleted RFC 2616 (section 13.5.1) and are used for backward +// compatibility. +var hopHeaders = []string{ + "Connection", + "Proxy-Connection", // non-standard but still sent by libcurl and rejected by e.g. google + "Keep-Alive", + "Proxy-Authenticate", + "Proxy-Authorization", + "Te", // canonicalized version of "TE" + "Trailer", // not Trailers per URL above; https://www.rfc-editor.org/errata_search.php?eid=4522 + "Transfer-Encoding", + "Upgrade", +} + +func UpgradeType(h http.Header) string { + if !httpguts.HeaderValuesContainsToken(h["Connection"], "Upgrade") { + return "" + } + return h.Get("Upgrade") +} + +// RemoveHopByHopHeaders removes hop-by-hop headers. +func RemoveHopByHopHeaders(h http.Header) { + // RFC 7230, section 6.1: Remove headers listed in the "Connection" header. + for _, f := range h["Connection"] { + for _, sf := range strutils.SplitComma(f) { + if sf = textproto.TrimString(sf); sf != "" { + h.Del(sf) + } + } + } + // RFC 2616, section 13.5.1: Remove a set of known hop-by-hop headers. + // This behavior is superseded by the RFC 7230 Connection header, but + // preserve it for backwards compatibility. + for _, f := range hopHeaders { + h.Del(f) + } +} + func RemoveHop(h http.Header) { reqUpType := UpgradeType(h) RemoveHopByHopHeaders(h) diff --git a/internal/net/http/logger.go b/internal/net/http/logger.go deleted file mode 100644 index 3e52c93..0000000 --- a/internal/net/http/logger.go +++ /dev/null @@ -1,5 +0,0 @@ -package http - -import "github.com/yusing/go-proxy/internal/logging" - -var logger = logging.With().Str("module", "http").Logger() diff --git a/internal/net/http/middleware/middleware.go b/internal/net/http/middleware/middleware.go index ff854e9..d20072e 100644 --- a/internal/net/http/middleware/middleware.go +++ b/internal/net/http/middleware/middleware.go @@ -9,14 +9,15 @@ import ( E "github.com/yusing/go-proxy/internal/error" "github.com/yusing/go-proxy/internal/logging" gphttp "github.com/yusing/go-proxy/internal/net/http" + "github.com/yusing/go-proxy/internal/net/http/reverseproxy" "github.com/yusing/go-proxy/internal/utils" ) type ( Error = E.Error - ReverseProxy = gphttp.ReverseProxy - ProxyRequest = gphttp.ProxyRequest + ReverseProxy = reverseproxy.ReverseProxy + ProxyRequest = reverseproxy.ProxyRequest ImplNewFunc = func() any OptionsRaw = map[string]any diff --git a/internal/net/http/middleware/set_upstream_headers.go b/internal/net/http/middleware/set_upstream_headers.go index e963cbf..009fc84 100644 --- a/internal/net/http/middleware/set_upstream_headers.go +++ b/internal/net/http/middleware/set_upstream_headers.go @@ -4,6 +4,7 @@ import ( "net/http" gphttp "github.com/yusing/go-proxy/internal/net/http" + "github.com/yusing/go-proxy/internal/net/http/reverseproxy" ) // internal use only. @@ -13,7 +14,7 @@ type setUpstreamHeaders struct { var suh = NewMiddleware[setUpstreamHeaders]() -func newSetUpstreamHeaders(rp *gphttp.ReverseProxy) *Middleware { +func newSetUpstreamHeaders(rp *reverseproxy.ReverseProxy) *Middleware { m, err := suh.New(OptionsRaw{ "name": rp.TargetName, "scheme": rp.TargetURL.Scheme, diff --git a/internal/net/http/middleware/test_utils.go b/internal/net/http/middleware/test_utils.go index eb6fdf3..dceeb39 100644 --- a/internal/net/http/middleware/test_utils.go +++ b/internal/net/http/middleware/test_utils.go @@ -10,7 +10,7 @@ import ( "github.com/yusing/go-proxy/internal/common" E "github.com/yusing/go-proxy/internal/error" - gphttp "github.com/yusing/go-proxy/internal/net/http" + "github.com/yusing/go-proxy/internal/net/http/reverseproxy" "github.com/yusing/go-proxy/internal/net/types" ) @@ -139,7 +139,7 @@ func newMiddlewareTest(middleware *Middleware, args *testArgs) (*TestResult, E.E rr.parent = http.DefaultTransport } - rp := gphttp.NewReverseProxy(middleware.name, args.upstreamURL, rr) + rp := reverseproxy.NewReverseProxy(middleware.name, args.upstreamURL, rr) mid, setOptErr := middleware.New(args.middlewareOpt) if setOptErr != nil { diff --git a/internal/net/http/reverse_proxy_mod.go b/internal/net/http/reverseproxy/reverse_proxy_mod.go similarity index 89% rename from internal/net/http/reverse_proxy_mod.go rename to internal/net/http/reverseproxy/reverse_proxy_mod.go index b0537ef..91f7754 100644 --- a/internal/net/http/reverse_proxy_mod.go +++ b/internal/net/http/reverseproxy/reverse_proxy_mod.go @@ -2,7 +2,7 @@ // Modified from the Go project under the a BSD-style License (https://cs.opensource.google/go/go/+/refs/tags/go1.23.1:src/net/http/httputil/reverseproxy.go) // https://cs.opensource.google/go/go/+/master:LICENSE -package http +package reverseproxy // This is a small mod on net/http/httputil/reverseproxy.go // that boosts performance in some cases @@ -26,11 +26,12 @@ import ( "github.com/rs/zerolog" "github.com/yusing/go-proxy/internal/common" + "github.com/yusing/go-proxy/internal/logging" "github.com/yusing/go-proxy/internal/metrics" + gphttp "github.com/yusing/go-proxy/internal/net/http" "github.com/yusing/go-proxy/internal/net/http/accesslog" "github.com/yusing/go-proxy/internal/net/types" U "github.com/yusing/go-proxy/internal/utils" - "github.com/yusing/go-proxy/internal/utils/strutils" "golang.org/x/net/http/httpguts" ) @@ -77,7 +78,6 @@ type ReverseProxy struct { zerolog.Logger // The transport used to perform proxy requests. - // If nil, http.DefaultTransport is used. Transport http.RoundTripper // ModifyResponse is an optional function that modifies the @@ -104,6 +104,8 @@ type httpMetricLogger struct { labels *metrics.HTTPRouteMetricLabels } +var logger = logging.With().Str("module", "reverse_proxy").Logger() + // WriteHeader implements http.ResponseWriter. func (l *httpMetricLogger) WriteHeader(status int) { l.ResponseWriter.WriteHeader(status) @@ -222,23 +224,6 @@ func copyHeader(dst, src http.Header) { } } -// Hop-by-hop headers. These are removed when sent to the backend. -// As of RFC 7230, hop-by-hop headers are required to appear in the -// Connection header field. These are the headers defined by the -// obsoleted RFC 2616 (section 13.5.1) and are used for backward -// compatibility. -var hopHeaders = []string{ - "Connection", - "Proxy-Connection", // non-standard but still sent by libcurl and rejected by e.g. google - "Keep-Alive", - "Proxy-Authenticate", - "Proxy-Authorization", - "Te", // canonicalized version of "TE" - "Trailer", // not Trailers per URL above; https://www.rfc-editor.org/errata_search.php?eid=4522 - "Transfer-Encoding", - "Upgrade", -} - func (p *ReverseProxy) errorHandler(rw http.ResponseWriter, r *http.Request, err error, writeHeader bool) { switch { case errors.Is(err, context.Canceled), @@ -348,14 +333,14 @@ func (p *ReverseProxy) handler(rw http.ResponseWriter, req *http.Request) { p.rewriteRequestURL(outreq) outreq.Close = false - reqUpType := UpgradeType(outreq.Header) + reqUpType := gphttp.UpgradeType(outreq.Header) if !IsPrint(reqUpType) { p.errorHandler(rw, req, fmt.Errorf("client tried to switch to invalid protocol %q", reqUpType), true) return } req.Header.Del("Forwarded") - RemoveHopByHopHeaders(outreq.Header) + gphttp.RemoveHopByHopHeaders(outreq.Header) // Issue 21096: tell backend applications that care about trailer support // that we support trailers. (We do, but we don't go out of our way to @@ -380,14 +365,14 @@ func (p *ReverseProxy) handler(rw http.ResponseWriter, req *http.Request) { // If we aren't the first proxy retain prior // X-Forwarded-For information as a comma+space // separated list and fold multiple headers into one. - prior, ok := outreq.Header[HeaderXForwardedFor] + prior, ok := outreq.Header[gphttp.HeaderXForwardedFor] omit := ok && prior == nil // Issue 38079: nil now means don't populate the header xff := visitorIP if len(prior) > 0 { xff = strings.Join(prior, ", ") + ", " + xff } if !omit { - outreq.Header.Set(HeaderXForwardedFor, xff) + outreq.Header.Set(gphttp.HeaderXForwardedFor, xff) } var reqScheme string @@ -397,10 +382,10 @@ func (p *ReverseProxy) handler(rw http.ResponseWriter, req *http.Request) { reqScheme = "http" } - outreq.Header.Set(HeaderXForwardedMethod, req.Method) - outreq.Header.Set(HeaderXForwardedProto, reqScheme) - outreq.Header.Set(HeaderXForwardedHost, req.Host) - outreq.Header.Set(HeaderXForwardedURI, req.RequestURI) + outreq.Header.Set(gphttp.HeaderXForwardedMethod, req.Method) + outreq.Header.Set(gphttp.HeaderXForwardedProto, reqScheme) + outreq.Header.Set(gphttp.HeaderXForwardedHost, req.Host) + outreq.Header.Set(gphttp.HeaderXForwardedURI, req.RequestURI) if _, ok := outreq.Header["User-Agent"]; !ok { // If the outbound request doesn't have a User-Agent header set, @@ -467,7 +452,7 @@ func (p *ReverseProxy) handler(rw http.ResponseWriter, req *http.Request) { return } - RemoveHopByHopHeaders(res.Header) + gphttp.RemoveHopByHopHeaders(res.Header) if !p.modifyResponse(rw, res, req, outreq) { return @@ -518,31 +503,6 @@ func (p *ReverseProxy) handler(rw http.ResponseWriter, req *http.Request) { } } -func UpgradeType(h http.Header) string { - if !httpguts.HeaderValuesContainsToken(h["Connection"], "Upgrade") { - return "" - } - return h.Get("Upgrade") -} - -// RemoveHopByHopHeaders removes hop-by-hop headers. -func RemoveHopByHopHeaders(h http.Header) { - // RFC 7230, section 6.1: Remove headers listed in the "Connection" header. - for _, f := range h["Connection"] { - for _, sf := range strutils.SplitComma(f) { - if sf = textproto.TrimString(sf); sf != "" { - h.Del(sf) - } - } - } - // RFC 2616, section 13.5.1: Remove a set of known hop-by-hop headers. - // This behavior is superseded by the RFC 7230 Connection header, but - // preserve it for backwards compatibility. - for _, f := range hopHeaders { - h.Del(f) - } -} - // reference: https://github.com/traefik/traefik/blob/master/pkg/proxy/httputil/proxy.go // https://tools.ietf.org/html/rfc6455#page-20 func cleanWebsocketHeaders(req *http.Request) { @@ -563,8 +523,8 @@ func cleanWebsocketHeaders(req *http.Request) { } func (p *ReverseProxy) handleUpgradeResponse(rw http.ResponseWriter, req *http.Request, res *http.Response) { - reqUpType := UpgradeType(req.Header) - resUpType := UpgradeType(res.Header) + reqUpType := gphttp.UpgradeType(req.Header) + resUpType := gphttp.UpgradeType(res.Header) if !IsPrint(resUpType) { // We know reqUpType is ASCII, it's checked by the caller. p.errorHandler(rw, req, fmt.Errorf("backend tried to switch to invalid protocol %q", resUpType), true) return diff --git a/internal/route/http.go b/internal/route/http.go index eb60cf2..96fb3f0 100755 --- a/internal/route/http.go +++ b/internal/route/http.go @@ -13,6 +13,7 @@ import ( "github.com/yusing/go-proxy/internal/net/http/loadbalancer" loadbalance "github.com/yusing/go-proxy/internal/net/http/loadbalancer/types" "github.com/yusing/go-proxy/internal/net/http/middleware" + "github.com/yusing/go-proxy/internal/net/http/reverseproxy" "github.com/yusing/go-proxy/internal/route/entry" "github.com/yusing/go-proxy/internal/route/routes" route "github.com/yusing/go-proxy/internal/route/types" @@ -30,7 +31,7 @@ type ( loadBalancer *loadbalancer.LoadBalancer server *loadbalancer.Server handler http.Handler - rp *gphttp.ReverseProxy + rp *reverseproxy.ReverseProxy task *task.Task @@ -49,7 +50,7 @@ func NewHTTPRoute(entry *entry.ReverseProxyEntry) (impl, E.Error) { } service := entry.TargetName() - rp := gphttp.NewReverseProxy(service, entry.URL, trans) + rp := reverseproxy.NewReverseProxy(service, entry.URL, trans) if len(entry.Raw.Middlewares) > 0 { err := middleware.PatchReverseProxy(rp, entry.Raw.Middlewares)