diff --git a/modules/caddyhttp/reverseproxy/httptransport.go b/modules/caddyhttp/reverseproxy/httptransport.go index f52a0805..80a49806 100644 --- a/modules/caddyhttp/reverseproxy/httptransport.go +++ b/modules/caddyhttp/reverseproxy/httptransport.go @@ -31,6 +31,7 @@ import ( "time" "github.com/pires/go-proxyproto" + "github.com/quic-go/quic-go/http3" "go.uber.org/zap" "golang.org/x/net/http2" @@ -124,12 +125,18 @@ type HTTPTransport struct { // can be specified to use H2C (HTTP/2 over Cleartext) to the // upstream (this feature is experimental and subject to // change or removal). Default: ["1.1", "2"] + // + // EXPERIMENTAL: "3" enables HTTP/3, but it must be the only + // version specified if enabled. Additionally, HTTPS must be + // enabled to the upstream as HTTP/3 requires TLS. Subject + // to change or removal while experimental. Versions []string `json:"versions,omitempty"` // The pre-configured underlying HTTP transport. Transport *http.Transport `json:"-"` h2cTransport *http2.Transport + h3Transport *http3.RoundTripper // TODO: EXPERIMENTAL (May 2024) } // CaddyModule returns the Caddy module information. @@ -350,6 +357,16 @@ func (h *HTTPTransport) NewTransport(caddyCtx caddy.Context) (*http.Transport, e } } + // configure HTTP/3 transport if enabled; however, this does not + // automatically fall back to lower versions like most web browsers + // do (that'd add latency and complexity, besides, we expect that + // site owners control the backends), so it must be exclusive + if len(h.Versions) == 1 && h.Versions[0] == "3" { + h.h3Transport = new(http3.RoundTripper) + } else if len(h.Versions) > 1 && sliceContains(h.Versions, "3") { + return nil, fmt.Errorf("if HTTP/3 is enabled to the upstream, no other HTTP versions are supported") + } + // if h2c is enabled, configure its transport (std lib http.Transport // does not "HTTP/2 over cleartext TCP") if sliceContains(h.Versions, "h2c") { @@ -414,6 +431,11 @@ func (h *HTTPTransport) RoundTrip(req *http.Request) (*http.Response, error) { transport.SetScheme(req) + // use HTTP/3 if enabled (TODO: This is EXPERIMENTAL) + if h.h3Transport != nil { + return h.h3Transport.RoundTrip(req) + } + // if H2C ("HTTP/2 over cleartext") is enabled and the upstream request is // HTTP without TLS, use the alternate H2C-capable transport instead if req.URL.Scheme == "http" && h.h2cTransport != nil {