go-resty:链式调用写 Go HTTP 客户端,内置重试、熔断与 SSE
项目:github.com/go-resty/resty · 文档:pkg.go.dev/github.com/go-resty/resty/v3
为什么需要 go-resty
用 Go 标准库的 net/http 发请求,代码往往又长又繁琐。重试、超时、熔断这些功能,标准库也不直接支持;调试 HTTP 请求时,还得手动拼 curl 命令。
一个真实场景:调用 REST API 获取用户信息。
用 net/http:
client := &http.Client{
Timeout: 10 * time.Second,
}
req, err := http.NewRequest("GET", "https://api.example.com/users/123", nil)
if err != nil {
return err
}
req.Header.Set("Authorization", "Bearer token123")
req.Header.Set("Content-Type", "application/json")
resp, err := client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return err
}
var user User
err = json.Unmarshal(body, &user)
if err != nil {
return err
}
用 go-resty:
client := resty.New()
resp, err := client.R().
SetHeader("Authorization", "Bearer token123").
SetResult(&User{}).
Get("https://api.example.com/users/123")
user := resp.Result().(*User)
代码更短,调用流程也更清楚。常见用途包括:调用第三方 API、微服务间通信、抓取网页数据、编写 API 测试工具。
go-resty 是什么
go-resty 是一个 Go 语言的 HTTP 客户端库。官方描述是:
Simple HTTP, REST, and SSE client library for Go.
它支持 HTTP 请求、REST API、SSE(Server-Sent Events),API 设计简洁。
核心定位
go-resty 不是 HTTP 服务器,也不是 HTTP 代理,而是一个 HTTP 客户端库,用于发送 HTTP 请求。它主要做了几件事:
- 封装
net/http - 提供链式调用 API
- 自动处理 JSON
- 支持重试、熔断、负载均衡
适用的场景包括调用 REST API、微服务通信、爬虫抓取、API 测试等。
核心特性:链式调用和高级功能
链式调用
resp, err := client.R().
SetHeader("Content-Type", "application/json").
SetQueryParam("page", "1").
SetBody(map[string]string{"name": "John"}).
Post("https://api.example.com/users")
自动重试
client.SetRetryCount(3).
SetRetryWaitTime(1 * time.Second).
SetRetryMaxWaitTime(10 * time.Second).
AddRetryCondition(func(r *resty.Response, err error) bool {
return r.StatusCode() >= 500
})
熔断降级
client.SetCircuitBreaker(&resty.CircuitBreakerConfig{
MaxRequests: 100,
Interval: 60 * time.Second,
Timeout: 10 * time.Second,
})
负载均衡
client.SetLoadBalancer(&resty.LoadBalancerConfig{
Strategy: resty.LBStrategyRoundRobin,
Backends: []string{
"https://api1.example.com",
"https://api2.example.com",
"https://api3.example.com",
},
})
这些能力适合不稳定 API 的自动重试、高并发场景下的熔断降级,以及多实例部署时的请求分散。
安装和使用
安装
go get github.com/go-resty/resty/v3
基本使用
GET 请求
package main
import (
"fmt"
"github.com/go-resty/resty/v3"
)
func main() {
client := resty.New()
resp, err := client.R().
SetQueryParam("page", "1").
Get("https://api.example.com/users")
if err != nil {
fmt.Println("Error:", err)
return
}
fmt.Println("Response:", resp.String())
fmt.Println("Status:", resp.Status())
}
POST 请求
type User struct {
Name string `json:"name"`
Email string `json:"email"`
}
func main() {
client := resty.New()
user := &User{
Name: "John Doe",
Email: "john@example.com",
}
resp, err := client.R().
SetHeader("Content-Type", "application/json").
SetBody(user).
Post("https://api.example.com/users")
if err != nil {
fmt.Println("Error:", err)
return
}
fmt.Println("Status:", resp.Status())
}
自动解析 JSON
type User struct {
ID int `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
}
func main() {
client := resty.New()
var user User
resp, err := client.R().
SetResult(&user).
Get("https://api.example.com/users/123")
if err != nil {
fmt.Println("Error:", err)
return
}
fmt.Printf("User: %+v\n", user)
}
错误处理
type APIError struct {
Code int `json:"code"`
Message string `json:"message"`
}
func main() {
client := resty.New()
var apiErr APIError
resp, err := client.R().
SetError(&apiErr).
Get("https://api.example.com/users/999")
if err != nil {
fmt.Println("Error:", err)
return
}
if resp.IsError() {
fmt.Printf("API Error: %d - %s\n", apiErr.Code, apiErr.Message)
return
}
fmt.Println("Success!")
}
基本流程可以概括为:New() → R() → Get/Post;SetResult() 自动解析 JSON;SetError() 处理 API 错误。
核心功能
go-resty 虽然定位是 HTTP 客户端,但功能覆盖比较全。
功能一:请求和响应中间件
// 请求中间件
client.OnBeforeRequest(func(c *resty.Client, r *resty.Request) error {
r.SetHeader("X-Request-ID", generateRequestID())
return nil
})
// 响应中间件
client.OnAfterResponse(func(c *resty.Client, resp *resty.Response) error {
log.Printf("Request: %s %s, Status: %s",
resp.Request.Method,
resp.Request.URL,
resp.Status())
return nil
})
功能二:超时控制
client.SetTimeout(30 * time.Second)
client.SetTransport(&http.Transport{
DialContext: (&net.Dialer{
Timeout: 5 * time.Second,
}).DialContext,
})
功能三:代理设置
client.SetProxy("http://proxy.example.com:8080")
client.RemoveProxy()
功能四:Cookie 管理
client.SetCookies([]*http.Cookie{
{Name: "session", Value: "abc123"},
})
resp, _ := client.R().Get("https://api.example.com")
cookies := resp.Cookies()
功能五:文件上传
resp, err := client.R().
SetFile("avatar", "/path/to/avatar.jpg").
SetFormData(map[string]string{
"name": "John",
}).
Post("https://api.example.com/upload")
功能六:SSE 支持
client.R().
SetSSE(true).
SetSSEHandler(func(event *resty.SSEEvent) {
fmt.Printf("Event: %s, Data: %s\n", event.Event, event.Data)
}).
Get("https://api.example.com/events")
对应场景包括:中间件记录请求日志、超时防止请求挂起、代理适配企业网络、文件上传使用多部分表单、SSE 处理实时数据流。
实战场景
场景一:调用第三方 API
需要调用天气 API,并支持重试和错误处理。
type WeatherResponse struct {
City string `json:"city"`
Temperature float64 `json:"temperature"`
Condition string `json:"condition"`
}
type WeatherClient struct {
client *resty.Client
}
func NewWeatherClient() *WeatherClient {
client := resty.New()
// 配置重试
client.SetRetryCount(3).
SetRetryWaitTime(1 * time.Second).
AddRetryCondition(func(r *resty.Response, err error) bool {
return err != nil || r.StatusCode() >= 500
})
// 配置超时
client.SetTimeout(10 * time.Second)
return &WeatherClient{client: client}
}
func (c *WeatherClient) GetWeather(city string) (*WeatherResponse, error) {
var resp WeatherResponse
result, err := c.client.R().
SetQueryParam("city", city).
SetResult(&resp).
Get("https://api.weather.com/v1/weather")
if err != nil {
return nil, fmt.Errorf("request failed: %w", err)
}
if result.IsError() {
return nil, fmt.Errorf("API error: %s", result.Status())
}
return &resp, nil
}
func main() {
client := NewWeatherClient()
weather, err := client.GetWeather("Beijing")
if err != nil {
fmt.Println("Error:", err)
return
}
fmt.Printf("Weather in %s: %.1f°C, %s\n",
weather.City, weather.Temperature, weather.Condition)
}
效果:自动重试,错误处理,代码保持简洁。
场景二:微服务间调用
微服务间调用需要负载均衡和熔断。
type ServiceClient struct {
client *resty.Client
}
func NewServiceClient() *ServiceClient {
client := resty.New()
// 配置负载均衡
client.SetLoadBalancer(&resty.LoadBalancerConfig{
Strategy: resty.LBStrategyRoundRobin,
Backends: []string{
"http://service-1:8080",
"http://service-2:8080",
"http://service-3:8080",
},
})
// 配置熔断
client.SetCircuitBreaker(&resty.CircuitBreakerConfig{
MaxRequests: 100,
Interval: 60 * time.Second,
Timeout: 10 * time.Second,
})
// 配置重试
client.SetRetryCount(2)
return &ServiceClient{client: client}
}
func (c *ServiceClient) CallService(ctx context.Context, req *Request) (*Response, error) {
var resp Response
result, err := c.client.R().
SetContext(ctx).
SetBody(req).
SetResult(&resp).
Post("/api/process")
if err != nil {
return nil, fmt.Errorf("service call failed: %w", err)
}
if result.IsError() {
return nil, fmt.Errorf("service error: %s", result.Status())
}
return &resp, nil
}
效果:负载均衡、熔断保护、提高可用性。
场景三:API 测试工具
需要测试 API 并生成 curl 命令。
func TestAPI() {
client := resty.New()
req := client.R().
SetHeader("Authorization", "Bearer token123").
SetBody(map[string]string{"name": "John"}).
SetResult(&User{})
// 生成 curl 命令
curlCmd := req.GenerateCurlCommand()
fmt.Println("Curl command:", curlCmd)
// 执行请求
resp, err := req.Post("https://api.example.com/users")
if err != nil {
fmt.Println("Error:", err)
return
}
fmt.Println("Response:", resp.String())
}
效果:生成 curl 命令,方便调试。
这些模式也可以用在爬虫的批量请求和超时控制上。
设计亮点
亮点一:链式调用
流式 API,代码简洁。可读性强,减少临时变量。
亮点二:自动 JSON 处理
自动序列化/反序列化。减少样板代码,类型安全,减少错误。
亮点三:重试机制
内置重试逻辑。提高成功率,应对网络波动,策略可配置。
亮点四:熔断降级
保护系统稳定性。防止雪崩,快速失败,自动恢复。
亮点五:中间件机制
可扩展的请求/响应处理。用于日志记录、认证注入等。
和类似方案对比
vs. net/http
| 维度 | go-resty | net/http |
|---|---|---|
| API | 链式调用 | 繁琐 |
| JSON | 自动处理 | 手动 |
| 重试 | 内置 | 无 |
| 熔断 | 内置 | 无 |
结论:go-resty 更易用,net/http 更底层。
vs. req
| 维度 | go-resty | req |
|---|---|---|
| Star | 11.8K | 6K |
| API | 链式 | 链式 |
| 功能 | 全面 | 精简 |
| 性能 | 好 | 好 |
结论:go-resty 功能更全,req 更轻量。
vs. heimdall
| 维度 | go-resty | heimdall |
|---|---|---|
| Star | 11.8K | 2K |
| 重试 | 内置 | 内置 |
| 熔断 | 内置 | 无 |
| SSE | 支持 | 无 |
结论:go-resty 功能更全。
局限性
go-resty 也有局限:
- 性能开销:封装带来一定性能损失
- 学习成本:API 较多,需要学习
- 依赖较多:比标准库复杂
- 调试困难:链式调用调试较难
- 过度封装:可能隐藏底层细节
- 版本兼容:v3 和 v2 有 breaking changes
选择时可以确认是否真的需要高级功能;性能敏感场景可以用 net/http;简单场景可以用更轻量的库;调试时可以用 GenerateCurlCommand。
总结
从 go-resty 可以看到几个趋势:
- 链式调用是主流:流式 API,代码简洁
- 自动处理是刚需:JSON 自动序列化
- 重试熔断很重要:提高系统稳定性
- 中间件是扩展方式:灵活处理请求/响应
- 开发者体验优先:让 HTTP 调用更优雅
如果还在用 net/http 写繁琐的 HTTP 调用,可以试试 go-resty。
参考资料:
- go-resty GitHub: https://github.com/go-resty/resty
- go-resty 文档: https://pkg.go.dev/github.com/go-resty/resty/v3
- net/http 文档: https://pkg.go.dev/net/http