在项目开发过程中,HTTP 请求可以说是非常常见的需求,无论是与外部 API 交互,还是实现微服务间的通信。

这篇文章以 Go 语言为背景,探讨 HTTP 客户端的构建。Go 的标准库 net/http 虽然功能强大,但在进行复杂的 HTTP 请求时,往往需要开发者写很多重复代码。

在这种情况下,开发者就需要一个既简单直观又功能强大的 HTTP 客户端库,最终我的调研结果为 go-resty/resty/v2,让我们看一下这强大的组件。

1. 安装

可以通过以下命令安装:

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

2. 创建一个简单的 HTTP 客户端

通过 resty.New() 来创建一个新的 Resty Client 实例。

package main

import (
 "fmt"
 "github.com/go-resty/resty/v2"
)

func main() {
 // 创建一个新的 Resty 客户端
 client := resty.New()

 // 发送 GET 请求
 resp, err := client.R().Get("https://demo.com/get")

 if err != nil {
  fmt.Printf("请求失败: %v\n", err)
 } else {
  fmt.Printf("响应状态码: %d\n", resp.StatusCode())
  fmt.Printf("响应正文: %s\n", resp.String())
 }
}

3. 常见的 HTTP 请求类型

Resty 提供了简化的 API 来处理不同的 HTTP 请求方法,例如 GET、POST、PUT、PATCH、DELETE 等。

package main

import (
 "fmt"
 "github.com/go-resty/resty/v2"
)

func main() {
 client := resty.New()

 // 发送 POST 请求,附带 JSON 数据
 resp, err := client.R().
  SetHeader("Content-Type", "application/json").
  SetBody(`{"username": "test", "password": "1234"}`).
  Post("https://demo.com/post")

 if err != nil {
  fmt.Printf("请求失败: %v\n", err)
 } else {
  fmt.Printf("响应状态码: %d\n", resp.StatusCode())
  fmt.Printf("响应正文: %s\n", resp.String())
 }
}

4. 请求参数和 Headers

可以使用 SetQueryParam 和 SetHeader 方法轻松地为请求添加查询参数和请求头。

设置查询参数:

resp, err := client.R().
 SetQueryParam("key1", "value1").
 SetQueryParam("key2", "value2").
 Get("https://demo.com/get")

设置请求头:

resp, err := client.R().
 SetHeader("Accept", "application/json").
 Get("https://demo.com/get")

5. 发送 JSON 和 XML 数据

支持通过 SetBody 方法发送 JSON 或 XML 格式的数据。可以直接传递结构体、字符串、字节数组等。

发送 JSON 数据:

resp, err := client.R().
 SetHeader("Content-Type", "application/json").
 SetBody(map[string]string{"username": "test", "password": "1234"}).
 Post("https://demo.com/post")

发送 XML 数据:

resp, err := client.R().
 SetHeader("Content-Type", "application/xml").
 SetBody(`<user><name>test</name><password>1234</password></user>`).
 Post("https://demo.com/post")

6. 自动解析 JSON 响应

允许将响应的 JSON 数据自动解析到结构体中,使用 SetResult 或 SetError 方法来处理。

type User struct {
 Name  string `json:"name"`
 Email string `json:"email"`
}

func main() {
 client := resty.New()

 user := &User{}
 resp, err := client.R().
  SetResult(user). // 自动解析 JSON 响应到 user 结构体
  Get("https://demo.com/users/1")

 if err != nil {
  fmt.Printf("请求失败: %v\n", err)
 } else {
  fmt.Printf("用户名: %s\n", user.Name)
  fmt.Printf("邮箱: %s\n", user.Email)
 }
}

7. 处理表单数据

支持发送 x-www-form-urlencoded 或 multipart/form-data 表单数据。

resp, err := client.R().
 SetFormData(map[string]string{
  "username": "test",
  "password": "1234",
 }).
 Post("https://demo.com/post")

8. 设置超时

可以通过 SetTimeout 方法为请求设置超时时间。

client := resty.New().
 SetTimeout(5 * time.Second) // 请求超时设置为 5 秒

9. 重试机制

内置了重试机制,允许你为失败的请求设置自动重试。

client := resty.New().
 SetRetryCount(3). // 重试 3 次
 SetRetryWaitTime(2 * time.Second) // 每次重试之间等待 2 秒
 SetRetryMaxWaitTime(10 * time.Second) // 最大重试等待时间 10 秒

resp, err := client.R().
 AddRetryCondition(
  // 如果状态码为 500 则重试
  func(r *resty.Response, err error) bool {
   return r.StatusCode() == 500
  },
 ).
 Get("https://demo.com/status/500")

10. 文件上传与下载

文件上传:

resp, err := client.R().
 SetFile("file", "example.txt").
 Post("https://demo.com/post")

文件下载:

resp, err := client.R().
 SetOutput("output.txt"). // 将响应内容保存到文件
 Get("https://demo.com/get")

11. Cookie 管理

支持通过 SetCookie 方法为请求设置 Cookie,也可以通过 SetCookies 方法批量设置多个 Cookie。

设置单个 Cookie:

client.R().
 SetCookie(&http.Cookie{
  Name:  "session_id",
  Value: "abc123",
 }).
 Get("https://demo.com/cookies")

设置多个 Cookie:

client.R().
 SetCookies([]*http.Cookie{
  {Name: "cookie1", Value: "value1"},
  {Name: "cookie2", Value: "value2"},
 }).
 Get("https://demo.com/cookies")

12. 全局请求设置

允许你通过 client.Set 方法为所有请求设置全局配置。例如,设置所有请求的默认头部或超时时间。

client := resty.New().
 SetHeader("Accept", "application/json") // 所有请求都带有 Accept 头

13. 代理支持

支持通过设置代理服务器来发送请求。

client := resty.New().
 SetProxy("http://myproxy:8080")

14. 请求与响应日志

内置了请求和响应的日志功能,允许你轻松调试 HTTP 请求。

client := resty.New().
 SetDebug(true) // 启用调试模式,打印所有请求和响应细节

15. 自定义 Transport 与 Middleware

允许通过 SetTransport 自定义 HTTP Transport,并支持为每个请求设置自定义的 Middleware。

client := resty.New().
 SetTransport(&http.Transport{
  Proxy: http.ProxyFromEnvironment,
 })

16. 链式调用

支持链式调用,允许你以简洁的方式进行多个设置操作。

client.R().
 SetHeader("Accept", "application/json").
 SetQueryParam("id", "123").
 SetTimeout(5 * time.Second).
 Get("https://demo.com/get")

17. 预定义请求(Pre-request Hooks)

允许在实际发送 HTTP 请求之前设置预定义的请求(Pre-request Hook)。

可以使用 OnBeforeRequest 钩子在每个请求发送前执行一些操作,例如修改请求参数、设置头部,或者做一些日志记录。

client := resty.New().
 OnBeforeRequest(func(c *resty.Client, r *resty.Request) error {
  fmt.Println("请求即将发送: ", r.URL)
  // 可以在这里动态设置请求头或其他参数
  r.SetHeader("Custom-Header", "MyValue")
  return nil
 })

resp, err := client.R().Get("https://demo.com/get")

18. Post-request Hooks

类似于 OnBeforeRequest。

也支持 OnAfterResponse 钩子,这个钩子允许在请求响应后执行一些操作,比如记录日志、修改响应内容等。

client := resty.New().
 OnAfterResponse(func(c *resty.Client, r *resty.Response) error {
  fmt.Printf("请求完成,状态码: %d\n", r.StatusCode())
  // 可以在这里做一些后处理,例如检查响应内容或记录日志
  return nil
 })

resp, err := client.R().Get("https://demo.com/get")

19. 多部分表单(Multipart Form)上传

在处理文件上传时,支持通过多部分表单数据发送多个文件或数据段。

可以使用 SetFileReader 或 SetFormData 来上传文件和表单字段。

fileReader, err := os.Open("example.txt")
if err != nil {
 log.Fatal(err)
}
defer fileReader.Close()

resp, err := client.R().
 SetFileReader("file", "example.txt", fileReader). // 使用文件读取器
 SetFormData(map[string]string{
  "username": "test",
  "password": "1234",
 }).
 Post("https://demo.com/post")

20. HTTP 代理、认证和 TLS

提供了强大的代理支持、基本认证、Bearer Token 和自定义 TLS 配置等功能。

使用代理:

client.SetProxy("http://proxy-server:8080")

使用认证和 Bearer Token:

client.R().SetBasicAuth("username", "password").Get("https://demo.com/basic-auth/user/passwd")

client.R().SetAuthToken("your-token").Get("https://demo.com/bearer")

自定义 TLS 配置:

client.SetTLSClientConfig(&tls.Config{InsecureSkipVerify: true})

21. 上下文(Context 支持)

完全支持 Go 的 context.Context,可以通过 SetContext 来为请求设置上下文,以便在处理超时或取消请求时使用。

ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()

resp, err := client.R().
 SetContext(ctx). // 使用 context 控制请求
 Get("https://demo.com/delay/5") // 这个 URL 会延迟 5 秒返回,context 会在 2 秒后超时

22. 自定义错误处理

允许通过 SetError 来指定一个用于处理错误的结构体。

当 HTTP 请求返回非 2xx 的状态码时,可以自动将响应体解析到指定的错误结构体中。

type ErrorResponse struct {
 Message string `json:"message"`
}

errResponse := &ErrorResponse{}
resp, err := client.R().
 SetError(errResponse). // 自动将错误响应解析为 ErrorResponse
 Get("https://demo.com/status/400")

if err != nil {
 fmt.Println("请求失败:", errResponse.Message)
}

23. REST API 版本控制

支持通过 SetPathParams 动态控制 URL 中的路径参数,适合用于处理 RESTful API 的版本控制。

resp, err := client.R().
 SetPathParams(map[string]string{
  "version": "v1",
  "id":      "1234",
 }).
 Get("https://api.demo.com/{version}/users/{id}")

24. 多请求并发处理

可以通过 Go 的 goroutine 与 channel 轻松处理并发请求,尤其适合需要同时向多个 API 发起请求的场景。

urls := []string{
 "https://demo.com/get?1",
 "https://demo.com/get?2",
 "https://demo.com/get?3",
}

ch := make(chan *resty.Response)

for _, url := range urls {
 go func(url string) {
  resp, _ := client.R().Get(url)
  ch <- resp
 }(url)
}

for range urls {
 resp := <-ch
 fmt.Println("响应状态码:", resp.StatusCode())
}

25. 全局错误处理器

可以设置全局的错误处理函数,确保每次请求在发生错误时都会调用这个函数。

通过 SetError 可以为每个请求配置错误处理。

client := resty.New().
 OnError(func(req *resty.Request, err error) {
  fmt.Printf("请求 %s 失败: %v\n", req.URL, err)
 })

resp, err := client.R().Get("https://demo.com/status/404")

小结

工作中常用的功能,基本上都包括了...

如果你正在开发需要处理 HTTP 请求的应用,resty 是一个非常值得使用的工具。

在我封装的框架中,HTTP 客户端使用的正是 Resty。