Skip to content

Commit a5e7694

Browse files
committed
New cleaner approach covering more scenarios
1 parent 067a464 commit a5e7694

1 file changed

Lines changed: 56 additions & 20 deletions

File tree

httputilx/httputilx.go

Lines changed: 56 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -232,27 +232,10 @@ func DoExponentialBackoff(req *http.Request, options ...ExponentialBackoffOption
232232

233233
backoff := o.initialBackoff
234234

235-
// Read the request body once if it exists, so we can replay it on retries.
236-
// This is necessary because some body types (like manually wrapped
237-
// io.NopCloser) don't have GetBody set, causing "ContentLength=N with Body
238-
// length 0" errors when req.Clone() tries to reuse the exhausted body.
239-
var bodyBytes []byte
240-
if req.Body != nil {
241-
var err error
242-
bodyBytes, err = io.ReadAll(req.Body)
243-
if err != nil {
244-
return nil, errors.Wrap(err, "failed to read request body")
245-
}
246-
if err := req.Body.Close(); err != nil {
247-
return nil, errors.Wrap(err, "failed to close request body")
248-
}
249-
}
250-
251235
for attempt := 0; attempt <= o.maxRetries; attempt++ {
252-
reqClone := req.Clone(req.Context())
253-
if bodyBytes != nil {
254-
reqClone.Body = io.NopCloser(bytes.NewReader(bodyBytes))
255-
reqClone.ContentLength = int64(len(bodyBytes))
236+
reqClone, err := cloneWithBody(req)
237+
if err != nil {
238+
return nil, errors.Wrap(err, "failed to clone request with body")
256239
}
257240

258241
resp, err := o.client.Do(reqClone)
@@ -284,3 +267,56 @@ func DoExponentialBackoff(req *http.Request, options ...ExponentialBackoffOption
284267

285268
return nil, fmt.Errorf("request failed after %d attempts", o.maxRetries+1)
286269
}
270+
271+
func cloneWithBody(req *http.Request) (*http.Request, error) {
272+
newReq := req.Clone(req.Context())
273+
if req.Body == nil {
274+
return newReq, nil
275+
}
276+
if req.GetBody != nil {
277+
var err error
278+
newReq.Body, err = req.GetBody()
279+
if err != nil {
280+
return nil, err
281+
}
282+
return newReq, nil
283+
}
284+
285+
if seeker, ok := req.Body.(io.Seeker); ok {
286+
if _, err := seeker.Seek(0, io.SeekStart); err != nil {
287+
return nil, err
288+
}
289+
newReq.Body = req.Body
290+
newReq.GetBody = func() (io.ReadCloser, error) {
291+
if _, err := seeker.Seek(0, io.SeekStart); err != nil {
292+
return nil, err
293+
}
294+
return req.Body, nil
295+
}
296+
return newReq, nil
297+
}
298+
299+
bodyBytes, err := io.ReadAll(req.Body)
300+
if err != nil {
301+
return nil, err
302+
}
303+
if err := req.Body.Close(); err != nil {
304+
return nil, err
305+
}
306+
307+
createBody := func(bodyBytes []byte) func() (io.ReadCloser, error) {
308+
return func() (io.ReadCloser, error) {
309+
return io.NopCloser(bytes.NewReader(bodyBytes)), nil
310+
}
311+
}
312+
313+
req.GetBody = createBody(bodyBytes)
314+
req.Body, _ = req.GetBody()
315+
req.ContentLength = int64(len(bodyBytes))
316+
317+
newReq.GetBody = createBody(bodyBytes)
318+
newReq.Body, _ = newReq.GetBody()
319+
newReq.ContentLength = int64(len(bodyBytes))
320+
321+
return newReq, nil
322+
}

0 commit comments

Comments
 (0)