From 01695a620f0fdb751fa4245616eb214480cd17b8 Mon Sep 17 00:00:00 2001 From: chaoranz758 <110596971+chaoranz758@users.noreply.github.com> Date: Sat, 22 Jul 2023 20:39:02 +0800 Subject: [PATCH] docs: Modify directory structure and supplement some content --- .../hertz/tutorials/basic-feature/client.md | 1660 +++++------------ 1 file changed, 451 insertions(+), 1209 deletions(-) diff --git a/content/zh/docs/hertz/tutorials/basic-feature/client.md b/content/zh/docs/hertz/tutorials/basic-feature/client.md index 60be49cdd2..3c73c33727 100644 --- a/content/zh/docs/hertz/tutorials/basic-feature/client.md +++ b/content/zh/docs/hertz/tutorials/basic-feature/client.md @@ -29,7 +29,6 @@ func performRequest() { req.SetMethod("GET") _ = c.Do(context.Background(), req, resp) fmt.Printf("get response: %s\n", resp.Body()) // status == 200 resp.Body() == []byte("hello hertz") - } func main() { @@ -40,7 +39,6 @@ func main() { go performRequest() h.Spin() } - ``` ## 配置 @@ -52,1343 +50,640 @@ func main() { | MaxIdleConnDuration | 10s | 最大的空闲连接持续时间,空闲的连接在此持续时间后被关闭 | | MaxConnDuration | 0s | 最大的连接持续时间,keep-alive 连接在此持续时间后被关闭 | | MaxConnWaitTimeout | 0s | 等待空闲连接的最大时间 | -| KeepAlive | true | 是否使用 keep-alive 连接 | +| KeepAlive | true | 是否使用 keep-alive 连接,默认使用 | | ReadTimeout | 0s | 完整读取响应(包括 body)的最大持续时间 | -| TLSConfig | nil | 用来创建一个 tls 连接的 tlsConfig | +| TLSConfig | nil | 设置用于创建 tls 连接的 tlsConfig,具体配置信息请看 [tls-config](https://pkg.go.dev/crypto/tls#Config) | | Dialer | network.Dialer | 设置指定的拨号器 | -| ResponseBodyStream | false | 是否在流中读取 body | -| DisableHeaderNamesNormalizing | false | 是否禁用头名称规范化 | +| ResponseBodyStream | false | 是否在流中读取 body,默认不在流中读取 | +| DisableHeaderNamesNormalizing | false | 是否禁用头名称规范化,默认不禁用,如 cONTENT-lenGTH -> Content-Length | | Name | "" | 用户代理头中使用的客户端名称 | -| NoDefaultUserAgentHeader | false | 是否没有默认的 User-Agent 头 | -| DisablePathNormalizing | false | 是否禁用路径规范化 | -| RetryConfig | nil | 重试配置 | -| WriteTimeout | 0s | 写入超时时间 | -| HostClientStateObserve | nil | 观察和记录 `HostClientState` 的状态的函数 | -| ObservationInterval | 5s | 状态观察执行间隔 | -| DialFunc | network.Dialer | 自定义拨号器功能,会覆盖自定义拨号器 | - - -### WithDialTimeout - -`WithDialTimeout` 函数用于设置拨号超时时间。 - -函数签名: - -```go -func WithDialTimeout(dialTimeout time.Duration) config.ClientOption -``` +| NoDefaultUserAgentHeader | false | 是否没有默认的 User-Agent 头,默认有 User-Agent 头 | +| DisablePathNormalizing | false | 是否禁用路径规范化,默认规范路径,如 http://localhost:8080/hello/../ hello -> http://localhost:8080/hello | +| RetryConfig | nil | HTTP 客户端的重试配置,重试配置详细说明请看 [重试](/zh/docs/hertz/tutorials/basic-feature/retry/) | +| WriteTimeout | 0s | HTTP 客户端的写入超时时间 | +| HostClientStateObserve | nil | 观察和记录 HTTP 客户端的连接状态的函数 | +| ObservationInterval | 5s | HTTP 客户端连接状态的观察执行间隔 | +| DialFunc | network.Dialer | 设置 HTTP 客户端拨号器函数,会覆盖自定义拨号器 | 示例代码: ```go -package main - -import ( - "context" - "fmt" - "time" - - "github.com/cloudwego/hertz/pkg/app/client" -) - func main() { - c, err := client.NewClient(client.WithDialTimeout(1 * time.Second)) + observeInterval := 10 * time.Second + stateFunc := func(state config.HostClientState) { + fmt.Printf("state=%v\n", state.ConnPoolState().Addr) + } + var customDialFunc network.DialFunc = func(addr string) (network.Conn, error) { + return nil, nil + } + c, err := client.NewClient( + client.WithDialTimeout(1*time.Second), + client.WithMaxConnsPerHost(1024), + client.WithMaxIdleConnDuration(10*time.Second), + client.WithMaxConnDuration(10*time.Second), + client.WithMaxConnWaitTimeout(10*time.Second), + client.WithKeepAlive(true), + client.WithClientReadTimeout(10*time.Second), + client.WithDialer(standard.NewDialer()), + client.WithResponseBodyStream(true), + client.WithDisableHeaderNamesNormalizing(true), + client.WithName("my-client"), + client.WithNoDefaultUserAgentHeader(true), + client.WithDisablePathNormalizing(true), + client.WithRetryConfig( + retry.WithMaxAttemptTimes(3), + retry.WithInitDelay(1000), + retry.WithMaxDelay(10000), + retry.WithDelayPolicy(retry.DefaultDelayPolicy), + retry.WithMaxJitter(1000), + ), + client.WithWriteTimeout(10*time.Second), + client.WithConnStateObserve(stateFunc, observeInterval), + client.WithDialFunc(customDialFunc, netpoll.NewDialer()), + ) if err != nil { return } - status, body, _ := c.Get(context.Background(), nil, "https://www.example.com") + + status, body, _ := c.Get(context.Background(), nil, "http://www.example.com") fmt.Printf("status=%v body=%v\n", status, string(body)) } ``` -### WithMaxConnsPerHost +## 获取客户端配置信息 + +```go +func (c *Client) GetOptions() *config.ClientOptions +``` + +### GetOptions -`WithMaxConnsPerHost` 函数用于设置每个主机最大连接数。 +`GetOptions` 函数返回 `Client` 实例的 `ClientOptions` 结构体指针。 函数签名: ```go -func WithMaxConnsPerHost(maxConns int) config.ClientOption +func (c *Client) GetOptions() *config.ClientOptions ``` 示例代码: ```go -package main - -import ( - "context" - "fmt" - "log" - - "github.com/cloudwego/hertz/pkg/app" - "github.com/cloudwego/hertz/pkg/app/client" - "github.com/cloudwego/hertz/pkg/app/server" - "github.com/cloudwego/hertz/pkg/protocol/consts" -) - -func performRequest() { - c, err := client.NewClient(client.WithMaxConnsPerHost(1)) +func main() { + c, err := client.NewClient() if err != nil { return } - - for i := 0; i < 10; i++ { - go func() { - status, body, err := c.Get(context.Background(), nil, "http://localhost:8080/hello") - if err != nil { - log.Fatal(err) - } - fmt.Printf("status=%v body=%v\n", status, string(body)) - // Only one received the message: status == 200 resp.Body() == []byte("hello hertz"), - // the error of others is "no free connections available to host". - }() - } + options := c.GetOptions() + fmt.Println(options.DialTimeout) + // options.DialTimeout == 1s } +``` -func main() { - h := server.New(server.WithHostPorts(":8080")) - h.GET("/hello", func(c context.Context, ctx *app.RequestContext) { - ctx.JSON(consts.StatusOK, "hello hertz") - }) - go performRequest() - h.Spin() -} +## 发送请求 + +```go +func (c *Client) Do(ctx context.Context, req *protocol.Request, resp *protocol.Response) error +func (c *Client) DoRedirects(ctx context.Context, req *protocol.Request, resp *protocol.Response, maxRedirectsCount int) error +func (c *Client) Get(ctx context.Context, dst []byte, url string, requestOptions ...config.RequestOption) (statusCode int, body []byte, err error) +func (c *Client) Post(ctx context.Context, dst []byte, url string, postArgs *protocol.Args, requestOptions ...config.RequestOption) (statusCode int, body []byte, err error) ``` -### WithMaxIdleConnDuration +### Do + +Do 函数执行给定的 http 请求并填充给定的 http 响应。请求必须包含至少一个非零的 RequestURI,其中包含完整的 URL 或非零的 Host header + RequestURI。 + +该函数不会跟随重定向,请使用 [Get](#get) 函数或 [DoRedirects](#doredirects) 函数或 [Post](#post) 函数来跟随重定向。 -`WithMaxIdleConnDuration` 函数用于设置空闲连接的最大持续时间。如果一条连接的空闲时间超过了设置的最大持续时间,它将被关闭。 +如果 resp 为 nil,则会忽略响应。如果所有针对请求主机的 DefaultMaxConnsPerHost 连接都已忙,则会返回 `ErrNoFreeConns` 错误。在性能关键的代码中,建议通过 AcquireRequest 和 AcquireResponse 获取 req 和 resp。 函数签名: ```go -func WithMaxIdleConnDuration(maxIdleConnDuration time.Duration) config.ClientOption +func (c *Client) Do(ctx context.Context, req *protocol.Request, resp *protocol.Response) error ``` 示例代码: ```go -package main - -import ( - "context" - "fmt" - "time" +func main() { + // hertz server:http://localhost:8080/ping ctx.String(consts.StatusOK, "pong") + c, err := client.NewClient() + if err != nil { + return + } - "github.com/cloudwego/hertz/pkg/app/client" -) + req, res := &protocol.Request{}, &protocol.Response{} + req.SetMethod(consts.MethodGet) + req.SetRequestURI("http://localhost:8080/ping") -func main() { - c, err := client.NewClient(client.WithMaxIdleConnDuration(30 * time.Second)) - if err != nil { - return - } - status, body, _ := c.Get(context.Background(), nil, "https://www.example.com") - fmt.Printf("status=%v body=%v\n", status, string(body)) + err = c.Do(context.Background(), req, res) + fmt.Printf("resp = %v,err = %+v", string(res.Body()), err) + // resp.Body() == []byte("pong") err == } ``` -### WithMaxConnDuration +### DoRedirects -`WithMaxConnDuration` 函数用于设置连接的最大持续时间。如果一条连接的持续时间超过了设置的最大持续时间,它将被关闭。 +DoRedirects 函数执行给定的 http 请求并填充给定的 http 响应,遵循最多 maxRedirectsCount 次重定向。当重定向次数超过 maxRedirectsCount 时,将返回 `ErrTooManyRedirects` 错误。 函数签名: ```go -func WithMaxConnDuration(maxConnDuration time.Duration) config.ClientOption +func (c *Client) DoRedirects(ctx context.Context, req *protocol.Request, resp *protocol.Response, maxRedirectsCount int) error ``` 示例代码: ```go -package main +func main() { + // hertz server + // http://localhost:8080/redirect ctx.Redirect(consts.StatusMovedPermanently, []byte("/redirect2")) + // http://localhost:8080/redirect2 ctx.Redirect(consts.StatusMovedPermanently, []byte("/redirect3")) + // http://localhost:8080/redirect3 ctx.String(consts.StatusOK, "pong") -import ( - "context" - "fmt" - "time" + c, err := client.NewClient() + if err != nil { + return + } - "github.com/cloudwego/hertz/pkg/app/client" -) + req, res := &protocol.Request{}, &protocol.Response{} + req.SetMethod(consts.MethodGet) + req.SetRequestURI("http://localhost:8080/redirect") -func main() { - c, err := client.NewClient(client.WithMaxConnDuration(10 * time.Second)) - if err != nil { - return - } - status, body, _ := c.Get(context.Background(), nil, "https://www.example.com") - fmt.Printf("status=%v body=%v\n", status, string(body)) + err = c.DoRedirects(context.Background(), req, res, 1) + fmt.Printf("resp = %v,err = %+v\n", string(res.Body()), err) + // res.Body() == []byte("") err.Error() == "too many redirects detected when doing the request" + + err = c.DoRedirects(context.Background(), req, res, 2) + fmt.Printf("resp = %v,err = %+v\n", string(res.Body()), err) + // res.Body() == []byte("pong") err == } ``` -### WithMaxConnWaitTimeout +### Get + +Get 函数返回 URL 的状态码和响应体。如果 dst 太小,则将被响应体替换并返回,否则将分配一个新的切片。 -`WithMaxConnWaitTimeout` 函数用于设置等待空闲连接的最大持续时间。当 HTTP 客户端需要新的连接时,如果没有可用的空闲连接,它将等待最长时间来获得一个新的连接。如果等待时间超过了设置的最大持续时间,它将返回一个错误。 +该函数会自动跟随重定向。 函数签名: ```go -func WithMaxConnWaitTimeout(t time.Duration) config.ClientOption +func (c *Client) Get(ctx context.Context, dst []byte, url string, requestOptions ...config.RequestOption) (statusCode int, body []byte, err error) ``` 示例代码: ```go -package main - -import ( - "context" - "fmt" - "time" - - "github.com/cloudwego/hertz/pkg/app/client" -) - func main() { - c, err := client.NewClient(client.WithMaxConnWaitTimeout(5 * time.Second)) - if err != nil { - return - } - status, body, _ := c.Get(context.Background(), nil, "https://www.example.com") - fmt.Printf("status=%v body=%v\n", status, string(body)) + // hertz server:http://localhost:8080/ping ctx.String(consts.StatusOK, "pong") + c, err := client.NewClient() + if err != nil { + return + } + status, body, err := c.Get(context.Background(), nil, "http://localhost:8080/ping") + fmt.Printf("status=%v body=%v err=%v\n", status, string(body), err) + // status == 200 res.Body() == []byte("pong") err == } - ``` -### WithKeepAlive +### Post + +Post 函数使用给定的 POST 参数向指定的 URL 发送 POST 请求。如果 dst 太小,则将被响应体替换并返回,否则将分配一个新的切片。 -`WithKeepAlive` 函数用于设置 HTTP 客户端是否使用 keep-alive 连接。keep-alive 是一种 HTTP 的持久连接技术,它可以在一次 TCP 连接中处理多个 HTTP 请求和响应,减少了建立和关闭连接的开销,提高了网络性能。 +该函数会自动跟随重定向。 + +如果 postArgs 为 nil ,则发送空的 POST 请求体。 函数签名: ```go -func WithKeepAlive(b bool) config.ClientOption +func (c *Client) Post(ctx context.Context, dst []byte, url string, postArgs *protocol.Args, requestOptions ...config.RequestOption) (statusCode int, body []byte, err error) ``` 示例代码: ```go -package main +func main() { + // hertz server:http://localhost:8080/hello ctx.String(consts.StatusOK, "hello %s", ctx.PostForm("name")) + c, err := client.NewClient() + if err != nil { + return + } -import ( - "context" - "fmt" + var postArgs protocol.Args + postArgs.Set("name", "cloudwego") // Set post args + status, body, err := c.Post(context.Background(), nil, "http://localhost:8080/hello", &postArgs) + fmt.Printf("status=%v body=%v err=%v\n", status, string(body), err) + // status == 200 res.Body() == []byte("hello cloudwego") err == +} +``` - "github.com/cloudwego/hertz/pkg/app/client" -) +## 请求超时 -func main() { - c, err := client.NewClient(client.WithKeepAlive(true)) - if err != nil { - return - } - status, body, _ := c.Get(context.Background(), nil, "https://www.example.com") - fmt.Printf("status=%v body=%v\n", status, string(body)) -} +```go +func (c *Client) DoTimeout(ctx context.Context, req *protocol.Request, resp *protocol.Response, timeout time.Duration) error +func (c *Client) DoDeadline(ctx context.Context, req *protocol.Request, resp *protocol.Response, deadline time.Time) error +func (c *Client) GetTimeout(ctx context.Context, dst []byte, url string, timeout time.Duration, requestOptions ...config.RequestOption) (statusCode int, body []byte, err error) +func (c *Client) GetDeadline(ctx context.Context, dst []byte, url string, deadline time.Time, requestOptions ...config.RequestOption) (statusCode int, body []byte, err error) ``` -### WithClientReadTimeout +### DoTimeout -`WithClientReadTimeout` 函数用于设置客户端读取完整响应(包括 body)的最大持续时间。它接受一个 `time.Duration` 类型的参数,表示最大持续时间。如果读取完整响应的时间超过了设置的最大持续时间,客户端将返回一个错误。 +DoTimeout 函数执行给定的请求并在给定的超时时间内等待响应。 -函数签名: +该函数不会跟随重定向,请使用 [Get](#get) 函数或 [DoRedirects](#doredirects) 函数或 [Post](#post) 函数来跟随重定向。 + +如果 resp 为 nil,则会忽略响应。如果在给定的超时时间内未能收到响应,则会返回 `errTimeout` 错误。 + +函数签名: ```go -func WithClientReadTimeout(t time.Duration) config.ClientOption +func (c *Client) DoTimeout(ctx context.Context, req *protocol.Request, resp *protocol.Response, timeout time.Duration) error ``` 示例代码: ```go -package main - -import ( - "context" - "fmt" - "log" - "strings" - "time" - - "github.com/cloudwego/hertz/pkg/app" - "github.com/cloudwego/hertz/pkg/app/client" - "github.com/cloudwego/hertz/pkg/app/server" - "github.com/cloudwego/hertz/pkg/protocol/consts" -) - -func performRequest() { - c1, err := client.NewClient(client.WithClientReadTimeout(time.Nanosecond)) +func main() { + // hertz server:http://localhost:8080/ping ctx.String(consts.StatusOK, "pong") + c, err := client.NewClient() if err != nil { return } - status, body, err = c1.Get(context.Background(), nil, "http://localhost:8080/hello") - if err != nil { - log.Fatal(err) // err.Error() == "timeout" - } - fmt.Printf("status=%v body=%v\n", status, string(body)) -} + req, res := &protocol.Request{}, &protocol.Response{} + req.SetMethod(consts.MethodGet) + req.SetRequestURI("http://localhost:8080/ping") -func main() { - h := server.New(server.WithHostPorts(":8080")) - h.GET("/hello", func(c context.Context, ctx *app.RequestContext) { - ctx.String(consts.StatusOK, strings.Repeat("a", 1024*1024)) - }) - go performRequest() - h.Spin() + err = c.DoTimeout(context.Background(), req, res, time.Second*3) + fmt.Printf("resp = %v,err = %+v\n", string(res.Body()), err) + // res.Body() == []byte("pong") err == + + err = c.DoTimeout(context.Background(), req, res, time.Second) + fmt.Printf("resp = %v,err = %+v\n", string(res.Body()), err) + // res.Body() == []byte("") err.Error() == "timeout" } ``` -### WithTLSConfig +### DoDeadline + +DoDeadline 执行给定的请求并等待响应,直至给定的最后期限。 -`WithTLSConfig` 函数用于设置 TLS 配置,以创建 TLS 连接。TLS(Transport Layer Security)是一种加密协议,用于保护网络通信的安全。 +该函数不会跟随重定向,请使用 [Get](#get) 函数或 [DoRedirects](#doredirects) 函数或 [Post](#post) 函数来跟随重定向。 -函数签名: +如果 resp 为 nil,则会忽略响应。如果在给定的截止日期之前未能收到响应,则会返回 `errTimeout` 错误。 + +函数签名: ```go -func WithTLSConfig(cfg *tls.Config) config.ClientOption +func (c *Client) DoDeadline(ctx context.Context, req *protocol.Request, resp *protocol.Response, deadline time.Time) error ``` 示例代码: ```go -package main - -import ( - "context" - "crypto/tls" - "fmt" - - "github.com/cloudwego/hertz/pkg/app/client" -) - func main() { - tlsConfig := &tls.Config{ - InsecureSkipVerify: true, - } - c, err := client.NewClient(client.WithTLSConfig(tlsConfig)) + // hertz server:http://localhost:8080/ping ctx.String(consts.StatusOK, "pong") + c, err := client.NewClient() if err != nil { return } - status, body, _ := c.Get(context.Background(), nil, "https://www.example.com") - fmt.Printf("status=%v body=%v\n", status, string(body)) + + req, res := &protocol.Request{}, &protocol.Response{} + req.SetMethod(consts.MethodGet) + req.SetRequestURI("http://localhost:8080/ping") + + err = c.DoDeadline(context.Background(), req, res, time.Now().Add(3*time.Second)) + fmt.Printf("resp = %v,err = %+v\n", string(res.Body()), err) + // res.Body() == []byte("pong") err == + + err = c.DoDeadline(context.Background(), req, res, time.Now().Add(1*time.Second)) + fmt.Printf("resp = %v,err = %+v\n", string(res.Body()), err) + // res.Body() == []byte("") err.Error() == "timeout" } ``` -### WithDialer +### GetTimeOut -`WithDialer` 函数用于设置 HTTP 客户端使用的特定 dialer(拨号器)。dialer 用于创建网络连接。 +GetTimeout 函数返回 URL 的状态码和响应体。如果 dst 太小,则将被响应体替换并返回,否则将分配一个新的切片。 -函数签名: +该函数会自动跟随重定向。 + +如果在给定的超时时间内无法获取 URL 的内容,则会返回 `errTimeout` 错误。 + +> 注意:GetTimeout 不会终止请求本身。该请求将在后台继续进行,并且响应将被丢弃。如果请求时间过长且连接池已满,请尝试使用具有 ReadTimeout 配置的自定义 Client 实例或像下面这样设置请求级别的读取超时时间: ```go -func WithDialer(d network.Dialer) config.ClientOption + codeGetTimeout(ctx, dst, url, timeout, config.WithReadTimeout(1 * time.Second)) ``` -示例代码: +函数签名: ```go -package main - -import ( - "context" - "fmt" +func (c *Client) GetTimeout(ctx context.Context, dst []byte, url string, timeout time.Duration, requestOptions ...config.RequestOption) (statusCode int, body []byte, err error) +``` - "github.com/cloudwego/hertz/pkg/app/client" - "github.com/cloudwego/hertz/pkg/network/netpoll" -) +示例代码: +```go func main() { - c, err := client.NewClient(client.WithDialer(netpoll.NewDialer())) + // hertz server:http://localhost:8080/ping ctx.String(consts.StatusOK, "pong") + c, err := client.NewClient() if err != nil { return } - status, body, _ := c.Get(context.Background(), nil, "https://www.example.com") - fmt.Printf("status=%v body=%v\n", status, string(body)) + status, body, err := c.GetTimeout(context.Background(), nil, "http://localhost:8080/ping", 3*time.Second) + fmt.Printf("status=%v body=%v err=%v\n", status, string(body), err) + // status == 200 res.Body() == []byte("pong") err == + + status, body, err = c.GetTimeout(context.Background(), nil, "http://localhost:8080/ping", 1*time.Second) + fmt.Printf("status=%v body=%v err=%v\n", status, string(body), err) + // status == 0 res.Body() == []byte("") err.Error() == "timeout" } ``` -### WithResponseBodyStream +### GetDeadline -`WithResponseBodyStream` 函数用于设置 HTTP 客户端是否以流式方式读取响应体。如果设置为 true,客户端将在读取响应时使用流式读取,以避免一次性将整个响应体加载到内存中。如果设置为 false,客户端将一次性将整个响应体加载到内存中。 +GetDeadline 函数返回 URL 的状态码和响应体。如果 dst 太小,则将被响应体替换并返回,否则将分配一个新的切片。 -函数签名: - -```go -func WithResponseBodyStream(b bool) config.ClientOption -``` - -示例代码: - -```go -package main - -import ( - "context" - "fmt" - - "github.com/cloudwego/hertz/pkg/app/client" -) - -func main() { - c, err := client.NewClient(client.WithResponseBodyStream(true)) - if err != nil { - return - } - status, body, _ := c.Get(context.Background(), nil, "https://www.example.com") - fmt.Printf("status=%v body=%v\n", status, string(body)) -} -``` - -### WithDisableHeaderNamesNormalizing - -`WithDisableHeaderNamesNormalizing` 函数用于设置 HTTP 客户端是否禁用标准化请求和响应头中的 header 名称。如果设置为 true,客户端将不会标准化请求和响应头中的 header 名称。否则,客户端将标准化请求和响应头中的 header 名称。 - -函数签名: - -```go -func WithDisableHeaderNamesNormalizing(disable bool) config.ClientOption -``` - -示例代码: - -```go -package main - -import ( - "context" - "fmt" - - "github.com/cloudwego/hertz/pkg/app/client" -) - -func main() { - c, err := client.NewClient(client.WithDisableHeaderNamesNormalizing(true)) - if err != nil { - return - } - status, body, _ := c.Get(context.Background(), nil, "https://www.example.com") - fmt.Printf("status=%v body=%v\n", status, string(body)) -} -``` - -### WithName - -`WithName` 函数用于设置 HTTP 客户端的名称,该名称将用于 User-Agent 头中。User-Agent 头是 HTTP 请求中的一个标头字段,它向服务器发送有关客户端应用程序、操作系统、版本和其他相关信息的字符串。 - -函数签名: - -```go -func WithName(name string) config.ClientOption -``` - -示例代码: - -```go -package main - -import ( - "context" - "fmt" - - "github.com/cloudwego/hertz/pkg/app" - "github.com/cloudwego/hertz/pkg/app/client" - "github.com/cloudwego/hertz/pkg/app/server" - "github.com/cloudwego/hertz/pkg/protocol/consts" -) - -func performRequest() { - c, err := client.NewClient(client.WithName("my-client")) - if err != nil { - return - } - status, body, _ := c.Get(context.Background(), nil, "http://localhost:8080/hello") - fmt.Printf("status=%v body=%v\n", status, string(body)) -} - -func main() { - h := server.New(server.WithHostPorts(":8080")) - h.GET("/hello", func(c context.Context, ctx *app.RequestContext) { - fmt.Printf("%s\n", ctx.Request.Header.Get("User-Agent")) // "my-client" - ctx.JSON(consts.StatusOK, "hello hertz") - }) - go performRequest() - h.Spin() -} -``` - -### WithNoDefaultUserAgentHeader - -`WithNoDefaultUserAgentHeader` 函数用于设置 HTTP 客户端是否禁用默认的 User-Agent 头。如果设置为 true,客户端将不会在请求中发送默认的 User-Agent 头。否则,客户端将发送默认的 User-Agent 头。 - -函数签名: - -```go -func WithNoDefaultUserAgentHeader(isNoDefaultUserAgentHeader bool) config.ClientOption -``` - -示例代码: - -```go -package main - -import ( - "context" - "fmt" - - "github.com/cloudwego/hertz/pkg/app" - "github.com/cloudwego/hertz/pkg/app/client" - "github.com/cloudwego/hertz/pkg/app/server" - "github.com/cloudwego/hertz/pkg/protocol/consts" -) - -func performRequest() { - c, err := client.NewClient(client.WithNoDefaultUserAgentHeader(false)) - if err != nil { - return - } - status, body, _ := c.Get(context.Background(), nil, "http://localhost:8080/hello") - fmt.Printf("status=%v body=%v\n", status, string(body)) - // User-Agent == "hertz" - - c, err = client.NewClient(client.WithNoDefaultUserAgentHeader(true)) - if err != nil { - return - } - status, body, _ = c.Get(context.Background(), nil, "http://localhost:8080/hello") - fmt.Printf("status=%v body=%v\n", status, string(body)) - // User-Agent == "" -} - -func main() { - h := server.New(server.WithHostPorts(":8080")) - h.GET("/hello", func(c context.Context, ctx *app.RequestContext) { - ctx.JSON(consts.StatusOK, "hello hertz") - }) - go performRequest() - h.Spin() -} -``` - -### WithDisablePathNormalizing - -`WithDisablePathNormalizing` 函数用于设置 HTTP 客户端是否禁用标准化请求路径。如果设置为 true,客户端将不会标准化请求路径。否则,客户端将标准化请求路径。 - -函数签名: - -```go -func WithDisablePathNormalizing(isDisablePathNormalizing bool) config.ClientOption -``` - -示例代码: - -```go -package main - -import ( - "context" - "fmt" - - "github.com/cloudwego/hertz/pkg/app" - "github.com/cloudwego/hertz/pkg/app/client" - "github.com/cloudwego/hertz/pkg/app/server" - "github.com/cloudwego/hertz/pkg/protocol/consts" -) - -func performRequest() { - c, err := client.NewClient(client.WithDisablePathNormalizing(false)) - if err != nil { - return - } - status, body, _ := c.Get(context.Background(), nil, "http://localhost:8080/hello/../hello") - fmt.Printf("status=%v body=%v\n", status, string(body)) - // request url == "http://localhost:8080/hello" - - c, err = client.NewClient(client.WithDisablePathNormalizing(true)) - if err != nil { - return - } - status, body, _ = c.Get(context.Background(), nil, "http://localhost:8080/hello/../hello") - fmt.Printf("status=%v body=%v\n", status, string(body)) - // request url == "http://localhost:8080/hello/../hello" -} - -func main() { - h := server.New(server.WithHostPorts(":8080")) - h.GET("/hello", func(c context.Context, ctx *app.RequestContext) { - ctx.JSON(consts.StatusOK, "hello hertz") - }) - go performRequest() - h.Spin() -} -``` - -### WithRetryConfig - -`WithRetryConfig` 函数用于设置 HTTP 客户端的重试配置。在发生网络故障或超时等问题时,客户端可以通过重试来尝试重新建立连接或重新发送请求。(更多内容请参考 [重试](/zh/docs/hertz/tutorials/basic-feature/retry)) - -函数签名: - -```go -func WithRetryConfig(opts ...retry.Option) config.ClientOption -``` - -示例代码: - -```go -package main - -import ( - "context" - "fmt" - "github.com/cloudwego/hertz/pkg/app/client" - "github.com/cloudwego/hertz/pkg/app/client/retry" -) - -func main() { - c, err := client.NewClient(client.WithRetryConfig( - retry.WithMaxAttemptTimes(3), - retry.WithInitDelay(1000), - retry.WithMaxDelay(10000), - retry.WithDelayPolicy(retry.DefaultDelayPolicy), - retry.WithMaxJitter(1000), - )) - if err != nil { - return - } - status, body, _ := c.Get(context.Background(), nil, "https://www.example.com") - fmt.Printf("status=%v body=%v\n", status, string(body)) -} -``` - -### WithWriteTimeout - -`WithWriteTimeout` 函数用于设置 HTTP 客户端的写入超时时间。在发送请求时,如果写入请求的数据超过了指定的超时时间,则客户端会中止请求并返回错误。 - -函数签名: - -```go -func WithWriteTimeout(t time.Duration) config.ClientOption -``` - -示例代码: - -```go -package main - -import ( - "context" - "fmt" - "github.com/cloudwego/hertz/pkg/protocol" - "log" - "strings" - "time" - - "github.com/cloudwego/hertz/pkg/app" - "github.com/cloudwego/hertz/pkg/app/client" - "github.com/cloudwego/hertz/pkg/app/server" - "github.com/cloudwego/hertz/pkg/protocol/consts" -) - -func performRequest() { - c, err := client.NewClient(client.WithWriteTimeout(time.Nanosecond)) - if err != nil { - return - } - - args := &protocol.Args{} - args.Set("data", strings.Repeat("a", 1024*1024)) - status, body, err := c.Post(context.Background(), nil, "http://localhost:8080/hello", args) - if err != nil { - log.Fatal(err) - } - //err.Error() == "i/o timeout" - fmt.Printf("status=%v body=%v\n", status, string(body)) -} - -func main() { - h := server.New(server.WithHostPorts(":8080")) - h.POST("/hello", func(c context.Context, ctx *app.RequestContext) { - ctx.JSON(consts.StatusOK, "hello hertz") - }) - go performRequest() - h.Spin() -} -``` - -### WithConnStateObserve - -`WithConnStateObserve` 函数用于设置 HTTP 客户端的连接状态观察函数。在客户端建立连接、关闭连接或发生其他连接状态变化时,该观察函数会被调用。 - -函数签名: - -```go -func WithConnStateObserve(hs config.HostClientStateFunc, interval ...time.Duration) config.ClientOption -``` - -示例代码: - -```go -package main - -import ( - "context" - "fmt" - "github.com/cloudwego/hertz/pkg/common/config" - "time" - - "github.com/cloudwego/hertz/pkg/app" - "github.com/cloudwego/hertz/pkg/app/client" - "github.com/cloudwego/hertz/pkg/app/server" - "github.com/cloudwego/hertz/pkg/protocol/consts" -) - -func performRequest() { - observeInterval := 10 * time.Second - stateFunc := func(state config.HostClientState) { - fmt.Printf("state=%v\n", state.ConnPoolState().Addr) - // state.ConnPoolState().Addr == "localhost:8080" - } - c, err := client.NewClient(client.WithConnStateObserve(stateFunc, observeInterval)) - if err != nil { - return - } - - status, body, _ := c.Get(context.Background(), nil, "http://localhost:8080/hello") - fmt.Printf("status=%v body=%v\n", status, string(body)) -} - -func main() { - h := server.New(server.WithHostPorts(":8080")) - h.GET("/hello", func(c context.Context, ctx *app.RequestContext) { - ctx.JSON(consts.StatusOK, "hello hertz") - }) - time.Sleep(time.Second) - go performRequest() - h.Spin() -} -``` - -### WithDialFunc - -`WithDialFunc` 函数用于设置 HTTP 客户端的拨号函数,即客户端用于建立连接的底层网络拨号器。该函数接受一个 `network.DialFunc` 类型的参数,表示客户端使用的自定义拨号函数。 - -函数签名: - -```go -func WithDialFunc(f network.DialFunc, dialers ...network.Dialer) config.ClientOption -``` - -示例代码: - -```go -package main - -import ( - "context" - "fmt" - - "github.com/cloudwego/hertz/pkg/app/client" - "github.com/cloudwego/hertz/pkg/network" - "github.com/cloudwego/hertz/pkg/network/netpoll" -) - -func main() { - var customDialFunc network.DialFunc = func(addr string) (network.Conn, error) { - return nil, nil - } - c, err := client.NewClient(client.WithDialFunc(customDialFunc, netpoll.NewDialer())) - if err != nil { - return - } - status, body, _ := c.Get(context.Background(), nil, "https://www.example.com") - fmt.Printf("status=%v body=%v\n", status, string(body)) -} -``` - -## Do - -Do 函数执行给定的 http 请求并填充给定的 http 响应。请求必须包含至少一个非零的 RequestURI,其中包含完整的 URL 或非零的 Host header + RequestURI。 - -该函数不会跟随重定向,请使用 Get 函数来跟随重定向。 - -如果 resp 为 nil,则会忽略响应。如果所有针对请求主机的 DefaultMaxConnsPerHost 连接都已忙,则会返回 `ErrNoFreeConns` 错误。在性能关键的代码中,建议通过 AcquireRequest 和 AcquireResponse 获取 req 和 resp。 - -函数签名: - -```go -func (c *Client) Do(ctx context.Context, req *protocol.Request, resp *protocol.Response) error -``` - -示例代码: - -```go -package main - -import ( - "context" - "fmt" - - "github.com/cloudwego/hertz/pkg/app" - "github.com/cloudwego/hertz/pkg/app/client" - "github.com/cloudwego/hertz/pkg/app/server" - "github.com/cloudwego/hertz/pkg/protocol" - "github.com/cloudwego/hertz/pkg/protocol/consts" -) - -func performRequest() { - c, err := client.NewClient() - if err != nil { - return - } - - req, res := &protocol.Request{}, &protocol.Response{} - req.SetMethod(consts.MethodGet) - req.SetRequestURI("http://localhost:8080/ping") - - err = c.Do(context.Background(), req, res) - fmt.Printf("resp = %v,err = %+v", string(res.Body()), err) - // resp.Body() == []byte("pong") err == -} - -func main() { - h := server.New(server.WithHostPorts(":8080")) - h.GET("/ping", func(c context.Context, ctx *app.RequestContext) { - ctx.String(consts.StatusOK, "pong") - }) - - go performRequest() - - h.Spin() -} - -``` - -## DoTimeout - -DoTimeout 函数执行给定的请求并在给定的超时时间内等待响应。 - -如果 resp 为 nil,则会忽略响应。如果在给定的超时时间内未能收到响应,则会返回 `errTimeout` 错误。 - -函数签名: - -```go -func DoTimeout(ctx context.Context, req *protocol.Request, resp *protocol.Response, timeout time.Duration) error -``` - -示例代码: - -```go -package main - -import ( - "context" - "fmt" - "time" - - "github.com/cloudwego/hertz/pkg/app" - "github.com/cloudwego/hertz/pkg/app/client" - "github.com/cloudwego/hertz/pkg/app/server" - "github.com/cloudwego/hertz/pkg/protocol" - "github.com/cloudwego/hertz/pkg/protocol/consts" -) - -func performRequest() { - c, err := client.NewClient() - if err != nil { - return - } - - req, res := &protocol.Request{}, &protocol.Response{} - req.SetMethod(consts.MethodGet) - req.SetRequestURI("http://localhost:8080/ping") - - err = c.DoTimeout(context.Background(), req, res, time.Second*3) - fmt.Printf("resp = %v,err = %+v\n", string(res.Body()), err) - // res.Body() == []byte("pong") err == - - err = c.DoTimeout(context.Background(), req, res, time.Second) - fmt.Printf("resp = %v,err = %+v\n", string(res.Body()), err) - // res.Body() == []byte("") err.Error() == "timeout" -} - -func main() { - h := server.New(server.WithHostPorts(":8080")) - h.GET("/ping", func(c context.Context, ctx *app.RequestContext) { - time.Sleep(2 * time.Second) - ctx.String(consts.StatusOK, "pong") - }) - - go performRequest() - - h.Spin() -} - -``` - -## DoDeadline - -DoDeadline 执行给定的请求并等待响应,直至给定的最后期限。 -如果 resp 为 nil,则会忽略响应。如果在给定的截止日期之前未能收到响应,则会返回 `errTimeout` 错误。 - -函数签名: - -```go -func DoDeadline(ctx context.Context, req *protocol.Request, resp *protocol.Response, deadline time.Time) error -``` - -示例代码: - -```go -package main - -import ( - "context" - "fmt" - "time" - - "github.com/cloudwego/hertz/pkg/app" - "github.com/cloudwego/hertz/pkg/app/client" - "github.com/cloudwego/hertz/pkg/app/server" - "github.com/cloudwego/hertz/pkg/protocol" - "github.com/cloudwego/hertz/pkg/protocol/consts" -) - -func performRequest() { - c, err := client.NewClient() - if err != nil { - return - } - - req, res := &protocol.Request{}, &protocol.Response{} - req.SetMethod(consts.MethodGet) - req.SetRequestURI("http://localhost:8080/ping") - - err = c.DoDeadline(context.Background(), req, res, time.Now().Add(3*time.Second)) - fmt.Printf("resp = %v,err = %+v\n", string(res.Body()), err) - // res.Body() == []byte("pong") err == +该函数会自动跟随重定向。 - err = c.DoDeadline(context.Background(), req, res, time.Now().Add(1*time.Second)) - fmt.Printf("resp = %v,err = %+v\n", string(res.Body()), err) - // res.Body() == []byte("") err.Error() == "timeout" - -} - -func main() { - h := server.New(server.WithHostPorts(":8080")) - h.GET("/ping", func(c context.Context, ctx *app.RequestContext) { - time.Sleep(2 * time.Second) - ctx.String(consts.StatusOK, "pong") - }) - - go performRequest() - - h.Spin() -} -``` - -## DoRedirects - -DoRedirects 函数执行给定的 http 请求并填充给定的 http 响应,遵循最多 maxRedirectsCount 次重定向。当重定向次数超过maxRedirectsCount 时,将返回 `ErrTooManyRedirects` 错误。 - -函数签名: - -```go -func DoRedirects(ctx context.Context, req *protocol.Request, resp *protocol.Response, maxRedirectsCount int) error -``` +如果在给定的截止时间之前无法获取 URL 的内容,则会返回 `errTimeout` 错误。 -示例代码: +> 注意:GetDeadline 不会终止请求本身。该请求将在后台继续进行,并且响应将被丢弃。如果请求时间过长且连接池已满,请尝试使用具有ReadTimeout 配置的自定义 Client 实例或像下面这样设置请求级别的读取超时时间: ```go -package main - -import ( - "context" - "fmt" - - "github.com/cloudwego/hertz/pkg/app" - "github.com/cloudwego/hertz/pkg/app/client" - "github.com/cloudwego/hertz/pkg/app/server" - "github.com/cloudwego/hertz/pkg/protocol" - "github.com/cloudwego/hertz/pkg/protocol/consts" -) - -func performRequest() { - c, err := client.NewClient() - if err != nil { - return - } - - req, res := &protocol.Request{}, &protocol.Response{} - req.SetMethod(consts.MethodGet) - req.SetRequestURI("http://localhost:8080/redirect") - - err = c.DoRedirects(context.Background(), req, res, 1) - fmt.Printf("resp = %v,err = %+v\n", string(res.Body()), err) - // res.Body() == []byte("") err.Error() == "too many redirects detected when doing the request" - - err = c.DoRedirects(context.Background(), req, res, 2) - fmt.Printf("resp = %v,err = %+v\n", string(res.Body()), err) - // res.Body() == []byte("pong") err == -} - -func main() { - h := server.New(server.WithHostPorts(":8080")) - h.GET("/redirect", func(c context.Context, ctx *app.RequestContext) { - ctx.Redirect(consts.StatusMovedPermanently, []byte("/redirect2")) - }) - h.GET("/redirect2", func(c context.Context, ctx *app.RequestContext) { - ctx.Redirect(consts.StatusMovedPermanently, []byte("/redirect3")) - }) - h.GET("/redirect3", func(c context.Context, ctx *app.RequestContext) { - ctx.String(consts.StatusOK, "pong") - }) - - go performRequest() - - h.Spin() -} +GetDeadline(ctx, dst, url, deadline, config.WithReadTimeout(1 * time.Second)) ``` -## Get - -Get 函数返回 URL 的状态码和响应体。如果 dst 太小,则将被响应体替换并返回,否则将分配一个新的切片。 - -该函数会自动跟随重定向。如果需要手动处理重定向,请使用 Do 函数。 - 函数签名: ```go -func Get(ctx context.Context, dst []byte, url string, requestOptions ...config.RequestOption) (statusCode int, body []byte, err error) +func (c *Client) GetDeadline(ctx context.Context, dst []byte, url string, deadline time.Time, requestOptions ...config.RequestOption) (statusCode int, body []byte, err error) ``` 示例代码: ```go -package main - -import ( - "context" - "fmt" - "time" - - "github.com/cloudwego/hertz/pkg/app" - "github.com/cloudwego/hertz/pkg/app/client" - "github.com/cloudwego/hertz/pkg/app/server" - "github.com/cloudwego/hertz/pkg/protocol/consts" -) - -func performRequest() { - c, err := client.NewClient() - if err != nil { - return - } - status, body, err := c.Get(context.Background(), nil, "http://localhost:8080/ping") - fmt.Printf("status=%v body=%v err=%v\n", status, string(body), err) - // status == 200 res.Body() == []byte("pong") err == -} - func main() { - h := server.New(server.WithHostPorts(":8080")) - h.GET("/ping", func(c context.Context, ctx *app.RequestContext) { - ctx.String(consts.StatusOK, "pong") - }) - go performRequest() - - h.Spin() -} -``` - -## GetTimeOut - -GetTimeout 函数返回 URL 的状态码和响应体。如果 dst 太小,则将被响应体替换并返回,否则将分配一个新的切片。该函数会自动跟随重定向。如果需要手动处理重定向,请使用 Do 函数。 - -如果在给定的超时时间内无法获取 URL 的内容,则会返回 `errTimeout` 错误。 - -警告:GetTimeout 不会终止请求本身。该请求将在后台继续进行,并且响应将被丢弃。如果请求时间过长且连接池已满,请尝试使用具有 ReadTimeout 配置的自定义 Client 实例或像下面这样设置请求级别的读取超时时间: - -```go - codeGetTimeout(ctx, dst, url, timeout, config.WithReadTimeout(1 * time.Second)) -``` - -函数签名: - -```go -func GetTimeout(ctx context.Context, dst []byte, url string, timeout time.Duration, requestOptions ...config.RequestOption) (statusCode int, body []byte, err error) -``` - -示例代码: - -```go -package main - -import ( - "context" - "fmt" - "time" - - "github.com/cloudwego/hertz/pkg/app" - "github.com/cloudwego/hertz/pkg/app/client" - "github.com/cloudwego/hertz/pkg/app/server" - "github.com/cloudwego/hertz/pkg/protocol/consts" -) - -func performRequest() { + // hertz server:http://localhost:8080/ping ctx.String(consts.StatusOK, "pong") c, err := client.NewClient() if err != nil { return } - status, body, err := c.GetTimeout(context.Background(), nil, "http://localhost:8080/ping", 3*time.Second) + status, body, err := c.GetDeadline(context.Background(), nil, "http://localhost:8080/ping", time.Now().Add(3*time.Second)) fmt.Printf("status=%v body=%v err=%v\n", status, string(body), err) // status == 200 res.Body() == []byte("pong") err == - status, body, err = c.GetTimeout(context.Background(), nil, "http://localhost:8080/ping", 1*time.Second) + status, body, err = c.GetDeadline(context.Background(), nil, "http://localhost:8080/ping", time.Now().Add(time.Second)) fmt.Printf("status=%v body=%v err=%v\n", status, string(body), err) // status == 0 res.Body() == []byte("") err.Error() == "timeout" } - -func main() { - h := server.New(server.WithHostPorts(":8080")) - h.GET("/ping", func(c context.Context, ctx *app.RequestContext) { - time.Sleep(2 * time.Second) - ctx.String(consts.StatusOK, "pong") - }) - go performRequest() - - h.Spin() -} - ``` -## GetDeadline +## 请求重试 -GetDeadline 函数返回 URL 的状态码和响应体。如果 dst 太小,则将被响应体替换并返回,否则将分配一个新的切片。该函数会自动跟随重定向。如果需要手动处理重定向,请使用 Do 函数。 - -如果在给定的截止时间之前无法获取 URL 的内容,则会返回 `errTimeout` 错误。 +```go +func (c *Client) SetRetryIfFunc(retryIf client.RetryIfFunc) +``` -警告:GetDeadline 不会终止请求本身。该请求将在后台继续进行,并且响应将被丢弃。如果请求时间过长且连接池已满,请尝试使用具有ReadTimeout 配置的自定义 Client 实例或像下面这样设置请求级别的读取超时时间: +### SetRetryIfFunc -```gi -GetDeadline(ctx, dst, url, deadline, config.WithReadTimeout(1 * time.Second)) -``` +`SetRetryIfFunc` 方法用于自定义配置重试发生的条件。(更多内容请参考 [retry-条件配置](/zh/docs/hertz/tutorials/basic-feature/retry/#retry-条件配置)) 函数签名: ```go -func GetDeadline(ctx context.Context, dst []byte, url string, deadline time.Time, requestOptions ...config.RequestOption) (statusCode int, body []byte, err error) +func (c *Client) SetRetryIfFunc(retryIf client.RetryIfFunc) ``` 示例代码: ```go -package main - -import ( - "context" - "fmt" - "time" - - "github.com/cloudwego/hertz/pkg/app" - "github.com/cloudwego/hertz/pkg/app/client" - "github.com/cloudwego/hertz/pkg/app/server" - "github.com/cloudwego/hertz/pkg/protocol/consts" -) - -func performRequest() { +func main() { c, err := client.NewClient() if err != nil { return } - status, body, err := c.GetDeadline(context.Background(), nil, "http://localhost:8080/ping", time.Now().Add(3*time.Second)) - fmt.Printf("status=%v body=%v err=%v\n", status, string(body), err) - // status == 200 res.Body() == []byte("pong") err == - - status, body, err = c.GetDeadline(context.Background(), nil, "http://localhost:8080/ping", time.Now().Add(time.Second)) - fmt.Printf("status=%v body=%v err=%v\n", status, string(body), err) - // status == 0 res.Body() == []byte("") err.Error() == "timeout" -} - -func main() { - h := server.New(server.WithHostPorts(":8080")) - h.GET("/ping", func(c context.Context, ctx *app.RequestContext) { - time.Sleep(2 * time.Second) - ctx.String(consts.StatusOK, "pong") - }) - go performRequest() - - h.Spin() -} -``` - -## Post - -Post 函数使用给定的 POST 参数向指定的 URL 发送 POST 请求。如果 dst 太小,则将被响应体替换并返回,否则将分配一个新的切片。该函数会自动跟随重定向。如果需要手动处理重定向,请使用 Do 函数。 - -如果 postArgs 为 nil ,则发送空的 POST 请求体。 + var customRetryIfFunc = func(req *protocol.Request, resp *protocol.Response, err error) bool { + return true + } + c.SetRetryIfFunc(customRetryIfFunc) + status2, body2, _ := c.Get(context.Background(), nil, "http://www.example.com") + fmt.Printf("status=%v body=%v\n", status2, string(body2)) +} +``` -函数签名: +## 添加请求内容 -```go -func Post(ctx context.Context, dst []byte, url string, postArgs *protocol.Args, requestOptions ...config.RequestOption) (statusCode int, body []byte, err error) -``` +Hertz 客户端可以在 HTTP 请求中添加 `query` 参数、`www-url-encoded`、`multipart/form-data`、`json` 等多种形式的请求内容。 示例代码: ```go -package main - -import ( - "context" - "fmt" +func main() { + client, err := client.NewClient() + if err != nil { + return + } + req := &protocol.Request{} + res := &protocol.Response{} - "github.com/cloudwego/hertz/pkg/app" - "github.com/cloudwego/hertz/pkg/app/client" - "github.com/cloudwego/hertz/pkg/app/server" - "github.com/cloudwego/hertz/pkg/protocol" - "github.com/cloudwego/hertz/pkg/protocol/consts" -) + // Use SetQueryString to set query parameters + req.Reset() + req.Header.SetMethod(consts.MethodPost) + req.SetRequestURI("http://127.0.0.1:8080/v1/bind") + req.SetQueryString("query=query&q=q1&q=q2&vd=1") + err = client.Do(context.Background(), req, res) + if err != nil { + return + } -func performRequest() { - c, err := client.NewClient() + // Send "www-url-encoded" request + req.Reset() + req.Header.SetMethod(consts.MethodPost) + req.SetRequestURI("http://127.0.0.1:8080/v1/bind?query=query&q=q1&q=q2&vd=1") + req.SetFormData(map[string]string{ + "form": "test form", + }) + err = client.Do(context.Background(), req, res) if err != nil { return } - - var postArgs protocol.Args - postArgs.Set("name", "cloudwego") // Set post args - status, body, err := c.Post(context.Background(), nil, "http://localhost:8080/hello", &postArgs) - fmt.Printf("status=%v body=%v err=%v\n", status, string(body), err) - // status == 200 res.Body() == []byte("hello cloudwego") err == -} -func main() { - h := server.New(server.WithHostPorts(":8080")) - h.POST("/hello", func(c context.Context, ctx *app.RequestContext) { - ctx.String(consts.StatusOK, "hello %s", ctx.PostForm("name")) + // Send "multipart/form-data" request + req.Reset() + req.Header.SetMethod(consts.MethodPost) + req.SetRequestURI("http://127.0.0.1:8080/v1/bind?query=query&q=q1&q=q2&vd=1") + req.SetMultipartFormData(map[string]string{ + "form": "test form", }) - go performRequest() + err = client.Do(context.Background(), req, res) + if err != nil { + return + } - h.Spin() + // Send "Json" request + req.Reset() + req.Header.SetMethod(consts.MethodPost) + req.Header.SetContentTypeBytes([]byte("application/json")) + req.SetRequestURI("http://127.0.0.1:8080/v1/bind?query=query&q=q1&q=q2&vd=1") + data := struct { + Json string `json:"json"` + }{ + "test json", + } + jsonByte, _ := json.Marshal(data) + req.SetBody(jsonByte) + err = client.Do(context.Background(), req, res) + if err != nil { + return + } } ``` -## SetProxy - -SetProxy 用来设置客户端代理。 +## 上传文件 -注意,同一个客户端不能设置多个代理,如果需要使用另一个代理,请创建另一个客户端并为其设置代理。 +Hertz 客户端支持向服务器上传文件。 示例代码: ```go -func (c *Client) SetProxy(p protocol.Proxy) -``` - -函数签名: +func main() { + client, err := client.NewClient() + if err != nil { + return + } + req := &protocol.Request{} + res := &protocol.Response{} + req.SetMethod(consts.MethodPost) + req.SetRequestURI("http://127.0.0.1:8080/singleFile") + req.SetFile("file", "your file path") -```go -package main + err = client.Do(context.Background(), req, res) + if err != nil { + return + } + fmt.Println(err, string(res.Body())) +} +``` -import ( - "context" - "fmt" - "log" +## 流式读响应内容 - "github.com/cloudwego/hertz/pkg/app" - "github.com/cloudwego/hertz/pkg/app/client" - "github.com/cloudwego/hertz/pkg/app/server" - "github.com/cloudwego/hertz/pkg/network/standard" - "github.com/cloudwego/hertz/pkg/protocol" - "github.com/cloudwego/hertz/pkg/protocol/consts" -) +Hertz 客户端支持流式读取 HTTP 响应内容。(更多内容请参考 [Client](/zh/docs/hertz/tutorials/basic-feature/stream/#client)) -func performRequest() { +示例代码: - client, err := client.NewClient(client.WithDialer(standard.NewDialer())) +```go +func main() { + c, _ := client.NewClient(client.WithResponseBodyStream(true)) + req := &protocol.Request{} + resp := &protocol.Response{} + req.SetMethod(consts.MethodGet) + req.SetRequestURI("http://127.0.0.1:8080/streamWrite") + err := c.Do(context.Background(), req, resp) if err != nil { - log.Print(err) return } - client.SetProxy(protocol.ProxyURI(protocol.ParseURI("http://localhost:8080"))) - status, body, err := client.Get(context.Background(), nil, "http://localhost:8080/ping") - - fmt.Printf("status=%v body=%v err=%v\n", status, string(body), err) - // status == 200 res.Body() == []byte("pong") err == + bodyStream := resp.BodyStream() + p := make([]byte, resp.Header.ContentLength()/2) + _, err = bodyStream.Read(p) + if err != nil { + fmt.Println(err.Error()) + } + left, _ := ioutil.ReadAll(bodyStream) + fmt.Println(string(p), string(left)) } +``` -func main() { +## 服务发现 - h1 := server.New(server.WithHostPorts(":8080")) +Hertz 客户端支持通过服务发现寻找目标服务器。 - h1.GET("/ping", func(c context.Context, ctx *app.RequestContext) { - cli, err := client.NewClient() - if err != nil { - log.Printf("client.NewClient err: %v", err) - } +Hertz 支持自定义服务发现模块,更多内容可参考 [服务发现拓展](/zh/docs/hertz/tutorials/framework-exten/service_discovery/#服务发现扩展)。 - req, res := &protocol.Request{}, &protocol.Response{} - req.SetMethod(consts.MethodGet) - req.SetRequestURI("http://localhost:8081/ping") +Hertz 目前已接入的服务发现中心相关内容可参考 [服务注册与发现](/zh/docs/hertz/tutorials/service-governance/service_discovery/)。 - cli.Do(context.Background(), req, res) - ctx.String(res.StatusCode(), res.BodyBuffer().String()) +## 负载均衡 - }) - go h1.Spin() +Hertz 客户端默认提供了 `WeightedRandom` 负载均衡实现,同时也支持自定义负载均衡实现,更多内容可参考 [负载均衡拓展](/zh/docs/hertz/tutorials/framework-exten/service_discovery/#负载均衡扩展)。 - h2 := server.New(server.WithHostPorts(":8081")) - h2.GET("/ping", func(c context.Context, ctx *app.RequestContext) { - ctx.String(consts.StatusOK, "pong") - }) - go h2.Spin() +## TLS - performRequest() +Hertz 客户端默认使用的网络库 netpoll 不支持 TLS,如果要配置 TLS 访问 https 地址,应该使用标准库。 -} -``` +TLS 相关的配置信息可参考 [tls-config](https://pkg.go.dev/crypto/tls#Config)。 -## SetRetryIfFunc +示例代码: -`SetRetryIfFunc` 方法用于自定义配置重试发生的条件。(更多内容请参考 [retry-条件配置](/zh/docs/hertz/tutorials/basic-feature/retry/#retry-条件配置)) +```go +func main() { + clientCfg := &tls.Config{ + InsecureSkipVerify: true, + } + c, err := client.NewClient( + client.WithTLSConfig(clientCfg), + client.WithDialer(standard.NewDialer()), + ) + if err != nil { + return + } + + req, res := &protocol.Request{}, &protocol.Response{} + req.SetMethod(consts.MethodGet) + req.SetRequestURI("https://www.example.com") -函数签名: + err = c.Do(context.Background(), req, res) + fmt.Printf("resp = %v,err = %+v", string(res.Body()), err) +} +``` + +## 正向代理 ```go -func (c *Client) SetRetryIfFunc(retryIf client.RetryIfFunc) +func (c *Client) SetProxy(p protocol.Proxy) ``` +### SetProxy + +SetProxy 用来设置客户端代理。(更多内容请参考 [正向代理](/zh/docs/hertz/tutorials/basic-feature/proxy/#正向代理)) + +> 注意:同一个客户端不能设置多个代理,如果需要使用另一个代理,请创建另一个客户端并为其设置代理。 + 示例代码: ```go -package main +func (c *Client) SetProxy(p protocol.Proxy) +``` -import ( - "context" - "fmt" - "github.com/cloudwego/hertz/pkg/app/client" - "github.com/cloudwego/hertz/pkg/protocol" -) +函数签名: +```go func main() { - c, err := client.NewClient() + // Proxy address + proxyURL := "http://<__user_name__>:<__password__>@<__proxy_addr__>:<__proxy_port__>" + + parsedProxyURL := protocol.ParseURI(proxyURL) + client, err := client.NewClient(client.WithDialer(standard.NewDialer())) if err != nil { return } - status, body, _ := c.Get(context.Background(), nil, "https://www.example.com") - fmt.Printf("status=%v body=%v\n", status, string(body)) - - var customRetryIfFunc = func(req *protocol.Request, resp *protocol.Response, err error) bool { - return true - } + client.SetProxy(protocol.ProxyURI(parsedProxyURL)) + upstreamURL := "http://google.com" + _, body, _ := client.Get(context.Background(), nil, upstreamURL) + fmt.Println(string(body)) +} +``` - c.SetRetryIfFunc(customRetryIfFunc) +## 设置客户端工厂对象 - status2, body2, _ := c.Get(context.Background(), nil, "https://www.example.com") - fmt.Printf("status=%v body=%v\n", status2, string(body2)) -} +```go +func (c *Client) SetClientFactory(cf suite.ClientFactory) ``` -## SetClientFactory +### SetClientFactory `SetClientFactory` 方法用于设置客户端工厂对象,该工厂对象用于创建 HTTP 客户端对象。 @@ -1401,18 +696,7 @@ func (c *Client) SetClientFactory(cf suite.ClientFactory) 示例代码: ```go -package main - -import ( - "context" - "fmt" - - "github.com/cloudwego/hertz/pkg/app/client" - "github.com/cloudwego/hertz/pkg/protocol/http1/factory" -) - func main() { - c, err := client.NewClient() if err != nil { return @@ -1420,12 +704,18 @@ func main() { c.SetClientFactory(factory.NewClientFactory(nil)) - status, body, _ := c.Get(context.Background(), nil, "https://www.example.com") + status, body, _ := c.Get(context.Background(), nil, "http://www.example.com") fmt.Printf("status=%v body=%v\n", status, string(body)) } ``` -## CloseIdleConnections +## 关闭空闲连接 + +```go +func (c *Client) CloseIdleConnections() +``` + +### CloseIdleConnections `CloseIdleConnections` 方法用于关闭任何处于空闲状态的 `keep-alive` 连接。这些连接可能是之前的请求所建立的,但现在已经空闲了一段时间。该方法不会中断任何当前正在使用的连接。 @@ -1438,21 +728,12 @@ func (c *Client) CloseIdleConnections() 示例代码: ```go -package main - -import ( - "context" - "fmt" - "github.com/cloudwego/hertz/pkg/app/client" -) - func main() { - c, err := client.NewClient() if err != nil { return } - status, body, _ := c.Get(context.Background(), nil, "https://www.example.com") + status, body, _ := c.Get(context.Background(), nil, "http://www.example.com") fmt.Printf("status=%v body=%v\n", status, string(body)) // close idle connections @@ -1460,9 +741,15 @@ func main() { } ``` -## GetDialerName +## 获取拨号器名称 + +```go +func (c *Client) GetDialerName() (dName string, err error) +``` + +### GetDialerName -`GetDialerName` 方法用于获取客户端当前使用的拨号器的名称。如果无法获取拨号器名称,则返回 "unknown"。 +`GetDialerName` 方法用于获取客户端当前使用的拨号器的名称。如果无法获取拨号器名称,则返回 `unknown`。 函数签名: @@ -1473,27 +760,11 @@ func (c *Client) GetDialerName() (dName string, err error) 示例代码: ```go -package main - -import ( - "context" - "fmt" - - "github.com/cloudwego/hertz/pkg/app" - "github.com/cloudwego/hertz/pkg/app/client" - "github.com/cloudwego/hertz/pkg/app/server" - "github.com/cloudwego/hertz/pkg/protocol/consts" -) - -func performRequest() { +func main() { c, err := client.NewClient() if err != nil { return } - status, body, _ := c.Get(context.Background(), nil, "http://localhost:8080/ping") - fmt.Printf("status=%v body=%v\n", status, string(body)) - // status == 200 res.Body() == []byte("pong") - // get dialer name dName, err := c.GetDialerName() if err != nil { @@ -1503,118 +774,89 @@ func performRequest() { fmt.Printf("dialer name=%v\n", dName) // dName == "standard" } +``` -func main() { - h := server.New(server.WithHostPorts(":8080")) - h.GET("/ping", func(c context.Context, ctx *app.RequestContext) { - ctx.String(consts.StatusOK, "pong") - }) - go h.Spin() +## 中间件 - performRequest() -} +```go +func (c *Client) Use(mws ...Middleware) +func (c *Client) UseAsLast(mw Middleware) error +func (c *Client) TakeOutLastMiddleware() Middleware ``` -## GetOptions +### Use -`GetOptions` 函数返回 `Client` 实例的 `ClientOptions` 结构体指针。 +使用 `Use` 方法对当前 client 增加一个中间件。(更多内容请参考 [客户端中间件](/zh/docs/hertz/tutorials/basic-feature/middleware/#客户端中间件)) 函数签名: ```go -func (c *Client) GetOptions() *config.ClientOptions +func (c *Client) Use(mws ...Middleware) ``` -示例代码: +### UseAsLast -```go -package main +`UseAsLast` 函数将中间件添加到客户端中间件链的最后。 -import ( - "fmt" +如果客户端中间件链在之前已经设置了最后一个中间件,`UseAsLast` 函数将会返回 `errorLastMiddlewareExist` 错误。因此,为确保客户端中间件链的最后一个中间件为空,可以先使用 [TakeOutLastMiddleware](#takeoutlastmiddleware) 函数清空客户端中间件链的最后一个中间件。 - "github.com/cloudwego/hertz/pkg/app/client" -) +>注意:`UseAsLast` 函数将中间件设置在了 `c.lastMiddleware` 中,而使用[Use](#use) 函数设置的中间件链存放在`c.mws`中,两者相对独立,只是在执行客户端中间件链的最后才执行 `c.lastMiddleware`,因此 `UseAsLast` 函数在 [Use](#use) 函数之前或之后调用皆可。 + +函数签名: + +```go +func (c *Client) UseAsLast(mw Middleware) error +``` +示例代码: + +```go func main() { - c, err := client.NewClient() + client, err := client.NewClient() + if err != nil { + return + } + client.Use(MyMiddleware) + client.UseAsLast(LastMiddleware) + req := &protocol.Request{} + res := &protocol.Response{} + req.SetRequestURI("http://www.example.com") + err = client.Do(context.Background(), req, res) if err != nil { return } - - options := c.GetOptions() - fmt.Println(options.DialTimeout) - // options.DialTimeout == 1s } - ``` -## Middleware +### TakeOutLastMiddleware -使用 `Use` 方法对当前 client 增加一个中间件。 +`TakeOutLastMiddleware` 函数返回 [UseAsLast](#useaslast) 函数中设置的最后一个中间件并将其清空,若没有设置则返回 `nil`。 函数签名: ```go -type Middleware func(Endpoint) Endpoint +func (c *Client) TakeOutLastMiddleware() Middleware ``` 示例代码: ```go -package main - -import ( - "context" - "fmt" - "time" - - "github.com/cloudwego/hertz/pkg/app" - "github.com/cloudwego/hertz/pkg/app/client" - "github.com/cloudwego/hertz/pkg/app/server" - "github.com/cloudwego/hertz/pkg/protocol" - "github.com/cloudwego/hertz/pkg/protocol/consts" -) - -func MyMiddleware(next client.Endpoint) client.Endpoint { - return func(ctx context.Context, req *protocol.Request, resp *protocol.Response) (err error) { - // pre-handle - fmt.Println("pre-clientHandle") - if err = next(ctx, req, resp); err != nil { - fmt.Println(err) - return err - } - // post-handle - fmt.Println("post-clientHandle") - return nil - } -} - -func performRequest() { - - time.Sleep(1 * time.Second) +func main() { client, err := client.NewClient() if err != nil { return } - client.Use(MyMiddleware) - req, res := &protocol.Request{}, &protocol.Response{} - req.SetRequestURI("http://127.0.0.1:8080/ping") - + client.UseAsLast(LastMiddleware) + req := &protocol.Request{} + res := &protocol.Response{} + req.SetRequestURI("http://www.example.com") err = client.Do(context.Background(), req, res) - fmt.Printf("resp = %v,err = %+v", string(res.Body()), err) - // res.Body() == []byte("pong") err == -} - -func main() { - h := server.New(server.WithHostPorts(":8080")) - h.GET("/ping", func(c context.Context, ctx *app.RequestContext) { - ctx.String(consts.StatusOK, "pong") - }) - - go performRequest() - - h.Spin() + if err != nil { + return + } + middleware := client.TakeOutLastMiddleware() // middleware == LastMiddleware + middleware = client.TakeOutLastMiddleware() // middleware == nil } ``` +