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