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