Repository: go-resty/resty
Pull request #597 (2022-11-04 15:26:09)

Fix race condition during request: Protect pre/post request hooks with RWMutex
Firstly, thanks for the wonderful library! It's an integral part of one of my team's projects and it works beautifully.

In our project, we have one resty client that performs all API requests. It is shared among multiple "users" that each inject their own auth tokens into each request via pre request hooks.

The issue is, if one user is performing requests at the same time as another user is being created (which involves setting up its pre-request hooks), our CI reports a data race.

Given that an API client such as resty is intended to be used in a highly parallel way, potentially by many different places in the code, it makes sense to me that user-modifiable things such as pre- and post-request hooks should be protected from data races. As such, I have added an RWMutex for each user-modifiable hook slice.

jeevatkm: @jameshoulahan Thanks for your compliment and the PR contribution.

I agree, if dynamically the middleware is getting added/removed from resty then RWMutex is needed!

I just noticed `codecov` reports are not coming in, so currently, many PR are blocked. Let me figure out that, then I will merge this PR once PR validation is done.
jeevatkm: @jameshoulahan I'm sorry for the late response from my end.
codecov[bot]: # [Codecov](https://codecov.io/gh/go-resty/resty/pull/597?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=go-resty) Report
> Merging [#597](https://codecov.io/gh/go-resty/resty/pull/597?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=go-resty) (f29e773) into [master](https://codecov.io/gh/go-resty/resty/commit/9e2a9b71fb72d6d6f6a8828ef5a026c0f16d8ab8?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=go-resty) (9e2a9b7) will **increase** coverage by `0.02%`.
> The diff coverage is `100.00%`.

```diff
@@            Coverage Diff             @@
##           master     #597      +/-   ##
==========================================
+ Coverage   95.94%   95.97%   +0.02%     
==========================================
  Files          10       10              
  Lines        1357     1365       +8     
==========================================
+ Hits         1302     1310       +8     
  Misses         34       34              
  Partials       21       21              
```


| [Impacted Files](https://codecov.io/gh/go-resty/resty/pull/597?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=go-resty) | Coverage Δ | |
|---|---|---|
| [client.go](https://codecov.io/gh/go-resty/resty/pull/597?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=go-resty#diff-Y2xpZW50Lmdv) | `97.81% <100.00%> (+0.04%)` | :arrow_up: |

Help us with your feedback. Take ten seconds to tell us [how you rate us](https://about.codecov.io/nps?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=go-resty). Have a feature suggestion? [Share it here.](https://app.codecov.io/gh/feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=go-resty)


Patch:
diff --git a/client.go b/client.go
index aaf6095..e65e3ce 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)
@@ -138,9 +139,11 @@ type Client struct {
 	proxyURL           *url.URL
 	beforeRequest      []RequestMiddleware
 	udBeforeRequest    []RequestMiddleware
+  udBeforeRequestLock sync.RWMutex
 	preReqHook         PreRequestHook
 	successHooks       []SuccessHook
 	afterResponse      []ResponseMiddleware
+  afterResponseLock   sync.RWMutex
 	requestLog         RequestLogCallback
 	responseLog        ResponseLogCallback
 	errorHooks         []ErrorHook
@@ -159,11 +162,12 @@ type User struct {
 
 // SetHostURL method is to set Host URL in the client instance. It will be used with request
 // raised from this client with relative URL
-//		// Setting HTTP address
-//		client.SetHostURL("http://myjeeva.com")
 //
-//		// Setting HTTPS address
-//		client.SetHostURL("https://myjeeva.com")
+//	// Setting HTTP address
+//	client.SetHostURL("http://myjeeva.com")
+//
+//	// Setting HTTPS address
+//	client.SetHostURL("https://myjeeva.com")
 //
 // Deprecated: use SetBaseURL instead. To be removed in v3.0.0 release.
 func (c *Client) SetHostURL(url string) *Client {
@@ -173,11 +177,12 @@ func (c *Client) SetHostURL(url string) *Client {
 
 // SetBaseURL method is to set Base URL in the client instance. It will be used with request
 // raised from this client with relative URL
-//		// Setting HTTP address
-//		client.SetBaseURL("http://myjeeva.com")
 //
-//		// Setting HTTPS address
-//		client.SetBaseURL("https://myjeeva.com")
+//	// Setting HTTP address
+//	client.SetBaseURL("http://myjeeva.com")
+//
+//	// Setting HTTPS address
+//	client.SetBaseURL("https://myjeeva.com")
 //
 // Since v2.7.0
 func (c *Client) SetBaseURL(url string) *Client {
@@ -194,9 +199,9 @@ func (c *Client) SetBaseURL(url string) *Client {
 //
 // For Example: To set `Content-Type` and `Accept` as `application/json`
 //
-// 		client.
-// 			SetHeader("Content-Type", "application/json").
-// 			SetHeader("Accept", "application/json")
+//	client.
+//		SetHeader("Content-Type", "application/json").
+//		SetHeader("Accept", "application/json")
 func (c *Client) SetHeader(header, value string) *Client {
 	c.Header.Set(header, value)
 	return c
@@ -210,10 +215,10 @@ func (c *Client) SetHeader(header, value string) *Client {
 //
 // For Example: To set `Content-Type` and `Accept` as `application/json`
 //
-// 		client.SetHeaders(map[string]string{
-//				"Content-Type": "application/json",
-//				"Accept": "application/json",
-//			})
+//	client.SetHeaders(map[string]string{
+//			"Content-Type": "application/json",
+//			"Accept": "application/json",
+//		})
 func (c *Client) SetHeaders(headers map[string]string) *Client {
 	for h, v := range headers {
 		c.Header.Set(h, v)
@@ -224,9 +229,10 @@ func (c *Client) SetHeaders(headers map[string]string) *Client {
 // SetHeaderVerbatim method is to set a single header field and its value verbatim in the current request.
 //
 // For Example: To set `all_lowercase` and `UPPERCASE` as `available`.
-// 		client.R().
-//			SetHeaderVerbatim("all_lowercase", "available").
-//			SetHeaderVerbatim("UPPERCASE", "available")
+//
+//	client.R().
+//		SetHeaderVerbatim("all_lowercase", "available").
+//		SetHeaderVerbatim("UPPERCASE", "available")
 //
 // Also you can override header value, which was set at client instance level.
 //
@@ -241,7 +247,7 @@ func (c *Client) SetHeaderVerbatim(header, value string) *Client {
 // For Example: sometimes we don't want to save cookies in api contacting, we can remove the default
 // CookieJar in resty client.
 //
-//		client.SetCookieJar(nil)
+//	client.SetCookieJar(nil)
 func (c *Client) SetCookieJar(jar http.CookieJar) *Client {
 	c.httpClient.Jar = jar
 	return c
@@ -249,10 +255,11 @@ func (c *Client) SetCookieJar(jar http.CookieJar) *Client {
 
 // SetCookie method appends a single cookie in the client instance.
 // These cookies will be added to all the request raised from this client instance.
-// 		client.SetCookie(&http.Cookie{
-// 					Name:"go-resty",
-//					Value:"This is cookie value",
-// 				})
+//
+//	client.SetCookie(&http.Cookie{
+//				Name:"go-resty",
+//				Value:"This is cookie value",
+//			})
 func (c *Client) SetCookie(hc *http.Cookie) *Client {
 	c.Cookies = append(c.Cookies, hc)
 	return c
@@ -260,19 +267,20 @@ func (c *Client) SetCookie(hc *http.Cookie) *Client {
 
 // SetCookies method sets an array of cookies in the client instance.
 // These cookies will be added to all the request raised from this client instance.
-// 		cookies := []*http.Cookie{
-// 			&http.Cookie{
-// 				Name:"go-resty-1",
-// 				Value:"This is cookie 1 value",
-// 			},
-// 			&http.Cookie{
-// 				Name:"go-resty-2",
-// 				Value:"This is cookie 2 value",
-// 			},
-// 		}
-//
-//		// Setting a cookies into resty
-// 		client.SetCookies(cookies)
+//
+//	cookies := []*http.Cookie{
+//		&http.Cookie{
+//			Name:"go-resty-1",
+//			Value:"This is cookie 1 value",
+//		},
+//		&http.Cookie{
+//			Name:"go-resty-2",
+//			Value:"This is cookie 2 value",
+//		},
+//	}
+//
+//	// Setting a cookies into resty
+//	client.SetCookies(cookies)
 func (c *Client) SetCookies(cs []*http.Cookie) *Client {
 	c.Cookies = append(c.Cookies, cs...)
 	return c
@@ -286,9 +294,10 @@ func (c *Client) SetCookies(cs []*http.Cookie) *Client {
 // this client instance. Also it can be overridden at request level Query Param options.
 //
 // See `Request.SetQueryParam` or `Request.SetQueryParams`.
-// 		client.
-//			SetQueryParam("search", "kitchen papers").
-//			SetQueryParam("size", "large")
+//
+//	client.
+//		SetQueryParam("search", "kitchen papers").
+//		SetQueryParam("size", "large")
 func (c *Client) SetQueryParam(param, value string) *Client {
 	c.QueryParam.Set(param, value)
 	return c
@@ -302,10 +311,11 @@ func (c *Client) SetQueryParam(param, value string) *Client {
 // client instance. Also it can be overridden at request level Query Param options.
 //
 // See `Request.SetQueryParams` or `Request.SetQueryParam`.
-// 		client.SetQueryParams(map[string]string{
-//				"search": "kitchen papers",
-//				"size": "large",
-//			})
+//
+//	client.SetQueryParams(map[string]string{
+//			"search": "kitchen papers",
+//			"size": "large",
+//		})
 func (c *Client) SetQueryParams(params map[string]string) *Client {
 	for p, v := range params {
 		c.SetQueryParam(p, v)
@@ -319,10 +329,11 @@ func (c *Client) SetQueryParams(params map[string]string) *Client {
 // this client instance. Also it can be overridden at request level form data.
 //
 // See `Request.SetFormData`.
-// 		client.SetFormData(map[string]string{
-//				"access_token": "BC594900-518B-4F7E-AC75-BD37F019E08F",
-//				"user_id": "3455454545",
-//			})
+//
+//	client.SetFormData(map[string]string{
+//			"access_token": "BC594900-518B-4F7E-AC75-BD37F019E08F",
+//			"user_id": "3455454545",
+//		})
 func (c *Client) SetFormData(data map[string]string) *Client {
 	for k, v := range data {
 		c.FormData.Set(k, v)
@@ -331,10 +342,12 @@ func (c *Client) SetFormData(data map[string]string) *Client {
 }
 
 // SetBasicAuth method sets the basic authentication header in the HTTP request. For Example:
-//		Authorization: Basic <base64-encoded-value>
+//
+//	Authorization: Basic <base64-encoded-value>
 //
 // For Example: To set the header for username "go-resty" and password "welcome"
-// 		client.SetBasicAuth("go-resty", "welcome")
+//
+//	client.SetBasicAuth("go-resty", "welcome")
 //
 // This basic auth information gets added to all the request rasied from this client instance.
 // Also it can be overridden or set one at the request level is supported.
@@ -347,11 +360,12 @@ func (c *Client) SetBasicAuth(username, password string) *Client {
 
 // SetAuthToken method sets the auth token of the `Authorization` header for all HTTP requests.
 // The default auth scheme is `Bearer`, it can be customized with the method `SetAuthScheme`. For Example:
-// 		Authorization: <auth-scheme> <auth-token-value>
+//
+//	Authorization: <auth-scheme> <auth-token-value>
 //
 // For Example: To set auth token BC594900518B4F7EAC75BD37F019E08FBC594900518B4F7EAC75BD37F019E08F
 //
-// 		client.SetAuthToken("BC594900518B4F7EAC75BD37F019E08FBC594900518B4F7EAC75BD37F019E08F")
+//	client.SetAuthToken("BC594900518B4F7EAC75BD37F019E08FBC594900518B4F7EAC75BD37F019E08F")
 //
 // This auth token gets added to all the requests rasied from this client instance.
 // Also it can be overridden or set one at the request level is supported.
@@ -363,19 +377,21 @@ func (c *Client) SetAuthToken(token string) *Client {
 }
 
 // SetAuthScheme method sets the auth scheme type in the HTTP request. For Example:
-//      Authorization: <auth-scheme-value> <auth-token-value>
+//
+//	Authorization: <auth-scheme-value> <auth-token-value>
 //
 // For Example: To set the scheme to use OAuth
 //
-// 		client.SetAuthScheme("OAuth")
+//	client.SetAuthScheme("OAuth")
 //
 // This auth scheme gets added to all the requests rasied from this client instance.
 // Also it can be overridden or set one at the request level is supported.
 //
 // Information about auth schemes can be found in RFC7235 which is linked to below
 // along with the page containing the currently defined official authentication schemes:
-//     https://tools.ietf.org/html/rfc7235
-//     https://www.iana.org/assignments/http-authschemes/http-authschemes.xhtml#authschemes
+//
+//	https://tools.ietf.org/html/rfc7235
+//	https://www.iana.org/assignments/http-authschemes/http-authschemes.xhtml#authschemes
 //
 // See `Request.SetAuthToken`.
 func (c *Client) SetAuthScheme(scheme string) *Client {
@@ -410,28 +426,38 @@ func (c *Client) NewRequest() *Request {
 // OnBeforeRequest method appends request middleware into the before request chain.
 // Its gets applied after default Resty request middlewares and before request
 // been sent from Resty to host server.
-// 		client.OnBeforeRequest(func(c *resty.Client, r *resty.Request) error {
-//				// Now you have access to Client and Request instance
-//				// manipulate it as per your need
 //
-//				return nil 	// if its success otherwise return error
-//			})
+//	client.OnBeforeRequest(func(c *resty.Client, r *resty.Request) error {
+//			// Now you have access to Client and Request instance
+//			// manipulate it as per your need
+//
+//			return nil 	// if its success otherwise return error
+//		})
 func (c *Client) OnBeforeRequest(m RequestMiddleware) *Client {
+	c.udBeforeRequestLock.Lock()
+	defer c.udBeforeRequestLock.Unlock()
+
 	c.udBeforeRequest = append(c.udBeforeRequest, m)
+
 	return c
 }
 
 // OnAfterResponse method appends response middleware into the after response chain.
 // Once we receive response from host server, default Resty response middleware
 // gets applied and then user assigened response middlewares applied.
-// 		client.OnAfterResponse(func(c *resty.Client, r *resty.Response) error {
-//				// Now you have access to Client and Response instance
-//				// manipulate it as per your need
 //
-//				return nil 	// if its success otherwise return error
-//			})
+//	client.OnAfterResponse(func(c *resty.Client, r *resty.Response) error {
+//			// Now you have access to Client and Response instance
+//			// manipulate it as per your need
+//
+//			return nil 	// if its success otherwise return error
+//		})
 func (c *Client) OnAfterResponse(m ResponseMiddleware) *Client {
+	c.afterResponseLock.Lock()
+	defer c.afterResponseLock.Unlock()
+
 	c.afterResponse = append(c.afterResponse, m)
+
 	return c
 }
 
@@ -440,12 +466,12 @@ func (c *Client) OnAfterResponse(m ResponseMiddleware) *Client {
 // If there was a response from the server, the error will be wrapped in *ResponseError
 // which has the last response received from the server.
 //
-//		client.OnError(func(req *resty.Request, err error) {
-//			if v, ok := err.(*resty.ResponseError); ok {
-//				// Do something with v.Response
-//			}
-//			// Log the error, increment a metric, etc...
-//		})
+//	client.OnError(func(req *resty.Request, err error) {
+//		if v, ok := err.(*resty.ResponseError); ok {
+//			// Do something with v.Response
+//		}
+//		// Log the error, increment a metric, etc...
+//	})
 //
 // Out of the OnSuccess, OnError, OnInvalid, OnPanic callbacks, exactly one
 // set will be invoked for each call to Request.Execute() that comletes.
@@ -501,14 +527,16 @@ func (c *Client) SetPreRequestHook(h PreRequestHook) *Client {
 // SetDebug method enables the debug mode on Resty client. Client logs details of every request and response.
 // For `Request` it logs information such as HTTP verb, Relative URL path, Host, Headers, Body if it has one.
 // For `Response` it logs information such as Status, Response Time, Headers, Body if it has one.
-//		client.SetDebug(true)
+//
+//	client.SetDebug(true)
 func (c *Client) SetDebug(d bool) *Client {
 	c.Debug = d
 	return c
 }
 
 // SetDebugBodyLimit sets the maximum size for which the response and request body will be logged in debug mode.
-//		client.SetDebugBodyLimit(1000000)
+//
+//	client.SetDebugBodyLimit(1000000)
 func (c *Client) SetDebugBodyLimit(sl int64) *Client {
 	c.debugBodySizeLimit = sl
 	return c
@@ -539,7 +567,8 @@ func (c *Client) OnResponseLog(rl ResponseLogCallback) *Client {
 // SetDisableWarn method disables the warning message on Resty client.
 //
 // For Example: Resty warns the user when BasicAuth used on non-TLS mode.
-//		client.SetDisableWarn(true)
+//
+//	client.SetDisableWarn(true)
 func (c *Client) SetDisableWarn(d bool) *Client {
 	c.DisableWarn = d
 	return c
@@ -548,7 +577,8 @@ func (c *Client) SetDisableWarn(d bool) *Client {
 // SetAllowGetMethodPayload method allows the GET method with payload on Resty client.
 //
 // For Example: Resty allows the user sends request with a payload on HTTP GET method.
-//		client.SetAllowGetMethodPayload(true)
+//
+//	client.SetAllowGetMethodPayload(true)
 func (c *Client) SetAllowGetMethodPayload(a bool) *Client {
 	c.AllowGetMethodPayload = a
 	return c
@@ -564,7 +594,8 @@ func (c *Client) SetLogger(l Logger) *Client {
 
 // SetContentLength method enables the HTTP header `Content-Length` value for every request.
 // By default Resty won't set `Content-Length`.
-// 		client.SetContentLength(true)
+//
+//	client.SetContentLength(true)
 //
 // Also you have an option to enable for particular request. See `Request.SetContentLength`
 func (c *Client) SetContentLength(l bool) *Client {
@@ -573,7 +604,8 @@ func (c *Client) SetContentLength(l bool) *Client {
 }
 
 // SetTimeout method sets timeout for request raised from client.
-//		client.SetTimeout(time.Duration(1 * time.Minute))
+//
+//	client.SetTimeout(time.Duration(1 * time.Minute))
 func (c *Client) SetTimeout(timeout time.Duration) *Client {
 	c.httpClient.Timeout = timeout
 	return c
@@ -582,9 +614,10 @@ func (c *Client) SetTimeout(timeout time.Duration) *Client {
 // SetError method is to register the global or client common `Error` object into Resty.
 // It is used for automatic unmarshalling if response status code is greater than 399 and
 // content type either JSON or XML. Can be pointer or non-pointer.
-// 		client.SetError(&Error{})
-//		// OR
-//		client.SetError(Error{})
+//
+//	client.SetError(&Error{})
+//	// OR
+//	client.SetError(Error{})
 func (c *Client) SetError(err interface{}) *Client {
 	c.Error = typeOf(err)
 	return c
@@ -593,10 +626,10 @@ func (c *Client) SetError(err interface{}) *Client {
 // SetRedirectPolicy method sets the client redirect poilicy. Resty provides ready to use
 // redirect policies. Wanna create one for yourself refer to `redirect.go`.
 //
-//		client.SetRedirectPolicy(FlexibleRedirectPolicy(20))
+//	client.SetRedirectPolicy(FlexibleRedirectPolicy(20))
 //
-// 		// Need multiple redirect policies together
-//		client.SetRedirectPolicy(FlexibleRedirectPolicy(20), DomainCheckRedirectPolicy("host1.com", "host2.net"))
+//	// Need multiple redirect policies together
+//	client.SetRedirectPolicy(FlexibleRedirectPolicy(20), DomainCheckRedirectPolicy("host1.com", "host2.net"))
 func (c *Client) SetRedirectPolicy(policies ...interface{}) *Client {
 	for _, p := range policies {
 		if _, ok := p.(RedirectPolicy); !ok {
@@ -680,14 +713,24 @@ 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:
-// 		// One can set custom root-certificate. Refer: http://golang.org/pkg/crypto/tls/#example_Dial
-//		client.SetTLSClientConfig(&tls.Config{ RootCAs: roots })
 //
-// 		// or One can disable security check (https)
-//		client.SetTLSClientConfig(&tls.Config{ InsecureSkipVerify: true })
+//	// One can set custom root-certificate. Refer: http://golang.org/pkg/crypto/tls/#example_Dial
+//	client.SetTLSClientConfig(&tls.Config{ RootCAs: roots })
+//
+//	// or One can disable security check (https)
+//	client.SetTLSClientConfig(&tls.Config{ InsecureSkipVerify: true })
 //
 // Note: This method overwrites existing `TLSClientConfig`.
 func (c *Client) SetTLSClientConfig(config *tls.Config) *Client {
@@ -701,7 +744,8 @@ func (c *Client) SetTLSClientConfig(config *tls.Config) *Client {
 }
 
 // SetProxy method sets the Proxy URL and Port for Resty client.
-//		client.SetProxy("http://proxyserver:8888")
+//
+//	client.SetProxy("http://proxyserver:8888")
 //
 // OR Without this `SetProxy` method, you could also set Proxy via environment variable.
 //
@@ -725,7 +769,8 @@ func (c *Client) SetProxy(proxyURL string) *Client {
 }
 
 // RemoveProxy method removes the proxy configuration from Resty client
-//		client.RemoveProxy()
+//
+//	client.RemoveProxy()
 func (c *Client) RemoveProxy() *Client {
 	transport, err := c.transport()
 	if err != nil {
@@ -749,7 +794,8 @@ func (c *Client) SetCertificates(certs ...tls.Certificate) *Client {
 }
 
 // SetRootCertificate method helps to add one or more root certificates into Resty client
-// 		client.SetRootCertificate("/path/to/root/pemFile.pem")
+//
+//	client.SetRootCertificate("/path/to/root/pemFile.pem")
 func (c *Client) SetRootCertificate(pemFilePath string) *Client {
 	rootPemData, err := ioutil.ReadFile(pemFilePath)
 	if err != nil {
@@ -771,7 +817,8 @@ func (c *Client) SetRootCertificate(pemFilePath string) *Client {
 }
 
 // SetRootCertificateFromString method helps to add one or more root certificates into Resty client
-// 		client.SetRootCertificateFromString("pem file content")
+//
+//	client.SetRootCertificateFromString("pem file content")
 func (c *Client) SetRootCertificateFromString(pemContent string) *Client {
 	config, err := c.tlsConfig()
 	if err != nil {
@@ -789,7 +836,8 @@ func (c *Client) SetRootCertificateFromString(pemContent string) *Client {
 // SetOutputDirectory method sets output directory for saving HTTP response into file.
 // If the output directory not exists then resty creates one. This setting is optional one,
 // if you're planning using absolute path in `Request.SetOutput` and can used together.
-// 		client.SetOutputDirectory("/save/http/response/here")
+//
+//	client.SetOutputDirectory("/save/http/response/here")
 func (c *Client) SetOutputDirectory(dirPath string) *Client {
 	c.outputDirectory = dirPath
 	return c
@@ -805,14 +853,14 @@ func (c *Client) SetOutputDirectory(dirPath string) *Client {
 //
 // - It overwrites the Resty client transport instance and it's configurations.
 //
-//		transport := &http.Transport{
-//			// somthing like Proxying to httptest.Server, etc...
-//			Proxy: func(req *http.Request) (*url.URL, error) {
-//				return url.Parse(server.URL)
-//			},
-//		}
+//	transport := &http.Transport{
+//		// somthing like Proxying to httptest.Server, etc...
+//		Proxy: func(req *http.Request) (*url.URL, error) {
+//			return url.Parse(server.URL)
+//		},
+//	}
 //
-//		client.SetTransport(transport)
+//	client.SetTransport(transport)
 func (c *Client) SetTransport(transport http.RoundTripper) *Client {
 	if transport != nil {
 		c.httpClient.Transport = transport
@@ -821,7 +869,8 @@ func (c *Client) SetTransport(transport http.RoundTripper) *Client {
 }
 
 // SetScheme method sets custom scheme in the Resty client. It's way to override default.
-// 		client.SetScheme("http")
+//
+//	client.SetScheme("http")
 func (c *Client) SetScheme(scheme string) *Client {
 	if !IsStringEmpty(scheme) {
 		c.scheme = strings.TrimSpace(scheme)
@@ -849,11 +898,13 @@ func (c *Client) SetDoNotParseResponse(parse bool) *Client {
 
 // SetPathParam method sets single URL path key-value pair in the
 // Resty client instance.
-// 		client.SetPathParam("userId", "sample@sample.com")
 //
-// 		Result:
-// 		   URL - /v1/users/{userId}/details
-// 		   Composed URL - /v1/users/sample@sample.com/details
+//	client.SetPathParam("userId", "sample@sample.com")
+//
+//	Result:
+//	   URL - /v1/users/{userId}/details
+//	   Composed URL - /v1/users/sample@sample.com/details
+//
 // It replaces the value of the key while composing the request URL.
 //
 // Also it can be overridden at request level Path Params options,
@@ -865,14 +916,16 @@ func (c *Client) SetPathParam(param, value string) *Client {
 
 // SetPathParams method sets multiple URL path key-value pairs at one go in the
 // Resty client instance.
-// 		client.SetPathParams(map[string]string{
-// 		   "userId": "sample@sample.com",
-// 		   "subAccountId": "100002",
-// 		})
-//
-// 		Result:
-// 		   URL - /v1/users/{userId}/{subAccountId}/details
-// 		   Composed URL - /v1/users/sample@sample.com/100002/details
+//
+//	client.SetPathParams(map[string]string{
+//	   "userId": "sample@sample.com",
+//	   "subAccountId": "100002",
+//	})
+//
+//	Result:
+//	   URL - /v1/users/{userId}/{subAccountId}/details
+//	   Composed URL - /v1/users/sample@sample.com/100002/details
+//
 // It replaces the value of the key while composing the request URL.
 //
 // Also it can be overridden at request level Path Params options,
@@ -895,11 +948,11 @@ func (c *Client) SetJSONEscapeHTML(b bool) *Client {
 // EnableTrace method enables the Resty client trace for the requests fired from
 // the client using `httptrace.ClientTrace` and provides insights.
 //
-// 		client := resty.New().EnableTrace()
+//	client := resty.New().EnableTrace()
 //
-// 		resp, err := client.R().Get("https://httpbin.org/get")
-// 		fmt.Println("Error:", err)
-// 		fmt.Println("Trace Info:", resp.Request.TraceInfo())
+//	resp, err := client.R().Get("https://httpbin.org/get")
+//	fmt.Println("Error:", err)
+//	fmt.Println("Trace Info:", resp.Request.TraceInfo())
 //
 // Also `Request.EnableTrace` available too to get trace info for single request.
 //
@@ -935,6 +988,14 @@ func (c *Client) GetClient() *http.Client {
 // Executes method executes the given `Request` object and returns response
 // error.
 func (c *Client) execute(req *Request) (*Response, error) {
+	// Lock the user-defined pre-request hooks.
+	c.udBeforeRequestLock.RLock()
+	defer c.udBeforeRequestLock.RUnlock()
+
+	// Lock the post-request hooks.
+	c.afterResponseLock.RLock()
+	defer c.afterResponseLock.RUnlock()
+
 	// Apply Request middleware
 	var err error
 
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/redirect.go b/redirect.go
index 7d7e43b..dfc4f42 100644
--- a/redirect.go
+++ b/redirect.go
@@ -33,7 +33,8 @@ func (f RedirectPolicyFunc) Apply(req *http.Request, via []*http.Request) error
 }
 
 // NoRedirectPolicy is used to disable redirects in the HTTP client
-// 		resty.SetRedirectPolicy(NoRedirectPolicy())
+//
+//	resty.SetRedirectPolicy(NoRedirectPolicy())
 func NoRedirectPolicy() RedirectPolicy {
 	return RedirectPolicyFunc(func(req *http.Request, via []*http.Request) error {
 		return errors.New("auto redirect is disabled")
@@ -41,7 +42,8 @@ func NoRedirectPolicy() RedirectPolicy {
 }
 
 // FlexibleRedirectPolicy is convenient method to create No of redirect policy for HTTP client.
-// 		resty.SetRedirectPolicy(FlexibleRedirectPolicy(20))
+//
+//	resty.SetRedirectPolicy(FlexibleRedirectPolicy(20))
 func FlexibleRedirectPolicy(noOfRedirect int) RedirectPolicy {
 	return RedirectPolicyFunc(func(req *http.Request, via []*http.Request) error {
 		if len(via) >= noOfRedirect {
@@ -54,7 +56,8 @@ func FlexibleRedirectPolicy(noOfRedirect int) RedirectPolicy {
 
 // DomainCheckRedirectPolicy is convenient method to define domain name redirect rule in resty client.
 // Redirect is allowed for only mentioned host in the policy.
-// 		resty.SetRedirectPolicy(DomainCheckRedirectPolicy("host1.com", "host2.org", "host3.net"))
+//
+//	resty.SetRedirectPolicy(DomainCheckRedirectPolicy("host1.com", "host2.org", "host3.net"))
 func DomainCheckRedirectPolicy(hostnames ...string) RedirectPolicy {
 	hosts := make(map[string]bool)
 	for _, h := range hostnames {
diff --git a/request.go b/request.go
index deedfb4..adb129e 100644
--- a/request.go
+++ b/request.go
@@ -92,9 +92,10 @@ func (r *Request) SetContext(ctx context.Context) *Request {
 // SetHeader method is to set a single header field and its value in the current request.
 //
 // For Example: To set `Content-Type` and `Accept` as `application/json`.
-// 		client.R().
-//			SetHeader("Content-Type", "application/json").
-//			SetHeader("Accept", "application/json")
+//
+//	client.R().
+//		SetHeader("Content-Type", "application/json").
+//		SetHeader("Accept", "application/json")
 //
 // Also you can override header value, which was set at client instance level.
 func (r *Request) SetHeader(header, value string) *Request {
@@ -106,11 +107,12 @@ func (r *Request) SetHeader(header, value string) *Request {
 //
 // For Example: To set `Content-Type` and `Accept` as `application/json`
 //
-// 		client.R().
-//			SetHeaders(map[string]string{
-//				"Content-Type": "application/json",
-//				"Accept": "application/json",
-//			})
+//	client.R().
+//		SetHeaders(map[string]string{
+//			"Content-Type": "application/json",
+//			"Accept": "application/json",
+//		})
+//
 // Also you can override header value, which was set at client instance level.
 func (r *Request) SetHeaders(headers map[string]string) *Request {
 	for h, v := range headers {
@@ -123,10 +125,11 @@ func (r *Request) SetHeaders(headers map[string]string) *Request {
 //
 // For Example: To set `Accept` as `text/html, application/xhtml+xml, application/xml;q=0.9, image/webp, */*;q=0.8`
 //
-// 		client.R().
-//			SetHeaderMultiValues(map[string][]string{
-//				"Accept": []string{"text/html", "application/xhtml+xml", "application/xml;q=0.9", "image/webp", "*/*;q=0.8"},
-//			})
+//	client.R().
+//		SetHeaderMultiValues(map[string][]string{
+//			"Accept": []string{"text/html", "application/xhtml+xml", "application/xml;q=0.9", "image/webp", "*/*;q=0.8"},
+//		})
+//
 // Also you can override header value, which was set at client instance level.
 func (r *Request) SetHeaderMultiValues(headers map[string][]string) *Request {
 	for key, values := range headers {
@@ -138,9 +141,10 @@ func (r *Request) SetHeaderMultiValues(headers map[string][]string) *Request {
 // SetHeaderVerbatim method is to set a single header field and its value verbatim in the current request.
 //
 // For Example: To set `all_lowercase` and `UPPERCASE` as `available`.
-// 		client.R().
-//			SetHeaderVerbatim("all_lowercase", "available").
-//			SetHeaderVerbatim("UPPERCASE", "available")
+//
+//	client.R().
+//		SetHeaderVerbatim("all_lowercase", "available").
+//		SetHeaderVerbatim("UPPERCASE", "available")
 //
 // Also you can override header value, which was set at client instance level.
 //
@@ -154,9 +158,11 @@ func (r *Request) SetHeaderVerbatim(header, value string) *Request {
 // It will be formed as query string for the request.
 //
 // For Example: `search=kitchen%20papers&size=large` in the URL after `?` mark.
-// 		client.R().
-//			SetQueryParam("search", "kitchen papers").
-//			SetQueryParam("size", "large")
+//
+//	client.R().
+//		SetQueryParam("search", "kitchen papers").
+//		SetQueryParam("size", "large")
+//
 // Also you can override query params value, which was set at client instance level.
 func (r *Request) SetQueryParam(param, value string) *Request {
 	r.QueryParam.Set(param, value)
@@ -167,11 +173,13 @@ func (r *Request) SetQueryParam(param, value string) *Request {
 // It will be formed as query string for the request.
 //
 // For Example: `search=kitchen%20papers&size=large` in the URL after `?` mark.
-// 		client.R().
-//			SetQueryParams(map[string]string{
-//				"search": "kitchen papers",
-//				"size": "large",
-//			})
+//
+//	client.R().
+//		SetQueryParams(map[string]string{
+//			"search": "kitchen papers",
+//			"size": "large",
+//		})
+//
 // Also you can override query params value, which was set at client instance level.
 func (r *Request) SetQueryParams(params map[string]string) *Request {
 	for p, v := range params {
@@ -185,10 +193,12 @@ func (r *Request) SetQueryParams(params map[string]string) *Request {
 // query string for the request.
 //
 // For Example: `status=pending&status=approved&status=open` in the URL after `?` mark.
-// 		client.R().
-//			SetQueryParamsFromValues(url.Values{
-//				"status": []string{"pending", "approved", "open"},
-//			})
+//
+//	client.R().
+//		SetQueryParamsFromValues(url.Values{
+//			"status": []string{"pending", "approved", "open"},
+//		})
+//
 // Also you can override query params value, which was set at client instance level.
 func (r *Request) SetQueryParamsFromValues(params url.Values) *Request {
 	for p, v := range params {
@@ -202,8 +212,9 @@ func (r *Request) SetQueryParamsFromValues(params url.Values) *Request {
 // SetQueryString method provides ability to use string as an input to set URL query string for the request.
 //
 // Using String as an input
-// 		client.R().
-//			SetQueryString("productId=232&template=fresh-sample&cat=resty&source=google&kw=buy a lot more")
+//
+//	client.R().
+//		SetQueryString("productId=232&template=fresh-sample&cat=resty&source=google&kw=buy a lot more")
 func (r *Request) SetQueryString(query string) *Request {
 	params, err := url.ParseQuery(strings.TrimSpace(query))
 	if err == nil {
@@ -221,11 +232,13 @@ func (r *Request) SetQueryString(query string) *Request {
 // SetFormData method sets Form parameters and their values in the current request.
 // It's applicable only HTTP method `POST` and `PUT` and requests content type would be set as
 // `application/x-www-form-urlencoded`.
-// 		client.R().
-// 			SetFormData(map[string]string{
-//				"access_token": "BC594900-518B-4F7E-AC75-BD37F019E08F",
-//				"user_id": "3455454545",
-//			})
+//
+//	client.R().
+//		SetFormData(map[string]string{
+//			"access_token": "BC594900-518B-4F7E-AC75-BD37F019E08F",
+//			"user_id": "3455454545",
+//		})
+//
 // Also you can override form data value, which was set at client instance level.
 func (r *Request) SetFormData(data map[string]string) *Request {
 	for k, v := range data {
@@ -236,10 +249,12 @@ func (r *Request) SetFormData(data map[string]string) *Request {
 
 // SetFormDataFromValues method appends multiple form parameters with multi-value
 // (`url.Values`) at one go in the current request.
-// 		client.R().
-//			SetFormDataFromValues(url.Values{
-//				"search_criteria": []string{"book", "glass", "pencil"},
-//			})
+//
+//	client.R().
+//		SetFormDataFromValues(url.Values{
+//			"search_criteria": []string{"book", "glass", "pencil"},
+//		})
+//
 // Also you can override form data value, which was set at client instance level.
 func (r *Request) SetFormDataFromValues(data url.Values) *Request {
 	for k, v := range data {
@@ -258,36 +273,40 @@ func (r *Request) SetFormDataFromValues(data url.Values) *Request {
 // Note: `io.Reader` is processed as bufferless mode while sending request.
 //
 // For Example: Struct as a body input, based on content type, it will be marshalled.
-//		client.R().
-//			SetBody(User{
-//				Username: "jeeva@myjeeva.com",
-//				Password: "welcome2resty",
-//			})
+//
+//	client.R().
+//		SetBody(User{
+//			Username: "jeeva@myjeeva.com",
+//			Password: "welcome2resty",
+//		})
 //
 // Map as a body input, based on content type, it will be marshalled.
-//		client.R().
-//			SetBody(map[string]interface{}{
-//				"username": "jeeva@myjeeva.com",
-//				"password": "welcome2resty",
-//				"address": &Address{
-//					Address1: "1111 This is my street",
-//					Address2: "Apt 201",
-//					City: "My City",
-//					State: "My State",
-//					ZipCode: 00000,
-//				},
-//			})
+//
+//	client.R().
+//		SetBody(map[string]interface{}{
+//			"username": "jeeva@myjeeva.com",
+//			"password": "welcome2resty",
+//			"address": &Address{
+//				Address1: "1111 This is my street",
+//				Address2: "Apt 201",
+//				City: "My City",
+//				State: "My State",
+//				ZipCode: 00000,
+//			},
+//		})
 //
 // String as a body input. Suitable for any need as a string input.
-//		client.R().
-//			SetBody(`{
-//				"username": "jeeva@getrightcare.com",
-//				"password": "admin"
-//			}`)
+//
+//	client.R().
+//		SetBody(`{
+//			"username": "jeeva@getrightcare.com",
+//			"password": "admin"
+//		}`)
 //
 // []byte as a body input. Suitable for raw request such as file upload, serialize & deserialize, etc.
-// 		client.R().
-//			SetBody([]byte("This is my raw request, sent as-is"))
+//
+//	client.R().
+//		SetBody([]byte("This is my raw request, sent as-is"))
 func (r *Request) SetBody(body interface{}) *Request {
 	r.Body = body
 	return r
@@ -297,12 +316,14 @@ func (r *Request) SetBody(body interface{}) *Request {
 // if response status code is between 200 and 299 and content type either JSON or XML.
 //
 // Note: Result object can be pointer or non-pointer.
-//		client.R().SetResult(&AuthToken{})
-//		// OR
-//		client.R().SetResult(AuthToken{})
+//
+//	client.R().SetResult(&AuthToken{})
+//	// OR
+//	client.R().SetResult(AuthToken{})
 //
 // Accessing a result value from response instance.
-//		response.Result().(*AuthToken)
+//
+//	response.Result().(*AuthToken)
 func (r *Request) SetResult(res interface{}) *Request {
 	if res != nil {
 		r.Result = getPointer(res)
@@ -314,18 +335,21 @@ func (r *Request) SetResult(res interface{}) *Request {
 // if response status code is greater than 399 and content type either JSON or XML.
 //
 // Note: Error object can be pointer or non-pointer.
-// 		client.R().SetError(&AuthError{})
-//		// OR
-//		client.R().SetError(AuthError{})
+//
+//	client.R().SetError(&AuthError{})
+//	// OR
+//	client.R().SetError(AuthError{})
 //
 // Accessing a error value from response instance.
-//		response.Error().(*AuthError)
+//
+//	response.Error().(*AuthError)
 func (r *Request) SetError(err interface{}) *Request {
 	r.Error = getPointer(err)
 	return r
 }
 
 // SetFile method is to set single file field name and its path for multipart upload.
+//
 //	client.R().
 //		SetFile("my_file", "/Users/jeeva/Gas Bill - Sep.pdf")
 func (r *Request) SetFile(param, filePath string) *Request {
@@ -335,6 +359,7 @@ func (r *Request) SetFile(param, filePath string) *Request {
 }
 
 // SetFiles method is to set multiple file field name and its path for multipart upload.
+//
 //	client.R().
 //		SetFiles(map[string]string{
 //				"my_file1": "/Users/jeeva/Gas Bill - Sep.pdf",
@@ -350,6 +375,7 @@ func (r *Request) SetFiles(files map[string]string) *Request {
 }
 
 // SetFileReader method is to set single file using io.Reader for multipart upload.
+//
 //	client.R().
 //		SetFileReader("profile_img", "my-profile-img.png", bytes.NewReader(profileImgBytes)).
 //		SetFileReader("notes", "user-notes.txt", bytes.NewReader(notesBytes))
@@ -387,8 +413,9 @@ func (r *Request) SetMultipartField(param, fileName, contentType string, reader
 // SetMultipartFields method is to set multiple data fields using io.Reader for multipart upload.
 //
 // For Example:
-// 	client.R().SetMultipartFields(
-// 		&resty.MultipartField{
+//
+//	client.R().SetMultipartFields(
+//		&resty.MultipartField{
 //			Param:       "uploadManifest1",
 //			FileName:    "upload-file-1.json",
 //			ContentType: "application/json",
@@ -402,7 +429,8 @@ func (r *Request) SetMultipartField(param, fileName, contentType string, reader
 //		})
 //
 // If you have slice already, then simply call-
-// 	client.R().SetMultipartFields(fields...)
+//
+//	client.R().SetMultipartFields(fields...)
 func (r *Request) SetMultipartFields(fields ...*MultipartField) *Request {
 	r.isMultiPart = true
 	r.multipartFields = append(r.multipartFields, fields...)
@@ -414,7 +442,8 @@ func (r *Request) SetMultipartFields(fields ...*MultipartField) *Request {
 // request.
 //
 // See `Client.SetContentLength`
-// 		client.R().SetContentLength(true)
+//
+//	client.R().SetContentLength(true)
 func (r *Request) SetContentLength(l bool) *Request {
 	r.setContentLength = l
 	return r
@@ -423,10 +452,12 @@ func (r *Request) SetContentLength(l bool) *Request {
 // SetBasicAuth method sets the basic authentication header in the current HTTP request.
 //
 // For Example:
-//		Authorization: Basic <base64-encoded-value>
+//
+//	Authorization: Basic <base64-encoded-value>
 //
 // To set the header for username "go-resty" and password "welcome"
-// 		client.R().SetBasicAuth("go-resty", "welcome")
+//
+//	client.R().SetBasicAuth("go-resty", "welcome")
 //
 // This method overrides the credentials set by method `Client.SetBasicAuth`.
 func (r *Request) SetBasicAuth(username, password string) *Request {
@@ -435,11 +466,12 @@ func (r *Request) SetBasicAuth(username, password string) *Request {
 }
 
 // SetAuthToken method sets the auth token header(Default Scheme: Bearer) in the current HTTP request. Header example:
-// 		Authorization: Bearer <auth-token-value-comes-here>
+//
+//	Authorization: Bearer <auth-token-value-comes-here>
 //
 // For Example: To set auth token BC594900518B4F7EAC75BD37F019E08FBC594900518B4F7EAC75BD37F019E08F
 //
-// 		client.R().SetAuthToken("BC594900518B4F7EAC75BD37F019E08FBC594900518B4F7EAC75BD37F019E08F")
+//	client.R().SetAuthToken("BC594900518B4F7EAC75BD37F019E08FBC594900518B4F7EAC75BD37F019E08F")
 //
 // This method overrides the Auth token set by method `Client.SetAuthToken`.
 func (r *Request) SetAuthToken(token string) *Request {
@@ -448,19 +480,21 @@ func (r *Request) SetAuthToken(token string) *Request {
 }
 
 // SetAuthScheme method sets the auth token scheme type in the HTTP request. For Example:
-//      Authorization: <auth-scheme-value-set-here> <auth-token-value>
+//
+//	Authorization: <auth-scheme-value-set-here> <auth-token-value>
 //
 // For Example: To set the scheme to use OAuth
 //
-// 		client.R().SetAuthScheme("OAuth")
+//	client.R().SetAuthScheme("OAuth")
 //
 // This auth header scheme gets added to all the request rasied from this client instance.
 // Also it can be overridden or set one at the request level is supported.
 //
 // Information about Auth schemes can be found in RFC7235 which is linked to below along with the page containing
 // the currently defined official authentication schemes:
-//     https://tools.ietf.org/html/rfc7235
-//     https://www.iana.org/assignments/http-authschemes/http-authschemes.xhtml#authschemes
+//
+//	https://tools.ietf.org/html/rfc7235
+//	https://www.iana.org/assignments/http-authschemes/http-authschemes.xhtml#authschemes
 //
 // This method overrides the Authorization scheme set by method `Client.SetAuthScheme`.
 func (r *Request) SetAuthScheme(scheme string) *Request {
@@ -472,9 +506,10 @@ func (r *Request) SetAuthScheme(scheme string) *Request {
 // saved into given file. It is similar to `curl -o` flag. Absolute path or relative path can be used.
 // If is it relative path then output file goes under the output directory, as mentioned
 // in the `Client.SetOutputDirectory`.
-// 		client.R().
-// 			SetOutput("/Users/jeeva/Downloads/ReplyWithHeader-v5.1-beta.zip").
-// 			Get("http://bit.ly/1LouEKr")
+//
+//	client.R().
+//		SetOutput("/Users/jeeva/Downloads/ReplyWithHeader-v5.1-beta.zip").
+//		Get("http://bit.ly/1LouEKr")
 //
 // Note: In this scenario `Response.Body` might be nil.
 func (r *Request) SetOutput(file string) *Request {
@@ -485,9 +520,10 @@ func (r *Request) SetOutput(file string) *Request {
 
 // SetSRV method sets the details to query the service SRV record and execute the
 // request.
-// 		client.R().
-//			SetSRV(SRVRecord{"web", "testservice.com"}).
-//			Get("/get")
+//
+//	client.R().
+//		SetSRV(SRVRecord{"web", "testservice.com"}).
+//		Get("/get")
 func (r *Request) SetSRV(srv *SRVRecord) *Request {
 	r.SRV = srv
 	return r
@@ -506,11 +542,13 @@ func (r *Request) SetDoNotParseResponse(parse bool) *Request {
 
 // SetPathParam method sets single URL path key-value pair in the
 // Resty current request instance.
-// 		client.R().SetPathParam("userId", "sample@sample.com")
 //
-// 		Result:
-// 		   URL - /v1/users/{userId}/details
-// 		   Composed URL - /v1/users/sample@sample.com/details
+//	client.R().SetPathParam("userId", "sample@sample.com")
+//
+//	Result:
+//	   URL - /v1/users/{userId}/details
+//	   Composed URL - /v1/users/sample@sample.com/details
+//
 // It replaces the value of the key while composing the request URL. Also you can
 // override Path Params value, which was set at client instance level.
 func (r *Request) SetPathParam(param, value string) *Request {
@@ -520,14 +558,16 @@ func (r *Request) SetPathParam(param, value string) *Request {
 
 // SetPathParams method sets multiple URL path key-value pairs at one go in the
 // Resty current request instance.
-// 		client.R().SetPathParams(map[string]string{
-// 		   "userId": "sample@sample.com",
-// 		   "subAccountId": "100002",
-// 		})
-//
-// 		Result:
-// 		   URL - /v1/users/{userId}/{subAccountId}/details
-// 		   Composed URL - /v1/users/sample@sample.com/100002/details
+//
+//	client.R().SetPathParams(map[string]string{
+//	   "userId": "sample@sample.com",
+//	   "subAccountId": "100002",
+//	})
+//
+//	Result:
+//	   URL - /v1/users/{userId}/{subAccountId}/details
+//	   Composed URL - /v1/users/sample@sample.com/100002/details
+//
 // It replaces the value of the key while composing request URL. Also you can
 // override Path Params value, which was set at client instance level.
 func (r *Request) SetPathParams(params map[string]string) *Request {
@@ -561,10 +601,11 @@ func (r *Request) SetJSONEscapeHTML(b bool) *Request {
 }
 
 // SetCookie method appends a single cookie in the current request instance.
-// 		client.R().SetCookie(&http.Cookie{
-// 					Name:"go-resty",
-// 					Value:"This is cookie value",
-// 				})
+//
+//	client.R().SetCookie(&http.Cookie{
+//				Name:"go-resty",
+//				Value:"This is cookie value",
+//			})
 //
 // Note: Method appends the Cookie value into existing Cookie if already existing.
 //
@@ -575,19 +616,20 @@ func (r *Request) SetCookie(hc *http.Cookie) *Request {
 }
 
 // SetCookies method sets an array of cookies in the current request instance.
-// 		cookies := []*http.Cookie{
-// 			&http.Cookie{
-// 				Name:"go-resty-1",
-// 				Value:"This is cookie 1 value",
-// 			},
-// 			&http.Cookie{
-// 				Name:"go-resty-2",
-// 				Value:"This is cookie 2 value",
-// 			},
-// 		}
-//
-//		// Setting a cookies into resty's current request
-// 		client.R().SetCookies(cookies)
+//
+//	cookies := []*http.Cookie{
+//		&http.Cookie{
+//			Name:"go-resty-1",
+//			Value:"This is cookie 1 value",
+//		},
+//		&http.Cookie{
+//			Name:"go-resty-2",
+//			Value:"This is cookie 2 value",
+//		},
+//	}
+//
+//	// Setting a cookies into resty's current request
+//	client.R().SetCookies(cookies)
 //
 // Note: Method appends the Cookie value into existing Cookie if already existing.
 //
@@ -625,11 +667,11 @@ func (r *Request) AddRetryCondition(condition RetryConditionFunc) *Request {
 // EnableTrace method enables trace for the current request
 // using `httptrace.ClientTrace` and provides insights.
 //
-// 		client := resty.New()
+//	client := resty.New()
 //
-// 		resp, err := client.R().EnableTrace().Get("https://httpbin.org/get")
-// 		fmt.Println("Error:", err)
-// 		fmt.Println("Trace Info:", resp.Request.TraceInfo())
+//	resp, err := client.R().EnableTrace().Get("https://httpbin.org/get")
+//	fmt.Println("Error:", err)
+//	fmt.Println("Trace Info:", resp.Request.TraceInfo())
 //
 // See `Client.EnableTrace` available too to get trace info for all requests.
 //
@@ -733,17 +775,19 @@ func (r *Request) Patch(url string) (*Response, error) {
 
 // Send method performs the HTTP request using the method and URL already defined
 // for current `Request`.
-//      req := client.R()
-//      req.Method = resty.GET
-//      req.URL = "http://httpbin.org/get"
-//      resp, err := req.Send()
+//
+//	req := client.R()
+//	req.Method = resty.GET
+//	req.URL = "http://httpbin.org/get"
+//	resp, err := req.Send()
 func (r *Request) Send() (*Response, error) {
 	return r.Execute(r.Method, r.URL)
 }
 
 // Execute method performs the HTTP request with given HTTP method and URL
 // for current `Request`.
-// 		resp, err := client.R().Execute(resty.GET, "http://httpbin.org/get")
+//
+//	resp, err := client.R().Execute(resty.GET, "http://httpbin.org/get")
 func (r *Request) Execute(method, url string) (*Response, error) {
 	var addrs []*net.SRV
 	var resp *Response
@@ -803,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/response.go b/response.go
index 8ae0e10..0356c97 100644
--- a/response.go
+++ b/response.go
@@ -38,6 +38,7 @@ func (r *Response) Body() []byte {
 }
 
 // Status method returns the HTTP status string for the executed request.
+//
 //	Example: 200 OK
 func (r *Response) Status() string {
 	if r.RawResponse == nil {
@@ -47,6 +48,7 @@ func (r *Response) Status() string {
 }
 
 // StatusCode method returns the HTTP status code for the executed request.
+//
 //	Example: 200
 func (r *Response) StatusCode() int {
 	if r.RawResponse == nil {
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)
+}