@marcel91200 / caddy Mirror · read-only / commits / 221ebc45
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, 03:30 UTC

fix: preserve FastCGI dial errors for retry (#8172)

Wrap backend dial failures as reverseproxy.DialError so non-GET requests can be retried when no connection was made. AI-assisted implementation and test; reviewed by zemamba. Signed-off-by: zemamba <zemamba@gmail.com>

zemamba committed Oct 8, 2026 at 07:02 UTC 221ebc4503398fa8c87d95a52d43d0d93a6aeef4
3 files changed +23 -1
modules/caddyhttp/reverseproxy/fastcgi/fastcgi.go
+1 -1
@@ -213,7 +213,7 @@ func (t Transport) RoundTrip(r *http.Request) (*http.Response, error) {
213 dialer := net.Dialer{Timeout: time.Duration(t.DialTimeout)}
214 conn, err := dialer.DialContext(ctx, network, address)
215 if err != nil {
216 - return nil, fmt.Errorf("dialing backend: %v", err)
216 + return nil, reverseproxy.NewDialError(fmt.Errorf("dialing backend: %w", err))
217 }
218 defer func() {
219 // conn will be closed with the response body unless there's an error
modules/caddyhttp/reverseproxy/fastcgi/incremental_test.go
+17
@@ -13,6 +13,7 @@ import (
13
14 "github.com/caddyserver/caddy/v2"
15 "github.com/caddyserver/caddy/v2/modules/caddyhttp"
16 + "github.com/caddyserver/caddy/v2/modules/caddyhttp/reverseproxy"
17 )
18
19 // fcgiBackend serves h over FastCGI and returns its address.
@@ -121,6 +122,22 @@ func TestRequiresContentLength(t *testing.T) {
122 }
123 }
124
125 +func TestRoundTripDialError(t *testing.T) {
126 + req := httptest.NewRequest(http.MethodPost, "http://example.com/index.php", strings.NewReader("body"))
127 + _, err := fcgiRoundTrip(t, "invalid address", req)
128 + if err == nil {
129 + t.Fatal("RoundTrip() error = nil, want a dial error")
130 + }
131 +
132 + var dialErr reverseproxy.DialError
133 + if !errors.As(err, &dialErr) {
134 + t.Fatalf("RoundTrip() error type = %T, want reverseproxy.DialError", err)
135 + }
136 + if !strings.HasPrefix(err.Error(), "dialing backend:") {
137 + t.Errorf("RoundTrip() error = %q, want dialing context", err)
138 + }
139 +}
140 +
141 // The request buffer fastcgi asks for by default is what supplies the
142 // CONTENT_LENGTH above, so these values matter beyond this package.
143 func TestDefaultBufferSizes(t *testing.T) {
modules/caddyhttp/reverseproxy/reverseproxy.go
+5
@@ -1925,6 +1925,11 @@ var hopHeaders = []string{
1925 // in a call to Dial or DialContext.
1926 type DialError struct{ error }
1927
1928 +// NewDialError wraps err as a DialError for transports outside this package.
1929 +func NewDialError(err error) DialError {
1930 + return DialError{err}
1931 +}
1932 +
1933 // TLSTransport is implemented by transports
1934 // that are capable of using TLS.
1935 type TLSTransport interface {