这是我参与「第五届青训营 」笔记创作活动的第2天
Hertz是一个微服务http框架,用户通过定义一个IDL,hz命令行便可以一键生成项目脚手架。hz可以根据IDL更新脚手架。
HZ支持Thrift和Protobuf两种IDL。
安装
下载Hertz
go install github.com/cloudwego/hertz/cmd/hz@latest
验证是否下载安装成功
hz -v
快速搭建
创建一个项目文件夹,文件夹在$GOPATH路径下或者不在,执行生成脚手架命令不同
mkdir hertzdemo
在$GOPATH 路径下可直接执行
hz new
不在路径下,执行下列命令生成demo脚手架
hz new -module hertzdemo
生成的项目结构如下所示:
执行下面命令,整理项目
go mod tidy
启动main.go,控制台输出
此时,服务已开启,在浏览器输入会得到响应消息
或者在cmd输入curl进行测试。
服务启动
包含两种方式 server.Default():默认集成一个recover中间件 server.New() 如下,服务监听8080端口,并且注册了一个GET方法的路由函数。
func main() {
h := server.Default(server.WithHostPorts("127.0.0.1:8080"))
h.GET("/ping", func(c context.Context, ctx *app.RequestContext) {
ctx.JSON(200, "The Message return")
})
h.Spin()
}
这里的h是一个hertz对象,我们在后文代码中统一用h表示。 服务启动后,通过127.0.0.1:8080/ping可以获得返回的消息
“The Message Return”
网络库
默认集成了两种网络库,可根据场景选择合适的网络库,达到最佳性能
- Netpoll:LT模型,由网络库处理读写事件,适合请求size较小的场景。
- Golang原生网络库:ET模型,由框架处理读写事件。适合大请求(request size>1M)场景
server.New(server.WithTransport(standard.NewTransporter))
server.New(server.WithTransport(netpoll.NewTransporter))
路由
路由注册
提供了多种路由注册方式,也可通过Handle自定义请求方法
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")
})
h.PATCH("/patch", func(ctx context.Context, c *app.RequestContext) {
c.String(consts.StatusOK, "patch")
})
h.HEAD("/head", func(ctx context.Context, c *app.RequestContext) {
c.String(consts.StatusOK, "head")
})
h.OPTIONS("/options", func(ctx context.Context, c *app.RequestContext) {
c.String(consts.StatusOK, "options")
})
//用于注册所有 HTTP Method 方法
h.Any("/ping_any", func(ctx context.Context, c *app.RequestContext) {
c.String(consts.StatusOK, "any")
})
//自定义请求方法
h.Handle("LOAD", "/load", func(ctx context.Context, c *app.RequestContext) {
c.String(consts.StatusOK, "load")
})
路由组
hertz支持路由分组功能。可以根据业务或者功能将路由划分到不同的组中。
// 路由组1
v1 := h.Group("/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")
})
//路由组2
v2 := h.Group("/v2")
v2.PUT("/put", func(ctx context.Context, c *app.RequestContext) {
c.String(consts.StatusOK, "put")
})
也可以在路由组中使用中间件,,Group方法创建路由组时,第二个参数填写中间件,例如进行权限验证,所有请求到该路由组的链接都将先进行权限验证。
v1 := h.Group("/v1", basic_auth.BasicAuth(map[string]string{"test": "test"}))
路由类型
Hertz包含的路由及其优先级如下:
静态路由>参数路由>通配路由。
静态路由:
前面的路由和路由组示例均为静态路由
参数路由
例如/user/:name命名参数设置路由。命名参数只匹配单个路径,从路由中可以获取到参数name。
- /user/login可匹配
- /user/view/list不可匹配
- /user/不可匹配
例如以下服务启动后:
h.GET("/get/:message", func(ctx context.Context, c *app.RequestContext) {
msg := c.Param("message")
c.String(consts.StatusOK, "the parameter is :"+msg)
})
通过RequestContext.Param()
方法,我们可以直接获取路径参数。如下浏览器请求:
通配路由:
如/get/*path,可以匹配
- /get/
- /get/user
- /get/user/name
h.GET("/get/:message/*path", func(ctx context.Context, c *app.RequestContext) {
msg := c.Param("message")
path := c.Param("path")
c.String(consts.StatusOK, "the parameter is :"+msg +" and" + path)
})
浏览器请求和返回结果如下:
路由请求参数
获取路由中的请求参数使用RequestContext。Query()
方法,例如路由
h.GET("/get", func(ctx context.Context, c *app.RequestContext) {
query1 := c.Query("name")
query2 := c.Query("age")
c.String(consts.StatusOK, query1+":"+query2)
})
浏览器请求输出如下:
路由信息
当使用匿名函数或者装饰器注册路由时,如果使用RequestContext.HandlerName方法获取handler时,会获取到错误的handler名字.
h.GET("/method", func(ctx context.Context, c *app.RequestContext) {
fmt.Println(c.HandlerName())
c.String(consts.StatusOK, "message")
})
当我们请求上面这个路由时,会打印错误的handler名字。
因此,需要使用Hertz 提供的
GETEX
、POSTEX
、PUTEX
、DELETEEX
、HEADEX
、AnyEX
、HandleEX
方法并手动传入 handler名字来注册路由,使用 app.GetHandlerName
方法来获取 handler名字。例如:
h.AnyEX("/ping", func(c context.Context, ctx *app.RequestContext) {
ctx.String(consts.StatusOK, app.GetHandlerName(ctx.Handler()))
}, "ping_handler")
路由信息通过Routes方法获取
routeInfo := h.Routes()
hlog.Info(routeInfo)
全局处理404和405的方法,NoRoute和NoMethod
中间件
服务端中间件
服务端中间件是http请求到响应周期中的一个函数,负责对请求执行检查、过滤、修改等操作。
在请求到达业务逻辑层之前,比如身份认证、权限鉴别等操作
在请求的业务处理完毕,返回的时候可以执行,比如记录响应时间,异常处理等。
中间件会按照定义的顺序进行执行。
实现中间件
func MyMiddleware() app.HandlerFunc {
return func(ctx context.Context, c *app.RequestContext) {
// pre-handle
// ...
c.Next(ctx)
}
}
// 方式二,处理逻辑后
func MyMiddleware() app.HandlerFunc {
return func(ctx context.Context, c *app.RequestContext) {
c.Next(ctx) // call the next middleware(handler)
// post-handle
// ...
}
}
当需要中止调用时,可以采用
Abort()
:终止后续调用AbortWithMsg(msg string, statusCode int)
:终止后续调用,并设置 response中body,和响应状态码AbortWithStatus(statusCode int)
:终止后续调用,并设置响应状态码
server级别中间件
对所有的路由生效
h.Use(GlobalMiddleware())
路由组级别中间件
对路由组中的路由生效
group := h.Group("/group")
group.Use(GroupMiddleware())
单一路由级别中间件
只针对当前路由生效
h.GET("/path", append(PathMiddleware(),
func(ctx context.Context, c *app.RequestContext) {
c.String(http.StatusOK, "path")
})...)
常用中间件
Hertz 提供了常用的 BasicAuth验证身份和鉴权、CORS、JWT等中间件
客户端中间件
客户端中间件可以在请求发出之前或获取响应之后执行:
- 中间件可以在请求发出之前执行,比如统一为请求添加签名或其他字段。
- 中间件也可以在收到响应之后执行,比如统一修改响应结果适配业务逻辑
参考: