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