1// Copyright (c) 2015-2018 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 "encoding/base64" 9 "encoding/json" 10 "encoding/xml" 11 "fmt" 12 "io" 13 "net" 14 "net/url" 15 "reflect" 16 "strings" 17) 18 19// SRVRecord holds the data to query the SRV record for the following service 20type SRVRecord struct { 21 Service string 22 Domain string 23} 24 25// SetHeader method is to set a single header field and its value in the current request. 26// Example: To set `Content-Type` and `Accept` as `application/json`. 27// resty.R(). 28// SetHeader("Content-Type", "application/json"). 29// SetHeader("Accept", "application/json") 30// 31// Also you can override header value, which was set at client instance level. 32// 33func (r *Request) SetHeader(header, value string) *Request { 34 r.Header.Set(header, value) 35 return r 36} 37 38// SetHeaders method sets multiple headers field and its values at one go in the current request. 39// Example: To set `Content-Type` and `Accept` as `application/json` 40// 41// resty.R(). 42// SetHeaders(map[string]string{ 43// "Content-Type": "application/json", 44// "Accept": "application/json", 45// }) 46// Also you can override header value, which was set at client instance level. 47// 48func (r *Request) SetHeaders(headers map[string]string) *Request { 49 for h, v := range headers { 50 r.SetHeader(h, v) 51 } 52 53 return r 54} 55 56// SetQueryParam method sets single parameter and its value in the current request. 57// It will be formed as query string for the request. 58// Example: `search=kitchen%20papers&size=large` in the URL after `?` mark. 59// resty.R(). 60// SetQueryParam("search", "kitchen papers"). 61// SetQueryParam("size", "large") 62// Also you can override query params value, which was set at client instance level 63// 64func (r *Request) SetQueryParam(param, value string) *Request { 65 r.QueryParam.Set(param, value) 66 return r 67} 68 69// SetQueryParams method sets multiple parameters and its values at one go in the current request. 70// It will be formed as query string for the request. 71// Example: `search=kitchen%20papers&size=large` in the URL after `?` mark. 72// resty.R(). 73// SetQueryParams(map[string]string{ 74// "search": "kitchen papers", 75// "size": "large", 76// }) 77// Also you can override query params value, which was set at client instance level 78// 79func (r *Request) SetQueryParams(params map[string]string) *Request { 80 for p, v := range params { 81 r.SetQueryParam(p, v) 82 } 83 84 return r 85} 86 87// SetMultiValueQueryParams method appends multiple parameters with multi-value 88// at one go in the current request. It will be formed as query string for the request. 89// Example: `status=pending&status=approved&status=open` in the URL after `?` mark. 90// resty.R(). 91// SetMultiValueQueryParams(url.Values{ 92// "status": []string{"pending", "approved", "open"}, 93// }) 94// Also you can override query params value, which was set at client instance level 95// 96func (r *Request) SetMultiValueQueryParams(params url.Values) *Request { 97 for p, v := range params { 98 for _, pv := range v { 99 r.QueryParam.Add(p, pv) 100 } 101 } 102 103 return r 104} 105 106// SetQueryString method provides ability to use string as an input to set URL query string for the request. 107// 108// Using String as an input 109// resty.R(). 110// SetQueryString("productId=232&template=fresh-sample&cat=resty&source=google&kw=buy a lot more") 111// 112func (r *Request) SetQueryString(query string) *Request { 113 params, err := url.ParseQuery(strings.TrimSpace(query)) 114 if err == nil { 115 for p, v := range params { 116 for _, pv := range v { 117 r.QueryParam.Add(p, pv) 118 } 119 } 120 } else { 121 r.client.Log.Printf("ERROR %v", err) 122 } 123 return r 124} 125 126// SetFormData method sets Form parameters and their values in the current request. 127// It's applicable only HTTP method `POST` and `PUT` and requests content type would be set as 128// `application/x-www-form-urlencoded`. 129// resty.R(). 130// SetFormData(map[string]string{ 131// "access_token": "BC594900-518B-4F7E-AC75-BD37F019E08F", 132// "user_id": "3455454545", 133// }) 134// Also you can override form data value, which was set at client instance level 135// 136func (r *Request) SetFormData(data map[string]string) *Request { 137 for k, v := range data { 138 r.FormData.Set(k, v) 139 } 140 141 return r 142} 143 144// SetMultiValueFormData method appends multiple form parameters with multi-value 145// at one go in the current request. 146// resty.R(). 147// SetMultiValueFormData(url.Values{ 148// "search_criteria": []string{"book", "glass", "pencil"}, 149// }) 150// Also you can override form data value, which was set at client instance level 151// 152func (r *Request) SetMultiValueFormData(params url.Values) *Request { 153 for k, v := range params { 154 for _, kv := range v { 155 r.FormData.Add(k, kv) 156 } 157 } 158 159 return r 160} 161 162// SetBody method sets the request body for the request. It supports various realtime need easy. 163// We can say its quite handy or powerful. Supported request body data types is `string`, `[]byte`, 164// `struct` and `map`. Body value can be pointer or non-pointer. Automatic marshalling 165// for JSON and XML content type, if it is `struct` or `map`. 166// 167// Example: 168// 169// Struct as a body input, based on content type, it will be marshalled. 170// resty.R(). 171// SetBody(User{ 172// Username: "jeeva@myjeeva.com", 173// Password: "welcome2resty", 174// }) 175// 176// Map as a body input, based on content type, it will be marshalled. 177// resty.R(). 178// SetBody(map[string]interface{}{ 179// "username": "jeeva@myjeeva.com", 180// "password": "welcome2resty", 181// "address": &Address{ 182// Address1: "1111 This is my street", 183// Address2: "Apt 201", 184// City: "My City", 185// State: "My State", 186// ZipCode: 00000, 187// }, 188// }) 189// 190// String as a body input. Suitable for any need as a string input. 191// resty.R(). 192// SetBody(`{ 193// "username": "jeeva@getrightcare.com", 194// "password": "admin" 195// }`) 196// 197// []byte as a body input. Suitable for raw request such as file upload, serialize & deserialize, etc. 198// resty.R(). 199// SetBody([]byte("This is my raw request, sent as-is")) 200// 201func (r *Request) SetBody(body interface{}) *Request { 202 r.Body = body 203 return r 204} 205 206// SetResult method is to register the response `Result` object for automatic unmarshalling in the RESTful mode 207// if response status code is between 200 and 299 and content type either JSON or XML. 208// 209// Note: Result object can be pointer or non-pointer. 210// resty.R().SetResult(&AuthToken{}) 211// // OR 212// resty.R().SetResult(AuthToken{}) 213// 214// Accessing a result value 215// response.Result().(*AuthToken) 216// 217func (r *Request) SetResult(res interface{}) *Request { 218 r.Result = getPointer(res) 219 return r 220} 221 222// SetError method is to register the request `Error` object for automatic unmarshalling in the RESTful mode 223// if response status code is greater than 399 and content type either JSON or XML. 224// 225// Note: Error object can be pointer or non-pointer. 226// resty.R().SetError(&AuthError{}) 227// // OR 228// resty.R().SetError(AuthError{}) 229// 230// Accessing a error value 231// response.Error().(*AuthError) 232// 233func (r *Request) SetError(err interface{}) *Request { 234 r.Error = getPointer(err) 235 return r 236} 237 238// SetFile method is to set single file field name and its path for multipart upload. 239// resty.R(). 240// SetFile("my_file", "/Users/jeeva/Gas Bill - Sep.pdf") 241// 242func (r *Request) SetFile(param, filePath string) *Request { 243 r.isMultiPart = true 244 r.FormData.Set("@"+param, filePath) 245 246 return r 247} 248 249// SetFiles method is to set multiple file field name and its path for multipart upload. 250// resty.R(). 251// SetFiles(map[string]string{ 252// "my_file1": "/Users/jeeva/Gas Bill - Sep.pdf", 253// "my_file2": "/Users/jeeva/Electricity Bill - Sep.pdf", 254// "my_file3": "/Users/jeeva/Water Bill - Sep.pdf", 255// }) 256// 257func (r *Request) SetFiles(files map[string]string) *Request { 258 r.isMultiPart = true 259 260 for f, fp := range files { 261 r.FormData.Set("@"+f, fp) 262 } 263 264 return r 265} 266 267// SetFileReader method is to set single file using io.Reader for multipart upload. 268// resty.R(). 269// SetFileReader("profile_img", "my-profile-img.png", bytes.NewReader(profileImgBytes)). 270// SetFileReader("notes", "user-notes.txt", bytes.NewReader(notesBytes)) 271// 272func (r *Request) SetFileReader(param, fileName string, reader io.Reader) *Request { 273 r.isMultiPart = true 274 275 r.multipartFiles = append(r.multipartFiles, &File{ 276 Name: fileName, 277 ParamName: param, 278 Reader: reader, 279 }) 280 281 return r 282} 283 284// SetMultipartField method is to set custom data using io.Reader for multipart upload. 285func (r *Request) SetMultipartField(param, fileName, contentType string, reader io.Reader) *Request { 286 r.isMultiPart = true 287 288 r.multipartFields = append(r.multipartFields, &multipartField{ 289 Param: param, 290 FileName: fileName, 291 ContentType: contentType, 292 Reader: reader, 293 }) 294 295 return r 296} 297 298// SetContentLength method sets the HTTP header `Content-Length` value for current request. 299// By default go-resty won't set `Content-Length`. Also you have an option to enable for every 300// request. See `resty.SetContentLength` 301// resty.R().SetContentLength(true) 302// 303func (r *Request) SetContentLength(l bool) *Request { 304 r.setContentLength = true 305 306 return r 307} 308 309// SetBasicAuth method sets the basic authentication header in the current HTTP request. 310// For Header example: 311// Authorization: Basic <base64-encoded-value> 312// 313// To set the header for username "go-resty" and password "welcome" 314// resty.R().SetBasicAuth("go-resty", "welcome") 315// 316// This method overrides the credentials set by method `resty.SetBasicAuth`. 317// 318func (r *Request) SetBasicAuth(username, password string) *Request { 319 r.UserInfo = &User{Username: username, Password: password} 320 return r 321} 322 323// SetAuthToken method sets bearer auth token header in the current HTTP request. Header example: 324// Authorization: Bearer <auth-token-value-comes-here> 325// 326// Example: To set auth token BC594900518B4F7EAC75BD37F019E08FBC594900518B4F7EAC75BD37F019E08F 327// 328// resty.R().SetAuthToken("BC594900518B4F7EAC75BD37F019E08FBC594900518B4F7EAC75BD37F019E08F") 329// 330// This method overrides the Auth token set by method `resty.SetAuthToken`. 331// 332func (r *Request) SetAuthToken(token string) *Request { 333 r.Token = token 334 return r 335} 336 337// SetOutput method sets the output file for current HTTP request. Current HTTP response will be 338// saved into given file. It is similar to `curl -o` flag. Absolute path or relative path can be used. 339// If is it relative path then output file goes under the output directory, as mentioned 340// in the `Client.SetOutputDirectory`. 341// resty.R(). 342// SetOutput("/Users/jeeva/Downloads/ReplyWithHeader-v5.1-beta.zip"). 343// Get("http://bit.ly/1LouEKr") 344// 345// Note: In this scenario `Response.Body` might be nil. 346func (r *Request) SetOutput(file string) *Request { 347 r.outputFile = file 348 r.isSaveResponse = true 349 return r 350} 351 352// SetSRV method sets the details to query the service SRV record and execute the 353// request. 354// resty.R(). 355// SetSRV(SRVRecord{"web", "testservice.com"}). 356// Get("/get") 357func (r *Request) SetSRV(srv *SRVRecord) *Request { 358 r.SRV = srv 359 return r 360} 361 362// SetDoNotParseResponse method instructs `Resty` not to parse the response body automatically. 363// Resty exposes the raw response body as `io.ReadCloser`. Also do not forget to close the body, 364// otherwise you might get into connection leaks, no connection reuse. 365// 366// Please Note: Response middlewares are not applicable, if you use this option. Basically you have 367// taken over the control of response parsing from `Resty`. 368func (r *Request) SetDoNotParseResponse(parse bool) *Request { 369 r.notParseResponse = parse 370 return r 371} 372 373// SetPathParams method sets multiple URL path key-value pairs at one go in the 374// resty current request instance. 375// resty.R().SetPathParams(map[string]string{ 376// "userId": "sample@sample.com", 377// "subAccountId": "100002", 378// }) 379// 380// Result: 381// URL - /v1/users/{userId}/{subAccountId}/details 382// Composed URL - /v1/users/sample@sample.com/100002/details 383// It replace the value of the key while composing request URL. Also you can 384// override Path Params value, which was set at client instance level. 385func (r *Request) SetPathParams(params map[string]string) *Request { 386 for p, v := range params { 387 r.pathParams[p] = v 388 } 389 return r 390} 391 392// ExpectContentType method allows to provide fallback `Content-Type` for automatic unmarshalling 393// when `Content-Type` response header is unavailable. 394func (r *Request) ExpectContentType(contentType string) *Request { 395 r.fallbackContentType = contentType 396 return r 397} 398 399// SetJSONEscapeHTML method is to enable/disable the HTML escape on JSON marshal. 400// 401// NOTE: This option only applicable to standard JSON Marshaller. 402func (r *Request) SetJSONEscapeHTML(b bool) *Request { 403 r.jsonEscapeHTML = b 404 return r 405} 406 407//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾ 408// HTTP verb method starts here 409//___________________________________ 410 411// Get method does GET HTTP request. It's defined in section 4.3.1 of RFC7231. 412func (r *Request) Get(url string) (*Response, error) { 413 return r.Execute(MethodGet, url) 414} 415 416// Head method does HEAD HTTP request. It's defined in section 4.3.2 of RFC7231. 417func (r *Request) Head(url string) (*Response, error) { 418 return r.Execute(MethodHead, url) 419} 420 421// Post method does POST HTTP request. It's defined in section 4.3.3 of RFC7231. 422func (r *Request) Post(url string) (*Response, error) { 423 return r.Execute(MethodPost, url) 424} 425 426// Put method does PUT HTTP request. It's defined in section 4.3.4 of RFC7231. 427func (r *Request) Put(url string) (*Response, error) { 428 return r.Execute(MethodPut, url) 429} 430 431// Delete method does DELETE HTTP request. It's defined in section 4.3.5 of RFC7231. 432func (r *Request) Delete(url string) (*Response, error) { 433 return r.Execute(MethodDelete, url) 434} 435 436// Options method does OPTIONS HTTP request. It's defined in section 4.3.7 of RFC7231. 437func (r *Request) Options(url string) (*Response, error) { 438 return r.Execute(MethodOptions, url) 439} 440 441// Patch method does PATCH HTTP request. It's defined in section 2 of RFC5789. 442func (r *Request) Patch(url string) (*Response, error) { 443 return r.Execute(MethodPatch, url) 444} 445 446// Execute method performs the HTTP request with given HTTP method and URL 447// for current `Request`. 448// resp, err := resty.R().Execute(resty.GET, "http://httpbin.org/get") 449// 450func (r *Request) Execute(method, url string) (*Response, error) { 451 var addrs []*net.SRV 452 var err error 453 454 if r.isMultiPart && !(method == MethodPost || method == MethodPut) { 455 return nil, fmt.Errorf("multipart content is not allowed in HTTP verb [%v]", method) 456 } 457 458 if r.SRV != nil { 459 _, addrs, err = net.LookupSRV(r.SRV.Service, "tcp", r.SRV.Domain) 460 if err != nil { 461 return nil, err 462 } 463 } 464 465 r.Method = method 466 r.URL = r.selectAddr(addrs, url, 0) 467 468 if r.client.RetryCount == 0 { 469 return r.client.execute(r) 470 } 471 472 var resp *Response 473 attempt := 0 474 _ = Backoff( 475 func() (*Response, error) { 476 attempt++ 477 478 r.URL = r.selectAddr(addrs, url, attempt) 479 480 resp, err = r.client.execute(r) 481 if err != nil { 482 r.client.Log.Printf("ERROR %v, Attempt %v", err, attempt) 483 if r.isContextCancelledIfAvailable() { 484 // stop Backoff from retrying request if request has been 485 // canceled by context 486 return resp, nil 487 } 488 } 489 490 return resp, err 491 }, 492 Retries(r.client.RetryCount), 493 WaitTime(r.client.RetryWaitTime), 494 MaxWaitTime(r.client.RetryMaxWaitTime), 495 RetryConditions(r.client.RetryConditions), 496 ) 497 498 return resp, err 499} 500 501//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾ 502// Request Unexported methods 503//___________________________________ 504 505func (r *Request) fmtBodyString() (body string) { 506 body = "***** NO CONTENT *****" 507 if isPayloadSupported(r.Method, r.client.AllowGetMethodPayload) { 508 if _, ok := r.Body.(io.Reader); ok { 509 body = "***** BODY IS io.Reader *****" 510 return 511 } 512 513 // multipart or form-data 514 if r.isMultiPart || r.isFormData { 515 body = r.bodyBuf.String() 516 return 517 } 518 519 // request body data 520 if r.Body == nil { 521 return 522 } 523 var prtBodyBytes []byte 524 var err error 525 526 contentType := r.Header.Get(hdrContentTypeKey) 527 kind := kindOf(r.Body) 528 if canJSONMarshal(contentType, kind) { 529 prtBodyBytes, err = json.MarshalIndent(&r.Body, "", " ") 530 } else if IsXMLType(contentType) && (kind == reflect.Struct) { 531 prtBodyBytes, err = xml.MarshalIndent(&r.Body, "", " ") 532 } else if b, ok := r.Body.(string); ok { 533 if IsJSONType(contentType) { 534 bodyBytes := []byte(b) 535 out := acquireBuffer() 536 defer releaseBuffer(out) 537 if err = json.Indent(out, bodyBytes, "", " "); err == nil { 538 prtBodyBytes = out.Bytes() 539 } 540 } else { 541 body = b 542 return 543 } 544 } else if b, ok := r.Body.([]byte); ok { 545 body = base64.StdEncoding.EncodeToString(b) 546 } 547 548 if prtBodyBytes != nil && err == nil { 549 body = string(prtBodyBytes) 550 } 551 } 552 553 return 554} 555 556func (r *Request) selectAddr(addrs []*net.SRV, path string, attempt int) string { 557 if addrs == nil { 558 return path 559 } 560 561 idx := attempt % len(addrs) 562 domain := strings.TrimRight(addrs[idx].Target, ".") 563 path = strings.TrimLeft(path, "/") 564 565 return fmt.Sprintf("%s://%s:%d/%s", r.client.scheme, domain, addrs[idx].Port, path) 566} 567