request.go raw

   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  	"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 requests from
  27  // Resty client. The [Request] provides an option to override client-level
  28  // settings and also an option 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  	RawPathParams map[string]string
  38  	Header        http.Header
  39  	Time          time.Time
  40  	Body          interface{}
  41  	Result        interface{}
  42  	resultCurlCmd *string
  43  	Error         interface{}
  44  	RawRequest    *http.Request
  45  	SRV           *SRVRecord
  46  	UserInfo      *User
  47  	Cookies       []*http.Cookie
  48  	Debug         bool
  49  
  50  	// Attempt is to represent the request attempt made during a Resty
  51  	// request execution flow, including retry count.
  52  	Attempt int
  53  
  54  	isMultiPart         bool
  55  	isFormData          bool
  56  	setContentLength    bool
  57  	isSaveResponse      bool
  58  	notParseResponse    bool
  59  	jsonEscapeHTML      bool
  60  	trace               bool
  61  	outputFile          string
  62  	fallbackContentType string
  63  	forceContentType    string
  64  	ctx                 context.Context
  65  	values              map[string]interface{}
  66  	client              *Client
  67  	bodyBuf             *bytes.Buffer
  68  	clientTrace         *clientTrace
  69  	log                 Logger
  70  	multipartBoundary   string
  71  	multipartFiles      []*File
  72  	multipartFields     []*MultipartField
  73  	retryConditions     []RetryConditionFunc
  74  	responseBodyLimit   int
  75  	generateCurlOnDebug bool
  76  	unescapeQueryParams bool
  77  }
  78  
  79  // GenerateCurlCommand method generates the CURL command for the request.
  80  func (r *Request) GenerateCurlCommand() string {
  81  	if !(r.Debug && r.generateCurlOnDebug) {
  82  		return ""
  83  	}
  84  
  85  	if r.resultCurlCmd != nil {
  86  		return *r.resultCurlCmd
  87  	}
  88  
  89  	if r.RawRequest == nil {
  90  		r.client.executeBefore(r) // mock with r.Get("/")
  91  	}
  92  	if r.resultCurlCmd == nil {
  93  		r.resultCurlCmd = new(string)
  94  	}
  95  	*r.resultCurlCmd = buildCurlRequest(r.RawRequest, r.client.httpClient.Jar)
  96  	return *r.resultCurlCmd
  97  }
  98  
  99  // Context method returns the Context if it is already set in the [Request]
 100  // otherwise, it creates a new one using [context.Background].
 101  func (r *Request) Context() context.Context {
 102  	if r.ctx == nil {
 103  		return context.Background()
 104  	}
 105  	return r.ctx
 106  }
 107  
 108  // SetContext method sets the [context.Context] for current [Request]. It allows
 109  // to interrupt the request execution if `ctx.Done()` channel is closed.
 110  // See https://blog.golang.org/context article and the package [context]
 111  // documentation.
 112  func (r *Request) SetContext(ctx context.Context) *Request {
 113  	r.ctx = ctx
 114  	return r
 115  }
 116  
 117  // SetHeader method sets a single header field and its value in the current request.
 118  //
 119  // For Example: To set `Content-Type` and `Accept` as `application/json`.
 120  //
 121  //	client.R().
 122  //		SetHeader("Content-Type", "application/json").
 123  //		SetHeader("Accept", "application/json")
 124  //
 125  // It overrides the header value set at the client instance level.
 126  func (r *Request) SetHeader(header, value string) *Request {
 127  	r.Header.Set(header, value)
 128  	return r
 129  }
 130  
 131  // SetHeaders method sets multiple header fields and their values at one go in the current request.
 132  //
 133  // For Example: To set `Content-Type` and `Accept` as `application/json`
 134  //
 135  //	client.R().
 136  //		SetHeaders(map[string]string{
 137  //			"Content-Type": "application/json",
 138  //			"Accept": "application/json",
 139  //		})
 140  //
 141  // It overrides the header value set at the client instance level.
 142  func (r *Request) SetHeaders(headers map[string]string) *Request {
 143  	for h, v := range headers {
 144  		r.SetHeader(h, v)
 145  	}
 146  	return r
 147  }
 148  
 149  // SetHeaderMultiValues sets multiple header fields and their values as a list of strings in the current request.
 150  //
 151  // For Example: To set `Accept` as `text/html, application/xhtml+xml, application/xml;q=0.9, image/webp, */*;q=0.8`
 152  //
 153  //	client.R().
 154  //		SetHeaderMultiValues(map[string][]string{
 155  //			"Accept": []string{"text/html", "application/xhtml+xml", "application/xml;q=0.9", "image/webp", "*/*;q=0.8"},
 156  //		})
 157  //
 158  // It overrides the header value set at the client instance level.
 159  func (r *Request) SetHeaderMultiValues(headers map[string][]string) *Request {
 160  	for key, values := range headers {
 161  		r.SetHeader(key, strings.Join(values, ", "))
 162  	}
 163  	return r
 164  }
 165  
 166  // SetHeaderVerbatim method sets a single header field and its value verbatim in the current request.
 167  //
 168  // For Example: To set `all_lowercase` and `UPPERCASE` as `available`.
 169  //
 170  //	client.R().
 171  //		SetHeaderVerbatim("all_lowercase", "available").
 172  //		SetHeaderVerbatim("UPPERCASE", "available")
 173  //
 174  // It overrides the header value set at the client instance level.
 175  func (r *Request) SetHeaderVerbatim(header, value string) *Request {
 176  	r.Header[header] = []string{value}
 177  	return r
 178  }
 179  
 180  // SetQueryParam method sets a single parameter and its value in the current request.
 181  // It will be formed as a query string for the request.
 182  //
 183  // For Example: `search=kitchen%20papers&size=large` in the URL after the `?` mark.
 184  //
 185  //	client.R().
 186  //		SetQueryParam("search", "kitchen papers").
 187  //		SetQueryParam("size", "large")
 188  //
 189  // It overrides the query parameter value set at the client instance level.
 190  func (r *Request) SetQueryParam(param, value string) *Request {
 191  	r.QueryParam.Set(param, value)
 192  	return r
 193  }
 194  
 195  // SetQueryParams method sets multiple parameters and their values at one go in the current request.
 196  // It will be formed as a query string for the request.
 197  //
 198  // For Example: `search=kitchen%20papers&size=large` in the URL after the `?` mark.
 199  //
 200  //	client.R().
 201  //		SetQueryParams(map[string]string{
 202  //			"search": "kitchen papers",
 203  //			"size": "large",
 204  //		})
 205  //
 206  // It overrides the query parameter value set at the client instance level.
 207  func (r *Request) SetQueryParams(params map[string]string) *Request {
 208  	for p, v := range params {
 209  		r.SetQueryParam(p, v)
 210  	}
 211  	return r
 212  }
 213  
 214  // SetUnescapeQueryParams method sets the unescape query parameters choice for request URL.
 215  // To prevent broken URL, resty replaces space (" ") with "+" in the query parameters.
 216  //
 217  // This method overrides the value set by [Client.SetUnescapeQueryParams]
 218  //
 219  // NOTE: Request failure is possible due to non-standard usage of Unescaped Query Parameters.
 220  func (r *Request) SetUnescapeQueryParams(unescape bool) *Request {
 221  	r.unescapeQueryParams = unescape
 222  	return r
 223  }
 224  
 225  // SetQueryParamsFromValues method appends multiple parameters with multi-value
 226  // ([url.Values]) at one go in the current request. It will be formed as
 227  // query string for the request.
 228  //
 229  // For Example: `status=pending&status=approved&status=open` in the URL after the `?` mark.
 230  //
 231  //	client.R().
 232  //		SetQueryParamsFromValues(url.Values{
 233  //			"status": []string{"pending", "approved", "open"},
 234  //		})
 235  //
 236  // It overrides the query parameter value set at the client instance level.
 237  func (r *Request) SetQueryParamsFromValues(params url.Values) *Request {
 238  	for p, v := range params {
 239  		for _, pv := range v {
 240  			r.QueryParam.Add(p, pv)
 241  		}
 242  	}
 243  	return r
 244  }
 245  
 246  // SetQueryString method provides the ability to use string as an input to set URL query string for the request.
 247  //
 248  //	client.R().
 249  //		SetQueryString("productId=232&template=fresh-sample&cat=resty&source=google&kw=buy a lot more")
 250  //
 251  // It overrides the query parameter value set at the client instance level.
 252  func (r *Request) SetQueryString(query string) *Request {
 253  	params, err := url.ParseQuery(strings.TrimSpace(query))
 254  	if err == nil {
 255  		for p, v := range params {
 256  			for _, pv := range v {
 257  				r.QueryParam.Add(p, pv)
 258  			}
 259  		}
 260  	} else {
 261  		r.log.Errorf("%v", err)
 262  	}
 263  	return r
 264  }
 265  
 266  // SetFormData method sets Form parameters and their values for the current request.
 267  // It applies only to HTTP methods `POST` and `PUT`, and by default requests
 268  // content type would be set as `application/x-www-form-urlencoded`.
 269  //
 270  //	client.R().
 271  //		SetFormData(map[string]string{
 272  //			"access_token": "BC594900-518B-4F7E-AC75-BD37F019E08F",
 273  //			"user_id": "3455454545",
 274  //		})
 275  //
 276  // It overrides the form data value set at the client instance level.
 277  func (r *Request) SetFormData(data map[string]string) *Request {
 278  	for k, v := range data {
 279  		r.FormData.Set(k, v)
 280  	}
 281  	return r
 282  }
 283  
 284  // SetFormDataFromValues method appends multiple form parameters with multi-value
 285  // ([url.Values]) at one go in the current request.
 286  //
 287  //	client.R().
 288  //		SetFormDataFromValues(url.Values{
 289  //			"search_criteria": []string{"book", "glass", "pencil"},
 290  //		})
 291  //
 292  // It overrides the form data value set at the client instance level.
 293  func (r *Request) SetFormDataFromValues(data url.Values) *Request {
 294  	for k, v := range data {
 295  		for _, kv := range v {
 296  			r.FormData.Add(k, kv)
 297  		}
 298  	}
 299  	return r
 300  }
 301  
 302  // SetBody method sets the request body for the request. It supports various practical needs as easy.
 303  // It's quite handy and powerful. Supported request body data types are `string`,
 304  // `[]byte`, `struct`, `map`, `slice` and [io.Reader].
 305  //
 306  // Body value can be pointer or non-pointer. Automatic marshalling for JSON and XML content type, if it is `struct`, `map`, or `slice`.
 307  //
 308  // NOTE: [io.Reader] is processed in bufferless mode while sending a request.
 309  //
 310  // For Example:
 311  //
 312  // `struct` gets marshaled based on the request header `Content-Type`.
 313  //
 314  //	client.R().
 315  //		SetBody(User{
 316  //			Username: "jeeva@myjeeva.com",
 317  //			Password: "welcome2resty",
 318  //		})
 319  //
 320  // 'map` gets marshaled based on the request header `Content-Type`.
 321  //
 322  //	client.R().
 323  //		SetBody(map[string]interface{}{
 324  //			"username": "jeeva@myjeeva.com",
 325  //			"password": "welcome2resty",
 326  //			"address": &Address{
 327  //				Address1: "1111 This is my street",
 328  //				Address2: "Apt 201",
 329  //				City: "My City",
 330  //				State: "My State",
 331  //				ZipCode: 00000,
 332  //			},
 333  //		})
 334  //
 335  // `string` as a body input. Suitable for any need as a string input.
 336  //
 337  //	client.R().
 338  //		SetBody(`{
 339  //			"username": "jeeva@getrightcare.com",
 340  //			"password": "admin"
 341  //		}`)
 342  //
 343  // `[]byte` as a body input. Suitable for raw requests such as file upload, serialize & deserialize, etc.
 344  //
 345  //	client.R().
 346  //		SetBody([]byte("This is my raw request, sent as-is"))
 347  //
 348  // and so on.
 349  func (r *Request) SetBody(body interface{}) *Request {
 350  	r.Body = body
 351  	return r
 352  }
 353  
 354  // SetResult method is to register the response `Result` object for automatic
 355  // unmarshalling of the HTTP response if the response status code is
 356  // between 200 and 299, and the content type is JSON or XML.
 357  //
 358  // Note: [Request.SetResult] input can be a pointer or non-pointer.
 359  //
 360  // The pointer with handle
 361  //
 362  //	authToken := &AuthToken{}
 363  //	client.R().SetResult(authToken)
 364  //
 365  //	// Can be accessed via -
 366  //	fmt.Println(authToken) OR fmt.Println(response.Result().(*AuthToken))
 367  //
 368  // OR -
 369  //
 370  // The pointer without handle or non-pointer
 371  //
 372  //	client.R().SetResult(&AuthToken{})
 373  //	// OR
 374  //	client.R().SetResult(AuthToken{})
 375  //
 376  //	// Can be accessed via -
 377  //	fmt.Println(response.Result().(*AuthToken))
 378  func (r *Request) SetResult(res interface{}) *Request {
 379  	if res != nil {
 380  		r.Result = getPointer(res)
 381  	}
 382  	return r
 383  }
 384  
 385  // SetError method is to register the request `Error` object for automatic unmarshalling for the request,
 386  // if the response status code is greater than 399 and the content type is either JSON or XML.
 387  //
 388  // NOTE: [Request.SetError] input can be a pointer or non-pointer.
 389  //
 390  //	client.R().SetError(&AuthError{})
 391  //	// OR
 392  //	client.R().SetError(AuthError{})
 393  //
 394  // Accessing an error value from response instance.
 395  //
 396  //	response.Error().(*AuthError)
 397  //
 398  // If this request Error object is nil, Resty will use the client-level error object Type if it is set.
 399  func (r *Request) SetError(err interface{}) *Request {
 400  	r.Error = getPointer(err)
 401  	return r
 402  }
 403  
 404  // SetFile method sets a single file field name and its path for multipart upload.
 405  //
 406  //	client.R().
 407  //		SetFile("my_file", "/Users/jeeva/Gas Bill - Sep.pdf")
 408  func (r *Request) SetFile(param, filePath string) *Request {
 409  	r.isMultiPart = true
 410  	r.FormData.Set("@"+param, filePath)
 411  	return r
 412  }
 413  
 414  // SetFiles method sets multiple file field names and their paths for multipart uploads.
 415  //
 416  //	client.R().
 417  //		SetFiles(map[string]string{
 418  //				"my_file1": "/Users/jeeva/Gas Bill - Sep.pdf",
 419  //				"my_file2": "/Users/jeeva/Electricity Bill - Sep.pdf",
 420  //				"my_file3": "/Users/jeeva/Water Bill - Sep.pdf",
 421  //			})
 422  func (r *Request) SetFiles(files map[string]string) *Request {
 423  	r.isMultiPart = true
 424  	for f, fp := range files {
 425  		r.FormData.Set("@"+f, fp)
 426  	}
 427  	return r
 428  }
 429  
 430  // SetFileReader method is to set a file using [io.Reader] for multipart upload.
 431  //
 432  //	client.R().
 433  //		SetFileReader("profile_img", "my-profile-img.png", bytes.NewReader(profileImgBytes)).
 434  //		SetFileReader("notes", "user-notes.txt", bytes.NewReader(notesBytes))
 435  func (r *Request) SetFileReader(param, fileName string, reader io.Reader) *Request {
 436  	r.isMultiPart = true
 437  	r.multipartFiles = append(r.multipartFiles, &File{
 438  		Name:      fileName,
 439  		ParamName: param,
 440  		Reader:    reader,
 441  	})
 442  	return r
 443  }
 444  
 445  // SetMultipartFormData method allows simple form data to be attached to the request
 446  // as `multipart:form-data`
 447  func (r *Request) SetMultipartFormData(data map[string]string) *Request {
 448  	for k, v := range data {
 449  		r = r.SetMultipartField(k, "", "", strings.NewReader(v))
 450  	}
 451  
 452  	return r
 453  }
 454  
 455  // SetMultipartField method sets custom data with Content-Type using [io.Reader] for multipart upload.
 456  func (r *Request) SetMultipartField(param, fileName, contentType string, reader io.Reader) *Request {
 457  	r.isMultiPart = true
 458  	r.multipartFields = append(r.multipartFields, &MultipartField{
 459  		Param:       param,
 460  		FileName:    fileName,
 461  		ContentType: contentType,
 462  		Reader:      reader,
 463  	})
 464  	return r
 465  }
 466  
 467  // SetMultipartFields method sets multiple data fields using [io.Reader] for multipart upload.
 468  //
 469  // For Example:
 470  //
 471  //	client.R().SetMultipartFields(
 472  //		&resty.MultipartField{
 473  //			Param:       "uploadManifest1",
 474  //			FileName:    "upload-file-1.json",
 475  //			ContentType: "application/json",
 476  //			Reader:      strings.NewReader(`{"input": {"name": "Uploaded document 1", "_filename" : ["file1.txt"]}}`),
 477  //		},
 478  //		&resty.MultipartField{
 479  //			Param:       "uploadManifest2",
 480  //			FileName:    "upload-file-2.json",
 481  //			ContentType: "application/json",
 482  //			Reader:      strings.NewReader(`{"input": {"name": "Uploaded document 2", "_filename" : ["file2.txt"]}}`),
 483  //		})
 484  //
 485  // If you have a `slice` of fields already, then call-
 486  //
 487  //	client.R().SetMultipartFields(fields...)
 488  func (r *Request) SetMultipartFields(fields ...*MultipartField) *Request {
 489  	r.isMultiPart = true
 490  	r.multipartFields = append(r.multipartFields, fields...)
 491  	return r
 492  }
 493  
 494  // SetMultipartBoundary method sets the custom multipart boundary for the multipart request.
 495  // Typically, the `mime/multipart` package generates a random multipart boundary if not provided.
 496  func (r *Request) SetMultipartBoundary(boundary string) *Request {
 497  	r.multipartBoundary = boundary
 498  	return r
 499  }
 500  
 501  // SetContentLength method sets the current request's HTTP header `Content-Length` value.
 502  // By default, Resty won't set `Content-Length`.
 503  //
 504  // See [Client.SetContentLength]
 505  //
 506  //	client.R().SetContentLength(true)
 507  //
 508  // It overrides the value set at the client instance level.
 509  func (r *Request) SetContentLength(l bool) *Request {
 510  	r.setContentLength = l
 511  	return r
 512  }
 513  
 514  // SetBasicAuth method sets the basic authentication header in the current HTTP request.
 515  //
 516  // For Example:
 517  //
 518  //	Authorization: Basic <base64-encoded-value>
 519  //
 520  // To set the header for username "go-resty" and password "welcome"
 521  //
 522  //	client.R().SetBasicAuth("go-resty", "welcome")
 523  //
 524  // It overrides the credentials set by method [Client.SetBasicAuth].
 525  func (r *Request) SetBasicAuth(username, password string) *Request {
 526  	r.UserInfo = &User{Username: username, Password: password}
 527  	return r
 528  }
 529  
 530  // SetAuthToken method sets the auth token header(Default Scheme: Bearer) in the current HTTP request. Header example:
 531  //
 532  //	Authorization: Bearer <auth-token-value-comes-here>
 533  //
 534  // For Example: To set auth token BC594900518B4F7EAC75BD37F019E08FBC594900518B4F7EAC75BD37F019E08F
 535  //
 536  //	client.R().SetAuthToken("BC594900518B4F7EAC75BD37F019E08FBC594900518B4F7EAC75BD37F019E08F")
 537  //
 538  // It overrides the Auth token set by method [Client.SetAuthToken].
 539  func (r *Request) SetAuthToken(token string) *Request {
 540  	r.Token = token
 541  	return r
 542  }
 543  
 544  // SetAuthScheme method sets the auth token scheme type in the HTTP request.
 545  //
 546  // Example Header value structure:
 547  //
 548  //	Authorization: <auth-scheme-value-set-here> <auth-token-value>
 549  //
 550  // For Example: To set the scheme to use OAuth
 551  //
 552  //	client.R().SetAuthScheme("OAuth")
 553  //
 554  //	// The outcome will be -
 555  //	Authorization: OAuth <auth-token-value>
 556  //
 557  // Information about Auth schemes can be found in [RFC 7235], IANA [HTTP Auth schemes]
 558  //
 559  // It overrides the `Authorization` scheme set by method [Client.SetAuthScheme].
 560  //
 561  // [RFC 7235]: https://tools.ietf.org/html/rfc7235
 562  // [HTTP Auth schemes]: https://www.iana.org/assignments/http-authschemes/http-authschemes.xhtml#authschemes
 563  func (r *Request) SetAuthScheme(scheme string) *Request {
 564  	r.AuthScheme = scheme
 565  	return r
 566  }
 567  
 568  // SetDigestAuth method sets the Digest Access auth scheme for the HTTP request.
 569  // If a server responds with 401 and sends a Digest challenge in the WWW-Authenticate Header,
 570  // the request will be resent with the appropriate Authorization Header.
 571  //
 572  // For Example: To set the Digest scheme with username "Mufasa" and password "Circle Of Life"
 573  //
 574  //	client.R().SetDigestAuth("Mufasa", "Circle Of Life")
 575  //
 576  // Information about Digest Access Authentication can be found in [RFC 7616]
 577  //
 578  // It overrides the digest username and password set by method [Client.SetDigestAuth].
 579  //
 580  // [RFC 7616]: https://datatracker.ietf.org/doc/html/rfc7616
 581  func (r *Request) SetDigestAuth(username, password string) *Request {
 582  	oldTransport := r.client.httpClient.Transport
 583  	r.client.OnBeforeRequest(func(c *Client, _ *Request) error {
 584  		c.httpClient.Transport = &digestTransport{
 585  			digestCredentials: digestCredentials{username, password},
 586  			transport:         oldTransport,
 587  		}
 588  		return nil
 589  	})
 590  	r.client.OnAfterResponse(func(c *Client, _ *Response) error {
 591  		c.httpClient.Transport = oldTransport
 592  		return nil
 593  	})
 594  
 595  	return r
 596  }
 597  
 598  // SetOutput method sets the output file for the current HTTP request. The current
 599  // HTTP response will be saved in the given file. It is similar to the `curl -o` flag.
 600  //
 601  // Absolute path or relative path can be used.
 602  //
 603  // If it is a relative path, then the output file goes under the output directory, as mentioned
 604  // in the [Client.SetOutputDirectory].
 605  //
 606  //	client.R().
 607  //		SetOutput("/Users/jeeva/Downloads/ReplyWithHeader-v5.1-beta.zip").
 608  //		Get("http://bit.ly/1LouEKr")
 609  //
 610  // NOTE: In this scenario [Response.Body] might be nil.
 611  func (r *Request) SetOutput(file string) *Request {
 612  	r.outputFile = file
 613  	r.isSaveResponse = true
 614  	return r
 615  }
 616  
 617  // SetSRV method sets the details to query the service SRV record and execute the
 618  // request.
 619  //
 620  //	client.R().
 621  //		SetSRV(SRVRecord{"web", "testservice.com"}).
 622  //		Get("/get")
 623  func (r *Request) SetSRV(srv *SRVRecord) *Request {
 624  	r.SRV = srv
 625  	return r
 626  }
 627  
 628  // SetDoNotParseResponse method instructs Resty not to parse the response body automatically.
 629  // Resty exposes the raw response body as [io.ReadCloser]. If you use it, do not
 630  // forget to close the body, otherwise, you might get into connection leaks, and connection
 631  // reuse may not happen.
 632  //
 633  // NOTE: [Response] middlewares are not executed using this option. You have
 634  // taken over the control of response parsing from Resty.
 635  func (r *Request) SetDoNotParseResponse(parse bool) *Request {
 636  	r.notParseResponse = parse
 637  	return r
 638  }
 639  
 640  // SetResponseBodyLimit method sets a maximum body size limit in bytes on response,
 641  // avoid reading too much data to memory.
 642  //
 643  // Client will return [resty.ErrResponseBodyTooLarge] if the body size of the body
 644  // in the uncompressed response is larger than the limit.
 645  // Body size limit will not be enforced in the following cases:
 646  //   - ResponseBodyLimit <= 0, which is the default behavior.
 647  //   - [Request.SetOutput] is called to save response data to the file.
 648  //   - "DoNotParseResponse" is set for client or request.
 649  //
 650  // It overrides the value set at the client instance level. see [Client.SetResponseBodyLimit]
 651  func (r *Request) SetResponseBodyLimit(v int) *Request {
 652  	r.responseBodyLimit = v
 653  	return r
 654  }
 655  
 656  // SetPathParam method sets a single URL path key-value pair in the
 657  // Resty current request instance.
 658  //
 659  //	client.R().SetPathParam("userId", "sample@sample.com")
 660  //
 661  //	Result:
 662  //	   URL - /v1/users/{userId}/details
 663  //	   Composed URL - /v1/users/sample@sample.com/details
 664  //
 665  //	client.R().SetPathParam("path", "groups/developers")
 666  //
 667  //	Result:
 668  //	   URL - /v1/users/{userId}/details
 669  //	   Composed URL - /v1/users/groups%2Fdevelopers/details
 670  //
 671  // It replaces the value of the key while composing the request URL.
 672  // The values will be escaped using function [url.PathEscape].
 673  //
 674  // It overrides the path parameter set at the client instance level.
 675  func (r *Request) SetPathParam(param, value string) *Request {
 676  	r.PathParams[param] = value
 677  	return r
 678  }
 679  
 680  // SetPathParams method sets multiple URL path key-value pairs at one go in the
 681  // Resty current request instance.
 682  //
 683  //	client.R().SetPathParams(map[string]string{
 684  //		"userId":       "sample@sample.com",
 685  //		"subAccountId": "100002",
 686  //		"path":         "groups/developers",
 687  //	})
 688  //
 689  //	Result:
 690  //	   URL - /v1/users/{userId}/{subAccountId}/{path}/details
 691  //	   Composed URL - /v1/users/sample@sample.com/100002/groups%2Fdevelopers/details
 692  //
 693  // It replaces the value of the key while composing the request URL.
 694  // The values will be escaped using function [url.PathEscape].
 695  //
 696  // It overrides the path parameter set at the client instance level.
 697  func (r *Request) SetPathParams(params map[string]string) *Request {
 698  	for p, v := range params {
 699  		r.SetPathParam(p, v)
 700  	}
 701  	return r
 702  }
 703  
 704  // SetRawPathParam method sets a single URL path key-value pair in the
 705  // Resty current request instance.
 706  //
 707  //	client.R().SetPathParam("userId", "sample@sample.com")
 708  //
 709  //	Result:
 710  //	   URL - /v1/users/{userId}/details
 711  //	   Composed URL - /v1/users/sample@sample.com/details
 712  //
 713  //	client.R().SetPathParam("path", "groups/developers")
 714  //
 715  //	Result:
 716  //	   URL - /v1/users/{userId}/details
 717  //	   Composed URL - /v1/users/groups/developers/details
 718  //
 719  // It replaces the value of the key while composing the request URL.
 720  // The value will be used as-is and has not been escaped.
 721  //
 722  // It overrides the raw path parameter set at the client instance level.
 723  func (r *Request) SetRawPathParam(param, value string) *Request {
 724  	r.RawPathParams[param] = value
 725  	return r
 726  }
 727  
 728  // SetRawPathParams method sets multiple URL path key-value pairs at one go in the
 729  // Resty current request instance.
 730  //
 731  //	client.R().SetPathParams(map[string]string{
 732  //		"userId": "sample@sample.com",
 733  //		"subAccountId": "100002",
 734  //		"path":         "groups/developers",
 735  //	})
 736  //
 737  //	Result:
 738  //	   URL - /v1/users/{userId}/{subAccountId}/{path}/details
 739  //	   Composed URL - /v1/users/sample@sample.com/100002/groups/developers/details
 740  //
 741  // It replaces the value of the key while composing the request URL.
 742  // The value will be used as-is and has not been escaped.
 743  //
 744  // It overrides the raw path parameter set at the client instance level.
 745  func (r *Request) SetRawPathParams(params map[string]string) *Request {
 746  	for p, v := range params {
 747  		r.SetRawPathParam(p, v)
 748  	}
 749  	return r
 750  }
 751  
 752  // ExpectContentType method allows to provide fallback `Content-Type` for automatic unmarshalling
 753  // when the `Content-Type` response header is unavailable.
 754  func (r *Request) ExpectContentType(contentType string) *Request {
 755  	r.fallbackContentType = contentType
 756  	return r
 757  }
 758  
 759  // ForceContentType method provides a strong sense of response `Content-Type` for
 760  // automatic unmarshalling. Resty gives this a higher priority than the `Content-Type`
 761  // response header.
 762  //
 763  // This means that if both [Request.ForceContentType] is set and
 764  // the response `Content-Type` is available, `ForceContentType` will win.
 765  func (r *Request) ForceContentType(contentType string) *Request {
 766  	r.forceContentType = contentType
 767  	return r
 768  }
 769  
 770  // SetJSONEscapeHTML method enables or disables the HTML escape on JSON marshal.
 771  // By default, escape HTML is false.
 772  //
 773  // NOTE: This option only applies to the standard JSON Marshaller used by Resty.
 774  //
 775  // It overrides the value set at the client instance level, see [Client.SetJSONEscapeHTML]
 776  func (r *Request) SetJSONEscapeHTML(b bool) *Request {
 777  	r.jsonEscapeHTML = b
 778  	return r
 779  }
 780  
 781  // SetCookie method appends a single cookie in the current request instance.
 782  //
 783  //	client.R().SetCookie(&http.Cookie{
 784  //				Name:"go-resty",
 785  //				Value:"This is cookie value",
 786  //			})
 787  //
 788  // NOTE: Method appends the Cookie value into existing Cookie even if its already existing.
 789  func (r *Request) SetCookie(hc *http.Cookie) *Request {
 790  	r.Cookies = append(r.Cookies, hc)
 791  	return r
 792  }
 793  
 794  // SetCookies method sets an array of cookies in the current request instance.
 795  //
 796  //	cookies := []*http.Cookie{
 797  //		&http.Cookie{
 798  //			Name:"go-resty-1",
 799  //			Value:"This is cookie 1 value",
 800  //		},
 801  //		&http.Cookie{
 802  //			Name:"go-resty-2",
 803  //			Value:"This is cookie 2 value",
 804  //		},
 805  //	}
 806  //
 807  //	// Setting a cookies into resty's current request
 808  //	client.R().SetCookies(cookies)
 809  //
 810  // NOTE: Method appends the Cookie value into existing Cookie even if its already existing.
 811  func (r *Request) SetCookies(rs []*http.Cookie) *Request {
 812  	r.Cookies = append(r.Cookies, rs...)
 813  	return r
 814  }
 815  
 816  // SetLogger method sets given writer for logging Resty request and response details.
 817  // By default, requests and responses inherit their logger from the client.
 818  //
 819  // Compliant to interface [resty.Logger].
 820  //
 821  // It overrides the logger value set at the client instance level.
 822  func (r *Request) SetLogger(l Logger) *Request {
 823  	r.log = l
 824  	return r
 825  }
 826  
 827  // SetDebug method enables the debug mode on the current request. It logs
 828  // the details current request and response.
 829  //
 830  //	client.SetDebug(true)
 831  //
 832  // Also, it can be enabled at the request level for a particular request; see [Request.SetDebug].
 833  //   - For [Request], it logs information such as HTTP verb, Relative URL path,
 834  //     Host, Headers, and Body if it has one.
 835  //   - For [Response], it logs information such as Status, Response Time, Headers,
 836  //     and Body if it has one.
 837  func (r *Request) SetDebug(d bool) *Request {
 838  	r.Debug = d
 839  	return r
 840  }
 841  
 842  // AddRetryCondition method adds a retry condition function to the request's
 843  // array of functions is checked to determine if the request can be retried.
 844  // The request will retry if any functions return true and the error is nil.
 845  //
 846  // NOTE: The request level retry conditions are checked before all retry
 847  // conditions from the client instance.
 848  func (r *Request) AddRetryCondition(condition RetryConditionFunc) *Request {
 849  	r.retryConditions = append(r.retryConditions, condition)
 850  	return r
 851  }
 852  
 853  //‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
 854  // HTTP request tracing
 855  //_______________________________________________________________________
 856  
 857  // EnableTrace method enables trace for the current request
 858  // using [httptrace.ClientTrace] and provides insights.
 859  //
 860  //	client := resty.New()
 861  //
 862  //	resp, err := client.R().EnableTrace().Get("https://httpbin.org/get")
 863  //	fmt.Println("Error:", err)
 864  //	fmt.Println("Trace Info:", resp.Request.TraceInfo())
 865  //
 866  // See [Client.EnableTrace] is also available to get trace info for all requests.
 867  func (r *Request) EnableTrace() *Request {
 868  	r.trace = true
 869  	return r
 870  }
 871  
 872  // EnableGenerateCurlOnDebug method enables the generation of CURL commands in the debug log.
 873  // It works in conjunction with debug mode. It overrides the options set by the [Client].
 874  //
 875  // NOTE: Use with care.
 876  //   - Potential to leak sensitive data from [Request] and [Response] in the debug log.
 877  //   - Beware of memory usage since the request body is reread.
 878  func (r *Request) EnableGenerateCurlOnDebug() *Request {
 879  	r.generateCurlOnDebug = true
 880  	return r
 881  }
 882  
 883  // DisableGenerateCurlOnDebug method disables the option set by [Request.EnableGenerateCurlOnDebug].
 884  // It overrides the options set by the [Client].
 885  func (r *Request) DisableGenerateCurlOnDebug() *Request {
 886  	r.generateCurlOnDebug = false
 887  	return r
 888  }
 889  
 890  // TraceInfo method returns the trace info for the request.
 891  // If either the [Client.EnableTrace] or [Request.EnableTrace] function has not been called
 892  // before the request is made, an empty [resty.TraceInfo] object is returned.
 893  func (r *Request) TraceInfo() TraceInfo {
 894  	ct := r.clientTrace
 895  
 896  	if ct == nil {
 897  		return TraceInfo{}
 898  	}
 899  
 900  	ct.lock.RLock()
 901  	defer ct.lock.RUnlock()
 902  
 903  	ti := TraceInfo{
 904  		DNSLookup:      0,
 905  		TCPConnTime:    0,
 906  		ServerTime:     0,
 907  		IsConnReused:   ct.gotConnInfo.Reused,
 908  		IsConnWasIdle:  ct.gotConnInfo.WasIdle,
 909  		ConnIdleTime:   ct.gotConnInfo.IdleTime,
 910  		RequestAttempt: r.Attempt,
 911  	}
 912  
 913  	if !ct.dnsStart.IsZero() && !ct.dnsDone.IsZero() {
 914  		ti.DNSLookup = ct.dnsDone.Sub(ct.dnsStart)
 915  	}
 916  
 917  	if !ct.tlsHandshakeDone.IsZero() && !ct.tlsHandshakeStart.IsZero() {
 918  		ti.TLSHandshake = ct.tlsHandshakeDone.Sub(ct.tlsHandshakeStart)
 919  	}
 920  
 921  	if !ct.gotFirstResponseByte.IsZero() && !ct.gotConn.IsZero() {
 922  		ti.ServerTime = ct.gotFirstResponseByte.Sub(ct.gotConn)
 923  	}
 924  
 925  	// Calculate the total time accordingly when connection is reused,
 926  	// and DNS start and get conn time may be zero if the request is invalid.
 927  	// See issue #1016.
 928  	requestStartTime := r.Time
 929  	if ct.gotConnInfo.Reused && !ct.getConn.IsZero() {
 930  		requestStartTime = ct.getConn
 931  	} else if !ct.dnsStart.IsZero() {
 932  		requestStartTime = ct.dnsStart
 933  	}
 934  	ti.TotalTime = ct.endTime.Sub(requestStartTime)
 935  
 936  	// Only calculate on successful connections
 937  	if !ct.connectDone.IsZero() {
 938  		ti.TCPConnTime = ct.connectDone.Sub(ct.dnsDone)
 939  	}
 940  
 941  	// Only calculate on successful connections
 942  	if !ct.gotConn.IsZero() {
 943  		ti.ConnTime = ct.gotConn.Sub(ct.getConn)
 944  	}
 945  
 946  	// Only calculate on successful connections
 947  	if !ct.gotFirstResponseByte.IsZero() {
 948  		ti.ResponseTime = ct.endTime.Sub(ct.gotFirstResponseByte)
 949  	}
 950  
 951  	// Capture remote address info when connection is non-nil
 952  	if ct.gotConnInfo.Conn != nil {
 953  		ti.RemoteAddr = ct.gotConnInfo.Conn.RemoteAddr()
 954  	}
 955  
 956  	return ti
 957  }
 958  
 959  //‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
 960  // HTTP verb method starts here
 961  //_______________________________________________________________________
 962  
 963  // Get method does GET HTTP request. It's defined in section 4.3.1 of RFC7231.
 964  func (r *Request) Get(url string) (*Response, error) {
 965  	return r.Execute(MethodGet, url)
 966  }
 967  
 968  // Head method does HEAD HTTP request. It's defined in section 4.3.2 of RFC7231.
 969  func (r *Request) Head(url string) (*Response, error) {
 970  	return r.Execute(MethodHead, url)
 971  }
 972  
 973  // Post method does POST HTTP request. It's defined in section 4.3.3 of RFC7231.
 974  func (r *Request) Post(url string) (*Response, error) {
 975  	return r.Execute(MethodPost, url)
 976  }
 977  
 978  // Put method does PUT HTTP request. It's defined in section 4.3.4 of RFC7231.
 979  func (r *Request) Put(url string) (*Response, error) {
 980  	return r.Execute(MethodPut, url)
 981  }
 982  
 983  // Delete method does DELETE HTTP request. It's defined in section 4.3.5 of RFC7231.
 984  func (r *Request) Delete(url string) (*Response, error) {
 985  	return r.Execute(MethodDelete, url)
 986  }
 987  
 988  // Options method does OPTIONS HTTP request. It's defined in section 4.3.7 of RFC7231.
 989  func (r *Request) Options(url string) (*Response, error) {
 990  	return r.Execute(MethodOptions, url)
 991  }
 992  
 993  // Patch method does PATCH HTTP request. It's defined in section 2 of RFC5789.
 994  func (r *Request) Patch(url string) (*Response, error) {
 995  	return r.Execute(MethodPatch, url)
 996  }
 997  
 998  // Send method performs the HTTP request using the method and URL already defined
 999  // for current [Request].
1000  //
1001  //	req := client.R()
1002  //	req.Method = resty.MethodGet
1003  //	req.URL = "http://httpbin.org/get"
1004  //	resp, err := req.Send()
1005  func (r *Request) Send() (*Response, error) {
1006  	return r.Execute(r.Method, r.URL)
1007  }
1008  
1009  // Execute method performs the HTTP request with the given HTTP method and URL
1010  // for current [Request].
1011  //
1012  //	resp, err := client.R().Execute(resty.MethodGet, "http://httpbin.org/get")
1013  func (r *Request) Execute(method, url string) (*Response, error) {
1014  	var addrs []*net.SRV
1015  	var resp *Response
1016  	var err error
1017  
1018  	defer func() {
1019  		if rec := recover(); rec != nil {
1020  			if err, ok := rec.(error); ok {
1021  				r.client.onPanicHooks(r, err)
1022  			} else {
1023  				r.client.onPanicHooks(r, fmt.Errorf("panic %v", rec))
1024  			}
1025  			panic(rec)
1026  		}
1027  	}()
1028  
1029  	if r.isMultiPart && !(method == MethodPost || method == MethodPut || method == MethodPatch) {
1030  		// No OnError hook here since this is a request validation error
1031  		err := fmt.Errorf("multipart content is not allowed in HTTP verb [%v]", method)
1032  		r.client.onInvalidHooks(r, err)
1033  		return nil, err
1034  	}
1035  
1036  	if r.SRV != nil {
1037  		_, addrs, err = net.LookupSRV(r.SRV.Service, "tcp", r.SRV.Domain)
1038  		if err != nil {
1039  			r.client.onErrorHooks(r, nil, err)
1040  			return nil, err
1041  		}
1042  	}
1043  
1044  	r.Method = method
1045  	r.URL = r.selectAddr(addrs, url, 0)
1046  
1047  	if r.client.RetryCount == 0 {
1048  		r.Attempt = 1
1049  		resp, err = r.client.execute(r)
1050  		r.client.onErrorHooks(r, resp, unwrapNoRetryErr(err))
1051  		backToBufPool(r.bodyBuf)
1052  		return resp, unwrapNoRetryErr(err)
1053  	}
1054  
1055  	err = Backoff(
1056  		func() (*Response, error) {
1057  			r.Attempt++
1058  
1059  			r.URL = r.selectAddr(addrs, url, r.Attempt)
1060  
1061  			resp, err = r.client.execute(r)
1062  			if err != nil {
1063  				r.log.Warnf("%v, Attempt %v", err, r.Attempt)
1064  			}
1065  
1066  			return resp, err
1067  		},
1068  		Retries(r.client.RetryCount),
1069  		WaitTime(r.client.RetryWaitTime),
1070  		MaxWaitTime(r.client.RetryMaxWaitTime),
1071  		RetryConditions(append(r.retryConditions, r.client.RetryConditions...)),
1072  		RetryHooks(r.client.RetryHooks),
1073  		ResetMultipartReaders(r.client.RetryResetReaders),
1074  	)
1075  
1076  	if err != nil {
1077  		r.log.Errorf("%v", err)
1078  	}
1079  
1080  	r.client.onErrorHooks(r, resp, unwrapNoRetryErr(err))
1081  	backToBufPool(r.bodyBuf)
1082  	return resp, unwrapNoRetryErr(err)
1083  }
1084  
1085  //‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
1086  // SRVRecord struct
1087  //_______________________________________________________________________
1088  
1089  // SRVRecord struct holds the data to query the SRV record for the
1090  // following service.
1091  type SRVRecord struct {
1092  	Service string
1093  	Domain  string
1094  }
1095  
1096  func (r *Request) fmtBodyString(sl int64) (body string) {
1097  	body = "***** NO CONTENT *****"
1098  	if !isPayloadSupported(r.Method, r.client.AllowGetMethodPayload) ||
1099  		r.Body == http.NoBody {
1100  		return
1101  	}
1102  
1103  	if _, ok := r.Body.(io.Reader); ok {
1104  		body = "***** BODY IS io.Reader *****"
1105  		return
1106  	}
1107  
1108  	// multipart or form-data
1109  	if r.isMultiPart || r.isFormData {
1110  		bodySize := int64(r.bodyBuf.Len())
1111  		if bodySize > sl {
1112  			body = fmt.Sprintf("***** REQUEST TOO LARGE (size - %d) *****", bodySize)
1113  			return
1114  		}
1115  		body = r.bodyBuf.String()
1116  		return
1117  	}
1118  
1119  	// request body data
1120  	if r.Body == nil {
1121  		return
1122  	}
1123  	var prtBodyBytes []byte
1124  	var err error
1125  
1126  	contentType := r.Header.Get(hdrContentTypeKey)
1127  	kind := kindOf(r.Body)
1128  	if canJSONMarshal(contentType, kind) {
1129  		var bodyBuf *bytes.Buffer
1130  		bodyBuf, err = noescapeJSONMarshalIndent(&r.Body)
1131  		if err == nil {
1132  			prtBodyBytes = bodyBuf.Bytes()
1133  			defer releaseBuffer(bodyBuf)
1134  		}
1135  	} else if IsXMLType(contentType) && (kind == reflect.Struct) {
1136  		prtBodyBytes, err = xml.MarshalIndent(&r.Body, "", "   ")
1137  	} else if b, ok := r.Body.(string); ok {
1138  		if IsJSONType(contentType) {
1139  			bodyBytes := []byte(b)
1140  			out := acquireBuffer()
1141  			defer releaseBuffer(out)
1142  			if err = json.Indent(out, bodyBytes, "", "   "); err == nil {
1143  				prtBodyBytes = out.Bytes()
1144  			}
1145  		} else {
1146  			body = b
1147  		}
1148  	} else if b, ok := r.Body.([]byte); ok {
1149  		body = fmt.Sprintf("***** BODY IS byte(s) (size - %d) *****", len(b))
1150  		return
1151  	}
1152  
1153  	if prtBodyBytes != nil && err == nil {
1154  		body = string(prtBodyBytes)
1155  	}
1156  
1157  	if len(body) > 0 {
1158  		bodySize := int64(len([]byte(body)))
1159  		if bodySize > sl {
1160  			body = fmt.Sprintf("***** REQUEST TOO LARGE (size - %d) *****", bodySize)
1161  		}
1162  	}
1163  
1164  	return
1165  }
1166  
1167  func (r *Request) selectAddr(addrs []*net.SRV, path string, attempt int) string {
1168  	if addrs == nil {
1169  		return path
1170  	}
1171  
1172  	idx := attempt % len(addrs)
1173  	domain := strings.TrimRight(addrs[idx].Target, ".")
1174  	path = strings.TrimLeft(path, "/")
1175  
1176  	return fmt.Sprintf("%s://%s:%d/%s", r.client.scheme, domain, addrs[idx].Port, path)
1177  }
1178  
1179  func (r *Request) initValuesMap() {
1180  	if r.values == nil {
1181  		r.values = make(map[string]interface{})
1182  	}
1183  }
1184  
1185  var noescapeJSONMarshal = func(v interface{}) (*bytes.Buffer, error) {
1186  	buf := acquireBuffer()
1187  	encoder := json.NewEncoder(buf)
1188  	encoder.SetEscapeHTML(false)
1189  	if err := encoder.Encode(v); err != nil {
1190  		releaseBuffer(buf)
1191  		return nil, err
1192  	}
1193  
1194  	return buf, nil
1195  }
1196  
1197  var noescapeJSONMarshalIndent = func(v interface{}) (*bytes.Buffer, error) {
1198  	buf := acquireBuffer()
1199  	encoder := json.NewEncoder(buf)
1200  	encoder.SetEscapeHTML(false)
1201  	encoder.SetIndent("", "   ")
1202  
1203  	if err := encoder.Encode(v); err != nil {
1204  		releaseBuffer(buf)
1205  		return nil, err
1206  	}
1207  
1208  	return buf, nil
1209  }
1210