这是我参与「第五届青训营 」伴学笔记创作活动的第 10 天,本文介绍了三个 Go 开发框架 GORM,Kitex,Hertz之一 Hertz 的基本使用方法,关于Hertz的介绍在第一篇.
第一篇文章地址:juejin.cn/post/719148…
上文地址(第二篇):juejin.cn/post/719155…
一.准备
安装命令行工具hz
- 确保
GOPATH环境变量已经被正确地定义并且将$GOPATH/bin添加到PATH环境变量之中. - 安装 hz:
go install github.com/cloudwego/hertz/cmd/hz@latest
详细可以查询Hertz
二.Hertz的使用
1.Hertz基本使用
import (
"context"
"github.com/cloudwego/hertz/pkg/app"
"github.com/cloudwego/hertz/pkg/app/server"
"github.com/cloudwego/hertz/pkg/common/utils"
"github.com/cloudwego/hertz/pkg/protocol/consts"
)
func main() {
server.New()
h := server.Default(server.WithHostPorts("127.0.0.1:8080")) //WithHostPorts 设置监听地址。
// server.Default() 使用默认中间件创建一个 hertz 实例,server.New()没有中间件
h.GET("/ping", func(c context.Context, ctx *app.RequestContext) { //注册一个GET路由
ctx.JSON(consts.StatusOK, utils.H{"ping": "pong"})
})
h.Spin() //Spin 运行服务器,直到捕捉到 os.Signal 或 h.Run() 返回的错误。
//程序运行起来后,会阻塞到这一行,后面代码不会运行
}
2.Hertz路由
Hertz提供了GET,POST,PUT,ELETE,ANY等方法用于注册路由.
func RegisterRoute(h *server.Hertz) {
h.GET("/get", func(c context.Context, ctx *app.RequestContext) { //注册一个GET路由
ctx.JSON(consts.StatusOK, utils.H{"ping": "pong"})
})
h.POST("/post", func(c context.Context, ctx *app.RequestContext) {
ctx.JSON(consts.StatusOK, utils.H{"ping": "pong"})
})
h.PUT("/put", func(c context.Context, ctx *app.RequestContext) {
ctx.JSON(consts.StatusOK, utils.H{"ping": "pong"})
})
h.DELETE("/delete", func(c context.Context, ctx *app.RequestContext) {
ctx.JSON(consts.StatusOK, utils.H{"ping": "pong"})
})
h.PATCH("/patch", func(c context.Context, ctx *app.RequestContext) {
ctx.JSON(consts.StatusOK, utils.H{"ping": "pong"})
})
h.GET("/get", func(c context.Context, ctx *app.RequestContext) {
ctx.JSON(consts.StatusOK, utils.H{"ping": "pong"})
})
h.HEAD("/head", func(c context.Context, ctx *app.RequestContext) {
ctx.JSON(consts.StatusOK, utils.H{"ping": "pong"})
})
h.OPTIONS("/options", func(c context.Context, ctx *app.RequestContext) {
ctx.JSON(consts.StatusOK, utils.H{"ping": "pong"})
})
h.Any("/any", func(ctx context.Context, c *app.RequestContext) {
c.String(consts.StatusOK, "any")
})
h.Handle("Handle","/Handle", func(ctx context.Context, c *app.RequestContext) {
c.String(consts.StatusOK, "load")
})
}
Any注册一个匹配所有 HTTP 方法的路由,GET、POST、PUT、PATCH、HEAD、OPTIONS、DELETE、CONNECT、TRACE。Handle可用于注册自定义 HTTP Method 方法,Handle 使用给定的路径和方法注册一个新的请求句柄和中间件。最后一个处理程序应该是真正的处理程序,其他的应该是可以并且应该在不同路由之间共享的中间件。
Hertz提供了路由组(Group)的能力,用于支持路由分组的功能
//Simple group : v1
v1 := h.Group("/v1")
{
// loginEndpoint is a handler func
v1.POST("/login", func(ctx context.Context, c *app.RequestContext) {
c.String(consts.StatusOK, "loginEndpoint")
})
v1.POST("/submit", func(ctx context.Context, c *app.RequestContext) {
c.String(consts.StatusOK, "submitEndpoint")
})
v1.POST("/streaming_read", func(ctx context.Context, c *app.RequestContext) {
c.String(consts.StatusOK, "readEndpoint")
})
}
// Simple group : v2
v2 := h.Group("/v2")
{
v2.POST("/login", func(ctx context.Context, c *app.RequestContext) {
c.String(consts.StatusOK, "loginEndpoint")
})
v2.POST("/submit", func(ctx context.Context, c *app.RequestContext) {
c.String(consts.StatusOK, "submitEndpoint")
})
v2.POST("/streaming_read", func(ctx context.Context, c *app.RequestContext) {
c.String(consts.StatusOK, "readEndpoint")
})
}
Hertz提供了参数路由和通配路由,路由的优先级为:静态路由 > 命名路由 > 通配路由 #### 参数路由
Hertz 支持使用 :name 这样的命名参数设置路由,并且命名参数只匹配单个路径段。
如果我们设置/user/:name路由,匹配情况如下
| 路径 | 是否匹配 |
|---|---|
| /user/gordon | 匹配 |
| /user/you | 匹配 |
| /user/gordon/profile | 不匹配 |
| /user/ | 不匹配 |
通过使用 RequestContext.Param 方法,我们可以获取路由中携带的参数。
// 此处理程序将匹配:"/hertz/version",但不会匹配:"/hertz/" 或 "/hertz"
h.GET("/hertz/:version", func(ctx context.Context, c *app.RequestContext) {
version := c.Param("version")
c.String(consts.StatusOK, "Hello %s", version)
})
通配路由
Hertz 支持使用 *path 这样的通配参数设置路由,并且通配参数会匹配所有内容。
如果我们设置/src/*path路由,匹配情况如下
| 路径 | 是否匹配 |
|---|---|
| /src/ | 匹配 |
| /src/somefile.go | 匹配 |
| /src/subdir/somefile.go | 匹配 |
通过使用 RequestContext.Param 方法,我们可以获取路由中携带的参数。
// 然而,这个将匹配"/hertz/v1/"和"/hertz/v2/send"
h.GET("/hertz/:version/*action", func(ctx context.Context, c *app.RequestContext) {
version := c.Param("version")
action := c.Param("action")
message := version + " is " + action
c.String(consts.StatusOK, message)
})
// 对于每一个匹配请求上下文都会路由定义
h.POST("/hertz/:version/*action", func(ctx context.Context, c *app.RequestContext) {
// ullPath 返回匹配的路由完整路径,c.FullPath == "/hertz/:version/*action" // true
c.String(consts.StatusOK, c.FullPath())
})
静态路由
只能是固定的参数,只能匹配固定的单个的内容.
如果我们设置/src路由,匹配情况如下
| 路径 | 是否匹配 |
|---|---|
| /src/ | 匹配 |
| /src/somefile.go | 不匹配 |
| /src/subdir/somefile.go | 不匹配 |
h.GET("/get", func(c context.Context, ctx *app.RequestContext) { //注册一个GET路由
ctx.JSON(consts.StatusOK, utils.H{"ping": "pong"})
})
3.Hertz参数绑定
- Hertz提供了Bind,Validate,BindAndValidate函数用于进行参数绑定和校验,使用了反射
- hertz 使用开源库 go-tagexpr 进行参数的绑定及验证
- 参数绑定可以完成请求参数映射到结构体与请求参数的验证
type person struct {
Age int `path:"age" json:"age"` // 从路径中获取参数
Name string `query:"name" json:"name"` // 从query中获取参数
City string `json:"city"` // 从body中获取参数
}
func main() {
h := server.Default(server.WithHostPorts("127.0.0.1:8080")) //WithHostPorts 设置监听地址。
h.POST("v:path/bind", func(ctx context.Context, c *app.RequestContext) {
var p person
//需要回写值,需要传递指针,小心不要传递指针的指针
if err := c.BindAndValidate(&p); err != nil {
panic(err)
}
c.JSON(200, utils.H{
"person": p,
})
})
h.Spin()
}
支持的 tag 及参数绑定优先级
支持的 tag
| go tag | 说明 |
|---|---|
| path | 绑定 url 上的路径参数,相当于 hertz 路由{:param}或{*param}中拿到的参数。例如:如果定义的路由为: /v:version/example,可以把 path 的参数指定为路由参数:path:"version",此时,url: http://127.0.0.1:8888/v1/example ,可以绑定path参数"1" |
| form | 绑定请求的 body 内容。content-type -> multipart/form-data 或 application/x-www-form-urlencoded,绑定 form 的 key-value |
| query | 绑定请求的 query 参数 |
| header | 绑定请求的 header 参数 |
| json | 绑定请求的 body 内容 content-type -> application/json,绑定 json 参数 |
| raw_body | 绑定请求的原始 body(bytes),绑定的字段名不指定,也能绑定参数。(注:raw_body 绑定优先级最低,当指定多个 tag 时,一旦其他 tag 成功绑定参数,则不会绑定 body 内容。) |
| vd | 参数校验,校验语法 |
参数绑定优先级
path > form > query > cookie > header > json > raw_body
4. Hertz中间件
Hertz中间件主要分为客户端中间件和服务端中间件,如下展示一个服务端中间件
func MyMiddleware() app.HandlerFunc {
return func(ctx context.Context, c *app.RequestContext) {
//pre-handle
fmt.Println("pre-handle")
c.Next(ctx) //Next 应该只在中间件内部使用。它在调用处理程序内执行链中的挂起处理程序。
//需要向下执行需要调用c.Next()
//post-handle
fmt.Println("post-handle")
}
}
func main() {
h := server.Default(server.WithHostPorts("127.0.0.1:8888"))
h.Use(MyMiddleware()) //注册为全局的
h.GET("/middleware", func(ctx context.Context, c *app.RequestContext) {
c.String(consts.StatusOK, "Hello hertz!")
})
h.Spin()
}
- 执行结果:
每次进入http://localhost:8888/middleware ,都会输出一次pre-handle和post-handle
- 如何终止中间件调用链的执行
- c.Abort
- c.AbortWithMsg
- c.AbortWithStats
5.Hertz Client
Hertz提供了HTTP Client用于帮助用户发送HTTP请求
c, err := client.NewClient()
if err != nil {
return
}
// send http get request
status, body, _ := c.Get(context.Background(), nil, "https://www.example.com")
fmt.Printf("status=%v body=%v\n", status, string(body))
// send http post request
var postArgs protocol.Args
postArgs.Set("arg", "a") // Set post args
status, body, _ = c.Post(context.Background(), nil, "https://www.example.com", &postArgs)
fmt.Printf("status=%v body=%v\n", status, string(body))
- NewClient 返回带有选项的客户端
- Get 返回 url 的状态码和正文。 dst 的内容将被 body 替换并返回,如果 dst 太小,将分配一个新的切片。该函数遵循重定向。使用 Do 手动处理重定向。
- Post 使用给定的 POST 参数向给定的 url 发送 POST 请求。 dst 的内容将被 body 替换并返回,如果 dst 太小,将分配一个新的切片。该函数遵循重定向。使用 Do 手动处理重定向。如果 postArgs 为 nil,则发送空的 POST 正文。
- dst : 做一些高性能开发时,希望有些结构体是复用的,手动传入一些字节数组,将数据写入字节数组,这样就不用频繁申请内存,申请数组.
6.Hertz 代码生成工具
Hertz提供了代码生成工具Hz,通过定义IDL文件即可生成对应的基础服务代码
namespace go hello.example
struct HelloReq{
1 : string Name (api.query="name");
}
struct HelloResp {
1 : string RespBody;
}
service HelloService {
HelloResp HelloMethod(1 : HelloReq request) (api.get="/hello");
}
- IDL文件创建完毕后,执行
hz new -idl [IDL文件]即可生成代码 生成的目录结构: - mian.go入口:
7.Hertz性能
- 网络库 Netpoll
- Json编解码Sonic
- 使用sync.Pool复用对象协议层数技术解析优化
8. Hertz生态
更多请查看github.com/cloudwego/h… .
引用 :
www.cloudwego.io/zh/docs/her…
live.juejin.cn/4354/989924…
github.com/bytedance/g…