|
- // Copyright (c) 2015-2020 Jeevanandam M (jeeva@myjeeva.com), All rights reserved.
- // resty source code and usage is governed by a MIT style
- // license that can be found in the LICENSE file.
-
- package resty
-
- import (
- "bytes"
- "compress/gzip"
- "crypto/tls"
- "crypto/x509"
- "encoding/json"
- "errors"
- "fmt"
- "io"
- "io/ioutil"
- "math"
- "net/http"
- "net/url"
- "reflect"
- "regexp"
- "strings"
- "sync"
- "time"
- )
-
- const (
- // MethodGet HTTP method
- MethodGet = "GET"
-
- // MethodPost HTTP method
- MethodPost = "POST"
-
- // MethodPut HTTP method
- MethodPut = "PUT"
-
- // MethodDelete HTTP method
- MethodDelete = "DELETE"
-
- // MethodPatch HTTP method
- MethodPatch = "PATCH"
-
- // MethodHead HTTP method
- MethodHead = "HEAD"
-
- // MethodOptions HTTP method
- MethodOptions = "OPTIONS"
- )
-
- var (
- hdrUserAgentKey = http.CanonicalHeaderKey("User-Agent")
- hdrAcceptKey = http.CanonicalHeaderKey("Accept")
- hdrContentTypeKey = http.CanonicalHeaderKey("Content-Type")
- hdrContentLengthKey = http.CanonicalHeaderKey("Content-Length")
- hdrContentEncodingKey = http.CanonicalHeaderKey("Content-Encoding")
- hdrAuthorizationKey = http.CanonicalHeaderKey("Authorization")
-
- plainTextType = "text/plain; charset=utf-8"
- jsonContentType = "application/json"
- formContentType = "application/x-www-form-urlencoded"
-
- jsonCheck = regexp.MustCompile(`(?i:(application|text)/(json|.*\+json|json\-.*)(;|$))`)
- xmlCheck = regexp.MustCompile(`(?i:(application|text)/(xml|.*\+xml)(;|$))`)
-
- hdrUserAgentValue = "go-resty/" + Version + " (https://github.com/go-resty/resty)"
- bufPool = &sync.Pool{New: func() interface{} { return &bytes.Buffer{} }}
- )
-
- type (
- // RequestMiddleware type is for request middleware, called before a request is sent
- RequestMiddleware func(*Client, *Request) error
-
- // ResponseMiddleware type is for response middleware, called after a response has been received
- ResponseMiddleware func(*Client, *Response) error
-
- // PreRequestHook type is for the request hook, called right before the request is sent
- PreRequestHook func(*Client, *http.Request) error
-
- // RequestLogCallback type is for request logs, called before the request is logged
- RequestLogCallback func(*RequestLog) error
-
- // ResponseLogCallback type is for response logs, called before the response is logged
- ResponseLogCallback func(*ResponseLog) error
- )
-
- // Client struct is used to create Resty client with client level settings,
- // these settings are applicable to all the request raised from the client.
- //
- // Resty also provides an options to override most of the client settings
- // at request level.
- type Client struct {
- HostURL string
- QueryParam url.Values
- FormData url.Values
- Header http.Header
- UserInfo *User
- Token string
- AuthScheme string
- Cookies []*http.Cookie
- Error reflect.Type
- Debug bool
- DisableWarn bool
- AllowGetMethodPayload bool
- RetryCount int
- RetryWaitTime time.Duration
- RetryMaxWaitTime time.Duration
- RetryConditions []RetryConditionFunc
- RetryAfter RetryAfterFunc
- JSONMarshal func(v interface{}) ([]byte, error)
- JSONUnmarshal func(data []byte, v interface{}) error
-
- jsonEscapeHTML bool
- setContentLength bool
- closeConnection bool
- notParseResponse bool
- trace bool
- debugBodySizeLimit int64
- outputDirectory string
- scheme string
- pathParams map[string]string
- log Logger
- httpClient *http.Client
- proxyURL *url.URL
- beforeRequest []RequestMiddleware
- udBeforeRequest []RequestMiddleware
- preReqHook PreRequestHook
- afterResponse []ResponseMiddleware
- requestLog RequestLogCallback
- responseLog ResponseLogCallback
- }
-
- // User type is to hold an username and password information
- type User struct {
- Username, Password string
- }
-
- //‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
- // Client methods
- //___________________________________
-
- // SetHostURL method is to set Host URL in the client instance. It will be used with request
- // raised from this client with relative URL
- // // Setting HTTP address
- // client.SetHostURL("http://myjeeva.com")
- //
- // // Setting HTTPS address
- // client.SetHostURL("https://myjeeva.com")
- func (c *Client) SetHostURL(url string) *Client {
- c.HostURL = strings.TrimRight(url, "/")
- return c
- }
-
- // SetHeader method sets a single header field and its value in the client instance.
- // These headers will be applied to all requests raised from this client instance.
- // Also it can be overridden at request level header options.
- //
- // See `Request.SetHeader` or `Request.SetHeaders`.
- //
- // For Example: To set `Content-Type` and `Accept` as `application/json`
- //
- // client.
- // SetHeader("Content-Type", "application/json").
- // SetHeader("Accept", "application/json")
- func (c *Client) SetHeader(header, value string) *Client {
- c.Header.Set(header, value)
- return c
- }
-
- // SetHeaders method sets multiple headers field and its values at one go in the client instance.
- // These headers will be applied to all requests raised from this client instance. Also it can be
- // overridden at request level headers options.
- //
- // See `Request.SetHeaders` or `Request.SetHeader`.
- //
- // For Example: To set `Content-Type` and `Accept` as `application/json`
- //
- // client.SetHeaders(map[string]string{
- // "Content-Type": "application/json",
- // "Accept": "application/json",
- // })
- func (c *Client) SetHeaders(headers map[string]string) *Client {
- for h, v := range headers {
- c.Header.Set(h, v)
- }
- return c
- }
-
- // SetCookieJar method sets custom http.CookieJar in the resty client. Its way to override default.
- //
- // For Example: sometimes we don't want to save cookies in api contacting, we can remove the default
- // CookieJar in resty client.
- //
- // client.SetCookieJar(nil)
- func (c *Client) SetCookieJar(jar http.CookieJar) *Client {
- c.httpClient.Jar = jar
- return c
- }
-
- // SetCookie method appends a single cookie in the client instance.
- // These cookies will be added to all the request raised from this client instance.
- // client.SetCookie(&http.Cookie{
- // Name:"go-resty",
- // Value:"This is cookie value",
- // })
- func (c *Client) SetCookie(hc *http.Cookie) *Client {
- c.Cookies = append(c.Cookies, hc)
- return c
- }
-
- // SetCookies method sets an array of cookies in the client instance.
- // These cookies will be added to all the request raised from this client instance.
- // cookies := []*http.Cookie{
- // &http.Cookie{
- // Name:"go-resty-1",
- // Value:"This is cookie 1 value",
- // },
- // &http.Cookie{
- // Name:"go-resty-2",
- // Value:"This is cookie 2 value",
- // },
- // }
- //
- // // Setting a cookies into resty
- // client.SetCookies(cookies)
- func (c *Client) SetCookies(cs []*http.Cookie) *Client {
- c.Cookies = append(c.Cookies, cs...)
- return c
- }
-
- // SetQueryParam method sets single parameter and its value in the client instance.
- // It will be formed as query string for the request.
- //
- // For Example: `search=kitchen%20papers&size=large`
- // in the URL after `?` mark. These query params will be added to all the request raised from
- // this client instance. Also it can be overridden at request level Query Param options.
- //
- // See `Request.SetQueryParam` or `Request.SetQueryParams`.
- // client.
- // SetQueryParam("search", "kitchen papers").
- // SetQueryParam("size", "large")
- func (c *Client) SetQueryParam(param, value string) *Client {
- c.QueryParam.Set(param, value)
- return c
- }
-
- // SetQueryParams method sets multiple parameters and their values at one go in the client instance.
- // It will be formed as query string for the request.
- //
- // For Example: `search=kitchen%20papers&size=large`
- // in the URL after `?` mark. These query params will be added to all the request raised from this
- // client instance. Also it can be overridden at request level Query Param options.
- //
- // See `Request.SetQueryParams` or `Request.SetQueryParam`.
- // client.SetQueryParams(map[string]string{
- // "search": "kitchen papers",
- // "size": "large",
- // })
- func (c *Client) SetQueryParams(params map[string]string) *Client {
- for p, v := range params {
- c.SetQueryParam(p, v)
- }
- return c
- }
-
- // SetFormData method sets Form parameters and their values in the client instance.
- // It's applicable only HTTP method `POST` and `PUT` and requets content type would be set as
- // `application/x-www-form-urlencoded`. These form data will be added to all the request raised from
- // this client instance. Also it can be overridden at request level form data.
- //
- // See `Request.SetFormData`.
- // client.SetFormData(map[string]string{
- // "access_token": "BC594900-518B-4F7E-AC75-BD37F019E08F",
- // "user_id": "3455454545",
- // })
- func (c *Client) SetFormData(data map[string]string) *Client {
- for k, v := range data {
- c.FormData.Set(k, v)
- }
- return c
- }
-
- // SetBasicAuth method sets the basic authentication header in the HTTP request. For Example:
- // Authorization: Basic <base64-encoded-value>
- //
- // For Example: To set the header for username "go-resty" and password "welcome"
- // client.SetBasicAuth("go-resty", "welcome")
- //
- // This basic auth information gets added to all the request rasied from this client instance.
- // Also it can be overridden or set one at the request level is supported.
- //
- // See `Request.SetBasicAuth`.
- func (c *Client) SetBasicAuth(username, password string) *Client {
- c.UserInfo = &User{Username: username, Password: password}
- return c
- }
-
- // SetAuthToken method sets the auth token of the `Authorization` header for all HTTP requests.
- // The default auth scheme is `Bearer`, it can be customized with the method `SetAuthScheme`. For Example:
- // Authorization: <auth-scheme> <auth-token-value>
- //
- // For Example: To set auth token BC594900518B4F7EAC75BD37F019E08FBC594900518B4F7EAC75BD37F019E08F
- //
- // client.SetAuthToken("BC594900518B4F7EAC75BD37F019E08FBC594900518B4F7EAC75BD37F019E08F")
- //
- // This auth token gets added to all the requests rasied from this client instance.
- // Also it can be overridden or set one at the request level is supported.
- //
- // See `Request.SetAuthToken`.
- func (c *Client) SetAuthToken(token string) *Client {
- c.Token = token
- return c
- }
-
- // SetAuthScheme method sets the auth scheme type in the HTTP request. For Example:
- // Authorization: <auth-scheme-value> <auth-token-value>
- //
- // For Example: To set the scheme to use OAuth
- //
- // client.SetAuthScheme("OAuth")
- //
- // This auth scheme gets added to all the requests rasied from this client instance.
- // Also it can be overridden or set one at the request level is supported.
- //
- // Information about auth schemes can be found in RFC7235 which is linked to below
- // along with the page containing the currently defined official authentication schemes:
- // https://tools.ietf.org/html/rfc7235
- // https://www.iana.org/assignments/http-authschemes/http-authschemes.xhtml#authschemes
- //
- // See `Request.SetAuthToken`.
- func (c *Client) SetAuthScheme(scheme string) *Client {
- c.AuthScheme = scheme
- return c
- }
-
- // R method creates a new request instance, its used for Get, Post, Put, Delete, Patch, Head, Options, etc.
- func (c *Client) R() *Request {
- r := &Request{
- QueryParam: url.Values{},
- FormData: url.Values{},
- Header: http.Header{},
- Cookies: make([]*http.Cookie, 0),
-
- client: c,
- multipartFiles: []*File{},
- multipartFields: []*MultipartField{},
- pathParams: map[string]string{},
- jsonEscapeHTML: true,
- }
- return r
- }
-
- // NewRequest is an alias for method `R()`. Creates a new request instance, its used for
- // Get, Post, Put, Delete, Patch, Head, Options, etc.
- func (c *Client) NewRequest() *Request {
- return c.R()
- }
-
- // OnBeforeRequest method appends request middleware into the before request chain.
- // Its gets applied after default Resty request middlewares and before request
- // been sent from Resty to host server.
- // client.OnBeforeRequest(func(c *resty.Client, r *resty.Request) error {
- // // Now you have access to Client and Request instance
- // // manipulate it as per your need
- //
- // return nil // if its success otherwise return error
- // })
- func (c *Client) OnBeforeRequest(m RequestMiddleware) *Client {
- c.udBeforeRequest = append(c.udBeforeRequest, m)
- return c
- }
-
- // OnAfterResponse method appends response middleware into the after response chain.
- // Once we receive response from host server, default Resty response middleware
- // gets applied and then user assigened response middlewares applied.
- // client.OnAfterResponse(func(c *resty.Client, r *resty.Response) error {
- // // Now you have access to Client and Response instance
- // // manipulate it as per your need
- //
- // return nil // if its success otherwise return error
- // })
- func (c *Client) OnAfterResponse(m ResponseMiddleware) *Client {
- c.afterResponse = append(c.afterResponse, m)
- return c
- }
-
- // SetPreRequestHook method sets the given pre-request function into resty client.
- // It is called right before the request is fired.
- //
- // Note: Only one pre-request hook can be registered. Use `client.OnBeforeRequest` for mutilple.
- func (c *Client) SetPreRequestHook(h PreRequestHook) *Client {
- if c.preReqHook != nil {
- c.log.Warnf("Overwriting an existing pre-request hook: %s", functionName(h))
- }
- c.preReqHook = h
- return c
- }
-
- // SetDebug method enables the debug mode on Resty client. Client logs details of every request and response.
- // For `Request` it logs information such as HTTP verb, Relative URL path, Host, Headers, Body if it has one.
- // For `Response` it logs information such as Status, Response Time, Headers, Body if it has one.
- // client.SetDebug(true)
- func (c *Client) SetDebug(d bool) *Client {
- c.Debug = d
- return c
- }
-
- // SetDebugBodyLimit sets the maximum size for which the response and request body will be logged in debug mode.
- // client.SetDebugBodyLimit(1000000)
- func (c *Client) SetDebugBodyLimit(sl int64) *Client {
- c.debugBodySizeLimit = sl
- return c
- }
-
- // OnRequestLog method used to set request log callback into Resty. Registered callback gets
- // called before the resty actually logs the information.
- func (c *Client) OnRequestLog(rl RequestLogCallback) *Client {
- if c.requestLog != nil {
- c.log.Warnf("Overwriting an existing on-request-log callback from=%s to=%s",
- functionName(c.requestLog), functionName(rl))
- }
- c.requestLog = rl
- return c
- }
-
- // OnResponseLog method used to set response log callback into Resty. Registered callback gets
- // called before the resty actually logs the information.
- func (c *Client) OnResponseLog(rl ResponseLogCallback) *Client {
- if c.responseLog != nil {
- c.log.Warnf("Overwriting an existing on-response-log callback from=%s to=%s",
- functionName(c.responseLog), functionName(rl))
- }
- c.responseLog = rl
- return c
- }
-
- // SetDisableWarn method disables the warning message on Resty client.
- //
- // For Example: Resty warns the user when BasicAuth used on non-TLS mode.
- // client.SetDisableWarn(true)
- func (c *Client) SetDisableWarn(d bool) *Client {
- c.DisableWarn = d
- return c
- }
-
- // SetAllowGetMethodPayload method allows the GET method with payload on Resty client.
- //
- // For Example: Resty allows the user sends request with a payload on HTTP GET method.
- // client.SetAllowGetMethodPayload(true)
- func (c *Client) SetAllowGetMethodPayload(a bool) *Client {
- c.AllowGetMethodPayload = a
- return c
- }
-
- // SetLogger method sets given writer for logging Resty request and response details.
- //
- // Compliant to interface `resty.Logger`.
- func (c *Client) SetLogger(l Logger) *Client {
- c.log = l
- return c
- }
-
- // SetContentLength method enables the HTTP header `Content-Length` value for every request.
- // By default Resty won't set `Content-Length`.
- // client.SetContentLength(true)
- //
- // Also you have an option to enable for particular request. See `Request.SetContentLength`
- func (c *Client) SetContentLength(l bool) *Client {
- c.setContentLength = l
- return c
- }
-
- // SetTimeout method sets timeout for request raised from client.
- // client.SetTimeout(time.Duration(1 * time.Minute))
- func (c *Client) SetTimeout(timeout time.Duration) *Client {
- c.httpClient.Timeout = timeout
- return c
- }
-
- // SetError method is to register the global or client common `Error` object into Resty.
- // It is used for automatic unmarshalling if response status code is greater than 399 and
- // content type either JSON or XML. Can be pointer or non-pointer.
- // client.SetError(&Error{})
- // // OR
- // client.SetError(Error{})
- func (c *Client) SetError(err interface{}) *Client {
- c.Error = typeOf(err)
- return c
- }
-
- // SetRedirectPolicy method sets the client redirect poilicy. Resty provides ready to use
- // redirect policies. Wanna create one for yourself refer to `redirect.go`.
- //
- // client.SetRedirectPolicy(FlexibleRedirectPolicy(20))
- //
- // // Need multiple redirect policies together
- // client.SetRedirectPolicy(FlexibleRedirectPolicy(20), DomainCheckRedirectPolicy("host1.com", "host2.net"))
- func (c *Client) SetRedirectPolicy(policies ...interface{}) *Client {
- for _, p := range policies {
- if _, ok := p.(RedirectPolicy); !ok {
- c.log.Errorf("%v does not implement resty.RedirectPolicy (missing Apply method)",
- functionName(p))
- }
- }
-
- c.httpClient.CheckRedirect = func(req *http.Request, via []*http.Request) error {
- for _, p := range policies {
- if err := p.(RedirectPolicy).Apply(req, via); err != nil {
- return err
- }
- }
- return nil // looks good, go ahead
- }
-
- return c
- }
-
- // SetRetryCount method enables retry on Resty client and allows you
- // to set no. of retry count. Resty uses a Backoff mechanism.
- func (c *Client) SetRetryCount(count int) *Client {
- c.RetryCount = count
- return c
- }
-
- // SetRetryWaitTime method sets default wait time to sleep before retrying
- // request.
- //
- // Default is 100 milliseconds.
- func (c *Client) SetRetryWaitTime(waitTime time.Duration) *Client {
- c.RetryWaitTime = waitTime
- return c
- }
-
- // SetRetryMaxWaitTime method sets max wait time to sleep before retrying
- // request.
- //
- // Default is 2 seconds.
- func (c *Client) SetRetryMaxWaitTime(maxWaitTime time.Duration) *Client {
- c.RetryMaxWaitTime = maxWaitTime
- return c
- }
-
- // SetRetryAfter sets callback to calculate wait time between retries.
- // Default (nil) implies exponential backoff with jitter
- func (c *Client) SetRetryAfter(callback RetryAfterFunc) *Client {
- c.RetryAfter = callback
- return c
- }
-
- // AddRetryCondition method adds a retry condition function to array of functions
- // that are checked to determine if the request is retried. The request will
- // retry if any of the functions return true and error is nil.
- func (c *Client) AddRetryCondition(condition RetryConditionFunc) *Client {
- c.RetryConditions = append(c.RetryConditions, condition)
- return c
- }
-
- // SetTLSClientConfig method sets TLSClientConfig for underling client Transport.
- //
- // For Example:
- // // One can set custom root-certificate. Refer: http://golang.org/pkg/crypto/tls/#example_Dial
- // client.SetTLSClientConfig(&tls.Config{ RootCAs: roots })
- //
- // // or One can disable security check (https)
- // client.SetTLSClientConfig(&tls.Config{ InsecureSkipVerify: true })
- //
- // Note: This method overwrites existing `TLSClientConfig`.
- func (c *Client) SetTLSClientConfig(config *tls.Config) *Client {
- transport, err := c.transport()
- if err != nil {
- c.log.Errorf("%v", err)
- return c
- }
- transport.TLSClientConfig = config
- return c
- }
-
- // SetProxy method sets the Proxy URL and Port for Resty client.
- // client.SetProxy("http://proxyserver:8888")
- //
- // OR Without this `SetProxy` method, you could also set Proxy via environment variable.
- //
- // Refer to godoc `http.ProxyFromEnvironment`.
- func (c *Client) SetProxy(proxyURL string) *Client {
- transport, err := c.transport()
- if err != nil {
- c.log.Errorf("%v", err)
- return c
- }
-
- pURL, err := url.Parse(proxyURL)
- if err != nil {
- c.log.Errorf("%v", err)
- return c
- }
-
- c.proxyURL = pURL
- transport.Proxy = http.ProxyURL(c.proxyURL)
- return c
- }
-
- // RemoveProxy method removes the proxy configuration from Resty client
- // client.RemoveProxy()
- func (c *Client) RemoveProxy() *Client {
- transport, err := c.transport()
- if err != nil {
- c.log.Errorf("%v", err)
- return c
- }
- c.proxyURL = nil
- transport.Proxy = nil
- return c
- }
-
- // SetCertificates method helps to set client certificates into Resty conveniently.
- func (c *Client) SetCertificates(certs ...tls.Certificate) *Client {
- config, err := c.tlsConfig()
- if err != nil {
- c.log.Errorf("%v", err)
- return c
- }
- config.Certificates = append(config.Certificates, certs...)
- return c
- }
-
- // SetRootCertificate method helps to add one or more root certificates into Resty client
- // client.SetRootCertificate("/path/to/root/pemFile.pem")
- func (c *Client) SetRootCertificate(pemFilePath string) *Client {
- rootPemData, err := ioutil.ReadFile(pemFilePath)
- if err != nil {
- c.log.Errorf("%v", err)
- return c
- }
-
- config, err := c.tlsConfig()
- if err != nil {
- c.log.Errorf("%v", err)
- return c
- }
- if config.RootCAs == nil {
- config.RootCAs = x509.NewCertPool()
- }
-
- config.RootCAs.AppendCertsFromPEM(rootPemData)
- return c
- }
-
- // SetRootCertificateFromString method helps to add one or more root certificates into Resty client
- // client.SetRootCertificateFromString("pem file content")
- func (c *Client) SetRootCertificateFromString(pemContent string) *Client {
- config, err := c.tlsConfig()
- if err != nil {
- c.log.Errorf("%v", err)
- return c
- }
- if config.RootCAs == nil {
- config.RootCAs = x509.NewCertPool()
- }
-
- config.RootCAs.AppendCertsFromPEM([]byte(pemContent))
- return c
- }
-
- // SetOutputDirectory method sets output directory for saving HTTP response into file.
- // If the output directory not exists then resty creates one. This setting is optional one,
- // if you're planning using absolute path in `Request.SetOutput` and can used together.
- // client.SetOutputDirectory("/save/http/response/here")
- func (c *Client) SetOutputDirectory(dirPath string) *Client {
- c.outputDirectory = dirPath
- return c
- }
-
- // SetTransport method sets custom `*http.Transport` or any `http.RoundTripper`
- // compatible interface implementation in the resty client.
- //
- // Note:
- //
- // - If transport is not type of `*http.Transport` then you may not be able to
- // take advantage of some of the Resty client settings.
- //
- // - It overwrites the Resty client transport instance and it's configurations.
- //
- // transport := &http.Transport{
- // // somthing like Proxying to httptest.Server, etc...
- // Proxy: func(req *http.Request) (*url.URL, error) {
- // return url.Parse(server.URL)
- // },
- // }
- //
- // client.SetTransport(transport)
- func (c *Client) SetTransport(transport http.RoundTripper) *Client {
- if transport != nil {
- c.httpClient.Transport = transport
- }
- return c
- }
-
- // SetScheme method sets custom scheme in the Resty client. It's way to override default.
- // client.SetScheme("http")
- func (c *Client) SetScheme(scheme string) *Client {
- if !IsStringEmpty(scheme) {
- c.scheme = scheme
- }
- return c
- }
-
- // SetCloseConnection method sets variable `Close` in http request struct with the given
- // value. More info: https://golang.org/src/net/http/request.go
- func (c *Client) SetCloseConnection(close bool) *Client {
- c.closeConnection = close
- return c
- }
-
- // SetDoNotParseResponse method instructs `Resty` not to parse the response body automatically.
- // Resty exposes the raw response body as `io.ReadCloser`. Also do not forget to close the body,
- // otherwise you might get into connection leaks, no connection reuse.
- //
- // Note: Response middlewares are not applicable, if you use this option. Basically you have
- // taken over the control of response parsing from `Resty`.
- func (c *Client) SetDoNotParseResponse(parse bool) *Client {
- c.notParseResponse = parse
- return c
- }
-
- // SetPathParams method sets multiple URL path key-value pairs at one go in the
- // Resty client instance.
- // client.SetPathParams(map[string]string{
- // "userId": "sample@sample.com",
- // "subAccountId": "100002",
- // })
- //
- // Result:
- // URL - /v1/users/{userId}/{subAccountId}/details
- // Composed URL - /v1/users/sample@sample.com/100002/details
- // It replace the value of the key while composing request URL. Also it can be
- // overridden at request level Path Params options, see `Request.SetPathParams`.
- func (c *Client) SetPathParams(params map[string]string) *Client {
- for p, v := range params {
- c.pathParams[p] = v
- }
- return c
- }
-
- // SetJSONEscapeHTML method is to enable/disable the HTML escape on JSON marshal.
- //
- // Note: This option only applicable to standard JSON Marshaller.
- func (c *Client) SetJSONEscapeHTML(b bool) *Client {
- c.jsonEscapeHTML = b
- return c
- }
-
- // EnableTrace method enables the Resty client trace for the requests fired from
- // the client using `httptrace.ClientTrace` and provides insights.
- //
- // client := resty.New().EnableTrace()
- //
- // resp, err := client.R().Get("https://httpbin.org/get")
- // fmt.Println("Error:", err)
- // fmt.Println("Trace Info:", resp.Request.TraceInfo())
- //
- // Also `Request.EnableTrace` available too to get trace info for single request.
- //
- // Since v2.0.0
- func (c *Client) EnableTrace() *Client {
- c.trace = true
- return c
- }
-
- // DisableTrace method disables the Resty client trace. Refer to `Client.EnableTrace`.
- //
- // Since v2.0.0
- func (c *Client) DisableTrace() *Client {
- c.trace = false
- return c
- }
-
- // IsProxySet method returns the true is proxy is set from resty client otherwise
- // false. By default proxy is set from environment, refer to `http.ProxyFromEnvironment`.
- func (c *Client) IsProxySet() bool {
- return c.proxyURL != nil
- }
-
- // GetClient method returns the current `http.Client` used by the resty client.
- func (c *Client) GetClient() *http.Client {
- return c.httpClient
- }
-
- //‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
- // Client Unexported methods
- //_______________________________________________________________________
-
- // Executes method executes the given `Request` object and returns response
- // error.
- func (c *Client) execute(req *Request) (*Response, error) {
- defer releaseBuffer(req.bodyBuf)
- // Apply Request middleware
- var err error
-
- // user defined on before request methods
- // to modify the *resty.Request object
- for _, f := range c.udBeforeRequest {
- if err = f(c, req); err != nil {
- return nil, wrapNoRetryErr(err)
- }
- }
-
- // resty middlewares
- for _, f := range c.beforeRequest {
- if err = f(c, req); err != nil {
- return nil, wrapNoRetryErr(err)
- }
- }
-
- if hostHeader := req.Header.Get("Host"); hostHeader != "" {
- req.RawRequest.Host = hostHeader
- }
-
- // call pre-request if defined
- if c.preReqHook != nil {
- if err = c.preReqHook(c, req.RawRequest); err != nil {
- return nil, wrapNoRetryErr(err)
- }
- }
-
- if err = requestLogger(c, req); err != nil {
- return nil, wrapNoRetryErr(err)
- }
-
- req.Time = time.Now()
- resp, err := c.httpClient.Do(req.RawRequest)
-
- response := &Response{
- Request: req,
- RawResponse: resp,
- }
-
- if err != nil || req.notParseResponse || c.notParseResponse {
- response.setReceivedAt()
- return response, err
- }
-
- if !req.isSaveResponse {
- defer closeq(resp.Body)
- body := resp.Body
-
- // GitHub #142 & #187
- if strings.EqualFold(resp.Header.Get(hdrContentEncodingKey), "gzip") && resp.ContentLength != 0 {
- if _, ok := body.(*gzip.Reader); !ok {
- body, err = gzip.NewReader(body)
- if err != nil {
- response.setReceivedAt()
- return response, err
- }
- defer closeq(body)
- }
- }
-
- if response.body, err = ioutil.ReadAll(body); err != nil {
- response.setReceivedAt()
- return response, err
- }
-
- response.setReceivedAt() // after we read the body
- response.size = int64(len(response.body))
- }
-
- // Apply Response middleware
- for _, f := range c.afterResponse {
- if err = f(c, response); err != nil {
- break
- }
- }
-
- return response, wrapNoRetryErr(err)
- }
-
- // getting TLS client config if not exists then create one
- func (c *Client) tlsConfig() (*tls.Config, error) {
- transport, err := c.transport()
- if err != nil {
- return nil, err
- }
- if transport.TLSClientConfig == nil {
- transport.TLSClientConfig = &tls.Config{}
- }
- return transport.TLSClientConfig, nil
- }
-
- // Transport method returns `*http.Transport` currently in use or error
- // in case currently used `transport` is not a `*http.Transport`.
- func (c *Client) transport() (*http.Transport, error) {
- if transport, ok := c.httpClient.Transport.(*http.Transport); ok {
- return transport, nil
- }
- return nil, errors.New("current transport is not an *http.Transport instance")
- }
-
- // just an internal helper method
- func (c *Client) outputLogTo(w io.Writer) *Client {
- c.log.(*logger).l.SetOutput(w)
- return c
- }
-
- //‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
- // File struct and its methods
- //_______________________________________________________________________
-
- // File struct represent file information for multipart request
- type File struct {
- Name string
- ParamName string
- io.Reader
- }
-
- // String returns string value of current file details
- func (f *File) String() string {
- return fmt.Sprintf("ParamName: %v; FileName: %v", f.ParamName, f.Name)
- }
-
- //‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
- // MultipartField struct
- //_______________________________________________________________________
-
- // MultipartField struct represent custom data part for multipart request
- type MultipartField struct {
- Param string
- FileName string
- ContentType string
- io.Reader
- }
-
- //‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
- // Unexported package methods
- //_______________________________________________________________________
-
- func createClient(hc *http.Client) *Client {
- if hc.Transport == nil {
- hc.Transport = createTransport(nil)
- }
-
- c := &Client{ // not setting lang default values
- QueryParam: url.Values{},
- FormData: url.Values{},
- Header: http.Header{},
- Cookies: make([]*http.Cookie, 0),
- RetryWaitTime: defaultWaitTime,
- RetryMaxWaitTime: defaultMaxWaitTime,
- JSONMarshal: json.Marshal,
- JSONUnmarshal: json.Unmarshal,
- jsonEscapeHTML: true,
- httpClient: hc,
- debugBodySizeLimit: math.MaxInt32,
- pathParams: make(map[string]string),
- }
-
- // Logger
- c.SetLogger(createLogger())
-
- // default before request middlewares
- c.beforeRequest = []RequestMiddleware{
- parseRequestURL,
- parseRequestHeader,
- parseRequestBody,
- createHTTPRequest,
- addCredentials,
- }
-
- // user defined request middlewares
- c.udBeforeRequest = []RequestMiddleware{}
-
- // default after response middlewares
- c.afterResponse = []ResponseMiddleware{
- responseLogger,
- parseResponseBody,
- saveResponseIntoFile,
- }
-
- return c
- }
|