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 }