Skip to content

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 客户端:

  1. 客户端基础:创建客户端、基本请求方法
  2. 请求配置:请求头、查询参数、请求体
  3. JSON 请求:发送和解析 JSON 数据
  4. 表单请求:URL 编码表单、文件上传
  5. 高级配置:超时控制、连接池、TLS 配置
  6. 代理设置:HTTP 代理配置
  7. 响应处理:读取响应、流式读取

在下一章中,我们将学习 Hertz 的 SSE 流式响应。