From 18d270f79b58a0b43ef2f83c1df34e093d070815 Mon Sep 17 00:00:00 2001 From: Dan Fuhry Date: Mon, 19 Oct 2026 15:35:53 -0400 Subject: [PATCH] [http] use httputil.ReverseProxy in proxyAction (rename from staticUpstreamAction) --- http/http.go | 1 + http/route_action_proxy.go | 97 +++++++++++++------------------------- 2 files changed, 34 insertions(+), 64 deletions(-) diff --git a/http/http.go b/http/http.go index 0025ff1..e147820 100644 --- a/http/http.go +++ b/http/http.go @@ -261,6 +261,7 @@ func (v *HTTPVirtualHost) fulfill(w http.ResponseWriter, r *http.Request, routes r.URL.Path, route.Path.String(), match) } else { http.Error(w, "nothing to match on in route", http.StatusInternalServerError) + return } if match { diff --git a/http/route_action_proxy.go b/http/route_action_proxy.go index a39a225..190e9c2 100644 --- a/http/route_action_proxy.go +++ b/http/route_action_proxy.go @@ -4,11 +4,11 @@ import ( "context" "crypto/tls" "fmt" - "io" "net" "net/http" + "net/http/httputil" + "net/url" "strconv" - "strings" "sync" "gopkg.in/yaml.v3" @@ -16,19 +16,20 @@ import ( "go.fuhry.dev/runtime/mtls" ) -type staticUpstreamAction struct { +type proxyAction struct { Host string `yaml:"host"` Port int `yaml:"port"` Identity string `yaml:"mtls_id"` - client *http.Client - clientOnce sync.Once + rProxy *httputil.ReverseProxy + rProxyErr error + rProxyOnce sync.Once } // httpClient returns an HTTP client for making requests to the backend. -func (su *staticUpstreamAction) httpClient() (*http.Client, error) { +func (su *proxyAction) reverseProxy() (*httputil.ReverseProxy, error) { var err error - su.clientOnce.Do(func() { + su.rProxyOnce.Do(func() { transport := &http.Transport{} var tlsConfig *tls.Config @@ -36,6 +37,7 @@ func (su *staticUpstreamAction) httpClient() (*http.Client, error) { myIdentity := mtls.DefaultIdentity() tlsConfig, err = myIdentity.TlsConfig(context.Background()) if err != nil { + su.rProxyErr = err return } @@ -43,26 +45,37 @@ func (su *staticUpstreamAction) httpClient() (*http.Client, error) { verifier.AllowFrom(mtls.Service, su.Identity) err = verifier.ConfigureClient(tlsConfig) if err != nil { + su.rProxyErr = err return } transport.TLSClientConfig = tlsConfig } - client := &http.Client{ - Transport: transport, + target := &url.URL{ + Scheme: "http", + Host: net.JoinHostPort(su.Host, strconv.Itoa(su.Port)), + } + if su.Identity != "" { + target.Scheme = "https" } - su.client = client + su.rProxy = &httputil.ReverseProxy{ + Transport: transport, + Rewrite: func(r *httputil.ProxyRequest) { + r.SetURL(target) + r.SetXForwarded() + }, + } }) - if err != nil { - return nil, err + if su.rProxyErr != nil { + return nil, su.rProxyErr } - return su.client, nil + return su.rProxy, nil } // Handle implements RouteAction. -func (su *staticUpstreamAction) Handle(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) { +func (su *proxyAction) Handle(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) { logger := LoggerFromContext(r.Context()) upstreamReq := r.Clone(r.Context()) @@ -71,6 +84,7 @@ func (su *staticUpstreamAction) Handle(w http.ResponseWriter, r *http.Request, n upstreamReq.URL.Scheme = "https" } upstreamReq.URL.Host = net.JoinHostPort(su.Host, strconv.Itoa(su.Port)) + upstreamReq.Host = upstreamReq.URL.Host upstreamReq.RequestURI = "" // set proxy headers @@ -80,65 +94,20 @@ func (su *staticUpstreamAction) Handle(w http.ResponseWriter, r *http.Request, n } // proxy the request to the backend - client, err := su.httpClient() + rproxy, err := su.reverseProxy() if err != nil { http.Error(w, fmt.Sprintf("error setting up connection to backend: %v", err), http.StatusInternalServerError) return } - response, err := client.Do(upstreamReq) - if err != nil { - http.Error(w, err.Error(), http.StatusBadGateway) - return - } - - for name, value := range response.Header { - w.Header().Set(name, strings.Join(value, ", ")) - } - - if response.StatusCode == http.StatusSwitchingProtocols { - hijacker, ok := w.(http.Hijacker) - if !ok { - http.Error(w, "websocket passthrough not supported", http.StatusMethodNotAllowed) - return - } - - upstreamWriter, ok := response.Body.(io.Writer) - if !ok { - http.Error(w, "body doesn't support io.Writer", http.StatusMethodNotAllowed) - return - } - - w.WriteHeader(response.StatusCode) - - conn, rw, err := hijacker.Hijack() - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - wg := sync.WaitGroup{} - wg.Add(2) - pipe := func(w io.Writer, r io.Reader) { - defer wg.Done() - io.Copy(w, r) - } - go pipe(rw, response.Body) - go pipe(upstreamWriter, rw) - - wg.Wait() - conn.Close() - return - } - w.WriteHeader(response.StatusCode) - io.Copy(w, response.Body) + rproxy.ServeHTTP(w, r) } -func staticUpstreamActionFromYaml(node *yaml.Node) (RouteAction, error) { +func proxyActionFromYaml(node *yaml.Node) (RouteAction, error) { var rawNode struct { - Proxy *staticUpstreamAction `yaml:"proxy"` + Proxy *proxyAction `yaml:"proxy"` } err := node.Decode(&rawNode) @@ -150,5 +119,5 @@ func staticUpstreamActionFromYaml(node *yaml.Node) (RouteAction, error) { } func init() { - AddRouteParseFunc(staticUpstreamActionFromYaml) + AddRouteParseFunc(proxyActionFromYaml) } -- 2.52.0