Repository: go-resty/resty (go)

Issue:
Bug report: Request.bodyBuf resused by mistake
# Summary
Resty [saved the request body's snapshot](https://github.com/go-resty/resty/blob/master/middleware.go#L521) for resending request with original HTTP method and body when http redirect such as 307 or 308 returned.
Then in [http.Client.do](https://github.com/golang/go/blob/master/src/net/http/client.go#L661), golang call the GetBody func registered by resty [createHTTPRequset](https://github.com/go-resty/resty/blob/master/middleware.go#L215) func for reset the request body. 

But the [code](https://github.com/go-resty/resty/blob/master/middleware.go#L521) is not the body copy.
```
func getBodyCopy(r *Request) (*bytes.Buffer, error) {
	// If r.bodyBuf present, return the copy
	if r.bodyBuf != nil {
		return bytes.NewBuffer(r.bodyBuf.Bytes()), nil
	}
```
bodyCopy and Request.bodyBuf shared the same bytes slice in go. Once HTTP redirect happened, request body(requestBodyReleaser) was closed and released by go in [http client](https://github.com/golang/go/blob/master/src/net/http/client.go#L733). The buffer return to bufPool and may be reused by other requests. If other requests rewrite this buffer, the origin bodyCopy is overwrited also and then the redirected request body passed wrong body mismatch with the original HTTP request.

# Reproduce

```
package main

import (
	"fmt"
	"io/ioutil"
	"math/rand"
	"net/http"
	"sync"
	"time"

	"github.com/go-resty/resty/v2"
)

var data = []byte("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")

func main() {
	client := resty.New()
	server := http.NewServeMux()
	server.HandleFunc("/test", func(writer http.ResponseWriter, request *http.Request) {
		writer.Header().Set("Location", "http://localhost:9999/redirect")
		writer.WriteHeader(http.StatusTemporaryRedirect)
		writer.Write(nil)
	})
	server.HandleFunc("/redirect", func(writer http.ResponseWriter, request *http.Request) {
		body, err := ioutil.ReadAll(request.Body)
		if err != nil {
			panic(err)
		}
		key := request.Header.Get("key")
		if key != string(body) {
			fmt.Println("mismatch key and body", key, string(body))
		}
		writer.WriteHeader(http.StatusOK)
	})

	go func() {
		err := http.ListenAndServe("localhost:9999", server)
		if err != nil {
			panic(err)
		}
	}()

	wg := sync.WaitGroup{}
	for i := 0; i < 2; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			post(client)
		}()
	}
	wg.Wait()
}

func post(client *resty.Client) {
	for {
		key := RandStr(64)
		client.NewRequest().SetHeader("key", key).SetBody([]byte(key)).Post("http://localhost:9999/test")
		time.Sleep(10 * time.Millisecond)
	}
}

func RandStr(length int) string {
	result := []byte{}
	randCli := rand.New(rand.NewSource(time.Now().UnixNano()))
	for i := 0; i < length; i++ {
		result = append(result, data[randCli.Intn(len(data))])
	}
	return string(result)
}
```
Expected key's value in header is always same as body, but appear mismatch by run the program above.

<img width="1295" alt="image" src="https://user-images.githubusercontent.com/13741813/181008406-04442615-dd03-4c59-a2cb-4af8fc29f5eb.png">


Pull request:
Fix redirect request body mismatch with origin request body caused by buffer reused by mistake
As issue #567 described.

Copy the body buffer by buffer.Write to fix body mismatch with origin request when 307 or 308 redirect.

closes #567

Patch:
diff --git a/client.go b/client.go
index f8823e8..326a4a5 100644
--- a/client.go
+++ b/client.go
@@ -116,6 +116,7 @@ type Client struct {
 	RetryConditions       []RetryConditionFunc
 	RetryHooks            []OnRetryFunc
 	RetryAfter            RetryAfterFunc
+	RetryResetReaders     bool
 	JSONMarshal           func(v interface{}) ([]byte, error)
 	JSONUnmarshal         func(data []byte, v interface{}) error
 	XMLMarshal            func(v interface{}) ([]byte, error)
@@ -702,6 +703,15 @@ func (c *Client) AddRetryHook(hook OnRetryFunc) *Client {
 	return c
 }
 
+// SetRetryResetReaders method enables the Resty client to seek the start of all
+// file readers given as multipart files, if the given object implements `io.ReadSeeker`.
+//
+// Since ...
+func (c *Client) SetRetryResetReaders(b bool) *Client {
+	c.RetryResetReaders = b
+	return c
+}
+
 // SetTLSClientConfig method sets TLSClientConfig for underling client Transport.
 //
 // For Example:
diff --git a/go.mod b/go.mod
index 5e78bdc..d712b6e 100644
--- a/go.mod
+++ b/go.mod
@@ -1,5 +1,5 @@
 module github.com/go-resty/resty/v2
 
-require golang.org/x/net v0.0.0-20211029224645-99673261e6eb
+require golang.org/x/net v0.0.0-20220325170049-de3da57026de
 
 go 1.11
diff --git a/go.sum b/go.sum
index 07a4515..7b4eb43 100644
--- a/go.sum
+++ b/go.sum
@@ -1,7 +1,7 @@
-golang.org/x/net v0.0.0-20211029224645-99673261e6eb h1:pirldcYWx7rx7kE5r+9WsOXPXK0+WH5+uZ7uPmJ44uM=
-golang.org/x/net v0.0.0-20211029224645-99673261e6eb/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
-golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
-golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/net v0.0.0-20220325170049-de3da57026de h1:pZB1TWnKi+o4bENlbzAgLrEbY4RMYmUIRobMcSmfeYc=
+golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
+golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
+golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
diff --git a/middleware.go b/middleware.go
index 2e311d9..4c5cb3c 100644
--- a/middleware.go
+++ b/middleware.go
@@ -514,7 +514,12 @@ func saveResponseIntoFile(c *Client, res *Response) error {
 func getBodyCopy(r *Request) (*bytes.Buffer, error) {
 	// If r.bodyBuf present, return the copy
 	if r.bodyBuf != nil {
-		return bytes.NewBuffer(r.bodyBuf.Bytes()), nil
+		bodyCopy := acquireBuffer()
+		if _, err := io.Copy(bodyCopy, bytes.NewReader(r.bodyBuf.Bytes())); err != nil {
+			// cannot use io.Copy(bodyCopy, r.bodyBuf) because io.Copy reset r.bodyBuf
+			return nil, err
+		}
+		return bodyCopy, nil
 	}
 
 	// Maybe body is `io.Reader`.
diff --git a/request.go b/request.go
index 25b4c3b..adb129e 100644
--- a/request.go
+++ b/request.go
@@ -847,6 +847,7 @@ func (r *Request) Execute(method, url string) (*Response, error) {
 		MaxWaitTime(r.client.RetryMaxWaitTime),
 		RetryConditions(append(r.retryConditions, r.client.RetryConditions...)),
 		RetryHooks(r.client.RetryHooks),
+		ResetMultipartReaders(r.client.RetryResetReaders),
 	)
 
 	r.client.onErrorHooks(r, resp, unwrapNoRetryErr(err))
diff --git a/retry.go b/retry.go
index 00b8514..f8c7665 100644
--- a/retry.go
+++ b/retry.go
@@ -6,6 +6,7 @@ package resty
 
 import (
 	"context"
+	"io"
 	"math"
 	"math/rand"
 	"sync"
@@ -43,6 +44,7 @@ type (
 		maxWaitTime     time.Duration
 		retryConditions []RetryConditionFunc
 		retryHooks      []OnRetryFunc
+		resetReaders    bool
 	}
 )
 
@@ -81,6 +83,14 @@ func RetryHooks(hooks []OnRetryFunc) Option {
 	}
 }
 
+// ResetMultipartReaders sets a boolean value which will lead the start being seeked out
+// on all multipart file readers, if they implement io.ReadSeeker
+func ResetMultipartReaders(value bool) Option {
+	return func(o *Options) {
+		o.resetReaders = value
+	}
+}
+
 // Backoff retries with increasing timeout duration up until X amount of retries
 // (Default is 3 attempts, Override with option Retries(n))
 func Backoff(operation func() (*Response, error), options ...Option) error {
@@ -125,6 +135,12 @@ func Backoff(operation func() (*Response, error), options ...Option) error {
 			return err
 		}
 
+		if opts.resetReaders {
+			if err := resetFileReaders(resp.Request.multipartFiles); err != nil {
+				return err
+			}
+		}
+
 		for _, hook := range opts.retryHooks {
 			hook(resp, err)
 		}
@@ -219,3 +235,15 @@ func newRnd() *rand.Rand {
 	var src = rand.NewSource(seed)
 	return rand.New(src)
 }
+
+func resetFileReaders(files []*File) error {
+	for _, f := range files {
+		if rs, ok := f.Reader.(io.ReadSeeker); ok {
+			if _, err := rs.Seek(0, io.SeekStart); err != nil {
+				return err
+			}
+		}
+	}
+
+	return nil
+}


Test patch:
diff --git a/client_test.go b/client_test.go
index 1e61a22..b640747 100644
--- a/client_test.go
+++ b/client_test.go
@@ -17,6 +17,7 @@ import (
 	"reflect"
 	"strconv"
 	"strings"
+	"sync"
 	"testing"
 	"time"
 )
@@ -858,3 +859,27 @@ func TestHostURLForGH318AndGH407(t *testing.T) {
 	assertNil(t, err)
 	assertNotNil(t, resp)
 }
+
+func TestPostRedirectWithBody(t *testing.T) {
+	ts := createPostServer(t)
+	defer ts.Close()
+
+	targetURL, _ := url.Parse(ts.URL)
+	t.Log("ts.URL:", ts.URL)
+	t.Log("targetURL.Host:", targetURL.Host)
+
+	c := dc()
+	wg := sync.WaitGroup{}
+	for i := 0; i < 100; i++ {
+		wg.Add(1)
+		go func() {
+			defer wg.Done()
+			resp, err := c.R().
+				SetBody([]byte(strconv.Itoa(newRnd().Int()))).
+				Post(targetURL.String() + "/redirect-with-body")
+			assertError(t, err)
+			assertNotNil(t, resp)
+		}()
+	}
+	wg.Wait()
+}
diff --git a/resty_test.go b/resty_test.go
index b815418..3671892 100644
--- a/resty_test.go
+++ b/resty_test.go
@@ -14,6 +14,7 @@ import (
 	"io/ioutil"
 	"net/http"
 	"net/http/httptest"
+	"net/url"
 	"os"
 	"path/filepath"
 	"reflect"
@@ -240,15 +241,13 @@ func createPostServer(t *testing.T) *httptest.Server {
 			handleLoginEndpoint(t, w, r)
 
 			handleUsersEndpoint(t, w, r)
-
-			if r.URL.Path == "/login-json-html" {
+			switch r.URL.Path {
+			case "/login-json-html":
 				w.Header().Set(hdrContentTypeKey, "text/html")
 				w.WriteHeader(http.StatusOK)
 				_, _ = w.Write([]byte(`<htm><body>Test JSON request with HTML response</body></html>`))
 				return
-			}
-
-			if r.URL.Path == "/usersmap" {
+			case "/usersmap":
 				// JSON
 				if IsJSONType(r.Header.Get(hdrContentTypeKey)) {
 					if r.URL.Query().Get("status") == "500" {
@@ -287,9 +286,19 @@ func createPostServer(t *testing.T) *httptest.Server {
 
 					return
 				}
-			} else if r.URL.Path == "/redirect" {
+			case "/redirect":
 				w.Header().Set(hdrLocationKey, "/login")
 				w.WriteHeader(http.StatusTemporaryRedirect)
+			case "/redirect-with-body":
+				body, _ := ioutil.ReadAll(r.Body)
+				query := url.Values{}
+				query.Add("body", string(body))
+				w.Header().Set(hdrLocationKey, "/redirected-with-body?"+query.Encode())
+				w.WriteHeader(http.StatusTemporaryRedirect)
+			case "/redirected-with-body":
+				body, _ := ioutil.ReadAll(r.Body)
+				assertEqual(t, r.URL.Query().Get("body"), string(body))
+				w.WriteHeader(http.StatusOK)
 			}
 		}
 	})
@@ -441,6 +450,10 @@ func createFilePostServer(t *testing.T) *httptest.Server {
 			size, _ := io.Copy(f, r.Body)
 
 			fmt.Fprintf(w, "File Uploaded successfully, file size: %v", size)
+		case "/set-reset-multipart-readers-test":
+			w.Header().Set(hdrContentTypeKey, "application/json; charset=utf-8")
+			w.WriteHeader(http.StatusInternalServerError)
+			_, _ = fmt.Fprintf(w, `{ "message": "error" }`)
 		}
 	})
 
diff --git a/retry_test.go b/retry_test.go
index 9f8fb38..d979fbf 100644
--- a/retry_test.go
+++ b/retry_test.go
@@ -5,9 +5,12 @@
 package resty
 
 import (
+	"bytes"
 	"context"
 	"encoding/json"
 	"errors"
+	"fmt"
+	"io"
 	"net/http"
 	"reflect"
 	"strconv"
@@ -55,7 +58,7 @@ func TestBackoffNoWaitForLastRetry(t *testing.T) {
 		externalCounter++
 		return resp, nil
 	}, RetryConditions([]RetryConditionFunc{func(response *Response, err error) bool {
-		if externalCounter == attempts + numRetries {
+		if externalCounter == attempts+numRetries {
 			// Backoff returns context canceled if goes to sleep after last retry.
 			cancel()
 		}
@@ -733,3 +736,76 @@ func TestClientRetryHook(t *testing.T) {
 func filler(*Response, error) bool {
 	return false
 }
+
+var seekFailure = fmt.Errorf("failing seek test")
+
+type failingSeeker struct {
+	reader *bytes.Reader
+}
+
+func (f failingSeeker) Read(b []byte) (n int, err error) {
+	return f.reader.Read(b)
+}
+
+func (f failingSeeker) Seek(offset int64, whence int) (int64, error) {
+	if offset == 0 && whence == io.SeekStart {
+		return 0, seekFailure
+	}
+
+	return f.reader.Seek(offset, whence)
+}
+
+func TestResetMultipartReaderSeekStartError(t *testing.T) {
+	ts := createFilePostServer(t)
+	defer ts.Close()
+
+	testSeeker := &failingSeeker{
+		bytes.NewReader([]byte("test")),
+	}
+
+	c := dc().
+		SetRetryCount(2).
+		SetTimeout(time.Second * 3).
+		SetRetryResetReaders(true).
+		AddRetryAfterErrorCondition()
+
+	resp, err := c.R().
+		SetFileReader("name", "filename", testSeeker).
+		Post(ts.URL + "/set-reset-multipart-readers-test")
+
+	assertEqual(t, 500, resp.StatusCode())
+	assertEqual(t, err.Error(), seekFailure.Error())
+}
+
+func TestResetMultipartReaders(t *testing.T) {
+	ts := createFilePostServer(t)
+	defer ts.Close()
+
+	str := "test"
+	buf := []byte(str)
+
+	bufReader := bytes.NewReader(buf)
+	bufCpy := make([]byte, len(buf))
+
+	c := dc().
+		SetRetryCount(2).
+		SetTimeout(time.Second * 3).
+		SetRetryResetReaders(true).
+		AddRetryAfterErrorCondition().
+		AddRetryHook(
+			func(response *Response, _ error) {
+				read, err := bufReader.Read(bufCpy)
+
+				assertNil(t, err)
+				assertEqual(t, len(buf), read)
+				assertEqual(t, str, string(bufCpy))
+			},
+		)
+
+	resp, err := c.R().
+		SetFileReader("name", "filename", bufReader).
+		Post(ts.URL + "/set-reset-multipart-readers-test")
+
+	assertEqual(t, 500, resp.StatusCode())
+	assertNil(t, err)
+}