github.com/bingoohuang/gg@v0.0.0-20240325092523-45da7dee9335/pkg/resty/client.go (about)

     1  // Copyright (c) 2015-2021 Jeevanandam M (jeeva@myjeeva.com), All rights reserved.
     2  // resty source code and usage is governed by a MIT style
     3  // license that can be found in the LICENSE file.
     4  
     5  package resty
     6  
     7  import (
     8  	"bytes"
     9  	"compress/gzip"
    10  	"crypto/tls"
    11  	"crypto/x509"
    12  	"encoding/json"
    13  	"encoding/xml"
    14  	"errors"
    15  	"fmt"
    16  	"io"
    17  	"math"
    18  	"net/http"
    19  	"net/url"
    20  	"os"
    21  	"reflect"
    22  	"regexp"
    23  	"strings"
    24  	"sync"
    25  	"time"
    26  )
    27  
    28  var (
    29  	hdrUserAgentKey       = http.CanonicalHeaderKey("User-Agent")
    30  	hdrAcceptKey          = http.CanonicalHeaderKey("Accept")
    31  	hdrContentTypeKey     = http.CanonicalHeaderKey("Content-Type")
    32  	hdrContentLengthKey   = http.CanonicalHeaderKey("Content-Length")
    33  	hdrContentEncodingKey = http.CanonicalHeaderKey("Content-Encoding")
    34  	hdrLocationKey        = http.CanonicalHeaderKey("Location")
    35  
    36  	plainTextType   = "text/plain; charset=utf-8"
    37  	jsonContentType = "application/json"
    38  	formContentType = "application/x-www-form-urlencoded"
    39  
    40  	jsonCheck = regexp.MustCompile(`(?i:(application|text)/(json|.*\+json|json-.*)(;|$))`)
    41  	xmlCheck  = regexp.MustCompile(`(?i:(application|text)/(xml|.*\+xml)(;|$))`)
    42  
    43  	hdrUserAgentValue = "go-resty/" + Version + " (https://github.com/go-resty/resty)"
    44  	bufPool           = &sync.Pool{New: func() interface{} { return &bytes.Buffer{} }}
    45  )
    46  
    47  type (
    48  	// RequestMiddleware type is for request middleware, called before a request is sent
    49  	RequestMiddleware func(*Client, *Request) error
    50  
    51  	// ResponseMiddleware type is for response middleware, called after a response has been received
    52  	ResponseMiddleware func(*Client, *Response) error
    53  
    54  	// PreRequestHook type is for the request hook, called right before the request is sent
    55  	PreRequestHook func(*Client, *http.Request) error
    56  
    57  	// RequestLogCallback type is for request logs, called before the request is logged
    58  	RequestLogCallback func(*RequestLog) error
    59  
    60  	// ResponseLogCallback type is for response logs, called before the response is logged
    61  	ResponseLogCallback func(*ResponseLog) error
    62  
    63  	// ErrorHook type is for reacting to request errors, called after all retries were attempted
    64  	ErrorHook func(*Request, error)
    65  )
    66  
    67  // Client struct is used to create Resty client with client level settings,
    68  // these settings are applicable to all the request raised from the client.
    69  //
    70  // Resty also provides an options to override most of the client settings
    71  // at request level.
    72  type Client struct {
    73  	BaseURL               string
    74  	QueryParam            url.Values
    75  	FormData              url.Values
    76  	PathParams            map[string]string
    77  	Header                http.Header
    78  	UserInfo              *User
    79  	Token                 string
    80  	AuthScheme            string
    81  	Cookies               []*http.Cookie
    82  	Error                 reflect.Type
    83  	Debug                 bool
    84  	DisableWarn           bool
    85  	AllowGetMethodPayload bool
    86  	RetryCount            int
    87  	RetryWaitTime         time.Duration
    88  	RetryMaxWaitTime      time.Duration
    89  	RetryConditions       []RetryConditionFunc
    90  	RetryHooks            []OnRetryFunc
    91  	RetryAfter            RetryAfterFunc
    92  	JSONMarshal           func(v interface{}) ([]byte, error)
    93  	JSONUnmarshal         func(data []byte, v interface{}) error
    94  	XMLMarshal            func(v interface{}) ([]byte, error)
    95  	XMLUnmarshal          func(data []byte, v interface{}) error
    96  
    97  	// HeaderAuthorizationKey is used to set/access Request Authorization header
    98  	// value when `SetAuthToken` option is used.
    99  	HeaderAuthorizationKey string
   100  
   101  	jsonEscapeHTML     bool
   102  	setContentLength   bool
   103  	closeConnection    bool
   104  	notParseResponse   bool
   105  	trace              bool
   106  	debugBodySizeLimit int64
   107  	outputDirectory    string
   108  	scheme             string
   109  
   110  	log             Logger
   111  	httpClient      *http.Client
   112  	proxyURL        *url.URL
   113  	beforeRequest   []RequestMiddleware
   114  	udBeforeRequest []RequestMiddleware
   115  	preReqHook      PreRequestHook
   116  	afterResponse   []ResponseMiddleware
   117  	requestLog      RequestLogCallback
   118  	responseLog     ResponseLogCallback
   119  	errorHooks      []ErrorHook
   120  }
   121  
   122  // User type is to hold a username and password information
   123  type User struct {
   124  	Username, Password string
   125  }
   126  
   127  //‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
   128  // Client methods
   129  //___________________________________
   130  
   131  // SetBaseURL method is to set Base URL in the client instance. It will be used with request
   132  // raised from this client with relative URL
   133  //
   134  //	// Setting HTTP address
   135  //	client.SetBaseURL("http://myjeeva.com")
   136  //
   137  //	// Setting HTTPS address
   138  //	client.SetBaseURL("https://myjeeva.com")
   139  func (c *Client) SetBaseURL(url string) *Client {
   140  	c.BaseURL = strings.TrimRight(url, "/")
   141  	return c
   142  }
   143  
   144  // SetHeader method sets a single header field and its value in the client instance.
   145  // These headers will be applied to all requests raised from this client instance.
   146  // Also, it can be overridden at request level header options.
   147  //
   148  // See `Request.SetHeader` or `Request.SetHeaders`.
   149  //
   150  // For Example: To set `Content-Type` and `Accept` as `application/json`
   151  //
   152  //	client.
   153  //		SetHeader("Content-Type", "application/json").
   154  //		SetHeader("Accept", "application/json")
   155  //		SetHeader("Content-Type", "application/json", "Accept", "application/json")
   156  func (c *Client) SetHeader(header, value string, headerValues ...string) *Client {
   157  	c.Header.Set(header, value)
   158  	for i := 0; i+1 < len(headerValues); i++ {
   159  		c.Header.Set(headerValues[i], headerValues[i+1])
   160  	}
   161  	return c
   162  }
   163  
   164  // SetHeaders method sets multiple headers field and its values at one go in the client instance.
   165  // These headers will be applied to all requests raised from this client instance. Also it can be
   166  // overridden at request level headers options.
   167  //
   168  // See `Request.SetHeaders` or `Request.SetHeader`.
   169  //
   170  // For Example: To set `Content-Type` and `Accept` as `application/json`
   171  //
   172  //	client.SetHeaders(map[string]string{
   173  //			"Content-Type": "application/json",
   174  //			"Accept": "application/json",
   175  //		})
   176  func (c *Client) SetHeaders(headers map[string]string) *Client {
   177  	for h, v := range headers {
   178  		c.Header.Set(h, v)
   179  	}
   180  	return c
   181  }
   182  
   183  // SetHeaderVerbatim method is to set a single header field and its value verbatim in the current request.
   184  //
   185  // For Example: To set `all_lowercase` and `UPPERCASE` as `available`.
   186  //
   187  //	client.R().
   188  //		SetHeaderVerbatim("all_lowercase", "available").
   189  //		SetHeaderVerbatim("UPPERCASE", "available")
   190  //
   191  // Also you can override header value, which was set at client instance level.
   192  func (c *Client) SetHeaderVerbatim(header, value string) *Client {
   193  	c.Header[header] = []string{value}
   194  	return c
   195  }
   196  
   197  // SetCookieJar method sets custom http.CookieJar in the resty client. Its way to override default.
   198  //
   199  // For Example: sometimes we don't want to save cookies in api contacting, we can remove the default
   200  // CookieJar in resty client.
   201  //
   202  //	client.SetCookieJar(nil)
   203  func (c *Client) SetCookieJar(jar http.CookieJar) *Client {
   204  	c.httpClient.Jar = jar
   205  	return c
   206  }
   207  
   208  // SetCookies method sets an array of cookies in the client instance.
   209  // These cookies will be added to all the request raised from this client instance.
   210  //
   211  //	client.SetCookies(&http.Cookie{
   212  //				Name:"go-resty",
   213  //				Value:"This is cookie value",
   214  //			})
   215  //	cookies := []*http.Cookie{
   216  //		&http.Cookie{
   217  //			Name:"go-resty-1",
   218  //			Value:"This is cookie 1 value",
   219  //		},
   220  //		&http.Cookie{
   221  //			Name:"go-resty-2",
   222  //			Value:"This is cookie 2 value",
   223  //		},
   224  //	}
   225  //
   226  //	// Setting a cookies into resty
   227  //	client.SetCookies(cookies...)
   228  func (c *Client) SetCookies(cs ...*http.Cookie) *Client {
   229  	c.Cookies = append(c.Cookies, cs...)
   230  	return c
   231  }
   232  
   233  // SetQueryParam method sets single parameter and its value in the client instance.
   234  // It will be formed as query string for the request.
   235  //
   236  // For Example: `search=kitchen%20papers&size=large`
   237  // in the URL after `?` mark. These query params will be added to all the request raised from
   238  // this client instance. Also, it can be overridden at request level Query Param options.
   239  //
   240  // See `Request.SetQueryParam` or `Request.SetQueryParams`.
   241  //
   242  //	client.
   243  //		SetQueryParam("search", "kitchen papers").
   244  //		SetQueryParam("size", "large")
   245  //		SetQueryParam("search", "kitchen papers", "size", "large")
   246  func (c *Client) SetQueryParam(param, value string, paramValues ...string) *Client {
   247  	c.QueryParam.Set(param, value)
   248  
   249  	for i := 0; i+1 < len(paramValues); i++ {
   250  		c.QueryParam.Set(paramValues[i], paramValues[i+1])
   251  	}
   252  	return c
   253  }
   254  
   255  // SetQueryParams method sets multiple parameters and their values at one go in the client instance.
   256  // It will be formed as query string for the request.
   257  //
   258  // For Example: `search=kitchen%20papers&size=large`
   259  // in the URL after `?` mark. These query params will be added to all the request raised from this
   260  // client instance. Also, it can be overridden at request level Query Param options.
   261  //
   262  // See `Request.SetQueryParams` or `Request.SetQueryParam`.
   263  //
   264  //	client.SetQueryParams(map[string]string{
   265  //			"search": "kitchen papers",
   266  //			"size": "large",
   267  //		})
   268  func (c *Client) SetQueryParams(params map[string]string) *Client {
   269  	for p, v := range params {
   270  		c.SetQueryParam(p, v)
   271  	}
   272  	return c
   273  }
   274  
   275  // SetFormData method sets Form parameters and their values in the client instance.
   276  // It's applicable only HTTP method `POST` and `PUT` and requets content type would be set as
   277  // `application/x-www-form-urlencoded`. These form data will be added to all the request raised from
   278  // this client instance. Also it can be overridden at request level form data.
   279  //
   280  // See `Request.SetFormData`.
   281  //
   282  //	client.SetFormData(map[string]string{
   283  //			"access_token": "BC594900-518B-4F7E-AC75-BD37F019E08F",
   284  //			"user_id": "3455454545",
   285  //		})
   286  func (c *Client) SetFormData(data map[string]string) *Client {
   287  	for k, v := range data {
   288  		c.FormData.Set(k, v)
   289  	}
   290  	return c
   291  }
   292  
   293  // SetBasicAuth method sets the basic authentication header in the HTTP request. For Example:
   294  //
   295  //	Authorization: Basic <base64-encoded-value>
   296  //
   297  // For Example: To set the header for username "go-resty" and password "welcome"
   298  //
   299  //	client.SetBasicAuth("go-resty", "welcome")
   300  //
   301  // This basic auth information gets added to all the request rasied from this client instance.
   302  // Also, it can be overridden or set one at the request level is supported.
   303  //
   304  // See `Request.SetBasicAuth`.
   305  func (c *Client) SetBasicAuth(username, password string) *Client {
   306  	c.UserInfo = &User{Username: username, Password: password}
   307  	return c
   308  }
   309  
   310  // SetAuthToken method sets the auth token of the `Authorization` header for all HTTP requests.
   311  // The default auth scheme is `Bearer`, it can be customized with the method `SetAuthScheme`. For Example:
   312  //
   313  //	Authorization: <auth-scheme> <auth-token-value>
   314  //
   315  // For Example: To set auth token BC594900518B4F7EAC75BD37F019E08FBC594900518B4F7EAC75BD37F019E08F
   316  //
   317  //	client.SetAuthToken("BC594900518B4F7EAC75BD37F019E08FBC594900518B4F7EAC75BD37F019E08F")
   318  //
   319  // This auth token gets added to all the requests rasied from this client instance.
   320  // Also, it can be overridden or set one at the request level is supported.
   321  //
   322  // See `Request.SetAuthToken`.
   323  func (c *Client) SetAuthToken(token string) *Client {
   324  	c.Token = token
   325  	return c
   326  }
   327  
   328  // SetAuthScheme method sets the auth scheme type in the HTTP request. For Example:
   329  //
   330  //	Authorization: <auth-scheme-value> <auth-token-value>
   331  //
   332  // For Example: To set the scheme to use OAuth
   333  //
   334  //	client.SetAuthScheme("OAuth")
   335  //
   336  // This auth scheme gets added to all the requests rasied from this client instance.
   337  // Also, it can be overridden or set one at the request level is supported.
   338  //
   339  // Information about auth schemes can be found in RFC7235 which is linked to below
   340  // along with the page containing the currently defined official authentication schemes:
   341  //
   342  //	https://tools.ietf.org/html/rfc7235
   343  //	https://www.iana.org/assignments/http-authschemes/http-authschemes.xhtml#authschemes
   344  //
   345  // See `Request.SetAuthToken`.
   346  func (c *Client) SetAuthScheme(scheme string) *Client {
   347  	c.AuthScheme = scheme
   348  	return c
   349  }
   350  
   351  // R method creates a new request instance, It's used for Get, Post, Put, Delete, Patch, Head, Options, etc.
   352  func (c *Client) R() *Request {
   353  	r := &Request{
   354  		QueryParam: url.Values{},
   355  		FormData:   url.Values{},
   356  		Header:     http.Header{},
   357  		Cookies:    make([]*http.Cookie, 0),
   358  
   359  		client:          c,
   360  		multipartFiles:  []*File{},
   361  		multipartFields: []*MultipartField{},
   362  		PathParams:      map[string]string{},
   363  		jsonEscapeHTML:  true,
   364  	}
   365  	return r
   366  }
   367  
   368  // NewRequest is an alias for method `R()`. Creates a new request instance, It's used for
   369  // Get, Post, Put, Delete, Patch, Head, Options, etc.
   370  func (c *Client) NewRequest() *Request {
   371  	return c.R()
   372  }
   373  
   374  // OnBeforeRequest method appends request middleware into the before request chain.
   375  // Its gets applied after default Resty request middlewares and before request
   376  // been sent from Resty to host server.
   377  //
   378  //	client.OnBeforeRequest(func(c *resty.Client, r *resty.Request) error {
   379  //			// Now you have access to Client and Request instance
   380  //			// manipulate it as per your need
   381  //
   382  //			return nil 	// if its success otherwise return error
   383  //		})
   384  func (c *Client) OnBeforeRequest(m RequestMiddleware) *Client {
   385  	c.udBeforeRequest = append(c.udBeforeRequest, m)
   386  	return c
   387  }
   388  
   389  // OnAfterResponse method appends response middleware into the after response chain.
   390  // Once we receive response from host server, default Resty response middleware
   391  // gets applied and then user assigned response middlewares applied.
   392  //
   393  //	client.OnAfterResponse(func(c *resty.Client, r *resty.Response) error {
   394  //			// Now you have access to Client and Response instance
   395  //			// manipulate it as per your need
   396  //
   397  //			return nil 	// if its success otherwise return error
   398  //		})
   399  func (c *Client) OnAfterResponse(m ResponseMiddleware) *Client {
   400  	c.afterResponse = append(c.afterResponse, m)
   401  	return c
   402  }
   403  
   404  // OnError method adds a callback that will be run whenever a request execution fails.
   405  // This is called after all retries have been attempted (if any).
   406  // If there was a response from the server, the error will be wrapped in *ResponseError
   407  // which has the last response received from the server.
   408  //
   409  //	client.OnError(func(req *resty.Request, err error) {
   410  //		if v, ok := err.(*resty.ResponseError); ok {
   411  //			// Do something with v.Response
   412  //		}
   413  //		// Log the error, increment a metric, etc...
   414  //	})
   415  func (c *Client) OnError(h ErrorHook) *Client {
   416  	c.errorHooks = append(c.errorHooks, h)
   417  	return c
   418  }
   419  
   420  // SetPreRequestHook method sets the given pre-request function into resty client.
   421  // It is called right before the request is fired.
   422  //
   423  // Note: Only one pre-request hook can be registered. Use `client.OnBeforeRequest` for multiple.
   424  func (c *Client) SetPreRequestHook(h PreRequestHook) *Client {
   425  	if c.preReqHook != nil {
   426  		c.log.Warnf("Overwriting an existing pre-request hook: %s", funcName(h))
   427  	}
   428  	c.preReqHook = h
   429  	return c
   430  }
   431  
   432  // SetDebug method enables the debug mode on Resty client. Client logs details of every request and response.
   433  // For `Request` it logs information such as HTTP verb, Relative URL path, Host, Headers, Body if it has one.
   434  // For `Response` it logs information such as Status, Response Time, Headers, Body if it has one.
   435  //
   436  //	client.SetDebug(true)
   437  func (c *Client) SetDebug(d bool) *Client {
   438  	c.Debug = d
   439  	return c
   440  }
   441  
   442  // SetDebugBodyLimit sets the maximum size for which the response and request body will be logged in debug mode.
   443  //
   444  //	client.SetDebugBodyLimit(1000000)
   445  func (c *Client) SetDebugBodyLimit(sl int64) *Client {
   446  	c.debugBodySizeLimit = sl
   447  	return c
   448  }
   449  
   450  // OnRequestLog method used to set request log callback into Resty. Registered callback gets
   451  // called before the resty actually logs the information.
   452  func (c *Client) OnRequestLog(rl RequestLogCallback) *Client {
   453  	if c.requestLog != nil {
   454  		c.log.Warnf("Overwriting on-request-log callback from=%s to=%s", funcName(c.requestLog), funcName(rl))
   455  	}
   456  	c.requestLog = rl
   457  	return c
   458  }
   459  
   460  // OnResponseLog method used to set response log callback into Resty. Registered callback gets
   461  // called before the resty actually logs the information.
   462  func (c *Client) OnResponseLog(rl ResponseLogCallback) *Client {
   463  	if c.responseLog != nil {
   464  		c.log.Warnf("Overwriting on-response-log callback from=%s to=%s", funcName(c.responseLog), funcName(rl))
   465  	}
   466  	c.responseLog = rl
   467  	return c
   468  }
   469  
   470  // SetDisableWarn method disables the warning message on Resty client.
   471  //
   472  // For Example: Resty warns the user when BasicAuth used on non-TLS mode.
   473  //
   474  //	client.SetDisableWarn(true)
   475  func (c *Client) SetDisableWarn(d bool) *Client {
   476  	c.DisableWarn = d
   477  	return c
   478  }
   479  
   480  // SetAllowGetMethodPayload method allows the GET method with payload on Resty client.
   481  //
   482  // For Example: Resty allows the user sends request with a payload on HTTP GET method.
   483  //
   484  //	client.SetAllowGetMethodPayload(true)
   485  func (c *Client) SetAllowGetMethodPayload(a bool) *Client {
   486  	c.AllowGetMethodPayload = a
   487  	return c
   488  }
   489  
   490  // SetLogger method sets given writer for logging Resty request and response details.
   491  //
   492  // Compliant to interface `resty.Logger`.
   493  func (c *Client) SetLogger(l Logger) *Client {
   494  	c.log = l
   495  	return c
   496  }
   497  
   498  // SetContentLength method enables the HTTP header `Content-Length` value for every request.
   499  // By default, Resty won't set `Content-Length`.
   500  //
   501  //	client.SetContentLength(true)
   502  //
   503  // Also you have an option to enable for particular request. See `Request.SetContentLength`
   504  func (c *Client) SetContentLength(l bool) *Client {
   505  	c.setContentLength = l
   506  	return c
   507  }
   508  
   509  // SetTimeout method sets timeout for request raised from client.
   510  //
   511  //	client.SetTimeout(time.Duration(1 * time.Minute))
   512  func (c *Client) SetTimeout(timeout time.Duration) *Client {
   513  	c.httpClient.Timeout = timeout
   514  	return c
   515  }
   516  
   517  // SetError method is to register the global or client common `Error` object into Resty.
   518  // It is used for automatic unmarshalling if response status code is greater than 399 and
   519  // content type either JSON or XML. Can be pointer or non-pointer.
   520  //
   521  //	client.SetError(&Error{})
   522  //	// OR
   523  //	client.SetError(Error{})
   524  func (c *Client) SetError(err interface{}) *Client {
   525  	c.Error = typeOf(err)
   526  	return c
   527  }
   528  
   529  // SetRedirectPolicy method sets the client redirect poilicy. Resty provides ready to use
   530  // redirect policies. Want to create one for yourself refer to `redirect.go`.
   531  //
   532  //	client.SetRedirectPolicy(FlexibleRedirectPolicy(20))
   533  //
   534  //	// Need multiple redirect policies together
   535  //	client.SetRedirectPolicy(FlexibleRedirectPolicy(20), DomainCheckRedirectPolicy("host1.com", "host2.net"))
   536  func (c *Client) SetRedirectPolicy(policies ...interface{}) *Client {
   537  	for _, p := range policies {
   538  		if _, ok := p.(RedirectPolicy); !ok {
   539  			c.log.Errorf("%v should implement resty.RedirectPolicy", funcName(p))
   540  		}
   541  	}
   542  
   543  	c.httpClient.CheckRedirect = func(req *http.Request, via []*http.Request) error {
   544  		for _, p := range policies {
   545  			if err := p.(RedirectPolicy).Apply(req, via); err != nil {
   546  				return err
   547  			}
   548  		}
   549  		return nil // looks good, go ahead
   550  	}
   551  
   552  	return c
   553  }
   554  
   555  // SetRetryCount method enables retry on Resty client and allows you
   556  // to set no. of retry count. Resty uses a Backoff mechanism.
   557  func (c *Client) SetRetryCount(count int) *Client {
   558  	c.RetryCount = count
   559  	return c
   560  }
   561  
   562  // SetRetryWaitTime method sets default wait time to sleep before retrying
   563  // request.
   564  //
   565  // Default is 100 milliseconds.
   566  func (c *Client) SetRetryWaitTime(waitTime time.Duration) *Client {
   567  	c.RetryWaitTime = waitTime
   568  	return c
   569  }
   570  
   571  // SetRetryMaxWaitTime method sets max wait time to sleep before retrying
   572  // request.
   573  //
   574  // Default is 2 seconds.
   575  func (c *Client) SetRetryMaxWaitTime(maxWaitTime time.Duration) *Client {
   576  	c.RetryMaxWaitTime = maxWaitTime
   577  	return c
   578  }
   579  
   580  // SetRetryAfter sets callback to calculate wait time between retries.
   581  // Default (nil) implies exponential backoff with jitter
   582  func (c *Client) SetRetryAfter(callback RetryAfterFunc) *Client {
   583  	c.RetryAfter = callback
   584  	return c
   585  }
   586  
   587  // AddRetryCondition method adds a retry condition function to array of functions
   588  // that are checked to determine if the request is retried. The request will
   589  // retry if any of the functions return true and error is nil.
   590  //
   591  // Note: These retry conditions are applied on all Request made using this Client.
   592  // For Request specific retry conditions check *Request.AddRetryCondition
   593  func (c *Client) AddRetryCondition(condition RetryConditionFunc) *Client {
   594  	c.RetryConditions = append(c.RetryConditions, condition)
   595  	return c
   596  }
   597  
   598  // AddRetryAfterErrorCondition adds the basic condition of retrying after encountering
   599  // an error from the http response
   600  func (c *Client) AddRetryAfterErrorCondition() *Client {
   601  	c.AddRetryCondition(func(response *Response, err error) bool {
   602  		return response.IsError()
   603  	})
   604  	return c
   605  }
   606  
   607  // AddRetryHook adds a side-effecting retry hook to an array of hooks
   608  // that will be executed on each retry.
   609  func (c *Client) AddRetryHook(hook OnRetryFunc) *Client {
   610  	c.RetryHooks = append(c.RetryHooks, hook)
   611  	return c
   612  }
   613  
   614  // SetTLSClientConfig method sets TLSClientConfig for underling client Transport.
   615  //
   616  // For Example:
   617  //
   618  //	// One can set custom root-certificate. Refer: http://golang.org/pkg/crypto/tls/#example_Dial
   619  //	client.SetTLSClientConfig(&tls.Config{ RootCAs: roots })
   620  //
   621  //	// or One can disable security check (https)
   622  //	client.SetTLSClientConfig(&tls.Config{ InsecureSkipVerify: true })
   623  //
   624  // Note: This method overwrites existing `TLSClientConfig`.
   625  func (c *Client) SetTLSClientConfig(config *tls.Config) *Client {
   626  	transport, err := c.transport()
   627  	if err != nil {
   628  		c.log.Errorf("%v", err)
   629  		return c
   630  	}
   631  	transport.TLSClientConfig = config
   632  	return c
   633  }
   634  
   635  // SetProxy method sets the Proxy URL and Port for Resty client.
   636  //
   637  //	client.SetProxy("http://proxyserver:8888")
   638  //
   639  // OR Without this `SetProxy` method, you could also set Proxy via environment variable.
   640  //
   641  // Refer to godoc `http.ProxyFromEnvironment`.
   642  func (c *Client) SetProxy(proxyURL string) *Client {
   643  	transport, err := c.transport()
   644  	if err != nil {
   645  		c.log.Errorf("%v", err)
   646  		return c
   647  	}
   648  
   649  	pURL, err := url.Parse(proxyURL)
   650  	if err != nil {
   651  		c.log.Errorf("%v", err)
   652  		return c
   653  	}
   654  
   655  	c.proxyURL = pURL
   656  	transport.Proxy = http.ProxyURL(c.proxyURL)
   657  	return c
   658  }
   659  
   660  // RemoveProxy method removes the proxy configuration from Resty client
   661  //
   662  //	client.RemoveProxy()
   663  func (c *Client) RemoveProxy() *Client {
   664  	transport, err := c.transport()
   665  	if err != nil {
   666  		c.log.Errorf("%v", err)
   667  		return c
   668  	}
   669  	c.proxyURL = nil
   670  	transport.Proxy = nil
   671  	return c
   672  }
   673  
   674  // SetCertificates method helps to set client certificates into Resty conveniently.
   675  func (c *Client) SetCertificates(certs ...tls.Certificate) *Client {
   676  	config, err := c.tlsConfig()
   677  	if err != nil {
   678  		c.log.Errorf("%v", err)
   679  		return c
   680  	}
   681  	config.Certificates = append(config.Certificates, certs...)
   682  	return c
   683  }
   684  
   685  // SetRootCertificate method helps to add one or more root certificates into Resty client
   686  //
   687  //	client.SetRootCertificate("/path/to/root/pemFile.pem")
   688  func (c *Client) SetRootCertificate(pemFilePath string) *Client {
   689  	rootPemData, err := os.ReadFile(pemFilePath)
   690  	if err != nil {
   691  		c.log.Errorf("%v", err)
   692  		return c
   693  	}
   694  
   695  	config, err := c.tlsConfig()
   696  	if err != nil {
   697  		c.log.Errorf("%v", err)
   698  		return c
   699  	}
   700  	if config.RootCAs == nil {
   701  		config.RootCAs = x509.NewCertPool()
   702  	}
   703  
   704  	config.RootCAs.AppendCertsFromPEM(rootPemData)
   705  	return c
   706  }
   707  
   708  // SetRootCertificateFromString method helps to add one or more root certificates into Resty client
   709  //
   710  //	client.SetRootCertificateFromString("pem file content")
   711  func (c *Client) SetRootCertificateFromString(pemContent string) *Client {
   712  	config, err := c.tlsConfig()
   713  	if err != nil {
   714  		c.log.Errorf("%v", err)
   715  		return c
   716  	}
   717  	if config.RootCAs == nil {
   718  		config.RootCAs = x509.NewCertPool()
   719  	}
   720  
   721  	config.RootCAs.AppendCertsFromPEM([]byte(pemContent))
   722  	return c
   723  }
   724  
   725  // SetOutputDirectory method sets output directory for saving HTTP response into file.
   726  // If the output directory not exists then resty creates one. This setting is optional one,
   727  // if you're planning using absolute path in `Request.SetOutput` and can used together.
   728  //
   729  //	client.SetOutputDirectory("/save/http/response/here")
   730  func (c *Client) SetOutputDirectory(dirPath string) *Client {
   731  	c.outputDirectory = dirPath
   732  	return c
   733  }
   734  
   735  // SetTransport method sets custom `*http.Transport` or any `http.RoundTripper`
   736  // compatible interface implementation in the resty client.
   737  //
   738  // Note:
   739  //
   740  // - If transport is not type of `*http.Transport` then you may not be able to
   741  // take advantage of some Resty client settings.
   742  //
   743  // - It overwrites the Resty client transport instance, and it's configurations.
   744  //
   745  //	transport := &http.Transport{
   746  //		// something like Proxying to httptest.Server, etc...
   747  //		Proxy: func(req *http.Request) (*url.URL, error) {
   748  //			return url.Parse(server.URL)
   749  //		},
   750  //	}
   751  //
   752  //	client.SetTransport(transport)
   753  func (c *Client) SetTransport(transport http.RoundTripper) *Client {
   754  	if transport != nil {
   755  		c.httpClient.Transport = transport
   756  	}
   757  	return c
   758  }
   759  
   760  // SetScheme method sets custom scheme in the Resty client. It's way to override default.
   761  //
   762  //	client.SetScheme("http")
   763  func (c *Client) SetScheme(scheme string) *Client {
   764  	if !IsStringEmpty(scheme) {
   765  		c.scheme = strings.TrimSpace(scheme)
   766  	}
   767  	return c
   768  }
   769  
   770  // SetCloseConnection method sets variable `Close` in http request struct with the given
   771  // value. More info: https://golang.org/src/net/http/request.go
   772  func (c *Client) SetCloseConnection(close bool) *Client {
   773  	c.closeConnection = close
   774  	return c
   775  }
   776  
   777  // SetDoNotParseResponse method instructs `Resty` not to parse the response body automatically.
   778  // Resty exposes the raw response body as `io.ReadCloser`. Also do not forget to close the body,
   779  // otherwise you might get into connection leaks, no connection reuse.
   780  //
   781  // Note: Response middlewares are not applicable, if you use this option. Basically you have
   782  // taken over the control of response parsing from `Resty`.
   783  func (c *Client) SetDoNotParseResponse(parse bool) *Client {
   784  	c.notParseResponse = parse
   785  	return c
   786  }
   787  
   788  // SetPathParam method sets single URL path key-value pair in the
   789  // Resty client instance.
   790  //
   791  //	client.SetPathParam("userId", "sample@sample.com")
   792  //
   793  //	Result:
   794  //	   URL - /v1/users/{userId}/details
   795  //	   Composed URL - /v1/users/sample@sample.com/details
   796  //
   797  // It replaces the value of the key while composing the request URL.
   798  //
   799  // Also, it can be overridden at request level Path Params options,
   800  // see `Request.SetPathParam` or `Request.SetPathParams`.
   801  func (c *Client) SetPathParam(param, value string) *Client {
   802  	c.PathParams[param] = value
   803  	return c
   804  }
   805  
   806  // SetPathParams method sets multiple URL path key-value pairs at one go in the
   807  // Resty client instance.
   808  //
   809  //	client.SetPathParams(map[string]string{
   810  //	   "userId": "sample@sample.com",
   811  //	   "subAccountId": "100002",
   812  //	})
   813  //
   814  //	Result:
   815  //	   URL - /v1/users/{userId}/{subAccountId}/details
   816  //	   Composed URL - /v1/users/sample@sample.com/100002/details
   817  //
   818  // It replaces the value of the key while composing the request URL.
   819  //
   820  // Also, it can be overridden at request level Path Params options,
   821  // see `Request.SetPathParam` or `Request.SetPathParams`.
   822  func (c *Client) SetPathParams(params map[string]string) *Client {
   823  	for p, v := range params {
   824  		c.SetPathParam(p, v)
   825  	}
   826  	return c
   827  }
   828  
   829  // SetJSONEscapeHTML method is to enable/disable the HTML escape on JSON marshal.
   830  //
   831  // Note: This option only applicable to standard JSON Marshaller.
   832  func (c *Client) SetJSONEscapeHTML(b bool) *Client {
   833  	c.jsonEscapeHTML = b
   834  	return c
   835  }
   836  
   837  // SetTrace method enables or disables the Resty client trace for the requests fired from
   838  // the client using `httptrace.ClientTrace` and provides insights.
   839  //
   840  //	client := resty.New().SetTrace(true)
   841  //
   842  //	resp, err := client.R().Get("https://httpbin.org/get")
   843  //	fmt.Println("Error:", err)
   844  //	fmt.Println("Trace Info:", resp.Request.TraceInfo())
   845  //
   846  // Also `Request.SetTrace` available too to get trace info for single request.
   847  func (c *Client) SetTrace(trace bool) *Client {
   848  	c.trace = trace
   849  	return c
   850  }
   851  
   852  // IsProxySet method returns the true is proxy is set from resty client otherwise
   853  // false. By default, proxy is set from environment, refer to `http.ProxyFromEnvironment`.
   854  func (c *Client) IsProxySet() bool {
   855  	return c.proxyURL != nil
   856  }
   857  
   858  // GetClient method returns the current `http.Client` used by the resty client.
   859  func (c *Client) GetClient() *http.Client {
   860  	return c.httpClient
   861  }
   862  
   863  //‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
   864  // Client Unexported methods
   865  //_______________________________________________________________________
   866  
   867  // Executes method executes the given `Request` object and returns response
   868  // error.
   869  func (c *Client) execute(req *Request) (*Response, error) {
   870  	// Apply Request middleware
   871  	var err error
   872  
   873  	// user defined on before request methods
   874  	// to modify the *resty.Request object
   875  	for _, f := range c.udBeforeRequest {
   876  		if err = f(c, req); err != nil {
   877  			return nil, wrapNoRetryErr(err)
   878  		}
   879  	}
   880  
   881  	// resty middlewares
   882  	for _, f := range c.beforeRequest {
   883  		if err = f(c, req); err != nil {
   884  			return nil, wrapNoRetryErr(err)
   885  		}
   886  	}
   887  
   888  	if hostHeader := req.Header.Get("Host"); hostHeader != "" {
   889  		req.RawRequest.Host = hostHeader
   890  	}
   891  
   892  	// call pre-request if defined
   893  	if c.preReqHook != nil {
   894  		if err = c.preReqHook(c, req.RawRequest); err != nil {
   895  			return nil, wrapNoRetryErr(err)
   896  		}
   897  	}
   898  
   899  	if err = requestLogger(c, req); err != nil {
   900  		return nil, wrapNoRetryErr(err)
   901  	}
   902  
   903  	req.RawRequest.Body = newRequestBodyReleaser(req.RawRequest.Body, req.bodyBuf)
   904  
   905  	req.Time = time.Now()
   906  	resp, err := c.httpClient.Do(req.RawRequest)
   907  
   908  	response := &Response{
   909  		Request:     req,
   910  		RawResponse: resp,
   911  	}
   912  
   913  	if err != nil || req.notParseResponse || c.notParseResponse {
   914  		response.setReceivedAt()
   915  		return response, err
   916  	}
   917  
   918  	if !req.isSaveResponse {
   919  		defer closeq(resp.Body)
   920  		body := resp.Body
   921  
   922  		// GitHub #142 & #187
   923  		if strings.EqualFold(resp.Header.Get(hdrContentEncodingKey), "gzip") && resp.ContentLength != 0 {
   924  			if _, ok := body.(*gzip.Reader); !ok {
   925  				body, err = gzip.NewReader(body)
   926  				if err != nil {
   927  					response.setReceivedAt()
   928  					return response, err
   929  				}
   930  				defer closeq(body)
   931  			}
   932  		}
   933  
   934  		if response.body, err = io.ReadAll(body); err != nil {
   935  			response.setReceivedAt()
   936  			return response, err
   937  		}
   938  
   939  		response.size = int64(len(response.body))
   940  	}
   941  
   942  	response.setReceivedAt() // after we read the body
   943  
   944  	// Apply Response middleware
   945  	for _, f := range c.afterResponse {
   946  		if err = f(c, response); err != nil {
   947  			break
   948  		}
   949  	}
   950  
   951  	return response, wrapNoRetryErr(err)
   952  }
   953  
   954  // getting TLS client config if not exists then create one
   955  func (c *Client) tlsConfig() (*tls.Config, error) {
   956  	transport, err := c.transport()
   957  	if err != nil {
   958  		return nil, err
   959  	}
   960  	if transport.TLSClientConfig == nil {
   961  		transport.TLSClientConfig = &tls.Config{}
   962  	}
   963  	return transport.TLSClientConfig, nil
   964  }
   965  
   966  // Transport method returns `*http.Transport` currently in use or error
   967  // in case currently used `transport` is not a `*http.Transport`.
   968  func (c *Client) transport() (*http.Transport, error) {
   969  	if transport, ok := c.httpClient.Transport.(*http.Transport); ok {
   970  		return transport, nil
   971  	}
   972  	return nil, errors.New("current transport is not an *http.Transport instance")
   973  }
   974  
   975  // just an internal helper method
   976  func (c *Client) outputLogTo(w io.Writer) *Client {
   977  	c.log.(*logger).l.SetOutput(w)
   978  	return c
   979  }
   980  
   981  // ResponseError is a wrapper for including the server response with an error.
   982  // Neither the err nor the response should be nil.
   983  type ResponseError struct {
   984  	Response *Response
   985  	Err      error
   986  }
   987  
   988  func (e *ResponseError) Error() string { return e.Err.Error() }
   989  func (e *ResponseError) Unwrap() error { return e.Err }
   990  
   991  // Helper to run onErrorHooks hooks.
   992  // It wraps the error in a ResponseError if the resp is not nil
   993  // so hooks can access it.
   994  func (c *Client) onErrorHooks(req *Request, resp *Response, err error) {
   995  	if err == nil {
   996  		return
   997  	}
   998  
   999  	if resp != nil { // wrap with ResponseError
  1000  		err = &ResponseError{Response: resp, Err: err}
  1001  	}
  1002  	for _, h := range c.errorHooks {
  1003  		h(req, err)
  1004  	}
  1005  }
  1006  
  1007  //‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
  1008  // File struct and its methods
  1009  //_______________________________________________________________________
  1010  
  1011  // File struct represent file information for multipart request
  1012  type File struct {
  1013  	Name      string
  1014  	ParamName string
  1015  	io.Reader
  1016  }
  1017  
  1018  // String returns string value of current file details
  1019  func (f *File) String() string {
  1020  	return fmt.Sprintf("ParamName: %v; FileName: %v", f.ParamName, f.Name)
  1021  }
  1022  
  1023  //‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
  1024  // MultipartField struct
  1025  //_______________________________________________________________________
  1026  
  1027  // MultipartField struct represent custom data part for multipart request
  1028  type MultipartField struct {
  1029  	Param       string
  1030  	FileName    string
  1031  	ContentType string
  1032  	io.Reader
  1033  }
  1034  
  1035  //‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
  1036  // Unexported package methods
  1037  //_______________________________________________________________________
  1038  
  1039  func createClient(hc *http.Client) *Client {
  1040  	if hc.Transport == nil {
  1041  		hc.Transport = createTransport(nil)
  1042  	}
  1043  
  1044  	c := &Client{ // not setting lang default values
  1045  		QueryParam:             url.Values{},
  1046  		FormData:               url.Values{},
  1047  		Header:                 http.Header{},
  1048  		Cookies:                make([]*http.Cookie, 0),
  1049  		RetryWaitTime:          defaultWaitTime,
  1050  		RetryMaxWaitTime:       defaultMaxWaitTime,
  1051  		PathParams:             map[string]string{},
  1052  		JSONMarshal:            json.Marshal,
  1053  		JSONUnmarshal:          json.Unmarshal,
  1054  		XMLMarshal:             xml.Marshal,
  1055  		XMLUnmarshal:           xml.Unmarshal,
  1056  		HeaderAuthorizationKey: http.CanonicalHeaderKey("Authorization"),
  1057  		jsonEscapeHTML:         true,
  1058  		httpClient:             hc,
  1059  		debugBodySizeLimit:     math.MaxInt32,
  1060  	}
  1061  
  1062  	// Logger
  1063  	c.SetLogger(createLogger())
  1064  
  1065  	// default before request middlewares
  1066  	c.beforeRequest = []RequestMiddleware{
  1067  		parseRequestURL,
  1068  		parseRequestHeader,
  1069  		parseRequestBody,
  1070  		createHTTPRequest,
  1071  		addCredentials,
  1072  	}
  1073  
  1074  	// user defined request middlewares
  1075  	c.udBeforeRequest = []RequestMiddleware{}
  1076  
  1077  	// default after response middlewares
  1078  	c.afterResponse = []ResponseMiddleware{
  1079  		responseLogger,
  1080  		parseResponseBody,
  1081  		saveResponseIntoFile,
  1082  	}
  1083  
  1084  	return c
  1085  }