MCPcopy
hub / github.com/caddyserver/caddy / TestDialErrorBodyRetry

Function TestDialErrorBodyRetry

modules/caddyhttp/reverseproxy/retries_test.go:140–260  ·  view source on GitHub ↗

TestDialErrorBodyRetry verifies that a POST request whose body has NOT been pre-buffered via request_buffers can still be retried after a dial error. Before the fix, a dial error caused Go's transport to close the shared body (via cloneRequest's shallow copy), so the retry attempt would read from a

(t *testing.T)

Source from the content-addressed store, hash-verified

138// shared body. Since dial errors never read any bytes, the body remains at
139// position 0 for the retry.
140func TestDialErrorBodyRetry(t *testing.T) {
141 // Good upstream: echoes the request body with 200 OK.
142 goodServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
143 body, err := io.ReadAll(r.Body)
144 if err != nil {
145 http.Error(w, "read body: "+err.Error(), http.StatusInternalServerError)
146 return
147 }
148 w.WriteHeader(http.StatusOK)
149 _, _ = w.Write(body)
150 }))
151 t.Cleanup(goodServer.Close)
152
153 const requestBody = "hello, retry"
154
155 tests := []struct {
156 name string
157 method string
158 body string
159 retries int
160 wantStatus int
161 wantBody string
162 }{
163 {
164 // Core regression case: POST with a body, no request_buffers,
165 // dial error on first upstream → retry to second upstream succeeds.
166 name: "POST body retried after dial error",
167 method: http.MethodPost,
168 body: requestBody,
169 retries: 1,
170 wantStatus: http.StatusOK,
171 wantBody: requestBody,
172 },
173 {
174 // Dial errors are always retried regardless of method, but there
175 // is no body to re-read, so GET has always worked. Keep it as a
176 // sanity check that we did not break the no-body path.
177 name: "GET without body retried after dial error",
178 method: http.MethodGet,
179 body: "",
180 retries: 1,
181 wantStatus: http.StatusOK,
182 wantBody: "",
183 },
184 {
185 // Without any retry configuration the handler must give up on the
186 // first dial error and return a 502. Confirms no wrapping occurs
187 // in the no-retry path.
188 name: "no retries configured returns 502 on dial error",
189 method: http.MethodPost,
190 body: requestBody,
191 retries: 0,
192 wantStatus: http.StatusBadGateway,
193 wantBody: "",
194 },
195 }
196
197 for _, tc := range tests {

Callers

nothing calls this directly

Calls 12

HandlerFuncFuncType · 0.92
deadUpstreamAddrFunction · 0.85
minimalHandlerFunction · 0.85
newCloseOnCloseReaderFunction · 0.85
prepareTestRequestFunction · 0.85
AddrMethod · 0.80
CleanupMethod · 0.65
ServeHTTPMethod · 0.65
ErrorMethod · 0.45
WriteHeaderMethod · 0.45
WriteMethod · 0.45
StringMethod · 0.45

Tested by

no test coverage detected