Simple HTTP and REST client library for Go

Resty

Simple HTTP and REST client library for Go (inspired by Ruby rest-client)

Features section describes in detail about Resty capabilities

Build Status Code Coverage Go Report Card Release Version GoDoc License Mentioned in Awesome Go

Resty Communication Channels

Chat on Gitter - Resty Community Twitter @go_resty

News

  • v2.4.0 released and tagged on Jan 11, 2021.
  • v2.3.0 released and tagged on May 20, 2020.
  • v2.0.0 released and tagged on Jul 16, 2019.
  • v1.12.0 released and tagged on Feb 27, 2019.
  • v1.0 released and tagged on Sep 25, 2017. - Resty's first version was released on Sep 15, 2015 then it grew gradually as a very handy and helpful library. Its been a two years since first release. I'm very thankful to Resty users and its contributors.

Features

  • GET, POST, PUT, DELETE, HEAD, PATCH, OPTIONS, etc.
  • Simple and chainable methods for settings and request
  • Request Body can be string, []byte, struct, map, slice and io.Reader too
    • Auto detects Content-Type
    • Buffer less processing for io.Reader
    • Native *http.Request instance may be accessed during middleware and request execution via Request.RawRequest
    • Request Body can be read multiple times via Request.RawRequest.GetBody()
  • Response object gives you more possibility
    • Access as []byte array - response.Body() OR Access as string - response.String()
    • Know your response.Time() and when we response.ReceivedAt()
  • Automatic marshal and unmarshal for JSON and XML content type
  • Easy to upload one or more file(s) via multipart/form-data
    • Auto detects file content type
  • Request URL Path Params (aka URI Params)
  • Backoff Retry Mechanism with retry condition function reference
  • Resty client HTTP & REST Request and Response middlewares
  • Request.SetContext supported
  • Authorization option of BasicAuth and Bearer token
  • Set request ContentLength value for all request or particular request
  • Custom Root Certificates and Client Certificates
  • Download/Save HTTP response directly into File, like curl -o flag. See SetOutputDirectory & SetOutput.
  • Cookies for your request and CookieJar support
  • SRV Record based request instead of Host URL
  • Client settings like Timeout, RedirectPolicy, Proxy, TLSClientConfig, Transport, etc.
  • Optionally allows GET request with payload, see SetAllowGetMethodPayload
  • Supports registering external JSON library into resty, see how to use
  • Exposes Response reader without reading response (no auto-unmarshaling) if need be, see how to use
  • Option to specify expected Content-Type when response Content-Type header missing. Refer to #92
  • Resty design
    • Have client level settings & options and also override at Request level if you want to
    • Request and Response middleware
    • Create Multiple clients if you want to resty.New()
    • Supports http.RoundTripper implementation, see SetTransport
    • goroutine concurrent safe
    • Resty Client trace, see Client.EnableTrace and Request.EnableTrace
      • Since v2.4.0, trace info contains a RequestAttempt value, and the Request object contains an Attempt attribute
    • Debug mode - clean and informative logging presentation
    • Gzip - Go does it automatically also resty has fallback handling too
    • Works fine with HTTP/2 and HTTP/1.1
  • Bazel support
  • Easily mock Resty for testing, for e.g.
  • Well tested client library

Included Batteries

  • Redirect Policies - see how to use
    • NoRedirectPolicy
    • FlexibleRedirectPolicy
    • DomainCheckRedirectPolicy
    • etc. more info
  • Retry Mechanism how to use
    • Backoff Retry
    • Conditional Retry
  • SRV Record based request instead of Host URL how to use
  • etc (upcoming - throw your idea's here).

Supported Go Versions

Initially Resty started supporting go modules since v1.10.0 release.

Starting Resty v2 and higher versions, it fully embraces go modules package release. It requires a Go version capable of understanding /vN suffixed imports:

  • 1.9.7+
  • 1.10.3+
  • 1.11+

It might be beneficial for your project 😄

Resty author also published following projects for Go Community.

  • aah framework - A secure, flexible, rapid Go web framework.
  • THUMBAI - Go Mod Repository, Go Vanity Service and Simple Proxy Server.
  • go-model - Robust & Easy to use model mapper and utility methods for Go struct.

Installation

# Go Modules
require github.com/go-resty/resty/v2 v2.4.0

Usage

The following samples will assist you to become as comfortable as possible with resty library.

// Import resty into your code and refer it as `resty`.
import "github.com/go-resty/resty/v2"

Simple GET

// Create a Resty Client
client := resty.New()

resp, err := client.R().
    EnableTrace().
    Get("https://httpbin.org/get")

// Explore response object
fmt.Println("Response Info:")
fmt.Println("  Error      :", err)
fmt.Println("  Status Code:", resp.StatusCode())
fmt.Println("  Status     :", resp.Status())
fmt.Println("  Proto      :", resp.Proto())
fmt.Println("  Time       :", resp.Time())
fmt.Println("  Received At:", resp.ReceivedAt())
fmt.Println("  Body       :\n", resp)
fmt.Println()

// Explore trace info
fmt.Println("Request Trace Info:")
ti := resp.Request.TraceInfo()
fmt.Println("  DNSLookup     :", ti.DNSLookup)
fmt.Println("  ConnTime      :", ti.ConnTime)
fmt.Println("  TCPConnTime   :", ti.TCPConnTime)
fmt.Println("  TLSHandshake  :", ti.TLSHandshake)
fmt.Println("  ServerTime    :", ti.ServerTime)
fmt.Println("  ResponseTime  :", ti.ResponseTime)
fmt.Println("  TotalTime     :", ti.TotalTime)
fmt.Println("  IsConnReused  :", ti.IsConnReused)
fmt.Println("  IsConnWasIdle :", ti.IsConnWasIdle)
fmt.Println("  ConnIdleTime  :", ti.ConnIdleTime)
fmt.Println("  RequestAttempt:", ti.RequestAttempt)
fmt.Println("  RemoteAddr    :", ti.RemoteAddr.String())

/* Output
Response Info:
  Error      : <nil>
  Status Code: 200
  Status     : 200 OK
  Proto      : HTTP/2.0
  Time       : 457.034718ms
  Received At: 2020-09-14 15:35:29.784681 -0700 PDT m=+0.458137045
  Body       :
  {
    "args": {},
    "headers": {
      "Accept-Encoding": "gzip",
      "Host": "httpbin.org",
      "User-Agent": "go-resty/2.4.0 (https://github.com/go-resty/resty)",
      "X-Amzn-Trace-Id": "Root=1-5f5ff031-000ff6292204aa6898e4de49"
    },
    "origin": "0.0.0.0",
    "url": "https://httpbin.org/get"
  }

Request Trace Info:
  DNSLookup     : 4.074657ms
  ConnTime      : 381.709936ms
  TCPConnTime   : 77.428048ms
  TLSHandshake  : 299.623597ms
  ServerTime    : 75.414703ms
  ResponseTime  : 79.337µs
  TotalTime     : 457.034718ms
  IsConnReused  : false
  IsConnWasIdle : false
  ConnIdleTime  : 0s
  RequestAttempt: 1
  RemoteAddr    : 3.221.81.55:443
*/

Enhanced GET

// Create a Resty Client
client := resty.New()

resp, err := client.R().
      SetQueryParams(map[string]string{
          "page_no": "1",
          "limit": "20",
          "sort":"name",
          "order": "asc",
          "random":strconv.FormatInt(time.Now().Unix(), 10),
      }).
      SetHeader("Accept", "application/json").
      SetAuthToken("BC594900518B4F7EAC75BD37F019E08FBC594900518B4F7EAC75BD37F019E08F").
      Get("/search_result")


// Sample of using Request.SetQueryString method
resp, err := client.R().
      SetQueryString("productId=232&template=fresh-sample&cat=resty&source=google&kw=buy a lot more").
      SetHeader("Accept", "application/json").
      SetAuthToken("BC594900518B4F7EAC75BD37F019E08FBC594900518B4F7EAC75BD37F019E08F").
      Get("/show_product")


// If necessary, you can force response content type to tell Resty to parse a JSON response into your struct
resp, err := client.R().
      SetResult(result).
      ForceContentType("application/json").
      Get("v2/alpine/manifests/latest")

Various POST method combinations

// Create a Resty Client
client := resty.New()

// POST JSON string
// No need to set content type, if you have client level setting
resp, err := client.R().
      SetHeader("Content-Type", "application/json").
      SetBody(`{"username":"testuser", "password":"testpass"}`).
      SetResult(&AuthSuccess{}).    // or SetResult(AuthSuccess{}).
      Post("https://myapp.com/login")

// POST []byte array
// No need to set content type, if you have client level setting
resp, err := client.R().
      SetHeader("Content-Type", "application/json").
      SetBody([]byte(`{"username":"testuser", "password":"testpass"}`)).
      SetResult(&AuthSuccess{}).    // or SetResult(AuthSuccess{}).
      Post("https://myapp.com/login")

// POST Struct, default is JSON content type. No need to set one
resp, err := client.R().
      SetBody(User{Username: "testuser", Password: "testpass"}).
      SetResult(&AuthSuccess{}).    // or SetResult(AuthSuccess{}).
      SetError(&AuthError{}).       // or SetError(AuthError{}).
      Post("https://myapp.com/login")

// POST Map, default is JSON content type. No need to set one
resp, err := client.R().
      SetBody(map[string]interface{}{"username": "testuser", "password": "testpass"}).
      SetResult(&AuthSuccess{}).    // or SetResult(AuthSuccess{}).
      SetError(&AuthError{}).       // or SetError(AuthError{}).
      Post("https://myapp.com/login")

// POST of raw bytes for file upload. For example: upload file to Dropbox
fileBytes, _ := ioutil.ReadFile("/Users/jeeva/mydocument.pdf")

// See we are not setting content-type header, since go-resty automatically detects Content-Type for you
resp, err := client.R().
      SetBody(fileBytes).
      SetContentLength(true).          // Dropbox expects this value
      SetAuthToken("<your-auth-token>").
      SetError(&DropboxError{}).       // or SetError(DropboxError{}).
      Post("https://content.dropboxapi.com/1/files_put/auto/resty/mydocument.pdf") // for upload Dropbox supports PUT too

// Note: resty detects Content-Type for request body/payload if content type header is not set.
//   * For struct and map data type defaults to 'application/json'
//   * Fallback is plain text content type

Sample PUT

You can use various combinations of PUT method call like demonstrated for POST.

// Note: This is one sample of PUT method usage, refer POST for more combination

// Create a Resty Client
client := resty.New()

// Request goes as JSON content type
// No need to set auth token, error, if you have client level settings
resp, err := client.R().
      SetBody(Article{
        Title: "go-resty",
        Content: "This is my article content, oh ya!",
        Author: "Jeevanandam M",
        Tags: []string{"article", "sample", "resty"},
      }).
      SetAuthToken("C6A79608-782F-4ED0-A11D-BD82FAD829CD").
      SetError(&Error{}).       // or SetError(Error{}).
      Put("https://myapp.com/article/1234")

Sample PATCH

You can use various combinations of PATCH method call like demonstrated for POST.

// Note: This is one sample of PUT method usage, refer POST for more combination

// Create a Resty Client
client := resty.New()

// Request goes as JSON content type
// No need to set auth token, error, if you have client level settings
resp, err := client.R().
      SetBody(Article{
        Tags: []string{"new tag1", "new tag2"},
      }).
      SetAuthToken("C6A79608-782F-4ED0-A11D-BD82FAD829CD").
      SetError(&Error{}).       // or SetError(Error{}).
      Patch("https://myapp.com/articles/1234")

Sample DELETE, HEAD, OPTIONS

// Create a Resty Client
client := resty.New()

// DELETE a article
// No need to set auth token, error, if you have client level settings
resp, err := client.R().
      SetAuthToken("C6A79608-782F-4ED0-A11D-BD82FAD829CD").
      SetError(&Error{}).       // or SetError(Error{}).
      Delete("https://myapp.com/articles/1234")

// DELETE a articles with payload/body as a JSON string
// No need to set auth token, error, if you have client level settings
resp, err := client.R().
      SetAuthToken("C6A79608-782F-4ED0-A11D-BD82FAD829CD").
      SetError(&Error{}).       // or SetError(Error{}).
      SetHeader("Content-Type", "application/json").
      SetBody(`{article_ids: [1002, 1006, 1007, 87683, 45432] }`).
      Delete("https://myapp.com/articles")

// HEAD of resource
// No need to set auth token, if you have client level settings
resp, err := client.R().
      SetAuthToken("C6A79608-782F-4ED0-A11D-BD82FAD829CD").
      Head("https://myapp.com/videos/hi-res-video")

// OPTIONS of resource
// No need to set auth token, if you have client level settings
resp, err := client.R().
      SetAuthToken("C6A79608-782F-4ED0-A11D-BD82FAD829CD").
      Options("https://myapp.com/servers/nyc-dc-01")

Multipart File(s) upload

Using io.Reader

profileImgBytes, _ := ioutil.ReadFile("/Users/jeeva/test-img.png")
notesBytes, _ := ioutil.ReadFile("/Users/jeeva/text-file.txt")

// Create a Resty Client
client := resty.New()

resp, err := client.R().
      SetFileReader("profile_img", "test-img.png", bytes.NewReader(profileImgBytes)).
      SetFileReader("notes", "text-file.txt", bytes.NewReader(notesBytes)).
      SetFormData(map[string]string{
          "first_name": "Jeevanandam",
          "last_name": "M",
      }).
      Post("http://myapp.com/upload")

Using File directly from Path

// Create a Resty Client
client := resty.New()

// Single file scenario
resp, err := client.R().
      SetFile("profile_img", "/Users/jeeva/test-img.png").
      Post("http://myapp.com/upload")

// Multiple files scenario
resp, err := client.R().
      SetFiles(map[string]string{
        "profile_img": "/Users/jeeva/test-img.png",
        "notes": "/Users/jeeva/text-file.txt",
      }).
      Post("http://myapp.com/upload")

// Multipart of form fields and files
resp, err := client.R().
      SetFiles(map[string]string{
        "profile_img": "/Users/jeeva/test-img.png",
        "notes": "/Users/jeeva/text-file.txt",
      }).
      SetFormData(map[string]string{
        "first_name": "Jeevanandam",
        "last_name": "M",
        "zip_code": "00001",
        "city": "my city",
        "access_token": "C6A79608-782F-4ED0-A11D-BD82FAD829CD",
      }).
      Post("http://myapp.com/profile")

Sample Form submission

// Create a Resty Client
client := resty.New()

// just mentioning about POST as an example with simple flow
// User Login
resp, err := client.R().
      SetFormData(map[string]string{
        "username": "jeeva",
        "password": "mypass",
      }).
      Post("http://myapp.com/login")

// Followed by profile update
resp, err := client.R().
      SetFormData(map[string]string{
        "first_name": "Jeevanandam",
        "last_name": "M",
        "zip_code": "00001",
        "city": "new city update",
      }).
      Post("http://myapp.com/profile")

// Multi value form data
criteria := url.Values{
  "search_criteria": []string{"book", "glass", "pencil"},
}
resp, err := client.R().
      SetFormDataFromValues(criteria).
      Post("http://myapp.com/search")

Save HTTP Response into File

// Create a Resty Client
client := resty.New()

// Setting output directory path, If directory not exists then resty creates one!
// This is optional one, if you're planning using absoule path in
// `Request.SetOutput` and can used together.
client.SetOutputDirectory("/Users/jeeva/Downloads")

// HTTP response gets saved into file, similar to curl -o flag
_, err := client.R().
          SetOutput("plugin/ReplyWithHeader-v5.1-beta.zip").
          Get("http://bit.ly/1LouEKr")

// OR using absolute path
// Note: output directory path is not used for absolute path
_, err := client.R().
          SetOutput("/MyDownloads/plugin/ReplyWithHeader-v5.1-beta.zip").
          Get("http://bit.ly/1LouEKr")

Request URL Path Params

Resty provides easy to use dynamic request URL path params. Params can be set at client and request level. Client level params value can be overridden at request level.

// Create a Resty Client
client := resty.New()

client.R().SetPathParams(map[string]string{
   "userId": "[email protected]",
   "subAccountId": "100002",
}).
Get("/v1/users/{userId}/{subAccountId}/details")

// Result:
//   Composed URL - /v1/users/[email protected]/100002/details

Request and Response Middleware

Resty provides middleware ability to manipulate for Request and Response. It is more flexible than callback approach.

// Create a Resty Client
client := resty.New()

// Registering Request Middleware
client.OnBeforeRequest(func(c *resty.Client, req *resty.Request) error {
    // Now you have access to Client and current Request object
    // manipulate it as per your need

    return nil  // if its success otherwise return error
  })

// Registering Response Middleware
client.OnAfterResponse(func(c *resty.Client, resp *resty.Response) error {
    // Now you have access to Client and current Response object
    // manipulate it as per your need

    return nil  // if its success otherwise return error
  })

OnError Hooks

Resty provides OnError hooks that may be called because:

  • The client failed to send the request due to connection timeout, TLS handshake failure, etc...
  • The request was retried the maximum amount of times, and still failed.

If there was a response from the server, the original error will be wrapped in *resty.ResponseError which contains the last response received.

// Create a Resty Client
client := resty.New()

client.OnError(func(req *resty.Request, err error) {
  if v, ok := err.(*resty.ResponseError); ok {
    // v.Response contains the last response from the server
    // v.Err contains the original error
  }
  // Log the error, increment a metric, etc...
})

Redirect Policy

Resty provides few ready to use redirect policy(s) also it supports multiple policies together.

// Create a Resty Client
client := resty.New()

// Assign Client Redirect Policy. Create one as per you need
client.SetRedirectPolicy(resty.FlexibleRedirectPolicy(15))

// Wanna multiple policies such as redirect count, domain name check, etc
client.SetRedirectPolicy(resty.FlexibleRedirectPolicy(20),
                        resty.DomainCheckRedirectPolicy("host1.com", "host2.org", "host3.net"))
Custom Redirect Policy

Implement RedirectPolicy interface and register it with resty client. Have a look redirect.go for more information.

// Create a Resty Client
client := resty.New()

// Using raw func into resty.SetRedirectPolicy
client.SetRedirectPolicy(resty.RedirectPolicyFunc(func(req *http.Request, via []*http.Request) error {
  // Implement your logic here

  // return nil for continue redirect otherwise return error to stop/prevent redirect
  return nil
}))

//---------------------------------------------------

// Using struct create more flexible redirect policy
type CustomRedirectPolicy struct {
  // variables goes here
}

func (c *CustomRedirectPolicy) Apply(req *http.Request, via []*http.Request) error {
  // Implement your logic here

  // return nil for continue redirect otherwise return error to stop/prevent redirect
  return nil
}

// Registering in resty
client.SetRedirectPolicy(CustomRedirectPolicy{/* initialize variables */})

Custom Root Certificates and Client Certificates

// Create a Resty Client
client := resty.New()

// Custom Root certificates, just supply .pem file.
// you can add one or more root certificates, its get appended
client.SetRootCertificate("/path/to/root/pemFile1.pem")
client.SetRootCertificate("/path/to/root/pemFile2.pem")
// ... and so on!

// Adding Client Certificates, you add one or more certificates
// Sample for creating certificate object
// Parsing public/private key pair from a pair of files. The files must contain PEM encoded data.
cert1, err := tls.LoadX509KeyPair("certs/client.pem", "certs/client.key")
if err != nil {
  log.Fatalf("ERROR client certificate: %s", err)
}
// ...

// You add one or more certificates
client.SetCertificates(cert1, cert2, cert3)

Custom Root Certificates and Client Certificates from string

// Custom Root certificates from string
// You can pass you certificates throught env variables as strings
// you can add one or more root certificates, its get appended
client.SetRootCertificateFromString("-----BEGIN CERTIFICATE-----content-----END CERTIFICATE-----")
client.SetRootCertificateFromString("-----BEGIN CERTIFICATE-----content-----END CERTIFICATE-----")
// ... and so on!

// Adding Client Certificates, you add one or more certificates
// Sample for creating certificate object
// Parsing public/private key pair from a pair of files. The files must contain PEM encoded data.
cert1, err := tls.X509KeyPair([]byte("-----BEGIN CERTIFICATE-----content-----END CERTIFICATE-----"), []byte("-----BEGIN CERTIFICATE-----content-----END CERTIFICATE-----"))
if err != nil {
  log.Fatalf("ERROR client certificate: %s", err)
}
// ...

// You add one or more certificates
client.SetCertificates(cert1, cert2, cert3)

Proxy Settings - Client as well as at Request Level

Default Go supports Proxy via environment variable HTTP_PROXY. Resty provides support via SetProxy & RemoveProxy. Choose as per your need.

Client Level Proxy settings applied to all the request

// Create a Resty Client
client := resty.New()

// Setting a Proxy URL and Port
client.SetProxy("http://proxyserver:8888")

// Want to remove proxy setting
client.RemoveProxy()

Retries

Resty uses backoff to increase retry intervals after each attempt.

Usage example:

// Create a Resty Client
client := resty.New()

// Retries are configured per client
client.
    // Set retry count to non zero to enable retries
    SetRetryCount(3).
    // You can override initial retry wait time.
    // Default is 100 milliseconds.
    SetRetryWaitTime(5 * time.Second).
    // MaxWaitTime can be overridden as well.
    // Default is 2 seconds.
    SetRetryMaxWaitTime(20 * time.Second).
    // SetRetryAfter sets callback to calculate wait time between retries.
    // Default (nil) implies exponential backoff with jitter
    SetRetryAfter(func(client *resty.Client, resp *resty.Response) (time.Duration, error) {
        return 0, errors.New("quota exceeded")
    })

Above setup will result in resty retrying requests returned non nil error up to 3 times with delay increased after each attempt.

You can optionally provide client with custom retry conditions:

// Create a Resty Client
client := resty.New()

client.AddRetryCondition(
    // RetryConditionFunc type is for retry condition function
    // input: non-nil Response OR request execution error
    func(r *resty.Response, err error) bool {
        return r.StatusCode() == http.StatusTooManyRequests
    },
)

Above example will make resty retry requests ended with 429 Too Many Requests status code.

Multiple retry conditions can be added.

It is also possible to use resty.Backoff(...) to get arbitrary retry scenarios implemented. Reference.

Allow GET request with Payload

// Create a Resty Client
client := resty.New()

// Allow GET request with Payload. This is disabled by default.
client.SetAllowGetMethodPayload(true)

Wanna Multiple Clients

// Here you go!
// Client 1
client1 := resty.New()
client1.R().Get("http://httpbin.org")
// ...

// Client 2
client2 := resty.New()
client2.R().Head("http://httpbin.org")
// ...

// Bend it as per your need!!!

Remaining Client Settings & its Options

// Create a Resty Client
client := resty.New()

// Unique settings at Client level
//--------------------------------
// Enable debug mode
client.SetDebug(true)

// Assign Client TLSClientConfig
// One can set custom root-certificate. Refer: http://golang.org/pkg/crypto/tls/#example_Dial
client.SetTLSClientConfig(&tls.Config{ RootCAs: roots })

// or One can disable security check (https)
client.SetTLSClientConfig(&tls.Config{ InsecureSkipVerify: true })

// Set client timeout as per your need
client.SetTimeout(1 * time.Minute)


// You can override all below settings and options at request level if you want to
//--------------------------------------------------------------------------------
// Host URL for all request. So you can use relative URL in the request
client.SetHostURL("http://httpbin.org")

// Headers for all request
client.SetHeader("Accept", "application/json")
client.SetHeaders(map[string]string{
        "Content-Type": "application/json",
        "User-Agent": "My custom User Agent String",
      })

// Cookies for all request
client.SetCookie(&http.Cookie{
      Name:"go-resty",
      Value:"This is cookie value",
      Path: "/",
      Domain: "sample.com",
      MaxAge: 36000,
      HttpOnly: true,
      Secure: false,
    })
client.SetCookies(cookies)

// URL query parameters for all request
client.SetQueryParam("user_id", "00001")
client.SetQueryParams(map[string]string{ // sample of those who use this manner
      "api_key": "api-key-here",
      "api_secert": "api-secert",
    })
client.R().SetQueryString("productId=232&template=fresh-sample&cat=resty&source=google&kw=buy a lot more")

// Form data for all request. Typically used with POST and PUT
client.SetFormData(map[string]string{
    "access_token": "BC594900-518B-4F7E-AC75-BD37F019E08F",
  })

// Basic Auth for all request
client.SetBasicAuth("myuser", "mypass")

// Bearer Auth Token for all request
client.SetAuthToken("BC594900518B4F7EAC75BD37F019E08FBC594900518B4F7EAC75BD37F019E08F")

// Enabling Content length value for all request
client.SetContentLength(true)

// Registering global Error object structure for JSON/XML request
client.SetError(&Error{})    // or resty.SetError(Error{})

Unix Socket

unixSocket := "/var/run/my_socket.sock"

// Create a Go's http.Transport so we can set it in resty.
transport := http.Transport{
	Dial: func(_, _ string) (net.Conn, error) {
		return net.Dial("unix", unixSocket)
	},
}

// Create a Resty Client
client := resty.New()

// Set the previous transport that we created, set the scheme of the communication to the
// socket and set the unixSocket as the HostURL.
client.SetTransport(&transport).SetScheme("http").SetHostURL(unixSocket)

// No need to write the host's URL on the request, just the path.
client.R().Get("/index.html")

Bazel support

Resty can be built, tested and depended upon via Bazel. For example, to run all tests:

bazel test :go_default_test

Mocking http requests using httpmock library

In order to mock the http requests when testing your application you could use the httpmock library.

When using the default resty client, you should pass the client to the library as follow:

// Create a Resty Client
client := resty.New()

// Get the underlying HTTP Client and set it to Mock
httpmock.ActivateNonDefault(client.GetClient())

More detailed example of mocking resty http requests using ginko could be found here.

Versioning

Resty releases versions according to Semantic Versioning

  • Resty v2 does not use gopkg.in service for library versioning.
  • Resty fully adapted to go mod capabilities since v1.10.0 release.
  • Resty v1 series was using gopkg.in to provide versioning. gopkg.in/resty.vX points to appropriate tagged versions; X denotes version series number and it's a stable release for production use. For e.g. gopkg.in/resty.v0.
  • Development takes place at the master branch. Although the code in master should always compile and test successfully, it might break API's. I aim to maintain backwards compatibility, but sometimes API's and behavior might be changed to fix a bug.

Contribution

I would welcome your contribution! If you find any improvement or issue you want to fix, feel free to send a pull request, I like pull requests that include test cases for fix/enhancement. I have done my best to bring pretty good code coverage. Feel free to write tests.

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.

Creator

Jeevanandam M. ([email protected])

Core Team

Have a look on Members page.

Contributors

Have a look on Contributors page.

License

Resty released under MIT license, refer LICENSE file.

Owner
Go Resty
Simple HTTP and REST client library for Go
Go Resty
Comments
  • Collecting Inputs for Resty v2.0 and Plan the release

    Collecting Inputs for Resty v2.0 and Plan the release

    The goal of this thread to collect feedback's from resty users and plan v2.0.0 release. Action items get recorded here #167.

    • Things could be improved
    • Things could be added (generalized one's not specific use case)
    • Things could be removed

    Share your resty experiences for upcoming v2.0.0

  • Go Module

    Go Module

    The documentation suggests that one should use resty as follows:

    import "gopkg.in/resty.v1"
    

    (https://github.com/go-resty/resty/blob/ec17de1c59a99e0743dac77aaab6435f364fdc42/README.md#usage)

    But this brakes with Go modules, because go.mod declares the module as follows:

    https://github.com/go-resty/resty/blob/ec17de1c59a99e0743dac77aaab6435f364fdc42/go.mod#L1

    This causes troubles when I try to compile code with go11.1rc2 and go modules enabled.

    All is fine when I import resty like this:

    import "github.com/go-resty/resty"
    

    Now there are two options:

    a) Adjust the documentation and suggest to import "github.com/go-resty/resty" b) Adjust the go.mod to be module "gopkg.in/resty.v1

    Option (a) is good for all new project that start with go modules right away, but it's backward incompatible. Option (b) would be good for projects that used plain go get or dep until now and upgrade to go.mod, because they don't have to change all the import statements. There are no further drawbacks, except that this option needs a new release with an updated go.mod.

  • Potential race in resty with combination of retries and the buffers pool

    Potential race in resty with combination of retries and the buffers pool

    We have a golang project that uses resty as the http client.
    We started using the golang race detector in our staging environment and encountered the following race errors:

    ==================
    WARNING: DATA RACE
    Write at 0x00c00025e610 by goroutine 149:
      bytes.(*Buffer).Read()
          /usr/local/go/src/bytes/buffer.go:299 +0x4a
      io/ioutil.(*nopCloser).Read()
          <autogenerated>:1 +0x87
      net/http.(*http2clientStream).writeRequestBody()
          /usr/local/go/src/net/http/h2_bundle.go:7825 +0x635
      net/http.(*http2Transport).getBodyWriterState.func1()
          /usr/local/go/src/net/http/h2_bundle.go:9040 +0x13d
    
    Previous write at 0x00c00025e610 by goroutine 109:
      bytes.(*Buffer).Read()
          /usr/local/go/src/bytes/buffer.go:299 +0x4a
      io/ioutil.(*nopCloser).Read()
          <autogenerated>:1 +0x87
      net/http.(*http2clientStream).writeRequestBody()
          /usr/local/go/src/net/http/h2_bundle.go:7825 +0x635
      net/http.(*http2Transport).getBodyWriterState.func1()
          /usr/local/go/src/net/http/h2_bundle.go:9040 +0x13d
    
    Goroutine 149 (running) created at:
      net/http.http2bodyWriterState.scheduleBodyWrite()
          /usr/local/go/src/net/http/h2_bundle.go:9087 +0xb8
      net/http.(*http2ClientConn).roundTrip()
          /usr/local/go/src/net/http/h2_bundle.go:7626 +0x81b
      net/http.(*http2Transport).RoundTripOpt()
          /usr/local/go/src/net/http/h2_bundle.go:7037 +0x22d
      net/http.(*http2Transport).RoundTrip()
          /usr/local/go/src/net/http/h2_bundle.go:6999 +0x4b
      net/http.(*Transport).roundTrip()
          /usr/local/go/src/net/http/transport.go:463 +0xa21
      net/http.(*Transport).RoundTrip()
          /usr/local/go/src/net/http/roundtrip.go:17 +0x42
      net/http.send()
          /usr/local/go/src/net/http/client.go:250 +0x304
      net/http.(*Client).send()
          /usr/local/go/src/net/http/client.go:174 +0x1ca
      net/http.(*Client).do()
          /usr/local/go/src/net/http/client.go:641 +0x53a
      net/http.(*Client).Do()
          /usr/local/go/src/net/http/client.go:509 +0x42
      github.com/Rookout/golib/vendor/gopkg.in/resty%2ev1.(*Client).execute()
          /go/src/github.com/Rookout/golib/vendor/gopkg.in/resty.v1/client.go:819 +0x3dd
      github.com/Rookout/golib/vendor/gopkg.in/resty%2ev1.(*Request).Execute.func1()
          /go/src/github.com/Rookout/golib/vendor/gopkg.in/resty.v1/request.go:482 +0x1c4
      github.com/Rookout/golib/vendor/gopkg.in/resty%2ev1.Backoff()
          /go/src/github.com/Rookout/golib/vendor/gopkg.in/resty.v1/retry.go:85 +0x330
      github.com/Rookout/golib/vendor/gopkg.in/resty%2ev1.(*Request).Execute()
          /go/src/github.com/Rookout/golib/vendor/gopkg.in/resty.v1/request.go:476 +0x58c
      github.com/Rookout/golib/pkg/routes.(*CrudSession).executeInternal()
          /go/src/github.com/Rookout/golib/pkg/routes/crud_session.go:168 +0x47d
      github.com/Rookout/golib/pkg/routes.(*CrudSession).Execute()
          /go/src/github.com/Rookout/golib/pkg/routes/crud_session.go:175 +0xb8
      github.com/Rookout/golib/pkg/routes.(*CrudClient).Post()
          /go/src/github.com/Rookout/golib/pkg/routes/crud_client.go:42 +0x132
      github.com/Rookout/golib/pkg/control.(*Notifications).SendRookRuleStatus.func1()
          /go/src/github.com/Rookout/golib/pkg/control/notifications.go:172 +0x13c
    
    Goroutine 109 (finished) created at:
      net/http.http2bodyWriterState.scheduleBodyWrite()
          /usr/local/go/src/net/http/h2_bundle.go:9087 +0xb8
      net/http.(*http2ClientConn).roundTrip()
          /usr/local/go/src/net/http/h2_bundle.go:7626 +0x81b
      net/http.(*http2Transport).RoundTripOpt()
          /usr/local/go/src/net/http/h2_bundle.go:7037 +0x22d
      net/http.(*http2Transport).RoundTrip()
          /usr/local/go/src/net/http/h2_bundle.go:6999 +0x4b
      net/http.http2noDialH2RoundTripper.RoundTrip()
          /usr/local/go/src/net/http/h2_bundle.go:1019 +0x46
      net/http.(*Transport).roundTrip()
          /usr/local/go/src/net/http/transport.go:415 +0x1269
      net/http.(*Transport).RoundTrip()
          /usr/local/go/src/net/http/roundtrip.go:17 +0x42
      net/http.send()
          /usr/local/go/src/net/http/client.go:250 +0x304
      net/http.(*Client).send()
          /usr/local/go/src/net/http/client.go:174 +0x1ca
      net/http.(*Client).do()
          /usr/local/go/src/net/http/client.go:641 +0x53a
      net/http.(*Client).Do()
          /usr/local/go/src/net/http/client.go:509 +0x42
      github.com/Rookout/golib/vendor/gopkg.in/resty%2ev1.(*Client).execute()
          /go/src/github.com/Rookout/golib/vendor/gopkg.in/resty.v1/client.go:819 +0x3dd
      github.com/Rookout/golib/vendor/gopkg.in/resty%2ev1.(*Request).Execute.func1()
          /go/src/github.com/Rookout/golib/vendor/gopkg.in/resty.v1/request.go:482 +0x1c4
      github.com/Rookout/golib/vendor/gopkg.in/resty%2ev1.Backoff()
          /go/src/github.com/Rookout/golib/vendor/gopkg.in/resty.v1/retry.go:85 +0x330
      github.com/Rookout/golib/vendor/gopkg.in/resty%2ev1.(*Request).Execute()
          /go/src/github.com/Rookout/golib/vendor/gopkg.in/resty.v1/request.go:476 +0x58c
      github.com/Rookout/golib/pkg/routes.(*CrudSession).executeInternal()
          /go/src/github.com/Rookout/golib/pkg/routes/crud_session.go:168 +0x47d
      github.com/Rookout/golib/pkg/routes.(*CrudSession).Execute()
          /go/src/github.com/Rookout/golib/pkg/routes/crud_session.go:175 +0xb8
      github.com/Rookout/golib/pkg/routes.(*CrudClient).Post()
          /go/src/github.com/Rookout/golib/pkg/routes/crud_client.go:42 +0x132
      github.com/Rookout/golib/pkg/control.(*Notifications).SendRookRuleStatus.func1()
          /go/src/github.com/Rookout/golib/pkg/control/notifications.go:172 +0x13c
    ==================
    

    The code is not open source atm so I cannot show you exactly what wer'e doing but the main takeaways are:

    1. We share a single client across multiple go routines
    2. We create a new request struct for every request we execute (client.NewRequest())
    3. We use the retry feature Any idea what's the problem here? Is it a false positive or not?
      Thanks
  • Verifying module, checksum mismatch (with new version 2.5.0)

    Verifying module, checksum mismatch (with new version 2.5.0)

    I'm getting this error after upgrading from resty 2.4.0 to 2.5.0:

    github.com/go-resty/resty/[email protected]: verifying module: checksum mismatch
            downloaded: h1:GmCjQ7qW3Dzr3Lh7FvTrRJSWgYNWswDdOy+T8sgTAFI=
            sum.golang.org: h1:WFb5bD49/85PO7WgAjZ+/TJQ+Ty1XOcWEfD1zIFCM1c=
    
    SECURITY ERROR
    This download does NOT match the one reported by the checksum server.
    The bits may have been replaced on the origin server, or an attacker may
    have intercepted the download attempt.
    
    For more information, see 'go help module-auth'.
    

    Already did:

    go clean -modcache
    go get -u ./... && go mod tidy
    

    Any ideas? Thanks!

  • Big file upload

    Big file upload

    Hi,

    i tried my luck with the other issues, sorry if this is a duplicate.

    Is it possible to upload files without loading the entire content into memory ?

    Cheers !

  • v2 Release error with go mod

    v2 Release error with go mod

    I have tried to make a release v2 with the following approach

    • Merging into master
    • Having v2 branch

    Not sure, what is missing? ran into an error. So reverted the master back to the previous state.

    May be go mod vs github.com/go-resty/resty vs gopkg.in/resty.v1 - Need an investigation!!

    go: finding github.com/go-resty/resty/v2 v2.0.0
    go: finding golang.org/x/net v0.0.0-20190619014844-b5b0513f8c1b
    go: finding golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2
    go: finding golang.org/x/text v0.3.0
    go: finding golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a
    go: finding github.com/go-resty/resty v1.12.0
    go: downloading github.com/go-resty/resty v1.12.0
    go: extracting github.com/go-resty/resty v1.12.0
    go: github.com/go-resty/[email protected]: parsing go.mod: unexpected module path "gopkg.in/resty.v1"
    go: error loading module requirements
    
  • Resty v2 release plan and Create migration guide from v1 to v2

    Resty v2 release plan and Create migration guide from v1 to v2

    This issue is to provide v2 release plan and track the migration guide from v1.x to v2.x

    Release Plan:

    • Create a v1 branch from master branch
      • v1 branch will be kept for critical bug fixes only for 6 months, no enhancements and no feature additions
      • Any bug fixes will be ported to master branch to reflect on v2 series
      • v1 will be unsupported and locked down in 6 months from the date of v2 release
    • Make Release Candidates
      • Fix any issues arises
    • Merge v2 branch into the master branch
    • Make a v2.0.0 release from the master branch

    Release Candidates:

    v2.0.0-rc.1 is ready to try it out.

    # add it go.mod
    github.com/go-resty/resty v2.0.0-rc.1
    

    Migration Guide: (In-progress)

    v2 brings https://github.com/go-resty/resty/issues?q=is%3Aclosed+milestone%3A%22v2.0.0+Milestone%22

  • DATA RACE in resty.(*Client).execute()

    DATA RACE in resty.(*Client).execute()

    Hello, I am use resty in multi-goroutine and case the DATA RACE

    test.go `package main

    import ( "github.com/go-resty/resty" "sync" )

    func main() { w := sync.WaitGroup{} for i := 0; i < 50; i++ { w.Add(1) go func() { defer w.Done() resty.R().Get("http://httpbin.org/get") }() } w.Wait() }go run -race test.go`

  • Support Error marshalling using application/problem+json content type

    Support Error marshalling using application/problem+json content type

    I just found out that Mailchimp uses this header which is unfortunate and unusual afaik but there is a spec: https://tools.ietf.org/html/rfc7807 that seems to define this.

    It would be nice to be able to use the SetError() functionality.

  • How to set Content-Length header

    How to set Content-Length header

    I'm trying to call an API, that expects 'Content-Length' = 0 for a HTTP PATCH Request. There is no request body for this request.

    It seems golang (from 1.8) does not allow to set Content-length with zero bytes. ( pls see -- https://github.com/golang/go/issues/20257)

    How do I set the header? I have tried

    restyClient.SetBody(http.NoBody).SetContentLength(true)
    

    And

    reqHeader := map[string]string {
    		"Content-Length": "0",
    }
    

    None of these 2 ways set the content-length header.

  • why resty very slow

    why resty very slow

    Resty vs Curl

    • Resty vs Curl
    • Access Same URL

    Result

    Resty + fmt :

    time ./main
    real	0m11.247s
    user	0m0.045s
    sys	0m0.027s
    

    curl :

    time curl "sameurl"
    real	0m1.373s
    user	0m0.069s
    sys	0m0.020s
    

    Code

    package main
    
    import (
    	"fmt"
    )
    
    import (
    	"gopkg.in/resty.v1"
    )
    
    func main() {
    	resp, _ := resty.R().Get("https://someurl")
    	fmt.Println(resp)
    }
    
  • Issues in using singleton resty

    Issues in using singleton resty

    should i create singleton resty in my project, converted my simple resty to singleton and faced the issue,

    request going to completely different domain which is not specified in url

    did anyone also faced the same?

  • Is this project alive??

    Is this project alive??

    Hi! I noticed there's not much going on with the project - Issues and PR's are piling. Resty seems to be one of the more popular clients out there, it would be a waste if it just slowly ceased to work.

    Are there any problems with maintenance, can I help?

  • Protocol not available when updating from go 1.17 to 1.19

    Protocol not available when updating from go 1.17 to 1.19

    I'm running resty in a wam application

    I got this error with default example :

    Error: Get "http://httpbin.org/get": dial tcp: lookup httpbin.org: Protocol not available
    wasm_exec.js:19 Response Status Code: 0
    wasm_exec.js:19 Response Status: 
    wasm_exec.js:19 Response Body: 
    wasm_exec.js:19 Response Time: 400.128µs
    

    https://gitlab.com/c1560/cryptofiscafacile/-/merge_requests/824

    I have tested http.get successfully

  • Remove default User-Agent header

    Remove default User-Agent header

    See discussion in #604.

    As resty is a generic HTTP client library, I think we should not advertise a User-Agent header. Leave that up to the end user as an implementation detail.

    I doubt this would break many user implementations, but we would need to highlight this as a potential breaking change in the release notes.

    Thoughts?

  • Why is ConnTime so long?

    Why is ConnTime so long?

    ufile download fileName: 67F0C1CA4548440EP08A07B6.jpg, trace: {DNSLookup:1.59315ms ConnTime:10.074076038s TCPConnTime:0s TLSHandshake:0s ServerTime:84.875535ms ResponseTime:20.539µs TotalTime:10.158972112s IsConnReused:true IsConnWasIdle:false ConnIdleTime:0s RequestAttempt:1 RemoteAddr:10.23.255.247:80}, status: 200
    
    // define global client
    defaultTransport := &http.Transport{
    		Proxy:                 http.ProxyFromEnvironment,
    		DialContext:           dialer.DialContext,
    		ForceAttemptHTTP2:     true,
    		MaxIdleConns:          100,
    		IdleConnTimeout:       90 * time.Second,
    		TLSHandshakeTimeout:   10 * time.Second,
    		ExpectContinueTimeout: 1 * time.Second,
    		MaxIdleConnsPerHost:   1000,
    	}
    uRfileClinet = resty.New()
    uRfileClinet.SetTransport(defaultTransport)
    
    // use in gin handler
    ctx, cacel := context.WithTimeout(context.Background(), time.Second*20)
    defer cacel()
    uFileResp, err := uRfileClinet.R().
    EnableTrace().
    SetContext(ctx).
    SetDoNotParseResponse(true).
    Get(url)
    log.LgReqAccess.Sugar().Infof("ufile_upstream_response_time: %s fileName: %s", time.Since(ufileUpstreamStartTime), fileName)
    trace := uFileResp.Request.TraceInfo()
    log.LgReqAccess.Sugar().Infof("ufile download fileName: %s, trace: %+v, status: %d", fileName, trace, uFileResp.StatusCode())
    if err != nil {
      log.LgReqAccess.Sugar().Errorf("ufile download error: %+v, fileName: %s", err, fileName)
      statusCode = uFileResp.StatusCode()
      return
    }
    defer uFileResp.RawBody().Close()
    
    _, err = io.Copy(c.Writer, uFileResp.RawBody())
    
An idiomatic Go REST API starter kit (boilerplate) following the SOLID principles and Clean Architecture

Go RESTful API Starter Kit (Boilerplate) This starter kit is designed to get you up and running with a project structure optimized for developing REST

Jan 3, 2023
package for building REST-style Web Services using Go

go-restful package for building REST-style Web Services using Google Go Code examples using v3 REST asks developers to use HTTP methods explicitly and

Jan 1, 2023
🍐 Elegant Golang REST API Framework
🍐 Elegant Golang REST API Framework

An Elegant Golang Web Framework Goyave is a progressive and accessible web application framework focused on REST APIs, aimed at making backend develop

Jan 4, 2023
A lightweight and fast http router from outer space

Alien Alien is a lightweight http router( multiplexer) for Go( Golang ), made for humans who don't like magic. Documentation docs Features fast ( see

Nov 13, 2022
Gin is a HTTP web framework written in Go (Golang). It features a Martini-like API with much better performance -- up to 40 times faster. If you need smashing performance, get yourself some Gin.
Gin is a HTTP web framework written in Go (Golang). It features a Martini-like API with much better performance -- up to 40 times faster. If you need smashing performance, get yourself some Gin.

Gin Web Framework Gin is a web framework written in Go (Golang). It features a martini-like API with performance that is up to 40 times faster thanks

Jan 2, 2023
Go http middleware handler for request coalescing

HTTP Coala NOTE: a new and improved implementation is available at https://github.com/go-chi/stampede Just a little bit of performance enhancing middl

Apr 3, 2022
A high performance HTTP request router that scales well

HttpRouter HttpRouter is a lightweight high performance HTTP request router (also called multiplexer or just mux for short) for Go. In contrast to the

Dec 29, 2022
Idiomatic HTTP Middleware for Golang

Negroni Notice: This is the library formerly known as github.com/codegangsta/negroni -- Github will automatically redirect requests to this repository

Dec 31, 2022
Go HTTP router

violetear Go HTTP router http://violetear.org Design Goals Keep it simple and small, avoiding extra complexity at all cost. KISS Support for static an

Dec 10, 2022
Flamingo Framework and Core Library. Flamingo is a go based framework for pluggable web projects. It is used to build scalable and maintainable (web)applications.
Flamingo Framework and Core Library. Flamingo is a go based framework for pluggable web projects. It is used to build scalable and maintainable (web)applications.

Flamingo Framework Flamingo is a web framework based on Go. It is designed to build pluggable and maintainable web projects. It is production ready, f

Jan 5, 2023
A simple blog framework built with GO. Uses HTML files and a JSON dict to give you more control over your content.

Go-Blog A simple template based blog framework. Instructions Built for GO version: 1 See the Documentation or Getting Started pages in the wiki. Notes

Sep 10, 2022
Thespian is a library supporting use of the actor model in Go code.

Thespian is a library supporting use of the actor model in Go code.

Nov 24, 2021
Dead simple rate limit middleware for Go.

Limiter Dead simple rate limit middleware for Go. Simple API "Store" approach for backend Redis support (but not tied too) Middlewares: HTTP, FastHTTP

Jan 1, 2023
Simple web framework for go, still quite beta at this point

WFDR Framework - Beta Release New 18/Feb/2012: Updated for go 1.0, new directory layout to take advantage of the go build tool. Background There's a m

Feb 11, 2021
Simple Contributors Report For Git

git-contrib Creates simple contributors report for git. Useful for different bra

Nov 20, 2022
:link: Generate HTML and CSS together, on the fly
:link: Generate HTML and CSS together, on the fly

On The Fly Package for generating HTML and CSS together, on the fly. Can also be used for generating HTML, XML or CSS (or templates). HTML and CSS can

Oct 12, 2022
Fast and Reliable Golang Web Framework
Fast and Reliable Golang Web Framework

Gramework The Good Framework Gramework long-term testing stand metrics screenshot made with Gramework Stats Dashboard and metrics middleware What is i

Dec 18, 2022
Mango is a modular web-application framework for Go, inspired by Rack, and PEP333.

Mango Mango is a modular web-application framework for Go, inspired by Rack and PEP333. Note: Not actively maintained. Overview Mango is most of all a

Nov 17, 2022
This is only a mirror and Moved to https://gitea.com/lunny/tango

Tango 简体中文 Package tango is a micro & pluggable web framework for Go. Current version: v0.5.0 Version History Getting Started To install Tango: go get

Nov 18, 2022