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