Repository: go-resty/resty
Pull request #865 (2024-09-22 04:06:14)

Bring changes from v2 to main


Patch:
diff --git a/BUILD.bazel b/BUILD.bazel
index 0799f53..7248abc 100644
--- a/BUILD.bazel
+++ b/BUILD.bazel
@@ -17,6 +17,8 @@ go_library(
         "resty.go",
         "retry.go",
         "trace.go",
+        "transport.go",
+        "transport112.go",
         "transport_js.go",
         "transport_other.go",
         "util.go",
diff --git a/README.md b/README.md
index 0469afc..08350eb 100644
--- a/README.md
+++ b/README.md
@@ -4,16 +4,12 @@
 <p align="center"><a href="#features">Features</a> section describes in detail about Resty capabilities</p>
 </p>
 <p align="center">
-<p align="center"><a href="https://github.com/go-resty/resty/actions/workflows/ci.yml?query=branch%3Amaster"><img src="https://github.com/go-resty/resty/actions/workflows/ci.yml/badge.svg" alt="Build Status"></a> <a href="https://codecov.io/gh/go-resty/resty/branch/master"><img src="https://codecov.io/gh/go-resty/resty/branch/master/graph/badge.svg" alt="Code Coverage"></a> <a href="https://goreportcard.com/report/go-resty/resty"><img src="https://goreportcard.com/badge/go-resty/resty" alt="Go Report Card"></a> <a href="https://github.com/go-resty/resty/releases/latest"><img src="https://img.shields.io/badge/version-2.15.0-blue.svg" alt="Release Version"></a> <a href="https://pkg.go.dev/github.com/go-resty/resty/v2"><img src="https://pkg.go.dev/badge/github.com/go-resty/resty" alt="GoDoc"></a> <a href="LICENSE"><img src="https://img.shields.io/github/license/go-resty/resty.svg" alt="License"></a> <a href="https://github.com/avelino/awesome-go"><img src="https://awesome.re/mentioned-badge.svg" alt="Mentioned in Awesome Go"></a></p>
-</p>
-<p align="center">
-<h4 align="center">Resty Communication Channels</h4>
-<p align="center"><a href="https://gitter.im/go_resty/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge"><img src="https://badges.gitter.im/go_resty/community.svg" alt="Chat on Gitter - Resty Community"></a> <a href="https://twitter.com/go_resty"><img src="https://img.shields.io/badge/twitter-@go__resty-55acee.svg" alt="Twitter @go_resty"></a></p>
+<p align="center"><a href="https://github.com/go-resty/resty/actions/workflows/ci.yml?query=branch%3Av2"><img src="https://github.com/go-resty/resty/actions/workflows/ci.yml/badge.svg?branch=v2" alt="Build Status"></a> <a href="https://app.codecov.io/gh/go-resty/resty/tree/v2"><img src="https://codecov.io/gh/go-resty/resty/branch/v2/graph/badge.svg" alt="Code Coverage"></a> <a href="https://goreportcard.com/report/go-resty/resty"><img src="https://goreportcard.com/badge/go-resty/resty" alt="Go Report Card"></a> <a href="https://github.com/go-resty/resty/releases/latest"><img src="https://img.shields.io/badge/version-2.15.2-blue.svg" alt="Release Version"></a> <a href="https://pkg.go.dev/github.com/go-resty/resty/v2"><img src="https://pkg.go.dev/badge/github.com/go-resty/resty" alt="GoDoc"></a> <a href="LICENSE"><img src="https://img.shields.io/github/license/go-resty/resty.svg" alt="License"></a> <a href="https://github.com/avelino/awesome-go"><img src="https://awesome.re/mentioned-badge.svg" alt="Mentioned in Awesome Go"></a></p>
 </p>
 
 ## News
 
-  * v2.15.0 [released](https://github.com/go-resty/resty/releases/tag/v2.15.0) and tagged on Sep 14, 2024.
+  * v2.15.2 [released](https://github.com/go-resty/resty/releases/tag/v2.15.2) and tagged on Sep 21, 2024.
   * v2.0.0 [released](https://github.com/go-resty/resty/releases/tag/v2.0.0) and tagged on Jul 16, 2019.
   * v1.12.0 [released](https://github.com/go-resty/resty/releases/tag/v1.12.0) and tagged on Feb 27, 2019.
   * v1.0 released and tagged on Sep 25, 2017. - Resty's first version was released on Sep 15, 2015 then it grew gradually as a very handy and helpful library. Its been a two years since first release. I'm very thankful to Resty users and its [contributors](https://github.com/go-resty/resty/graphs/contributors).
@@ -109,7 +105,7 @@ Resty author also published following projects for Go Community.
 
 ```bash
 # Go Modules
-require github.com/go-resty/resty/v2 v2.15.0
+require github.com/go-resty/resty/v2 v2.15.2
 ```
 
 ## Usage
diff --git a/client.go b/client.go
index ffc0279..87a88da 100644
--- a/client.go
+++ b/client.go
@@ -6,11 +6,15 @@ package resty
 
 import (
 	"bytes"
+	"compress/gzip"
 	"crypto/tls"
 	"crypto/x509"
+	"encoding/json"
+	"encoding/xml"
 	"errors"
 	"fmt"
 	"io"
+	"math"
 	"net/http"
 	"net/url"
 	"os"
@@ -58,14 +62,11 @@ var (
 	jsonContentType = "application/json"
 	formContentType = "application/x-www-form-urlencoded"
 
-	jsonKey = "json"
-	xmlKey  = "xml"
-
 	jsonCheck = regexp.MustCompile(`(?i:(application|text)/(.*json.*)(;|$))`)
 	xmlCheck  = regexp.MustCompile(`(?i:(application|text)/(.*xml.*)(;|$))`)
 
 	hdrUserAgentValue = "go-resty/" + Version + " (https://github.com/go-resty/resty)"
-	bufPool           = &sync.Pool{New: func() any { return &bytes.Buffer{} }}
+	bufPool           = &sync.Pool{New: func() interface{} { return &bytes.Buffer{} }}
 )
 
 type (
@@ -91,121 +92,69 @@ type (
 	SuccessHook func(*Client, *Response)
 )
 
-// TransportSettings struct is used to define custom dialer and transport
-// values for the Resty client. Please refer to individual
-// struct fields to know the default values.
-//
-// Also, refer to https://pkg.go.dev/net/http#Transport for more details.
-type TransportSettings struct {
-	// DialerTimeout, default value is `30` seconds.
-	DialerTimeout time.Duration
-
-	// DialerKeepAlive, default value is `30` seconds.
-	DialerKeepAlive time.Duration
-
-	// IdleConnTimeout, default value is `90` seconds.
-	IdleConnTimeout time.Duration
-
-	// TLSHandshakeTimeout, default value is `10` seconds.
-	TLSHandshakeTimeout time.Duration
-
-	// ExpectContinueTimeout, default value is `1` seconds.
-	ExpectContinueTimeout time.Duration
-
-	// ResponseHeaderTimeout, added to provide ability to
-	// set value. No default value in Resty, the Go
-	// HTTP client default value applies.
-	ResponseHeaderTimeout time.Duration
-
-	// MaxIdleConns, default value is `100`.
-	MaxIdleConns int
-
-	// MaxIdleConnsPerHost, default value is `runtime.GOMAXPROCS(0) + 1`.
-	MaxIdleConnsPerHost int
-
-	// DisableKeepAlives, default value is `false`.
-	DisableKeepAlives bool
-
-	// DisableCompression, default value is `false`.
-	DisableCompression bool
-
-	// MaxResponseHeaderBytes, added to provide ability to
-	// set value. No default value in Resty, the Go
-	// HTTP client default value applies.
-	MaxResponseHeaderBytes int64
-
-	// WriteBufferSize, added to provide ability to
-	// set value. No default value in Resty, the Go
-	// HTTP client default value applies.
-	WriteBufferSize int
-
-	// ReadBufferSize, added to provide ability to
-	// set value. No default value in Resty, the Go
-	// HTTP client default value applies.
-	ReadBufferSize int
-}
-
 // Client struct is used to create a Resty client with client-level settings,
 // these settings apply to all the requests raised from the client.
 //
 // Resty also provides an option to override most of the client settings
 // at [Request] level.
 type Client struct {
-	lock                   *sync.RWMutex
-	baseURL                string
-	queryParams            url.Values
-	formData               url.Values
-	pathParams             map[string]string
-	rawPathParams          map[string]string
-	header                 http.Header
-	userInfo               *User
-	token                  string
-	authScheme             string
-	cookies                []*http.Cookie
-	errorType              reflect.Type
-	debug                  bool
-	disableWarn            bool
-	allowGetMethodPayload  bool
-	retryCount             int
-	retryWaitTime          time.Duration
-	retryMaxWaitTime       time.Duration
-	retryConditions        []RetryConditionFunc
-	retryHooks             []OnRetryFunc
-	retryAfter             RetryAfterFunc
-	retryResetReaders      bool
-	jsonMarshal            func(v any) ([]byte, error)
-	jsonUnmarshal          func(data []byte, v any) error
-	xmlMarshal             func(v any) ([]byte, error)
-	xmlUnmarshal           func(data []byte, v any) error
-	headerAuthorizationKey string
-	responseBodyLimit      int64
-	jsonEscapeHTML         bool
-	setContentLength       bool
-	closeConnection        bool
-	notParseResponse       bool
-	trace                  bool
-	debugBodySizeLimit     int
-	outputDirectory        string
-	scheme                 string
-	log                    Logger
-	httpClient             *http.Client
-	proxyURL               *url.URL
-	requestLog             RequestLogCallback
-	responseLog            ResponseLogCallback
-	rateLimiter            RateLimiter
-	generateCurlOnDebug    bool
-	beforeRequest          []RequestMiddleware
-	udBeforeRequest        []RequestMiddleware
-	afterResponse          []ResponseMiddleware
-	errorHooks             []ErrorHook
-	invalidHooks           []ErrorHook
-	panicHooks             []ErrorHook
-	successHooks           []SuccessHook
-	contentTypeEncoders    map[string]ContentTypeEncoder
-	contentTypeDecoders    map[string]ContentTypeDecoder
-
-	// TODO don't put mutex now, it may go away
-	preReqHook PreRequestHook
+	BaseURL               string
+	HostURL               string // Deprecated: use BaseURL instead. To be removed in v3.0.0 release.
+	QueryParam            url.Values
+	FormData              url.Values
+	PathParams            map[string]string
+	RawPathParams         map[string]string
+	Header                http.Header
+	UserInfo              *User
+	Token                 string
+	AuthScheme            string
+	Cookies               []*http.Cookie
+	Error                 reflect.Type
+	Debug                 bool
+	DisableWarn           bool
+	AllowGetMethodPayload bool
+	RetryCount            int
+	RetryWaitTime         time.Duration
+	RetryMaxWaitTime      time.Duration
+	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)
+	XMLUnmarshal          func(data []byte, v interface{}) error
+
+	// HeaderAuthorizationKey is used to set/access Request Authorization header
+	// value when `SetAuthToken` option is used.
+	HeaderAuthorizationKey string
+	ResponseBodyLimit      int
+
+	jsonEscapeHTML      bool
+	setContentLength    bool
+	closeConnection     bool
+	notParseResponse    bool
+	trace               bool
+	debugBodySizeLimit  int64
+	outputDirectory     string
+	scheme              string
+	log                 Logger
+	httpClient          *http.Client
+	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
+	invalidHooks        []ErrorHook
+	panicHooks          []ErrorHook
+	rateLimiter         RateLimiter
+	generateCurlOnDebug bool
 }
 
 // User type is to hold an username and password information
@@ -217,11 +166,19 @@ type User struct {
 // Client methods
 //___________________________________
 
-// BaseURL method returns the Base URL value from the client instance.
-func (c *Client) BaseURL() string {
-	c.lock.RLock()
-	defer c.lock.RUnlock()
-	return c.baseURL
+// SetHostURL method sets the Host URL in the client instance. It will be used with a request
+// raised from this client with a relative URL
+//
+//	// Setting HTTP address
+//	client.SetHostURL("http://myjeeva.com")
+//
+//	// Setting HTTPS address
+//	client.SetHostURL("https://myjeeva.com")
+//
+// Deprecated: use [Client.SetBaseURL] instead. To be removed in the v3.0.0 release.
+func (c *Client) SetHostURL(url string) *Client {
+	c.SetBaseURL(url)
+	return c
 }
 
 // SetBaseURL method sets the Base URL in the client instance. It will be used with a request
@@ -233,19 +190,11 @@ func (c *Client) BaseURL() string {
 //	// Setting HTTPS address
 //	client.SetBaseURL("https://myjeeva.com")
 func (c *Client) SetBaseURL(url string) *Client {
-	c.lock.Lock()
-	defer c.lock.Unlock()
-	c.baseURL = strings.TrimRight(url, "/")
+	c.BaseURL = strings.TrimRight(url, "/")
+	c.HostURL = c.BaseURL
 	return c
 }
 
-// Header method returns the headers from the client instance.
-func (c *Client) Header() http.Header {
-	c.lock.RLock()
-	defer c.lock.RUnlock()
-	return c.header
-}
-
 // SetHeader method sets a single header field and its value in the client instance.
 // These headers will be applied to all requests from this client instance.
 // Also, it can be overridden by request-level header options.
@@ -258,9 +207,7 @@ func (c *Client) Header() http.Header {
 //		SetHeader("Content-Type", "application/json").
 //		SetHeader("Accept", "application/json")
 func (c *Client) SetHeader(header, value string) *Client {
-	c.lock.Lock()
-	defer c.lock.Unlock()
-	c.header.Set(header, value)
+	c.Header.Set(header, value)
 	return c
 }
 
@@ -277,10 +224,8 @@ func (c *Client) SetHeader(header, value string) *Client {
 //			"Accept": "application/json",
 //		})
 func (c *Client) SetHeaders(headers map[string]string) *Client {
-	c.lock.Lock()
-	defer c.lock.Unlock()
 	for h, v := range headers {
-		c.header.Set(h, v)
+		c.Header.Set(h, v)
 	}
 	return c
 }
@@ -293,9 +238,7 @@ func (c *Client) SetHeaders(headers map[string]string) *Client {
 //		SetHeaderVerbatim("all_lowercase", "available").
 //		SetHeaderVerbatim("UPPERCASE", "available")
 func (c *Client) SetHeaderVerbatim(header, value string) *Client {
-	c.lock.Lock()
-	defer c.lock.Unlock()
-	c.header[header] = []string{value}
+	c.Header[header] = []string{value}
 	return c
 }
 
@@ -306,17 +249,10 @@ func (c *Client) SetHeaderVerbatim(header, value string) *Client {
 //
 //	client.SetCookieJar(nil)
 func (c *Client) SetCookieJar(jar http.CookieJar) *Client {
-	c.GetClient().Jar = jar
+	c.httpClient.Jar = jar
 	return c
 }
 
-// Cookies method returns all cookies registered in the client instance.
-func (c *Client) Cookies() []*http.Cookie {
-	c.lock.RLock()
-	defer c.lock.RUnlock()
-	return c.cookies
-}
-
 // SetCookie method appends a single cookie to the client instance.
 // These cookies will be added to all the requests from this client instance.
 //
@@ -325,9 +261,7 @@ func (c *Client) Cookies() []*http.Cookie {
 //				Value:"This is cookie value",
 //			})
 func (c *Client) SetCookie(hc *http.Cookie) *Client {
-	c.lock.Lock()
-	defer c.lock.Unlock()
-	c.cookies = append(c.cookies, hc)
+	c.Cookies = append(c.Cookies, hc)
 	return c
 }
 
@@ -348,19 +282,10 @@ func (c *Client) SetCookie(hc *http.Cookie) *Client {
 //	// Setting a cookies into resty
 //	client.SetCookies(cookies)
 func (c *Client) SetCookies(cs []*http.Cookie) *Client {
-	c.lock.Lock()
-	defer c.lock.Unlock()
-	c.cookies = append(c.cookies, cs...)
+	c.Cookies = append(c.Cookies, cs...)
 	return c
 }
 
-// QueryParams method returns all query parameters and their values from the client instance.
-func (c *Client) QueryParams() url.Values {
-	c.lock.RLock()
-	defer c.lock.RUnlock()
-	return c.queryParams
-}
-
 // SetQueryParam method sets a single parameter and its value in the client instance.
 // It will be formed as a query string for the request.
 //
@@ -375,9 +300,7 @@ func (c *Client) QueryParams() url.Values {
 //		SetQueryParam("search", "kitchen papers").
 //		SetQueryParam("size", "large")
 func (c *Client) SetQueryParam(param, value string) *Client {
-	c.lock.Lock()
-	defer c.lock.Unlock()
-	c.queryParams.Set(param, value)
+	c.QueryParam.Set(param, value)
 	return c
 }
 
@@ -396,20 +319,12 @@ func (c *Client) SetQueryParam(param, value string) *Client {
 //			"size": "large",
 //		})
 func (c *Client) SetQueryParams(params map[string]string) *Client {
-	// Do not lock here since there is potential deadlock.
 	for p, v := range params {
 		c.SetQueryParam(p, v)
 	}
 	return c
 }
 
-// FormData method returns the form parameters and their values from the client instance.
-func (c *Client) FormData() url.Values {
-	c.lock.RLock()
-	defer c.lock.RUnlock()
-	return c.formData
-}
-
 // SetFormData method sets Form parameters and their values in the client instance.
 // It applies only to HTTP methods `POST` and `PUT`, and the request content type would be set as
 // `application/x-www-form-urlencoded`. These form data will be added to all the requests raised from
@@ -422,23 +337,12 @@ func (c *Client) FormData() url.Values {
 //			"user_id": "3455454545",
 //		})
 func (c *Client) SetFormData(data map[string]string) *Client {
-	c.lock.Lock()
-	defer c.lock.Unlock()
 	for k, v := range data {
-		c.formData.Set(k, v)
+		c.FormData.Set(k, v)
 	}
 	return c
 }
 
-// UserInfo method returns the authorization username and password.
-//
-//	userInfo := client.UserInfo()
-func (c *Client) UserInfo() *User {
-	c.lock.RLock()
-	defer c.lock.RUnlock()
-	return c.userInfo
-}
-
 // SetBasicAuth method sets the basic authentication header in the HTTP request. For Example:
 //
 //	Authorization: Basic <base64-encoded-value>
@@ -452,26 +356,10 @@ func (c *Client) UserInfo() *User {
 //
 // See [Request.SetBasicAuth].
 func (c *Client) SetBasicAuth(username, password string) *Client {
-	c.lock.Lock()
-	defer c.lock.Unlock()
-	c.userInfo = &User{Username: username, Password: password}
+	c.UserInfo = &User{Username: username, Password: password}
 	return c
 }
 
-// Token method returns the auth token value registered in the client instance.
-func (c *Client) Token() string {
-	c.lock.RLock()
-	defer c.lock.RUnlock()
-	return c.token
-}
-
-// HeaderAuthorizationKey method returns the HTTP header name for Authorization from the client instance.
-func (c *Client) HeaderAuthorizationKey() string {
-	c.lock.RLock()
-	defer c.lock.RUnlock()
-	return c.headerAuthorizationKey
-}
-
 // 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 [Client.SetAuthScheme]. For Example:
 //
@@ -486,21 +374,10 @@ func (c *Client) HeaderAuthorizationKey() string {
 //
 // See [Request.SetAuthToken].
 func (c *Client) SetAuthToken(token string) *Client {
-	c.lock.Lock()
-	defer c.lock.Unlock()
-	c.token = token
+	c.Token = token
 	return c
 }
 
-// AuthScheme method returns the auth scheme name set in the client instance.
-//
-// See [Client.SetAuthScheme], [Request.SetAuthScheme].
-func (c *Client) AuthScheme() string {
-	c.lock.RLock()
-	defer c.lock.RUnlock()
-	return c.authScheme
-}
-
 // SetAuthScheme method sets the auth scheme type in the HTTP request. For Example:
 //
 //	Authorization: <auth-scheme-value> <auth-token-value>
@@ -514,19 +391,17 @@ func (c *Client) AuthScheme() string {
 //
 // Information about auth schemes can be found in [RFC 7235], IANA [HTTP Auth schemes].
 //
-// See [Request.SetAuthScheme].
+// See [Request.SetAuthToken].
 //
 // [RFC 7235]: https://tools.ietf.org/html/rfc7235
 // [HTTP Auth schemes]: https://www.iana.org/assignments/http-authschemes/http-authschemes.xhtml#authschemes
 func (c *Client) SetAuthScheme(scheme string) *Client {
-	c.lock.Lock()
-	defer c.lock.Unlock()
-	c.authScheme = scheme
+	c.AuthScheme = scheme
 	return c
 }
 
 // SetDigestAuth method sets the Digest Access auth scheme for the client. If a server responds with 401 and sends
-// a Digest challenge in the WWW-Authenticate header, requests will be resent with the appropriate Authorization header.
+// a Digest challenge in the WWW-Authenticate Header, requests will be resent with the appropriate Authorization Header.
 //
 // For Example: To set the Digest scheme with user "Mufasa" and password "Circle Of Life"
 //
@@ -538,9 +413,7 @@ func (c *Client) SetAuthScheme(scheme string) *Client {
 //
 // [RFC 7616]: https://datatracker.ietf.org/doc/html/rfc7616
 func (c *Client) SetDigestAuth(username, password string) *Client {
-	c.lock.Lock()
 	oldTransport := c.httpClient.Transport
-	c.lock.Unlock()
 	c.OnBeforeRequest(func(c *Client, _ *Request) error {
 		c.httpClient.Transport = &digestTransport{
 			digestCredentials: digestCredentials{username, password},
@@ -557,39 +430,23 @@ func (c *Client) SetDigestAuth(username, password string) *Client {
 
 // R method creates a new request instance; it's used for Get, Post, Put, Delete, Patch, Head, Options, etc.
 func (c *Client) R() *Request {
-	c.lock.RLock()
-	defer c.lock.RUnlock()
 	r := &Request{
-		QueryParams:       url.Values{},
-		FormData:          url.Values{},
-		Header:            http.Header{},
-		Cookies:           make([]*http.Cookie, 0),
-		PathParams:        make(map[string]string),
-		RawPathParams:     make(map[string]string),
-		Debug:             c.debug,
-		AuthScheme:        c.authScheme,
-		Token:             c.token,
-		UserInfo:          c.userInfo,
-		RetryCount:        c.retryCount,
-		RetryWaitTime:     c.retryWaitTime,
-		RetryMaxWaitTime:  c.retryMaxWaitTime,
-		RetryResetReaders: c.retryResetReaders,
-		CloseConnection:   c.closeConnection,
-		NotParseResponse:  c.notParseResponse,
+		QueryParam:    url.Values{},
+		FormData:      url.Values{},
+		Header:        http.Header{},
+		Cookies:       make([]*http.Cookie, 0),
+		PathParams:    map[string]string{},
+		RawPathParams: map[string]string{},
+		Debug:         c.Debug,
 
 		client:              c,
 		multipartFiles:      []*File{},
 		multipartFields:     []*MultipartField{},
 		jsonEscapeHTML:      c.jsonEscapeHTML,
 		log:                 c.log,
-		setContentLength:    c.setContentLength,
-		trace:               c.trace,
-		debugBodySizeLimit:  c.debugBodySizeLimit,
-		responseBodyLimit:   c.responseBodyLimit,
+		responseBodyLimit:   c.ResponseBodyLimit,
 		generateCurlOnDebug: c.generateCurlOnDebug,
 	}
-
-	r.retryConditions = append(r.retryConditions, c.retryConditions...)
 	return r
 }
 
@@ -598,12 +455,6 @@ func (c *Client) NewRequest() *Request {
 	return c.R()
 }
 
-func (c *Client) beforeRequestMiddlewares() []RequestMiddleware {
-	c.lock.RLock()
-	defer c.lock.RUnlock()
-	return c.udBeforeRequest
-}
-
 // OnBeforeRequest method appends a request middleware to the before request chain.
 // The user-defined middlewares are applied before the default Resty request middlewares.
 // After all middlewares have been applied, the request is sent from Resty to the host server.
@@ -615,16 +466,12 @@ func (c *Client) beforeRequestMiddlewares() []RequestMiddleware {
 //			return nil 	// if its successful otherwise return error
 //		})
 func (c *Client) OnBeforeRequest(m RequestMiddleware) *Client {
-	c.lock.Lock()
-	defer c.lock.Unlock()
+	c.udBeforeRequestLock.Lock()
+	defer c.udBeforeRequestLock.Unlock()
+
 	c.udBeforeRequest = append(c.udBeforeRequest, m)
-	return c
-}
 
-func (c *Client) afterResponseMiddlewares() []ResponseMiddleware {
-	c.lock.RLock()
-	defer c.lock.RUnlock()
-	return c.afterResponse
+	return c
 }
 
 // OnAfterResponse method appends response middleware to the after-response chain.
@@ -638,9 +485,11 @@ func (c *Client) afterResponseMiddlewares() []ResponseMiddleware {
 //			return nil 	// if its successful otherwise return error
 //		})
 func (c *Client) OnAfterResponse(m ResponseMiddleware) *Client {
-	c.lock.Lock()
-	defer c.lock.Unlock()
+	c.afterResponseLock.Lock()
+	defer c.afterResponseLock.Unlock()
+
 	c.afterResponse = append(c.afterResponse, m)
+
 	return c
 }
 
@@ -659,8 +508,6 @@ func (c *Client) OnAfterResponse(m ResponseMiddleware) *Client {
 // Out of the [Client.OnSuccess], [Client.OnError], [Client.OnInvalid], [Client.OnPanic]
 // callbacks, exactly one set will be invoked for each call to [Request.Execute] that completes.
 func (c *Client) OnError(h ErrorHook) *Client {
-	c.lock.Lock()
-	defer c.lock.Unlock()
 	c.errorHooks = append(c.errorHooks, h)
 	return c
 }
@@ -671,8 +518,6 @@ func (c *Client) OnError(h ErrorHook) *Client {
 // Out of the [Client.OnSuccess], [Client.OnError], [Client.OnInvalid], [Client.OnPanic]
 // callbacks, exactly one set will be invoked for each call to [Request.Execute] that completes.
 func (c *Client) OnSuccess(h SuccessHook) *Client {
-	c.lock.Lock()
-	defer c.lock.Unlock()
 	c.successHooks = append(c.successHooks, h)
 	return c
 }
@@ -683,8 +528,6 @@ func (c *Client) OnSuccess(h SuccessHook) *Client {
 // Out of the [Client.OnSuccess], [Client.OnError], [Client.OnInvalid], [Client.OnPanic]
 // callbacks, exactly one set will be invoked for each call to [Request.Execute] that completes.
 func (c *Client) OnInvalid(h ErrorHook) *Client {
-	c.lock.Lock()
-	defer c.lock.Unlock()
 	c.invalidHooks = append(c.invalidHooks, h)
 	return c
 }
@@ -698,8 +541,6 @@ func (c *Client) OnInvalid(h ErrorHook) *Client {
 // If an [Client.OnSuccess], [Client.OnError], or [Client.OnInvalid] callback panics,
 // then exactly one rule can be violated.
 func (c *Client) OnPanic(h ErrorHook) *Client {
-	c.lock.Lock()
-	defer c.lock.Unlock()
 	c.panicHooks = append(c.panicHooks, h)
 	return c
 }
@@ -716,69 +557,6 @@ func (c *Client) SetPreRequestHook(h PreRequestHook) *Client {
 	return c
 }
 
-// ContentTypeEncoders method returns all the registered content type encoders.
-func (c *Client) ContentTypeEncoders() map[string]ContentTypeEncoder {
-	c.lock.RLock()
-	defer c.lock.RUnlock()
-	return c.contentTypeEncoders
-}
-
-// AddContentTypeEncoder method adds the user-provided Content-Type encoder into a client.
-//
-// NOTE: It overwrites the encoder function if the given Content-Type key already exists.
-func (c *Client) AddContentTypeEncoder(ct string, e ContentTypeEncoder) *Client {
-	c.lock.Lock()
-	defer c.lock.Unlock()
-	c.contentTypeEncoders[ct] = e
-	return c
-}
-
-func (c *Client) inferContentTypeEncoder(ct ...string) (ContentTypeEncoder, bool) {
-	c.lock.RLock()
-	defer c.lock.RUnlock()
-	for _, v := range ct {
-		if d, f := c.contentTypeEncoders[v]; f {
-			return d, f
-		}
-	}
-	return nil, false
-}
-
-// ContentTypeDecoders method returns all the registered content type decoders.
-func (c *Client) ContentTypeDecoders() map[string]ContentTypeDecoder {
-	c.lock.RLock()
-	defer c.lock.RUnlock()
-	return c.contentTypeDecoders
-}
-
-// AddContentTypeDecoder method adds the user-provided Content-Type decoder into a client.
-//
-// NOTE: It overwrites the decoder function if the given Content-Type key already exists.
-func (c *Client) AddContentTypeDecoder(ct string, d ContentTypeDecoder) *Client {
-	c.lock.Lock()
-	defer c.lock.Unlock()
-	c.contentTypeDecoders[ct] = d
-	return c
-}
-
-func (c *Client) inferContentTypeDecoder(ct ...string) (ContentTypeDecoder, bool) {
-	c.lock.RLock()
-	defer c.lock.RUnlock()
-	for _, v := range ct {
-		if d, f := c.contentTypeDecoders[v]; f {
-			return d, f
-		}
-	}
-	return nil, false
-}
-
-// Debug method returns `true` if the client is in debug mode; otherwise, it is `false`.
-func (c *Client) Debug() bool {
-	c.lock.RLock()
-	defer c.lock.RUnlock()
-	return c.debug
-}
-
 // SetDebug method enables the debug mode on the Resty client. The client logs details
 // of every request and response.
 //
@@ -790,9 +568,7 @@ func (c *Client) Debug() bool {
 //   - For [Response], it logs information such as Status, Response Time, Headers,
 //     and Body if it has one.
 func (c *Client) SetDebug(d bool) *Client {
-	c.lock.Lock()
-	defer c.lock.Unlock()
-	c.debug = d
+	c.Debug = d
 	return c
 }
 
@@ -800,9 +576,7 @@ func (c *Client) SetDebug(d bool) *Client {
 // request body will be logged in debug mode.
 //
 //	client.SetDebugBodyLimit(1000000)
-func (c *Client) SetDebugBodyLimit(sl int) *Client {
-	c.lock.Lock()
-	defer c.lock.Unlock()
+func (c *Client) SetDebugBodyLimit(sl int64) *Client {
 	c.debugBodySizeLimit = sl
 	return c
 }
@@ -810,8 +584,6 @@ func (c *Client) SetDebugBodyLimit(sl int) *Client {
 // OnRequestLog method sets the request log callback to Resty. Registered callback gets
 // called before the resty logs the information.
 func (c *Client) OnRequestLog(rl RequestLogCallback) *Client {
-	c.lock.Lock()
-	defer c.lock.Unlock()
 	if c.requestLog != nil {
 		c.log.Warnf("Overwriting an existing on-request-log callback from=%s to=%s",
 			functionName(c.requestLog), functionName(rl))
@@ -823,8 +595,6 @@ func (c *Client) OnRequestLog(rl RequestLogCallback) *Client {
 // OnResponseLog method sets the response log callback to Resty. Registered callback gets
 // called before the resty logs the information.
 func (c *Client) OnResponseLog(rl ResponseLogCallback) *Client {
-	c.lock.Lock()
-	defer c.lock.Unlock()
 	if c.responseLog != nil {
 		c.log.Warnf("Overwriting an existing on-response-log callback from=%s to=%s",
 			functionName(c.responseLog), functionName(rl))
@@ -833,42 +603,23 @@ func (c *Client) OnResponseLog(rl ResponseLogCallback) *Client {
 	return c
 }
 
-// DisableWarn method returns `true` if the warning message is disabled; otherwise, it is `false`.
-func (c *Client) DisableWarn() bool {
-	c.lock.RLock()
-	defer c.lock.RUnlock()
-	return c.disableWarn
-}
-
 // SetDisableWarn method disables the warning log message on the Resty client.
 //
 // For example, Resty warns users when BasicAuth is used in non-TLS mode.
 //
 //	client.SetDisableWarn(true)
 func (c *Client) SetDisableWarn(d bool) *Client {
-	c.lock.Lock()
-	defer c.lock.Unlock()
-	c.disableWarn = d
+	c.DisableWarn = d
 	return c
 }
 
-// AllowGetMethodPayload method returns `true` if the client is enabled to allow
-// payload with GET method; otherwise, it is `false`.
-func (c *Client) AllowGetMethodPayload() bool {
-	c.lock.RLock()
-	defer c.lock.RUnlock()
-	return c.allowGetMethodPayload
-}
-
 // SetAllowGetMethodPayload method allows the GET method with payload on the Resty client.
 //
 // For example, Resty allows the user to send a request with a payload using the HTTP GET method.
 //
 //	client.SetAllowGetMethodPayload(true)
 func (c *Client) SetAllowGetMethodPayload(a bool) *Client {
-	c.lock.Lock()
-	defer c.lock.Unlock()
-	c.allowGetMethodPayload = a
+	c.AllowGetMethodPayload = a
 	return c
 }
 
@@ -876,8 +627,6 @@ func (c *Client) SetAllowGetMethodPayload(a bool) *Client {
 //
 // Compliant to interface [resty.Logger]
 func (c *Client) SetLogger(l Logger) *Client {
-	c.lock.Lock()
-	defer c.lock.Unlock()
 	c.log = l
 	return c
 }
@@ -889,8 +638,6 @@ func (c *Client) SetLogger(l Logger) *Client {
 //
 // Also, you have the option to enable a particular request. See [Request.SetContentLength]
 func (c *Client) SetContentLength(l bool) *Client {
-	c.lock.Lock()
-	defer c.lock.Unlock()
 	c.setContentLength = l
 	return c
 }
@@ -899,19 +646,10 @@ func (c *Client) SetContentLength(l bool) *Client {
 //
 //	client.SetTimeout(time.Duration(1 * time.Minute))
 func (c *Client) SetTimeout(timeout time.Duration) *Client {
-	c.lock.Lock()
-	defer c.lock.Unlock()
 	c.httpClient.Timeout = timeout
 	return c
 }
 
-// Error method returns the global or client common `Error` object type registered in the Resty.
-func (c *Client) Error() reflect.Type {
-	c.lock.RLock()
-	defer c.lock.RUnlock()
-	return c.errorType
-}
-
 // SetError method registers the global or client common `Error` object into Resty.
 // It is used for automatic unmarshalling if the response status code is greater than 399 and
 // content type is JSON or XML. It can be a pointer or a non-pointer.
@@ -919,21 +657,11 @@ func (c *Client) Error() reflect.Type {
 //	client.SetError(&Error{})
 //	// OR
 //	client.SetError(Error{})
-func (c *Client) SetError(v any) *Client {
-	c.lock.Lock()
-	defer c.lock.Unlock()
-	c.errorType = inferType(v)
+func (c *Client) SetError(err interface{}) *Client {
+	c.Error = typeOf(err)
 	return c
 }
 
-func (c *Client) newErrorInterface() any {
-	e := c.Error()
-	if e == nil {
-		return e
-	}
-	return reflect.New(e).Interface()
-}
-
 // SetRedirectPolicy method sets the redirect policy for the client. Resty provides ready-to-use
 // redirect policies. Wanna create one for yourself, refer to `redirect.go`.
 //
@@ -941,7 +669,7 @@ func (c *Client) newErrorInterface() any {
 //
 //	// Need multiple redirect policies together
 //	client.SetRedirectPolicy(FlexibleRedirectPolicy(20), DomainCheckRedirectPolicy("host1.com", "host2.net"))
-func (c *Client) SetRedirectPolicy(policies ...any) *Client {
+func (c *Client) SetRedirectPolicy(policies ...interface{}) *Client {
 	for _, p := range policies {
 		if _, ok := p.(RedirectPolicy); !ok {
 			c.log.Errorf("%v does not implement resty.RedirectPolicy (missing Apply method)",
@@ -949,8 +677,6 @@ func (c *Client) SetRedirectPolicy(policies ...any) *Client {
 		}
 	}
 
-	c.lock.Lock()
-	defer c.lock.Unlock()
 	c.httpClient.CheckRedirect = func(req *http.Request, via []*http.Request) error {
 		for _, p := range policies {
 			if err := p.(RedirectPolicy).Apply(req, via); err != nil {
@@ -959,121 +685,70 @@ func (c *Client) SetRedirectPolicy(policies ...any) *Client {
 		}
 		return nil // looks good, go ahead
 	}
-	return c
-}
 
-// RetryCount method returns the retry count value from the client instance.
-func (c *Client) RetryCount() int {
-	c.lock.RLock()
-	defer c.lock.RUnlock()
-	return c.retryCount
+	return c
 }
 
 // SetRetryCount method enables retry on Resty client and allows you
 // to set no. of retry count. Resty uses a Backoff mechanism.
 func (c *Client) SetRetryCount(count int) *Client {
-	c.lock.Lock()
-	defer c.lock.Unlock()
-	c.retryCount = count
+	c.RetryCount = count
 	return c
 }
 
-// RetryWaitTime method returns the retry wait time that is used to sleep before
-// retrying the request.
-func (c *Client) RetryWaitTime() time.Duration {
-	c.lock.RLock()
-	defer c.lock.RUnlock()
-	return c.retryWaitTime
-}
-
 // SetRetryWaitTime method sets the default wait time for sleep before retrying
+// request.
 //
 // Default is 100 milliseconds.
 func (c *Client) SetRetryWaitTime(waitTime time.Duration) *Client {
-	c.lock.Lock()
-	defer c.lock.Unlock()
-	c.retryWaitTime = waitTime
+	c.RetryWaitTime = waitTime
 	return c
 }
 
-// RetryMaxWaitTime method returns the retry max wait time that is used to sleep
-// before retrying the request.
-func (c *Client) RetryMaxWaitTime() time.Duration {
-	c.lock.RLock()
-	defer c.lock.RUnlock()
-	return c.retryMaxWaitTime
-}
-
 // SetRetryMaxWaitTime method sets the max wait time for sleep before retrying
+// request.
 //
 // Default is 2 seconds.
 func (c *Client) SetRetryMaxWaitTime(maxWaitTime time.Duration) *Client {
-	c.lock.Lock()
-	defer c.lock.Unlock()
-	c.retryMaxWaitTime = maxWaitTime
+	c.RetryMaxWaitTime = maxWaitTime
 	return c
 }
 
-// RetryAfter method returns the retry after callback function, that is
-// used to calculate wait time between retries if it's registered; otherwise, it is nil.
-func (c *Client) RetryAfter() RetryAfterFunc {
-	c.lock.RLock()
-	defer c.lock.RUnlock()
-	return c.retryAfter
-}
-
 // SetRetryAfter sets a callback to calculate the wait time between retries.
 // Default (nil) implies exponential backoff with jitter
 func (c *Client) SetRetryAfter(callback RetryAfterFunc) *Client {
-	c.lock.Lock()
-	defer c.lock.Unlock()
-	c.retryAfter = callback
+	c.RetryAfter = callback
 	return c
 }
 
 // SetJSONMarshaler method sets the JSON marshaler function to marshal the request body.
 // By default, Resty uses [encoding/json] package to marshal the request body.
-func (c *Client) SetJSONMarshaler(marshaler func(v any) ([]byte, error)) *Client {
-	c.lock.Lock()
-	defer c.lock.Unlock()
-	c.jsonMarshal = marshaler
+func (c *Client) SetJSONMarshaler(marshaler func(v interface{}) ([]byte, error)) *Client {
+	c.JSONMarshal = marshaler
 	return c
 }
 
 // SetJSONUnmarshaler method sets the JSON unmarshaler function to unmarshal the response body.
 // By default, Resty uses [encoding/json] package to unmarshal the response body.
-func (c *Client) SetJSONUnmarshaler(unmarshaler func(data []byte, v any) error) *Client {
-	c.lock.Lock()
-	defer c.lock.Unlock()
-	c.jsonUnmarshal = unmarshaler
+func (c *Client) SetJSONUnmarshaler(unmarshaler func(data []byte, v interface{}) error) *Client {
+	c.JSONUnmarshal = unmarshaler
 	return c
 }
 
 // SetXMLMarshaler method sets the XML marshaler function to marshal the request body.
 // By default, Resty uses [encoding/xml] package to marshal the request body.
-func (c *Client) SetXMLMarshaler(marshaler func(v any) ([]byte, error)) *Client {
-	c.lock.Lock()
-	defer c.lock.Unlock()
-	c.xmlMarshal = marshaler
+func (c *Client) SetXMLMarshaler(marshaler func(v interface{}) ([]byte, error)) *Client {
+	c.XMLMarshal = marshaler
 	return c
 }
 
 // SetXMLUnmarshaler method sets the XML unmarshaler function to unmarshal the response body.
 // By default, Resty uses [encoding/xml] package to unmarshal the response body.
-func (c *Client) SetXMLUnmarshaler(unmarshaler func(data []byte, v any) error) *Client {
-	c.lock.Lock()
-	defer c.lock.Unlock()
-	c.xmlUnmarshal = unmarshaler
+func (c *Client) SetXMLUnmarshaler(unmarshaler func(data []byte, v interface{}) error) *Client {
+	c.XMLUnmarshal = unmarshaler
 	return c
 }
 
-// RetryConditions method returns all the retry condition functions.
-func (c *Client) RetryConditions() []RetryConditionFunc {
-	c.lock.RLock()
-	defer c.lock.RUnlock()
-	return c.retryConditions
-}
-
 // AddRetryCondition method adds a retry condition function to an array of functions
 // that are checked to determine if the request is retried. The request will
 // retry if any functions return true and the error is nil.
@@ -1081,9 +756,7 @@ func (c *Client) RetryConditions() []RetryConditionFunc {
 // NOTE: These retry conditions are applied on all requests made using this Client.
 // For [Request] specific retry conditions, check [Request.AddRetryCondition]
 func (c *Client) AddRetryCondition(condition RetryConditionFunc) *Client {
-	c.lock.Lock()
-	defer c.lock.Unlock()
-	c.retryConditions = append(c.retryConditions, condition)
+	c.RetryConditions = append(c.RetryConditions, condition)
 	return c
 }
 
@@ -1096,35 +769,17 @@ func (c *Client) AddRetryAfterErrorCondition() *Client {
 	return c
 }
 
-// RetryHooks method returns all the retry hook functions.
-func (c *Client) RetryHooks() []OnRetryFunc {
-	c.lock.RLock()
-	defer c.lock.RUnlock()
-	return c.retryHooks
-}
-
 // AddRetryHook adds a side-effecting retry hook to an array of hooks
 // that will be executed on each retry.
 func (c *Client) AddRetryHook(hook OnRetryFunc) *Client {
-	c.lock.Lock()
-	defer c.lock.Unlock()
-	c.retryHooks = append(c.retryHooks, hook)
+	c.RetryHooks = append(c.RetryHooks, hook)
 	return c
 }
 
-// RetryResetReaders method returns true if the retry reset readers are enabled; otherwise, it is nil.
-func (c *Client) RetryResetReaders() bool {
-	c.lock.RLock()
-	defer c.lock.RUnlock()
-	return c.retryResetReaders
-}
-
 // SetRetryResetReaders method enables the Resty client to seek the start of all
 // file readers are given as multipart files if the object implements [io.ReadSeeker].
 func (c *Client) SetRetryResetReaders(b bool) *Client {
-	c.lock.Lock()
-	defer c.lock.Unlock()
-	c.retryResetReaders = b
+	c.RetryResetReaders = b
 	return c
 }
 
@@ -1145,19 +800,10 @@ func (c *Client) SetTLSClientConfig(config *tls.Config) *Client {
 		c.log.Errorf("%v", err)
 		return c
 	}
-	c.lock.Lock()
-	defer c.lock.Unlock()
 	transport.TLSClientConfig = config
 	return c
 }
 
-// ProxyURL method returns the proxy URL if set otherwise nil.
-func (c *Client) ProxyURL() *url.URL {
-	c.lock.RLock()
-	defer c.lock.RUnlock()
-	return c.proxyURL
-}
-
 // SetProxy method sets the Proxy URL and Port for the Resty client.
 //
 //	client.SetProxy("http://proxyserver:8888")
@@ -1176,10 +822,8 @@ func (c *Client) SetProxy(proxyURL string) *Client {
 		return c
 	}
 
-	c.lock.Lock()
 	c.proxyURL = pURL
-	c.lock.Unlock()
-	transport.Proxy = http.ProxyURL(c.ProxyURL())
+	transport.Proxy = http.ProxyURL(c.proxyURL)
 	return c
 }
 
@@ -1192,9 +836,6 @@ func (c *Client) RemoveProxy() *Client {
 		c.log.Errorf("%v", err)
 		return c
 	}
-
-	c.lock.Lock()
-	defer c.lock.Unlock()
 	c.proxyURL = nil
 	transport.Proxy = nil
 	return c
@@ -1207,9 +848,6 @@ func (c *Client) SetCertificates(certs ...tls.Certificate) *Client {
 		c.log.Errorf("%v", err)
 		return c
 	}
-
-	c.lock.Lock()
-	defer c.lock.Unlock()
 	config.Certificates = append(config.Certificates, certs...)
 	return c
 }
@@ -1266,8 +904,6 @@ func (c *Client) handleCAs(scope string, permCerts []byte) {
 		return
 	}
 
-	c.lock.Lock()
-	defer c.lock.Unlock()
 	switch scope {
 	case "root":
 		if config.RootCAs == nil {
@@ -1282,37 +918,19 @@ func (c *Client) handleCAs(scope string, permCerts []byte) {
 	}
 }
 
-// OutputDirectory method returns the output directory value from the client.
-func (c *Client) OutputDirectory() string {
-	c.lock.RLock()
-	defer c.lock.RUnlock()
-	return c.outputDirectory
-}
-
 // SetOutputDirectory method sets the output directory for saving HTTP responses in a file.
 // Resty creates one if the output directory does not exist. This setting is optional,
 // if you plan to use the absolute path in [Request.SetOutput] and can used together.
 //
 //	client.SetOutputDirectory("/save/http/response/here")
 func (c *Client) SetOutputDirectory(dirPath string) *Client {
-	c.lock.Lock()
-	defer c.lock.Unlock()
 	c.outputDirectory = dirPath
 	return c
 }
 
-// RateLimiter method returns the rate limiter interface
-func (c *Client) RateLimiter() RateLimiter {
-	c.lock.RLock()
-	defer c.lock.RUnlock()
-	return c.rateLimiter
-}
-
 // SetRateLimiter sets an optional [RateLimiter]. If set, the rate limiter will control
 // all requests were made by this client.
 func (c *Client) SetRateLimiter(rl RateLimiter) *Client {
-	c.lock.Lock()
-	defer c.lock.Unlock()
 	c.rateLimiter = rl
 	return c
 }
@@ -1333,29 +951,16 @@ func (c *Client) SetRateLimiter(rl RateLimiter) *Client {
 //     take advantage of some of the Resty client settings.
 //   - It overwrites the Resty client transport instance and its configurations.
 func (c *Client) SetTransport(transport http.RoundTripper) *Client {
-	c.lock.Lock()
-	defer c.lock.Unlock()
 	if transport != nil {
 		c.httpClient.Transport = transport
 	}
 	return c
 }
 
-// Scheme method returns custom scheme value from the client.
-//
-//	scheme := client.Scheme()
-func (c *Client) Scheme() string {
-	c.lock.RLock()
-	defer c.lock.RUnlock()
-	return c.scheme
-}
-
 // SetScheme method sets a custom scheme for the Resty client. It's a way to override the default.
 //
 //	client.SetScheme("http")
 func (c *Client) SetScheme(scheme string) *Client {
-	c.lock.Lock()
-	defer c.lock.Unlock()
 	if !IsStringEmpty(scheme) {
 		c.scheme = strings.TrimSpace(scheme)
 	}
@@ -1364,11 +969,7 @@ func (c *Client) SetScheme(scheme string) *Client {
 
 // SetCloseConnection method sets variable `Close` in HTTP request struct with the given
 // value. More info: https://golang.org/src/net/http/request.go
-//
-// It can be overridden at the request level, see [Request.SetCloseConnection]
 func (c *Client) SetCloseConnection(close bool) *Client {
-	c.lock.Lock()
-	defer c.lock.Unlock()
 	c.closeConnection = close
 	return c
 }
@@ -1381,21 +982,10 @@ func (c *Client) SetCloseConnection(close bool) *Client {
 // NOTE: [Response] middlewares are not executed using this option. You have
 // taken over the control of response parsing from Resty.
 func (c *Client) SetDoNotParseResponse(notParse bool) *Client {
-	c.lock.Lock()
-	defer c.lock.Unlock()
 	c.notParseResponse = notParse
 	return c
 }
 
-// PathParams method returns the path parameters from the client.
-//
-//	pathParams := client.PathParams()
-func (c *Client) PathParams() map[string]string {
-	c.lock.RLock()
-	defer c.lock.RUnlock()
-	return c.pathParams
-}
-
 // SetPathParam method sets a single URL path key-value pair in the
 // Resty client instance.
 //
@@ -1411,9 +1001,7 @@ func (c *Client) PathParams() map[string]string {
 // It can be overridden at the request level,
 // see [Request.SetPathParam] or [Request.SetPathParams]
 func (c *Client) SetPathParam(param, value string) *Client {
-	c.lock.Lock()
-	defer c.lock.Unlock()
-	c.pathParams[param] = value
+	c.PathParams[param] = value
 	return c
 }
 
@@ -1442,13 +1030,6 @@ func (c *Client) SetPathParams(params map[string]string) *Client {
 	return c
 }
 
-// RawPathParams method returns the raw path parameters from the client.
-func (c *Client) RawPathParams() map[string]string {
-	c.lock.RLock()
-	defer c.lock.RUnlock()
-	return c.rawPathParams
-}
-
 // SetRawPathParam method sets a single URL path key-value pair in the
 // Resty client instance.
 //
@@ -1470,9 +1051,7 @@ func (c *Client) RawPathParams() map[string]string {
 // It can be overridden at the request level,
 // see [Request.SetRawPathParam] or [Request.SetRawPathParams]
 func (c *Client) SetRawPathParam(param, value string) *Client {
-	c.lock.Lock()
-	defer c.lock.Unlock()
-	c.rawPathParams[param] = value
+	c.RawPathParams[param] = value
 	return c
 }
 
@@ -1502,26 +1081,16 @@ func (c *Client) SetRawPathParams(params map[string]string) *Client {
 }
 
 // SetJSONEscapeHTML method enables or disables the HTML escape on JSON marshal.
-// By default, escape HTML is `true`.
+// By default, escape HTML is false.
 //
 // NOTE: This option only applies to the standard JSON Marshaller used by Resty.
 //
-// It can be overridden at the request level, see [Request.SetJSONEscapeHTML]
+// It can be overridden at the request level, see [Client.SetJSONEscapeHTML]
 func (c *Client) SetJSONEscapeHTML(b bool) *Client {
-	c.lock.Lock()
-	defer c.lock.Unlock()
 	c.jsonEscapeHTML = b
 	return c
 }
 
-// ResponseBodyLimit method returns the value max body size limit in bytes from
-// the client instance.
-func (c *Client) ResponseBodyLimit() int64 {
-	c.lock.RLock()
-	defer c.lock.RUnlock()
-	return c.responseBodyLimit
-}
-
 // SetResponseBodyLimit method sets a maximum body size limit in bytes on response,
 // avoid reading too much data to memory.
 //
@@ -1533,10 +1102,8 @@ func (c *Client) ResponseBodyLimit() int64 {
 //   - "DoNotParseResponse" is set for client or request.
 //
 // It can be overridden at the request level; see [Request.SetResponseBodyLimit]
-func (c *Client) SetResponseBodyLimit(v int64) *Client {
-	c.lock.Lock()
-	defer c.lock.Unlock()
-	c.responseBodyLimit = v
+func (c *Client) SetResponseBodyLimit(v int) *Client {
+	c.ResponseBodyLimit = v
 	return c
 }
 
@@ -1546,21 +1113,17 @@ func (c *Client) SetResponseBodyLimit(v int64) *Client {
 //	client := resty.New().EnableTrace()
 //
 //	resp, err := client.R().Get("https://httpbin.org/get")
-//	fmt.Println("error:", err)
+//	fmt.Println("Error:", err)
 //	fmt.Println("Trace Info:", resp.Request.TraceInfo())
 //
 // The method [Request.EnableTrace] is also available to get trace info for a single request.
 func (c *Client) EnableTrace() *Client {
-	c.lock.Lock()
-	defer c.lock.Unlock()
 	c.trace = true
 	return c
 }
 
 // DisableTrace method disables the Resty client trace. Refer to [Client.EnableTrace].
 func (c *Client) DisableTrace() *Client {
-	c.lock.Lock()
-	defer c.lock.Unlock()
 	c.trace = false
 	return c
 }
@@ -1585,30 +1148,15 @@ func (c *Client) DisableGenerateCurlOnDebug() *Client {
 // IsProxySet method returns the true is proxy is set from the Resty client; otherwise
 // false. By default, the proxy is set from the environment variable; refer to [http.ProxyFromEnvironment].
 func (c *Client) IsProxySet() bool {
-	return c.ProxyURL() != nil
+	return c.proxyURL != nil
 }
 
 // GetClient method returns the underlying [http.Client] used by the Resty.
 func (c *Client) GetClient() *http.Client {
-	c.lock.RLock()
-	defer c.lock.RUnlock()
 	return c.httpClient
 }
 
-// Transport method returns [http.Transport] currently in use or error
-// in case the currently used `transport` is not a [http.Transport].
-//
-// Since v2.8.0 has become exported method.
-func (c *Client) Transport() (*http.Transport, error) {
-	c.lock.RLock()
-	defer c.lock.RUnlock()
-	if transport, ok := c.httpClient.Transport.(*http.Transport); ok {
-		return transport, nil
-	}
-	return nil, errors.New("current transport is not an *http.Transport instance")
-}
-
-// Clone method returns a clone of the original client.
+// Clone returns a clone of the original client.
 //
 // NOTE: Use with care:
 //   - Interface values are not deeply cloned. Thus, both the original and the
@@ -1620,17 +1168,26 @@ func (c *Client) Clone() *Client {
 	cc := *c
 
 	// lock values should not be copied - thus new values are used.
-	cc.lock = &sync.RWMutex{}
+	cc.afterResponseLock = &sync.RWMutex{}
+	cc.udBeforeRequestLock = &sync.RWMutex{}
 	return &cc
 }
 
 func (c *Client) executeBefore(req *Request) 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
 
 	// user defined on before request methods
 	// to modify the *resty.Request object
-	for _, f := range c.beforeRequestMiddlewares() {
+	for _, f := range c.udBeforeRequest {
 		if err = f(c, req); err != nil {
 			return wrapNoRetryErr(err)
 		}
@@ -1638,8 +1195,8 @@ func (c *Client) executeBefore(req *Request) error {
 
 	// If there is a rate limiter set for this client, the Execute call
 	// will return an error if the rate limit is exceeded.
-	if req.client.RateLimiter() != nil {
-		if !req.client.RateLimiter().Allow() {
+	if req.client.rateLimiter != nil {
+		if !req.client.rateLimiter.Allow() {
 			return wrapNoRetryErr(ErrRateLimitExceeded)
 		}
 	}
@@ -1678,61 +1235,123 @@ func (c *Client) execute(req *Request) (*Response, error) {
 	}
 
 	req.Time = time.Now()
-	resp, err := c.GetClient().Do(req.RawRequest)
+	resp, err := c.httpClient.Do(req.RawRequest)
 
 	response := &Response{
 		Request:     req,
 		RawResponse: resp,
 	}
 
-	response.setReceivedAt()
-	if resp != nil {
-		response.Body = &limitReadCloser{
-			r: resp.Body,
-			l: req.responseBodyLimit,
-			f: func(s int64) {
-				response.size = s
-			},
+	if err != nil || req.notParseResponse || c.notParseResponse {
+		response.setReceivedAt()
+		if logErr := responseLogger(c, response); logErr != nil {
+			return response, wrapErrors(logErr, err)
 		}
-	}
-
-	if err != nil || req.NotParseResponse { // error or do not parse response
-		logErr := responseLogger(c, response)
 		if err != nil {
-			return response, errors.Join(err, logErr)
+			return response, err
 		}
-		return response, wrapNoRetryErr(logErr)
+		return response, nil
 	}
 
-	// Apply Response middleware
-	for _, f := range c.afterResponseMiddlewares() {
-		if err = f(c, response); err != nil {
+	if !req.isSaveResponse {
+		defer closeq(resp.Body)
+		body := resp.Body
+
+		// GitHub #142 & #187
+		if strings.EqualFold(resp.Header.Get(hdrContentEncodingKey), "gzip") && resp.ContentLength != 0 {
+			if _, ok := body.(*gzip.Reader); !ok {
+				body, err = gzip.NewReader(body)
+				if err != nil {
+					err = wrapErrors(responseLogger(c, response), err)
+					response.setReceivedAt()
+					return response, err
+				}
+				defer closeq(body)
+			}
+		}
+
+		if response.body, err = readAllWithLimit(body, req.responseBodyLimit); err != nil {
+			err = wrapErrors(responseLogger(c, response), err)
+			response.setReceivedAt()
 			return response, err
 		}
+
+		response.size = int64(len(response.body))
 	}
-	// TODO figure out debug response logger with body copy, etc.
+
+	response.setReceivedAt() // after we read the body
+
+	// Apply Response middleware
 	err = responseLogger(c, response)
 	if err != nil {
 		return response, wrapNoRetryErr(err)
 	}
 
+	for _, f := range c.afterResponse {
+		if err = f(c, response); err != nil {
+			break
+		}
+	}
+
 	return response, wrapNoRetryErr(err)
 }
 
+var ErrResponseBodyTooLarge = errors.New("resty: response body too large")
+
+// https://github.com/golang/go/issues/51115
+// [io.LimitedReader] can only return [io.EOF]
+func readAllWithLimit(r io.Reader, maxSize int) ([]byte, error) {
+	if maxSize <= 0 {
+		return io.ReadAll(r)
+	}
+
+	var buf [512]byte // make buf stack allocated
+	result := make([]byte, 0, 512)
+	total := 0
+	for {
+		n, err := r.Read(buf[:])
+		total += n
+		if total > maxSize {
+			return nil, ErrResponseBodyTooLarge
+		}
+
+		if err != nil {
+			if err == io.EOF {
+				result = append(result, buf[:n]...)
+				break
+			}
+			return nil, err
+		}
+
+		result = append(result, buf[:n]...)
+	}
+
+	return result, nil
+}
+
 // getting TLS client config if not exists then create one
 func (c *Client) tlsConfig() (*tls.Config, error) {
 	transport, err := c.Transport()
 	if err != nil {
 		return nil, err
 	}
-	c.lock.Lock()
-	defer c.lock.Unlock()
 	if transport.TLSClientConfig == nil {
 		transport.TLSClientConfig = &tls.Config{}
 	}
 	return transport.TLSClientConfig, nil
 }
 
+// Transport method returns [http.Transport] currently in use or error
+// in case the currently used `transport` is not a [http.Transport].
+//
+// Since v2.8.0 has become exported method.
+func (c *Client) Transport() (*http.Transport, error) {
+	if transport, ok := c.httpClient.Transport.(*http.Transport); ok {
+		return transport, nil
+	}
+	return nil, errors.New("current transport is not an *http.Transport instance")
+}
+
 // just an internal helper method
 func (c *Client) outputLogTo(w io.Writer) *Client {
 	c.log.(*logger).l.SetOutput(w)
@@ -1758,8 +1377,6 @@ func (e *ResponseError) Unwrap() error {
 // It wraps the error in a [ResponseError] if the resp is not nil
 // so hooks can access it.
 func (c *Client) onErrorHooks(req *Request, resp *Response, err error) {
-	c.lock.RLock()
-	defer c.lock.RUnlock()
 	if err != nil {
 		if resp != nil { // wrap with ResponseError
 			err = &ResponseError{Response: resp, Err: err}
@@ -1776,8 +1393,6 @@ func (c *Client) onErrorHooks(req *Request, resp *Response, err error) {
 
 // Helper to run panicHooks hooks.
 func (c *Client) onPanicHooks(req *Request, err error) {
-	c.lock.RLock()
-	defer c.lock.RUnlock()
 	for _, h := range c.panicHooks {
 		h(req, err)
 	}
@@ -1785,8 +1400,6 @@ func (c *Client) onPanicHooks(req *Request, err error) {
 
 // Helper to run invalidHooks hooks.
 func (c *Client) onInvalidHooks(req *Request, err error) {
-	c.lock.RLock()
-	defer c.lock.RUnlock()
 	for _, h := range c.invalidHooks {
 		h(req, err)
 	}
@@ -1819,3 +1432,55 @@ type MultipartField struct {
 	ContentType string
 	io.Reader
 }
+
+func createClient(hc *http.Client) *Client {
+	if hc.Transport == nil {
+		hc.Transport = createTransport(nil)
+	}
+
+	c := &Client{ // not setting lang default values
+		QueryParam:             url.Values{},
+		FormData:               url.Values{},
+		Header:                 http.Header{},
+		Cookies:                make([]*http.Cookie, 0),
+		RetryWaitTime:          defaultWaitTime,
+		RetryMaxWaitTime:       defaultMaxWaitTime,
+		PathParams:             make(map[string]string),
+		RawPathParams:          make(map[string]string),
+		JSONMarshal:            json.Marshal,
+		JSONUnmarshal:          json.Unmarshal,
+		XMLMarshal:             xml.Marshal,
+		XMLUnmarshal:           xml.Unmarshal,
+		HeaderAuthorizationKey: http.CanonicalHeaderKey("Authorization"),
+
+		jsonEscapeHTML:      true,
+		httpClient:          hc,
+		debugBodySizeLimit:  math.MaxInt32,
+		udBeforeRequestLock: &sync.RWMutex{},
+		afterResponseLock:   &sync.RWMutex{},
+	}
+
+	// Logger
+	c.SetLogger(createLogger())
+
+	// default before request middlewares
+	c.beforeRequest = []RequestMiddleware{
+		parseRequestURL,
+		parseRequestHeader,
+		parseRequestBody,
+		createHTTPRequest,
+		addCredentials,
+		createCurlCmd,
+	}
+
+	// user defined request middlewares
+	c.udBeforeRequest = []RequestMiddleware{}
+
+	// default after response middlewares
+	c.afterResponse = []ResponseMiddleware{
+		parseResponseBody,
+		saveResponseIntoFile,
+	}
+
+	return c
+}
diff --git a/go.mod b/go.mod
index 47a301a..8f69113 100644
--- a/go.mod
+++ b/go.mod
@@ -1,4 +1,4 @@
-module github.com/go-resty/resty/v3
+module github.com/go-resty/resty/v2
 
 go 1.20
 
diff --git a/middleware.go b/middleware.go
index 13efc1e..84f0fde 100644
--- a/middleware.go
+++ b/middleware.go
@@ -27,14 +27,14 @@ const debugRequestLogKey = "__restyDebugRequestLog"
 //_______________________________________________________________________
 
 func parseRequestURL(c *Client, r *Request) error {
-	if l := len(c.PathParams()) + len(c.RawPathParams()) + len(r.PathParams) + len(r.RawPathParams); l > 0 {
+	if l := len(c.PathParams) + len(c.RawPathParams) + len(r.PathParams) + len(r.RawPathParams); l > 0 {
 		params := make(map[string]string, l)
 
 		// GitHub #103 Path Params
 		for p, v := range r.PathParams {
 			params[p] = url.PathEscape(v)
 		}
-		for p, v := range c.PathParams() {
+		for p, v := range c.PathParams {
 			if _, ok := params[p]; !ok {
 				params[p] = url.PathEscape(v)
 			}
@@ -46,7 +46,7 @@ func parseRequestURL(c *Client, r *Request) error {
 				params[p] = v
 			}
 		}
-		for p, v := range c.RawPathParams() {
+		for p, v := range c.RawPathParams {
 			if _, ok := params[p]; !ok {
 				params[p] = v
 			}
@@ -114,37 +114,42 @@ func parseRequestURL(c *Client, r *Request) error {
 			r.URL = "/" + r.URL
 		}
 
-		reqURL, err = url.Parse(c.BaseURL() + r.URL)
+		// TODO: change to use c.BaseURL only in v3.0.0
+		baseURL := c.BaseURL
+		if len(baseURL) == 0 {
+			baseURL = c.HostURL
+		}
+		reqURL, err = url.Parse(baseURL + r.URL)
 		if err != nil {
 			return err
 		}
 	}
 
 	// GH #407 && #318
-	if reqURL.Scheme == "" && len(c.Scheme()) > 0 {
-		reqURL.Scheme = c.Scheme()
+	if reqURL.Scheme == "" && len(c.scheme) > 0 {
+		reqURL.Scheme = c.scheme
 	}
 
 	// Adding Query Param
-	if len(c.QueryParams())+len(r.QueryParams) > 0 {
-		for k, v := range c.QueryParams() {
+	if len(c.QueryParam)+len(r.QueryParam) > 0 {
+		for k, v := range c.QueryParam {
 			// skip query parameter if it was set in request
-			if _, ok := r.QueryParams[k]; ok {
+			if _, ok := r.QueryParam[k]; ok {
 				continue
 			}
 
-			r.QueryParams[k] = v[:]
+			r.QueryParam[k] = v[:]
 		}
 
 		// GitHub #123 Preserve query string order partially.
 		// Since not feasible in `SetQuery*` resty methods, because
 		// standard package `url.Encode(...)` sorts the query params
 		// alphabetically
-		if len(r.QueryParams) > 0 {
+		if len(r.QueryParam) > 0 {
 			if IsStringEmpty(reqURL.RawQuery) {
-				reqURL.RawQuery = r.QueryParams.Encode()
+				reqURL.RawQuery = r.QueryParam.Encode()
 			} else {
-				reqURL.RawQuery = reqURL.RawQuery + "&" + r.QueryParams.Encode()
+				reqURL.RawQuery = reqURL.RawQuery + "&" + r.QueryParam.Encode()
 			}
 		}
 	}
@@ -155,7 +160,7 @@ func parseRequestURL(c *Client, r *Request) error {
 }
 
 func parseRequestHeader(c *Client, r *Request) error {
-	for k, v := range c.Header() {
+	for k, v := range c.Header {
 		if _, ok := r.Header[k]; ok {
 			continue
 		}
@@ -174,15 +179,17 @@ func parseRequestHeader(c *Client, r *Request) error {
 }
 
 func parseRequestBody(c *Client, r *Request) error {
-	if isPayloadSupported(r.Method, c.AllowGetMethodPayload()) {
+	if isPayloadSupported(r.Method, c.AllowGetMethodPayload) {
 		switch {
 		case r.isMultiPart: // Handling Multipart
 			if err := handleMultipart(c, r); err != nil {
 				return err
 			}
-		case len(c.FormData()) > 0 || len(r.FormData) > 0: // Handling Form Data
+		case len(c.FormData) > 0 || len(r.FormData) > 0: // Handling Form Data
 			handleFormData(c, r)
 		case r.Body != nil: // Handling Request body
+			handleContentType(c, r)
+
 			if err := handleRequestBody(c, r); err != nil {
 				return err
 			}
@@ -190,7 +197,7 @@ func parseRequestBody(c *Client, r *Request) error {
 	}
 
 	// by default resty won't set content length, you can if you want to :)
-	if r.setContentLength {
+	if c.setContentLength || r.setContentLength {
 		if r.bodyBuf == nil {
 			r.Header.Set(hdrContentLengthKey, "0")
 		} else {
@@ -203,9 +210,9 @@ func parseRequestBody(c *Client, r *Request) error {
 
 func createHTTPRequest(c *Client, r *Request) (err error) {
 	if r.bodyBuf == nil {
-		if reader, ok := r.Body.(io.Reader); ok && isPayloadSupported(r.Method, c.AllowGetMethodPayload()) {
+		if reader, ok := r.Body.(io.Reader); ok && isPayloadSupported(r.Method, c.AllowGetMethodPayload) {
 			r.RawRequest, err = http.NewRequest(r.Method, r.URL, reader)
-		} else if r.setContentLength {
+		} else if c.setContentLength || r.setContentLength {
 			r.RawRequest, err = http.NewRequest(r.Method, r.URL, http.NoBody)
 		} else {
 			r.RawRequest, err = http.NewRequest(r.Method, r.URL, nil)
@@ -221,13 +228,13 @@ func createHTTPRequest(c *Client, r *Request) (err error) {
 	}
 
 	// Assign close connection option
-	r.RawRequest.Close = r.CloseConnection
+	r.RawRequest.Close = c.closeConnection
 
 	// Add headers into http request
 	r.RawRequest.Header = r.Header
 
 	// Add cookies from client instance into http request
-	for _, cookie := range c.Cookies() {
+	for _, cookie := range c.Cookies {
 		r.RawRequest.AddCookie(cookie)
 	}
 
@@ -237,7 +244,7 @@ func createHTTPRequest(c *Client, r *Request) (err error) {
 	}
 
 	// Enable trace
-	if r.trace {
+	if c.trace || r.trace {
 		r.clientTrace = &clientTrace{}
 		r.ctx = r.clientTrace.createContext(r.Context())
 	}
@@ -266,26 +273,35 @@ func createHTTPRequest(c *Client, r *Request) (err error) {
 func addCredentials(c *Client, r *Request) error {
 	var isBasicAuth bool
 	// Basic Auth
-	if r.UserInfo != nil {
+	if r.UserInfo != nil { // takes precedence
 		r.RawRequest.SetBasicAuth(r.UserInfo.Username, r.UserInfo.Password)
 		isBasicAuth = true
+	} else if c.UserInfo != nil {
+		r.RawRequest.SetBasicAuth(c.UserInfo.Username, c.UserInfo.Password)
+		isBasicAuth = true
 	}
 
-	if !c.disableWarn {
+	if !c.DisableWarn {
 		if isBasicAuth && !strings.HasPrefix(r.URL, "https") {
 			r.log.Warnf("Using Basic Auth in HTTP mode is not secure, use HTTPS")
 		}
 	}
 
-	// Build the token Auth header
-	if !IsStringEmpty(r.Token) {
-		var authScheme string
-		if IsStringEmpty(r.AuthScheme) {
-			authScheme = "Bearer"
-		} else {
-			authScheme = r.AuthScheme
-		}
-		r.RawRequest.Header.Set(c.HeaderAuthorizationKey(), authScheme+" "+r.Token)
+	// Set the Authorization Header Scheme
+	var authScheme string
+	if !IsStringEmpty(r.AuthScheme) {
+		authScheme = r.AuthScheme
+	} else if !IsStringEmpty(c.AuthScheme) {
+		authScheme = c.AuthScheme
+	} else {
+		authScheme = "Bearer"
+	}
+
+	// Build the Token Auth header
+	if !IsStringEmpty(r.Token) { // takes precedence
+		r.RawRequest.Header.Set(c.HeaderAuthorizationKey, authScheme+" "+r.Token)
+	} else if !IsStringEmpty(c.Token) {
+		r.RawRequest.Header.Set(c.HeaderAuthorizationKey, authScheme+" "+c.Token)
 	}
 
 	return nil
@@ -296,7 +312,7 @@ func createCurlCmd(c *Client, r *Request) (err error) {
 		if r.resultCurlCmd == nil {
 			r.resultCurlCmd = new(string)
 		}
-		*r.resultCurlCmd = buildCurlRequest(r.RawRequest, c.GetClient().Jar)
+		*r.resultCurlCmd = buildCurlRequest(r.RawRequest, c.httpClient.Jar)
 	}
 	return nil
 }
@@ -315,7 +331,7 @@ func requestLogger(c *Client, r *Request) error {
 				}
 			}
 		}
-		rl := &RequestLog{Header: rh, Body: r.fmtBodyString(r.debugBodySizeLimit)}
+		rl := &RequestLog{Header: rh, Body: r.fmtBodyString(c.debugBodySizeLimit)}
 		if c.requestLog != nil {
 			if err := c.requestLog(rl); err != nil {
 				return err
@@ -349,10 +365,8 @@ func requestLogger(c *Client, r *Request) error {
 
 func responseLogger(c *Client, res *Response) error {
 	if res.Request.Debug {
-		rl := &ResponseLog{Header: copyHeaders(res.Header()), Body: res.fmtBodyString(res.Request.debugBodySizeLimit)}
+		rl := &ResponseLog{Header: copyHeaders(res.Header()), Body: res.fmtBodyString(c.debugBodySizeLimit)}
 		if c.responseLog != nil {
-			c.lock.RLock()
-			defer c.lock.RUnlock()
 			if err := c.responseLog(rl); err != nil {
 				return err
 			}
@@ -380,54 +394,38 @@ func responseLogger(c *Client, res *Response) error {
 }
 
 func parseResponseBody(c *Client, res *Response) (err error) {
-	if res.Request.isSaveResponse {
-		return // move on
-	}
-
 	if res.StatusCode() == http.StatusNoContent {
 		res.Request.Error = nil
 		return
 	}
-
-	// TODO Attention Required when working on Compression
-
-	rct := firstNonEmpty(
-		res.Request.forceContentType,
-		res.Header().Get(hdrContentTypeKey),
-		res.Request.fallbackContentType,
-	)
-	decKey := inferContentTypeMapKey(rct)
-	decFunc, found := c.inferContentTypeDecoder(rct, decKey)
-	if !found {
-		// the Content-Type decoder is not found; just read all the body bytes
-		err = res.readAllBytes()
-		return
-	}
-
-	// HTTP status code > 199 and < 300, considered as Result
-	if res.IsSuccess() && res.Request.Result != nil {
-		res.Request.Error = nil
-		defer closeq(res.Body)
-		err = decFunc(res.Body, res.Request.Result)
-		return
-	}
-
-	// HTTP status code > 399, considered as Error
-	if res.IsError() {
-		// global error type registered at client-instance
-		if res.Request.Error == nil {
-			res.Request.Error = c.newErrorInterface()
+	// Handles only JSON or XML content type
+	ct := firstNonEmpty(res.Request.forceContentType, res.Header().Get(hdrContentTypeKey), res.Request.fallbackContentType)
+	if IsJSONType(ct) || IsXMLType(ct) {
+		// HTTP status code > 199 and < 300, considered as Result
+		if res.IsSuccess() {
+			res.Request.Error = nil
+			if res.Request.Result != nil {
+				err = Unmarshalc(c, ct, res.body, res.Request.Result)
+				return
+			}
 		}
 
-		if res.Request.Error != nil {
-			defer closeq(res.Body)
-			err = decFunc(res.Body, res.Request.Error)
-			return
+		// HTTP status code > 399, considered as Error
+		if res.IsError() {
+			// global error interface
+			if res.Request.Error == nil && c.Error != nil {
+				res.Request.Error = reflect.New(c.Error).Interface()
+			}
+
+			if res.Request.Error != nil {
+				unmarshalErr := Unmarshalc(c, ct, res.body, res.Request.Error)
+				if unmarshalErr != nil {
+					c.log.Warnf("Cannot unmarshal response body: %s", unmarshalErr)
+				}
+			}
 		}
 	}
 
-	// read all bytes when auto-unmarshal didn't take place
-	err = res.readAllBytes()
 	return
 }
 
@@ -442,7 +440,7 @@ func handleMultipart(c *Client, r *Request) error {
 		}
 	}
 
-	for k, v := range c.FormData() {
+	for k, v := range c.FormData {
 		for _, iv := range v {
 			if err := w.WriteField(k, iv); err != nil {
 				return err
@@ -483,7 +481,7 @@ func handleMultipart(c *Client, r *Request) error {
 }
 
 func handleFormData(c *Client, r *Request) {
-	for k, v := range c.FormData() {
+	for k, v := range c.FormData {
 		if _, ok := r.FormData[k]; ok {
 			continue
 		}
@@ -496,62 +494,58 @@ func handleFormData(c *Client, r *Request) {
 	r.isFormData = true
 }
 
-var ErrUnsupportedRequestBodyKind = errors.New("resty: unsupported request body kind")
-
-func handleRequestBody(c *Client, r *Request) error {
+func handleContentType(c *Client, r *Request) {
 	contentType := r.Header.Get(hdrContentTypeKey)
 	if IsStringEmpty(contentType) {
-		// it is highly recommended that the user provide a request content-type
 		contentType = DetectContentType(r.Body)
 		r.Header.Set(hdrContentTypeKey, contentType)
 	}
+}
 
-	r.bodyBuf = acquireBuffer()
+func handleRequestBody(c *Client, r *Request) error {
+	var bodyBytes []byte
+	r.bodyBuf = nil
 
 	switch body := r.Body.(type) {
 	case io.Reader:
-		// TODO create pass through reader to capture content-length
-		if r.setContentLength { // keep backward compatibility
+		if c.setContentLength || r.setContentLength { // keep backward compatibility
+			r.bodyBuf = acquireBuffer()
 			if _, err := r.bodyBuf.ReadFrom(body); err != nil {
-				releaseBuffer(r.bodyBuf)
 				return err
 			}
 			r.Body = nil
 		} else {
 			// Otherwise buffer less processing for `io.Reader`, sounds good.
-			releaseBuffer(r.bodyBuf)
-			r.bodyBuf = nil
 			return nil
 		}
 	case []byte:
-		r.bodyBuf.Write(body)
+		bodyBytes = body
 	case string:
-		r.bodyBuf.Write([]byte(body))
+		bodyBytes = []byte(body)
 	default:
-		encKey := inferContentTypeMapKey(contentType)
-		if jsonKey == encKey {
-			if !r.jsonEscapeHTML {
-				return encodeJSONEscapeHTML(r.bodyBuf, r.Body, r.jsonEscapeHTML)
-			}
-		} else if xmlKey == encKey {
-			if inferKind(r.Body) != reflect.Struct {
-				releaseBuffer(r.bodyBuf)
-				return ErrUnsupportedRequestBodyKind
-			}
+		contentType := r.Header.Get(hdrContentTypeKey)
+		kind := kindOf(r.Body)
+		var err error
+		if IsJSONType(contentType) && (kind == reflect.Struct || kind == reflect.Map || kind == reflect.Slice) {
+			r.bodyBuf, err = jsonMarshal(c, r, r.Body)
+		} else if IsXMLType(contentType) && (kind == reflect.Struct) {
+			bodyBytes, err = c.XMLMarshal(r.Body)
 		}
-
-		// user registered encoders with resty fallback key
-		encFunc, found := c.inferContentTypeEncoder(contentType, encKey)
-		if !found {
-			releaseBuffer(r.bodyBuf)
-			return fmt.Errorf("resty: content-type encoder not found for %s", contentType)
-		}
-		if err := encFunc(r.bodyBuf, r.Body); err != nil {
-			releaseBuffer(r.bodyBuf)
+		if err != nil {
 			return err
 		}
 	}
 
+	if bodyBytes == nil && r.bodyBuf == nil {
+		return errors.New("unsupported 'Body' type/value")
+	}
+
+	// []byte into Buffer
+	if bodyBytes != nil && r.bodyBuf == nil {
+		r.bodyBuf = acquireBuffer()
+		_, _ = r.bodyBuf.Write(bodyBytes)
+	}
+
 	return nil
 }
 
@@ -559,8 +553,8 @@ func saveResponseIntoFile(c *Client, res *Response) error {
 	if res.Request.isSaveResponse {
 		file := ""
 
-		if len(c.OutputDirectory()) > 0 && !filepath.IsAbs(res.Request.outputFile) {
-			file += c.OutputDirectory() + string(filepath.Separator)
+		if len(c.outputDirectory) > 0 && !filepath.IsAbs(res.Request.outputFile) {
+			file += c.outputDirectory + string(filepath.Separator)
 		}
 
 		file = filepath.Clean(file + res.Request.outputFile)
@@ -575,9 +569,9 @@ func saveResponseIntoFile(c *Client, res *Response) error {
 		defer closeq(outFile)
 
 		// io.Copy reads maximum 32kb size, it is perfect for large file download too
-		defer closeq(res.Body)
+		defer closeq(res.RawResponse.Body)
 
-		written, err := io.Copy(outFile, res.Body)
+		written, err := io.Copy(outFile, res.RawResponse.Body)
 		if err != nil {
 			return err
 		}
diff --git a/request.go b/request.go
index 7620eb7..1834071 100644
--- a/request.go
+++ b/request.go
@@ -27,32 +27,25 @@ import (
 // Resty client. The [Request] provides an option to override client-level
 // settings and also an option for the request composition.
 type Request struct {
-	URL              string
-	Method           string
-	Token            string
-	AuthScheme       string
-	QueryParams      url.Values
-	FormData         url.Values
-	PathParams       map[string]string
-	RawPathParams    map[string]string
-	Header           http.Header
-	Time             time.Time
-	Body             any
-	Result           any
-	Error            any
-	RawRequest       *http.Request
-	SRV              *SRVRecord
-	UserInfo         *User
-	Cookies          []*http.Cookie
-	Debug            bool
-	CloseConnection  bool
-	NotParseResponse bool
-
-	// Retry
-	RetryCount        int
-	RetryWaitTime     time.Duration
-	RetryMaxWaitTime  time.Duration
-	RetryResetReaders bool
+	URL           string
+	Method        string
+	Token         string
+	AuthScheme    string
+	QueryParam    url.Values
+	FormData      url.Values
+	PathParams    map[string]string
+	RawPathParams map[string]string
+	Header        http.Header
+	Time          time.Time
+	Body          interface{}
+	Result        interface{}
+	resultCurlCmd *string
+	Error         interface{}
+	RawRequest    *http.Request
+	SRV           *SRVRecord
+	UserInfo      *User
+	Cookies       []*http.Cookie
+	Debug         bool
 
 	// Attempt is to represent the request attempt made during a Resty
 	// request execution flow, including retry count.
@@ -62,13 +55,14 @@ type Request struct {
 	isFormData          bool
 	setContentLength    bool
 	isSaveResponse      bool
+	notParseResponse    bool
 	jsonEscapeHTML      bool
 	trace               bool
 	outputFile          string
 	fallbackContentType string
 	forceContentType    string
 	ctx                 context.Context
-	values              map[string]any
+	values              map[string]interface{}
 	client              *Client
 	bodyBuf             *bytes.Buffer
 	clientTrace         *clientTrace
@@ -77,9 +71,7 @@ type Request struct {
 	multipartFiles      []*File
 	multipartFields     []*MultipartField
 	retryConditions     []RetryConditionFunc
-	debugBodySizeLimit  int
-	responseBodyLimit   int64
-	resultCurlCmd       *string
+	responseBodyLimit   int
 	generateCurlOnDebug bool
 }
 
@@ -195,7 +187,7 @@ func (r *Request) SetHeaderVerbatim(header, value string) *Request {
 //
 // It overrides the query parameter value set at the client instance level.
 func (r *Request) SetQueryParam(param, value string) *Request {
-	r.QueryParams.Set(param, value)
+	r.QueryParam.Set(param, value)
 	return r
 }
 
@@ -233,7 +225,7 @@ func (r *Request) SetQueryParams(params map[string]string) *Request {
 func (r *Request) SetQueryParamsFromValues(params url.Values) *Request {
 	for p, v := range params {
 		for _, pv := range v {
-			r.QueryParams.Add(p, pv)
+			r.QueryParam.Add(p, pv)
 		}
 	}
 	return r
@@ -250,7 +242,7 @@ func (r *Request) SetQueryString(query string) *Request {
 	if err == nil {
 		for p, v := range params {
 			for _, pv := range v {
-				r.QueryParams.Add(p, pv)
+				r.QueryParam.Add(p, pv)
 			}
 		}
 	} else {
@@ -316,7 +308,7 @@ func (r *Request) SetFormDataFromValues(data url.Values) *Request {
 // 'map` gets marshaled based on the request header `Content-Type`.
 //
 //	client.R().
-//		SetBody(map[string]any{
+//		SetBody(map[string]interface{}{
 //			"username": "jeeva@myjeeva.com",
 //			"password": "welcome2resty",
 //			"address": &Address{
@@ -342,7 +334,7 @@ func (r *Request) SetFormDataFromValues(data url.Values) *Request {
 //		SetBody([]byte("This is my raw request, sent as-is"))
 //
 // and so on.
-func (r *Request) SetBody(body any) *Request {
+func (r *Request) SetBody(body interface{}) *Request {
 	r.Body = body
 	return r
 }
@@ -371,9 +363,9 @@ func (r *Request) SetBody(body any) *Request {
 //
 //	// Can be accessed via -
 //	fmt.Println(response.Result().(*AuthToken))
-func (r *Request) SetResult(v any) *Request {
-	if v != nil {
-		r.Result = getPointer(v)
+func (r *Request) SetResult(res interface{}) *Request {
+	if res != nil {
+		r.Result = getPointer(res)
 	}
 	return r
 }
@@ -392,7 +384,7 @@ func (r *Request) SetResult(v any) *Request {
 //	response.Error().(*AuthError)
 //
 // If this request Error object is nil, Resty will use the client-level error object Type if it is set.
-func (r *Request) SetError(err any) *Request {
+func (r *Request) SetError(err interface{}) *Request {
 	r.Error = getPointer(err)
 	return r
 }
@@ -603,9 +595,7 @@ func (r *Request) SetDigestAuth(username, password string) *Request {
 //		SetOutput("/Users/jeeva/Downloads/ReplyWithHeader-v5.1-beta.zip").
 //		Get("http://bit.ly/1LouEKr")
 //
-// NOTE: In this scenario
-//   - [Response.BodyBytes] might be nil.
-//   - [Response].Body might be already read.
+// NOTE: In this scenario [Response.Body] might be nil.
 func (r *Request) SetOutput(file string) *Request {
 	r.outputFile = file
 	r.isSaveResponse = true
@@ -623,15 +613,6 @@ func (r *Request) SetSRV(srv *SRVRecord) *Request {
 	return r
 }
 
-// SetCloseConnection method sets variable `Close` in HTTP request struct with the given
-// value. More info: https://golang.org/src/net/http/request.go
-//
-// It overrides the value set at the client instance level, see [Client.SetCloseConnection]
-func (r *Request) SetCloseConnection(close bool) *Request {
-	r.CloseConnection = close
-	return r
-}
-
 // SetDoNotParseResponse method instructs Resty not to parse the response body automatically.
 // Resty exposes the raw response body as [io.ReadCloser]. If you use it, do not
 // forget to close the body, otherwise, you might get into connection leaks, and connection
@@ -639,8 +620,8 @@ func (r *Request) SetCloseConnection(close bool) *Request {
 //
 // NOTE: [Response] middlewares are not executed using this option. You have
 // taken over the control of response parsing from Resty.
-func (r *Request) SetDoNotParseResponse(notParse bool) *Request {
-	r.NotParseResponse = notParse
+func (r *Request) SetDoNotParseResponse(parse bool) *Request {
+	r.notParseResponse = parse
 	return r
 }
 
@@ -654,8 +635,8 @@ func (r *Request) SetDoNotParseResponse(notParse bool) *Request {
 //   - [Request.SetOutput] is called to save response data to the file.
 //   - "DoNotParseResponse" is set for client or request.
 //
-// It overrides the value set at the client instance level, see [Client.SetResponseBodyLimit]
-func (r *Request) SetResponseBodyLimit(v int64) *Request {
+// It overrides the value set at the client instance level. see [Client.SetResponseBodyLimit]
+func (r *Request) SetResponseBodyLimit(v int) *Request {
 	r.responseBodyLimit = v
 	return r
 }
@@ -775,7 +756,7 @@ func (r *Request) ForceContentType(contentType string) *Request {
 }
 
 // SetJSONEscapeHTML method enables or disables the HTML escape on JSON marshal.
-// By default, escape HTML is `true`.
+// By default, escape HTML is false.
 //
 // NOTE: This option only applies to the standard JSON Marshaller used by Resty.
 //
@@ -857,36 +838,6 @@ func (r *Request) AddRetryCondition(condition RetryConditionFunc) *Request {
 	return r
 }
 
-// SetRetryCount method enables retry on Resty client and allows you
-// to set no. of retry count. Resty uses a Backoff mechanism.
-func (r *Request) SetRetryCount(count int) *Request {
-	r.RetryCount = count
-	return r
-}
-
-// SetRetryWaitTime method sets the default wait time for sleep before retrying
-//
-// Default is 100 milliseconds.
-func (r *Request) SetRetryWaitTime(waitTime time.Duration) *Request {
-	r.RetryWaitTime = waitTime
-	return r
-}
-
-// SetRetryMaxWaitTime method sets the max wait time for sleep before retrying
-//
-// Default is 2 seconds.
-func (r *Request) SetRetryMaxWaitTime(maxWaitTime time.Duration) *Request {
-	r.RetryMaxWaitTime = maxWaitTime
-	return r
-}
-
-// SetRetryResetReaders method enables the Resty client to seek the start of all
-// file readers are given as multipart files if the object implements [io.ReadSeeker].
-func (r *Request) SetRetryResetReaders(b bool) *Request {
-	r.RetryResetReaders = b
-	return r
-}
-
 //‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
 // HTTP request tracing
 //_______________________________________________________________________
@@ -1063,7 +1014,7 @@ func (r *Request) Execute(method, url string) (*Response, error) {
 	r.Method = method
 	r.URL = r.selectAddr(addrs, url, 0)
 
-	if r.RetryCount == 0 {
+	if r.client.RetryCount == 0 {
 		r.Attempt = 1
 		resp, err = r.client.execute(r)
 		r.client.onErrorHooks(r, resp, unwrapNoRetryErr(err))
@@ -1083,12 +1034,12 @@ func (r *Request) Execute(method, url string) (*Response, error) {
 
 			return resp, err
 		},
-		Retries(r.RetryCount),
-		WaitTime(r.RetryWaitTime),
-		MaxWaitTime(r.RetryMaxWaitTime),
-		RetryConditions(r.retryConditions),
-		RetryHooks(r.client.RetryHooks()),
-		ResetMultipartReaders(r.RetryResetReaders),
+		Retries(r.client.RetryCount),
+		WaitTime(r.client.RetryWaitTime),
+		MaxWaitTime(r.client.RetryMaxWaitTime),
+		RetryConditions(append(r.retryConditions, r.client.RetryConditions...)),
+		RetryHooks(r.client.RetryHooks),
+		ResetMultipartReaders(r.client.RetryResetReaders),
 	)
 
 	if err != nil {
@@ -1099,76 +1050,6 @@ func (r *Request) Execute(method, url string) (*Response, error) {
 	return resp, unwrapNoRetryErr(err)
 }
 
-// Clone returns a deep copy of r with it's context changed to ctx.
-// The method clones some important fields of the request:
-//   - Header: a new header is created and all the values are copied.
-//   - bodyBuf: a new buffer is created and the content is copied.
-//   - RawRequest: a new RawRequest is created and the content is copied.
-//   - ctx: the context is replaced with the new one.
-//
-// The body is not copied, it's a reference to the original body.
-//
-//	request := client.R()
-//	request.SetBody("body")
-//	request.SetHeader("header", "value")
-//	clonedRequest := request.Clone(context.Background())
-func (r *Request) Clone(ctx context.Context) *Request {
-	rr := new(Request)
-	*rr = *r
-
-	// set new context
-	rr.ctx = ctx
-
-	// clone URL values
-	rr.FormData = cloneURLValues(r.FormData)
-	rr.QueryParams = cloneURLValues(r.QueryParams)
-
-	// clone path params
-	if r.PathParams != nil {
-		rr.PathParams = make(map[string]string, len(r.PathParams))
-		for k, v := range r.PathParams {
-			rr.PathParams[k] = v
-		}
-	}
-
-	// clone raw path params
-	if r.RawPathParams != nil {
-		rr.RawPathParams = make(map[string]string, len(r.RawPathParams))
-		for k, v := range r.RawPathParams {
-			rr.RawPathParams[k] = v
-		}
-	}
-
-	// clone basic auth
-	if r.UserInfo != nil {
-		rr.UserInfo = &User{Username: r.UserInfo.Username, Password: r.UserInfo.Password}
-	}
-
-	// clone the SRV record
-	if r.SRV != nil {
-		rr.SRV = &SRVRecord{Service: r.SRV.Service, Domain: r.SRV.Domain}
-	}
-
-	// clone header
-	if r.Header != nil {
-		rr.Header = r.Header.Clone()
-	}
-
-	// copy bodyBuf since it's an interface value
-	// if a request is used, the bodyBuf will be nil and
-	// any clone will have an empty bodyBuf
-	if r.bodyBuf != nil {
-		rr.bodyBuf = acquireBuffer()
-		rr.bodyBuf.Write(r.bodyBuf.Bytes())
-	}
-
-	// copy raw request to reuse it
-	if r.RawRequest != nil {
-		rr.RawRequest = r.RawRequest.Clone(ctx)
-	}
-	return rr
-}
-
 //‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
 // SRVRecord struct
 //_______________________________________________________________________
@@ -1180,9 +1061,9 @@ type SRVRecord struct {
 	Domain  string
 }
 
-func (r *Request) fmtBodyString(sl int) (body string) {
+func (r *Request) fmtBodyString(sl int64) (body string) {
 	body = "***** NO CONTENT *****"
-	if !isPayloadSupported(r.Method, r.client.AllowGetMethodPayload()) {
+	if !isPayloadSupported(r.Method, r.client.AllowGetMethodPayload) {
 		return
 	}
 
@@ -1193,7 +1074,7 @@ func (r *Request) fmtBodyString(sl int) (body string) {
 
 	// multipart or form-data
 	if r.isMultiPart || r.isFormData {
-		bodySize := r.bodyBuf.Len()
+		bodySize := int64(r.bodyBuf.Len())
 		if bodySize > sl {
 			body = fmt.Sprintf("***** REQUEST TOO LARGE (size - %d) *****", bodySize)
 			return
@@ -1210,7 +1091,7 @@ func (r *Request) fmtBodyString(sl int) (body string) {
 	var err error
 
 	contentType := r.Header.Get(hdrContentTypeKey)
-	kind := inferKind(r.Body)
+	kind := kindOf(r.Body)
 	if canJSONMarshal(contentType, kind) {
 		var bodyBuf *bytes.Buffer
 		bodyBuf, err = noescapeJSONMarshalIndent(&r.Body)
@@ -1241,7 +1122,7 @@ func (r *Request) fmtBodyString(sl int) (body string) {
 	}
 
 	if len(body) > 0 {
-		bodySize := len([]byte(body))
+		bodySize := int64(len([]byte(body)))
 		if bodySize > sl {
 			body = fmt.Sprintf("***** REQUEST TOO LARGE (size - %d) *****", bodySize)
 		}
@@ -1259,16 +1140,28 @@ func (r *Request) selectAddr(addrs []*net.SRV, path string, attempt int) string
 	domain := strings.TrimRight(addrs[idx].Target, ".")
 	path = strings.TrimLeft(path, "/")
 
-	return fmt.Sprintf("%s://%s:%d/%s", r.client.Scheme(), domain, addrs[idx].Port, path)
+	return fmt.Sprintf("%s://%s:%d/%s", r.client.scheme, domain, addrs[idx].Port, path)
 }
 
 func (r *Request) initValuesMap() {
 	if r.values == nil {
-		r.values = make(map[string]any)
+		r.values = make(map[string]interface{})
 	}
 }
 
-var noescapeJSONMarshalIndent = func(v any) (*bytes.Buffer, error) {
+var noescapeJSONMarshal = func(v interface{}) (*bytes.Buffer, error) {
+	buf := acquireBuffer()
+	encoder := json.NewEncoder(buf)
+	encoder.SetEscapeHTML(false)
+	if err := encoder.Encode(v); err != nil {
+		releaseBuffer(buf)
+		return nil, err
+	}
+
+	return buf, nil
+}
+
+var noescapeJSONMarshalIndent = func(v interface{}) (*bytes.Buffer, error) {
 	buf := acquireBuffer()
 	encoder := json.NewEncoder(buf)
 	encoder.SetEscapeHTML(false)
diff --git a/response.go b/response.go
index ddc3d91..f52b5c6 100644
--- a/response.go
+++ b/response.go
@@ -5,7 +5,6 @@
 package resty
 
 import (
-	"bytes"
 	"encoding/json"
 	"fmt"
 	"io"
@@ -21,36 +20,31 @@ import (
 // Response struct holds response values of executed requests.
 type Response struct {
 	Request     *Request
-	Body        io.ReadCloser
 	RawResponse *http.Response
 
-	bodyBytes  []byte
+	body       []byte
 	size       int64
 	receivedAt time.Time
 }
 
-// BodyBytes method returns the HTTP response as `[]byte` slice for the executed request.
+// Body method returns the HTTP response as `[]byte` slice for the executed request.
 //
-// NOTE:
-//   - [Response.BodyBytes] might be `nil` if [Request.SetOutput], [Request.SetDoNotParseResponse],
-//     [Client.SetDoNotParseResponse] method is used.
-//   - [Response.BodyBytes] might be `nil` if [Response].Body is already read.
-func (r *Response) BodyBytes() []byte {
+// NOTE: [Response.Body] might be nil if [Request.SetOutput] is used.
+// Also see [Request.SetDoNotParseResponse], [Client.SetDoNotParseResponse]
+func (r *Response) Body() []byte {
 	if r.RawResponse == nil {
 		return []byte{}
 	}
-	return r.bodyBytes
+	return r.body
 }
 
-// SetBodyBytes method sets [Response] body in byte slice. Typically,
+// SetBody method sets [Response] body in byte slice. Typically,
 // It is helpful for test cases.
 //
-//	resp.SetBodyBytes([]byte("This is test body content"))
-//	resp.SetBodyBytes(nil)
-//
-// NOTE: Returns an empty byte slice on auto-unmarshal scenarios
-func (r *Response) SetBodyBytes(b []byte) *Response {
-	r.bodyBytes = b
+//	resp.SetBody([]byte("This is test body content"))
+//	resp.SetBody(nil)
+func (r *Response) SetBody(b []byte) *Response {
+	r.body = b
 	return r
 }
 
@@ -85,14 +79,14 @@ func (r *Response) Proto() string {
 // Result method returns the response value as an object if it has one
 //
 // See [Request.SetResult]
-func (r *Response) Result() any {
+func (r *Response) Result() interface{} {
 	return r.Request.Result
 }
 
 // Error method returns the error object if it has one
 //
 // See [Request.SetError], [Client.SetError]
-func (r *Response) Error() any {
+func (r *Response) Error() interface{} {
 	return r.Request.Error
 }
 
@@ -114,14 +108,11 @@ func (r *Response) Cookies() []*http.Cookie {
 
 // String method returns the body of the HTTP response as a `string`.
 // It returns an empty string if it is nil or the body is zero length.
-//
-// NOTE:
-//   - Returns an empty string on auto-unmarshal scenarios
 func (r *Response) String() string {
-	if len(r.bodyBytes) == 0 {
+	if len(r.body) == 0 {
 		return ""
 	}
-	return strings.TrimSpace(string(r.bodyBytes))
+	return strings.TrimSpace(string(r.body))
 }
 
 // Time method returns the duration of HTTP response time from the request we sent
@@ -179,19 +170,19 @@ func (r *Response) setReceivedAt() {
 	}
 }
 
-func (r *Response) fmtBodyString(sl int) string {
-	if r.Request.NotParseResponse {
+func (r *Response) fmtBodyString(sl int64) string {
+	if r.Request.client.notParseResponse || r.Request.notParseResponse {
 		return "***** DO NOT PARSE RESPONSE - Enabled *****"
 	}
-	if len(r.bodyBytes) > 0 {
-		if len(r.bodyBytes) > sl {
-			return fmt.Sprintf("***** RESPONSE TOO LARGE (size - %d) *****", len(r.bodyBytes))
+	if len(r.body) > 0 {
+		if int64(len(r.body)) > sl {
+			return fmt.Sprintf("***** RESPONSE TOO LARGE (size - %d) *****", len(r.body))
 		}
 		ct := r.Header().Get(hdrContentTypeKey)
 		if IsJSONType(ct) {
 			out := acquireBuffer()
 			defer releaseBuffer(out)
-			err := json.Indent(out, r.bodyBytes, "", "   ")
+			err := json.Indent(out, r.body, "", "   ")
 			if err != nil {
 				return fmt.Sprintf("*** Error: Unable to format response body - \"%s\" ***\n\nLog Body as-is:\n%s", err, r.String())
 			}
@@ -202,12 +193,3 @@ func (r *Response) fmtBodyString(sl int) string {
 
 	return "***** NO CONTENT *****"
 }
-
-// auto-unmarshal didn't happen, so fallback to
-// old behavior of reading response as body bytes
-func (r *Response) readAllBytes() (err error) {
-	defer closeq(r.Body)
-	r.bodyBytes, err = io.ReadAll(r.Body)
-	r.Body = io.NopCloser(bytes.NewReader(r.bodyBytes))
-	return
-}
diff --git a/resty.go b/resty.go
index b92522f..32b84f2 100644
--- a/resty.go
+++ b/resty.go
@@ -6,32 +6,22 @@
 package resty
 
 import (
-	"encoding/json"
-	"encoding/xml"
-	"math"
 	"net"
 	"net/http"
 	"net/http/cookiejar"
-	"net/url"
-	"runtime"
-	"sync"
-	"time"
 
 	"golang.org/x/net/publicsuffix"
 )
 
 // Version # of resty
-const Version = "3.0.0-dev"
+const Version = "2.15.2"
 
 // New method creates a new Resty client.
 func New() *Client {
-	return NewWithTransportSettings(nil)
-}
-
-// NewWithTransportSettings method creates a new Resty client with provided
-// timeout values.
-func NewWithTransportSettings(transportSettings *TransportSettings) *Client {
-	return NewWithDialerAndTransportSettings(nil, transportSettings)
+	cookieJar, _ := cookiejar.New(&cookiejar.Options{PublicSuffixList: publicsuffix.List})
+	return createClient(&http.Client{
+		Jar: cookieJar,
+	})
 }
 
 // NewWithClient method creates a new Resty client with given [http.Client].
@@ -39,175 +29,12 @@ func NewWithClient(hc *http.Client) *Client {
 	return createClient(hc)
 }
 
-// NewWithDialer method creates a new Resty client with given Local Address
-// to dial from.
-func NewWithDialer(dialer *net.Dialer) *Client {
-	return NewWithDialerAndTransportSettings(dialer, nil)
-}
-
 // NewWithLocalAddr method creates a new Resty client with the given Local Address.
-func NewWithLocalAddr(localAddr net.Addr) *Client {
-	return NewWithDialerAndTransportSettings(
-		&net.Dialer{LocalAddr: localAddr},
-		nil,
-	)
-}
-
-// NewWithDialerAndTransportSettings method creates a new Resty client with given Local Address
 // to dial from.
-func NewWithDialerAndTransportSettings(dialer *net.Dialer, transportSettings *TransportSettings) *Client {
+func NewWithLocalAddr(localAddr net.Addr) *Client {
+	cookieJar, _ := cookiejar.New(&cookiejar.Options{PublicSuffixList: publicsuffix.List})
 	return createClient(&http.Client{
-		Jar:       createCookieJar(),
-		Transport: createTransport(dialer, transportSettings),
+		Jar:       cookieJar,
+		Transport: createTransport(localAddr),
 	})
 }
-
-//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
-// Unexported methods
-//_______________________________________________________________________
-
-func createTransport(dialer *net.Dialer, transportSettings *TransportSettings) *http.Transport {
-	if transportSettings == nil {
-		transportSettings = &TransportSettings{}
-	}
-
-	// Dialer
-
-	if dialer == nil {
-		dialer = &net.Dialer{}
-	}
-
-	if transportSettings.DialerTimeout > 0 {
-		dialer.Timeout = transportSettings.DialerTimeout
-	} else {
-		dialer.Timeout = 30 * time.Second
-	}
-
-	if transportSettings.DialerKeepAlive > 0 {
-		dialer.KeepAlive = transportSettings.DialerKeepAlive
-	} else {
-		dialer.KeepAlive = 30 * time.Second
-	}
-
-	// Transport
-	t := &http.Transport{
-		Proxy:              http.ProxyFromEnvironment,
-		DialContext:        transportDialContext(dialer),
-		DisableKeepAlives:  transportSettings.DisableKeepAlives,
-		DisableCompression: transportSettings.DisableCompression,
-		ForceAttemptHTTP2:  true,
-	}
-
-	if transportSettings.IdleConnTimeout > 0 {
-		t.IdleConnTimeout = transportSettings.IdleConnTimeout
-	} else {
-		t.IdleConnTimeout = 90 * time.Second
-	}
-
-	if transportSettings.TLSHandshakeTimeout > 0 {
-		t.TLSHandshakeTimeout = transportSettings.TLSHandshakeTimeout
-	} else {
-		t.TLSHandshakeTimeout = 10 * time.Second
-	}
-
-	if transportSettings.ExpectContinueTimeout > 0 {
-		t.ExpectContinueTimeout = transportSettings.ExpectContinueTimeout
-	} else {
-		t.ExpectContinueTimeout = 1 * time.Second
-	}
-
-	if transportSettings.MaxIdleConns > 0 {
-		t.MaxIdleConns = transportSettings.MaxIdleConns
-	} else {
-		t.MaxIdleConns = 100
-	}
-
-	if transportSettings.MaxIdleConnsPerHost > 0 {
-		t.MaxIdleConnsPerHost = transportSettings.MaxIdleConnsPerHost
-	} else {
-		t.MaxIdleConnsPerHost = runtime.GOMAXPROCS(0) + 1
-	}
-
-	//
-	// No default value in Resty for following settings, added to
-	// provide ability to set value otherwise the Go HTTP client
-	// default value applies.
-	//
-
-	if transportSettings.ResponseHeaderTimeout > 0 {
-		t.ResponseHeaderTimeout = transportSettings.ResponseHeaderTimeout
-	}
-
-	if transportSettings.MaxResponseHeaderBytes > 0 {
-		t.MaxResponseHeaderBytes = transportSettings.MaxResponseHeaderBytes
-	}
-
-	if transportSettings.WriteBufferSize > 0 {
-		t.WriteBufferSize = transportSettings.WriteBufferSize
-	}
-
-	if transportSettings.ReadBufferSize > 0 {
-		t.ReadBufferSize = transportSettings.ReadBufferSize
-	}
-
-	return t
-}
-
-func createCookieJar() *cookiejar.Jar {
-	cookieJar, _ := cookiejar.New(&cookiejar.Options{PublicSuffixList: publicsuffix.List})
-	return cookieJar
-}
-
-func createClient(hc *http.Client) *Client {
-	c := &Client{ // not setting language default values
-		lock:                   &sync.RWMutex{},
-		queryParams:            url.Values{},
-		formData:               url.Values{},
-		header:                 http.Header{},
-		cookies:                make([]*http.Cookie, 0),
-		retryWaitTime:          defaultWaitTime,
-		retryMaxWaitTime:       defaultMaxWaitTime,
-		pathParams:             make(map[string]string),
-		rawPathParams:          make(map[string]string),
-		jsonMarshal:            json.Marshal,
-		jsonUnmarshal:          json.Unmarshal,
-		xmlMarshal:             xml.Marshal,
-		xmlUnmarshal:           xml.Unmarshal,
-		headerAuthorizationKey: hdrAuthorizationKey,
-		jsonEscapeHTML:         true,
-		httpClient:             hc,
-		debugBodySizeLimit:     math.MaxInt32,
-		contentTypeEncoders:    make(map[string]ContentTypeEncoder),
-		contentTypeDecoders:    make(map[string]ContentTypeDecoder),
-	}
-
-	// Logger
-	c.SetLogger(createLogger())
-
-	c.AddContentTypeEncoder(jsonKey, encodeJSON)
-	c.AddContentTypeEncoder(xmlKey, encodeXML)
-
-	c.AddContentTypeDecoder(jsonKey, decodeJSON)
-	c.AddContentTypeDecoder(xmlKey, decodeXML)
-
-	// default before request middlewares
-	c.beforeRequest = []RequestMiddleware{
-		parseRequestURL,
-		parseRequestHeader,
-		parseRequestBody,
-		createHTTPRequest,
-		addCredentials,
-		createCurlCmd,
-	}
-
-	// user defined request middlewares
-	c.udBeforeRequest = []RequestMiddleware{}
-
-	// default after response middlewares
-	c.afterResponse = []ResponseMiddleware{
-		parseResponseBody,
-		saveResponseIntoFile,
-	}
-
-	return c
-}
diff --git a/retry.go b/retry.go
index e7d3960..416107e 100644
--- a/retry.go
+++ b/retry.go
@@ -178,7 +178,7 @@ func sleepDuration(resp *Response, min, max time.Duration, attempt int) (time.Du
 		return jitterBackoff(min, max, attempt), nil
 	}
 
-	retryAfterFunc := resp.Request.client.RetryAfter()
+	retryAfterFunc := resp.Request.client.RetryAfter
 
 	// Check for custom callback
 	if retryAfterFunc == nil {
diff --git a/stream.go b/stream.go
deleted file mode 100644
index 513e93d..0000000
--- a/stream.go
+++ /dev/null
@@ -1,92 +0,0 @@
-// Copyright (c) 2015-present Jeevanandam M (jeeva@myjeeva.com), All rights reserved.
-// resty source code and usage is governed by a MIT style
-// license that can be found in the LICENSE file.
-
-package resty
-
-import (
-	"encoding/json"
-	"encoding/xml"
-	"errors"
-	"io"
-)
-
-type (
-	// ContentTypeEncoder type is for encoding the request body based on header Content-Type
-	ContentTypeEncoder func(w io.Writer, v any) error
-
-	// ContentTypeDecoder type is for decoding the response body based on header Content-Type
-	ContentTypeDecoder func(r io.Reader, v any) error
-)
-
-func encodeJSON(w io.Writer, v any) error {
-	return encodeJSONEscapeHTML(w, v, true)
-}
-
-func encodeJSONEscapeHTML(w io.Writer, v any, esc bool) error {
-	enc := json.NewEncoder(w)
-	enc.SetEscapeHTML(esc)
-	return enc.Encode(v)
-}
-
-func decodeJSON(r io.Reader, v any) error {
-	dec := json.NewDecoder(r)
-	for {
-		if err := dec.Decode(v); err == io.EOF {
-			break
-		} else if err != nil {
-			return err
-		}
-	}
-	return nil
-}
-
-func encodeXML(w io.Writer, v any) error {
-	return xml.NewEncoder(w).Encode(v)
-}
-
-func decodeXML(r io.Reader, v any) error {
-	dec := xml.NewDecoder(r)
-	for {
-		if err := dec.Decode(v); err == io.EOF {
-			break
-		} else if err != nil {
-			return err
-		}
-	}
-	return nil
-}
-
-var ErrReadExceedsThresholdLimit = errors.New("resty: read exceeds the threshold limit")
-
-var _ io.ReadCloser = (*limitReadCloser)(nil)
-
-type limitReadCloser struct {
-	r io.Reader
-	l int64
-	t int64
-	f func(s int64)
-}
-
-func (l *limitReadCloser) Read(p []byte) (n int, err error) {
-	if l.l == 0 {
-		n, err = l.r.Read(p)
-		l.t += int64(n)
-		l.f(l.t)
-		return n, err
-	}
-	if l.t > l.l {
-		return 0, ErrReadExceedsThresholdLimit
-	}
-	n, err = l.r.Read(p)
-	l.t += int64(n)
-	l.f(l.t)
-	return n, err
-}
-
-func (l *limitReadCloser) Close() error {
-	if c, ok := l.r.(io.Closer); ok {
-		return c.Close()
-	}
-	return nil
-}
diff --git a/transport.go b/transport.go
new file mode 100644
index 0000000..13c3de3
--- /dev/null
+++ b/transport.go
@@ -0,0 +1,36 @@
+//go:build go1.13
+// +build go1.13
+
+// Copyright (c) 2015-2024 Jeevanandam M (jeeva@myjeeva.com), All rights reserved.
+// resty source code and usage is governed by a MIT style
+// license that can be found in the LICENSE file.
+
+package resty
+
+import (
+	"net"
+	"net/http"
+	"runtime"
+	"time"
+)
+
+func createTransport(localAddr net.Addr) *http.Transport {
+	dialer := &net.Dialer{
+		Timeout:   30 * time.Second,
+		KeepAlive: 30 * time.Second,
+		DualStack: true,
+	}
+	if localAddr != nil {
+		dialer.LocalAddr = localAddr
+	}
+	return &http.Transport{
+		Proxy:                 http.ProxyFromEnvironment,
+		DialContext:           transportDialContext(dialer),
+		ForceAttemptHTTP2:     true,
+		MaxIdleConns:          100,
+		IdleConnTimeout:       90 * time.Second,
+		TLSHandshakeTimeout:   10 * time.Second,
+		ExpectContinueTimeout: 1 * time.Second,
+		MaxIdleConnsPerHost:   runtime.GOMAXPROCS(0) + 1,
+	}
+}
diff --git a/transport112.go b/transport112.go
new file mode 100644
index 0000000..beb0301
--- /dev/null
+++ b/transport112.go
@@ -0,0 +1,35 @@
+//go:build !go1.13
+// +build !go1.13
+
+// Copyright (c) 2015-2024 Jeevanandam M (jeeva@myjeeva.com), All rights reserved.
+// resty source code and usage is governed by a MIT style
+// license that can be found in the LICENSE file.
+
+package resty
+
+import (
+	"net"
+	"net/http"
+	"runtime"
+	"time"
+)
+
+func createTransport(localAddr net.Addr) *http.Transport {
+	dialer := &net.Dialer{
+		Timeout:   30 * time.Second,
+		KeepAlive: 30 * time.Second,
+		DualStack: true,
+	}
+	if localAddr != nil {
+		dialer.LocalAddr = localAddr
+	}
+	return &http.Transport{
+		Proxy:                 http.ProxyFromEnvironment,
+		DialContext:           dialer.DialContext,
+		MaxIdleConns:          100,
+		IdleConnTimeout:       90 * time.Second,
+		TLSHandshakeTimeout:   10 * time.Second,
+		ExpectContinueTimeout: 1 * time.Second,
+		MaxIdleConnsPerHost:   runtime.GOMAXPROCS(0) + 1,
+	}
+}
diff --git a/util.go b/util.go
index 73faa35..e1a66ff 100644
--- a/util.go
+++ b/util.go
@@ -13,7 +13,6 @@ import (
 	"mime/multipart"
 	"net/http"
 	"net/textproto"
-	"net/url"
 	"os"
 	"path/filepath"
 	"reflect"
@@ -30,9 +29,9 @@ import (
 // Logger interface is to abstract the logging from Resty. Gives control to
 // the Resty users, choice of the logger.
 type Logger interface {
-	Errorf(format string, v ...any)
-	Warnf(format string, v ...any)
-	Debugf(format string, v ...any)
+	Errorf(format string, v ...interface{})
+	Warnf(format string, v ...interface{})
+	Debugf(format string, v ...interface{})
 }
 
 func createLogger() *logger {
@@ -46,19 +45,19 @@ type logger struct {
 	l *log.Logger
 }
 
-func (l *logger) Errorf(format string, v ...any) {
+func (l *logger) Errorf(format string, v ...interface{}) {
 	l.output("ERROR RESTY "+format, v...)
 }
 
-func (l *logger) Warnf(format string, v ...any) {
+func (l *logger) Warnf(format string, v ...interface{}) {
 	l.output("WARN RESTY "+format, v...)
 }
 
-func (l *logger) Debugf(format string, v ...any) {
+func (l *logger) Debugf(format string, v ...interface{}) {
 	l.output("DEBUG RESTY "+format, v...)
 }
 
-func (l *logger) output(format string, v ...any) {
+func (l *logger) output(format string, v ...interface{}) {
 	if len(v) == 0 {
 		l.l.Print(format)
 		return
@@ -86,9 +85,9 @@ func IsStringEmpty(str string) bool {
 }
 
 // DetectContentType method is used to figure out `Request.Body` content type for request header
-func DetectContentType(body any) string {
+func DetectContentType(body interface{}) string {
 	contentType := plainTextType
-	kind := inferKind(body)
+	kind := kindOf(body)
 	switch kind {
 	case reflect.Struct, reflect.Map:
 		contentType = jsonContentType
@@ -97,7 +96,7 @@ func DetectContentType(body any) string {
 	default:
 		if b, ok := body.([]byte); ok {
 			contentType = http.DetectContentType(b)
-		} else if kind == reflect.Slice { // check slice here to differentiate between any slice vs byte slice
+		} else if kind == reflect.Slice {
 			contentType = jsonContentType
 		}
 	}
@@ -115,21 +114,15 @@ func IsXMLType(ct string) bool {
 	return xmlCheck.MatchString(ct)
 }
 
-func isJSONContentType(ct string) bool {
-	return strings.Contains(ct, jsonKey)
-}
-
-func isXMLContentType(ct string) bool {
-	return strings.Contains(ct, xmlKey)
-}
-
-func inferContentTypeMapKey(v string) string {
-	if isJSONContentType(v) {
-		return jsonKey
-	} else if isXMLContentType(v) {
-		return xmlKey
+// Unmarshalc content into object from JSON or XML
+func Unmarshalc(c *Client, ct string, b []byte, d interface{}) (err error) {
+	if IsJSONType(ct) {
+		err = c.JSONUnmarshal(b, d)
+	} else if IsXMLType(ct) {
+		err = c.XMLUnmarshal(b, d)
 	}
-	return ""
+
+	return
 }
 
 //‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
@@ -152,6 +145,22 @@ type ResponseLog struct {
 	Body   string
 }
 
+// way to disable the HTML escape as opt-in
+func jsonMarshal(c *Client, r *Request, d interface{}) (*bytes.Buffer, error) {
+	if !r.jsonEscapeHTML || !c.jsonEscapeHTML {
+		return noescapeJSONMarshal(d)
+	}
+
+	data, err := c.JSONMarshal(d)
+	if err != nil {
+		return nil, err
+	}
+
+	buf := acquireBuffer()
+	_, _ = buf.Write(data)
+	return buf, nil
+}
+
 func firstNonEmpty(v ...string) string {
 	for _, s := range v {
 		if !IsStringEmpty(s) {
@@ -229,10 +238,34 @@ func addFileReader(w *multipart.Writer, f *File) error {
 	return writeMultipartFormFile(w, f.ParamName, f.Name, f.Reader)
 }
 
+func getPointer(v interface{}) interface{} {
+	vv := valueOf(v)
+	if vv.Kind() == reflect.Ptr {
+		return v
+	}
+	return reflect.New(vv.Type()).Interface()
+}
+
 func isPayloadSupported(m string, allowMethodGet bool) bool {
 	return !(m == MethodHead || m == MethodOptions || (m == MethodGet && !allowMethodGet))
 }
 
+func typeOf(i interface{}) reflect.Type {
+	return indirect(valueOf(i)).Type()
+}
+
+func valueOf(i interface{}) reflect.Value {
+	return reflect.ValueOf(i)
+}
+
+func indirect(v reflect.Value) reflect.Value {
+	return reflect.Indirect(v)
+}
+
+func kindOf(v interface{}) reflect.Kind {
+	return typeOf(v).Kind()
+}
+
 func createDirectory(dir string) (err error) {
 	if _, err = os.Stat(dir); err != nil {
 		if os.IsNotExist(err) {
@@ -248,23 +281,7 @@ func canJSONMarshal(contentType string, kind reflect.Kind) bool {
 	return IsJSONType(contentType) && (kind == reflect.Struct || kind == reflect.Map || kind == reflect.Slice)
 }
 
-func getPointer(v any) any {
-	vv := reflect.ValueOf(v)
-	if vv.Kind() == reflect.Ptr {
-		return v
-	}
-	return reflect.New(vv.Type()).Interface()
-}
-
-func inferType(v any) reflect.Type {
-	return reflect.Indirect(reflect.ValueOf(v)).Type()
-}
-
-func inferKind(v any) reflect.Kind {
-	return inferType(v).Kind()
-}
-
-func functionName(i any) string {
+func functionName(i interface{}) string {
 	return runtime.FuncForPC(reflect.ValueOf(i).Pointer()).Name()
 }
 
@@ -307,13 +324,13 @@ func (rr *requestBodyReleaser) Close() error {
 	return err
 }
 
-func closeq(v any) {
+func closeq(v interface{}) {
 	if c, ok := v.(io.Closer); ok {
 		silently(c.Close())
 	}
 }
 
-func silently(_ ...any) {}
+func silently(_ ...interface{}) {}
 
 func composeHeaders(c *Client, r *Request, hdrs http.Header) string {
 	str := make([]string, 0, len(hdrs))
@@ -340,6 +357,32 @@ func copyHeaders(hdrs http.Header) http.Header {
 	return nh
 }
 
+func wrapErrors(n error, inner error) error {
+	if inner == nil {
+		return n
+	}
+	if n == nil {
+		return inner
+	}
+	return &restyError{
+		err:   n,
+		inner: inner,
+	}
+}
+
+type restyError struct {
+	err   error
+	inner error
+}
+
+func (e *restyError) Error() string {
+	return e.err.Error()
+}
+
+func (e *restyError) Unwrap() error {
+	return e.inner
+}
+
 type noRetryErr struct {
 	err error
 }
@@ -361,11 +404,3 @@ func unwrapNoRetryErr(err error) error {
 	}
 	return err
 }
-
-// cloneURLValues is a helper function to deep copy url.Values.
-func cloneURLValues(v url.Values) url.Values {
-	if v == nil {
-		return nil
-	}
-	return url.Values(http.Header(v).Clone())
-}
diff --git a/util_curl.go b/util_curl.go
index f899216..2bd9127 100644
--- a/util_curl.go
+++ b/util_curl.go
@@ -9,7 +9,7 @@ import (
 	"net/url"
 	"strings"
 
-	"github.com/go-resty/resty/v3/shellescape"
+	"github.com/go-resty/resty/v2/shellescape"
 )
 
 func buildCurlRequest(req *http.Request, httpCookiejar http.CookieJar) (curl string) {
@@ -35,7 +35,7 @@ func buildCurlRequest(req *http.Request, httpCookiejar http.CookieJar) (curl str
 	if req.Body != nil {
 		buf, _ := io.ReadAll(req.Body)
 		req.Body = io.NopCloser(bytes.NewBuffer(buf)) // important!!
-		curl += `-d ` + shellescape.Quote(string(bytes.TrimRight(buf, "\n")))
+		curl += `-d ` + shellescape.Quote(string(buf))
 	}
 
 	urlString := shellescape.Quote(req.URL.String())


Test patch:
diff --git a/client_test.go b/client_test.go
index d1dd2e3..ac14c0b 100644
--- a/client_test.go
+++ b/client_test.go
@@ -31,7 +31,7 @@ func TestClientBasicAuth(t *testing.T) {
 
 	c := dc()
 	c.SetBasicAuth("myuser", "basicauth").
-		SetBaseURL(ts.URL).
+		SetHostURL(ts.URL).
 		SetTLSClientConfig(&tls.Config{InsecureSkipVerify: true})
 
 	resp, err := c.R().
@@ -52,7 +52,7 @@ func TestClientAuthToken(t *testing.T) {
 	c := dc()
 	c.SetTLSClientConfig(&tls.Config{InsecureSkipVerify: true}).
 		SetAuthToken("004DDB79-6801-4587-B976-F093E6AC44FF").
-		SetBaseURL(ts.URL + "/")
+		SetHostURL(ts.URL + "/")
 
 	resp, err := c.R().Get("/profile")
 
@@ -68,7 +68,7 @@ func TestClientAuthScheme(t *testing.T) {
 	// Ensure default Bearer
 	c.SetTLSClientConfig(&tls.Config{InsecureSkipVerify: true}).
 		SetAuthToken("004DDB79-6801-4587-B976-F093E6AC44FF").
-		SetBaseURL(ts.URL + "/")
+		SetHostURL(ts.URL + "/")
 
 	resp, err := c.R().Get("/profile")
 
@@ -77,7 +77,6 @@ func TestClientAuthScheme(t *testing.T) {
 
 	// Ensure setting the scheme works as well
 	c.SetAuthScheme("Bearer")
-	assertEqual(t, "Bearer", c.AuthScheme())
 
 	resp2, err2 := c.R().Get("/profile")
 	assertError(t, err2)
@@ -241,7 +240,7 @@ func TestClientProxy(t *testing.T) {
 	assertNotNil(t, resp)
 	assertNotNil(t, err)
 
-	// error
+	// Error
 	c.SetProxy("//not.a.user@%66%6f%6f.com:8888")
 
 	resp, err = c.R().
@@ -375,7 +374,7 @@ func TestClientOnBeforeRequestModification(t *testing.T) {
 	assertError(t, err)
 	assertEqual(t, http.StatusOK, resp.StatusCode())
 	assertEqual(t, "200 OK", resp.Status())
-	assertNotNil(t, resp.BodyBytes())
+	assertNotNil(t, resp.Body())
 	assertEqual(t, "TestGet: text response", resp.String())
 
 	logResponse(t, resp)
@@ -390,9 +389,9 @@ func TestClientSetHeaderVerbatim(t *testing.T) {
 		SetHeader("header-lowercase", "value_standard")
 
 	//lint:ignore SA1008 valid one, so ignore this!
-	unConventionHdrValue := strings.Join(c.Header()["header-lowercase"], "")
+	unConventionHdrValue := strings.Join(c.Header["header-lowercase"], "")
 	assertEqual(t, "value_lowercase", unConventionHdrValue)
-	assertEqual(t, "value_standard", c.Header().Get("Header-Lowercase"))
+	assertEqual(t, "value_standard", c.Header.Get("Header-Lowercase"))
 }
 
 func TestClientSetTransport(t *testing.T) {
@@ -437,21 +436,21 @@ func TestClientOptions(t *testing.T) {
 	client.SetContentLength(true)
 	assertEqual(t, client.setContentLength, true)
 
-	client.SetBaseURL("http://httpbin.org")
-	assertEqual(t, "http://httpbin.org", client.BaseURL())
+	client.SetHostURL("http://httpbin.org")
+	assertEqual(t, "http://httpbin.org", client.HostURL)
 
 	client.SetHeader(hdrContentTypeKey, "application/json; charset=utf-8")
 	client.SetHeaders(map[string]string{
 		hdrUserAgentKey: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) go-resty v0.1",
 		"X-Request-Id":  strconv.FormatInt(time.Now().UnixNano(), 10),
 	})
-	assertEqual(t, "application/json; charset=utf-8", client.Header().Get(hdrContentTypeKey))
+	assertEqual(t, "application/json; charset=utf-8", client.Header.Get(hdrContentTypeKey))
 
 	client.SetCookie(&http.Cookie{
 		Name:  "default-cookie",
 		Value: "This is cookie default-cookie value",
 	})
-	assertEqual(t, "default-cookie", client.Cookies()[0].Name)
+	assertEqual(t, "default-cookie", client.Cookies[0].Name)
 
 	cookies := []*http.Cookie{
 		{
@@ -463,45 +462,45 @@ func TestClientOptions(t *testing.T) {
 		},
 	}
 	client.SetCookies(cookies)
-	assertEqual(t, "default-cookie-1", client.Cookies()[1].Name)
-	assertEqual(t, "default-cookie-2", client.Cookies()[2].Name)
+	assertEqual(t, "default-cookie-1", client.Cookies[1].Name)
+	assertEqual(t, "default-cookie-2", client.Cookies[2].Name)
 
 	client.SetQueryParam("test_param_1", "Param_1")
 	client.SetQueryParams(map[string]string{"test_param_2": "Param_2", "test_param_3": "Param_3"})
-	assertEqual(t, "Param_3", client.QueryParams().Get("test_param_3"))
+	assertEqual(t, "Param_3", client.QueryParam.Get("test_param_3"))
 
 	rTime := strconv.FormatInt(time.Now().UnixNano(), 10)
 	client.SetFormData(map[string]string{"r_time": rTime})
-	assertEqual(t, rTime, client.FormData().Get("r_time"))
+	assertEqual(t, rTime, client.FormData.Get("r_time"))
 
 	client.SetBasicAuth("myuser", "mypass")
-	assertEqual(t, "myuser", client.UserInfo().Username)
+	assertEqual(t, "myuser", client.UserInfo.Username)
 
 	client.SetAuthToken("AC75BD37F019E08FBC594900518B4F7E")
-	assertEqual(t, "AC75BD37F019E08FBC594900518B4F7E", client.Token())
+	assertEqual(t, "AC75BD37F019E08FBC594900518B4F7E", client.Token)
 
 	client.SetDisableWarn(true)
-	assertEqual(t, client.DisableWarn(), true)
+	assertEqual(t, client.DisableWarn, true)
 
 	client.SetRetryCount(3)
-	assertEqual(t, 3, client.RetryCount())
+	assertEqual(t, 3, client.RetryCount)
 
 	rwt := time.Duration(1000) * time.Millisecond
 	client.SetRetryWaitTime(rwt)
-	assertEqual(t, rwt, client.RetryWaitTime())
+	assertEqual(t, rwt, client.RetryWaitTime)
 
 	mrwt := time.Duration(2) * time.Second
 	client.SetRetryMaxWaitTime(mrwt)
-	assertEqual(t, mrwt, client.RetryMaxWaitTime())
+	assertEqual(t, mrwt, client.RetryMaxWaitTime)
 
 	client.AddRetryAfterErrorCondition()
-	equal(client.RetryConditions()[0], func(response *Response, err error) bool {
+	equal(client.RetryConditions[0], func(response *Response, err error) bool {
 		return response.IsError()
 	})
 
 	err := &AuthError{}
 	client.SetError(err)
-	if reflect.TypeOf(err) == client.Error() {
+	if reflect.TypeOf(err) == client.Error {
 		t.Error("SetError failed")
 	}
 
@@ -527,14 +526,14 @@ func TestClientOptions(t *testing.T) {
 	client.SetContentLength(true)
 
 	client.SetDebug(true)
-	assertEqual(t, client.Debug(), true)
+	assertEqual(t, client.Debug, true)
 
-	var sl int = 1000000
+	var sl int64 = 1000000
 	client.SetDebugBodyLimit(sl)
 	assertEqual(t, client.debugBodySizeLimit, sl)
 
 	client.SetAllowGetMethodPayload(true)
-	assertEqual(t, client.AllowGetMethodPayload(), true)
+	assertEqual(t, client.AllowGetMethodPayload, true)
 
 	client.SetScheme("http")
 	assertEqual(t, client.scheme, "http")
@@ -579,7 +578,7 @@ func TestClientPreRequestHook(t *testing.T) {
 
 	// Regular bodybuf use case
 	resp, _ := client.R().
-		SetBody(map[string]any{"username": "testuser", "password": "testpass"}).
+		SetBody(map[string]interface{}{"username": "testuser", "password": "testpass"}).
 		Post(ts.URL + "/login")
 	assertEqual(t, http.StatusOK, resp.StatusCode())
 	assertEqual(t, `{ "id": "success", "message": "login successful" }`, resp.String())
@@ -666,33 +665,33 @@ func TestClientNewRequest(t *testing.T) {
 }
 
 func TestClientSetJSONMarshaler(t *testing.T) {
-	m := func(v any) ([]byte, error) { return nil, nil }
+	m := func(v interface{}) ([]byte, error) { return nil, nil }
 	c := New().SetJSONMarshaler(m)
-	p1 := fmt.Sprintf("%p", c.jsonMarshal)
+	p1 := fmt.Sprintf("%p", c.JSONMarshal)
 	p2 := fmt.Sprintf("%p", m)
 	assertEqual(t, p1, p2) // functions can not be compared, we only can compare pointers
 }
 
 func TestClientSetJSONUnmarshaler(t *testing.T) {
-	m := func([]byte, any) error { return nil }
+	m := func([]byte, interface{}) error { return nil }
 	c := New().SetJSONUnmarshaler(m)
-	p1 := fmt.Sprintf("%p", c.jsonUnmarshal)
+	p1 := fmt.Sprintf("%p", c.JSONUnmarshal)
 	p2 := fmt.Sprintf("%p", m)
 	assertEqual(t, p1, p2) // functions can not be compared, we only can compare pointers
 }
 
 func TestClientSetXMLMarshaler(t *testing.T) {
-	m := func(v any) ([]byte, error) { return nil, nil }
+	m := func(v interface{}) ([]byte, error) { return nil, nil }
 	c := New().SetXMLMarshaler(m)
-	p1 := fmt.Sprintf("%p", c.xmlMarshal)
+	p1 := fmt.Sprintf("%p", c.XMLMarshal)
 	p2 := fmt.Sprintf("%p", m)
 	assertEqual(t, p1, p2) // functions can not be compared, we only can compare pointers
 }
 
 func TestClientSetXMLUnmarshaler(t *testing.T) {
-	m := func([]byte, any) error { return nil }
+	m := func([]byte, interface{}) error { return nil }
 	c := New().SetXMLUnmarshaler(m)
-	p1 := fmt.Sprintf("%p", c.xmlUnmarshal)
+	p1 := fmt.Sprintf("%p", c.XMLUnmarshal)
 	p2 := fmt.Sprintf("%p", m)
 	assertEqual(t, p1, p2) // functions can not be compared, we only can compare pointers
 }
@@ -701,12 +700,11 @@ func TestDebugBodySizeLimit(t *testing.T) {
 	ts := createGetServer(t)
 	defer ts.Close()
 
-	c := dc().
-		SetDebug(true).
-		SetDebugBodyLimit(30)
-
 	var lgr bytes.Buffer
-	c.outputLogTo(&lgr) // internal method
+	c := dc()
+	c.SetDebug(true)
+	c.SetDebugBodyLimit(30)
+	c.outputLogTo(&lgr)
 
 	testcases := []struct{ url, want string }{
 		// Text, does not exceed limit.
@@ -755,13 +753,13 @@ func TestAutoGzip(t *testing.T) {
 	}
 	for _, tc := range testcases {
 		resp, err := c.R().
-			// SetHeader("Accept-Encoding", "gzip"). // TODO put back when implementing compression handling
+			SetHeader("Accept-Encoding", "gzip").
 			Get(tc.url)
 
 		assertError(t, err)
 		assertEqual(t, http.StatusOK, resp.StatusCode())
 		assertEqual(t, "200 OK", resp.Status())
-		assertNotNil(t, resp.BodyBytes())
+		assertNotNil(t, resp.Body())
 		assertEqual(t, tc.want, resp.String())
 
 		logResponse(t, resp)
@@ -845,77 +843,13 @@ func TestDebugLogSimultaneously(t *testing.T) {
 	}
 }
 
-func TestCustomTransportSettings(t *testing.T) {
-	ts := createGetServer(t)
-	defer ts.Close()
-
-	customTransportSettings := &TransportSettings{
-		DialerTimeout:          30 * time.Second,
-		DialerKeepAlive:        15 * time.Second,
-		IdleConnTimeout:        120 * time.Second,
-		TLSHandshakeTimeout:    20 * time.Second,
-		ExpectContinueTimeout:  1 * time.Second,
-		MaxIdleConns:           50,
-		MaxIdleConnsPerHost:    3,
-		ResponseHeaderTimeout:  10 * time.Second,
-		MaxResponseHeaderBytes: 1 << 10,
-		WriteBufferSize:        2 << 10,
-		ReadBufferSize:         2 << 10,
-	}
-	client := NewWithTransportSettings(customTransportSettings)
-	client.SetBaseURL(ts.URL)
-
-	resp, err := client.R().Get("/")
-	assertNil(t, err)
-	assertEqual(t, resp.String(), "TestGet: text response")
-}
-
-func TestDefaultDialerTransportSettings(t *testing.T) {
-	ts := createGetServer(t)
-	defer ts.Close()
-
-	t.Run("transport-default", func(t *testing.T) {
-		client := NewWithTransportSettings(nil)
-		client.SetBaseURL(ts.URL)
-
-		resp, err := client.R().Get("/")
-		assertNil(t, err)
-		assertEqual(t, resp.String(), "TestGet: text response")
-	})
-
-	t.Run("dialer-transport-default", func(t *testing.T) {
-		client := NewWithDialerAndTransportSettings(nil, nil)
-		client.SetBaseURL(ts.URL)
-
-		resp, err := client.R().Get("/")
-		assertNil(t, err)
-		assertEqual(t, resp.String(), "TestGet: text response")
-	})
-}
-
-func TestNewWithDialer(t *testing.T) {
-	ts := createGetServer(t)
-	defer ts.Close()
-
-	dialer := &net.Dialer{
-		Timeout:   15 * time.Second,
-		KeepAlive: 15 * time.Second,
-	}
-	client := NewWithDialer(dialer)
-	client.SetBaseURL(ts.URL)
-
-	resp, err := client.R().Get("/")
-	assertNil(t, err)
-	assertEqual(t, resp.String(), "TestGet: text response")
-}
-
 func TestNewWithLocalAddr(t *testing.T) {
 	ts := createGetServer(t)
 	defer ts.Close()
 
 	localAddress, _ := net.ResolveTCPAddr("tcp", "127.0.0.1")
 	client := NewWithLocalAddr(localAddress)
-	client.SetBaseURL(ts.URL)
+	client.SetHostURL(ts.URL)
 
 	resp, err := client.R().Get("/")
 	assertNil(t, err)
@@ -1130,9 +1064,9 @@ func TestHostURLForGH318AndGH407(t *testing.T) {
 	// assertNotNil(t, resp)
 
 	t.Log("with leading `/` on request & with trailing `/` on host url")
-	c.SetBaseURL(ts.URL + "/")
+	c.SetHostURL(ts.URL + "/")
 	resp, err := c.R().
-		SetBody(map[string]any{"username": "testuser", "password": "testpass"}).
+		SetBody(map[string]interface{}{"username": "testuser", "password": "testpass"}).
 		Post("/login")
 	assertNil(t, err)
 	assertNotNil(t, resp)
@@ -1178,7 +1112,7 @@ func TestUnixSocket(t *testing.T) {
 
 	// Set the previous transport that we created, set the scheme of the communication to the
 	// socket and set the unixSocket as the HostURL.
-	client.SetTransport(&transport).SetScheme("http").SetBaseURL(unixSocketAddr)
+	client.SetTransport(&transport).SetScheme("http").SetHostURL(unixSocketAddr)
 
 	// No need to write the host's URL on the request, just the path.
 	res, err := client.R().Get("http://localhost/")
@@ -1190,28 +1124,30 @@ func TestUnixSocket(t *testing.T) {
 	assertEqual(t, "Hello resty client from a server running on endpoint /hello!", res.String())
 }
 
-func TestClientClone(t *testing.T) {
+func TestClone(t *testing.T) {
 	parent := New()
 
 	// set a non-interface field
 	parent.SetBaseURL("http://localhost")
 
 	// set an interface field
-	parent.SetBasicAuth("parent", "")
+	parent.UserInfo = &User{
+		Username: "parent",
+	}
 
 	clone := parent.Clone()
 	// update value of non-interface type - change will only happen on clone
 	clone.SetBaseURL("https://local.host")
 	// update value of interface type - change will also happen on parent
-	clone.UserInfo().Username = "clone"
+	clone.UserInfo.Username = "clone"
 
-	// assert non-interface type
-	assertEqual(t, "http://localhost", parent.BaseURL())
-	assertEqual(t, "https://local.host", clone.BaseURL())
+	// asert non-interface type
+	assertEqual(t, "http://localhost", parent.BaseURL)
+	assertEqual(t, "https://local.host", clone.BaseURL)
 
 	// assert interface type
-	assertEqual(t, "clone", parent.UserInfo().Username)
-	assertEqual(t, "clone", clone.UserInfo().Username)
+	assertEqual(t, "clone", parent.UserInfo.Username)
+	assertEqual(t, "clone", clone.UserInfo.Username)
 }
 
 func TestResponseBodyLimit(t *testing.T) {
@@ -1222,20 +1158,18 @@ func TestResponseBodyLimit(t *testing.T) {
 
 	t.Run("Client body limit", func(t *testing.T) {
 		c := dc().SetResponseBodyLimit(1024)
-		assertEqual(t, int64(1024), c.ResponseBodyLimit())
-		resp, err := c.R().Get(ts.URL + "/")
+
+		_, err := c.R().Get(ts.URL + "/")
 		assertNotNil(t, err)
-		assertErrorIs(t, ErrReadExceedsThresholdLimit, err)
-		assertEqual(t, int64(1408), resp.Size())
+		assertErrorIs(t, ErrResponseBodyTooLarge, err)
 	})
 
 	t.Run("request body limit", func(t *testing.T) {
 		c := dc()
 
-		resp, err := c.R().SetResponseBodyLimit(1024).Get(ts.URL + "/")
+		_, err := c.R().SetResponseBodyLimit(1024).Get(ts.URL + "/")
 		assertNotNil(t, err)
-		assertErrorIs(t, ErrReadExceedsThresholdLimit, err)
-		assertEqual(t, int64(1408), resp.Size())
+		assertErrorIs(t, ErrResponseBodyTooLarge, err)
 	})
 
 	t.Run("body less than limit", func(t *testing.T) {
@@ -1243,8 +1177,7 @@ func TestResponseBodyLimit(t *testing.T) {
 
 		res, err := c.R().SetResponseBodyLimit(800*100 + 10).Get(ts.URL + "/")
 		assertNil(t, err)
-		assertEqual(t, 800*100, len(res.BodyBytes()))
-		assertEqual(t, int64(800*100), res.Size())
+		assertEqual(t, 800*100, len(res.body))
 	})
 
 	t.Run("no body limit", func(t *testing.T) {
@@ -1252,8 +1185,7 @@ func TestResponseBodyLimit(t *testing.T) {
 
 		res, err := c.R().Get(ts.URL + "/")
 		assertNil(t, err)
-		assertEqual(t, 800*100, len(res.BodyBytes()))
-		assertEqual(t, int64(800*100), res.Size())
+		assertEqual(t, 800*100, len(res.body))
 	})
 
 	t.Run("read error", func(t *testing.T) {
diff --git a/context_test.go b/context_test.go
index 378358a..1bbc83d 100644
--- a/context_test.go
+++ b/context_test.go
@@ -27,7 +27,7 @@ func TestSetContext(t *testing.T) {
 	assertError(t, err)
 	assertEqual(t, http.StatusOK, resp.StatusCode())
 	assertEqual(t, "200 OK", resp.Status())
-	assertEqual(t, true, resp.BodyBytes() != nil)
+	assertEqual(t, true, resp.Body() != nil)
 	assertEqual(t, "TestGet: text response", resp.String())
 
 	logResponse(t, resp)
diff --git a/example_test.go b/example_test.go
index fae92d0..7b7514d 100644
--- a/example_test.go
+++ b/example_test.go
@@ -15,7 +15,7 @@ import (
 
 	"golang.org/x/net/proxy"
 
-	"github.com/go-resty/resty/v3"
+	"github.com/go-resty/resty/v2"
 )
 
 type DropboxError struct {
@@ -109,7 +109,7 @@ func Example_post() {
 
 	// POST Map, default is JSON content type. No need to set one
 	resp3, err3 := client.R().
-		SetBody(map[string]any{"username": "testuser", "password": "testpass"}).
+		SetBody(map[string]interface{}{"username": "testuser", "password": "testpass"}).
 		SetResult(&AuthSuccess{}). // or SetResult(AuthSuccess{}).
 		SetError(&AuthError{}).    // or SetError(AuthError{}).
 		Post("https://myapp.com/login")
diff --git a/middleware_test.go b/middleware_test.go
index 6381539..49733e9 100644
--- a/middleware_test.go
+++ b/middleware_test.go
@@ -98,7 +98,7 @@ func Test_parseRequestURL(t *testing.T) {
 				r.SetPathParams(map[string]string{
 					"foo": "4/5",
 				}).SetRawPathParams(map[string]string{
-					"foo": "4/5", // ignored, because the pathParams takes precedence over the rawPathParams
+					"foo": "4/5", // ignored, because the PathParams takes precedence over the RawPathParams
 					"bar": "6/7",
 				})
 				r.URL = "https://example.com/{foo}/{bar}"
@@ -182,7 +182,7 @@ func Test_parseRequestURL(t *testing.T) {
 		{
 			name: "using deprecated HostURL with relative path in request URL",
 			init: func(c *Client, r *Request) {
-				c.SetBaseURL("https://example.com")
+				c.HostURL = "https://example.com"
 				r.URL = "foo/bar"
 			},
 			expectedURL: "https://example.com/foo/bar",
@@ -259,8 +259,8 @@ func Test_parseRequestURL(t *testing.T) {
 		{
 			name: "adding query parameters by request with multiple values",
 			init: func(c *Client, r *Request) {
-				r.QueryParams.Add("foo", "1")
-				r.QueryParams.Add("foo", "2")
+				r.QueryParam.Add("foo", "1")
+				r.QueryParam.Add("foo", "2")
 				r.URL = "https://example.com/"
 			},
 			expectedURL: "https://example.com/?foo=1&foo=2",
@@ -454,11 +454,10 @@ func (errorReader) Read(p []byte) (n int, err error) {
 	return 0, errors.New("fake")
 }
 
-func TestParseRequestBody(t *testing.T) {
+func Test_parseRequestBody(t *testing.T) {
 	for _, tt := range []struct {
 		name                  string
-		initClient            func(c *Client)
-		initRequest           func(r *Request)
+		init                  func(c *Client, r *Request)
 		expectedBodyBuf       []byte
 		expectedContentLength string
 		expectedContentType   string
@@ -466,24 +465,25 @@ func TestParseRequestBody(t *testing.T) {
 	}{
 		{
 			name: "empty body",
+			init: func(c *Client, r *Request) {},
 		},
 		{
 			name: "empty body with SetContentLength by request",
-			initRequest: func(r *Request) {
+			init: func(c *Client, r *Request) {
 				r.SetContentLength(true)
 			},
 			expectedContentLength: "0",
 		},
 		{
 			name: "empty body with SetContentLength by client",
-			initClient: func(c *Client) {
+			init: func(c *Client, r *Request) {
 				c.SetContentLength(true)
 			},
 			expectedContentLength: "0",
 		},
 		{
 			name: "string body",
-			initRequest: func(r *Request) {
+			init: func(c *Client, r *Request) {
 				r.SetBody("foo")
 			},
 			expectedBodyBuf:     []byte("foo"),
@@ -491,17 +491,15 @@ func TestParseRequestBody(t *testing.T) {
 		},
 		{
 			name: "string body with GET method",
-			initRequest: func(r *Request) {
+			init: func(c *Client, r *Request) {
 				r.SetBody("foo")
 				r.Method = http.MethodGet
 			},
 		},
 		{
 			name: "string body with GET method and AllowGetMethodPayload",
-			initClient: func(c *Client) {
+			init: func(c *Client, r *Request) {
 				c.SetAllowGetMethodPayload(true)
-			},
-			initRequest: func(r *Request) {
 				r.SetBody("foo")
 				r.Method = http.MethodGet
 			},
@@ -510,21 +508,21 @@ func TestParseRequestBody(t *testing.T) {
 		},
 		{
 			name: "string body with HEAD method",
-			initRequest: func(r *Request) {
+			init: func(c *Client, r *Request) {
 				r.SetBody("foo")
 				r.Method = http.MethodHead
 			},
 		},
 		{
 			name: "string body with OPTIONS method",
-			initRequest: func(r *Request) {
+			init: func(c *Client, r *Request) {
 				r.SetBody("foo")
 				r.Method = http.MethodOptions
 			},
 		},
 		{
 			name: "string body with POST method",
-			initRequest: func(r *Request) {
+			init: func(c *Client, r *Request) {
 				r.SetBody("foo")
 				r.Method = http.MethodPost
 			},
@@ -533,7 +531,7 @@ func TestParseRequestBody(t *testing.T) {
 		},
 		{
 			name: "string body with PATCH method",
-			initRequest: func(r *Request) {
+			init: func(c *Client, r *Request) {
 				r.SetBody("foo")
 				r.Method = http.MethodPatch
 			},
@@ -542,7 +540,7 @@ func TestParseRequestBody(t *testing.T) {
 		},
 		{
 			name: "string body with PUT method",
-			initRequest: func(r *Request) {
+			init: func(c *Client, r *Request) {
 				r.SetBody("foo")
 				r.Method = http.MethodPut
 			},
@@ -551,7 +549,7 @@ func TestParseRequestBody(t *testing.T) {
 		},
 		{
 			name: "string body with DELETE method",
-			initRequest: func(r *Request) {
+			init: func(c *Client, r *Request) {
 				r.SetBody("foo")
 				r.Method = http.MethodDelete
 			},
@@ -560,7 +558,7 @@ func TestParseRequestBody(t *testing.T) {
 		},
 		{
 			name: "string body with CONNECT method",
-			initRequest: func(r *Request) {
+			init: func(c *Client, r *Request) {
 				r.SetBody("foo")
 				r.Method = http.MethodConnect
 			},
@@ -569,7 +567,7 @@ func TestParseRequestBody(t *testing.T) {
 		},
 		{
 			name: "string body with TRACE method",
-			initRequest: func(r *Request) {
+			init: func(c *Client, r *Request) {
 				r.SetBody("foo")
 				r.Method = http.MethodTrace
 			},
@@ -578,7 +576,7 @@ func TestParseRequestBody(t *testing.T) {
 		},
 		{
 			name: "string body with BAR method",
-			initRequest: func(r *Request) {
+			init: func(c *Client, r *Request) {
 				r.SetBody("foo")
 				r.Method = "BAR"
 			},
@@ -587,7 +585,7 @@ func TestParseRequestBody(t *testing.T) {
 		},
 		{
 			name: "byte body",
-			initRequest: func(r *Request) {
+			init: func(c *Client, r *Request) {
 				r.SetBody([]byte("foo"))
 			},
 			expectedBodyBuf:     []byte("foo"),
@@ -595,14 +593,14 @@ func TestParseRequestBody(t *testing.T) {
 		},
 		{
 			name: "io.Reader body, no bodyBuf",
-			initRequest: func(r *Request) {
+			init: func(c *Client, r *Request) {
 				r.SetBody(bytes.NewBufferString("foo"))
 			},
 			expectedContentType: jsonContentType,
 		},
 		{
 			name: "io.Reader body with SetContentLength by request",
-			initRequest: func(r *Request) {
+			init: func(c *Client, r *Request) {
 				r.SetBody(bytes.NewBufferString("foo")).
 					SetContentLength(true)
 			},
@@ -612,10 +610,8 @@ func TestParseRequestBody(t *testing.T) {
 		},
 		{
 			name: "io.Reader body with SetContentLength by client",
-			initClient: func(c *Client) {
+			init: func(c *Client, r *Request) {
 				c.SetContentLength(true)
-			},
-			initRequest: func(r *Request) {
 				r.SetBody(bytes.NewBufferString("foo"))
 			},
 			expectedBodyBuf:       []byte("foo"),
@@ -624,7 +620,7 @@ func TestParseRequestBody(t *testing.T) {
 		},
 		{
 			name: "form data by request",
-			initRequest: func(r *Request) {
+			init: func(c *Client, r *Request) {
 				r.SetFormData(map[string]string{
 					"foo": "1",
 					"bar": "2",
@@ -635,7 +631,7 @@ func TestParseRequestBody(t *testing.T) {
 		},
 		{
 			name: "form data by client",
-			initClient: func(c *Client) {
+			init: func(c *Client, r *Request) {
 				c.SetFormData(map[string]string{
 					"foo": "1",
 					"bar": "2",
@@ -646,13 +642,11 @@ func TestParseRequestBody(t *testing.T) {
 		},
 		{
 			name: "form data by client and request",
-			initClient: func(c *Client) {
+			init: func(c *Client, r *Request) {
 				c.SetFormData(map[string]string{
 					"foo": "1",
 					"bar": "2",
 				})
-			},
-			initRequest: func(r *Request) {
 				r.SetFormData(map[string]string{
 					"foo": "3",
 					"baz": "4",
@@ -663,7 +657,7 @@ func TestParseRequestBody(t *testing.T) {
 		},
 		{
 			name: "json from struct",
-			initRequest: func(r *Request) {
+			init: func(c *Client, r *Request) {
 				r.SetBody(struct {
 					Foo string `json:"foo"`
 					Bar string `json:"bar"`
@@ -672,23 +666,23 @@ func TestParseRequestBody(t *testing.T) {
 					Bar: "2",
 				}).SetContentLength(true)
 			},
-			expectedBodyBuf:       append([]byte(`{"foo":"1","bar":"2"}`), '\n'),
+			expectedBodyBuf:       []byte(`{"foo":"1","bar":"2"}`),
 			expectedContentType:   jsonContentType,
-			expectedContentLength: "22",
+			expectedContentLength: "21",
 		},
 		{
 			name: "json from slice",
-			initRequest: func(r *Request) {
+			init: func(c *Client, r *Request) {
 				r.SetBody([]string{"foo", "bar"}).SetContentLength(true)
 			},
-			expectedBodyBuf:       append([]byte(`["foo","bar"]`), '\n'),
+			expectedBodyBuf:       []byte(`["foo","bar"]`),
 			expectedContentType:   jsonContentType,
-			expectedContentLength: "14",
+			expectedContentLength: "13",
 		},
 		{
 			name: "json from map",
-			initRequest: func(r *Request) {
-				r.SetBody(map[string]any{
+			init: func(c *Client, r *Request) {
+				r.SetBody(map[string]interface{}{
 					"foo": "1",
 					"bar": []int{1, 2, 3},
 					"baz": map[string]string{
@@ -697,14 +691,14 @@ func TestParseRequestBody(t *testing.T) {
 					"xyz": nil,
 				}).SetContentLength(true)
 			},
-			expectedBodyBuf:       append([]byte(`{"bar":[1,2,3],"baz":{"qux":"4"},"foo":"1","xyz":null}`), '\n'),
+			expectedBodyBuf:       []byte(`{"bar":[1,2,3],"baz":{"qux":"4"},"foo":"1","xyz":null}`),
 			expectedContentType:   jsonContentType,
-			expectedContentLength: "55",
+			expectedContentLength: "54",
 		},
 		{
 			name: "json from map",
-			initRequest: func(r *Request) {
-				r.SetBody(map[string]any{
+			init: func(c *Client, r *Request) {
+				r.SetBody(map[string]interface{}{
 					"foo": "1",
 					"bar": []int{1, 2, 3},
 					"baz": map[string]string{
@@ -713,14 +707,14 @@ func TestParseRequestBody(t *testing.T) {
 					"xyz": nil,
 				}).SetContentLength(true)
 			},
-			expectedBodyBuf:       append([]byte(`{"bar":[1,2,3],"baz":{"qux":"4"},"foo":"1","xyz":null}`), '\n'),
+			expectedBodyBuf:       []byte(`{"bar":[1,2,3],"baz":{"qux":"4"},"foo":"1","xyz":null}`),
 			expectedContentType:   jsonContentType,
-			expectedContentLength: "55",
+			expectedContentLength: "54",
 		},
 		{
 			name: "json from map",
-			initRequest: func(r *Request) {
-				r.SetBody(map[string]any{
+			init: func(c *Client, r *Request) {
+				r.SetBody(map[string]interface{}{
 					"foo": "1",
 					"bar": []int{1, 2, 3},
 					"baz": map[string]string{
@@ -729,13 +723,13 @@ func TestParseRequestBody(t *testing.T) {
 					"xyz": nil,
 				}).SetContentLength(true)
 			},
-			expectedBodyBuf:       append([]byte(`{"bar":[1,2,3],"baz":{"qux":"4"},"foo":"1","xyz":null}`), '\n'),
+			expectedBodyBuf:       []byte(`{"bar":[1,2,3],"baz":{"qux":"4"},"foo":"1","xyz":null}`),
 			expectedContentType:   jsonContentType,
-			expectedContentLength: "55",
+			expectedContentLength: "54",
 		},
 		{
 			name: "xml from struct",
-			initRequest: func(r *Request) {
+			init: func(c *Client, r *Request) {
 				type FooBar struct {
 					Foo string `xml:"foo"`
 					Bar string `xml:"bar"`
@@ -752,14 +746,12 @@ func TestParseRequestBody(t *testing.T) {
 			expectedContentLength: "41",
 		},
 		{
-			name: "multipart form data",
-			initClient: func(c *Client) {
+			name: "mulipart form data",
+			init: func(c *Client, r *Request) {
 				c.SetFormData(map[string]string{
 					"foo": "1",
 					"bar": "2",
 				})
-			},
-			initRequest: func(r *Request) {
 				r.SetFormData(map[string]string{
 					"foo": "3",
 					"baz": "4",
@@ -775,7 +767,7 @@ func TestParseRequestBody(t *testing.T) {
 		},
 		{
 			name: "multipart fields",
-			initRequest: func(r *Request) {
+			init: func(c *Client, r *Request) {
 				r.SetMultipartFields(
 					&MultipartField{
 						Param:       "foo",
@@ -795,7 +787,7 @@ func TestParseRequestBody(t *testing.T) {
 		},
 		{
 			name: "multipart files",
-			initRequest: func(r *Request) {
+			init: func(c *Client, r *Request) {
 				r.SetFileReader("foo", "foo.txt", strings.NewReader("1")).
 					SetFileReader("bar", "bar.txt", strings.NewReader("2")).
 					SetContentLength(true)
@@ -806,21 +798,21 @@ func TestParseRequestBody(t *testing.T) {
 		},
 		{
 			name: "body with errorReader",
-			initRequest: func(r *Request) {
+			init: func(c *Client, r *Request) {
 				r.SetBody(&errorReader{}).SetContentLength(true)
 			},
 			wantErr: true,
 		},
 		{
 			name: "unsupported type",
-			initRequest: func(r *Request) {
+			init: func(c *Client, r *Request) {
 				r.SetBody(1)
 			},
 			wantErr: true,
 		},
 		{
 			name: "unsupported xml",
-			initRequest: func(r *Request) {
+			init: func(c *Client, r *Request) {
 				r.SetBody(struct {
 					Foo string `xml:"foo"`
 					Bar string `xml:"bar"`
@@ -833,7 +825,7 @@ func TestParseRequestBody(t *testing.T) {
 		},
 		{
 			name: "multipart fields with errorReader",
-			initRequest: func(r *Request) {
+			init: func(c *Client, r *Request) {
 				r.SetMultipartFields(&MultipartField{
 					Param:       "foo",
 					ContentType: "text/plain",
@@ -844,14 +836,14 @@ func TestParseRequestBody(t *testing.T) {
 		},
 		{
 			name: "multipart files with errorReader",
-			initRequest: func(r *Request) {
+			init: func(c *Client, r *Request) {
 				r.SetFileReader("foo", "foo.txt", &errorReader{})
 			},
 			wantErr: true,
 		},
 		{
 			name: "multipart with file not found",
-			initRequest: func(r *Request) {
+			init: func(c *Client, r *Request) {
 				r.SetFormData(map[string]string{
 					"@foo": "foo.txt",
 				})
@@ -862,15 +854,8 @@ func TestParseRequestBody(t *testing.T) {
 	} {
 		t.Run(tt.name, func(t *testing.T) {
 			c := New()
-			if tt.initClient != nil {
-				tt.initClient(c)
-			}
-
 			r := c.R()
-			if tt.initRequest != nil {
-				tt.initRequest(r)
-			}
-
+			tt.init(c, r)
 			if err := parseRequestBody(c, r); err != nil {
 				if tt.wantErr {
 					return
@@ -885,7 +870,7 @@ func TestParseRequestBody(t *testing.T) {
 			case r.bodyBuf != nil && tt.expectedBodyBuf == nil:
 				t.Errorf("bodyBuf is not nil, but expected nil: %s", r.bodyBuf.String())
 			case r.bodyBuf != nil && tt.expectedBodyBuf != nil:
-				var actual, expected any = r.bodyBuf.Bytes(), tt.expectedBodyBuf
+				var actual, expected interface{} = r.bodyBuf.Bytes(), tt.expectedBodyBuf
 				if r.isFormData {
 					var err error
 					actual, err = url.ParseQuery(r.bodyBuf.String())
@@ -906,7 +891,7 @@ func TestParseRequestBody(t *testing.T) {
 						t.Errorf("boundary not found in Content-Type header")
 					}
 					reader := multipart.NewReader(r.bodyBuf, boundary)
-					body := make(map[string]any)
+					body := make(map[string]interface{})
 					for part, perr := reader.NextPart(); perr != io.EOF; part, perr = reader.NextPart() {
 						if perr != nil {
 							t.Errorf("NextPart() error = %v", perr)
diff --git a/request_test.go b/request_test.go
index 4428a21..44bec45 100644
--- a/request_test.go
+++ b/request_test.go
@@ -6,7 +6,6 @@ package resty
 
 import (
 	"bytes"
-	"context"
 	"crypto/tls"
 	"encoding/xml"
 	"errors"
@@ -44,7 +43,7 @@ func TestGet(t *testing.T) {
 	assertEqual(t, http.StatusOK, resp.StatusCode())
 	assertEqual(t, "HTTP/1.1", resp.Proto())
 	assertEqual(t, "200 OK", resp.Status())
-	assertNotNil(t, resp.BodyBytes())
+	assertNotNil(t, resp.Body())
 	assertEqual(t, "TestGet: text response", resp.String())
 
 	logResponse(t, resp)
@@ -165,7 +164,7 @@ func TestGetRelativePath(t *testing.T) {
 	defer ts.Close()
 
 	c := dc()
-	c.SetBaseURL(ts.URL)
+	c.SetHostURL(ts.URL)
 
 	resp, err := c.R().Get("mypage2")
 
@@ -269,7 +268,6 @@ func TestPostJSONStructSuccess(t *testing.T) {
 
 	assertError(t, err)
 	assertEqual(t, http.StatusOK, resp.StatusCode())
-	assertEqual(t, int64(50), resp.Size())
 
 	t.Logf("Result Success: %q", resp.Result().(*AuthSuccess))
 
@@ -292,7 +290,6 @@ func TestPostJSONRPCStructSuccess(t *testing.T) {
 
 	assertError(t, err)
 	assertEqual(t, http.StatusOK, resp.StatusCode())
-	assertEqual(t, int64(50), resp.Size())
 
 	t.Logf("Result Success: %q", resp.Result().(*AuthSuccess))
 
@@ -354,7 +351,7 @@ func TestPostJSONMapSuccess(t *testing.T) {
 	c.SetDebug(false)
 
 	resp, err := c.R().
-		SetBody(map[string]any{"username": "testuser", "password": "testpass"}).
+		SetBody(map[string]interface{}{"username": "testuser", "password": "testpass"}).
 		SetResult(AuthSuccess{}).
 		Post(ts.URL + "/login")
 
@@ -371,7 +368,7 @@ func TestPostJSONMapInvalidResponseJson(t *testing.T) {
 	defer ts.Close()
 
 	resp, err := dclr().
-		SetBody(map[string]any{"username": "testuser", "password": "invalidjson"}).
+		SetBody(map[string]interface{}{"username": "testuser", "password": "invalidjson"}).
 		SetResult(&AuthSuccess{}).
 		Post(ts.URL + "/login")
 
@@ -427,15 +424,14 @@ func TestForceContentTypeForGH276andGH240(t *testing.T) {
 	}))
 
 	resp, err := c.R().
-		SetBody(map[string]any{"username": "testuser", "password": "testpass"}).
+		SetBody(map[string]interface{}{"username": "testuser", "password": "testpass"}).
 		SetResult(AuthSuccess{}).
 		ForceContentType("application/json").
 		Post(ts.URL + "/login-json-html")
 
-	assertNil(t, err) // JSON response comes with incorrect content-type, we correct it with ForceContentType
+	assertNotNil(t, err) // expecting error due to incorrect content type from server end
 	assertEqual(t, http.StatusOK, resp.StatusCode())
 	assertEqual(t, 0, retried)
-	assertEqual(t, int64(50), resp.Size())
 
 	t.Logf("Result Success: %q", resp.Result().(*AuthSuccess))
 
@@ -457,7 +453,6 @@ func TestPostXMLStringSuccess(t *testing.T) {
 
 	assertError(t, err)
 	assertEqual(t, http.StatusOK, resp.StatusCode())
-	assertEqual(t, int64(116), resp.Size())
 
 	logResponse(t, resp)
 }
@@ -588,10 +583,10 @@ func TestPostXMLMapNotSupported(t *testing.T) {
 
 	_, err := dclr().
 		SetHeader(hdrContentTypeKey, "application/xml").
-		SetBody(map[string]any{"Username": "testuser", "Password": "testpass"}).
+		SetBody(map[string]interface{}{"Username": "testuser", "Password": "testpass"}).
 		Post(ts.URL + "/login")
 
-	assertErrorIs(t, ErrUnsupportedRequestBodyKind, err)
+	assertEqual(t, "unsupported 'Body' type/value", err.Error())
 }
 
 func TestRequestBasicAuth(t *testing.T) {
@@ -599,7 +594,7 @@ func TestRequestBasicAuth(t *testing.T) {
 	defer ts.Close()
 
 	c := dc()
-	c.SetBaseURL(ts.URL).
+	c.SetHostURL(ts.URL).
 		SetTLSClientConfig(&tls.Config{InsecureSkipVerify: true})
 
 	resp, err := c.R().
@@ -623,7 +618,7 @@ func TestRequestInsecureBasicAuth(t *testing.T) {
 	logger.l.SetOutput(&logBuf)
 
 	c := dc()
-	c.SetBaseURL(ts.URL)
+	c.SetHostURL(ts.URL)
 
 	resp, err := c.R().
 		SetBasicAuth("myuser", "basicauth").
@@ -736,7 +731,7 @@ func TestRequestDigestAuthWithBody(t *testing.T) {
 		SetDigestAuth(conf.username, conf.password).
 		SetResult(&AuthSuccess{}).
 		SetHeader(hdrContentTypeKey, "application/json").
-		SetBody(map[string]any{"zip_code": "00000", "city": "Los Angeles"}).
+		SetBody(map[string]interface{}{"zip_code": "00000", "city": "Los Angeles"}).
 		Post(ts.URL + conf.uri)
 
 	assertError(t, err)
@@ -1067,7 +1062,7 @@ func TestGetWithCookie(t *testing.T) {
 	defer ts.Close()
 
 	c := dcl()
-	c.SetBaseURL(ts.URL)
+	c.SetHostURL(ts.URL)
 	c.SetCookie(&http.Cookie{
 		Name:  "go-resty-1",
 		Value: "This is cookie 1 value",
@@ -1098,7 +1093,7 @@ func TestGetWithCookies(t *testing.T) {
 	defer ts.Close()
 
 	c := dc()
-	c.SetBaseURL(ts.URL).SetDebug(true)
+	c.SetHostURL(ts.URL).SetDebug(true)
 
 	tu, _ := url.Parse(ts.URL)
 	c.GetClient().Jar.SetCookies(tu, []*http.Cookie{
@@ -1275,7 +1270,7 @@ func TestPatchMethod(t *testing.T) {
 	assertError(t, err)
 	assertEqual(t, http.StatusOK, resp.StatusCode())
 
-	resp.SetBodyBytes(nil)
+	resp.SetBody(nil)
 	assertEqual(t, "", resp.String())
 }
 
@@ -1376,7 +1371,7 @@ func TestProxySetting(t *testing.T) {
 	c.SetProxy("http://sampleproxy:8888")
 	assertEqual(t, true, c.IsProxySet())
 	c.RemoveProxy()
-	assertNil(t, c.ProxyURL())
+	assertNil(t, c.proxyURL)
 	assertNil(t, transport.Proxy)
 }
 
@@ -1397,7 +1392,7 @@ func TestIncorrectURL(t *testing.T) {
 	assertEqual(t, true, (strings.Contains(err.Error(), "parse //not.a.user@%66%6f%6f.com/just/a/path/also") ||
 		strings.Contains(err.Error(), "parse \"//not.a.user@%66%6f%6f.com/just/a/path/also\"")))
 
-	c.SetBaseURL("//not.a.user@%66%6f%6f.com")
+	c.SetHostURL("//not.a.user@%66%6f%6f.com")
 	_, err1 := c.R().Get("/just/a/path/also")
 	assertEqual(t, true, (strings.Contains(err1.Error(), "parse //not.a.user@%66%6f%6f.com/just/a/path/also") ||
 		strings.Contains(err1.Error(), "parse \"//not.a.user@%66%6f%6f.com/just/a/path/also\"")))
@@ -1454,13 +1449,13 @@ func TestDetectContentTypeForPointerWithSliceMap(t *testing.T) {
 	ts := createPostServer(t)
 	defer ts.Close()
 
-	usersmap := map[string]any{
+	usersmap := map[string]interface{}{
 		"user1": ExampleUser{FirstName: "firstname1", LastName: "lastname1", ZipCode: "10001"},
 		"user2": &ExampleUser{FirstName: "firstname2", LastName: "lastname3", ZipCode: "10002"},
 		"user3": ExampleUser{FirstName: "firstname3", LastName: "lastname3", ZipCode: "10003"},
 	}
 
-	var users []map[string]any
+	var users []map[string]interface{}
 	users = append(users, usersmap)
 
 	resp, err := dclr().
@@ -1593,25 +1588,19 @@ func TestOutputFileWithBaseDirAndRelativePath(t *testing.T) {
 	defer ts.Close()
 	defer cleanupFiles(".testdata/dir-sample")
 
-	baseOutputDir := filepath.Join(getTestDataPath(), "dir-sample")
 	client := dc().
 		SetRedirectPolicy(FlexibleRedirectPolicy(10)).
-		SetOutputDirectory(baseOutputDir).
+		SetOutputDirectory(filepath.Join(getTestDataPath(), "dir-sample")).
 		SetDebug(true)
 	client.outputLogTo(io.Discard)
 
-	outputFilePath := "go-resty/test-img-success.png"
 	resp, err := client.R().
-		SetOutput(outputFilePath).
+		SetOutput("go-resty/test-img-success.png").
 		Get(ts.URL + "/my-image.png")
 
 	assertError(t, err)
 	assertEqual(t, true, resp.Size() != 0)
 	assertEqual(t, true, resp.Time() > 0)
-
-	f, err1 := os.Open(filepath.Join(baseOutputDir, outputFilePath))
-	defer closeq(f)
-	assertError(t, err1)
 }
 
 func TestOutputFileWithBaseDirError(t *testing.T) {
@@ -1721,6 +1710,43 @@ func TestRequestDoNotParseResponse(t *testing.T) {
 	assertNil(t, resp.RawBody())
 }
 
+func TestRequestDoNotParseResponseDebugLog(t *testing.T) {
+	ts := createGetServer(t)
+	defer ts.Close()
+
+	t.Run("do not parse response debug log client level", func(t *testing.T) {
+		c := dc().
+			SetDoNotParseResponse(true).
+			SetDebug(true)
+
+		var lgr bytes.Buffer
+		c.outputLogTo(&lgr)
+
+		_, err := c.R().
+			SetQueryParam("request_no", strconv.FormatInt(time.Now().Unix(), 10)).
+			Get(ts.URL + "/")
+
+		assertError(t, err)
+		assertEqual(t, true, strings.Contains(lgr.String(), "***** DO NOT PARSE RESPONSE - Enabled *****"))
+	})
+
+	t.Run("do not parse response debug log request level", func(t *testing.T) {
+		c := dc()
+
+		var lgr bytes.Buffer
+		c.outputLogTo(&lgr)
+
+		_, err := c.R().
+			SetDebug(true).
+			SetDoNotParseResponse(true).
+			SetQueryParam("request_no", strconv.FormatInt(time.Now().Unix(), 10)).
+			Get(ts.URL + "/")
+
+		assertError(t, err)
+		assertEqual(t, true, strings.Contains(lgr.String(), "***** DO NOT PARSE RESPONSE - Enabled *****"))
+	})
+}
+
 type noCtTest struct {
 	Response string `json:"response"`
 }
@@ -1751,8 +1777,6 @@ func TestGetPathParamAndPathParams(t *testing.T) {
 		SetBaseURL(ts.URL).
 		SetPathParam("userId", "sample@sample.com")
 
-	assertEqual(t, "sample@sample.com", c.PathParams()["userId"])
-
 	resp, err := c.R().SetPathParam("subAccountId", "100002").
 		Get("/v1/users/{userId}/{subAccountId}/details")
 
@@ -1788,7 +1812,7 @@ func TestRequestQueryStringOrder(t *testing.T) {
 	assertError(t, err)
 	assertEqual(t, http.StatusOK, resp.StatusCode())
 	assertEqual(t, "200 OK", resp.Status())
-	assertNotNil(t, resp.BodyBytes())
+	assertNotNil(t, resp.Body())
 	assertEqual(t, "TestGet: text response", resp.String())
 
 	logResponse(t, resp)
@@ -1801,7 +1825,7 @@ func TestRequestOverridesClientAuthorizationHeader(t *testing.T) {
 	c := dc()
 	c.SetTLSClientConfig(&tls.Config{InsecureSkipVerify: true}).
 		SetHeader("Authorization", "some token").
-		SetBaseURL(ts.URL + "/")
+		SetHostURL(ts.URL + "/")
 
 	resp, err := c.R().
 		SetHeader("Authorization", "Bearer 004DDB79-6801-4587-B976-F093E6AC44FF").
@@ -1852,7 +1876,7 @@ func TestHostHeaderOverride(t *testing.T) {
 	assertError(t, err)
 	assertEqual(t, http.StatusOK, resp.StatusCode())
 	assertEqual(t, "200 OK", resp.Status())
-	assertNotNil(t, resp.BodyBytes())
+	assertNotNil(t, resp.Body())
 	assertEqual(t, "myhostname", resp.String())
 
 	logResponse(t, resp)
@@ -1875,6 +1899,8 @@ func TestNotFoundWithError(t *testing.T) {
 	assertError(t, err)
 	assertEqual(t, http.StatusNotFound, resp.StatusCode())
 	assertEqual(t, "404 Not Found", resp.Status())
+	assertNotNil(t, resp.Body())
+	assertEqual(t, "{\"error\": \"Not found\"}", resp.String())
 	assertNotNil(t, httpError)
 	assertEqual(t, "Not found", httpError.Error)
 
@@ -1896,6 +1922,8 @@ func TestNotFoundWithoutError(t *testing.T) {
 	assertError(t, err)
 	assertEqual(t, http.StatusNotFound, resp.StatusCode())
 	assertEqual(t, "404 Not Found", resp.Status())
+	assertNotNil(t, resp.Body())
+	assertEqual(t, 0, len(resp.Body()))
 	assertNotNil(t, httpError)
 	assertEqual(t, "", httpError.Error)
 
@@ -1939,9 +1967,6 @@ func TestRawPathParamURLInput(t *testing.T) {
 			"path":   "users/developers",
 		})
 
-	assertEqual(t, "sample@sample.com", c.RawPathParams()["userId"])
-	assertEqual(t, "users/developers", c.RawPathParams()["path"])
-
 	resp, err := c.R().
 		SetRawPathParams(map[string]string{
 			"subAccountId": "100002",
@@ -1964,7 +1989,7 @@ func TestTraceInfo(t *testing.T) {
 	serverAddr := ts.URL[strings.LastIndex(ts.URL, "/")+1:]
 
 	client := dc()
-	client.SetBaseURL(ts.URL).EnableTrace()
+	client.SetHostURL(ts.URL).EnableTrace()
 	for _, u := range []string{"/", "/json", "/long-text", "/long-json"} {
 		resp, err := client.R().Get(u)
 		assertNil(t, err)
@@ -2009,7 +2034,7 @@ func TestTraceInfoWithoutEnableTrace(t *testing.T) {
 	defer ts.Close()
 
 	client := dc()
-	client.SetBaseURL(ts.URL)
+	client.SetHostURL(ts.URL)
 	for _, u := range []string{"/", "/json", "/long-text", "/long-json"} {
 		resp, err := client.R().Get(u)
 		assertNil(t, err)
@@ -2027,7 +2052,7 @@ func TestTraceInfoWithoutEnableTrace(t *testing.T) {
 
 func TestTraceInfoOnTimeout(t *testing.T) {
 	client := dc()
-	client.SetBaseURL("http://resty-nowhere.local").EnableTrace()
+	client.SetHostURL("http://resty-nowhere.local").EnableTrace()
 
 	resp, err := client.R().Get("/")
 	assertNotNil(t, err)
@@ -2048,97 +2073,84 @@ func TestDebugLoggerRequestBodyTooLarge(t *testing.T) {
 	ts := createFilePostServer(t)
 	defer ts.Close()
 
-	debugBodySizeLimit := 512
+	debugBodySizeLimit := int64(512)
 
-	t.Run("upload an image with more than 512 bytes", func(t *testing.T) {
-		// upload an image with more than 512 bytes
-		output := bytes.NewBufferString("")
-		resp, err := New().SetDebug(true).outputLogTo(output).SetDebugBodyLimit(debugBodySizeLimit).R().
-			SetFile("file", filepath.Join(getTestDataPath(), "test-img.png")).
-			SetHeader("Content-Type", "image/png").
-			Post(ts.URL + "/upload")
-		assertNil(t, err)
-		assertNotNil(t, resp)
-		assertEqual(t, true, strings.Contains(output.String(), "REQUEST TOO LARGE"))
-	})
+	// upload an image with more than 512 bytes
+	output := bytes.NewBufferString("")
+	resp, err := New().SetDebug(true).outputLogTo(output).SetDebugBodyLimit(debugBodySizeLimit).R().
+		SetFile("file", filepath.Join(getTestDataPath(), "test-img.png")).
+		SetHeader("Content-Type", "image/png").
+		Post(ts.URL + "/upload")
+	assertNil(t, err)
+	assertNotNil(t, resp)
+	assertEqual(t, true, strings.Contains(output.String(), "REQUEST TOO LARGE"))
 
-	t.Run("upload a text file with no more than 512 bytes", func(t *testing.T) {
-		// upload a text file with no more than 512 bytes
-		output := bytes.NewBufferString("")
-		resp, err := New().outputLogTo(output).SetDebugBodyLimit(debugBodySizeLimit).R().
-			SetDebug(true).
-			SetFile("file", filepath.Join(getTestDataPath(), "text-file.txt")).
-			SetHeader("Content-Type", "text/plain").
-			Post(ts.URL + "/upload")
-		assertNil(t, err)
-		assertNotNil(t, resp)
-		assertEqual(t, true, strings.Contains(output.String(), " THIS IS TEXT FILE FOR MULTIPART UPLOAD TEST "))
-	})
+	// upload a text file with no more than 512 bytes
+	output = bytes.NewBufferString("")
+	resp, err = New().outputLogTo(output).SetDebugBodyLimit(debugBodySizeLimit).R().
+		SetDebug(true).
+		SetFile("file", filepath.Join(getTestDataPath(), "text-file.txt")).
+		SetHeader("Content-Type", "text/plain").
+		Post(ts.URL + "/upload")
+	assertNil(t, err)
+	assertNotNil(t, resp)
+	assertEqual(t, true, strings.Contains(output.String(), " THIS IS TEXT FILE FOR MULTIPART UPLOAD TEST "))
 
 	formTs := createFormPostServer(t)
 	defer formTs.Close()
 
-	t.Run("post form with more than 512 bytes data", func(t *testing.T) {
-		// post form with more than 512 bytes data
-		output := bytes.NewBufferString("")
-		resp, err := New().SetDebug(true).outputLogTo(output).SetDebugBodyLimit(debugBodySizeLimit).R().
-			SetFormData(map[string]string{
-				"first_name": "Alex",
-				"last_name":  strings.Repeat("C", int(debugBodySizeLimit)),
-				"zip_code":   "00001",
-			}).
-			SetBasicAuth("myuser", "mypass").
-			Post(formTs.URL + "/profile")
-		assertNil(t, err)
-		assertNotNil(t, resp)
-		assertEqual(t, true, strings.Contains(output.String(), "REQUEST TOO LARGE"))
-	})
+	// post form with more than 512 bytes data
+	output = bytes.NewBufferString("")
+	resp, err = New().SetDebug(true).outputLogTo(output).SetDebugBodyLimit(debugBodySizeLimit).R().
+		SetFormData(map[string]string{
+			"first_name": "Alex",
+			"last_name":  strings.Repeat("C", int(debugBodySizeLimit)),
+			"zip_code":   "00001",
+		}).
+		SetBasicAuth("myuser", "mypass").
+		Post(formTs.URL + "/profile")
+	assertNil(t, err)
+	assertNotNil(t, resp)
+	assertEqual(t, true, strings.Contains(output.String(), "REQUEST TOO LARGE"))
 
-	t.Run("post form with no more than 512 bytes data", func(t *testing.T) {
-		// post form with no more than 512 bytes data
-		output := bytes.NewBufferString("")
-		resp, err := New().outputLogTo(output).SetDebugBodyLimit(debugBodySizeLimit).R().
-			SetDebug(true).
-			SetFormData(map[string]string{
-				"first_name": "Alex",
-				"last_name":  "C",
-				"zip_code":   "00001",
-			}).
-			SetBasicAuth("myuser", "mypass").
-			Post(formTs.URL + "/profile")
-		assertNil(t, err)
-		assertNotNil(t, resp)
-		assertEqual(t, true, strings.Contains(output.String(), "Alex"))
-	})
+	// post form with no more than 512 bytes data
+	output = bytes.NewBufferString("")
+	resp, err = New().outputLogTo(output).SetDebugBodyLimit(debugBodySizeLimit).R().
+		SetDebug(true).
+		SetFormData(map[string]string{
+			"first_name": "Alex",
+			"last_name":  "C",
+			"zip_code":   "00001",
+		}).
+		SetBasicAuth("myuser", "mypass").
+		Post(formTs.URL + "/profile")
+	assertNil(t, err)
+	assertNotNil(t, resp)
+	assertEqual(t, true, strings.Contains(output.String(), "Alex"))
 
-	t.Run("post string with more than 512 bytes data", func(t *testing.T) {
-		// post string with more than 512 bytes data
-		output := bytes.NewBufferString("")
-		resp, err := New().SetDebug(true).outputLogTo(output).SetDebugBodyLimit(debugBodySizeLimit).R().
-			SetBody(`{
+	// post string with more than 512 bytes data
+	output = bytes.NewBufferString("")
+	resp, err = New().SetDebug(true).outputLogTo(output).SetDebugBodyLimit(debugBodySizeLimit).R().
+		SetBody(`{
 			"first_name": "Alex",
 			"last_name": "`+strings.Repeat("C", int(debugBodySizeLimit))+`C",
 			"zip_code": "00001"}`).
-			SetBasicAuth("myuser", "mypass").
-			Post(formTs.URL + "/profile")
-		assertNil(t, err)
-		assertNotNil(t, resp)
-		assertEqual(t, true, strings.Contains(output.String(), "REQUEST TOO LARGE"))
-	})
-
-	t.Run("post slice with more than 512 bytes data", func(t *testing.T) {
-		// post slice with more than 512 bytes data
-		output := bytes.NewBufferString("")
-		resp, err := New().outputLogTo(output).SetDebugBodyLimit(debugBodySizeLimit).R().
-			SetDebug(true).
-			SetBody([]string{strings.Repeat("C", int(debugBodySizeLimit))}).
-			SetBasicAuth("myuser", "mypass").
-			Post(formTs.URL + "/profile")
-		assertNil(t, err)
-		assertNotNil(t, resp)
-		assertEqual(t, true, strings.Contains(output.String(), "REQUEST TOO LARGE"))
-	})
+		SetBasicAuth("myuser", "mypass").
+		Post(formTs.URL + "/profile")
+	assertNil(t, err)
+	assertNotNil(t, resp)
+	assertEqual(t, true, strings.Contains(output.String(), "REQUEST TOO LARGE"))
 
+	// post slice with more than 512 bytes data
+	output = bytes.NewBufferString("")
+	resp, err = New().outputLogTo(output).SetDebugBodyLimit(debugBodySizeLimit).R().
+		SetDebug(true).
+		SetBody([]string{strings.Repeat("C", int(debugBodySizeLimit))}).
+		SetBasicAuth("myuser", "mypass").
+		Post(formTs.URL + "/profile")
+	assertNil(t, err)
+	assertNotNil(t, resp)
+	assertEqual(t, true, strings.Contains(output.String(), "REQUEST TOO LARGE"))
 }
 
 func TestPostMapTemporaryRedirect(t *testing.T) {
@@ -2183,70 +2195,3 @@ func TestSetResultMustNotPanicOnNil(t *testing.T) {
 	}()
 	dc().R().SetResult(nil)
 }
-
-func TestRequestClone(t *testing.T) {
-	ts := createGetServer(t)
-	defer ts.Close()
-
-	c := dc()
-	parent := c.R()
-
-	// set an non-interface value
-	parent.URL = ts.URL
-	parent.SetPathParam("name", "parent")
-	parent.SetRawPathParam("name", "parent")
-	// set http header
-	parent.SetHeader("X-Header", "parent")
-	// set an interface value
-	parent.SetBasicAuth("parent", "")
-	parent.bodyBuf = acquireBuffer()
-	parent.bodyBuf.WriteString("parent")
-	parent.RawRequest = &http.Request{}
-
-	clone := parent.Clone(context.Background())
-
-	// assume parent request is used
-	_, _ = parent.Get(ts.URL)
-
-	// update value of non-interface type - change will only happen on clone
-	clone.URL = "http://localhost.clone"
-	clone.PathParams["name"] = "clone"
-	clone.RawPathParams["name"] = "clone"
-	// update value of http header - change will only happen on clone
-	clone.SetHeader("X-Header", "clone")
-	// update value of interface type - change will only happen on clone
-	clone.UserInfo.Username = "clone"
-	clone.bodyBuf.Reset()
-	clone.bodyBuf.WriteString("clone")
-
-	// assert non-interface type
-	assertEqual(t, "http://localhost.clone", clone.URL)
-	assertEqual(t, ts.URL, parent.URL)
-	assertEqual(t, "clone", clone.PathParams["name"])
-	assertEqual(t, "parent", parent.PathParams["name"])
-	assertEqual(t, "clone", clone.RawPathParams["name"])
-	assertEqual(t, "parent", parent.RawPathParams["name"])
-	// assert http header
-	assertEqual(t, "parent", parent.Header.Get("X-Header"))
-	assertEqual(t, "clone", clone.Header.Get("X-Header"))
-	// assert interface type
-	assertEqual(t, "parent", parent.UserInfo.Username)
-	assertEqual(t, "clone", clone.UserInfo.Username)
-	assertEqual(t, "", parent.bodyBuf.String())
-	assertEqual(t, "clone", clone.bodyBuf.String())
-
-	// parent request should have raw request while clone should not
-	assertNotNil(t, clone.RawRequest)
-	assertNotNil(t, parent.RawRequest)
-	assertNotEqual(t, parent.RawRequest, clone.RawRequest)
-
-	// test SRV
-	parent = c.R()
-	parent.SetSRV(&SRVRecord{"xmpp-server", "google.com"})
-
-	clone = parent.Clone(context.Background())
-	clone.SRV.Service = "xmpp-server-clone"
-
-	assertEqual(t, "xmpp-server", parent.SRV.Service)
-	assertEqual(t, "xmpp-server-clone", clone.SRV.Service)
-}
diff --git a/resty_test.go b/resty_test.go
index bbb568e..e10421c 100644
--- a/resty_test.go
+++ b/resty_test.go
@@ -254,7 +254,7 @@ func createPostServer(t *testing.T) *httptest.Server {
 			case "/login-json-html":
 				w.Header().Set(hdrContentTypeKey, "text/html")
 				w.WriteHeader(http.StatusOK)
-				_, _ = w.Write([]byte(`{ "id": "success", "message": "login successful" }`))
+				_, _ = w.Write([]byte(`<htm><body>Test JSON request with HTML response</body></html>`))
 				return
 			case "/usersmap":
 				// JSON
@@ -271,7 +271,7 @@ func createPostServer(t *testing.T) *httptest.Server {
 						return
 					}
 
-					var users []map[string]any
+					var users []map[string]interface{}
 					jd := json.NewDecoder(r.Body)
 					err := jd.Decode(&users)
 					w.Header().Set(hdrContentTypeKey, "application/json; charset=utf-8")
@@ -385,17 +385,6 @@ func createFormPostServer(t *testing.T) *httptest.Server {
 				return
 			}
 		}
-
-		if r.Method == MethodPut {
-
-			if r.URL.Path == "/raw-upload" {
-				body, _ := io.ReadAll(r.Body)
-				bl, _ := strconv.Atoi(r.Header.Get("Content-Length"))
-				assertEqual(t, len(body), bl)
-				w.WriteHeader(http.StatusOK)
-			}
-
-		}
 	})
 
 	return ts
@@ -829,20 +818,20 @@ func dclr() *Request {
 	return c.R()
 }
 
-func assertNil(t *testing.T, v any) {
+func assertNil(t *testing.T, v interface{}) {
 	t.Helper()
 	if !isNil(v) {
 		t.Errorf("[%v] was expected to be nil", v)
 	}
 }
 
-func assertNotNil(t *testing.T, v any) {
+func assertNotNil(t *testing.T, v interface{}) {
 	if isNil(v) {
 		t.Errorf("[%v] was expected to be non-nil", v)
 	}
 }
 
-func assertType(t *testing.T, typ, v any) {
+func assertType(t *testing.T, typ, v interface{}) {
 	if reflect.DeepEqual(reflect.TypeOf(typ), reflect.TypeOf(v)) {
 		t.Errorf("Expected type %t, got %t", typ, v)
 	}
@@ -862,7 +851,7 @@ func assertErrorIs(t *testing.T, e, g error) (r bool) {
 	return true
 }
 
-func assertEqual(t *testing.T, e, g any) (r bool) {
+func assertEqual(t *testing.T, e, g interface{}) (r bool) {
 	t.Helper()
 	if !equal(e, g) {
 		t.Errorf("Expected [%v], got [%v]", e, g)
@@ -871,7 +860,7 @@ func assertEqual(t *testing.T, e, g any) (r bool) {
 	return
 }
 
-func assertNotEqual(t *testing.T, e, g any) (r bool) {
+func assertNotEqual(t *testing.T, e, g interface{}) (r bool) {
 	if equal(e, g) {
 		t.Errorf("Expected [%v], got [%v]", e, g)
 	} else {
@@ -881,11 +870,11 @@ func assertNotEqual(t *testing.T, e, g any) (r bool) {
 	return
 }
 
-func equal(expected, got any) bool {
+func equal(expected, got interface{}) bool {
 	return reflect.DeepEqual(expected, got)
 }
 
-func isNil(v any) bool {
+func isNil(v interface{}) bool {
 	if v == nil {
 		return true
 	}
diff --git a/retry_test.go b/retry_test.go
index 9d80331..84c12a4 100644
--- a/retry_test.go
+++ b/retry_test.go
@@ -43,14 +43,14 @@ func TestBackoffNoWaitForLastRetry(t *testing.T) {
 	canceledCtx, cancel := context.WithCancel(context.Background())
 	defer cancel()
 
-	client := New()
-	client.SetRetryAfter(func(*Client, *Response) (time.Duration, error) {
-		return 6, nil
-	})
 	resp := &Response{
 		Request: &Request{
-			ctx:    canceledCtx,
-			client: client,
+			ctx: canceledCtx,
+			client: &Client{
+				RetryAfter: func(*Client, *Response) (time.Duration, error) {
+					return 6, nil
+				},
+			},
 		},
 	}
 
@@ -151,14 +151,15 @@ func TestConditionalGet(t *testing.T) {
 	assertError(t, err)
 	assertEqual(t, http.StatusOK, resp.StatusCode())
 	assertEqual(t, "200 OK", resp.Status())
-	assertNotNil(t, resp.BodyBytes())
+	assertNotNil(t, resp.Body())
 	assertEqual(t, "TestGet: text response", resp.String())
 	assertEqual(t, externalCounter, attemptCount)
 
 	logResponse(t, resp)
 }
 
-func TestConditionalGetRequestLevel(t *testing.T) {
+// Check to make sure the package Function works.
+func TestConditionalGetDefaultClient(t *testing.T) {
 	ts := createGetServer(t)
 	defer ts.Close()
 	attemptCount := 1
@@ -172,19 +173,16 @@ func TestConditionalGetRequestLevel(t *testing.T) {
 
 	// Clear the default client.
 	client := dc()
+	// Proceed to check.
+	client.AddRetryCondition(check).SetRetryCount(1)
 	resp, err := client.R().
-		AddRetryCondition(check).
-		SetRetryCount(1).
-		SetRetryWaitTime(time.Duration(50)*time.Millisecond).
-		SetRetryMaxWaitTime(time.Duration(1)*time.Second).
-		SetRetryResetReaders(true).
 		SetQueryParam("request_no", strconv.FormatInt(time.Now().Unix(), 10)).
 		Get(ts.URL + "/")
 
 	assertError(t, err)
 	assertEqual(t, http.StatusOK, resp.StatusCode())
 	assertEqual(t, "200 OK", resp.Status())
-	assertNotNil(t, resp.BodyBytes())
+	assertNotNil(t, resp.Body())
 	assertEqual(t, "TestGet: text response", resp.String())
 	assertEqual(t, externalCounter, attemptCount)
 
@@ -204,7 +202,7 @@ func TestClientRetryGet(t *testing.T) {
 	assertEqual(t, "", resp.Proto())
 	assertEqual(t, 0, resp.StatusCode())
 	assertEqual(t, 0, len(resp.Cookies()))
-	assertNotNil(t, resp.BodyBytes())
+	assertNotNil(t, resp.Body())
 	assertEqual(t, 0, len(resp.Header()))
 
 	assertEqual(t, true, strings.HasPrefix(err.Error(), "Get "+ts.URL+"/set-retrycount-test") ||
@@ -230,7 +228,7 @@ func TestClientRetryWait(t *testing.T) {
 		SetRetryMaxWaitTime(retryMaxWaitTime).
 		AddRetryCondition(
 			func(r *Response, _ error) bool {
-				timeSlept, _ := strconv.ParseUint(string(r.BodyBytes()), 10, 64)
+				timeSlept, _ := strconv.ParseUint(string(r.Body()), 10, 64)
 				retryIntervals[attempt] = timeSlept
 				attempt++
 				return true
@@ -273,7 +271,7 @@ func TestClientRetryWaitMaxInfinite(t *testing.T) {
 		SetRetryMaxWaitTime(retryMaxWaitTime).
 		AddRetryCondition(
 			func(r *Response, _ error) bool {
-				timeSlept, _ := strconv.ParseUint(string(r.BodyBytes()), 10, 64)
+				timeSlept, _ := strconv.ParseUint(string(r.Body()), 10, 64)
 				retryIntervals[attempt] = timeSlept
 				attempt++
 				return true
@@ -335,7 +333,7 @@ func TestClientRetryWaitCallbackError(t *testing.T) {
 		SetRetryAfter(retryAfter).
 		AddRetryCondition(
 			func(r *Response, _ error) bool {
-				timeSlept, _ := strconv.ParseUint(string(r.BodyBytes()), 10, 64)
+				timeSlept, _ := strconv.ParseUint(string(r.Body()), 10, 64)
 				retryIntervals[attempt] = timeSlept
 				attempt++
 				return true
@@ -375,7 +373,7 @@ func TestClientRetryWaitCallback(t *testing.T) {
 		SetRetryAfter(retryAfter).
 		AddRetryCondition(
 			func(r *Response, _ error) bool {
-				timeSlept, _ := strconv.ParseUint(string(r.BodyBytes()), 10, 64)
+				timeSlept, _ := strconv.ParseUint(string(r.Body()), 10, 64)
 				retryIntervals[attempt] = timeSlept
 				attempt++
 				return true
@@ -423,7 +421,7 @@ func TestClientRetryWaitCallbackTooShort(t *testing.T) {
 		SetRetryAfter(retryAfter).
 		AddRetryCondition(
 			func(r *Response, _ error) bool {
-				timeSlept, _ := strconv.ParseUint(string(r.BodyBytes()), 10, 64)
+				timeSlept, _ := strconv.ParseUint(string(r.Body()), 10, 64)
 				retryIntervals[attempt] = timeSlept
 				attempt++
 				return true
@@ -471,7 +469,7 @@ func TestClientRetryWaitCallbackTooLong(t *testing.T) {
 		SetRetryAfter(retryAfter).
 		AddRetryCondition(
 			func(r *Response, _ error) bool {
-				timeSlept, _ := strconv.ParseUint(string(r.BodyBytes()), 10, 64)
+				timeSlept, _ := strconv.ParseUint(string(r.Body()), 10, 64)
 				retryIntervals[attempt] = timeSlept
 				attempt++
 				return true
@@ -520,7 +518,7 @@ func TestClientRetryWaitCallbackSwitchToDefault(t *testing.T) {
 		SetRetryAfter(retryAfter).
 		AddRetryCondition(
 			func(r *Response, _ error) bool {
-				timeSlept, _ := strconv.ParseUint(string(r.BodyBytes()), 10, 64)
+				timeSlept, _ := strconv.ParseUint(string(r.Body()), 10, 64)
 				retryIntervals[attempt] = timeSlept
 				attempt++
 				return true
@@ -570,7 +568,7 @@ func TestClientRetryCancel(t *testing.T) {
 		SetRetryMaxWaitTime(retryMaxWaitTime).
 		AddRetryCondition(
 			func(r *Response, _ error) bool {
-				timeSlept, _ := strconv.ParseUint(string(r.BodyBytes()), 10, 64)
+				timeSlept, _ := strconv.ParseUint(string(r.Body()), 10, 64)
 				retryIntervals[attempt] = timeSlept
 				attempt++
 				return true
@@ -599,11 +597,11 @@ func TestClientRetryPost(t *testing.T) {
 	ts := createPostServer(t)
 	defer ts.Close()
 
-	usersmap := map[string]any{
-		"user1": map[string]any{"FirstName": "firstname1", "LastName": "lastname1", "ZipCode": "10001"},
+	usersmap := map[string]interface{}{
+		"user1": map[string]interface{}{"FirstName": "firstname1", "LastName": "lastname1", "ZipCode": "10001"},
 	}
 
-	var users []map[string]any
+	var users []map[string]interface{}
 	users = append(users, usersmap)
 
 	c := dc()
@@ -619,8 +617,8 @@ func TestClientRetryPost(t *testing.T) {
 	if resp != nil {
 		if resp.StatusCode() == http.StatusInternalServerError {
 			t.Logf("Got response body: %s", resp.String())
-			var usersResponse []map[string]any
-			err := json.Unmarshal(resp.BodyBytes(), &usersResponse)
+			var usersResponse []map[string]interface{}
+			err := json.Unmarshal(resp.body, &usersResponse)
 			assertError(t, err)
 
 			if !reflect.DeepEqual(users, usersResponse) {
@@ -685,7 +683,7 @@ func TestClientRetryCount(t *testing.T) {
 	assertEqual(t, "", resp.Proto())
 	assertEqual(t, 0, resp.StatusCode())
 	assertEqual(t, 0, len(resp.Cookies()))
-	assertNotNil(t, resp.BodyBytes())
+	assertNotNil(t, resp.Body())
 	assertEqual(t, 0, len(resp.Header()))
 
 	// 2 attempts were made
@@ -726,28 +724,21 @@ func TestClientRetryHook(t *testing.T) {
 
 	attempt := 0
 
-	retryHook := func(r *Response, _ error) {
-		attempt++
-	}
-
 	c := dc().
 		SetRetryCount(2).
 		SetTimeout(time.Second * 3).
-		AddRetryHook(retryHook)
-
-	// Since reflect.DeepEqual can not compare two functions
-	// just compare pointers of the two hooks
-	originHookPointer := reflect.ValueOf(retryHook).Pointer()
-	getterHookPointer := reflect.ValueOf(c.RetryHooks()[0]).Pointer()
-
-	assertEqual(t, originHookPointer, getterHookPointer)
+		AddRetryHook(
+			func(r *Response, _ error) {
+				attempt++
+			},
+		)
 
 	resp, err := c.R().Get(ts.URL + "/set-retrycount-test")
 	assertEqual(t, "", resp.Status())
 	assertEqual(t, "", resp.Proto())
 	assertEqual(t, 0, resp.StatusCode())
 	assertEqual(t, 0, len(resp.Cookies()))
-	assertNotNil(t, resp.BodyBytes())
+	assertNotNil(t, resp.Body())
 	assertEqual(t, 0, len(resp.Header()))
 
 	assertEqual(t, 3, attempt)
@@ -792,8 +783,6 @@ func TestResetMultipartReaderSeekStartError(t *testing.T) {
 		SetRetryResetReaders(true).
 		AddRetryAfterErrorCondition()
 
-	assertEqual(t, true, c.RetryResetReaders())
-
 	resp, err := c.R().
 		SetFileReader("name", "filename", testSeeker).
 		Post(ts.URL + "/set-reset-multipart-readers-test")
@@ -802,7 +791,7 @@ func TestResetMultipartReaderSeekStartError(t *testing.T) {
 	assertEqual(t, err.Error(), errSeekFailure.Error())
 }
 
-func TestClientResetMultipartReaders(t *testing.T) {
+func TestResetMultipartReaders(t *testing.T) {
 	ts := createFilePostServer(t)
 	defer ts.Close()
 
@@ -827,8 +816,6 @@ func TestClientResetMultipartReaders(t *testing.T) {
 			},
 		)
 
-	assertEqual(t, true, c.RetryResetReaders())
-
 	resp, err := c.R().
 		SetFileReader("name", "filename", bufReader).
 		Post(ts.URL + "/set-reset-multipart-readers-test")
@@ -836,39 +823,3 @@ func TestClientResetMultipartReaders(t *testing.T) {
 	assertEqual(t, 500, resp.StatusCode())
 	assertNil(t, err)
 }
-
-func TestRequestResetMultipartReaders(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().
-		SetTimeout(time.Second * 3).
-		AddRetryAfterErrorCondition().
-		AddRetryHook(
-			func(response *Response, _ error) {
-				read, err := bufReader.Read(bufCpy)
-
-				assertNil(t, err)
-				assertEqual(t, len(buf), read)
-				assertEqual(t, str, string(bufCpy))
-			},
-		)
-
-	assertEqual(t, false, c.RetryResetReaders())
-
-	req := c.R().
-		SetRetryCount(2).
-		SetRetryResetReaders(true).
-		SetFileReader("name", "filename", bufReader)
-	resp, err := req.Post(ts.URL + "/set-reset-multipart-readers-test")
-
-	assertEqual(t, true, req.RetryResetReaders)
-	assertEqual(t, 500, resp.StatusCode())
-	assertNil(t, err)
-}
diff --git a/util_test.go b/util_test.go
index c6039cf..4d0a888 100644
--- a/util_test.go
+++ b/util_test.go
@@ -6,8 +6,8 @@ package resty
 
 import (
 	"bytes"
+	"errors"
 	"mime/multipart"
-	"net/url"
 	"testing"
 )
 
@@ -91,14 +91,17 @@ func TestWriteMultipartFormFileReaderError(t *testing.T) {
 	assertEqual(t, "read error", err.Error())
 }
 
-func TestCloneURLValues(t *testing.T) {
-	v := url.Values{}
-	v.Add("foo", "bar")
-	v.Add("foo", "baz")
-	v.Add("qux", "quux")
+func TestRestyErrorFuncs(t *testing.T) {
+	ne1 := errors.New("new error 1")
+	nie1 := errors.New("inner error 1")
 
-	c := cloneURLValues(v)
-	nilUrl := cloneURLValues(nil)
-	assertEqual(t, v, c)
-	assertNil(t, nilUrl)
+	e := wrapErrors(ne1, nie1)
+	assertEqual(t, "new error 1", e.Error())
+	assertEqual(t, "inner error 1", errors.Unwrap(e).Error())
+
+	e = wrapErrors(ne1, nil)
+	assertEqual(t, "new error 1", e.Error())
+
+	e = wrapErrors(nil, nie1)
+	assertEqual(t, "inner error 1", e.Error())
 }