day10 Go框架三件套(Web/RPC/ORM) -- Hertz | 青训营笔记

187 阅读6分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 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 方法的路由,GETPOSTPUTPATCHHEADOPTIONSDELETECONNECTTRACE
  • 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-handlepost-handle
    image.png
  • 如何终止中间件调用链的执行
    • 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文件]即可生成代码 生成的目录结构: image.png
  • mian.go入口: image.png
    image.png

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…