Add support for unix socket as upstream (#1866)

* Add support for unix socket as upstream

* Add CHANGELOG.md entry

* Add Unix socket documentation

* Don't export unixRoundTripper, switch from string prefix to Scheme match

* Add basic unix server mock

* Add some tests and comments
This commit is contained in:
Damien Degois
2023-10-26 10:57:00 +01:00
committed by GitHub
parent 4816e87316
commit 70571d96e1
9 changed files with 108 additions and 9 deletions
+34 -1
View File
@@ -1,6 +1,8 @@
package upstream
import (
"context"
"net"
"net/http"
"net/http/httputil"
"net/url"
@@ -18,6 +20,7 @@ const (
httpScheme = "http"
httpsScheme = "https"
unixScheme = "unix"
)
// SignatureHeaders contains the headers to be signed by the hmac algorithm
@@ -40,7 +43,10 @@ var SignatureHeaders = []string{
// to a single upstream host.
func newHTTPUpstreamProxy(upstream options.Upstream, u *url.URL, sigData *options.SignatureData, errorHandler ProxyErrorHandler) http.Handler {
// Set path to empty so that request paths start at the server root
u.Path = ""
// Unix scheme need the path to find the socket
if u.Scheme != "unix" {
u.Path = ""
}
// Create a ReverseProxy
proxy := newReverseProxy(u, upstream, errorHandler)
@@ -92,6 +98,25 @@ func (h *httpUpstreamProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request)
}
}
// Unix implementation of http.RoundTripper, required to register unix protocol in reverse proxy
type unixRoundTripper struct {
Transport *http.Transport
}
// Implementation of https://pkg.go.dev/net/http#RoundTripper interface to support http protocol over unix socket
func (t *unixRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
// Inspired by https://github.com/tv42/httpunix
// Not having a Host, even if not used, makes the reverseproxy fail with a "no Host in request URL"
if req.Host == "" {
req.Host = "localhost"
}
req.URL.Host = req.Host
tt := t.Transport
req = req.Clone(req.Context())
req.URL.Scheme = "http"
return tt.RoundTrip(req)
}
// newReverseProxy creates a new reverse proxy for proxying requests to upstream
// servers based on the upstream configuration provided.
// The proxy should render an error page if there are failures connecting to the
@@ -102,6 +127,14 @@ func newReverseProxy(target *url.URL, upstream options.Upstream, errorHandler Pr
// Inherit default transport options from Go's stdlib
transport := http.DefaultTransport.(*http.Transport).Clone()
if target.Scheme == "unix" {
transport.DialContext = func(ctx context.Context, _, _ string) (net.Conn, error) {
dialer := net.Dialer{}
return dialer.DialContext(ctx, target.Scheme, target.Path)
}
transport.RegisterProtocol(target.Scheme, &unixRoundTripper{Transport: transport})
}
// Change default duration for waiting for an upstream response
if upstream.Timeout != nil {
transport.ResponseHeaderTimeout = upstream.Timeout.Duration()