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