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