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