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