github.com/bingoohuang/gg@v0.0.0-20240325092523-45da7dee9335/pkg/resty/README.md (about)

     1  # Resty
     2  
     3  Simple HTTP and REST client library for Go (inspired by Ruby rest-client)
     4  <p><a href="#features">Features</a> section describes in detail about Resty capabilities</p>
     5  
     6  ## News
     7  
     8  1. Merge changes from  `https://github.com/go-resty/resty/compare/3d08b36...master` on 4 Nov 2021
     9  2. Merge changes from `https://github.com/go-resty/resty/compare/1792d62...master`
    10  
    11  ## Features
    12  
    13  * GET, POST, PUT, DELETE, HEAD, PATCH, OPTIONS, etc.
    14  * Simple and chainable methods for settings and request
    15  * [Request](https://pkg.go.dev/github.com/bingoohuang/resty#Request) Body can be `string`, `[]byte`, `struct`, `map`
    16    , `slice` and `io.Reader` too
    17      * Auto detects `Content-Type`
    18      * Buffer less processing for `io.Reader`
    19      * Native `*http.Request` instance may be accessed during middleware and request execution via `Request.RawRequest`
    20      * Request Body can be read multiple times via `Request.RawRequest.GetBody()`
    21  * [Response](https://pkg.go.dev/github.com/bingoohuang/resty#Response) object gives you more possibility
    22      * Access as `[]byte` array - `response.Body()` OR Access as `string` - `response.String()`
    23      * Know your `response.Time()` and when we `response.ReceivedAt()`
    24  * Automatic marshal and unmarshal for `JSON` and `XML` content type
    25      * Default is `JSON`, if you supply `struct/map` without header `Content-Type`
    26      * For auto-unmarshal, refer to -
    27          - Success scenario [Request.SetResult()](https://pkg.go.dev/github.com/bingoohuang/resty#Request.SetResult)
    28            and [Response.Result()](https://pkg.go.dev/github.com/bingoohuang/resty#Response.Result).
    29          - Error scenario [Request.SetError()](https://pkg.go.dev/github.com/bingoohuang/resty#Request.SetError)
    30            and [Response.Error()](https://pkg.go.dev/github.com/bingoohuang/resty#Response.Error).
    31          - Supports [RFC7807](https://tools.ietf.org/html/rfc7807) - `application/problem+json`
    32            & `application/problem+xml`
    33  * Easy to upload one or more file(s) via `multipart/form-data`
    34      * Auto detects file content type
    35  * Request URL [Path Params (aka URI Params)](https://pkg.go.dev/github.com/bingoohuang/resty#Request.SetPathParams)
    36  * Backoff Retry Mechanism with retry condition function [reference](retry_test.go)
    37  * Resty client HTTP & REST [Request](https://pkg.go.dev/github.com/bingoohuang/resty#Client.OnBeforeRequest)
    38    and [Response](https://pkg.go.dev/github.com/bingoohuang/resty#Client.OnAfterResponse) middlewares
    39  * `Request.SetContext` supported
    40  * Authorization option of `BasicAuth` and `Bearer` token
    41  * Set request `ContentLength` value for all request or particular request
    42  * Custom [Root Certificates](https://pkg.go.dev/github.com/bingoohuang/resty#Client.SetRootCertificate) and
    43    Client [Certificates](https://pkg.go.dev/github.com/bingoohuang/resty#Client.SetCertificates)
    44  * Download/Save HTTP response directly into File, like `curl -o` flag.
    45    See [SetOutputDirectory](https://pkg.go.dev/github.com/bingoohuang/resty#Client.SetOutputDirectory)
    46    & [SetOutput](https://pkg.go.dev/github.com/bingoohuang/resty#Request.SetOutput).
    47  * Cookies for your request and CookieJar support
    48  * SRV Record based request instead of Host URL
    49  * Client settings like `Timeout`, `RedirectPolicy`, `Proxy`, `TLSClientConfig`, `Transport`, etc.
    50  * Optionally allows GET request with payload,
    51    see [SetAllowGetMethodPayload](https://pkg.go.dev/github.com/bingoohuang/resty#Client.SetAllowGetMethodPayload)
    52  * Supports registering external JSON library into resty,
    53    see [how to use](https://github.com/go-resty/resty/issues/76#issuecomment-314015250)
    54  * Exposes Response reader without reading response (no auto-unmarshaling) if need be,
    55    see [how to use](https://github.com/go-resty/resty/issues/87#issuecomment-322100604)
    56  * Option to specify expected `Content-Type` when response `Content-Type` header missing. Refer
    57    to [#92](https://github.com/go-resty/resty/issues/92)
    58  * Resty design
    59      * Have client level settings & options and also override at Request level if you want to
    60      * Request and Response middleware
    61      * Create Multiple clients if you want to `resty.New()`
    62      * Supports `http.RoundTripper` implementation,
    63        see [SetTransport](https://pkg.go.dev/github.com/bingoohuang/resty#Client.SetTransport)
    64      * goroutine concurrent safe
    65      * Resty Client trace, see [Client.EnableTrace](https://pkg.go.dev/github.com/bingoohuang/resty#Client.EnableTrace)
    66        and [Request.EnableTrace](https://pkg.go.dev/github.com/bingoohuang/resty#Request.EnableTrace)
    67          * Since v2.4.0, trace info contains a `RequestAttempt` value, and the `Request` object contains an `Attempt`
    68            attribute
    69      * Debug mode - clean and informative logging presentation
    70      * Gzip - Go does it automatically also resty has fallback handling too
    71      * Works fine with `HTTP/2` and `HTTP/1.1`
    72  * [Bazel support](#bazel-support)
    73  * Easily mock Resty for testing, [for e.g.](#mocking-http-requests-using-httpmock-library)
    74  * Well tested client library
    75  
    76  ### Included Batteries
    77  
    78  * Redirect Policies - see [how to use](#redirect-policy)
    79      * NoRedirectPolicy
    80      * FlexibleRedirectPolicy
    81      * DomainCheckRedirectPolicy
    82      * etc. [more info](redirect.go)
    83  * Retry Mechanism [how to use](#retries)
    84      * Backoff Retry
    85      * Conditional Retry
    86      * Since v2.6.0, Retry Hooks - [Client](https://pkg.go.dev/github.com/bingoohuang/resty#Client.AddRetryHook)
    87        , [Request](https://pkg.go.dev/github.com/bingoohuang/resty#Request.AddRetryHook)
    88  * SRV Record based request instead of Host URL [how to use](resty_test.go#L1412)
    89  * etc (upcoming - throw your idea's [here](https://github.com/go-resty/resty/issues)).
    90  
    91  #### Supported Go Versions
    92  
    93  Initially Resty started supporting `go modules` since `v1.10.0` release.
    94  
    95  Starting Resty v2 and higher versions, it fully embraces [go modules](https://github.com/golang/go/wiki/Modules) package
    96  release. It requires a Go version capable of understanding `/vN` suffixed imports:
    97  
    98  - 1.9.7+
    99  - 1.10.3+
   100  - 1.11+
   101  
   102  ## It might be beneficial for your project :smile:
   103  
   104  Resty author also published following projects for Go Community.
   105  
   106  * [aah framework](https://aahframework.org) - A secure, flexible, rapid Go web framework.
   107  * [THUMBAI](https://thumbai.app) - Go Mod Repository, Go Vanity Service and Simple Proxy Server.
   108  * [go-model](https://github.com/jeevatkm/go-model) - Robust & Easy to use model mapper and utility methods for
   109    Go `struct`.
   110  
   111  ## Usage
   112  
   113  The following samples will assist you to become as comfortable as possible with resty library.
   114  
   115  ```go
   116  // Import resty into your code and refer it as `resty`.
   117  import "github.com/bingoohuang/resty"
   118  ```
   119  
   120  #### Simple GET
   121  
   122  ```go
   123  // Create a Resty Client
   124  client := resty.New()
   125  
   126  resp, err := client.R().
   127  EnableTrace().
   128  Get("https://httpbin.org/get")
   129  
   130  // Explore response object
   131  fmt.Println("Response Info:")
   132  fmt.Println("  Error      :", err)
   133  fmt.Println("  Status Code:", resp.StatusCode())
   134  fmt.Println("  Status     :", resp.Status())
   135  fmt.Println("  Proto      :", resp.Proto())
   136  fmt.Println("  Time       :", resp.Time())
   137  fmt.Println("  Received At:", resp.ReceivedAt())
   138  fmt.Println("  Body       :\n", resp)
   139  fmt.Println()
   140  
   141  // Explore trace info
   142  fmt.Println("Request Trace Info:")
   143  ti := resp.Request.TraceInfo()
   144  fmt.Println("  DNSLookup     :", ti.DNSLookup)
   145  fmt.Println("  ConnTime      :", ti.ConnTime)
   146  fmt.Println("  TCPConnTime   :", ti.TCPConnTime)
   147  fmt.Println("  TLSHandshake  :", ti.TLSHandshake)
   148  fmt.Println("  ServerTime    :", ti.ServerTime)
   149  fmt.Println("  ResponseTime  :", ti.ResponseTime)
   150  fmt.Println("  TotalTime     :", ti.TotalTime)
   151  fmt.Println("  IsConnReused  :", ti.IsConnReused)
   152  fmt.Println("  IsConnWasIdle :", ti.IsConnWasIdle)
   153  fmt.Println("  ConnIdleTime  :", ti.ConnIdleTime)
   154  fmt.Println("  RequestAttempt:", ti.RequestAttempt)
   155  fmt.Println("  RemoteAddr    :", ti.RemoteAddr.String())
   156  
   157  /* Output
   158  Response Info:
   159    Error      : <nil>
   160    Status Code: 200
   161    Status     : 200 OK
   162    Proto      : HTTP/2.0
   163    Time       : 457.034718ms
   164    Received At: 2020-09-14 15:35:29.784681 -0700 PDT m=+0.458137045
   165    Body       :
   166    {
   167      "args": {},
   168      "headers": {
   169        "Accept-Encoding": "gzip",
   170        "Host": "httpbin.org",
   171        "User-Agent": "go-resty/2.4.0 (https://github.com/go-resty/resty)",
   172        "X-Amzn-Trace-Id": "Root=1-5f5ff031-000ff6292204aa6898e4de49"
   173      },
   174      "origin": "0.0.0.0",
   175      "url": "https://httpbin.org/get"
   176    }
   177  
   178  Request Trace Info:
   179    DNSLookup     : 4.074657ms
   180    ConnTime      : 381.709936ms
   181    TCPConnTime   : 77.428048ms
   182    TLSHandshake  : 299.623597ms
   183    ServerTime    : 75.414703ms
   184    ResponseTime  : 79.337µs
   185    TotalTime     : 457.034718ms
   186    IsConnReused  : false
   187    IsConnWasIdle : false
   188    ConnIdleTime  : 0s
   189    RequestAttempt: 1
   190    RemoteAddr    : 3.221.81.55:443
   191  */
   192  ```
   193  
   194  #### Enhanced GET
   195  
   196  ```go
   197  // Create a Resty Client
   198  client := resty.New()
   199  
   200  resp, err := client.R().
   201  SetQueryParams(map[string]string{
   202  "page_no": "1",
   203  "limit": "20",
   204  "sort":"name",
   205  "order": "asc",
   206  "random":strconv.FormatInt(time.Now().Unix(), 10),
   207  }).
   208  SetHeader("Accept", "application/json").
   209  SetAuthToken("BC594900518B4F7EAC75BD37F019E08FBC594900518B4F7EAC75BD37F019E08F").
   210  Get("/search_result")
   211  
   212  // Sample of using Request.SetQueryString method
   213  resp, err := client.R().
   214  SetQueryString("productId=232&template=fresh-sample&cat=resty&source=google&kw=buy a lot more").
   215  SetHeader("Accept", "application/json").
   216  SetAuthToken("BC594900518B4F7EAC75BD37F019E08FBC594900518B4F7EAC75BD37F019E08F").
   217  Get("/show_product")
   218  
   219  
   220  // If necessary, you can force response content type to tell Resty to parse a JSON response into your struct
   221  resp, err := client.R().
   222  SetResult(result).
   223  ForceContentType("application/json").
   224  Get("v2/alpine/manifests/latest")
   225  ```
   226  
   227  #### Various POST method combinations
   228  
   229  ```go
   230  // Create a Resty Client
   231  client := resty.New()
   232  
   233  // POST JSON string
   234  // No need to set content type, if you have client level setting
   235  resp, err := client.R().
   236  SetHeader("Content-Type", "application/json").
   237  SetBody(`{"username":"testuser", "password":"testpass"}`).
   238  SetResult(&AuthSuccess{}). // or SetResult(AuthSuccess{}).
   239  Post("https://myapp.com/login")
   240  
   241  // POST []byte array
   242  // No need to set content type, if you have client level setting
   243  resp, err := client.R().
   244  SetHeader("Content-Type", "application/json").
   245  SetBody([]byte(`{"username":"testuser", "password":"testpass"}`)).
   246  SetResult(&AuthSuccess{}). // or SetResult(AuthSuccess{}).
   247  Post("https://myapp.com/login")
   248  
   249  // POST Struct, default is JSON content type. No need to set one
   250  resp, err := client.R().
   251  SetBody(User{Username: "testuser", Password: "testpass"}).
   252  SetResult(&AuthSuccess{}). // or SetResult(AuthSuccess{}).
   253  SetError(&AuthError{}). // or SetError(AuthError{}).
   254  Post("https://myapp.com/login")
   255  
   256  // POST Map, default is JSON content type. No need to set one
   257  resp, err := client.R().
   258  SetBody(map[string]interface{}{"username": "testuser", "password": "testpass"}).
   259  SetResult(&AuthSuccess{}). // or SetResult(AuthSuccess{}).
   260  SetError(&AuthError{}). // or SetError(AuthError{}).
   261  Post("https://myapp.com/login")
   262  
   263  // POST of raw bytes for file upload. For example: upload file to Dropbox
   264  fileBytes, _ := os.ReadFile("/Users/jeeva/mydocument.pdf")
   265  
   266  // See we are not setting content-type header, since go-resty automatically detects Content-Type for you
   267  resp, err := client.R().
   268  SetBody(fileBytes).
   269  SetContentLength(true). // Dropbox expects this value
   270  SetAuthToken("<your-auth-token>").
   271  SetError(&DropboxError{}).                                                   // or SetError(DropboxError{}).
   272  Post("https://content.dropboxapi.com/1/files_put/auto/resty/mydocument.pdf") // for upload Dropbox supports PUT too
   273  
   274  // Note: resty detects Content-Type for request body/payload if content type header is not set.
   275  //   * For struct and map data type defaults to 'application/json'
   276  //   * Fallback is plain text content type
   277  ```
   278  
   279  #### Sample PUT
   280  
   281  You can use various combinations of `PUT` method call like demonstrated for `POST`.
   282  
   283  ```go
   284  // Note: This is one sample of PUT method usage, refer POST for more combination
   285  
   286  // Create a Resty Client
   287  client := resty.New()
   288  
   289  // Request goes as JSON content type
   290  // No need to set auth token, error, if you have client level settings
   291  resp, err := client.R().
   292  SetBody(Article{
   293  Title: "go-resty",
   294  Content: "This is my article content, oh ya!",
   295  Author: "Jeevanandam M",
   296  Tags: []string{"article", "sample", "resty"},
   297  }).
   298  SetAuthToken("C6A79608-782F-4ED0-A11D-BD82FAD829CD").
   299  SetError(&Error{}). // or SetError(Error{}).
   300  Put("https://myapp.com/article/1234")
   301  ```
   302  
   303  #### Sample PATCH
   304  
   305  You can use various combinations of `PATCH` method call like demonstrated for `POST`.
   306  
   307  ```go
   308  // Note: This is one sample of PUT method usage, refer POST for more combination
   309  
   310  // Create a Resty Client
   311  client := resty.New()
   312  
   313  // Request goes as JSON content type
   314  // No need to set auth token, error, if you have client level settings
   315  resp, err := client.R().
   316  SetBody(Article{
   317  Tags: []string{"new tag1", "new tag2"},
   318  }).
   319  SetAuthToken("C6A79608-782F-4ED0-A11D-BD82FAD829CD").
   320  SetError(&Error{}). // or SetError(Error{}).
   321  Patch("https://myapp.com/articles/1234")
   322  ```
   323  
   324  #### Sample DELETE, HEAD, OPTIONS
   325  
   326  ```go
   327  // Create a Resty Client
   328  client := resty.New()
   329  
   330  // DELETE a article
   331  // No need to set auth token, error, if you have client level settings
   332  resp, err := client.R().
   333  SetAuthToken("C6A79608-782F-4ED0-A11D-BD82FAD829CD").
   334  SetError(&Error{}). // or SetError(Error{}).
   335  Delete("https://myapp.com/articles/1234")
   336  
   337  // DELETE a articles with payload/body as a JSON string
   338  // No need to set auth token, error, if you have client level settings
   339  resp, err := client.R().
   340  SetAuthToken("C6A79608-782F-4ED0-A11D-BD82FAD829CD").
   341  SetError(&Error{}). // or SetError(Error{}).
   342  SetHeader("Content-Type", "application/json").
   343  SetBody(`{article_ids: [1002, 1006, 1007, 87683, 45432] }`).
   344  Delete("https://myapp.com/articles")
   345  
   346  // HEAD of resource
   347  // No need to set auth token, if you have client level settings
   348  resp, err := client.R().
   349  SetAuthToken("C6A79608-782F-4ED0-A11D-BD82FAD829CD").
   350  Head("https://myapp.com/videos/hi-res-video")
   351  
   352  // OPTIONS of resource
   353  // No need to set auth token, if you have client level settings
   354  resp, err := client.R().
   355  SetAuthToken("C6A79608-782F-4ED0-A11D-BD82FAD829CD").
   356  Options("https://myapp.com/servers/nyc-dc-01")
   357  ```
   358  
   359  #### Override JSON & XML Marshal/Unmarshal
   360  
   361  User could register choice of JSON/XML library into resty or write your own. By default resty registers
   362  standard `encoding/json` and `encoding/xml` respectively.
   363  
   364  ```go
   365  // Example of registering json-iterator
   366  import jsoniter "github.com/json-iterator/go"
   367  
   368  json := jsoniter.ConfigCompatibleWithStandardLibrary
   369  
   370  client := resty.New()
   371  client.JSONMarshal = json.Marshal
   372  client.JSONUnmarshal = json.Unmarshal
   373  // similarly, user could do for XML too with -
   374  client.XMLMarshal
   375  client.XMLUnmarshal
   376  ```
   377  
   378  ### Multipart File(s) upload
   379  
   380  #### Using io.Reader
   381  
   382  ```go
   383  profileImgBytes, _ := os.ReadFile("/Users/jeeva/test-img.png")
   384  notesBytes, _ := os.ReadFile("/Users/jeeva/text-file.txt")
   385  
   386  // Create a Resty Client
   387  client := resty.New()
   388  
   389  resp, err := client.R().
   390  SetFileReader("profile_img", "test-img.png", bytes.NewReader(profileImgBytes)).
   391  SetFileReader("notes", "text-file.txt", bytes.NewReader(notesBytes)).
   392  SetFormData(map[string]string{
   393  "first_name": "Jeevanandam",
   394  "last_name": "M",
   395  }).
   396  Post("http://myapp.com/upload")
   397  ```
   398  
   399  #### Using File directly from Path
   400  
   401  ```go
   402  // Create a Resty Client
   403  client := resty.New()
   404  
   405  // Single file scenario
   406  resp, err := client.R().
   407  SetFile("profile_img", "/Users/jeeva/test-img.png").
   408  Post("http://myapp.com/upload")
   409  
   410  // Multiple files scenario
   411  resp, err := client.R().
   412  SetFiles(map[string]string{
   413  "profile_img": "/Users/jeeva/test-img.png",
   414  "notes": "/Users/jeeva/text-file.txt",
   415  }).
   416  Post("http://myapp.com/upload")
   417  
   418  // Multipart of form fields and files
   419  resp, err := client.R().
   420  SetFiles(map[string]string{
   421  "profile_img": "/Users/jeeva/test-img.png",
   422  "notes": "/Users/jeeva/text-file.txt",
   423  }).
   424  SetFormData(map[string]string{
   425  "first_name": "Jeevanandam",
   426  "last_name": "M",
   427  "zip_code": "00001",
   428  "city": "my city",
   429  "access_token": "C6A79608-782F-4ED0-A11D-BD82FAD829CD",
   430  }).
   431  Post("http://myapp.com/profile")
   432  ```
   433  
   434  #### Sample Form submission
   435  
   436  ```go
   437  // Create a Resty Client
   438  client := resty.New()
   439  
   440  // just mentioning about POST as an example with simple flow
   441  // User Login
   442  resp, err := client.R().
   443  SetFormData(map[string]string{
   444  "username": "jeeva",
   445  "password": "mypass",
   446  }).
   447  Post("http://myapp.com/login")
   448  
   449  // Followed by profile update
   450  resp, err := client.R().
   451  SetFormData(map[string]string{
   452  "first_name": "Jeevanandam",
   453  "last_name": "M",
   454  "zip_code": "00001",
   455  "city": "new city update",
   456  }).
   457  Post("http://myapp.com/profile")
   458  
   459  // Multi value form data
   460  criteria := url.Values{
   461  "search_criteria": []string{"book", "glass", "pencil"},
   462  }
   463  resp, err := client.R().
   464  SetFormDataFromValues(criteria).
   465  Post("http://myapp.com/search")
   466  ```
   467  
   468  #### Save HTTP Response into File
   469  
   470  ```go
   471  // Create a Resty Client
   472  client := resty.New()
   473  
   474  // Setting output directory path, If directory not exists then resty creates one!
   475  // This is optional one, if you're planning using absoule path in
   476  // `Request.SetOutput` and can used together.
   477  client.SetOutputDirectory("/Users/jeeva/Downloads")
   478  
   479  // HTTP response gets saved into file, similar to curl -o flag
   480  _, err := client.R().
   481  SetOutput("plugin/ReplyWithHeader-v5.1-beta.zip").
   482  Get("http://bit.ly/1LouEKr")
   483  
   484  // OR using absolute path
   485  // Note: output directory path is not used for absolute path
   486  _, err := client.R().
   487  SetOutput("/MyDownloads/plugin/ReplyWithHeader-v5.1-beta.zip").
   488  Get("http://bit.ly/1LouEKr")
   489  ```
   490  
   491  #### Request URL Path Params
   492  
   493  Resty provides easy to use dynamic request URL path params. Params can be set at client and request level. Client level
   494  params value can be overridden at request level.
   495  
   496  ```go
   497  // Create a Resty Client
   498  client := resty.New()
   499  
   500  client.R().SetPathParams(map[string]string{
   501  "userId": "sample@sample.com",
   502  "subAccountId": "100002",
   503  }).
   504  Get("/v1/users/{userId}/{subAccountId}/details")
   505  
   506  // Result:
   507  //   Composed URL - /v1/users/sample@sample.com/100002/details
   508  ```
   509  
   510  #### Request and Response Middleware
   511  
   512  Resty provides middleware ability to manipulate for Request and Response. It is more flexible than callback approach.
   513  
   514  ```go
   515  // Create a Resty Client
   516  client := resty.New()
   517  
   518  // Registering Request Middleware
   519  client.OnBeforeRequest(func (c *resty.Client, req *resty.Request) error {
   520  // Now you have access to Client and current Request object
   521  // manipulate it as per your need
   522  
   523  return nil // if its success otherwise return error
   524  })
   525  
   526  // Registering Response Middleware
   527  client.OnAfterResponse(func (c *resty.Client, resp *resty.Response) error {
   528  // Now you have access to Client and current Response object
   529  // manipulate it as per your need
   530  
   531  return nil // if its success otherwise return error
   532  })
   533  ```
   534  
   535  #### OnError Hooks
   536  
   537  Resty provides OnError hooks that may be called because:
   538  
   539  - The client failed to send the request due to connection timeout, TLS handshake failure, etc...
   540  - The request was retried the maximum amount of times, and still failed.
   541  
   542  If there was a response from the server, the original error will be wrapped in `*resty.ResponseError` which contains the
   543  last response received.
   544  
   545  ```go
   546  // Create a Resty Client
   547  client := resty.New()
   548  
   549  client.OnError(func (req *resty.Request, err error) {
   550  if v, ok := err.(*resty.ResponseError); ok {
   551  // v.Response contains the last response from the server
   552  // v.Err contains the original error
   553  }
   554  // Log the error, increment a metric, etc...
   555  })
   556  ```
   557  
   558  #### Redirect Policy
   559  
   560  Resty provides few ready to use redirect policy(s) also it supports multiple policies together.
   561  
   562  ```go
   563  // Create a Resty Client
   564  client := resty.New()
   565  
   566  // Assign Client Redirect Policy. Create one as per you need
   567  client.SetRedirectPolicy(resty.FlexibleRedirectPolicy(15))
   568  
   569  // Wanna multiple policies such as redirect count, domain name check, etc
   570  client.SetRedirectPolicy(resty.FlexibleRedirectPolicy(20),
   571  resty.DomainCheckRedirectPolicy("host1.com", "host2.org", "host3.net"))
   572  ```
   573  
   574  ##### Custom Redirect Policy
   575  
   576  Implement [RedirectPolicy](redirect.go#L20) interface and register it with resty client. Have a
   577  look [redirect.go](redirect.go) for more information.
   578  
   579  ```go
   580  // Create a Resty Client
   581  client := resty.New()
   582  
   583  // Using raw func into resty.SetRedirectPolicy
   584  client.SetRedirectPolicy(resty.RedirectPolicyFunc(func (req *http.Request, via []*http.Request) error {
   585  // Implement your logic here
   586  
   587  // return nil for continue redirect otherwise return error to stop/prevent redirect
   588  return nil
   589  }))
   590  
   591  //---------------------------------------------------
   592  
   593  // Using struct create more flexible redirect policy
   594  type CustomRedirectPolicy struct {
   595  // variables goes here
   596  }
   597  
   598  func (c *CustomRedirectPolicy) Apply(req *http.Request, via []*http.Request) error {
   599  // Implement your logic here
   600  
   601  // return nil for continue redirect otherwise return error to stop/prevent redirect
   602  return nil
   603  }
   604  
   605  // Registering in resty
   606  client.SetRedirectPolicy(CustomRedirectPolicy{ /* initialize variables */ })
   607  ```
   608  
   609  #### Custom Root Certificates and Client Certificates
   610  
   611  ```go
   612  // Create a Resty Client
   613  client := resty.New()
   614  
   615  // Custom Root certificates, just supply .pem file.
   616  // you can add one or more root certificates, its get appended
   617  client.SetRootCertificate("/path/to/root/pemFile1.pem")
   618  client.SetRootCertificate("/path/to/root/pemFile2.pem")
   619  // ... and so on!
   620  
   621  // Adding Client Certificates, you add one or more certificates
   622  // Sample for creating certificate object
   623  // Parsing public/private key pair from a pair of files. The files must contain PEM encoded data.
   624  cert1, err := tls.LoadX509KeyPair("certs/client.pem", "certs/client.key")
   625  if err != nil {
   626  log.Fatalf("ERROR client certificate: %s", err)
   627  }
   628  // ...
   629  
   630  // You add one or more certificates
   631  client.SetCertificates(cert1, cert2, cert3)
   632  ```
   633  
   634  #### Custom Root Certificates and Client Certificates from string
   635  
   636  ```go
   637  // Custom Root certificates from string
   638  // You can pass you certificates throught env variables as strings
   639  // you can add one or more root certificates, its get appended
   640  client.SetRootCertificateFromString("-----BEGIN CERTIFICATE-----content-----END CERTIFICATE-----")
   641  client.SetRootCertificateFromString("-----BEGIN CERTIFICATE-----content-----END CERTIFICATE-----")
   642  // ... and so on!
   643  
   644  // Adding Client Certificates, you add one or more certificates
   645  // Sample for creating certificate object
   646  // Parsing public/private key pair from a pair of files. The files must contain PEM encoded data.
   647  cert1, err := tls.X509KeyPair([]byte("-----BEGIN CERTIFICATE-----content-----END CERTIFICATE-----"), []byte("-----BEGIN CERTIFICATE-----content-----END CERTIFICATE-----"))
   648  if err != nil {
   649  log.Fatalf("ERROR client certificate: %s", err)
   650  }
   651  // ...
   652  
   653  // You add one or more certificates
   654  client.SetCertificates(cert1, cert2, cert3)
   655  ```
   656  
   657  #### Proxy Settings - Client as well as at Request Level
   658  
   659  Default `Go` supports Proxy via environment variable `HTTP_PROXY`. Resty provides support via `SetProxy` & `RemoveProxy`
   660  . Choose as per your need.
   661  
   662  **Client Level Proxy** settings applied to all the request
   663  
   664  ```go
   665  // Create a Resty Client
   666  client := resty.New()
   667  
   668  // Setting a Proxy URL and Port
   669  client.SetProxy("http://proxyserver:8888")
   670  
   671  // Want to remove proxy setting
   672  client.RemoveProxy()
   673  ```
   674  
   675  #### Retries
   676  
   677  Resty uses [backoff](http://www.awsarchitectureblog.com/2015/03/backoff.html)
   678  to increase retry intervals after each attempt.
   679  
   680  Usage example:
   681  
   682  ```go
   683  // Create a Resty Client
   684  client := resty.New()
   685  
   686  // Retries are configured per client
   687  client.
   688  // Set retry count to non zero to enable retries
   689  SetRetryCount(3).
   690  // You can override initial retry wait time.
   691  // Default is 100 milliseconds.
   692  SetRetryWaitTime(5 * time.Second).
   693  // MaxWaitTime can be overridden as well.
   694  // Default is 2 seconds.
   695  SetRetryMaxWaitTime(20 * time.Second).
   696  // SetRetryAfter sets callback to calculate wait time between retries.
   697  // Default (nil) implies exponential backoff with jitter
   698  SetRetryAfter(func (client *resty.Client, resp *resty.Response) (time.Duration, error) {
   699  return 0, errors.New("quota exceeded")
   700  })
   701  ```
   702  
   703  Above setup will result in resty retrying requests returned non nil error up to 3 times with delay increased after each
   704  attempt.
   705  
   706  You can optionally provide client
   707  with [custom retry conditions](https://pkg.go.dev/github.com/bingoohuang/resty#RetryConditionFunc):
   708  
   709  ```go
   710  // Create a Resty Client
   711  client := resty.New()
   712  
   713  client.AddRetryCondition(
   714  // RetryConditionFunc type is for retry condition function
   715  // input: non-nil Response OR request execution error
   716  func (r *resty.Response, err error) bool {
   717  return r.StatusCode() == http.StatusTooManyRequests
   718  },
   719  )
   720  ```
   721  
   722  Above example will make resty retry requests ended with `429 Too Many Requests`
   723  status code.
   724  
   725  Multiple retry conditions can be added.
   726  
   727  It is also possible to use `resty.Backoff(...)` to get arbitrary retry scenarios implemented. [Reference](retry_test.go)
   728  .
   729  
   730  #### Allow GET request with Payload
   731  
   732  ```go
   733  // Create a Resty Client
   734  client := resty.New()
   735  
   736  // Allow GET request with Payload. This is disabled by default.
   737  client.SetAllowGetMethodPayload(true)
   738  ```
   739  
   740  #### Wanna Multiple Clients
   741  
   742  ```go
   743  // Here you go!
   744  // Client 1
   745  client1 := resty.New()
   746  client1.R().Get("http://httpbin.org")
   747  // ...
   748  
   749  // Client 2
   750  client2 := resty.New()
   751  client2.R().Head("http://httpbin.org")
   752  // ...
   753  
   754  // Bend it as per your need!!!
   755  ```
   756  
   757  #### Remaining Client Settings & its Options
   758  
   759  ```go
   760  // Create a Resty Client
   761  client := resty.New()
   762  
   763  // Unique settings at Client level
   764  //--------------------------------
   765  // Enable debug mode
   766  client.SetDebug(true)
   767  
   768  // Assign Client TLSClientConfig
   769  // One can set custom root-certificate. Refer: http://golang.org/pkg/crypto/tls/#example_Dial
   770  client.SetTLSClientConfig(&tls.Config{ RootCAs: roots })
   771  
   772  // or One can disable security check (https)
   773  client.SetTLSClientConfig(&tls.Config{ InsecureSkipVerify: true })
   774  
   775  // Set client timeout as per your need
   776  client.SetTimeout(1 * time.Minute)
   777  
   778  // You can override all below settings and options at request level if you want to
   779  //--------------------------------------------------------------------------------
   780  // Host URL for all request. So you can use relative URL in the request
   781  client.SetHostURL("http://httpbin.org")
   782  
   783  // Headers for all request
   784  client.SetHeader("Accept", "application/json")
   785  client.SetHeaders(map[string]string{
   786  "Content-Type": "application/json",
   787  "User-Agent": "My custom User Agent String",
   788  })
   789  
   790  // Cookies for all request
   791  client.SetCookie(&http.Cookie{
   792  Name:"go-resty",
   793  Value:"This is cookie value",
   794  Path: "/",
   795  Domain: "sample.com",
   796  MaxAge: 36000,
   797  HttpOnly: true,
   798  Secure: false,
   799  })
   800  client.SetCookies(cookies)
   801  
   802  // URL query parameters for all request
   803  client.SetQueryParam("user_id", "00001")
   804  client.SetQueryParams(map[string]string{ // sample of those who use this manner
   805  "api_key": "api-key-here",
   806  "api_secert": "api-secert",
   807  })
   808  client.R().SetQueryString("productId=232&template=fresh-sample&cat=resty&source=google&kw=buy a lot more")
   809  
   810  // Form data for all request. Typically used with POST and PUT
   811  client.SetFormData(map[string]string{
   812  "access_token": "BC594900-518B-4F7E-AC75-BD37F019E08F",
   813  })
   814  
   815  // Basic Auth for all request
   816  client.SetBasicAuth("myuser", "mypass")
   817  
   818  // Bearer Auth Token for all request
   819  client.SetAuthToken("BC594900518B4F7EAC75BD37F019E08FBC594900518B4F7EAC75BD37F019E08F")
   820  
   821  // Enabling Content length value for all request
   822  client.SetContentLength(true)
   823  
   824  // Registering global Error object structure for JSON/XML request
   825  client.SetError(&Error{}) // or resty.SetError(Error{})
   826  ```
   827  
   828  #### Unix Socket
   829  
   830  ```go
   831  unixSocket := "/var/run/my_socket.sock"
   832  
   833  // Create a Go's http.Transport so we can set it in resty.
   834  transport := http.Transport{
   835  Dial: func (_, _ string) (net.Conn, error) {
   836  return net.Dial("unix", unixSocket)
   837  },
   838  }
   839  
   840  // Create a Resty Client
   841  client := resty.New()
   842  
   843  // Set the previous transport that we created, set the scheme of the communication to the
   844  // socket and set the unixSocket as the HostURL.
   845  client.SetTransport(&transport).SetScheme("http").SetHostURL(unixSocket)
   846  
   847  // No need to write the host's URL on the request, just the path.
   848  client.R().Get("/index.html")
   849  ```
   850  
   851  #### Bazel Support
   852  
   853  Resty can be built, tested and depended upon via [Bazel](https://bazel.build). For example, to run all tests:
   854  
   855  ```shell
   856  bazel test :resty_test
   857  ```
   858  
   859  #### Mocking http requests using [httpmock](https://github.com/jarcoal/httpmock) library
   860  
   861  In order to mock the http requests when testing your application you could use the `httpmock` library.
   862  
   863  When using the default resty client, you should pass the client to the library as follow:
   864  
   865  ```go
   866  // Create a Resty Client
   867  client := resty.New()
   868  
   869  // Get the underlying HTTP Client and set it to Mock
   870  httpmock.ActivateNonDefault(client.GetClient())
   871  ```
   872  
   873  More detailed example of mocking resty http requests using ginko could be
   874  found [here](https://github.com/jarcoal/httpmock#ginkgo--resty-example).
   875  
   876  ## Versioning
   877  
   878  Resty releases versions according to [Semantic Versioning](http://semver.org)
   879  
   880  * Resty v2 does not use `gopkg.in` service for library versioning.
   881  * Resty fully adapted to `go mod` capabilities since `v1.10.0` release.
   882  * Resty v1 series was using `gopkg.in` to provide versioning. `gopkg.in/resty.vX` points to appropriate tagged
   883    versions; `X` denotes version series number and it's a stable release for production use. For e.g. `gopkg.in/resty.v0`
   884    .
   885  * Development takes place at the master branch. Although the code in master should always compile and test successfully,
   886    it might break API's. I aim to maintain backwards compatibility, but sometimes API's and behavior might be changed to
   887    fix a bug.
   888  
   889  ## Contribution
   890  
   891  I would welcome your contribution! If you find any improvement or issue you want to fix, feel free to send a pull
   892  request, I like pull requests that include test cases for fix/enhancement. I have done my best to bring pretty good code
   893  coverage. Feel free to write tests.
   894  
   895  BTW, I'd like to know what you think about `Resty`. Kindly open an issue or send me an email; it'd mean a lot to me.
   896  
   897  ## Creator
   898  
   899  [Jeevanandam M.](https://github.com/jeevatkm) (jeeva@myjeeva.com)
   900  
   901  ## Core Team
   902  
   903  Have a look on [Members](https://github.com/orgs/go-resty/people) page.
   904  
   905  ## Contributors
   906  
   907  Have a look on [Contributors](https://github.com/go-resty/resty/graphs/contributors) page.
   908  
   909  ## License
   910  
   911  Resty released under MIT license, refer [LICENSE](LICENSE) file.