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