@@ -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