本文整理自微信公众号文章,结合 Resty 官方文档 重新编写
原文来源:

前言

在 Go 语言中,使用标准库 net/http 发起 HTTP 请求时,代码往往比较繁琐。为了简化开发,社区诞生了许多优秀的第三方 HTTP 客户端库,其中 go-resty 以其简洁优雅的链式调用、强大的功能和出色的性能脱颖而出。

本文将介绍 Resty v3 的核心特性和使用方法,帮助你快速掌握这个高效的 HTTP 客户端库。

Resty 简介

Resty 是一个基于 Go 标准库 net/http 构建的 HTTP 和 REST 客户端库,同时支持 Server-Sent Events (SSE)

为什么选择 Resty?

特性 说明
🔗 链式调用 直观优雅的 API 设计,代码可读性极高
🔄 自动序列化 轻松将结构体转为 JSON/XML,自动解析响应
🔁 重试机制 内置可定制的请求重试逻辑,增强可靠性
🔌 中间件支持 请求/响应中间件实现日志、认证等统一处理
🐛 调试友好 便捷的调试模式,输出详细请求日志
📊 请求追踪 支持 DNS、TCP、TLS 各阶段耗时分析
📁 文件上传/下载 简化 multipart 表单和文件操作

Resty v3 的改进

Resty v3 相比 v2 版本带来了显著提升:

  • 更好的性能 - 内存效率优化,请求处理更快
  • SSE 支持 - 原生支持 Server-Sent Events
  • 更简洁的 API - 改进的接口设计
  • 更好的错误处理 - 更清晰的错误信息

安装

1
2
3
4
5
# Resty v3 (推荐,更高效)
go get resty.dev/v3@latest

# Resty v2 (稳定版)
go get github.com/go-resty/resty/v2

注意:Resty v3 目前为 Beta 版本,生产环境可根据稳定性要求选择 v2。本文示例基于 v3 语法,v2 用户可参考 官方文档 进行适配。

快速开始

简单的 GET 请求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package main

import (
"fmt"
"resty.dev/v3"
)

func main() {
// 创建 Resty 客户端
client := resty.New()
defer client.Close()

// 发起 GET 请求
resp, err := client.R().
EnableTrace(). // 启用请求追踪
Get("https://httpbin.org/get")

if err != nil {
panic(err)
}

// 查看响应信息
fmt.Println("Status Code:", resp.StatusCode())
fmt.Println("Status:", resp.Status())
fmt.Println("Time:", resp.Time())
fmt.Println("Body:", resp.String())

// 查看请求追踪信息
ti := resp.Request.TraceInfo()
fmt.Println("DNS Lookup:", ti.DNSLookup)
fmt.Println("TCP Connection:", ti.TCPConnTime)
fmt.Println("TLS Handshake:", ti.TLSHandshake)
fmt.Println("Server Time:", ti.ServerTime)
fmt.Println("Total Time:", ti.TotalTime)
}

运行结果示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Status Code: 200
Status: 200 OK
Time: 457.034718ms
Body: {
"args": {},
"headers": {
"Accept-Encoding": "gzip",
"Host": "httpbin.org",
"User-Agent": "go-resty/3.0.0"
},
"origin": "0.0.0.0",
"url": "https://httpbin.org/get"
}

DNS Lookup: 4.074657ms
TCP Connection: 77.428048ms
TLS Handshake: 299.623597ms
Server Time: 75.414703ms
Total Time: 457.034718ms

核心用法详解

1. GET 请求进阶

带查询参数的 GET 请求

1
2
3
4
5
6
7
8
9
10
11
12
13
client := resty.New()
defer client.Close()

resp, err := client.R().
SetQueryParams(map[string]string{
"page_no": "1",
"limit": "20",
"sort": "name",
"order": "asc",
}).
SetHeader("Accept", "application/json").
SetAuthToken("your-auth-token").
Get("https://api.example.com/search")

使用 QueryString

1
2
3
resp, err := client.R().
SetQueryString("productId=232&category=resty&source=google").
Get("https://api.example.com/product")

自动解析 JSON 到结构体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
type User struct {
ID int `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
}

var user User

resp, err := client.R().
SetResult(&user). // 自动解析到结构体
ForceContentType("application/json").
Get("https://api.example.com/user/123")

// 现在可以使用 user.Name, user.Email 等

2. POST 请求

发送 JSON 字符串

1
2
3
4
5
resp, err := client.R().
SetHeader("Content-Type", "application/json").
SetBody(`{"username":"testuser", "password":"testpass"}`).
SetResult(&AuthSuccess{}).
Post("https://api.example.com/login")

发送结构体

1
2
3
4
5
6
7
8
9
10
11
12
13
type LoginRequest struct {
Username string `json:"username"`
Password string `json:"password"`
}

resp, err := client.R().
SetBody(LoginRequest{
Username: "testuser",
Password: "testpass",
}).
SetResult(&AuthSuccess{}).
SetError(&AuthError{}).
Post("https://api.example.com/login")

发送 Map

1
2
3
4
5
6
resp, err := client.R().
SetBody(map[string]interface{}{
"username": "testuser",
"password": "testpass",
}).
Post("https://api.example.com/login")

提示:Resty 会自动检测 Content-Type,struct 和 map 默认使用 application/json

3. PUT / PATCH / DELETE 请求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// PUT - 更新资源
resp, err := client.R().
SetBody(Article{
Title: "Go Resty Guide",
Content: "This is a comprehensive guide",
Author: "Developer",
}).
SetAuthToken("your-token").
Put("https://api.example.com/articles/123")

// PATCH - 部分更新
resp, err := client.R().
SetBody(map[string]interface{}{
"tags": []string{"go", "http", "resty"},
}).
Patch("https://api.example.com/articles/123")

// DELETE - 删除资源
resp, err := client.R().
SetAuthToken("your-token").
Delete("https://api.example.com/articles/123")

// 带请求体的 DELETE
resp, err := client.R().
SetHeader("Content-Type", "application/json").
SetBody(`{"ids": [1002, 1006, 1007]}`).
Delete("https://api.example.com/articles/batch")

// HEAD 和 OPTIONS
resp, err := client.R().Head("https://api.example.com/resource")
resp, err := client.R().Options("https://api.example.com/resource")

4. 文件上传

单文件上传

1
2
3
resp, err := client.R().
SetFile("profile_img", "/path/to/image.png").
Post("https://api.example.com/upload")

多文件上传

1
2
3
4
5
6
resp, err := client.R().
SetFiles(map[string]string{
"profile_img": "/path/to/image.png",
"document": "/path/to/file.pdf",
}).
Post("https://api.example.com/upload")

带表单数据的文件上传

1
2
3
4
5
6
7
8
9
10
resp, err := client.R().
SetFiles(map[string]string{
"avatar": "/path/to/avatar.png",
}).
SetFormData(map[string]string{
"first_name": "John",
"last_name": "Doe",
"email": "john@example.com",
}).
Post("https://api.example.com/profile")

5. 文件下载

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
client := resty.New()
defer client.Close()

// 设置下载目录(可选)
client.SetOutputDirectory("/Users/jeeva/Downloads")

// 下载文件
_, err := client.R().
SetOutput("plugin.zip"). // 相对路径,使用 SetOutputDirectory
Get("https://example.com/files/plugin.zip")

// 或使用绝对路径
_, err := client.R().
SetOutput("/absolute/path/to/plugin.zip").
Get("https://example.com/files/plugin.zip")

6. 表单提交

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 简单表单
resp, err := client.R().
SetFormData(map[string]string{
"username": "testuser",
"password": "testpass",
}).
Post("https://api.example.com/login")

// 多值表单
resp, err := client.R().
SetFormDataFromValues(url.Values{
"tags": []string{"go", "http", "resty"},
}).
Post("https://api.example.com/search")

7. URL 路径参数

1
2
3
4
5
6
7
8
resp, err := client.R().
SetPathParams(map[string]string{
"userId": "user@example.com",
"subAccountId": "100002",
}).
Get("/v1/users/{userId}/{subAccountId}/details")

// 结果 URL: /v1/users/user@example.com/100002/details

高级特性

1. 重试机制

Resty 内置强大的重试功能,支持指数退避:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
client := resty.New()
defer client.Close()

client.
// 设置重试次数
SetRetryCount(3).
// 初始等待时间(默认 100ms)
SetRetryWaitTime(5 * time.Second).
// 最大等待时间(默认 2s)
SetRetryMaxWaitTime(20 * time.Second).
// 自定义重试条件
AddRetryCondition(func(r *resty.Response) (bool, error) {
return r.StatusCode() == http.StatusTooManyRequests, nil
})

2. 请求/响应中间件

1
2
3
4
5
6
7
8
9
10
11
12
13
// 请求中间件 - 在请求发送前执行
client.OnBeforeRequest(func(c *resty.Client, req *resty.Request) error {
// 添加通用请求头
req.SetHeader("X-Request-ID", uuid.New().String())
return nil
})

// 响应中间件 - 在收到响应后执行
client.OnAfterResponse(func(c *resty.Client, resp *resty.Response) error {
// 记录响应日志
log.Printf("Response: %d %s", resp.StatusCode(), resp.Status())
return nil
})

3. 错误处理钩子

1
2
3
4
5
6
7
client.OnError(func(req *resty.Request, err error) {
if v, ok := err.(*resty.ResponseError); ok {
// v.Response 包含最后的响应
// v.Err 包含原始错误
log.Printf("Request failed after retries: %v", v.Err)
}
})

4. 调试模式与生成 CURL 命令

1
2
3
4
5
6
7
8
9
10
11
resp, err := client.R().
SetDebug(true).
EnableGenerateCurlOnDebug().
SetBody(map[string]string{"name": "Alex"}).
Post("https://httpbin.org/post")

// 获取等效的 CURL 命令
curlCmd := resp.Request.GenerateCurlCommand()
fmt.Println("Curl Command:", curlCmd)

// 输出: curl -X POST -H 'Content-Type: application/json' -d '{"name":"Alex"}' https://httpbin.org/post

5. 重定向策略

1
2
3
4
5
6
7
8
9
10
11
client := resty.New()
defer client.Close()

// 设置最大重定向次数
client.SetRedirectPolicy(resty.FlexibleRedirectPolicy(15))

// 多重策略:限制次数 + 域名检查
client.SetRedirectPolicy(
resty.FlexibleRedirectPolicy(20),
resty.DomainCheckRedirectPolicy("api.example.com", "cdn.example.com"),
)

6. 代理设置

1
2
3
4
5
6
7
8
client := resty.New()
defer client.Close()

// 设置代理
client.SetProxy("http://proxyserver:8888")

// 移除代理
client.RemoveProxy()

7. 自定义 TLS 证书

1
2
3
4
5
6
7
8
9
client := resty.New()
defer client.Close()

// 添加根证书
client.SetRootCertificate("/path/to/root.pem")

// 添加客户端证书
cert, _ := tls.LoadX509KeyPair("certs/client.pem", "certs/client.key")
client.SetCertificates(cert)

8. 自定义 JSON/XML 库

1
2
3
4
5
6
7
8
import jsoniter "github.com/json-iterator/go"

json := jsoniter.ConfigCompatibleWithStandardLibrary

client := resty.New()
client.
SetJSONMarshaler(json.Marshal).
SetJSONUnmarshaler(json.Unmarshal)

Server-Sent Events (SSE) 支持

Resty v3 原生支持 SSE,这是相比 v2 的重要新增功能:

1
2
3
4
5
6
7
8
9
10
11
12
13
// 创建 SSE 客户端
es := resty.NewEventSource().
SetURL("https://sse.dev/test").
OnMessage(func(e any) {
event := e.(*resty.Event)
fmt.Printf("Event: %s, Data: %s\n", event.Type, event.Data)
}, nil)

// 开始监听
err := es.Get()
if err != nil {
panic(err)
}

多客户端管理

1
2
3
4
5
6
7
8
9
10
11
12
13
// 客户端 1
client1 := resty.New()
client1.SetBaseURL("https://api.service1.com")
client1.R().Get("/users")

// 客户端 2
client2 := resty.New()
client2.SetBaseURL("https://api.service2.com")
client2.R().Get("/products")

// 记得关闭客户端释放资源
defer client1.Close()
defer client2.Close()

性能对比

特性 net/http Resty v2 Resty v3
代码简洁度 ⭐⭐ ⭐⭐⭐⭐ ⭐⭐⭐⭐⭐
内存效率 ⭐⭐⭐ ⭐⭐⭐ ⭐⭐⭐⭐
功能丰富度 ⭐⭐ ⭐⭐⭐⭐ ⭐⭐⭐⭐⭐
学习曲线 ⭐⭐ ⭐⭐⭐⭐ ⭐⭐⭐⭐
调试能力 ⭐⭐ ⭐⭐⭐⭐ ⭐⭐⭐⭐⭐

最佳实践

  1. 使用 defer client.Close() - 确保释放资源
  2. 复用客户端实例 - 避免频繁创建销毁
  3. 设置合理的超时 - client.SetTimeout(30 * time.Second)
  4. 使用中间件统一处理 - 日志、认证、错误处理
  5. 生产环境配置重试 - 提高请求可靠性
  6. 启用调试模式排查问题 - 开发阶段使用 SetDebug(true)

总结

Resty 通过简洁的链式 API 设计,让开发者能够专注于业务逻辑而非 HTTP 细节。无论是快速原型开发还是构建生产级应用,Resty 都是 Go 语言开发者值得信赖的 HTTP 客户端库。

Resty v3 带来的性能提升和 SSE 支持,使其更加完善。如果你的项目对稳定性要求极高,可以继续使用 v2;如果希望体验新特性和更好的性能,不妨尝试 v3。

参考资料


Happy Coding! 🚀