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