@marcel91200 / caddy Mirror · read-only / commits / a6da1acd
Fork 0

Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS

Mirrored from https://github.com/Slazee/caddy.git

Synced Nov 11, 18:30 UTC

reverse_proxy: use interfaces to modify the behaviors of the transports (#7353)

WeidiDeng committed Nov 18, 2025 at 00:51 UTC a6da1acdc86199a7f99fa2347d5f91cd59ff90d8
5 files changed +89 -30
modules/caddyhttp/reverseproxy/caddyfile.go
+5 -2
@@ -888,8 +888,11 @@ func (h *Handler) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
888 if commonScheme == "http" && te.TLSEnabled() {
889 return d.Errf("upstream address scheme is HTTP but transport is configured for HTTP+TLS (HTTPS)")
890 }
891 - if te, ok := transport.(*HTTPTransport); ok && commonScheme == "h2c" {
892 - te.Versions = []string{"h2c", "2"}
891 + if h2ct, ok := transport.(H2CTransport); ok && commonScheme == "h2c" {
892 + err := h2ct.EnableH2C()
893 + if err != nil {
894 + return err
895 + }
896 }
897 } else if commonScheme == "https" {
898 return d.Errf("upstreams are configured for HTTPS but transport module does not support TLS: %T", transport)
modules/caddyhttp/reverseproxy/fastcgi/fastcgi.go
+17 -2
@@ -112,6 +112,20 @@ func (t *Transport) Provision(ctx caddy.Context) error {
112 return nil
113 }
114
115 +// DefaultBufferSizes enables request buffering for fastcgi if not configured.
116 +// This is because most fastcgi servers are php-fpm that require the content length to be set to read the body, golang
117 +// std has fastcgi implementation that doesn't need this value to process the body, but we can safely assume that's
118 +// not used.
119 +// http3 requests have a negative content length for GET and HEAD requests, if that header is not sent.
120 +// see: https://github.com/caddyserver/caddy/issues/6678#issuecomment-2472224182
121 +// Though it appears even if CONTENT_LENGTH is invalid, php-fpm can handle just fine if the body is empty (no Stdin records sent).
122 +// php-fpm will hang if there is any data in the body though, https://github.com/caddyserver/caddy/issues/5420#issuecomment-2415943516
123 +
124 +// TODO: better default buffering for fastcgi requests without content length, in theory a value of 1 should be enough, make it bigger anyway
125 +func (t Transport) DefaultBufferSizes() (int64, int64) {
126 + return 4096, 0
127 +}
128 +
129 // RoundTrip implements http.RoundTripper.
130 func (t Transport) RoundTrip(r *http.Request) (*http.Response, error) {
131 server := r.Context().Value(caddyhttp.ServerCtxKey).(*caddyhttp.Server)
@@ -427,6 +441,7 @@ var headerNameReplacer = strings.NewReplacer(" ", "_", "-", "_")
441 var (
442 _ zapcore.ObjectMarshaler = (*loggableEnv)(nil)
443
430 - _ caddy.Provisioner = (*Transport)(nil)
431 - _ http.RoundTripper = (*Transport)(nil)
444 + _ caddy.Provisioner = (*Transport)(nil)
445 + _ http.RoundTripper = (*Transport)(nil)
446 + _ reverseproxy.BufferedTransport = (*Transport)(nil)
447 )
modules/caddyhttp/reverseproxy/healthchecks.go
+3 -9
@@ -23,7 +23,6 @@ import (
23 "net/url"
24 "regexp"
25 "runtime/debug"
26 - "slices"
26 "strconv"
27 "strings"
28 "time"
@@ -405,14 +404,9 @@ func (h *Handler) doActiveHealthCheck(dialInfo DialInfo, hostAddr string, networ
404 u.Host = net.JoinHostPort(host, port)
405 }
406
408 - // this is kind of a hacky way to know if we should use HTTPS, but whatever
409 - if tt, ok := h.Transport.(TLSTransport); ok && tt.TLSEnabled() {
410 - u.Scheme = "https"
411 -
412 - // if the port is in the except list, flip back to HTTP
413 - if ht, ok := h.Transport.(*HTTPTransport); ok && slices.Contains(ht.TLS.ExceptPorts, port) {
414 - u.Scheme = "http"
415 - }
407 + // override health check schemes if applicable
408 + if hcsot, ok := h.Transport.(HealthCheckSchemeOverriderTransport); ok {
409 + hcsot.OverrideHealthCheckScheme(u, port)
410 }
411
412 // if we have a provisioned uri, use that, otherwise use
modules/caddyhttp/reverseproxy/httptransport.go
+27 -4
@@ -564,6 +564,26 @@ func (h *HTTPTransport) EnableTLS(base *TLSConfig) error {
564 return nil
565 }
566
567 +// EnableH2C enables H2C (HTTP/2 over Cleartext) on the transport.
568 +func (h *HTTPTransport) EnableH2C() error {
569 + h.Versions = []string{"h2c", "2"}
570 + return nil
571 +}
572 +
573 +// OverrideHealthCheckScheme overrides the scheme of the given URL
574 +// used for health checks.
575 +func (h HTTPTransport) OverrideHealthCheckScheme(base *url.URL, port string) {
576 + // if tls is enabled and the port isn't in the except list, use HTTPs
577 + if h.TLSEnabled() && !slices.Contains(h.TLS.ExceptPorts, port) {
578 + base.Scheme = "https"
579 + }
580 +}
581 +
582 +// ProxyProtocolEnabled returns true if proxy protocol is enabled.
583 +func (h HTTPTransport) ProxyProtocolEnabled() bool {
584 + return h.ProxyProtocol != ""
585 +}
586 +
587 // Cleanup implements caddy.CleanerUpper and closes any idle connections.
588 func (h HTTPTransport) Cleanup() error {
589 if h.Transport == nil {
@@ -820,8 +840,11 @@ func decodeBase64DERCert(certStr string) (*x509.Certificate, error) {
840
841 // Interface guards
842 var (
823 - _ caddy.Provisioner = (*HTTPTransport)(nil)
824 - _ http.RoundTripper = (*HTTPTransport)(nil)
825 - _ caddy.CleanerUpper = (*HTTPTransport)(nil)
826 - _ TLSTransport = (*HTTPTransport)(nil)
843 + _ caddy.Provisioner = (*HTTPTransport)(nil)
844 + _ http.RoundTripper = (*HTTPTransport)(nil)
845 + _ caddy.CleanerUpper = (*HTTPTransport)(nil)
846 + _ TLSTransport = (*HTTPTransport)(nil)
847 + _ H2CTransport = (*HTTPTransport)(nil)
848 + _ HealthCheckSchemeOverriderTransport = (*HTTPTransport)(nil)
849 + _ ProxyProtocolTransport = (*HTTPTransport)(nil)
850 )
modules/caddyhttp/reverseproxy/reverseproxy.go
+37 -13
@@ -243,18 +243,16 @@ func (h *Handler) Provision(ctx caddy.Context) error {
243 return fmt.Errorf("loading transport: %v", err)
244 }
245 h.Transport = mod.(http.RoundTripper)
246 - // enable request buffering for fastcgi if not configured
247 - // This is because most fastcgi servers are php-fpm that require the content length to be set to read the body, golang
248 - // std has fastcgi implementation that doesn't need this value to process the body, but we can safely assume that's
249 - // not used.
250 - // http3 requests have a negative content length for GET and HEAD requests, if that header is not sent.
251 - // see: https://github.com/caddyserver/caddy/issues/6678#issuecomment-2472224182
252 - // Though it appears even if CONTENT_LENGTH is invalid, php-fpm can handle just fine if the body is empty (no Stdin records sent).
253 - // php-fpm will hang if there is any data in the body though, https://github.com/caddyserver/caddy/issues/5420#issuecomment-2415943516
254 -
255 - // TODO: better default buffering for fastcgi requests without content length, in theory a value of 1 should be enough, make it bigger anyway
256 - if module, ok := h.Transport.(caddy.Module); ok && module.CaddyModule().ID.Name() == "fastcgi" && h.RequestBuffers == 0 {
257 - h.RequestBuffers = 4096
246 +
247 + // set default buffer sizes if applicable
248 + if bt, ok := h.Transport.(BufferedTransport); ok {
249 + reqBuffers, respBuffers := bt.DefaultBufferSizes()
250 + if h.RequestBuffers == 0 {
251 + h.RequestBuffers = reqBuffers
252 + }
253 + if h.ResponseBuffers == 0 {
254 + h.ResponseBuffers = respBuffers
255 + }
256 }
257 }
258 if h.LoadBalancing != nil && h.LoadBalancing.SelectionPolicyRaw != nil {
@@ -1210,7 +1208,7 @@ func (h *Handler) directRequest(req *http.Request, di DialInfo) {
1208 }
1209
1210 // add client address to the host to let transport differentiate requests from different clients
1213 - if ht, ok := h.Transport.(*HTTPTransport); ok && ht.ProxyProtocol != "" {
1211 + if ppt, ok := h.Transport.(ProxyProtocolTransport); ok && ppt.ProxyProtocolEnabled() {
1212 if proxyProtocolInfo, ok := caddyhttp.GetVar(req.Context(), proxyProtocolInfoVarKey).(ProxyProtocolInfo); ok {
1213 reqHost = proxyProtocolInfo.AddrPort.String() + "->" + reqHost
1214 }
@@ -1501,6 +1499,32 @@ type TLSTransport interface {
1499 EnableTLS(base *TLSConfig) error
1500 }
1501
1502 +// H2CTransport is implemented by transports
1503 +// that are capable of using h2c.
1504 +type H2CTransport interface {
1505 + EnableH2C() error
1506 +}
1507 +
1508 +// ProxyProtocolTransport is implemented by transports
1509 +// that are capable of using proxy protocol.
1510 +type ProxyProtocolTransport interface {
1511 + ProxyProtocolEnabled() bool
1512 +}
1513 +
1514 +// HealthCheckSchemeOverriderTransport is implemented by transports
1515 +// that can override the scheme used for health checks.
1516 +type HealthCheckSchemeOverriderTransport interface {
1517 + OverrideHealthCheckScheme(base *url.URL, port string)
1518 +}
1519 +
1520 +// BufferedTransport is implemented by transports
1521 +// that needs to buffer requests and/or responses.
1522 +type BufferedTransport interface {
1523 + // DefaultBufferSizes returns the default buffer sizes
1524 + // for requests and responses, respectively if buffering isn't enabled.
1525 + DefaultBufferSizes() (int64, int64)
1526 +}
1527 +
1528 // roundtripSucceededError is an error type that is returned if the
1529 // roundtrip succeeded, but an error occurred after-the-fact.
1530 type roundtripSucceededError struct{ error }