Hertz入门|青训营笔记

144 阅读5分钟

这是我参与「第五届青训营 」笔记创作活动的第2天

Hertz是一个微服务http框架,用户通过定义一个IDL,hz命令行便可以一键生成项目脚手架。hz可以根据IDL更新脚手架。

HZ支持Thrift和Protobuf两种IDL。

安装

下载Hertz

go install github.com/cloudwego/hertz/cmd/hz@latest

验证是否下载安装成功

hz -v

image.png

快速搭建

创建一个项目文件夹,文件夹在$GOPATH路径下或者不在,执行生成脚手架命令不同

mkdir hertzdemo

在$GOPATH 路径下可直接执行

hz new

不在路径下,执行下列命令生成demo脚手架

hz new -module hertzdemo

生成的项目结构如下所示:

image.png 执行下面命令,整理项目

go mod tidy

启动main.go,控制台输出

image.png 此时,服务已开启,在浏览器输入会得到响应消息

image.png 或者在cmd输入curl进行测试。

image.png

服务启动

包含两种方式 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()方法,我们可以直接获取路径参数。如下浏览器请求:

image.png

通配路由:

如/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)
	})

浏览器请求和返回结果如下:

image.png

路由请求参数

获取路由中的请求参数使用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)
	})

浏览器请求输出如下:

image.png

路由信息

当使用匿名函数或者装饰器注册路由时,如果使用RequestContext.HandlerName方法获取handler时,会获取到错误的handler名字.

h.GET("/method", func(ctx context.Context, c *app.RequestContext) {
	fmt.Println(c.HandlerName())
	c.String(consts.StatusOK, "message")
	})

当我们请求上面这个路由时,会打印错误的handler名字。

image.png 因此,需要使用Hertz 提供的 GETEXPOSTEXPUTEXDELETEEXHEADEXAnyEXHandleEX 方法并手动传入 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

image.png

中间件

服务端中间件

服务端中间件是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等中间件

客户端中间件

客户端中间件可以在请求发出之前或获取响应之后执行:

  • 中间件可以在请求发出之前执行,比如统一为请求添加签名或其他字段。
  • 中间件也可以在收到响应之后执行,比如统一修改响应结果适配业务逻辑

参考:

github.com/cloudwego/h… www.cloudwego.io/zh/docs/her…