極簡 HTTP 客戶端,Resty 如何讓 Golang 請求效率翻倍?
Resty 是一個基於 Golang 標準庫 net/http 構建的高效、易用的 HTTP 客戶端庫。它通過鏈式調用、自動化處理和豐富的擴展功能,極大簡化了 HTTP 請求的發送與響應處理流程,尤其適合與 RESTful API 交互的場景。
Resty 的核心優勢
- 鏈式調用:支持直觀的鏈式語法,提升代碼可讀性。
- 自動序列化:輕鬆將結構體轉爲 JSON/XML,自動解析響應內容。
- 重試機制:內置可定製的請求重試邏輯,增強請求可靠性。
- 中間件支持:可通過請求 / 響應中間件實現日誌、認證等統一處理。
- 調試友好:便捷的調試模式可輸出詳細請求日誌。
Resty 的典型應用場景
-
快速接入外部 API 服務(如支付接口、社交媒體平臺)
-
編寫 API 自動化測試用例
-
構建微服務間的通信客戶端
-
開發需要複雜 HTTP 配置的後臺應用
看到它的優勢和應用場景,你就知道在哪裏使用它,以及它的效率了。使用 Golang 寫過 net 客戶端請求可以知道使用該庫有多方便快捷。
下面我們來看下它的示例,首先,我們需要安裝它,目前該庫已經有 V3 版本,它更高效,功能更強大,但是現在還是 Beta 版本,我們看下正在使用的 V2 版本。
go get github.com/go-resty/resty/v2
我們就列舉日常用到最多的示例,下面的示例總有一款適合你。
- 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")
- 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
- 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