介绍
Hertz 是 字节跳动(ByteDance)开源的高性能 Golang 微服务框架,用于构建 Web 服务,特别适合对性能要求极高的场景。
基于字节内部自研的网络库 netpoll,具备极低的延迟和高并发能力。
对比标准库 net/http,在 QPS、RT、内存占用等指标上表现更优秀。
灵活易用
支持中间件机制,兼容 Gin 风格。
可插拔的组件设计,便于自定义协议栈、序列化方式、拦截器等。
支持自动路由注册(借助 IDL 工具)。
全面功能
路由、请求解析、响应封装
自动生成 HTTP 服务代码(结合 Kitex 使用)
内建 metrics、pprof、tracing、限流、熔断、超时等能力
多种序列化支持:JSON、Protobuf、Thrift、MsgPack 等
微服务友好
适配 CloudWeGo 微服务生态,和 Kitex 天然集成。
支持服务注册、发现、链路追踪等。
一个示例:
package main
import (
"github.com/cloudwego/hertz/pkg/app"
"github.com/cloudwego/hertz/pkg/app/server"
)
func main() {
h := server.Default()
h.GET("/ping", func(c context.Context, ctx *app.RequestContext) {
ctx.JSON(200, map[string]string{
"message": "pong",
})
})
h.Spin()
}
创建client
创建一个标准的client
纯净的golang http client,sdk 一般用这个
package main
func main() {
c, err := client.NewClient()
// your code after this
}
使用配置创建标准的client
package main
import (
"time"
)
func main() {
c, err := client.NewClient(client.WithDialTimeout(1 * time.Second))
//your code after this
}
支持的配置项
Hertz Client 配置表
配置名称 | 类型 | 说明 |
---|---|---|
WithDialTimeout |
time.Duration |
建立连接超时时间,默认值:1000ms |
WithMaxConnsPerHost |
int |
单个 ip:port 的最大连接数,默认 512 |
WithMaxIdleConnDuration |
time.Duration |
长连接的最大空闲时间,默认 10s |
WithMaxConnDuration |
time.Duration |
长连接的最大存在时间,默认无限 |
WithMaxConnWaitTimeout |
time.Duration |
最大等待连接时长,默认不等待 |
WithKeepAlive |
bool |
设置是否使用长连接,默认为 true |
WithMaxIdemponentCallAttempts |
int |
幂等调用的最大尝试次数,默认为 5 (内部版本 < 1.7.0,外部版本 < 0.4.0) |
WithClientReadTimeout |
time.Duration |
读取 response 的最长时间,默认无限 |
WithWriteTimeout |
time.Duration |
写超时,默认无限(外部版本 > v1.7.0) |
WithDialer(standard.NewDialer()) |
network.Dialer |
底层网络库 dialer,默认 netpoll.NewDialer()。设置为 standard.NewDialer() 会使用 Golang 原生 dialer。⚠️ Netpoll 不支持 TLS,因此使用 TLS 时需切换到标准库 |
WithTLSConfig |
*tls.Config |
用于发送 HTTPS 请求,设置该配置会自动将 dialer 切换为标准库 |
WithResponseBodyStream |
bool |
是否使用流式读取 response body |
WithRetryConfig |
...retry.Option |
设置 Client 的 retry 配置 (内部版本 >= 1.7.0,外部版本 >= 0.4.0) |
WithRequestTimeout |
time.Duration |
设置请求超时。注意:Do 方法搭配此选项不会与 mesh 联动,mesh 默认超时为 4s,最终取较短者。如需联动应使用 DoTimeout / DoDeadline 。 |
WithDisablePathNormalizing |
bool |
是否禁用 URL 归一化,默认开启归一化。设置为 true 可使用原始 URL 发送请求 |
使用配置创建byted client
package main
import (
"time"
)
func main() {
c, err := byted.NewClient(byted.WithAppClientOptions(client.WithDialTimeout(1 * time.Second)))
}
支持的配置项
配置名称 | 类型 | 说明 |
---|---|---|
WithAppClientOptions |
...config.ClientOption |
设置标准的 client 配置,详情见「Client API 示例」 |
WithEnableTracing |
bool |
是否开启 trace,默认为 true |
WithEnableSpanLog |
bool |
是否为每次请求打印日志,默认为 true |
WithEnableSpanMetrics |
bool |
是否为每次请求打印 metrics,默认为 true |
WithDumpRequest |
bool |
是否在 trace span 上记录请求体和 URL 参数,默认为 false |
WithSlowRequestThreshold |
time.Duration |
如果请求时间超过该值,trace 将强制采样 |
WithLoadBalancer |
loadbalance.Loadbalancer |
使用自定义负载均衡,详情见「服务发现 & 负载均衡」 |
WithDisableDefaultDiscovery |
bool |
禁用默认服务发现中间件,详情见「服务发现 & 负载均衡」 |
WithHostPorts |
...discovery.Instance |
指定 client 访问的下游实例,详情见「服务发现 & 负载均衡」 |
WithToMethodTagDecider |
(类型未指定) | 指定 client 上报 method tag 的方法 |
发起请求
- Do
func (c *Client) Do(ctx context.Context,req *Request, resp Response) error
Do会发送请求并将响应内容写入resp中,该函数不支持重定向跳转,如要使用,请使用DoRedirects或者Get方法
package main
import (
"context"
"fmt"
)
func main() {
c, err := byted.NewClient()
if err != nil {
return
}
req := &protocol.Request{}
res := &protocol.Response{}
req.SetMethod(consts.MethodGet) //设置请求方法
req.Header.SetContentTypeBytes([]byte("application/json")) //设置请求header
req.SetRequestURI("http://example.com") //设置请求url
err = c.Do(context.Background(), req, res)
if err != nil {
return
}
fmt.Printf("%v", string(res.Body())) //读取响应body
}
- DoDeadline
func (c *Client) DoDeadline(ctx context.Context,req *Request, resp Response, deadline time.Time) error
DoDeadline会发送请求并将响应内容写入resp中,如果在给定截止日期(deadline)后没有响应则会返回github.com/cloudwego/hertz/pkg/common/errors 下的 error.ErrTimeout 错误。该方法不支持重定向跳转,如要使用,请使用DoRedirects或者Get方法
该方法会和 Mesh 的超时联动
package main
import (
"context"
"fmt"
"time"
)
func main() {
c, err := byted.NewClient()
if err != nil {
return
}
req := &protocol.Request{}
res := &protocol.Response{}
req.SetMethod(consts.MethodGet) //设置请求方法
req.SetRequestURI("http://example.com") //设置请求url
c.DoDeadline(context.Background(), req, res, time.Now().Add(1*time.Second))
fmt.Printf("%v\n", string(res.Body()))
}
- DoRedirects
func (c *Client) DoRedirects(ctx context.Context,req *Request, resp *Response, maxRedirectsCount int) error
Hertz Client DoRedirects 接口预期外 404
DoRedirects与Do的区别是它支持重定向跳转,当重定向次数超过maxRedirectsCount时会返回error
package main
import (
"context"
"fmt"
)
func main() {
c, err := byted.NewClient()
if err != nil {
return
}
req := &protocol.Request{}
res := &protocol.Response{}
req.SetMethod(consts.MethodGet) //设置请求方法
req.Header.SetContentTypeBytes([]byte("application/json")) //设置请求header
req.SetRequestURI("http://example.com") //设置请求url
err = c.DoRedirects(context.Background(), req, res, 1)
if err != nil {
return
}
fmt.Printf("%v\n", string(res.Body())) //读取响应body
}
- DoTimeout
func (c *Client) DoTimeout(ctx context.Context,req *Request, resp Response, timeout time.Duration) error
DoTimeout发送给定的请求并将响应内容写入resp中,如果在给定超时时间(timeout)后没有响应则会返回timeout错误。该方法不支持重定向跳转,如要使用,请使用DoRedirects或者Get方法
该方法会和 Mesh 的超时联动,这个超时会传递给 mesh,以代码超时为准
package main
import (
"context"
"fmt"
"time"
)
func main() {
c, err := byted.NewClient()
if err != nil {
return
}
req := &protocol.Request{}
res := &protocol.Response{}
req.SetMethod(consts.MethodGet) //设置请求方法
req.Header.SetContentTypeBytes([]byte("application/json")) //设置请求header
req.SetRequestURI("http://example.com") //设置请求url
err = c.DoTimeout(context.Background(), req, res, 1*time.Second)
if err != nil {
return
}
fmt.Printf("%v\n", string(res.Body())) //读取响应body
}
- Get
func (c *Client) Get(ctx context.Context,dst []byte, url string) (statusCode int, body []byte, err error)
Get会根据url返回相应的状态码和响应body;dst会被替换成响应body(响应的body会赋值给dst),如果dst太小会分配一个新的slice;该方法支持重定向跳转
package main
import (
"context"
"fmt"
)
func main() {
c, err := byted.NewClient()
if err != nil {
return
}
status, body, _ := c.Get(context.Background(), nil, "http://google.com")
fmt.Printf("status=%v body=%v\n", status, string(body))
}
- GetDeadline
func (c *Client) GetDeadline(ctx context.Context,dst []byte, url string, deadline time.Time) (statusCode int, body []byte, err error)
GetDeadline会根据url返回相应的状态码和响应body;如果在给定截止日期(deadline)后没有响应则会返回timeout错误,dst会被替换成响应请求body,如果dst太小会分配一个新的slice;该方法支持重定向跳转
package main
import (
"context"
"fmt"
"time"
)
func main() {
c, err := byted.NewClient()
if err != nil {
return
}
status, body, _ := c.GetDeadline(context.Background(), nil, "http://google.com", time.Now().Add(1*time.Second))
fmt.Printf("status=%v body=%v\n", status, string(body))
}
- GetTimeout
func (c *Client) GetTimeout(ctx context.Context,dst []byte, url string, timeout time.Duration) (statusCode int, body []byte, err error)
GetTimeout会根据url返回相应的状态码和响应body;如果在给定超时时间(timeout)后没有响应则会返回hertz.ErrTimeout错误,dst会被替换成响应请求body,如果dst太小会分配一个新的slice;该方法支持重定向跳转
package main
import (
"context"
"fmt"
"time"
)
func main() {
c, err := byted.NewClient()
if err != nil {
return
}
status, body, _ := c.GetTimeout(context.Background(), nil, "http://google.com", 1*time.Second)
fmt.Printf("status=%v body=%v\n", status, string(body))
}
- Post
func (c *Client) Post(ctx context.Context,dst []byte, url string, postArgs *Args) (statusCode int, body []byte, err error)
Post会根据url和post参数发送post请求,dst会被替换成响应请求body(响应的body会赋值给dst),如果dst太小会分配一个新的slice;该方法支持重定向跳转;使用的content-type是application/x-www-url-form
package main
import (
"context"
"fmt"
)
func main() {
c, err := byted.NewClient()
if err != nil {
return
}
var postArgs protocol.Args
postArgs.Set("arg", "a") //设置post请求参数
status, body, _ := c.Post(context.Background(), nil, "http://google.com", &postArgs)
fmt.Printf("status=%v body=%v\n", status, string(body))
}
发起multipart/form-data请求
package main
import (
"context"
)
func main() {
client, err := byted.NewClient()
if err != nil {
return
}
req := &protocol.Request{}
res := &protocol.Response{}
req.Header.SetMethod(consts.MethodPost) //设置请求方法
req.SetRequestURI("http://example.com") //设置请求url
req.SetMultipartFormData(map[string]string{
"msg": "test message",
})
err = client.Do(context.Background(), req, res)
return
}