|
- // Copyright (c) 2015-2020 Jeevanandam M (jeeva@myjeeva.com), All rights reserved.
- // resty source code and usage is governed by a MIT style
- // license that can be found in the LICENSE file.
-
- package resty
-
- import (
- "bytes"
- "context"
- "encoding/json"
- "encoding/xml"
- "fmt"
- "io"
- "net"
- "net/http"
- "net/url"
- "reflect"
- "strings"
- "time"
- )
-
- //‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
- // Request struct and methods
- //_______________________________________________________________________
-
- // Request struct is used to compose and fire individual request from
- // resty client. Request provides an options to override client level
- // settings and also an options for the request composition.
- type Request struct {
- URL string
- Method string
- Token string
- AuthScheme string
- QueryParam url.Values
- FormData url.Values
- Header http.Header
- Time time.Time
- Body interface{}
- Result interface{}
- Error interface{}
- RawRequest *http.Request
- SRV *SRVRecord
- UserInfo *User
- Cookies []*http.Cookie
-
- isMultiPart bool
- isFormData bool
- setContentLength bool
- isSaveResponse bool
- notParseResponse bool
- jsonEscapeHTML bool
- trace bool
- outputFile string
- fallbackContentType string
- forceContentType string
- ctx context.Context
- pathParams map[string]string
- values map[string]interface{}
- client *Client
- bodyBuf *bytes.Buffer
- clientTrace *clientTrace
- multipartFiles []*File
- multipartFields []*MultipartField
- }
-
- // Context method returns the Context if its already set in request
- // otherwise it creates new one using `context.Background()`.
- func (r *Request) Context() context.Context {
- if r.ctx == nil {
- return context.Background()
- }
- return r.ctx
- }
-
- // SetContext method sets the context.Context for current Request. It allows
- // to interrupt the request execution if ctx.Done() channel is closed.
- // See https://blog.golang.org/context article and the "context" package
- // documentation.
- func (r *Request) SetContext(ctx context.Context) *Request {
- r.ctx = ctx
- return r
- }
-
- // SetHeader method is to set a single header field and its value in the current request.
- //
- // For Example: To set `Content-Type` and `Accept` as `application/json`.
- // client.R().
- // SetHeader("Content-Type", "application/json").
- // SetHeader("Accept", "application/json")
- //
- // Also you can override header value, which was set at client instance level.
- func (r *Request) SetHeader(header, value string) *Request {
- r.Header.Set(header, value)
- return r
- }
-
- // SetHeaders method sets multiple headers field and its values at one go in the current request.
- //
- // For Example: To set `Content-Type` and `Accept` as `application/json`
- //
- // client.R().
- // SetHeaders(map[string]string{
- // "Content-Type": "application/json",
- // "Accept": "application/json",
- // })
- // Also you can override header value, which was set at client instance level.
- func (r *Request) SetHeaders(headers map[string]string) *Request {
- for h, v := range headers {
- r.SetHeader(h, v)
- }
- return r
- }
-
- // SetQueryParam method sets single parameter and its value in the current request.
- // It will be formed as query string for the request.
- //
- // For Example: `search=kitchen%20papers&size=large` in the URL after `?` mark.
- // client.R().
- // SetQueryParam("search", "kitchen papers").
- // SetQueryParam("size", "large")
- // Also you can override query params value, which was set at client instance level.
- func (r *Request) SetQueryParam(param, value string) *Request {
- r.QueryParam.Set(param, value)
- return r
- }
-
- // SetQueryParams method sets multiple parameters and its values at one go in the current request.
- // It will be formed as query string for the request.
- //
- // For Example: `search=kitchen%20papers&size=large` in the URL after `?` mark.
- // client.R().
- // SetQueryParams(map[string]string{
- // "search": "kitchen papers",
- // "size": "large",
- // })
- // Also you can override query params value, which was set at client instance level.
- func (r *Request) SetQueryParams(params map[string]string) *Request {
- for p, v := range params {
- r.SetQueryParam(p, v)
- }
- return r
- }
-
- // SetQueryParamsFromValues method appends multiple parameters with multi-value
- // (`url.Values`) at one go in the current request. It will be formed as
- // query string for the request.
- //
- // For Example: `status=pending&status=approved&status=open` in the URL after `?` mark.
- // client.R().
- // SetQueryParamsFromValues(url.Values{
- // "status": []string{"pending", "approved", "open"},
- // })
- // Also you can override query params value, which was set at client instance level.
- func (r *Request) SetQueryParamsFromValues(params url.Values) *Request {
- for p, v := range params {
- for _, pv := range v {
- r.QueryParam.Add(p, pv)
- }
- }
- return r
- }
-
- // SetQueryString method provides ability to use string as an input to set URL query string for the request.
- //
- // Using String as an input
- // client.R().
- // SetQueryString("productId=232&template=fresh-sample&cat=resty&source=google&kw=buy a lot more")
- func (r *Request) SetQueryString(query string) *Request {
- params, err := url.ParseQuery(strings.TrimSpace(query))
- if err == nil {
- for p, v := range params {
- for _, pv := range v {
- r.QueryParam.Add(p, pv)
- }
- }
- } else {
- r.client.log.Errorf("%v", err)
- }
- return r
- }
-
- // SetFormData method sets Form parameters and their values in the current request.
- // It's applicable only HTTP method `POST` and `PUT` and requests content type would be set as
- // `application/x-www-form-urlencoded`.
- // client.R().
- // SetFormData(map[string]string{
- // "access_token": "BC594900-518B-4F7E-AC75-BD37F019E08F",
- // "user_id": "3455454545",
- // })
- // Also you can override form data value, which was set at client instance level.
- func (r *Request) SetFormData(data map[string]string) *Request {
- for k, v := range data {
- r.FormData.Set(k, v)
- }
- return r
- }
-
- // SetFormDataFromValues method appends multiple form parameters with multi-value
- // (`url.Values`) at one go in the current request.
- // client.R().
- // SetFormDataFromValues(url.Values{
- // "search_criteria": []string{"book", "glass", "pencil"},
- // })
- // Also you can override form data value, which was set at client instance level.
- func (r *Request) SetFormDataFromValues(data url.Values) *Request {
- for k, v := range data {
- for _, kv := range v {
- r.FormData.Add(k, kv)
- }
- }
- return r
- }
-
- // SetBody method sets the request body for the request. It supports various realtime needs as easy.
- // We can say its quite handy or powerful. Supported request body data types is `string`,
- // `[]byte`, `struct`, `map`, `slice` and `io.Reader`. Body value can be pointer or non-pointer.
- // Automatic marshalling for JSON and XML content type, if it is `struct`, `map`, or `slice`.
- //
- // Note: `io.Reader` is processed as bufferless mode while sending request.
- //
- // For Example: Struct as a body input, based on content type, it will be marshalled.
- // client.R().
- // SetBody(User{
- // Username: "jeeva@myjeeva.com",
- // Password: "welcome2resty",
- // })
- //
- // Map as a body input, based on content type, it will be marshalled.
- // client.R().
- // SetBody(map[string]interface{}{
- // "username": "jeeva@myjeeva.com",
- // "password": "welcome2resty",
- // "address": &Address{
- // Address1: "1111 This is my street",
- // Address2: "Apt 201",
- // City: "My City",
- // State: "My State",
- // ZipCode: 00000,
- // },
- // })
- //
- // String as a body input. Suitable for any need as a string input.
- // client.R().
- // SetBody(`{
- // "username": "jeeva@getrightcare.com",
- // "password": "admin"
- // }`)
- //
- // []byte as a body input. Suitable for raw request such as file upload, serialize & deserialize, etc.
- // client.R().
- // SetBody([]byte("This is my raw request, sent as-is"))
- func (r *Request) SetBody(body interface{}) *Request {
- r.Body = body
- return r
- }
-
- // SetResult method is to register the response `Result` object for automatic unmarshalling for the request,
- // if response status code is between 200 and 299 and content type either JSON or XML.
- //
- // Note: Result object can be pointer or non-pointer.
- // client.R().SetResult(&AuthToken{})
- // // OR
- // client.R().SetResult(AuthToken{})
- //
- // Accessing a result value from response instance.
- // response.Result().(*AuthToken)
- func (r *Request) SetResult(res interface{}) *Request {
- r.Result = getPointer(res)
- return r
- }
-
- // SetError method is to register the request `Error` object for automatic unmarshalling for the request,
- // if response status code is greater than 399 and content type either JSON or XML.
- //
- // Note: Error object can be pointer or non-pointer.
- // client.R().SetError(&AuthError{})
- // // OR
- // client.R().SetError(AuthError{})
- //
- // Accessing a error value from response instance.
- // response.Error().(*AuthError)
- func (r *Request) SetError(err interface{}) *Request {
- r.Error = getPointer(err)
- return r
- }
-
- // SetFile method is to set single file field name and its path for multipart upload.
- // client.R().
- // SetFile("my_file", "/Users/jeeva/Gas Bill - Sep.pdf")
- func (r *Request) SetFile(param, filePath string) *Request {
- r.isMultiPart = true
- r.FormData.Set("@"+param, filePath)
- return r
- }
-
- // SetFiles method is to set multiple file field name and its path for multipart upload.
- // client.R().
- // SetFiles(map[string]string{
- // "my_file1": "/Users/jeeva/Gas Bill - Sep.pdf",
- // "my_file2": "/Users/jeeva/Electricity Bill - Sep.pdf",
- // "my_file3": "/Users/jeeva/Water Bill - Sep.pdf",
- // })
- func (r *Request) SetFiles(files map[string]string) *Request {
- r.isMultiPart = true
- for f, fp := range files {
- r.FormData.Set("@"+f, fp)
- }
- return r
- }
-
- // SetFileReader method is to set single file using io.Reader for multipart upload.
- // client.R().
- // SetFileReader("profile_img", "my-profile-img.png", bytes.NewReader(profileImgBytes)).
- // SetFileReader("notes", "user-notes.txt", bytes.NewReader(notesBytes))
- func (r *Request) SetFileReader(param, fileName string, reader io.Reader) *Request {
- r.isMultiPart = true
- r.multipartFiles = append(r.multipartFiles, &File{
- Name: fileName,
- ParamName: param,
- Reader: reader,
- })
- return r
- }
-
- // SetMultipartFormData method allows simple form data to be attached to the request as `multipart:form-data`
- func (r *Request) SetMultipartFormData(data map[string]string) *Request {
- for k, v := range data {
- r = r.SetMultipartField(k, "", "", strings.NewReader(v))
- }
-
- return r
- }
-
- // SetMultipartField method is to set custom data using io.Reader for multipart upload.
- func (r *Request) SetMultipartField(param, fileName, contentType string, reader io.Reader) *Request {
- r.isMultiPart = true
- r.multipartFields = append(r.multipartFields, &MultipartField{
- Param: param,
- FileName: fileName,
- ContentType: contentType,
- Reader: reader,
- })
- return r
- }
-
- // SetMultipartFields method is to set multiple data fields using io.Reader for multipart upload.
- //
- // For Example:
- // client.R().SetMultipartFields(
- // &resty.MultipartField{
- // Param: "uploadManifest1",
- // FileName: "upload-file-1.json",
- // ContentType: "application/json",
- // Reader: strings.NewReader(`{"input": {"name": "Uploaded document 1", "_filename" : ["file1.txt"]}}`),
- // },
- // &resty.MultipartField{
- // Param: "uploadManifest2",
- // FileName: "upload-file-2.json",
- // ContentType: "application/json",
- // Reader: strings.NewReader(`{"input": {"name": "Uploaded document 2", "_filename" : ["file2.txt"]}}`),
- // })
- //
- // If you have slice already, then simply call-
- // client.R().SetMultipartFields(fields...)
- func (r *Request) SetMultipartFields(fields ...*MultipartField) *Request {
- r.isMultiPart = true
- r.multipartFields = append(r.multipartFields, fields...)
- return r
- }
-
- // SetContentLength method sets the HTTP header `Content-Length` value for current request.
- // By default Resty won't set `Content-Length`. Also you have an option to enable for every
- // request.
- //
- // See `Client.SetContentLength`
- // client.R().SetContentLength(true)
- func (r *Request) SetContentLength(l bool) *Request {
- r.setContentLength = true
- return r
- }
-
- // SetBasicAuth method sets the basic authentication header in the current HTTP request.
- //
- // For Example:
- // Authorization: Basic <base64-encoded-value>
- //
- // To set the header for username "go-resty" and password "welcome"
- // client.R().SetBasicAuth("go-resty", "welcome")
- //
- // This method overrides the credentials set by method `Client.SetBasicAuth`.
- func (r *Request) SetBasicAuth(username, password string) *Request {
- r.UserInfo = &User{Username: username, Password: password}
- return r
- }
-
- // SetAuthToken method sets the auth token header(Default Scheme: Bearer) in the current HTTP request. Header example:
- // Authorization: Bearer <auth-token-value-comes-here>
- //
- // For Example: To set auth token BC594900518B4F7EAC75BD37F019E08FBC594900518B4F7EAC75BD37F019E08F
- //
- // client.R().SetAuthToken("BC594900518B4F7EAC75BD37F019E08FBC594900518B4F7EAC75BD37F019E08F")
- //
- // This method overrides the Auth token set by method `Client.SetAuthToken`.
- func (r *Request) SetAuthToken(token string) *Request {
- r.Token = token
- return r
- }
-
- // SetAuthScheme method sets the auth token scheme type in the HTTP request. For Example:
- // Authorization: <auth-scheme-value-set-here> <auth-token-value>
- //
- // For Example: To set the scheme to use OAuth
- //
- // client.R().SetAuthScheme("OAuth")
- //
- // This auth header scheme gets added to all the request rasied from this client instance.
- // Also it can be overridden or set one at the request level is supported.
- //
- // Information about Auth schemes can be found in RFC7235 which is linked to below along with the page containing
- // the currently defined official authentication schemes:
- // https://tools.ietf.org/html/rfc7235
- // https://www.iana.org/assignments/http-authschemes/http-authschemes.xhtml#authschemes
- //
- // This method overrides the Authorization scheme set by method `Client.SetAuthScheme`.
- func (r *Request) SetAuthScheme(scheme string) *Request {
- r.AuthScheme = scheme
- return r
- }
-
- // SetOutput method sets the output file for current HTTP request. Current HTTP response will be
- // saved into given file. It is similar to `curl -o` flag. Absolute path or relative path can be used.
- // If is it relative path then output file goes under the output directory, as mentioned
- // in the `Client.SetOutputDirectory`.
- // client.R().
- // SetOutput("/Users/jeeva/Downloads/ReplyWithHeader-v5.1-beta.zip").
- // Get("http://bit.ly/1LouEKr")
- //
- // Note: In this scenario `Response.Body` might be nil.
- func (r *Request) SetOutput(file string) *Request {
- r.outputFile = file
- r.isSaveResponse = true
- return r
- }
-
- // SetSRV method sets the details to query the service SRV record and execute the
- // request.
- // client.R().
- // SetSRV(SRVRecord{"web", "testservice.com"}).
- // Get("/get")
- func (r *Request) SetSRV(srv *SRVRecord) *Request {
- r.SRV = srv
- return r
- }
-
- // SetDoNotParseResponse method instructs `Resty` not to parse the response body automatically.
- // Resty exposes the raw response body as `io.ReadCloser`. Also do not forget to close the body,
- // otherwise you might get into connection leaks, no connection reuse.
- //
- // Note: Response middlewares are not applicable, if you use this option. Basically you have
- // taken over the control of response parsing from `Resty`.
- func (r *Request) SetDoNotParseResponse(parse bool) *Request {
- r.notParseResponse = parse
- return r
- }
-
- // SetPathParams method sets multiple URL path key-value pairs at one go in the
- // Resty current request instance.
- // client.R().SetPathParams(map[string]string{
- // "userId": "sample@sample.com",
- // "subAccountId": "100002",
- // })
- //
- // Result:
- // URL - /v1/users/{userId}/{subAccountId}/details
- // Composed URL - /v1/users/sample@sample.com/100002/details
- // It replace the value of the key while composing request URL. Also you can
- // override Path Params value, which was set at client instance level.
- func (r *Request) SetPathParams(params map[string]string) *Request {
- for p, v := range params {
- r.pathParams[p] = v
- }
- return r
- }
-
- // ExpectContentType method allows to provide fallback `Content-Type` for automatic unmarshalling
- // when `Content-Type` response header is unavailable.
- func (r *Request) ExpectContentType(contentType string) *Request {
- r.fallbackContentType = contentType
- return r
- }
-
- // ForceContentType method provides a strong sense of response `Content-Type` for automatic unmarshalling.
- // Resty will respect it with higher priority; even response `Content-Type` response header value is available.
- func (r *Request) ForceContentType(contentType string) *Request {
- r.forceContentType = contentType
- return r
- }
-
- // SetJSONEscapeHTML method is to enable/disable the HTML escape on JSON marshal.
- //
- // Note: This option only applicable to standard JSON Marshaller.
- func (r *Request) SetJSONEscapeHTML(b bool) *Request {
- r.jsonEscapeHTML = b
- return r
- }
-
- // SetCookie method appends a single cookie in the current request instance.
- // client.R().SetCookie(&http.Cookie{
- // Name:"go-resty",
- // Value:"This is cookie value",
- // })
- //
- // Note: Method appends the Cookie value into existing Cookie if already existing.
- //
- // Since v2.1.0
- func (r *Request) SetCookie(hc *http.Cookie) *Request {
- r.Cookies = append(r.Cookies, hc)
- return r
- }
-
- // SetCookies method sets an array of cookies in the current request instance.
- // cookies := []*http.Cookie{
- // &http.Cookie{
- // Name:"go-resty-1",
- // Value:"This is cookie 1 value",
- // },
- // &http.Cookie{
- // Name:"go-resty-2",
- // Value:"This is cookie 2 value",
- // },
- // }
- //
- // // Setting a cookies into resty's current request
- // client.R().SetCookies(cookies)
- //
- // Note: Method appends the Cookie value into existing Cookie if already existing.
- //
- // Since v2.1.0
- func (r *Request) SetCookies(rs []*http.Cookie) *Request {
- r.Cookies = append(r.Cookies, rs...)
- return r
- }
-
- //‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
- // HTTP request tracing
- //_______________________________________________________________________
-
- // EnableTrace method enables trace for the current request
- // using `httptrace.ClientTrace` and provides insights.
- //
- // client := resty.New()
- //
- // resp, err := client.R().EnableTrace().Get("https://httpbin.org/get")
- // fmt.Println("Error:", err)
- // fmt.Println("Trace Info:", resp.Request.TraceInfo())
- //
- // See `Client.EnableTrace` available too to get trace info for all requests.
- //
- // Since v2.0.0
- func (r *Request) EnableTrace() *Request {
- r.trace = true
- return r
- }
-
- // TraceInfo method returns the trace info for the request.
- // If either the Client or Request EnableTrace function has not been called
- // prior to the request being made, an empty TraceInfo object will be returned.
- //
- // Since v2.0.0
- func (r *Request) TraceInfo() TraceInfo {
- ct := r.clientTrace
-
- if ct == nil {
- return TraceInfo{}
- }
-
- ti := TraceInfo{
- DNSLookup: ct.dnsDone.Sub(ct.dnsStart),
- TLSHandshake: ct.tlsHandshakeDone.Sub(ct.tlsHandshakeStart),
- ServerTime: ct.gotFirstResponseByte.Sub(ct.gotConn),
- TotalTime: ct.endTime.Sub(ct.dnsStart),
- IsConnReused: ct.gotConnInfo.Reused,
- IsConnWasIdle: ct.gotConnInfo.WasIdle,
- ConnIdleTime: ct.gotConnInfo.IdleTime,
- }
-
- // Only calcuate on successful connections
- if !ct.connectDone.IsZero() {
- ti.TCPConnTime = ct.connectDone.Sub(ct.dnsDone)
- }
-
- // Only calcuate on successful connections
- if !ct.gotConn.IsZero() {
- ti.ConnTime = ct.gotConn.Sub(ct.getConn)
- }
-
- // Only calcuate on successful connections
- if !ct.gotFirstResponseByte.IsZero() {
- ti.ResponseTime = ct.endTime.Sub(ct.gotFirstResponseByte)
- }
-
- return ti
- }
-
- //‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
- // HTTP verb method starts here
- //_______________________________________________________________________
-
- // Get method does GET HTTP request. It's defined in section 4.3.1 of RFC7231.
- func (r *Request) Get(url string) (*Response, error) {
- return r.Execute(MethodGet, url)
- }
-
- // Head method does HEAD HTTP request. It's defined in section 4.3.2 of RFC7231.
- func (r *Request) Head(url string) (*Response, error) {
- return r.Execute(MethodHead, url)
- }
-
- // Post method does POST HTTP request. It's defined in section 4.3.3 of RFC7231.
- func (r *Request) Post(url string) (*Response, error) {
- return r.Execute(MethodPost, url)
- }
-
- // Put method does PUT HTTP request. It's defined in section 4.3.4 of RFC7231.
- func (r *Request) Put(url string) (*Response, error) {
- return r.Execute(MethodPut, url)
- }
-
- // Delete method does DELETE HTTP request. It's defined in section 4.3.5 of RFC7231.
- func (r *Request) Delete(url string) (*Response, error) {
- return r.Execute(MethodDelete, url)
- }
-
- // Options method does OPTIONS HTTP request. It's defined in section 4.3.7 of RFC7231.
- func (r *Request) Options(url string) (*Response, error) {
- return r.Execute(MethodOptions, url)
- }
-
- // Patch method does PATCH HTTP request. It's defined in section 2 of RFC5789.
- func (r *Request) Patch(url string) (*Response, error) {
- return r.Execute(MethodPatch, url)
- }
-
- // Send method performs the HTTP request using the method and URL already defined
- // for current `Request`.
- // req := client.R()
- // req.Method = resty.GET
- // req.URL = "http://httpbin.org/get"
- // resp, err := client.R().Send()
- func (r *Request) Send() (*Response, error) {
- return r.Execute(r.Method, r.URL)
- }
-
- // Execute method performs the HTTP request with given HTTP method and URL
- // for current `Request`.
- // resp, err := client.R().Execute(resty.GET, "http://httpbin.org/get")
- func (r *Request) Execute(method, url string) (*Response, error) {
- var addrs []*net.SRV
- var resp *Response
- var err error
-
- if r.isMultiPart && !(method == MethodPost || method == MethodPut || method == MethodPatch) {
- return nil, fmt.Errorf("multipart content is not allowed in HTTP verb [%v]", method)
- }
-
- if r.SRV != nil {
- _, addrs, err = net.LookupSRV(r.SRV.Service, "tcp", r.SRV.Domain)
- if err != nil {
- return nil, err
- }
- }
-
- r.Method = method
- r.URL = r.selectAddr(addrs, url, 0)
-
- if r.client.RetryCount == 0 {
- resp, err = r.client.execute(r)
- return resp, unwrapNoRetryErr(err)
- }
-
- attempt := 0
- err = Backoff(
- func() (*Response, error) {
- attempt++
-
- r.URL = r.selectAddr(addrs, url, attempt)
-
- resp, err = r.client.execute(r)
- if err != nil {
- r.client.log.Errorf("%v, Attempt %v", err, attempt)
- }
-
- return resp, err
- },
- Retries(r.client.RetryCount),
- WaitTime(r.client.RetryWaitTime),
- MaxWaitTime(r.client.RetryMaxWaitTime),
- RetryConditions(r.client.RetryConditions),
- )
-
- return resp, unwrapNoRetryErr(err)
- }
-
- //‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
- // SRVRecord struct
- //_______________________________________________________________________
-
- // SRVRecord struct holds the data to query the SRV record for the
- // following service.
- type SRVRecord struct {
- Service string
- Domain string
- }
-
- //‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
- // Request Unexported methods
- //_______________________________________________________________________
-
- func (r *Request) fmtBodyString(sl int64) (body string) {
- body = "***** NO CONTENT *****"
- if !isPayloadSupported(r.Method, r.client.AllowGetMethodPayload) {
- return
- }
-
- if _, ok := r.Body.(io.Reader); ok {
- body = "***** BODY IS io.Reader *****"
- return
- }
-
- // multipart or form-data
- if r.isMultiPart || r.isFormData {
- bodySize := int64(r.bodyBuf.Len())
- if bodySize > sl {
- body = fmt.Sprintf("***** REQUEST TOO LARGE (size - %d) *****", bodySize)
- return
- }
- body = r.bodyBuf.String()
- return
- }
-
- // request body data
- if r.Body == nil {
- return
- }
- var prtBodyBytes []byte
- var err error
-
- contentType := r.Header.Get(hdrContentTypeKey)
- kind := kindOf(r.Body)
- if canJSONMarshal(contentType, kind) {
- prtBodyBytes, err = json.MarshalIndent(&r.Body, "", " ")
- } else if IsXMLType(contentType) && (kind == reflect.Struct) {
- prtBodyBytes, err = xml.MarshalIndent(&r.Body, "", " ")
- } else if b, ok := r.Body.(string); ok {
- if IsJSONType(contentType) {
- bodyBytes := []byte(b)
- out := acquireBuffer()
- defer releaseBuffer(out)
- if err = json.Indent(out, bodyBytes, "", " "); err == nil {
- prtBodyBytes = out.Bytes()
- }
- } else {
- body = b
- }
- } else if b, ok := r.Body.([]byte); ok {
- body = fmt.Sprintf("***** BODY IS byte(s) (size - %d) *****", len(b))
- return
- }
-
- if prtBodyBytes != nil && err == nil {
- body = string(prtBodyBytes)
- }
-
- if len(body) > 0 {
- bodySize := int64(len([]byte(body)))
- if bodySize > sl {
- body = fmt.Sprintf("***** REQUEST TOO LARGE (size - %d) *****", bodySize)
- }
- }
-
- return
- }
-
- func (r *Request) selectAddr(addrs []*net.SRV, path string, attempt int) string {
- if addrs == nil {
- return path
- }
-
- idx := attempt % len(addrs)
- domain := strings.TrimRight(addrs[idx].Target, ".")
- path = strings.TrimLeft(path, "/")
-
- return fmt.Sprintf("%s://%s:%d/%s", r.client.scheme, domain, addrs[idx].Port, path)
- }
-
- func (r *Request) initValuesMap() {
- if r.values == nil {
- r.values = make(map[string]interface{})
- }
- }
-
- var noescapeJSONMarshal = func(v interface{}) ([]byte, error) {
- buf := acquireBuffer()
- defer releaseBuffer(buf)
- encoder := json.NewEncoder(buf)
- encoder.SetEscapeHTML(false)
- err := encoder.Encode(v)
- return buf.Bytes(), err
- }
|