github.com/bingoohuang/gg@v0.0.0-20240325092523-45da7dee9335/pkg/resty/request.go (about)

     1  // Copyright (c) 2015-2021 Jeevanandam M (jeeva@myjeeva.com), All rights reserved.
     2  // resty source code and usage is governed by a MIT style
     3  // license that can be found in the LICENSE file.
     4  
     5  package resty
     6  
     7  import (
     8  	"bytes"
     9  	"context"
    10  	"encoding/json"
    11  	"encoding/xml"
    12  	"fmt"
    13  	"io"
    14  	"net"
    15  	"net/http"
    16  	"net/url"
    17  	"reflect"
    18  	"strings"
    19  	"time"
    20  )
    21  
    22  //‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
    23  // Request struct and methods
    24  //_______________________________________________________________________
    25  
    26  // Request struct is used to compose and fire individual request from
    27  // resty client. Request provides an options to override client level
    28  // settings and also an options for the request composition.
    29  type Request struct {
    30  	URL        string
    31  	Method     string
    32  	Token      string
    33  	AuthScheme string
    34  	QueryParam url.Values
    35  	FormData   url.Values
    36  	PathParams map[string]string
    37  	Header     http.Header
    38  	Time       time.Time
    39  	Body       interface{}
    40  	Result     interface{}
    41  	Error      interface{}
    42  	RawRequest *http.Request
    43  	SRV        *SRVRecord
    44  	UserInfo   *User
    45  	Cookies    []*http.Cookie
    46  
    47  	// Attempt is to represent the request attempt made during a Resty
    48  	// request execution flow, including retry count.
    49  	Attempt int
    50  
    51  	isMultiPart         bool
    52  	isFormData          bool
    53  	setContentLength    bool
    54  	isSaveResponse      bool
    55  	notParseResponse    bool
    56  	jsonEscapeHTML      bool
    57  	trace               bool
    58  	outputFile          string
    59  	fallbackContentType string
    60  	forceContentType    string
    61  	ctx                 context.Context
    62  	values              map[string]interface{}
    63  	client              *Client
    64  	bodyBuf             *bytes.Buffer
    65  	clientTrace         *clientTrace
    66  	multipartFiles      []*File
    67  	multipartFields     []*MultipartField
    68  	retryConditions     []RetryConditionFunc
    69  }
    70  
    71  // Context method returns the Context if its already set in request
    72  // otherwise it creates new one using `context.Background()`.
    73  func (r *Request) Context() context.Context {
    74  	if r.ctx == nil {
    75  		return context.Background()
    76  	}
    77  	return r.ctx
    78  }
    79  
    80  // SetContext method sets the context.Context for current Request. It allows
    81  // to interrupt the request execution if ctx.Done() channel is closed.
    82  // See https://blog.golang.org/context article and the "context" package
    83  // documentation.
    84  func (r *Request) SetContext(ctx context.Context) *Request {
    85  	r.ctx = ctx
    86  	return r
    87  }
    88  
    89  // SetHeader method is to set a single header field and its value in the current request.
    90  //
    91  // For Example: To set `Content-Type` and `Accept` as `application/json`.
    92  //
    93  //	client.R().
    94  //		SetHeader("Content-Type", "application/json").
    95  //		SetHeader("Accept", "application/json")
    96  //
    97  // Also you can override header value, which was set at client instance level.
    98  func (r *Request) SetHeader(header, value string) *Request {
    99  	r.Header.Set(header, value)
   100  	return r
   101  }
   102  
   103  // SetHeaders method sets multiple headers field and its values at one go in the current request.
   104  //
   105  // For Example: To set `Content-Type` and `Accept` as `application/json`
   106  //
   107  //	client.R().
   108  //		SetHeaders(map[string]string{
   109  //			"Content-Type": "application/json",
   110  //			"Accept": "application/json",
   111  //		})
   112  //
   113  // Also you can override header value, which was set at client instance level.
   114  func (r *Request) SetHeaders(headers map[string]string) *Request {
   115  	for h, v := range headers {
   116  		r.SetHeader(h, v)
   117  	}
   118  	return r
   119  }
   120  
   121  // SetHeaderMultiValues sets multiple headers fields and its values is list of strings at one go in the current request.
   122  //
   123  // For Example: To set `Accept` as `text/html, application/xhtml+xml, application/xml;q=0.9, image/webp, */*;q=0.8`
   124  //
   125  //	client.R().
   126  //		SetHeaderMultiValues(map[string][]string{
   127  //			"Accept": []string{"text/html", "application/xhtml+xml", "application/xml;q=0.9", "image/webp", "*/*;q=0.8"},
   128  //		})
   129  //
   130  // Also you can override header value, which was set at client instance level.
   131  func (r *Request) SetHeaderMultiValues(headers map[string][]string) *Request {
   132  	for key, values := range headers {
   133  		r.SetHeader(key, strings.Join(values, ", "))
   134  	}
   135  	return r
   136  }
   137  
   138  // SetHeaderVerbatim method is to set a single header field and its value verbatim in the current request.
   139  //
   140  // For Example: To set `all_lowercase` and `UPPERCASE` as `available`.
   141  //
   142  //	client.R().
   143  //		SetHeaderVerbatim("all_lowercase", "available").
   144  //		SetHeaderVerbatim("UPPERCASE", "available")
   145  //
   146  // Also you can override header value, which was set at client instance level.
   147  func (r *Request) SetHeaderVerbatim(header, value string) *Request {
   148  	r.Header[header] = []string{value}
   149  	return r
   150  }
   151  
   152  // SetQueryParam method sets single parameter and its value in the current request.
   153  // It will be formed as query string for the request.
   154  //
   155  // For Example: `search=kitchen%20papers&size=large` in the URL after `?` mark.
   156  //
   157  //	client.R().
   158  //		SetQueryParam("search", "kitchen papers").
   159  //		SetQueryParam("size", "large")
   160  //
   161  // Also you can override query params value, which was set at client instance level.
   162  func (r *Request) SetQueryParam(param, value string) *Request {
   163  	r.QueryParam.Set(param, value)
   164  	return r
   165  }
   166  
   167  // SetQueryParams method sets multiple parameters and its values at one go in the current request.
   168  // It will be formed as query string for the request.
   169  //
   170  // For Example: `search=kitchen%20papers&size=large` in the URL after `?` mark.
   171  //
   172  //	client.R().
   173  //		SetQueryParams(map[string]string{
   174  //			"search": "kitchen papers",
   175  //			"size": "large",
   176  //		})
   177  //
   178  // Also you can override query params value, which was set at client instance level.
   179  func (r *Request) SetQueryParams(params map[string]string) *Request {
   180  	for p, v := range params {
   181  		r.SetQueryParam(p, v)
   182  	}
   183  	return r
   184  }
   185  
   186  // SetQueryParamsFromValues method appends multiple parameters with multi-value
   187  // (`url.Values`) at one go in the current request. It will be formed as
   188  // query string for the request.
   189  //
   190  // For Example: `status=pending&status=approved&status=open` in the URL after `?` mark.
   191  //
   192  //	client.R().
   193  //		SetQueryParamsFromValues(url.Values{
   194  //			"status": []string{"pending", "approved", "open"},
   195  //		})
   196  //
   197  // Also you can override query params value, which was set at client instance level.
   198  func (r *Request) SetQueryParamsFromValues(params url.Values) *Request {
   199  	for p, v := range params {
   200  		for _, pv := range v {
   201  			r.QueryParam.Add(p, pv)
   202  		}
   203  	}
   204  	return r
   205  }
   206  
   207  // SetQueryString method provides ability to use string as an input to set URL query string for the request.
   208  //
   209  // Using String as an input
   210  //
   211  //	client.R().
   212  //		SetQueryString("productId=232&template=fresh-sample&cat=resty&source=google&kw=buy a lot more")
   213  func (r *Request) SetQueryString(query string) *Request {
   214  	params, err := url.ParseQuery(strings.TrimSpace(query))
   215  	if err == nil {
   216  		for p, v := range params {
   217  			for _, pv := range v {
   218  				r.QueryParam.Add(p, pv)
   219  			}
   220  		}
   221  	} else {
   222  		r.client.log.Errorf("%v", err)
   223  	}
   224  	return r
   225  }
   226  
   227  // SetFormData method sets Form parameters and their values in the current request.
   228  // It's applicable only HTTP method `POST` and `PUT` and requests content type would be set as
   229  // `application/x-www-form-urlencoded`.
   230  //
   231  //	client.R().
   232  //		SetFormData(map[string]string{
   233  //			"access_token": "BC594900-518B-4F7E-AC75-BD37F019E08F",
   234  //			"user_id": "3455454545",
   235  //		})
   236  //
   237  // Also you can override form data value, which was set at client instance level.
   238  func (r *Request) SetFormData(data map[string]string) *Request {
   239  	for k, v := range data {
   240  		r.FormData.Set(k, v)
   241  	}
   242  	return r
   243  }
   244  
   245  // SetFormDataFromValues method appends multiple form parameters with multi-value
   246  // (`url.Values`) at one go in the current request.
   247  //
   248  //	client.R().
   249  //		SetFormDataFromValues(url.Values{
   250  //			"search_criteria": []string{"book", "glass", "pencil"},
   251  //		})
   252  //
   253  // Also you can override form data value, which was set at client instance level.
   254  func (r *Request) SetFormDataFromValues(data url.Values) *Request {
   255  	for k, v := range data {
   256  		for _, kv := range v {
   257  			r.FormData.Add(k, kv)
   258  		}
   259  	}
   260  	return r
   261  }
   262  
   263  // SetBody method sets the request body for the request. It supports various realtime needs as easy.
   264  // We can say its quite handy or powerful. Supported request body data types is `string`,
   265  // `[]byte`, `struct`, `map`, `slice` and `io.Reader`. Body value can be pointer or non-pointer.
   266  // Automatic marshalling for JSON and XML content type, if it is `struct`, `map`, or `slice`.
   267  //
   268  // Note: `io.Reader` is processed as bufferless mode while sending request.
   269  //
   270  // For Example: Struct as a body input, based on content type, it will be marshalled.
   271  //
   272  //	client.R().
   273  //		SetBody(User{
   274  //			Username: "jeeva@myjeeva.com",
   275  //			Password: "welcome2resty",
   276  //		})
   277  //
   278  // Map as a body input, based on content type, it will be marshalled.
   279  //
   280  //	client.R().
   281  //		SetBody(map[string]interface{}{
   282  //			"username": "jeeva@myjeeva.com",
   283  //			"password": "welcome2resty",
   284  //			"address": &Address{
   285  //				Address1: "1111 This is my street",
   286  //				Address2: "Apt 201",
   287  //				City: "My City",
   288  //				State: "My State",
   289  //				ZipCode: 00000,
   290  //			},
   291  //		})
   292  //
   293  // String as a body input. Suitable for any need as a string input.
   294  //
   295  //	client.R().
   296  //		SetBody(`{
   297  //			"username": "jeeva@getrightcare.com",
   298  //			"password": "admin"
   299  //		}`)
   300  //
   301  // []byte as a body input. Suitable for raw request such as file upload, serialize & deserialize, etc.
   302  //
   303  //	client.R().
   304  //		SetBody([]byte("This is my raw request, sent as-is"))
   305  func (r *Request) SetBody(body interface{}) *Request {
   306  	r.Body = body
   307  	return r
   308  }
   309  
   310  // SetResult method is to register the response `Result` object for automatic unmarshalling for the request,
   311  // if response status code is between 200 and 299 and content type either JSON or XML.
   312  //
   313  // Note: Result object can be pointer or non-pointer.
   314  //
   315  //	client.R().SetResult(&AuthToken{})
   316  //	// OR
   317  //	client.R().SetResult(AuthToken{})
   318  //
   319  // Accessing a result value from response instance.
   320  //
   321  //	response.Result().(*AuthToken)
   322  func (r *Request) SetResult(res interface{}) *Request {
   323  	r.Result = getPointer(res)
   324  	return r
   325  }
   326  
   327  // SetError method is to register the request `Error` object for automatic unmarshalling for the request,
   328  // if response status code is greater than 399 and content type either JSON or XML.
   329  //
   330  // Note: Error object can be pointer or non-pointer.
   331  //
   332  //	client.R().SetError(&AuthError{})
   333  //	// OR
   334  //	client.R().SetError(AuthError{})
   335  //
   336  // Accessing a error value from response instance.
   337  //
   338  //	response.Error().(*AuthError)
   339  func (r *Request) SetError(err interface{}) *Request {
   340  	r.Error = getPointer(err)
   341  	return r
   342  }
   343  
   344  // SetFile method is to set single file field name and its path for multipart upload.
   345  //
   346  //	client.R().
   347  //		SetFile("my_file", "/Users/jeeva/Gas Bill - Sep.pdf")
   348  func (r *Request) SetFile(param, filePath string) *Request {
   349  	r.isMultiPart = true
   350  	r.FormData.Set("@"+param, filePath)
   351  	return r
   352  }
   353  
   354  // SetFiles method is to set multiple file field name and its path for multipart upload.
   355  //
   356  //	client.R().
   357  //		SetFiles(map[string]string{
   358  //				"my_file1": "/Users/jeeva/Gas Bill - Sep.pdf",
   359  //				"my_file2": "/Users/jeeva/Electricity Bill - Sep.pdf",
   360  //				"my_file3": "/Users/jeeva/Water Bill - Sep.pdf",
   361  //			})
   362  func (r *Request) SetFiles(files map[string]string) *Request {
   363  	r.isMultiPart = true
   364  	for f, fp := range files {
   365  		r.FormData.Set("@"+f, fp)
   366  	}
   367  	return r
   368  }
   369  
   370  // SetFileReader method is to set single file using io.Reader for multipart upload.
   371  //
   372  //	client.R().
   373  //		SetFileReader("profile_img", "my-profile-img.png", bytes.NewReader(profileImgBytes)).
   374  //		SetFileReader("notes", "user-notes.txt", bytes.NewReader(notesBytes))
   375  func (r *Request) SetFileReader(param, fileName string, reader io.Reader) *Request {
   376  	r.isMultiPart = true
   377  	r.multipartFiles = append(r.multipartFiles, &File{
   378  		Name:      fileName,
   379  		ParamName: param,
   380  		Reader:    reader,
   381  	})
   382  	return r
   383  }
   384  
   385  // SetMultipartFormData method allows simple form data to be attached to the request as `multipart:form-data`
   386  func (r *Request) SetMultipartFormData(data map[string]string) *Request {
   387  	for k, v := range data {
   388  		r = r.SetMultipartField(k, "", "", strings.NewReader(v))
   389  	}
   390  
   391  	return r
   392  }
   393  
   394  // SetMultipartField method is to set custom data using io.Reader for multipart upload.
   395  func (r *Request) SetMultipartField(param, fileName, contentType string, reader io.Reader) *Request {
   396  	r.isMultiPart = true
   397  	r.multipartFields = append(r.multipartFields, &MultipartField{
   398  		Param:       param,
   399  		FileName:    fileName,
   400  		ContentType: contentType,
   401  		Reader:      reader,
   402  	})
   403  	return r
   404  }
   405  
   406  // SetMultipartFields method is to set multiple data fields using io.Reader for multipart upload.
   407  //
   408  // For Example:
   409  //
   410  //	client.R().SetMultipartFields(
   411  //		&resty.MultipartField{
   412  //			Param:       "uploadManifest1",
   413  //			FileName:    "upload-file-1.json",
   414  //			ContentType: "application/json",
   415  //			Reader:      strings.NewReader(`{"input": {"name": "Uploaded document 1", "_filename" : ["file1.txt"]}}`),
   416  //		},
   417  //		&resty.MultipartField{
   418  //			Param:       "uploadManifest2",
   419  //			FileName:    "upload-file-2.json",
   420  //			ContentType: "application/json",
   421  //			Reader:      strings.NewReader(`{"input": {"name": "Uploaded document 2", "_filename" : ["file2.txt"]}}`),
   422  //		})
   423  //
   424  // If you have slice already, then simply call-
   425  //
   426  //	client.R().SetMultipartFields(fields...)
   427  func (r *Request) SetMultipartFields(fields ...*MultipartField) *Request {
   428  	r.isMultiPart = true
   429  	r.multipartFields = append(r.multipartFields, fields...)
   430  	return r
   431  }
   432  
   433  // SetContentLength method sets the HTTP header `Content-Length` value for current request.
   434  // By default Resty won't set `Content-Length`. Also you have an option to enable for every
   435  // request.
   436  //
   437  // See `Client.SetContentLength`
   438  //
   439  //	client.R().SetContentLength(true)
   440  func (r *Request) SetContentLength(l bool) *Request {
   441  	r.setContentLength = l
   442  	return r
   443  }
   444  
   445  // SetBasicAuth method sets the basic authentication header in the current HTTP request.
   446  //
   447  // For Example:
   448  //
   449  //	Authorization: Basic <base64-encoded-value>
   450  //
   451  // To set the header for username "go-resty" and password "welcome"
   452  //
   453  //	client.R().SetBasicAuth("go-resty", "welcome")
   454  //
   455  // This method overrides the credentials set by method `Client.SetBasicAuth`.
   456  func (r *Request) SetBasicAuth(username, password string) *Request {
   457  	r.UserInfo = &User{Username: username, Password: password}
   458  	return r
   459  }
   460  
   461  // SetAuthToken method sets the auth token header(Default Scheme: Bearer) in the current HTTP request. Header example:
   462  //
   463  //	Authorization: Bearer <auth-token-value-comes-here>
   464  //
   465  // For Example: To set auth token BC594900518B4F7EAC75BD37F019E08FBC594900518B4F7EAC75BD37F019E08F
   466  //
   467  //	client.R().SetAuthToken("BC594900518B4F7EAC75BD37F019E08FBC594900518B4F7EAC75BD37F019E08F")
   468  //
   469  // This method overrides the Auth token set by method `Client.SetAuthToken`.
   470  func (r *Request) SetAuthToken(token string) *Request {
   471  	r.Token = token
   472  	return r
   473  }
   474  
   475  // SetAuthScheme method sets the auth token scheme type in the HTTP request. For Example:
   476  //
   477  //	Authorization: <auth-scheme-value-set-here> <auth-token-value>
   478  //
   479  // For Example: To set the scheme to use OAuth
   480  //
   481  //	client.R().SetAuthScheme("OAuth")
   482  //
   483  // This auth header scheme gets added to all the request rasied from this client instance.
   484  // Also it can be overridden or set one at the request level is supported.
   485  //
   486  // Information about Auth schemes can be found in RFC7235 which is linked to below along with the page containing
   487  // the currently defined official authentication schemes:
   488  //
   489  //	https://tools.ietf.org/html/rfc7235
   490  //	https://www.iana.org/assignments/http-authschemes/http-authschemes.xhtml#authschemes
   491  //
   492  // This method overrides the Authorization scheme set by method `Client.SetAuthScheme`.
   493  func (r *Request) SetAuthScheme(scheme string) *Request {
   494  	r.AuthScheme = scheme
   495  	return r
   496  }
   497  
   498  // SetOutput method sets the output file for current HTTP request. Current HTTP response will be
   499  // saved into given file. It is similar to `curl -o` flag. Absolute path or relative path can be used.
   500  // If is it relative path then output file goes under the output directory, as mentioned
   501  // in the `Client.SetOutputDirectory`.
   502  //
   503  //	client.R().
   504  //		SetOutput("/Users/jeeva/Downloads/ReplyWithHeader-v5.1-beta.zip").
   505  //		Get("http://bit.ly/1LouEKr")
   506  //
   507  // Note: In this scenario `Response.Body` might be nil.
   508  func (r *Request) SetOutput(file string) *Request {
   509  	r.outputFile = file
   510  	r.isSaveResponse = true
   511  	return r
   512  }
   513  
   514  // SetSRV method sets the details to query the service SRV record and execute the
   515  // request.
   516  //
   517  //	client.R().
   518  //		SetSRV(SRVRecord{"web", "testservice.com"}).
   519  //		Get("/get")
   520  func (r *Request) SetSRV(srv *SRVRecord) *Request {
   521  	r.SRV = srv
   522  	return r
   523  }
   524  
   525  // SetDoNotParseResponse method instructs `Resty` not to parse the response body automatically.
   526  // Resty exposes the raw response body as `io.ReadCloser`. Also do not forget to close the body,
   527  // otherwise you might get into connection leaks, no connection reuse.
   528  //
   529  // Note: Response middlewares are not applicable, if you use this option. Basically you have
   530  // taken over the control of response parsing from `Resty`.
   531  func (r *Request) SetDoNotParseResponse(parse bool) *Request {
   532  	r.notParseResponse = parse
   533  	return r
   534  }
   535  
   536  // SetPathParam method sets single URL path key-value pair in the
   537  // Resty current request instance.
   538  //
   539  //	client.R().SetPathParam("userId", "sample@sample.com")
   540  //
   541  //	Result:
   542  //	   URL - /v1/users/{userId}/details
   543  //	   Composed URL - /v1/users/sample@sample.com/details
   544  //
   545  // It replaces the value of the key while composing the request URL. Also you can
   546  // override Path Params value, which was set at client instance level.
   547  func (r *Request) SetPathParam(param, value string) *Request {
   548  	r.PathParams[param] = value
   549  	return r
   550  }
   551  
   552  // SetPathParams method sets multiple URL path key-value pairs at one go in the
   553  // Resty current request instance.
   554  //
   555  //	client.R().SetPathParams(map[string]string{
   556  //	   "userId": "sample@sample.com",
   557  //	   "subAccountId": "100002",
   558  //	})
   559  //
   560  //	Result:
   561  //	   URL - /v1/users/{userId}/{subAccountId}/details
   562  //	   Composed URL - /v1/users/sample@sample.com/100002/details
   563  //
   564  // It replaces the value of the key while composing request URL. Also you can
   565  // override Path Params value, which was set at client instance level.
   566  func (r *Request) SetPathParams(params map[string]string) *Request {
   567  	for p, v := range params {
   568  		r.SetPathParam(p, v)
   569  	}
   570  	return r
   571  }
   572  
   573  // ExpectContentType method allows to provide fallback `Content-Type` for automatic unmarshalling
   574  // when `Content-Type` response header is unavailable.
   575  func (r *Request) ExpectContentType(contentType string) *Request {
   576  	r.fallbackContentType = contentType
   577  	return r
   578  }
   579  
   580  // ForceContentType method provides a strong sense of response `Content-Type` for automatic unmarshalling.
   581  // Resty gives this a higher priority than the `Content-Type` response header.  This means that if both
   582  // `Request.ForceContentType` is set and the response `Content-Type` is available, `ForceContentType` will win.
   583  func (r *Request) ForceContentType(contentType string) *Request {
   584  	r.forceContentType = contentType
   585  	return r
   586  }
   587  
   588  // SetJSONEscapeHTML method is to enable/disable the HTML escape on JSON marshal.
   589  //
   590  // Note: This option only applicable to standard JSON Marshaller.
   591  func (r *Request) SetJSONEscapeHTML(b bool) *Request {
   592  	r.jsonEscapeHTML = b
   593  	return r
   594  }
   595  
   596  // SetCookie method appends a single cookie in the current request instance.
   597  //
   598  //	client.R().SetCookie(&http.Cookie{
   599  //				Name:"go-resty",
   600  //				Value:"This is cookie value",
   601  //			})
   602  //
   603  // Note: Method appends the Cookie value into existing Cookie if already existing.
   604  func (r *Request) SetCookie(hc *http.Cookie) *Request {
   605  	r.Cookies = append(r.Cookies, hc)
   606  	return r
   607  }
   608  
   609  // SetCookies method sets an array of cookies in the current request instance.
   610  //
   611  //	cookies := []*http.Cookie{
   612  //		&http.Cookie{
   613  //			Name:"go-resty-1",
   614  //			Value:"This is cookie 1 value",
   615  //		},
   616  //		&http.Cookie{
   617  //			Name:"go-resty-2",
   618  //			Value:"This is cookie 2 value",
   619  //		},
   620  //	}
   621  //
   622  //	// Setting a cookies into resty's current request
   623  //	client.R().SetCookies(cookies)
   624  //
   625  // Note: Method appends the Cookie value into existing Cookie if already existing.
   626  func (r *Request) SetCookies(rs []*http.Cookie) *Request {
   627  	r.Cookies = append(r.Cookies, rs...)
   628  	return r
   629  }
   630  
   631  // AddRetryCondition method adds a retry condition function to the request's
   632  // array of functions that are checked to determine if the request is retried.
   633  // The request will retry if any of the functions return true and error is nil.
   634  //
   635  // Note: These retry conditions are checked before all retry conditions of the client.
   636  func (r *Request) AddRetryCondition(condition RetryConditionFunc) *Request {
   637  	r.retryConditions = append(r.retryConditions, condition)
   638  	return r
   639  }
   640  
   641  //‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
   642  // HTTP request tracing
   643  //_______________________________________________________________________
   644  
   645  // SetTrace method enables trace for the current request
   646  // using `httptrace.ClientTrace` and provides insights.
   647  //
   648  //	client := resty.New()
   649  //
   650  //	resp, err := client.R().SetTrace(true).Get("https://httpbin.org/get")
   651  //	fmt.Println("Error:", err)
   652  //	fmt.Println("Trace Info:", resp.Request.TraceInfo())
   653  //
   654  // See `Client.SetTrace` available too to get trace info for all requests.
   655  func (r *Request) SetTrace(trace bool) *Request {
   656  	r.trace = trace
   657  	return r
   658  }
   659  
   660  // TraceInfo method returns the trace info for the request.
   661  // If either the Client or Request SetTrace function has not been called
   662  // prior to the request being made, an empty TraceInfo object will be returned.
   663  func (r *Request) TraceInfo() TraceInfo {
   664  	ct := r.clientTrace
   665  
   666  	if ct == nil {
   667  		return TraceInfo{}
   668  	}
   669  
   670  	ti := TraceInfo{
   671  		DNSLookup:      ct.dnsDone.Sub(ct.dnsStart),
   672  		TLSHandshake:   ct.tlsHandshakeDone.Sub(ct.tlsHandshakeStart),
   673  		ServerTime:     ct.gotFirstResponseByte.Sub(ct.gotConn),
   674  		IsConnReused:   ct.gotConnInfo.Reused,
   675  		IsConnWasIdle:  ct.gotConnInfo.WasIdle,
   676  		ConnIdleTime:   ct.gotConnInfo.IdleTime,
   677  		RequestAttempt: r.Attempt,
   678  	}
   679  
   680  	// Calculate the total time accordingly,
   681  	// when connection is reused
   682  	if ct.gotConnInfo.Reused {
   683  		ti.TotalTime = ct.endTime.Sub(ct.getConn)
   684  	} else {
   685  		ti.TotalTime = ct.endTime.Sub(ct.dnsStart)
   686  	}
   687  
   688  	// Only calculate on successful connections
   689  	if !ct.connectDone.IsZero() {
   690  		ti.TCPConnTime = ct.connectDone.Sub(ct.dnsDone)
   691  	}
   692  
   693  	// Only calculate on successful connections
   694  	if !ct.gotConn.IsZero() {
   695  		ti.ConnTime = ct.gotConn.Sub(ct.getConn)
   696  	}
   697  
   698  	// Only calculate on successful connections
   699  	if !ct.gotFirstResponseByte.IsZero() {
   700  		ti.ResponseTime = ct.endTime.Sub(ct.gotFirstResponseByte)
   701  	}
   702  
   703  	// Capture remote address info when connection is non-nil
   704  	if ct.gotConnInfo.Conn != nil {
   705  		ti.RemoteAddr = ct.gotConnInfo.Conn.RemoteAddr()
   706  	}
   707  
   708  	return ti
   709  }
   710  
   711  //‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
   712  // HTTP verb method starts here
   713  //_______________________________________________________________________
   714  
   715  // Get method does GET HTTP request. It's defined in section 4.3.1 of RFC7231.
   716  func (r *Request) Get(url string) (*Response, error) { return r.Execute(http.MethodGet, url) }
   717  
   718  // Head method does HEAD HTTP request. It's defined in section 4.3.2 of RFC7231.
   719  func (r *Request) Head(url string) (*Response, error) { return r.Execute(http.MethodHead, url) }
   720  
   721  // Post method does POST HTTP request. It's defined in section 4.3.3 of RFC7231.
   722  func (r *Request) Post(url string) (*Response, error) { return r.Execute(http.MethodPost, url) }
   723  
   724  // Put method does PUT HTTP request. It's defined in section 4.3.4 of RFC7231.
   725  func (r *Request) Put(url string) (*Response, error) { return r.Execute(http.MethodPut, url) }
   726  
   727  // Delete method does DELETE HTTP request. It's defined in section 4.3.5 of RFC7231.
   728  func (r *Request) Delete(url string) (*Response, error) { return r.Execute(http.MethodDelete, url) }
   729  
   730  // Options method does OPTIONS HTTP request. It's defined in section 4.3.7 of RFC7231.
   731  func (r *Request) Options(url string) (*Response, error) { return r.Execute(http.MethodOptions, url) }
   732  
   733  // Patch method does PATCH HTTP request. It's defined in section 2 of RFC5789.
   734  func (r *Request) Patch(url string) (*Response, error) { return r.Execute(http.MethodPatch, url) }
   735  
   736  // Send method performs the HTTP request using the method and URL already defined
   737  // for current `Request`.
   738  //
   739  //	     req := client.R()
   740  //	     req.Method = resty.GET
   741  //	     req.URL = "http://httpbin.org/get"
   742  //			resp, err := client.R().Send()
   743  func (r *Request) Send() (*Response, error) { return r.Execute(r.Method, r.URL) }
   744  
   745  // Execute method performs the HTTP request with given HTTP method and URL
   746  // for current `Request`.
   747  //
   748  //	resp, err := client.R().Execute(resty.GET, "http://httpbin.org/get")
   749  func (r *Request) Execute(method, url string) (*Response, error) {
   750  	var addrs []*net.SRV
   751  	var resp *Response
   752  	var err error
   753  
   754  	if r.isMultiPart && !(method == http.MethodPost || method == http.MethodPut || method == http.MethodPatch) {
   755  		// No OnError hook here since this is a request validation error
   756  		return nil, fmt.Errorf("multipart content is not allowed in HTTP verb [%v]", method)
   757  	}
   758  
   759  	if r.SRV != nil {
   760  		_, addrs, err = net.LookupSRV(r.SRV.Service, "tcp", r.SRV.Domain)
   761  		if err != nil {
   762  			r.client.onErrorHooks(r, nil, err)
   763  			return nil, err
   764  		}
   765  	}
   766  
   767  	r.Method = method
   768  	r.URL = r.selectAddr(addrs, url, 0)
   769  
   770  	if r.client.RetryCount == 0 {
   771  		r.Attempt = 1
   772  		resp, err = r.client.execute(r)
   773  		r.client.onErrorHooks(r, resp, unwrapNoRetryErr(err))
   774  		return resp, unwrapNoRetryErr(err)
   775  	}
   776  
   777  	err = Backoff(
   778  		func() (*Response, error) {
   779  			r.Attempt++
   780  
   781  			r.URL = r.selectAddr(addrs, url, r.Attempt)
   782  
   783  			resp, err = r.client.execute(r)
   784  			if err != nil {
   785  				r.client.log.Errorf("%v, Attempt %v", err, r.Attempt)
   786  			}
   787  
   788  			return resp, err
   789  		},
   790  		Retries(r.client.RetryCount),
   791  		WaitTime(r.client.RetryWaitTime),
   792  		MaxWaitTime(r.client.RetryMaxWaitTime),
   793  		RetryConditions(append(r.retryConditions, r.client.RetryConditions...)),
   794  		RetryHooks(r.client.RetryHooks),
   795  	)
   796  
   797  	r.client.onErrorHooks(r, resp, unwrapNoRetryErr(err))
   798  
   799  	return resp, unwrapNoRetryErr(err)
   800  }
   801  
   802  //‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
   803  // SRVRecord struct
   804  //_______________________________________________________________________
   805  
   806  // SRVRecord struct holds the data to query the SRV record for the
   807  // following service.
   808  type SRVRecord struct {
   809  	Service string
   810  	Domain  string
   811  }
   812  
   813  //‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
   814  // Request Unexported methods
   815  //_______________________________________________________________________
   816  
   817  func (r *Request) fmtBodyString(sl int64) (body string) {
   818  	body = "***** NO CONTENT *****"
   819  	if !isPayloadSupported(r.Method, r.client.AllowGetMethodPayload) {
   820  		return
   821  	}
   822  
   823  	if _, ok := r.Body.(io.Reader); ok {
   824  		body = "***** BODY IS io.Reader *****"
   825  		return
   826  	}
   827  
   828  	// multipart or form-data
   829  	if r.isMultiPart || r.isFormData {
   830  		bodySize := int64(r.bodyBuf.Len())
   831  		if bodySize > sl {
   832  			body = fmt.Sprintf("***** REQUEST TOO LARGE (size - %d) *****", bodySize)
   833  			return
   834  		}
   835  		body = r.bodyBuf.String()
   836  		return
   837  	}
   838  
   839  	// request body data
   840  	if r.Body == nil {
   841  		return
   842  	}
   843  	var prtBodyBytes []byte
   844  	var err error
   845  
   846  	contentType := r.Header.Get(hdrContentTypeKey)
   847  	kind := kindOf(r.Body)
   848  	if canJSONMarshal(contentType, kind) {
   849  		prtBodyBytes, err = json.MarshalIndent(&r.Body, "", "   ")
   850  	} else if IsXMLType(contentType) && (kind == reflect.Struct) {
   851  		prtBodyBytes, err = xml.MarshalIndent(&r.Body, "", "   ")
   852  	} else if b, ok := r.Body.(string); ok {
   853  		if IsJSONType(contentType) {
   854  			bodyBytes := []byte(b)
   855  			out := acquireBuffer()
   856  			defer releaseBuffer(out)
   857  			if err = json.Indent(out, bodyBytes, "", "   "); err == nil {
   858  				prtBodyBytes = out.Bytes()
   859  			}
   860  		} else {
   861  			body = b
   862  		}
   863  	} else if b, ok := r.Body.([]byte); ok {
   864  		body = fmt.Sprintf("***** BODY IS byte(s) (size - %d) *****", len(b))
   865  		return
   866  	}
   867  
   868  	if prtBodyBytes != nil && err == nil {
   869  		body = string(prtBodyBytes)
   870  	}
   871  
   872  	if len(body) > 0 {
   873  		bodySize := int64(len([]byte(body)))
   874  		if bodySize > sl {
   875  			body = fmt.Sprintf("***** REQUEST TOO LARGE (size - %d) *****", bodySize)
   876  		}
   877  	}
   878  
   879  	return
   880  }
   881  
   882  func (r *Request) selectAddr(addrs []*net.SRV, path string, attempt int) string {
   883  	if addrs == nil {
   884  		return path
   885  	}
   886  
   887  	idx := attempt % len(addrs)
   888  	domain := strings.TrimRight(addrs[idx].Target, ".")
   889  	path = strings.TrimLeft(path, "/")
   890  
   891  	return fmt.Sprintf("%s://%s:%d/%s", r.client.scheme, domain, addrs[idx].Port, path)
   892  }
   893  
   894  func (r *Request) initValuesMap() {
   895  	if r.values == nil {
   896  		r.values = make(map[string]interface{})
   897  	}
   898  }
   899  
   900  var noescapeJSONMarshal = func(v interface{}) (*bytes.Buffer, error) {
   901  	buf := acquireBuffer()
   902  	encoder := json.NewEncoder(buf)
   903  	encoder.SetEscapeHTML(false)
   904  	if err := encoder.Encode(v); err != nil {
   905  		releaseBuffer(buf)
   906  		return nil, err
   907  	}
   908  
   909  	return buf, nil
   910  }