...

Source file src/github.com/go-resty/resty/v2/client.go

Documentation: github.com/go-resty/resty/v2

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

View as plain text