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