HTTP 客户端
概述
本章将深入介绍 Hertz 的 HTTP 客户端。Hertz 提供了高性能的 HTTP 客户端实现,支持连接池、超时控制、中间件等特性,底层同样使用 Netpoll 网络库,性能优异。
核心内容
客户端基础
创建客户端
go
package main
import (
"context"
"fmt"
"time"
"github.com/cloudwego/hertz/pkg/common/client"
)
func main() {
cli := client.NewClient(
client.WithTimeout(10*time.Second),
client.WithMaxConnsPerHost(100),
)
resp, err := cli.Get(context.Background(), nil, "https://httpbin.org/get")
if err != nil {
panic(err)
}
fmt.Printf("Status: %d\n", resp.StatusCode())
fmt.Printf("Body: %s\n", string(resp.Body()))
}基本请求方法
go
package main
import (
"context"
"github.com/cloudwego/hertz/pkg/common/client"
)
func main() {
cli := client.NewClient()
// GET 请求
resp, err := cli.Get(context.Background(), nil, "https://httpbin.org/get?name=hertz")
// POST 请求
resp, err = cli.Post(context.Background(), nil, "https://httpbin.org/post", nil)
// PUT 请求
resp, err = cli.Put(context.Background(), nil, "https://httpbin.org/put", nil)
// DELETE 请求
resp, err = cli.Delete(context.Background(), nil, "https://httpbin.org/delete", nil)
// HEAD 请求
resp, err = cli.Head(context.Background(), nil, "https://httpbin.org/get")
// OPTIONS 请求
resp, err = cli.Options(context.Background(), nil, "https://httpbin.org/get")
}请求配置
设置请求头
go
func main() {
cli := client.NewClient()
req := &protocol.Request{}
req.SetMethod("GET")
req.SetRequestURI("https://httpbin.org/headers")
req.SetHeader("Content-Type", "application/json")
req.SetHeader("Authorization", "Bearer token123")
req.SetHeader("X-Custom-Header", "custom-value")
resp := &protocol.Response{}
if err := cli.Do(context.Background(), req, resp); err != nil {
panic(err)
}
fmt.Println(string(resp.Body()))
}设置查询参数
go
import (
"github.com/cloudwego/hertz/pkg/protocol"
"github.com/cloudwego/hertz/pkg/protocol/consts"
)
func main() {
cli := client.NewClient()
req := &protocol.Request{}
req.SetMethod(consts.MethodGet)
req.SetRequestURI("https://httpbin.org/get")
req.SetQueryString("name=hertz&version=1.0")
resp := &protocol.Response{}
cli.Do(context.Background(), req, resp)
}设置请求体
go
func main() {
cli := client.NewClient()
req := &protocol.Request{}
req.SetMethod(consts.MethodPost)
req.SetRequestURI("https://httpbin.org/post")
req.SetBody([]byte(`{"name":"hertz","version":"1.0"}`))
req.SetHeader("Content-Type", "application/json")
resp := &protocol.Response{}
if err := cli.Do(context.Background(), req, resp); err != nil {
panic(err)
}
fmt.Println(string(resp.Body()))
}JSON 请求
发送 JSON 数据
go
import (
"encoding/json"
"github.com/cloudwego/hertz/pkg/common/json"
)
type User struct {
Name string `json:"name"`
Email string `json:"email"`
}
func main() {
cli := client.NewClient()
user := &User{
Name: "Hertz",
Email: "hertz@example.com",
}
body, _ := json.Marshal(user)
req := &protocol.Request{}
req.SetMethod(consts.MethodPost)
req.SetRequestURI("https://httpbin.org/post")
req.SetBody(body)
req.SetHeader("Content-Type", "application/json")
resp := &protocol.Response{}
cli.Do(context.Background(), req, resp)
fmt.Println(string(resp.Body()))
}解析 JSON 响应
go
type Response struct {
Args map[string]interface{} `json:"args"`
Headers map[string]string `json:"headers"`
Origin string `json:"origin"`
URL string `json:"url"`
}
func main() {
cli := client.NewClient()
resp, err := cli.Get(context.Background(), nil, "https://httpbin.org/get")
if err != nil {
panic(err)
}
var result Response
if err := json.Unmarshal(resp.Body(), &result); err != nil {
panic(err)
}
fmt.Printf("Origin: %s\n", result.Origin)
fmt.Printf("URL: %s\n", result.URL)
}表单请求
URL 编码表单
go
func main() {
cli := client.NewClient()
req := &protocol.Request{}
req.SetMethod(consts.MethodPost)
req.SetRequestURI("https://httpbin.org/post")
args := protocol.Args{}
args.Set("username", "hertz")
args.Set("password", "123456")
req.SetFormData(args)
req.SetHeader("Content-Type", "application/x-www-form-urlencoded")
resp := &protocol.Response{}
cli.Do(context.Background(), req, resp)
fmt.Println(string(resp.Body()))
}文件上传
go
import (
"os"
"github.com/cloudwego/hertz/pkg/protocol/multipart"
)
func main() {
cli := client.NewClient()
file, _ := os.Open("test.txt")
defer file.Close()
req := &protocol.Request{}
req.SetMethod(consts.MethodPost)
req.SetRequestURI("https://httpbin.org/post")
mw := multipart.NewWriter(req.BodyWriter())
mw.WriteField("name", "hertz")
mw.WriteFile("file", "test.txt", file)
mw.Close()
req.SetHeader("Content-Type", mw.FormDataContentType())
resp := &protocol.Response{}
cli.Do(context.Background(), req, resp)
fmt.Println(string(resp.Body()))
}高级配置
超时控制
go
func main() {
cli := client.NewClient(
client.WithTimeout(5*time.Second),
client.WithConnectTimeout(3*time.Second),
client.WithReadTimeout(10*time.Second),
client.WithWriteTimeout(10*time.Second),
)
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
resp, err := cli.Get(ctx, nil, "https://httpbin.org/delay/5")
if err != nil {
fmt.Println("Request timeout:", err)
return
}
fmt.Println(string(resp.Body()))
}连接池配置
go
func main() {
cli := client.NewClient(
client.WithMaxConnsPerHost(100),
client.WithMaxIdleConnDuration(30*time.Second),
client.WithMaxConnDuration(60*time.Second),
client.WithMaxConnWaitTimeout(5*time.Second),
)
for i := 0; i < 10; i++ {
go func() {
resp, _ := cli.Get(context.Background(), nil, "https://httpbin.org/get")
fmt.Println(resp.StatusCode())
}()
}
time.Sleep(5 * time.Second)
}TLS 配置
go
import (
"crypto/tls"
"crypto/x509"
"io/ioutil"
)
func main() {
certPool := x509.NewCertPool()
caCert, _ := ioutil.ReadFile("ca.crt")
certPool.AppendCertsFromPEM(caCert)
cli := client.NewClient(
client.WithTLSConfig(&tls.Config{
RootCAs: certPool,
InsecureSkipVerify: false,
}),
)
resp, _ := cli.Get(context.Background(), nil, "https://example.com")
fmt.Println(resp.StatusCode())
}代理设置
HTTP 代理
go
import (
"github.com/cloudwego/hertz/pkg/common/config"
)
func main() {
cli := client.NewClient()
req := &protocol.Request{}
req.SetMethod(consts.MethodGet)
req.SetRequestURI("https://httpbin.org/ip")
req.SetOptions(config.WithDialer(
&config.Dialer{
Proxy: func(req *protocol.Request) *protocol.URI {
proxyURI := &protocol.URI{}
proxyURI.Parse(nil, []byte("http://127.0.0.1:8080"))
return proxyURI
},
},
))
resp := &protocol.Response{}
cli.Do(context.Background(), req, resp)
fmt.Println(string(resp.Body()))
}重试机制
自定义重试
go
func main() {
cli := client.NewClient()
maxRetries := 3
for i := 0; i < maxRetries; i++ {
resp, err := cli.Get(context.Background(), nil, "https://httpbin.org/status/500")
if err == nil && resp.StatusCode() < 500 {
fmt.Println("Success:", resp.StatusCode())
return
}
fmt.Printf("Retry %d: %v\n", i+1, err)
time.Sleep(time.Duration(i+1) * time.Second)
}
fmt.Println("Max retries exceeded")
}响应处理
读取响应
go
func main() {
cli := client.NewClient()
resp, err := cli.Get(context.Background(), nil, "https://httpbin.org/get")
if err != nil {
panic(err)
}
fmt.Printf("Status Code: %d\n", resp.StatusCode())
fmt.Printf("Content-Length: %d\n", resp.ContentLength())
resp.Header.VisitAll(func(key, value []byte) {
fmt.Printf("Header: %s = %s\n", key, value)
})
fmt.Printf("Body: %s\n", string(resp.Body()))
}流式读取
go
func main() {
cli := client.NewClient()
req := &protocol.Request{}
req.SetMethod(consts.MethodGet)
req.SetRequestURI("https://httpbin.org/stream-bytes/1024")
resp := &protocol.Response{}
resp.SkipBody = false
if err := cli.Do(context.Background(), req, resp); err != nil {
panic(err)
}
reader := resp.BodyStream()
if reader != nil {
buf := make([]byte, 1024)
for {
n, err := reader.Read(buf)
if err != nil {
break
}
fmt.Printf("Read %d bytes\n", n)
}
}
}完整示例
go
package main
import (
"context"
"encoding/json"
"fmt"
"time"
"github.com/cloudwego/hertz/pkg/common/client"
"github.com/cloudwego/hertz/pkg/common/json"
"github.com/cloudwego/hertz/pkg/protocol"
"github.com/cloudwego/hertz/pkg/protocol/consts"
)
type User struct {
ID int `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
}
type APIResponse struct {
Code int `json:"code"`
Data interface{} `json:"data"`
Msg string `json:"msg"`
}
func main() {
cli := client.NewClient(
client.WithTimeout(10*time.Second),
client.WithMaxConnsPerHost(50),
)
fmt.Println("=== GET Request ===")
doGet(cli)
fmt.Println("\n=== POST Request ===")
doPost(cli)
fmt.Println("\n=== PUT Request ===")
doPut(cli)
fmt.Println("\n=== DELETE Request ===")
doDelete(cli)
}
func doGet(cli *client.Client) {
req := &protocol.Request{}
req.SetMethod(consts.MethodGet)
req.SetRequestURI("https://httpbin.org/get")
req.SetQueryString("name=hertz&version=1.0")
req.SetHeader("X-API-Key", "your-api-key")
resp := &protocol.Response{}
if err := cli.Do(context.Background(), req, resp); err != nil {
fmt.Println("Error:", err)
return
}
fmt.Printf("Status: %d\n", resp.StatusCode())
fmt.Printf("Body: %s\n", string(resp.Body()))
}
func doPost(cli *client.Client) {
user := &User{
Name: "Hertz",
Email: "hertz@example.com",
}
body, _ := json.Marshal(user)
req := &protocol.Request{}
req.SetMethod(consts.MethodPost)
req.SetRequestURI("https://httpbin.org/post")
req.SetBody(body)
req.SetHeader("Content-Type", "application/json")
resp := &protocol.Response{}
if err := cli.Do(context.Background(), req, resp); err != nil {
fmt.Println("Error:", err)
return
}
fmt.Printf("Status: %d\n", resp.StatusCode())
var result map[string]interface{}
json.Unmarshal(resp.Body(), &result)
fmt.Printf("JSON: %v\n", result["json"])
}
func doPut(cli *client.Client) {
user := &User{
ID: 1,
Name: "Hertz Updated",
Email: "hertz-updated@example.com",
}
body, _ := json.Marshal(user)
req := &protocol.Request{}
req.SetMethod(consts.MethodPut)
req.SetRequestURI("https://httpbin.org/put")
req.SetBody(body)
req.SetHeader("Content-Type", "application/json")
resp := &protocol.Response{}
cli.Do(context.Background(), req, resp)
fmt.Printf("Status: %d\n", resp.StatusCode())
}
func doDelete(cli *client.Client) {
req := &protocol.Request{}
req.SetMethod(consts.MethodDelete)
req.SetRequestURI("https://httpbin.org/delete")
resp := &protocol.Response{}
cli.Do(context.Background(), req, resp)
fmt.Printf("Status: %d\n", resp.StatusCode())
}小结
本章介绍了 Hertz 的 HTTP 客户端:
- 客户端基础:创建客户端、基本请求方法
- 请求配置:请求头、查询参数、请求体
- JSON 请求:发送和解析 JSON 数据
- 表单请求:URL 编码表单、文件上传
- 高级配置:超时控制、连接池、TLS 配置
- 代理设置:HTTP 代理配置
- 响应处理:读取响应、流式读取
在下一章中,我们将学习 Hertz 的 SSE 流式响应。