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