]> go.fuhry.dev Git - runtime.git/commitdiff
[http] use httputil.ReverseProxy in proxyAction (rename from staticUpstreamAction)
authorDan Fuhry <dan@fuhry.com>
Mon, 19 Oct 2026 19:35:53 +0000 (15:35 -0400)
committerDan Fuhry <dan@fuhry.com>
Mon, 19 Oct 2026 21:28:28 +0000 (17:28 -0400)
http/http.go
http/route_action_proxy.go

index 0025ff1c636559b0b46b45e4926841a4c5e64fc3..e147820da13362a6e99e980537177e57aeec45ed 100644 (file)
@@ -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 {
index a39a225f2cf81d5d39f583713e3d89085730bc96..190e9c211ffb9de981ee2e8feaecde7f0228b1fb 100644 (file)
@@ -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)
 }