1 // Copyright (c) 2015-2024 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 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 hdrAuthorizationKey = http.CanonicalHeaderKey("Authorization")
59 hdrWwwAuthenticateKey = http.CanonicalHeaderKey("WWW-Authenticate")
60 61 plainTextType = "text/plain; charset=utf-8"
62 jsonContentType = "application/json"
63 formContentType = "application/x-www-form-urlencoded"
64 65 jsonCheck = regexp.MustCompile(`(?i:(application|text)/(.*json.*)(;|$))`)
66 xmlCheck = regexp.MustCompile(`(?i:(application|text)/(.*xml.*)(;|$))`)
67 68 hdrUserAgentValue = "go-resty/" + Version + " (https://github.com/go-resty/resty)"
69 bufPool = &sync.Pool{New: func() interface{} { return &bytes.Buffer{} }}
70 )
71 72 type (
73 // RequestMiddleware type is for request middleware, called before a request is sent
74 RequestMiddleware func(*Client, *Request) error
75 76 // ResponseMiddleware type is for response middleware, called after a response has been received
77 ResponseMiddleware func(*Client, *Response) error
78 79 // PreRequestHook type is for the request hook, called right before the request is sent
80 PreRequestHook func(*Client, *http.Request) error
81 82 // RequestLogCallback type is for request logs, called before the request is logged
83 RequestLogCallback func(*RequestLog) error
84 85 // ResponseLogCallback type is for response logs, called before the response is logged
86 ResponseLogCallback func(*ResponseLog) error
87 88 // ErrorHook type is for reacting to request errors, called after all retries were attempted
89 ErrorHook func(*Request, error)
90 91 // SuccessHook type is for reacting to request success
92 SuccessHook func(*Client, *Response)
93 )
94 95 // Client struct is used to create a Resty client with client-level settings,
96 // these settings apply to all the requests raised from the client.
97 //
98 // Resty also provides an option to override most of the client settings
99 // at [Request] level.
100 type Client struct {
101 BaseURL string
102 HostURL string // Deprecated: use BaseURL instead. To be removed in v3.0.0 release.
103 QueryParam url.Values
104 FormData url.Values
105 PathParams map[string]string
106 RawPathParams map[string]string
107 Header http.Header
108 UserInfo *User
109 Token string
110 AuthScheme string
111 Cookies []*http.Cookie
112 Error reflect.Type
113 Debug bool
114 DisableWarn bool
115 AllowGetMethodPayload bool
116 RetryCount int
117 RetryWaitTime time.Duration
118 RetryMaxWaitTime time.Duration
119 RetryConditions []RetryConditionFunc
120 RetryHooks []OnRetryFunc
121 RetryAfter RetryAfterFunc
122 RetryResetReaders bool
123 JSONMarshal func(v interface{}) ([]byte, error)
124 JSONUnmarshal func(data []byte, v interface{}) error
125 XMLMarshal func(v interface{}) ([]byte, error)
126 XMLUnmarshal func(data []byte, v interface{}) error
127 128 // HeaderAuthorizationKey is used to set/access Request Authorization header
129 // value when `SetAuthToken` option is used.
130 HeaderAuthorizationKey string
131 ResponseBodyLimit int
132 133 jsonEscapeHTML bool
134 setContentLength bool
135 closeConnection bool
136 notParseResponse bool
137 trace bool
138 debugBodySizeLimit int64
139 outputDirectory string
140 scheme string
141 log Logger
142 httpClient *http.Client
143 proxyURL *url.URL
144 beforeRequest []RequestMiddleware
145 udBeforeRequest []RequestMiddleware
146 udBeforeRequestLock *sync.RWMutex
147 preReqHook PreRequestHook
148 successHooks []SuccessHook
149 afterResponse []ResponseMiddleware
150 afterResponseLock *sync.RWMutex
151 requestLog RequestLogCallback
152 responseLog ResponseLogCallback
153 errorHooks []ErrorHook
154 invalidHooks []ErrorHook
155 panicHooks []ErrorHook
156 rateLimiter RateLimiter
157 generateCurlOnDebug bool
158 unescapeQueryParams bool
159 }
160 161 // User type is to hold an username and password information
162 type User struct {
163 Username, Password string
164 }
165 166 //‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
167 // Client methods
168 //___________________________________
169 170 // SetHostURL method sets the Host URL in the client instance. It will be used with a request
171 // raised from this client with a relative URL
172 //
173 // // Setting HTTP address
174 // client.SetHostURL("http://myjeeva.com")
175 //
176 // // Setting HTTPS address
177 // client.SetHostURL("https://myjeeva.com")
178 //
179 // Deprecated: use [Client.SetBaseURL] instead. To be removed in the v3.0.0 release.
180 func (c *Client) SetHostURL(url string) *Client {
181 c.SetBaseURL(url)
182 return c
183 }
184 185 // SetBaseURL method sets the Base URL in the client instance. It will be used with a request
186 // raised from this client with a relative URL
187 //
188 // // Setting HTTP address
189 // client.SetBaseURL("http://myjeeva.com")
190 //
191 // // Setting HTTPS address
192 // client.SetBaseURL("https://myjeeva.com")
193 func (c *Client) SetBaseURL(url string) *Client {
194 c.BaseURL = strings.TrimRight(url, "/")
195 c.HostURL = c.BaseURL
196 return c
197 }
198 199 // SetHeader method sets a single header field and its value in the client instance.
200 // These headers will be applied to all requests from this client instance.
201 // Also, it can be overridden by request-level header options.
202 //
203 // See [Request.SetHeader] or [Request.SetHeaders].
204 //
205 // For Example: To set `Content-Type` and `Accept` as `application/json`
206 //
207 // client.
208 // SetHeader("Content-Type", "application/json").
209 // SetHeader("Accept", "application/json")
210 func (c *Client) SetHeader(header, value string) *Client {
211 c.Header.Set(header, value)
212 return c
213 }
214 215 // SetHeaders method sets multiple header fields and their values at one go in the client instance.
216 // These headers will be applied to all requests from this client instance. Also, it can be
217 // overridden at request level headers options.
218 //
219 // See [Request.SetHeaders] or [Request.SetHeader].
220 //
221 // For Example: To set `Content-Type` and `Accept` as `application/json`
222 //
223 // client.SetHeaders(map[string]string{
224 // "Content-Type": "application/json",
225 // "Accept": "application/json",
226 // })
227 func (c *Client) SetHeaders(headers map[string]string) *Client {
228 for h, v := range headers {
229 c.Header.Set(h, v)
230 }
231 return c
232 }
233 234 // SetHeaderVerbatim method sets a single header field and its value verbatim in the current request.
235 //
236 // For Example: To set `all_lowercase` and `UPPERCASE` as `available`.
237 //
238 // client.
239 // SetHeaderVerbatim("all_lowercase", "available").
240 // SetHeaderVerbatim("UPPERCASE", "available")
241 func (c *Client) SetHeaderVerbatim(header, value string) *Client {
242 c.Header[header] = []string{value}
243 return c
244 }
245 246 // SetCookieJar method sets custom [http.CookieJar] in the resty client. It's a way to override the default.
247 //
248 // For Example, sometimes we don't want to save cookies in API mode so that we can remove the default
249 // CookieJar in resty client.
250 //
251 // client.SetCookieJar(nil)
252 func (c *Client) SetCookieJar(jar http.CookieJar) *Client {
253 c.httpClient.Jar = jar
254 return c
255 }
256 257 // SetCookie method appends a single cookie to the client instance.
258 // These cookies will be added to all the requests from this client instance.
259 //
260 // client.SetCookie(&http.Cookie{
261 // Name:"go-resty",
262 // Value:"This is cookie value",
263 // })
264 func (c *Client) SetCookie(hc *http.Cookie) *Client {
265 c.Cookies = append(c.Cookies, hc)
266 return c
267 }
268 269 // SetCookies method sets an array of cookies in the client instance.
270 // These cookies will be added to all the requests from this client instance.
271 //
272 // cookies := []*http.Cookie{
273 // &http.Cookie{
274 // Name:"go-resty-1",
275 // Value:"This is cookie 1 value",
276 // },
277 // &http.Cookie{
278 // Name:"go-resty-2",
279 // Value:"This is cookie 2 value",
280 // },
281 // }
282 //
283 // // Setting a cookies into resty
284 // client.SetCookies(cookies)
285 func (c *Client) SetCookies(cs []*http.Cookie) *Client {
286 c.Cookies = append(c.Cookies, cs...)
287 return c
288 }
289 290 // SetQueryParam method sets a single parameter and its value in the client instance.
291 // It will be formed as a query string for the request.
292 //
293 // For Example: `search=kitchen%20papers&size=large`
294 //
295 // In the URL after the `?` mark. These query params will be added to all the requests raised from
296 // this client instance. Also, it can be overridden at the request level.
297 //
298 // See [Request.SetQueryParam] or [Request.SetQueryParams].
299 //
300 // client.
301 // SetQueryParam("search", "kitchen papers").
302 // SetQueryParam("size", "large")
303 func (c *Client) SetQueryParam(param, value string) *Client {
304 c.QueryParam.Set(param, value)
305 return c
306 }
307 308 // SetQueryParams method sets multiple parameters and their values at one go in the client instance.
309 // It will be formed as a query string for the request.
310 //
311 // For Example: `search=kitchen%20papers&size=large`
312 //
313 // In the URL after the `?` mark. These query params will be added to all the requests raised from this
314 // client instance. Also, it can be overridden at the request level.
315 //
316 // See [Request.SetQueryParams] or [Request.SetQueryParam].
317 //
318 // client.SetQueryParams(map[string]string{
319 // "search": "kitchen papers",
320 // "size": "large",
321 // })
322 func (c *Client) SetQueryParams(params map[string]string) *Client {
323 for p, v := range params {
324 c.SetQueryParam(p, v)
325 }
326 return c
327 }
328 329 // SetUnescapeQueryParams method sets the unescape query parameters choice for request URL.
330 // To prevent broken URL, resty replaces space (" ") with "+" in the query parameters.
331 //
332 // See [Request.SetUnescapeQueryParams]
333 //
334 // NOTE: Request failure is possible due to non-standard usage of Unescaped Query Parameters.
335 func (c *Client) SetUnescapeQueryParams(unescape bool) *Client {
336 c.unescapeQueryParams = unescape
337 return c
338 }
339 340 // SetFormData method sets Form parameters and their values in the client instance.
341 // It applies only to HTTP methods `POST` and `PUT`, and the request content type would be set as
342 // `application/x-www-form-urlencoded`. These form data will be added to all the requests raised from
343 // this client instance. Also, it can be overridden at the request level.
344 //
345 // See [Request.SetFormData].
346 //
347 // client.SetFormData(map[string]string{
348 // "access_token": "BC594900-518B-4F7E-AC75-BD37F019E08F",
349 // "user_id": "3455454545",
350 // })
351 func (c *Client) SetFormData(data map[string]string) *Client {
352 for k, v := range data {
353 c.FormData.Set(k, v)
354 }
355 return c
356 }
357 358 // SetBasicAuth method sets the basic authentication header in the HTTP request. For Example:
359 //
360 // Authorization: Basic <base64-encoded-value>
361 //
362 // For Example: To set the header for username "go-resty" and password "welcome"
363 //
364 // client.SetBasicAuth("go-resty", "welcome")
365 //
366 // This basic auth information is added to all requests from this client instance.
367 // It can also be overridden at the request level.
368 //
369 // See [Request.SetBasicAuth].
370 func (c *Client) SetBasicAuth(username, password string) *Client {
371 c.UserInfo = &User{Username: username, Password: password}
372 return c
373 }
374 375 // SetAuthToken method sets the auth token of the `Authorization` header for all HTTP requests.
376 // The default auth scheme is `Bearer`; it can be customized with the method [Client.SetAuthScheme]. For Example:
377 //
378 // Authorization: <auth-scheme> <auth-token-value>
379 //
380 // For Example: To set auth token BC594900518B4F7EAC75BD37F019E08FBC594900518B4F7EAC75BD37F019E08F
381 //
382 // client.SetAuthToken("BC594900518B4F7EAC75BD37F019E08FBC594900518B4F7EAC75BD37F019E08F")
383 //
384 // This auth token gets added to all the requests raised from this client instance.
385 // Also, it can be overridden at the request level.
386 //
387 // See [Request.SetAuthToken].
388 func (c *Client) SetAuthToken(token string) *Client {
389 c.Token = token
390 return c
391 }
392 393 // SetAuthScheme method sets the auth scheme type in the HTTP request. For Example:
394 //
395 // Authorization: <auth-scheme-value> <auth-token-value>
396 //
397 // For Example: To set the scheme to use OAuth
398 //
399 // client.SetAuthScheme("OAuth")
400 //
401 // This auth scheme gets added to all the requests raised from this client instance.
402 // Also, it can be overridden at the request level.
403 //
404 // Information about auth schemes can be found in [RFC 7235], IANA [HTTP Auth schemes].
405 //
406 // See [Request.SetAuthToken].
407 //
408 // [RFC 7235]: https://tools.ietf.org/html/rfc7235
409 // [HTTP Auth schemes]: https://www.iana.org/assignments/http-authschemes/http-authschemes.xhtml#authschemes
410 func (c *Client) SetAuthScheme(scheme string) *Client {
411 c.AuthScheme = scheme
412 return c
413 }
414 415 // SetDigestAuth method sets the Digest Access auth scheme for the client. If a server responds with 401 and sends
416 // a Digest challenge in the WWW-Authenticate Header, requests will be resent with the appropriate Authorization Header.
417 //
418 // For Example: To set the Digest scheme with user "Mufasa" and password "Circle Of Life"
419 //
420 // client.SetDigestAuth("Mufasa", "Circle Of Life")
421 //
422 // Information about Digest Access Authentication can be found in [RFC 7616].
423 //
424 // See [Request.SetDigestAuth].
425 //
426 // [RFC 7616]: https://datatracker.ietf.org/doc/html/rfc7616
427 func (c *Client) SetDigestAuth(username, password string) *Client {
428 oldTransport := c.httpClient.Transport
429 c.OnBeforeRequest(func(c *Client, _ *Request) error {
430 c.httpClient.Transport = &digestTransport{
431 digestCredentials: digestCredentials{username, password},
432 transport: oldTransport,
433 }
434 return nil
435 })
436 c.OnAfterResponse(func(c *Client, _ *Response) error {
437 c.httpClient.Transport = oldTransport
438 return nil
439 })
440 return c
441 }
442 443 // R method creates a new request instance; it's used for Get, Post, Put, Delete, Patch, Head, Options, etc.
444 func (c *Client) R() *Request {
445 r := &Request{
446 QueryParam: url.Values{},
447 FormData: url.Values{},
448 Header: http.Header{},
449 Cookies: make([]*http.Cookie, 0),
450 PathParams: map[string]string{},
451 RawPathParams: map[string]string{},
452 Debug: c.Debug,
453 AuthScheme: c.AuthScheme,
454 455 client: c,
456 multipartFiles: []*File{},
457 multipartFields: []*MultipartField{},
458 jsonEscapeHTML: c.jsonEscapeHTML,
459 log: c.log,
460 responseBodyLimit: c.ResponseBodyLimit,
461 generateCurlOnDebug: c.generateCurlOnDebug,
462 unescapeQueryParams: c.unescapeQueryParams,
463 }
464 return r
465 }
466 467 // NewRequest method is an alias for method `R()`.
468 func (c *Client) NewRequest() *Request {
469 return c.R()
470 }
471 472 // OnBeforeRequest method appends a request middleware to the before request chain.
473 // The user-defined middlewares are applied before the default Resty request middlewares.
474 // After all middlewares have been applied, the request is sent from Resty to the host server.
475 //
476 // client.OnBeforeRequest(func(c *resty.Client, r *resty.Request) error {
477 // // Now you have access to the Client and Request instance
478 // // manipulate it as per your need
479 //
480 // return nil // if its successful otherwise return error
481 // })
482 func (c *Client) OnBeforeRequest(m RequestMiddleware) *Client {
483 c.udBeforeRequestLock.Lock()
484 defer c.udBeforeRequestLock.Unlock()
485 486 c.udBeforeRequest = append(c.udBeforeRequest, m)
487 488 return c
489 }
490 491 // OnAfterResponse method appends response middleware to the after-response chain.
492 // Once we receive a response from the host server, the default Resty response middleware
493 // gets applied, and then the user-assigned response middleware is applied.
494 //
495 // client.OnAfterResponse(func(c *resty.Client, r *resty.Response) error {
496 // // Now you have access to the Client and Response instance
497 // // manipulate it as per your need
498 //
499 // return nil // if its successful otherwise return error
500 // })
501 func (c *Client) OnAfterResponse(m ResponseMiddleware) *Client {
502 c.afterResponseLock.Lock()
503 defer c.afterResponseLock.Unlock()
504 505 c.afterResponse = append(c.afterResponse, m)
506 507 return c
508 }
509 510 // OnError method adds a callback that will be run whenever a request execution fails.
511 // This is called after all retries have been attempted (if any).
512 // If there was a response from the server, the error will be wrapped in [ResponseError]
513 // which has the last response received from the server.
514 //
515 // client.OnError(func(req *resty.Request, err error) {
516 // if v, ok := err.(*resty.ResponseError); ok {
517 // // Do something with v.Response
518 // }
519 // // Log the error, increment a metric, etc...
520 // })
521 //
522 // Out of the [Client.OnSuccess], [Client.OnError], [Client.OnInvalid], [Client.OnPanic]
523 // callbacks, exactly one set will be invoked for each call to [Request.Execute] that completes.
524 func (c *Client) OnError(h ErrorHook) *Client {
525 c.errorHooks = append(c.errorHooks, h)
526 return c
527 }
528 529 // OnSuccess method adds a callback that will be run whenever a request execution
530 // succeeds. This is called after all retries have been attempted (if any).
531 //
532 // Out of the [Client.OnSuccess], [Client.OnError], [Client.OnInvalid], [Client.OnPanic]
533 // callbacks, exactly one set will be invoked for each call to [Request.Execute] that completes.
534 func (c *Client) OnSuccess(h SuccessHook) *Client {
535 c.successHooks = append(c.successHooks, h)
536 return c
537 }
538 539 // OnInvalid method adds a callback that will be run whenever a request execution
540 // fails before it starts because the request is invalid.
541 //
542 // Out of the [Client.OnSuccess], [Client.OnError], [Client.OnInvalid], [Client.OnPanic]
543 // callbacks, exactly one set will be invoked for each call to [Request.Execute] that completes.
544 func (c *Client) OnInvalid(h ErrorHook) *Client {
545 c.invalidHooks = append(c.invalidHooks, h)
546 return c
547 }
548 549 // OnPanic method adds a callback that will be run whenever a request execution
550 // panics.
551 //
552 // Out of the [Client.OnSuccess], [Client.OnError], [Client.OnInvalid], [Client.OnPanic]
553 // callbacks, exactly one set will be invoked for each call to [Request.Execute] that completes.
554 //
555 // If an [Client.OnSuccess], [Client.OnError], or [Client.OnInvalid] callback panics,
556 // then exactly one rule can be violated.
557 func (c *Client) OnPanic(h ErrorHook) *Client {
558 c.panicHooks = append(c.panicHooks, h)
559 return c
560 }
561 562 // SetPreRequestHook method sets the given pre-request function into a resty client.
563 // It is called right before the request is fired.
564 //
565 // NOTE: Only one pre-request hook can be registered. Use [Client.OnBeforeRequest] for multiple.
566 func (c *Client) SetPreRequestHook(h PreRequestHook) *Client {
567 if c.preReqHook != nil {
568 c.log.Warnf("Overwriting an existing pre-request hook: %s", functionName(h))
569 }
570 c.preReqHook = h
571 return c
572 }
573 574 // SetDebug method enables the debug mode on the Resty client. The client logs details
575 // of every request and response.
576 //
577 // client.SetDebug(true)
578 //
579 // Also, it can be enabled at the request level for a particular request; see [Request.SetDebug].
580 // - For [Request], it logs information such as HTTP verb, Relative URL path,
581 // Host, Headers, and Body if it has one.
582 // - For [Response], it logs information such as Status, Response Time, Headers,
583 // and Body if it has one.
584 func (c *Client) SetDebug(d bool) *Client {
585 c.Debug = d
586 return c
587 }
588 589 // SetDebugBodyLimit sets the maximum size in bytes for which the response and
590 // request body will be logged in debug mode.
591 //
592 // client.SetDebugBodyLimit(1000000)
593 func (c *Client) SetDebugBodyLimit(sl int64) *Client {
594 c.debugBodySizeLimit = sl
595 return c
596 }
597 598 // OnRequestLog method sets the request log callback to Resty. Registered callback gets
599 // called before the resty logs the information.
600 func (c *Client) OnRequestLog(rl RequestLogCallback) *Client {
601 if c.requestLog != nil {
602 c.log.Warnf("Overwriting an existing on-request-log callback from=%s to=%s",
603 functionName(c.requestLog), functionName(rl))
604 }
605 c.requestLog = rl
606 return c
607 }
608 609 // OnResponseLog method sets the response log callback to Resty. Registered callback gets
610 // called before the resty logs the information.
611 func (c *Client) OnResponseLog(rl ResponseLogCallback) *Client {
612 if c.responseLog != nil {
613 c.log.Warnf("Overwriting an existing on-response-log callback from=%s to=%s",
614 functionName(c.responseLog), functionName(rl))
615 }
616 c.responseLog = rl
617 return c
618 }
619 620 // SetDisableWarn method disables the warning log message on the Resty client.
621 //
622 // For example, Resty warns users when BasicAuth is used in non-TLS mode.
623 //
624 // client.SetDisableWarn(true)
625 func (c *Client) SetDisableWarn(d bool) *Client {
626 c.DisableWarn = d
627 return c
628 }
629 630 // SetAllowGetMethodPayload method allows the GET method with payload on the Resty client.
631 //
632 // For example, Resty allows the user to send a request with a payload using the HTTP GET method.
633 //
634 // client.SetAllowGetMethodPayload(true)
635 func (c *Client) SetAllowGetMethodPayload(a bool) *Client {
636 c.AllowGetMethodPayload = a
637 return c
638 }
639 640 // SetLogger method sets given writer for logging Resty request and response details.
641 //
642 // Compliant to interface [resty.Logger]
643 func (c *Client) SetLogger(l Logger) *Client {
644 c.log = l
645 return c
646 }
647 648 // SetContentLength method enables the HTTP header `Content-Length` value for every request.
649 // By default, Resty won't set `Content-Length`.
650 //
651 // client.SetContentLength(true)
652 //
653 // Also, you have the option to enable a particular request. See [Request.SetContentLength]
654 func (c *Client) SetContentLength(l bool) *Client {
655 c.setContentLength = l
656 return c
657 }
658 659 // SetTimeout method sets the timeout for a request raised by the client.
660 //
661 // client.SetTimeout(time.Duration(1 * time.Minute))
662 func (c *Client) SetTimeout(timeout time.Duration) *Client {
663 c.httpClient.Timeout = timeout
664 return c
665 }
666 667 // SetError method registers the global or client common `Error` object into Resty.
668 // It is used for automatic unmarshalling if the response status code is greater than 399 and
669 // content type is JSON or XML. It can be a pointer or a non-pointer.
670 //
671 // client.SetError(&Error{})
672 // // OR
673 // client.SetError(Error{})
674 func (c *Client) SetError(err interface{}) *Client {
675 c.Error = typeOf(err)
676 return c
677 }
678 679 // SetRedirectPolicy method sets the redirect policy for the client. Resty provides ready-to-use
680 // redirect policies. Wanna create one for yourself, refer to `redirect.go`.
681 //
682 // client.SetRedirectPolicy(FlexibleRedirectPolicy(20))
683 //
684 // // Need multiple redirect policies together
685 // client.SetRedirectPolicy(FlexibleRedirectPolicy(20), DomainCheckRedirectPolicy("host1.com", "host2.net"))
686 func (c *Client) SetRedirectPolicy(policies ...interface{}) *Client {
687 for _, p := range policies {
688 if _, ok := p.(RedirectPolicy); !ok {
689 c.log.Errorf("%v does not implement resty.RedirectPolicy (missing Apply method)",
690 functionName(p))
691 }
692 }
693 694 c.httpClient.CheckRedirect = func(req *http.Request, via []*http.Request) error {
695 for _, p := range policies {
696 if err := p.(RedirectPolicy).Apply(req, via); err != nil {
697 return err
698 }
699 }
700 return nil // looks good, go ahead
701 }
702 703 return c
704 }
705 706 // SetRetryCount method enables retry on Resty client and allows you
707 // to set no. of retry count. Resty uses a Backoff mechanism.
708 func (c *Client) SetRetryCount(count int) *Client {
709 c.RetryCount = count
710 return c
711 }
712 713 // SetRetryWaitTime method sets the default wait time for sleep before retrying
714 // request.
715 //
716 // Default is 100 milliseconds.
717 func (c *Client) SetRetryWaitTime(waitTime time.Duration) *Client {
718 c.RetryWaitTime = waitTime
719 return c
720 }
721 722 // SetRetryMaxWaitTime method sets the max wait time for sleep before retrying
723 // request.
724 //
725 // Default is 2 seconds.
726 func (c *Client) SetRetryMaxWaitTime(maxWaitTime time.Duration) *Client {
727 c.RetryMaxWaitTime = maxWaitTime
728 return c
729 }
730 731 // SetRetryAfter sets a callback to calculate the wait time between retries.
732 // Default (nil) implies exponential backoff with jitter
733 func (c *Client) SetRetryAfter(callback RetryAfterFunc) *Client {
734 c.RetryAfter = callback
735 return c
736 }
737 738 // SetJSONMarshaler method sets the JSON marshaler function to marshal the request body.
739 // By default, Resty uses [encoding/json] package to marshal the request body.
740 func (c *Client) SetJSONMarshaler(marshaler func(v interface{}) ([]byte, error)) *Client {
741 c.JSONMarshal = marshaler
742 return c
743 }
744 745 // SetJSONUnmarshaler method sets the JSON unmarshaler function to unmarshal the response body.
746 // By default, Resty uses [encoding/json] package to unmarshal the response body.
747 func (c *Client) SetJSONUnmarshaler(unmarshaler func(data []byte, v interface{}) error) *Client {
748 c.JSONUnmarshal = unmarshaler
749 return c
750 }
751 752 // SetXMLMarshaler method sets the XML marshaler function to marshal the request body.
753 // By default, Resty uses [encoding/xml] package to marshal the request body.
754 func (c *Client) SetXMLMarshaler(marshaler func(v interface{}) ([]byte, error)) *Client {
755 c.XMLMarshal = marshaler
756 return c
757 }
758 759 // SetXMLUnmarshaler method sets the XML unmarshaler function to unmarshal the response body.
760 // By default, Resty uses [encoding/xml] package to unmarshal the response body.
761 func (c *Client) SetXMLUnmarshaler(unmarshaler func(data []byte, v interface{}) error) *Client {
762 c.XMLUnmarshal = unmarshaler
763 return c
764 }
765 766 // AddRetryCondition method adds a retry condition function to an array of functions
767 // that are checked to determine if the request is retried. The request will
768 // retry if any functions return true and the error is nil.
769 //
770 // NOTE: These retry conditions are applied on all requests made using this Client.
771 // For [Request] specific retry conditions, check [Request.AddRetryCondition]
772 func (c *Client) AddRetryCondition(condition RetryConditionFunc) *Client {
773 c.RetryConditions = append(c.RetryConditions, condition)
774 return c
775 }
776 777 // AddRetryAfterErrorCondition adds the basic condition of retrying after encountering
778 // an error from the HTTP response
779 func (c *Client) AddRetryAfterErrorCondition() *Client {
780 c.AddRetryCondition(func(response *Response, err error) bool {
781 return response.IsError()
782 })
783 return c
784 }
785 786 // AddRetryHook adds a side-effecting retry hook to an array of hooks
787 // that will be executed on each retry.
788 func (c *Client) AddRetryHook(hook OnRetryFunc) *Client {
789 c.RetryHooks = append(c.RetryHooks, hook)
790 return c
791 }
792 793 // SetRetryResetReaders method enables the Resty client to seek the start of all
794 // file readers are given as multipart files if the object implements [io.ReadSeeker].
795 func (c *Client) SetRetryResetReaders(b bool) *Client {
796 c.RetryResetReaders = b
797 return c
798 }
799 800 // SetTLSClientConfig method sets TLSClientConfig for underlying client Transport.
801 //
802 // For Example:
803 //
804 // // One can set a custom root certificate. Refer: http://golang.org/pkg/crypto/tls/#example_Dial
805 // client.SetTLSClientConfig(&tls.Config{ RootCAs: roots })
806 //
807 // // or One can disable security check (https)
808 // client.SetTLSClientConfig(&tls.Config{ InsecureSkipVerify: true })
809 //
810 // NOTE: This method overwrites existing [http.Transport.TLSClientConfig]
811 func (c *Client) SetTLSClientConfig(config *tls.Config) *Client {
812 transport, err := c.Transport()
813 if err != nil {
814 c.log.Errorf("%v", err)
815 return c
816 }
817 transport.TLSClientConfig = config
818 return c
819 }
820 821 // SetProxy method sets the Proxy URL and Port for the Resty client.
822 //
823 // client.SetProxy("http://proxyserver:8888")
824 //
825 // OR you could also set Proxy via environment variable, refer to [http.ProxyFromEnvironment]
826 func (c *Client) SetProxy(proxyURL string) *Client {
827 transport, err := c.Transport()
828 if err != nil {
829 c.log.Errorf("%v", err)
830 return c
831 }
832 833 pURL, err := url.Parse(proxyURL)
834 if err != nil {
835 c.log.Errorf("%v", err)
836 return c
837 }
838 839 c.proxyURL = pURL
840 transport.Proxy = http.ProxyURL(c.proxyURL)
841 return c
842 }
843 844 // RemoveProxy method removes the proxy configuration from the Resty client
845 //
846 // client.RemoveProxy()
847 func (c *Client) RemoveProxy() *Client {
848 transport, err := c.Transport()
849 if err != nil {
850 c.log.Errorf("%v", err)
851 return c
852 }
853 c.proxyURL = nil
854 transport.Proxy = nil
855 return c
856 }
857 858 // SetCertificates method helps to conveniently set client certificates into Resty.
859 func (c *Client) SetCertificates(certs ...tls.Certificate) *Client {
860 config, err := c.tlsConfig()
861 if err != nil {
862 c.log.Errorf("%v", err)
863 return c
864 }
865 config.Certificates = append(config.Certificates, certs...)
866 return c
867 }
868 869 // SetRootCertificate method helps to add one or more root certificates into the Resty client
870 //
871 // client.SetRootCertificate("/path/to/root/pemFile.pem")
872 func (c *Client) SetRootCertificate(pemFilePath string) *Client {
873 rootPemData, err := os.ReadFile(pemFilePath)
874 if err != nil {
875 c.log.Errorf("%v", err)
876 return c
877 }
878 c.handleCAs("root", rootPemData)
879 return c
880 }
881 882 // SetRootCertificateFromString method helps to add one or more root certificates
883 // into the Resty client
884 //
885 // client.SetRootCertificateFromString("pem certs content")
886 func (c *Client) SetRootCertificateFromString(pemCerts string) *Client {
887 c.handleCAs("root", []byte(pemCerts))
888 return c
889 }
890 891 // SetClientRootCertificate method helps to add one or more client's root
892 // certificates into the Resty client
893 //
894 // client.SetClientRootCertificate("/path/to/root/pemFile.pem")
895 func (c *Client) SetClientRootCertificate(pemFilePath string) *Client {
896 rootPemData, err := os.ReadFile(pemFilePath)
897 if err != nil {
898 c.log.Errorf("%v", err)
899 return c
900 }
901 c.handleCAs("client", rootPemData)
902 return c
903 }
904 905 // SetClientRootCertificateFromString method helps to add one or more clients
906 // root certificates into the Resty client
907 //
908 // client.SetClientRootCertificateFromString("pem certs content")
909 func (c *Client) SetClientRootCertificateFromString(pemCerts string) *Client {
910 c.handleCAs("client", []byte(pemCerts))
911 return c
912 }
913 914 func (c *Client) handleCAs(scope string, permCerts []byte) {
915 config, err := c.tlsConfig()
916 if err != nil {
917 c.log.Errorf("%v", err)
918 return
919 }
920 921 switch scope {
922 case "root":
923 if config.RootCAs == nil {
924 config.RootCAs = x509.NewCertPool()
925 }
926 config.RootCAs.AppendCertsFromPEM(permCerts)
927 case "client":
928 if config.ClientCAs == nil {
929 config.ClientCAs = x509.NewCertPool()
930 }
931 config.ClientCAs.AppendCertsFromPEM(permCerts)
932 }
933 }
934 935 // SetOutputDirectory method sets the output directory for saving HTTP responses in a file.
936 // Resty creates one if the output directory does not exist. This setting is optional,
937 // if you plan to use the absolute path in [Request.SetOutput] and can used together.
938 //
939 // client.SetOutputDirectory("/save/http/response/here")
940 func (c *Client) SetOutputDirectory(dirPath string) *Client {
941 c.outputDirectory = dirPath
942 return c
943 }
944 945 // SetRateLimiter sets an optional [RateLimiter]. If set, the rate limiter will control
946 // all requests were made by this client.
947 func (c *Client) SetRateLimiter(rl RateLimiter) *Client {
948 c.rateLimiter = rl
949 return c
950 }
951 952 // SetTransport method sets custom [http.Transport] or any [http.RoundTripper]
953 // compatible interface implementation in the Resty client.
954 //
955 // transport := &http.Transport{
956 // // something like Proxying to httptest.Server, etc...
957 // Proxy: func(req *http.Request) (*url.URL, error) {
958 // return url.Parse(server.URL)
959 // },
960 // }
961 // client.SetTransport(transport)
962 //
963 // NOTE:
964 // - If transport is not the type of `*http.Transport`, then you may not be able to
965 // take advantage of some of the Resty client settings.
966 // - It overwrites the Resty client transport instance and its configurations.
967 func (c *Client) SetTransport(transport http.RoundTripper) *Client {
968 if transport != nil {
969 c.httpClient.Transport = transport
970 }
971 return c
972 }
973 974 // SetScheme method sets a custom scheme for the Resty client. It's a way to override the default.
975 //
976 // client.SetScheme("http")
977 func (c *Client) SetScheme(scheme string) *Client {
978 if !IsStringEmpty(scheme) {
979 c.scheme = strings.TrimSpace(scheme)
980 }
981 return c
982 }
983 984 // SetCloseConnection method sets variable `Close` in HTTP request struct with the given
985 // value. More info: https://golang.org/src/net/http/request.go
986 func (c *Client) SetCloseConnection(close bool) *Client {
987 c.closeConnection = close
988 return c
989 }
990 991 // SetDoNotParseResponse method instructs Resty not to parse the response body automatically.
992 // Resty exposes the raw response body as [io.ReadCloser]. If you use it, do not
993 // forget to close the body, otherwise, you might get into connection leaks, and connection
994 // reuse may not happen.
995 //
996 // NOTE: [Response] middlewares are not executed using this option. You have
997 // taken over the control of response parsing from Resty.
998 func (c *Client) SetDoNotParseResponse(notParse bool) *Client {
999 c.notParseResponse = notParse
1000 return c
1001 }
1002 1003 // SetPathParam method sets a single URL path key-value pair in the
1004 // Resty client instance.
1005 //
1006 // client.SetPathParam("userId", "sample@sample.com")
1007 //
1008 // Result:
1009 // URL - /v1/users/{userId}/details
1010 // Composed URL - /v1/users/sample@sample.com/details
1011 //
1012 // It replaces the value of the key while composing the request URL.
1013 // The value will be escaped using [url.PathEscape] function.
1014 //
1015 // It can be overridden at the request level,
1016 // see [Request.SetPathParam] or [Request.SetPathParams]
1017 func (c *Client) SetPathParam(param, value string) *Client {
1018 c.PathParams[param] = value
1019 return c
1020 }
1021 1022 // SetPathParams method sets multiple URL path key-value pairs at one go in the
1023 // Resty client instance.
1024 //
1025 // client.SetPathParams(map[string]string{
1026 // "userId": "sample@sample.com",
1027 // "subAccountId": "100002",
1028 // "path": "groups/developers",
1029 // })
1030 //
1031 // Result:
1032 // URL - /v1/users/{userId}/{subAccountId}/{path}/details
1033 // Composed URL - /v1/users/sample@sample.com/100002/groups%2Fdevelopers/details
1034 //
1035 // It replaces the value of the key while composing the request URL.
1036 // The values will be escaped using [url.PathEscape] function.
1037 //
1038 // It can be overridden at the request level,
1039 // see [Request.SetPathParam] or [Request.SetPathParams]
1040 func (c *Client) SetPathParams(params map[string]string) *Client {
1041 for p, v := range params {
1042 c.SetPathParam(p, v)
1043 }
1044 return c
1045 }
1046 1047 // SetRawPathParam method sets a single URL path key-value pair in the
1048 // Resty client instance.
1049 //
1050 // client.SetPathParam("userId", "sample@sample.com")
1051 //
1052 // Result:
1053 // URL - /v1/users/{userId}/details
1054 // Composed URL - /v1/users/sample@sample.com/details
1055 //
1056 // client.SetPathParam("path", "groups/developers")
1057 //
1058 // Result:
1059 // URL - /v1/users/{userId}/details
1060 // Composed URL - /v1/users/groups%2Fdevelopers/details
1061 //
1062 // It replaces the value of the key while composing the request URL.
1063 // The value will be used as it is and will not be escaped.
1064 //
1065 // It can be overridden at the request level,
1066 // see [Request.SetRawPathParam] or [Request.SetRawPathParams]
1067 func (c *Client) SetRawPathParam(param, value string) *Client {
1068 c.RawPathParams[param] = value
1069 return c
1070 }
1071 1072 // SetRawPathParams method sets multiple URL path key-value pairs at one go in the
1073 // Resty client instance.
1074 //
1075 // client.SetPathParams(map[string]string{
1076 // "userId": "sample@sample.com",
1077 // "subAccountId": "100002",
1078 // "path": "groups/developers",
1079 // })
1080 //
1081 // Result:
1082 // URL - /v1/users/{userId}/{subAccountId}/{path}/details
1083 // Composed URL - /v1/users/sample@sample.com/100002/groups/developers/details
1084 //
1085 // It replaces the value of the key while composing the request URL.
1086 // The values will be used as they are and will not be escaped.
1087 //
1088 // It can be overridden at the request level,
1089 // see [Request.SetRawPathParam] or [Request.SetRawPathParams]
1090 func (c *Client) SetRawPathParams(params map[string]string) *Client {
1091 for p, v := range params {
1092 c.SetRawPathParam(p, v)
1093 }
1094 return c
1095 }
1096 1097 // SetJSONEscapeHTML method enables or disables the HTML escape on JSON marshal.
1098 // By default, escape HTML is false.
1099 //
1100 // NOTE: This option only applies to the standard JSON Marshaller used by Resty.
1101 //
1102 // It can be overridden at the request level, see [Client.SetJSONEscapeHTML]
1103 func (c *Client) SetJSONEscapeHTML(b bool) *Client {
1104 c.jsonEscapeHTML = b
1105 return c
1106 }
1107 1108 // SetResponseBodyLimit method sets a maximum body size limit in bytes on response,
1109 // avoid reading too much data to memory.
1110 //
1111 // Client will return [resty.ErrResponseBodyTooLarge] if the body size of the body
1112 // in the uncompressed response is larger than the limit.
1113 // Body size limit will not be enforced in the following cases:
1114 // - ResponseBodyLimit <= 0, which is the default behavior.
1115 // - [Request.SetOutput] is called to save response data to the file.
1116 // - "DoNotParseResponse" is set for client or request.
1117 //
1118 // It can be overridden at the request level; see [Request.SetResponseBodyLimit]
1119 func (c *Client) SetResponseBodyLimit(v int) *Client {
1120 c.ResponseBodyLimit = v
1121 return c
1122 }
1123 1124 // EnableTrace method enables the Resty client trace for the requests fired from
1125 // the client using [httptrace.ClientTrace] and provides insights.
1126 //
1127 // client := resty.New().EnableTrace()
1128 //
1129 // resp, err := client.R().Get("https://httpbin.org/get")
1130 // fmt.Println("Error:", err)
1131 // fmt.Println("Trace Info:", resp.Request.TraceInfo())
1132 //
1133 // The method [Request.EnableTrace] is also available to get trace info for a single request.
1134 func (c *Client) EnableTrace() *Client {
1135 c.trace = true
1136 return c
1137 }
1138 1139 // DisableTrace method disables the Resty client trace. Refer to [Client.EnableTrace].
1140 func (c *Client) DisableTrace() *Client {
1141 c.trace = false
1142 return c
1143 }
1144 1145 // EnableGenerateCurlOnDebug method enables the generation of CURL commands in the debug log.
1146 // It works in conjunction with debug mode.
1147 //
1148 // NOTE: Use with care.
1149 // - Potential to leak sensitive data from [Request] and [Response] in the debug log.
1150 // - Beware of memory usage since the request body is reread.
1151 func (c *Client) EnableGenerateCurlOnDebug() *Client {
1152 c.generateCurlOnDebug = true
1153 return c
1154 }
1155 1156 // DisableGenerateCurlOnDebug method disables the option set by [Client.EnableGenerateCurlOnDebug].
1157 func (c *Client) DisableGenerateCurlOnDebug() *Client {
1158 c.generateCurlOnDebug = false
1159 return c
1160 }
1161 1162 // IsProxySet method returns the true is proxy is set from the Resty client; otherwise
1163 // false. By default, the proxy is set from the environment variable; refer to [http.ProxyFromEnvironment].
1164 func (c *Client) IsProxySet() bool {
1165 return c.proxyURL != nil
1166 }
1167 1168 // GetClient method returns the underlying [http.Client] used by the Resty.
1169 func (c *Client) GetClient() *http.Client {
1170 return c.httpClient
1171 }
1172 1173 // Clone returns a clone of the original client.
1174 //
1175 // NOTE: Use with care:
1176 // - Interface values are not deeply cloned. Thus, both the original and the
1177 // clone will use the same value.
1178 // - This function is not safe for concurrent use. You should only use this method
1179 // when you are sure that any other goroutine is not using the client.
1180 func (c *Client) Clone() *Client {
1181 // dereference the pointer and copy the value
1182 cc := *c
1183 1184 // lock values should not be copied - thus new values are used.
1185 cc.afterResponseLock = &sync.RWMutex{}
1186 cc.udBeforeRequestLock = &sync.RWMutex{}
1187 return &cc
1188 }
1189 1190 func (c *Client) executeBefore(req *Request) error {
1191 // Lock the user-defined pre-request hooks.
1192 c.udBeforeRequestLock.RLock()
1193 defer c.udBeforeRequestLock.RUnlock()
1194 1195 // Lock the post-request hooks.
1196 c.afterResponseLock.RLock()
1197 defer c.afterResponseLock.RUnlock()
1198 1199 // Apply Request middleware
1200 var err error
1201 1202 // user defined on before request methods
1203 // to modify the *resty.Request object
1204 for _, f := range c.udBeforeRequest {
1205 if err = f(c, req); err != nil {
1206 return wrapNoRetryErr(err)
1207 }
1208 }
1209 1210 // If there is a rate limiter set for this client, the Execute call
1211 // will return an error if the rate limit is exceeded.
1212 if req.client.rateLimiter != nil {
1213 if !req.client.rateLimiter.Allow() {
1214 return wrapNoRetryErr(ErrRateLimitExceeded)
1215 }
1216 }
1217 1218 // resty middlewares
1219 for _, f := range c.beforeRequest {
1220 if err = f(c, req); err != nil {
1221 return wrapNoRetryErr(err)
1222 }
1223 }
1224 1225 if hostHeader := req.Header.Get("Host"); hostHeader != "" {
1226 req.RawRequest.Host = hostHeader
1227 }
1228 1229 // call pre-request if defined
1230 if c.preReqHook != nil {
1231 if err = c.preReqHook(c, req.RawRequest); err != nil {
1232 return wrapNoRetryErr(err)
1233 }
1234 }
1235 1236 if err = requestLogger(c, req); err != nil {
1237 return wrapNoRetryErr(err)
1238 }
1239 1240 return nil
1241 }
1242 1243 // Executes method executes the given `Request` object and returns
1244 // response or error.
1245 func (c *Client) execute(req *Request) (*Response, error) {
1246 if err := c.executeBefore(req); err != nil {
1247 return nil, err
1248 }
1249 1250 req.Time = time.Now()
1251 resp, err := c.httpClient.Do(req.RawRequest)
1252 1253 response := &Response{
1254 Request: req,
1255 RawResponse: resp,
1256 }
1257 1258 if err != nil || req.notParseResponse || c.notParseResponse {
1259 response.setReceivedAt()
1260 if logErr := responseLogger(c, response); logErr != nil {
1261 return response, wrapErrors(logErr, err)
1262 }
1263 if err != nil {
1264 return response, err
1265 }
1266 return response, nil
1267 }
1268 1269 if !req.isSaveResponse {
1270 defer closeq(resp.Body)
1271 body := resp.Body
1272 1273 // GitHub #142 & #187
1274 if strings.EqualFold(resp.Header.Get(hdrContentEncodingKey), "gzip") && resp.ContentLength != 0 {
1275 if _, ok := body.(*gzip.Reader); !ok {
1276 body, err = gzip.NewReader(body)
1277 if err != nil {
1278 err = wrapErrors(responseLogger(c, response), err)
1279 response.setReceivedAt()
1280 return response, err
1281 }
1282 defer closeq(body)
1283 }
1284 }
1285 1286 if response.body, err = readAllWithLimit(body, req.responseBodyLimit); err != nil {
1287 err = wrapErrors(responseLogger(c, response), err)
1288 response.setReceivedAt()
1289 return response, err
1290 }
1291 1292 response.size = int64(len(response.body))
1293 }
1294 1295 response.setReceivedAt() // after we read the body
1296 1297 // Apply Response middleware
1298 err = responseLogger(c, response)
1299 if err != nil {
1300 return response, wrapNoRetryErr(err)
1301 }
1302 1303 for _, f := range c.afterResponse {
1304 if err = f(c, response); err != nil {
1305 break
1306 }
1307 }
1308 1309 return response, wrapNoRetryErr(err)
1310 }
1311 1312 var ErrResponseBodyTooLarge = errors.New("resty: response body too large")
1313 1314 // https://github.com/golang/go/issues/51115
1315 // [io.LimitedReader] can only return [io.EOF]
1316 func readAllWithLimit(r io.Reader, maxSize int) ([]byte, error) {
1317 if maxSize <= 0 {
1318 return io.ReadAll(r)
1319 }
1320 1321 var buf [512]byte // make buf stack allocated
1322 result := make([]byte, 0, 512)
1323 total := 0
1324 for {
1325 n, err := r.Read(buf[:])
1326 total += n
1327 if total > maxSize {
1328 return nil, ErrResponseBodyTooLarge
1329 }
1330 1331 if err != nil {
1332 if err == io.EOF {
1333 result = append(result, buf[:n]...)
1334 break
1335 }
1336 return nil, err
1337 }
1338 1339 result = append(result, buf[:n]...)
1340 }
1341 1342 return result, nil
1343 }
1344 1345 // getting TLS client config if not exists then create one
1346 func (c *Client) tlsConfig() (*tls.Config, error) {
1347 transport, err := c.Transport()
1348 if err != nil {
1349 return nil, err
1350 }
1351 if transport.TLSClientConfig == nil {
1352 transport.TLSClientConfig = &tls.Config{}
1353 }
1354 return transport.TLSClientConfig, nil
1355 }
1356 1357 // Transport method returns [http.Transport] currently in use or error
1358 // in case the currently used `transport` is not a [http.Transport].
1359 //
1360 // Since v2.8.0 has become exported method.
1361 func (c *Client) Transport() (*http.Transport, error) {
1362 if transport, ok := c.httpClient.Transport.(*http.Transport); ok {
1363 return transport, nil
1364 }
1365 return nil, errors.New("current transport is not an *http.Transport instance")
1366 }
1367 1368 // just an internal helper method
1369 func (c *Client) outputLogTo(w io.Writer) *Client {
1370 c.log.(*logger).l.SetOutput(w)
1371 return c
1372 }
1373 1374 // ResponseError is a wrapper that includes the server response with an error.
1375 // Neither the err nor the response should be nil.
1376 type ResponseError struct {
1377 Response *Response
1378 Err error
1379 }
1380 1381 func (e *ResponseError) Error() string {
1382 return e.Err.Error()
1383 }
1384 1385 func (e *ResponseError) Unwrap() error {
1386 return e.Err
1387 }
1388 1389 // Helper to run errorHooks hooks.
1390 // It wraps the error in a [ResponseError] if the resp is not nil
1391 // so hooks can access it.
1392 func (c *Client) onErrorHooks(req *Request, resp *Response, err error) {
1393 if err != nil {
1394 if resp != nil { // wrap with ResponseError
1395 err = &ResponseError{Response: resp, Err: err}
1396 }
1397 for _, h := range c.errorHooks {
1398 h(req, err)
1399 }
1400 } else {
1401 for _, h := range c.successHooks {
1402 h(c, resp)
1403 }
1404 }
1405 }
1406 1407 // Helper to run panicHooks hooks.
1408 func (c *Client) onPanicHooks(req *Request, err error) {
1409 for _, h := range c.panicHooks {
1410 h(req, err)
1411 }
1412 }
1413 1414 // Helper to run invalidHooks hooks.
1415 func (c *Client) onInvalidHooks(req *Request, err error) {
1416 for _, h := range c.invalidHooks {
1417 h(req, err)
1418 }
1419 }
1420 1421 //‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
1422 // File struct and its methods
1423 //_______________________________________________________________________
1424 1425 // File struct represents file information for multipart request
1426 type File struct {
1427 Name string
1428 ParamName string
1429 io.Reader
1430 }
1431 1432 // String method returns the string value of current file details
1433 func (f *File) String() string {
1434 return fmt.Sprintf("ParamName: %v; FileName: %v", f.ParamName, f.Name)
1435 }
1436 1437 //‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
1438 // MultipartField struct
1439 //_______________________________________________________________________
1440 1441 // MultipartField struct represents the custom data part for a multipart request
1442 type MultipartField struct {
1443 Param string
1444 FileName string
1445 ContentType string
1446 io.Reader
1447 }
1448 1449 func createClient(hc *http.Client) *Client {
1450 if hc.Transport == nil {
1451 hc.Transport = createTransport(nil)
1452 }
1453 1454 c := &Client{ // not setting lang default values
1455 QueryParam: url.Values{},
1456 FormData: url.Values{},
1457 Header: http.Header{},
1458 Cookies: make([]*http.Cookie, 0),
1459 RetryWaitTime: defaultWaitTime,
1460 RetryMaxWaitTime: defaultMaxWaitTime,
1461 PathParams: make(map[string]string),
1462 RawPathParams: make(map[string]string),
1463 JSONMarshal: json.Marshal,
1464 JSONUnmarshal: json.Unmarshal,
1465 XMLMarshal: xml.Marshal,
1466 XMLUnmarshal: xml.Unmarshal,
1467 HeaderAuthorizationKey: http.CanonicalHeaderKey("Authorization"),
1468 AuthScheme: "Bearer",
1469 1470 jsonEscapeHTML: true,
1471 httpClient: hc,
1472 debugBodySizeLimit: math.MaxInt32,
1473 udBeforeRequestLock: &sync.RWMutex{},
1474 afterResponseLock: &sync.RWMutex{},
1475 }
1476 1477 // Logger
1478 c.SetLogger(createLogger())
1479 1480 // default before request middlewares
1481 c.beforeRequest = []RequestMiddleware{
1482 parseRequestURL,
1483 parseRequestHeader,
1484 parseRequestBody,
1485 createHTTPRequest,
1486 addCredentials,
1487 createCurlCmd,
1488 }
1489 1490 // user defined request middlewares
1491 c.udBeforeRequest = []RequestMiddleware{}
1492 1493 // default after response middlewares
1494 c.afterResponse = []ResponseMiddleware{
1495 parseResponseBody,
1496 saveResponseIntoFile,
1497 }
1498 1499 return c
1500 }
1501