这是我参与「第五届青训营 」笔记创作活动的第5天
一、本堂课重点内容:
HTTP 框架——Hertz简介
通过阅读 www.cloudwego.io/zh/docs/her… 尝试运行 Hertz 的示例代码
- Hertz 框架地址: github.com/cloudwego/h…
安装Hertz
go install github.com/cloudwego/hertz/cmd/hz@latest
示例
使用Hertz实现,服务监听8080端口并注册了一个GET方法的路由函数。
package main
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() {
//h := server.Default() //Default会默认继承recover中间件
h := server.Default(server.WithHostPorts("127.0.0.1:8080")) //生成Hertz对象
h.GET("/ping", func(c context.Context, ctx *app.RequestContext) {
ctx.JSON(consts.StatusOK, utils.H{"message": "pong"}) //使用两个上下文
}) //注册GET路由
h.Spin() //开启自旋,服务未停止之前,夯在此行,不再往下执行
}
一个简单的Hertz Server代码便完成了,经过buid后便可以进行调用服务了。具体步骤参考:www.cloudwego.io/zh/docs/her…
Hertz路由
路由注册
Hertz 提供了 GET、POST、PUT、DELETE、ANY 等方法用于注册路由。
分别用于注册 HTTP Method 为 GET 的方法 、HTTP Method 为 POST 的方法等等,Hertz.Any用于注册所有 HTTP Method 方法。详见:路由注册
func RegisterRoute(h *server.Hertz){
h := server.Default(server.WithHostPorts("127.0.0.1:8080"))
h.StaticFS("/", &app.FS{Root: "./", GenerateIndexPages: true})
h.GET("/get", func(ctx context.Context, c *app.RequestContext) {
c.String(consts.StatusOK, "get")
})
h.POST("/post", func(ctx context.Context, c *app.RequestContext) {
c.String(consts.StatusOK, "post")
})
h.PUT("/put", func(ctx context.Context, c *app.RequestContext) {
c.String(consts.StatusOK, "put")
})
h.DELETE("/delete", func(ctx context.Context, c *app.RequestContext) {
c.String(consts.StatusOK, "delete")
})
}
路由组
Hertz 提供了路由组( Group )的能力,用于支持路由分组的功能,同时中间件也可以注册到路由组上。
eg:
func main(){
h := server.Default(server.WithHostPorts("127.0.0.1:8080"))
v1 := h.Group("/v1") //v1组
v1.GET("/get", func(ctx context.Context, c *app.RequestContext) {
c.String(consts.StatusOK, "get")
})
v1.POST("/post", func(ctx context.Context, c *app.RequestContext) {
c.String(consts.StatusOK, "post")
})
v2 := h.Group("/v2") //v2组
v2.PUT("/put", func(ctx context.Context, c *app.RequestContext) {
c.String(consts.StatusOK, "put")
})
v2.DELETE("/delete", func(ctx context.Context, c *app.RequestContext) {
c.String(consts.StatusOK, "delete")
})
h.Spin()
}
路由类型
Hertz 支持丰富的路由类型用于实现复杂的功能,包括静态路由、参数路由、通配路由。当同一个请求命中多条路由时,路由的优先级:静态路由 > 命名路由 > 通配路由
静态路由
上述示例中皆为静态路由
h.GET("/get", func(ctx context.Context, c *app.RequestContext) {
c.String(consts.StatusOK, "get")
})
参数路由
// This handler will match: "/hertz/version", but will not match : "/hertz/" or "/hertz"
h.GET("/hertz/:version", func(ctx context.Context, c *app.RequestContext) {
version := c.Param("version")
c.String(consts.StatusOK, "Hello %s", version)
})
通配路由
// However, this one will match "/hertz/v1/" and "/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)
})
Hertz参数绑定
参数绑定的作用:帮助我们把HTTP请求里的参数转到结构体中。
hertz 使用开源库 go-tagexpr 进行参数的绑定及验证,提供了Bind、Validate、BindAndValidate函数用于进行参数绑定和校验。 不同于其他框架,hertz参数绑定需要配合特定的go tag使用(Validate,只需要使用 "vd" tag),具体的tag说明详见:www.cloudwego.io/zh/docs/her…
注:bind后是需要回写值的,所以需要传递地址。
Hertz中间件
Hertz中间件的种类是多种多样的,简单分为两大类:
- 服务端中间件
- 客户端中间件
中间件使用场景:当有一些通用的逻辑,比如打印日志,计算接口耗时,一些元信息的计算和传递等等。
服务端中间件:
Hertz 服务端中间件是 HTTP 请求-响应周期中的一个函数,提供了一种方便的机制来检查和过滤进入应用程序的 HTTP 请求, 例如记录每个请求或者启用CORS。
func MyMiddleware() app.HandlerFunc {
return func(ctx context.Context, c *app.RequestContext) {
// pre-handle // 方式一,先处理
// ...
c.Next(ctx) // call the next middleware(handler) 类似于洋葱模型
// post-handle // 方式二,后处理
// ...
}
}
中间件调用链:
中间件可以在请求更深入地传递到业务逻辑之前或之后执行:
- 中间件可以在请求到达业务逻辑之前执行,比如执行身份认证和权限认证,当中间件只有初始化(pre-handle)相关逻辑,且没有和 real handler 在一个函数调用栈中的需求时,中间件中可以省略掉最后的
.Next,如图中间件 B。 - 中间件也可以在执行过业务逻辑之后执行,比如记录响应时间和从异常中恢复。如果在业务 handler 处理之后有其它处理逻辑( post-handle ),或对函数调用链(栈)有强需求,则必须显式调用
.Next,如图中间件 C。
注册服务端中间件
在服务端注册中间件分类三个层次,根据其注册的层次不同,中间件生效的范围也不同。
Hertz 框架已经预置了常用的 recover 中间件,使用 server.Default() 默认可以注册该中间件。
1、Server 级别中间件 会对整个server的路由生效:
h := server.Default()
h.Use(GlobalMiddleware())
2、路由组级别中间件 对当前路由组下的路径生效:
h := server.Default()
group := h.Group("/group")
group.Use(GroupMiddleware())
3、单一路由级别中间件 只对当前路径生效:
h := server.Default(server.WithHostPorts("127.0.0.1:8888"))
h.GET("/path", append(PathMiddleware(),
func(ctx context.Context, c *app.RequestContext) {
c.String(http.StatusOK, "path")
})...)
Hertz Client
Hertz提供了HTTP Client用于帮助用户发送HTTP请求,示例:github.com/cloudwego/h…
func Get() {
c, err := client.NewClient()
if err != nil {
return
}
status, body, _ := c.Get(context.Background(), nil, "http://www.example.com")
fmt.Printf("status=%v body=%v\n", status, string(body))
}
func Post() {
c, err := client.NewClient()
if err != nil {
return
}
var postArgs protocol.Args
postArgs.Set("arg", "a") // Set post args
status, body, _ := c.Post(context.Background(), nil, "http://www.example.com", &postArgs)
fmt.Printf("status=%v body=%v\n", status, string(body))
}
Hertz代码生成工具
hz 是 Hertz 框架提供的一个用于生成代码的命令行工具。目前,hz 可以基于 thrift 和 protobuf 的 IDL 生成 Hertz 项目的脚手架(项目基础代码)。
demo
同样以thrift为例,在当前目录下创建 thrift idl 文件:具体步骤
// idl/hello.thrift
namespace go hello.example
struct HelloReq {
1: string Name (api.query="name"); // 添加 api 注解为方便进行参数绑定(参数绑定下沉到IDL中)
}
struct HelloResp {
1: string RespBody;
}
service HelloService {
HelloResp HelloMethod(1: HelloReq request) (api.get="/hello"); //get路由
}
生成代码结构
hz 生成的代码结构都类似,以基于 thrift IDL生成的代码结构为例:文件,用户在该文件里实现 IDL service 定义的方法,update 时会查找 当前文件
.
├── biz // business 层,存放业务逻辑相关流程
│ ├── handler // 存放 handler 文件
│ │ ├── hello // hello/example 对应 thrift idl 中定义的 namespace;而对于 protobuf idl,则是对应 go_package 的最后一级
│ │ │ └── example
│ │ │ ├── hello_service.go // handler 文件,用户在该文件里实现 IDL service 定义的方法,update 时会查找 当前文件已有的 handler 在尾部追加新的 handler
│ │ │ └── new_service.go // 同上,idl 中定义的每一个 service 对应一个文件
│ │ └── ping.go // 默认携带的 ping handler,用于生成代码快速调试,无其他特殊含义
│ ├── model // IDL 内容相关的生成代码
│ │ └── hello // hello/example 对应 thrift idl 中定义的 namespace;而对于 protobuf idl,则是对应 go_package
│ │ └── example
│ │ └── hello.go // thriftgo 的产物,包含 hello.thrift 定义的内容的 go 代码,update 时会重新生成
│ └── router // idl 中定义的路由相关生成代码
│ ├── hello // hello/example 对应 thrift idl 中定义的namespace;而对于 protobuf idl,则是对应 go_package 的最后一级
│ │ └── example
│ │ ├── hello.go // hz 为 hello.thrift 中定义的路由生成的路由注册代码;每次 update 相关 idl 会重新生成该文件
│ │ └── middleware.go // 默认中间件函数,hz 为每一个生成的路由组都默认加了一个中间件;update 时会查找当前文件已有的 middleware 在尾部追加新的 middleware
│ └── register.go // 调用注册每一个 idl 文件中的路由定义;当有新的 idl 加入,在更新的时候会自动插入其路由注册的调用;勿动
├── go.mod // go.mod 文件,如不在命令行指定,则默认使用相对于GOPATH的相对路径作为 module 名
├── idl // 用户定义的idl,位置可任意
│ └── hello.thrift
├── main.go // 程序入口
├── router.go // 用户自定义除 idl 外的路由方法
└── router_gen.go // hz 生成的路由注册代码,用于调用用户自定义的路由以及 hz 生成的路由
Hertz性能
Hertz性能远高于其他框架,体现在以下三点:
1、网络库Netpoll
Hertz 默认集成了 Netpoll 和 Golang 原生网络库 两个网络库,用户可以根据自己的场景选择合适的网络库以达到最佳性能。默认Netpoll。
2、Json编解码Sonic
默认使用高性能Sonic对Json进行编解码
3、使用sync.Pool复用对象,协议层数据解析优化