極簡 HTTP 客戶端,Resty 如何讓 Golang 請求效率翻倍?

Resty 是一個基於 Golang 標準庫 net/http 構建的高效、易用的 HTTP 客戶端庫。它通過鏈式調用、自動化處理和豐富的擴展功能,極大簡化了 HTTP 請求的發送與響應處理流程,尤其適合與 RESTful API 交互的場景。

Resty 的核心優勢

  1. 鏈式調用:支持直觀的鏈式語法,提升代碼可讀性。
  2. 自動序列化:輕鬆將結構體轉爲 JSON/XML,自動解析響應內容。
  3. 重試機制:內置可定製的請求重試邏輯,增強請求可靠性。
  4. 中間件支持:可通過請求 / 響應中間件實現日誌、認證等統一處理。
  5. 調試友好:便捷的調試模式可輸出詳細請求日誌。

Resty 的典型應用場景

看到它的優勢和應用場景,你就知道在哪裏使用它,以及它的效率了。使用 Golang 寫過 net 客戶端請求可以知道使用該庫有多方便快捷。

下面我們來看下它的示例,首先,我們需要安裝它,目前該庫已經有 V3 版本,它更高效,功能更強大,但是現在還是 Beta 版本,我們看下正在使用的 V2 版本。

go get github.com/go-resty/resty/v2

我們就列舉日常用到最多的示例,下面的示例總有一款適合你。

  1.  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
*/

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")
  1.  POST 請求:創建資源
 // 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, _ := os.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
  1.  PUT 請求:更新資源
// 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")

4. DELETE請求:刪除資源和HEAD請求等

// 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")

5. PATCH請求示例:

// 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")

6. 選擇其它的JSON和XML庫

用戶可以選擇其它的JSON和XML庫,默認情況下,resty使用標準encoding/json和encoding/xml。

// Example of registering json-iterator
import jsoniter "github.com/json-iterator/go"
json := jsoniter.ConfigCompatibleWithStandardLibrary
client := resty.New().
    SetJSONMarshaler(json.Marshal).
    SetJSONUnmarshaler(json.Unmarshal)
// similarly user could do for XML too with -
client.SetXMLMarshaler(xml.Marshal).
    SetXMLUnmarshaler(xml.Unmarshal)

7. 多文件上傳

profileImgBytes, _ := os.ReadFile("/Users/jeeva/test-img.png")
notesBytes, _ := os.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")

8. 在路徑中使用文件

// 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")

9. 提交Form表單

// 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")

10. 將HTTP響應存爲文件,即下載文件

// 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 absolute 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")

11. 請求URL路徑參數

Resty提供了易於使用的動態請求URL路徑參數。參數可以在客戶端和請求級別設置。客戶端級別的參數值可以在請求級別被覆蓋。

// Create a Resty Client
client := resty.New()
client.R().SetPathParams(map[string]string{
   "userId""sample@sample.com",
   "subAccountId""100002",
}).
Get("/v1/users/{userId}/{subAccountId}/details")
// Result:
//   Composed URL - /v1/users/sample@sample.com/100002/details

12. 請求和響應中間件

Resty提供了中間件處理請求和響應的能力。它比回調方法更靈活。

// 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
  })

13. OnError Hooks

Resty提供了OnError 鉤子,例如由於連接超時、TLS握手失敗等原因,客戶端無法發送請求,請求被重試了最大次數,但仍然失敗。如果服務器有響應,最後收到了響應,原始錯誤將包裝在*resty.ResponseError中。

// 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...
})

14. 生成CURL命令

// Create a Resty Client
client := resty.New()
resp, err := client.R().
    SetDebug(true).
    EnableGenerateCurlOnDebug(). // CURL command generated when debug mode enabled with this option
    SetBody(map[string]string{"name""Alex"}).
    Post("https://httpbin.org/post")
curlCmdExecuted := resp.Request.GenerateCurlCommand()
// Explore curl command
fmt.Println("Curl Command:\n  ", curlCmdExecuted+"\n")
/* Output
Curl Command:
   curl -X POST -H 'Content-Type: application/json' -H 'User-Agent: go-resty/2.14.0 (https://github.com/go-resty/resty)' -d '{"name":"Alex"}' https://httpbin.org/post
*/

15. 重定向策略

// 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"))

16. 自定義根證書和客戶端證書

// 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)

17. 設置代理

// 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()

18. 重試,Resty使用回退來增加每次嘗試後的重試間隔。

// 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")
    })

Resty通過簡化的API設計,讓開發者能夠專注於業務邏輯而非HTTP細節。其鏈式調用與自動化處理機制顯著提升了開發效率,尤其適合高頻度、多樣化的API調用場景。無論是快速原型開發還是構建生產級應用,Resty都是Golang開發者值得信賴的工具。

更多內容,請查看 Github 項目地址:

https://github.com/go-resty/resty

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/WjG2ljxMRPssiJja0GJ-Jw