mirror of
https://github.com/caddyserver/caddy.git
synced 2024-12-27 22:23:48 +03:00
httpserver: Add experimental H2C support (#3289)
* reverse_proxy: Initial attempt at H2C transport/client support (#3218) I have not tested this yet * Experimentally enabling H2C server support (closes #3227) See also #3218 I have not tested this * reverseproxy: Clean up H2C transport a bit * caddyhttp: Update godoc for h2c server; clarify experimental status * caddyhttp: Fix trailers when recording responses (fixes #3236) * caddyhttp: Tweak h2c config settings and docs
This commit is contained in:
parent
96d6d277a4
commit
41c7bd27b4
4 changed files with 63 additions and 1 deletions
|
@ -27,6 +27,8 @@ import (
|
||||||
"github.com/caddyserver/caddy/v2/modules/caddytls"
|
"github.com/caddyserver/caddy/v2/modules/caddytls"
|
||||||
"github.com/lucas-clemente/quic-go/http3"
|
"github.com/lucas-clemente/quic-go/http3"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
"golang.org/x/net/http2"
|
||||||
|
"golang.org/x/net/http2/h2c"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
@ -282,6 +284,14 @@ func (app *App) Start() error {
|
||||||
Handler: srv,
|
Handler: srv,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// enable h2c if configured
|
||||||
|
if srv.AllowH2C {
|
||||||
|
h2server := &http2.Server{
|
||||||
|
IdleTimeout: time.Duration(srv.IdleTimeout),
|
||||||
|
}
|
||||||
|
s.Handler = h2c.NewHandler(srv, h2server)
|
||||||
|
}
|
||||||
|
|
||||||
for _, lnAddr := range srv.Listen {
|
for _, lnAddr := range srv.Listen {
|
||||||
listenAddr, err := caddy.ParseNetworkAddress(lnAddr)
|
listenAddr, err := caddy.ParseNetworkAddress(lnAddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -584,6 +584,7 @@ func (h *Handler) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
||||||
// tls_trusted_ca_certs <cert_files...>
|
// tls_trusted_ca_certs <cert_files...>
|
||||||
// keepalive [off|<duration>]
|
// keepalive [off|<duration>]
|
||||||
// keepalive_idle_conns <max_count>
|
// keepalive_idle_conns <max_count>
|
||||||
|
// versions <versions...>
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
func (h *HTTPTransport) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
func (h *HTTPTransport) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
||||||
|
@ -701,6 +702,12 @@ func (h *HTTPTransport) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
||||||
h.KeepAlive.MaxIdleConns = num
|
h.KeepAlive.MaxIdleConns = num
|
||||||
h.KeepAlive.MaxIdleConnsPerHost = num
|
h.KeepAlive.MaxIdleConnsPerHost = num
|
||||||
|
|
||||||
|
case "versions":
|
||||||
|
h.Versions = d.RemainingArgs()
|
||||||
|
if len(h.Versions) == 0 {
|
||||||
|
return d.ArgErr()
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return d.Errf("unrecognized subdirective %s", d.Val())
|
return d.Errf("unrecognized subdirective %s", d.Val())
|
||||||
}
|
}
|
||||||
|
|
|
@ -82,11 +82,16 @@ type HTTPTransport struct {
|
||||||
// The size of the read buffer in bytes.
|
// The size of the read buffer in bytes.
|
||||||
ReadBufferSize int `json:"read_buffer_size,omitempty"`
|
ReadBufferSize int `json:"read_buffer_size,omitempty"`
|
||||||
|
|
||||||
// The versions of HTTP to support. Default: ["1.1", "2"]
|
// The versions of HTTP to support. As a special case, "h2c"
|
||||||
|
// 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"]
|
||||||
Versions []string `json:"versions,omitempty"`
|
Versions []string `json:"versions,omitempty"`
|
||||||
|
|
||||||
// The pre-configured underlying HTTP transport.
|
// The pre-configured underlying HTTP transport.
|
||||||
Transport *http.Transport `json:"-"`
|
Transport *http.Transport `json:"-"`
|
||||||
|
|
||||||
|
h2cTransport *http2.Transport
|
||||||
}
|
}
|
||||||
|
|
||||||
// CaddyModule returns the Caddy module information.
|
// CaddyModule returns the Caddy module information.
|
||||||
|
@ -110,6 +115,28 @@ func (h *HTTPTransport) Provision(ctx caddy.Context) error {
|
||||||
}
|
}
|
||||||
h.Transport = rt
|
h.Transport = rt
|
||||||
|
|
||||||
|
// if h2c is enabled, configure its transport (std lib http.Transport
|
||||||
|
// does not "HTTP/2 over cleartext TCP")
|
||||||
|
if sliceContains(h.Versions, "h2c") {
|
||||||
|
// crafting our own http2.Transport doesn't allow us to utilize
|
||||||
|
// most of the customizations/preferences on the http.Transport,
|
||||||
|
// because, for some reason, only http2.ConfigureTransport()
|
||||||
|
// is allowed to set the unexported field that refers to a base
|
||||||
|
// http.Transport config; oh well
|
||||||
|
h2t := &http2.Transport{
|
||||||
|
// kind of a hack, but for plaintext/H2C requests, pretend to dial TLS
|
||||||
|
DialTLS: func(network, addr string, _ *tls.Config) (net.Conn, error) {
|
||||||
|
// TODO: no context, thus potentially wrong dial info
|
||||||
|
return net.Dial(network, addr)
|
||||||
|
},
|
||||||
|
AllowHTTP: true,
|
||||||
|
}
|
||||||
|
if h.Compression != nil {
|
||||||
|
h2t.DisableCompression = !*h.Compression
|
||||||
|
}
|
||||||
|
h.h2cTransport = h2t
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -182,6 +209,13 @@ func (h *HTTPTransport) NewTransport(_ caddy.Context) (*http.Transport, error) {
|
||||||
// RoundTrip implements http.RoundTripper.
|
// RoundTrip implements http.RoundTripper.
|
||||||
func (h *HTTPTransport) RoundTrip(req *http.Request) (*http.Response, error) {
|
func (h *HTTPTransport) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||||
h.SetScheme(req)
|
h.SetScheme(req)
|
||||||
|
|
||||||
|
// if H2C ("HTTP/2 over cleartext") is enabled and the upstream request is
|
||||||
|
// HTTP/2 without TLS, use the alternate H2C-capable transport instead
|
||||||
|
if req.ProtoMajor == 2 && req.URL.Scheme == "http" && h.h2cTransport != nil {
|
||||||
|
return h.h2cTransport.RoundTrip(req)
|
||||||
|
}
|
||||||
|
|
||||||
return h.Transport.RoundTrip(req)
|
return h.Transport.RoundTrip(req)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -111,6 +111,17 @@ type Server struct {
|
||||||
// This field is not subject to compatibility promises.
|
// This field is not subject to compatibility promises.
|
||||||
ExperimentalHTTP3 bool `json:"experimental_http3,omitempty"`
|
ExperimentalHTTP3 bool `json:"experimental_http3,omitempty"`
|
||||||
|
|
||||||
|
// Enables H2C ("Cleartext HTTP/2" or "H2 over TCP") support,
|
||||||
|
// which will serve HTTP/2 over plaintext TCP connections if
|
||||||
|
// a client support it. Because this is not implemented by the
|
||||||
|
// Go standard library, using H2C is incompatible with most
|
||||||
|
// of the other options for this server. Do not enable this
|
||||||
|
// only to achieve maximum client compatibility. In practice,
|
||||||
|
// very few clients implement H2C, and even fewer require it.
|
||||||
|
// This setting applies only to unencrypted HTTP listeners.
|
||||||
|
// ⚠️ Experimental feature; subject to change or removal.
|
||||||
|
AllowH2C bool `json:"allow_h2c,omitempty"`
|
||||||
|
|
||||||
primaryHandlerChain Handler
|
primaryHandlerChain Handler
|
||||||
errorHandlerChain Handler
|
errorHandlerChain Handler
|
||||||
listenerWrappers []caddy.ListenerWrapper
|
listenerWrappers []caddy.ListenerWrapper
|
||||||
|
|
Loading…
Reference in a new issue