网络请求
网络请求
Go 标准库已经提供完整的 HTTP 客户端。第三方库主要用于简化 JSON、认证、重试、调试和中间件等常见操作。
| 名称 | 特点 | 适用场景 |
|---|---|---|
net/http | 标准库、生态兼容性最好、控制粒度高 | 大多数项目和公共 SDK |
Resty | 链式 API、JSON 编解码、认证、中间件和重试 | 常规 REST API 调用 |
Req | API 简洁,内置调试、自动解码、重试和 HTTP/3 | 偏好开箱即用体验的新项目 |
go-retryablehttp | 在 net/http 之上提供退避重试 | 需要保留标准库接口并增强重试 |
fasthttp | 独立 HTTP 实现,针对极高吞吐和低分配优化 | 性能测试证明 net/http 是瓶颈的特殊场景 |
通常从 net/http 开始;想减少业务 API 的样板代码时选择 Resty 或 Req;只缺少可靠重试时选择 go-retryablehttp。fasthttp 与 net/http 生态不完全兼容,不应仅因为名称里有“fast”就默认使用。
net/http
自定义并复用 http.Client,不要在每次请求时创建新客户端:
client := &http.Client{
Timeout: 10 * time.Second,
}
request, err := http.NewRequestWithContext(ctx, http.MethodGet,
"https://api.example.com/users/1", nil)
if err != nil {
return err
}
request.Header.Set("Accept", "application/json")
response, err := client.Do(request)
if err != nil {
return err
}
defer response.Body.Close()
if response.StatusCode != http.StatusOK {
return fmt.Errorf("unexpected status: %s", response.Status)
}
var user User
if err := json.NewDecoder(response.Body).Decode(&user); err != nil {
return err
}
标准库代码稍长,但没有额外依赖,并且容易通过自定义 http.RoundTripper 添加日志、追踪、认证或 Mock。
Resty
Resty 适合频繁调用 JSON API 的业务项目。当前稳定版使用 v2 导入路径:
go get github.com/go-resty/resty/v2
client := resty.New().
SetBaseURL("https://api.example.com").
SetTimeout(10 * time.Second).
SetRetryCount(3)
var user User
response, err := client.R().
SetContext(ctx).
SetHeader("Accept", "application/json").
SetAuthToken(token).
SetResult(&user).
Get("/users/1")
if err != nil {
return err
}
if response.IsError() {
return fmt.Errorf("unexpected status: %s", response.Status())
}
可以在 Client 级别统一配置 Base URL、Header、认证和中间件,再在单次请求中覆盖。
Req
Req 提供类似 Resty 的链式 API,并强化了开发调试、自动类型识别、请求重试和协议支持。
go get github.com/imroc/req/v3
client := req.C().
SetBaseURL("https://api.example.com").
SetTimeout(10 * time.Second).
SetCommonBearerAuthToken(token)
var user User
response, err := client.R().
SetContext(ctx).
SetSuccessResult(&user).
Get("/users/1")
if err != nil {
return err
}
if !response.IsSuccessState() {
return fmt.Errorf("unexpected status: %s", response.Status)
}
开发模式可能输出请求头和请求体,启用前应确认日志不会泄漏 Token、Cookie 或个人信息。
go-retryablehttp
只想在标准客户端上增加指数退避重试时,可以使用 go-retryablehttp:
go get github.com/hashicorp/go-retryablehttp
client := retryablehttp.NewClient()
client.RetryMax = 3
client.RetryWaitMin = 200 * time.Millisecond
client.RetryWaitMax = 2 * time.Second
client.HTTPClient.Timeout = 10 * time.Second
request, err := retryablehttp.NewRequestWithContext(
ctx,
http.MethodGet,
"https://api.example.com/users/1",
nil,
)
if err != nil {
return err
}
response, err := client.Do(request)
StandardClient 可以返回普通的 *http.Client,便于接入只接受标准库客户端的 SDK。
重试边界
网络失败不代表服务端没有完成操作。重试前需要判断请求是否幂等:
GET、HEAD等读取请求通常可以重试。PUT和DELETE是否可重试取决于服务端实现。POST默认不应自动重试;支付、下单等操作应使用幂等键。- 重试次数、单次超时和总 Deadline 应同时受控,并加入指数退避与随机抖动。
429和503响应应在可信范围内遵守Retry-After。
实践建议
- 复用 Client 和底层连接池,不要为每个请求创建 Client。
- 每个请求都传递
context.Context,设置合理的超时和取消条件。 - 使用
net/http时及时关闭响应体;需要复用连接时还应完整读取响应体。 - 限制错误响应体的读取大小,避免把超大响应全部载入内存。
- 日志应记录方法、主机、状态码、耗时和请求 ID,但不能记录认证信息和敏感 Body。
- 使用
httptest.Server或自定义 Transport 测试客户端,不要让单元测试依赖真实外部服务。