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