Go三件套之Hertz学习笔记 | 青训营

129 阅读5分钟

1 服务端基础操作

1.1 简单服务建立

1.1.1 初始化

server 包提供了 New 和 Default 2种函数用于初始化服务。

Default 函数使用了 Recovery 中间件以保证服务在运行时不会因为 panic 导致服务崩溃。New 和 Default都可以接受额外的配置参数进行初始化。

可以使用如下代码初始化一个监听服务,其中Default如果不传入参数,默认监听的是本地的8888端口:

h := server.Default(server.WithHostPorts("127.0.0.1:8888"))
h.GET("/ping", func(ctx context.Context, c *app.RequestContext) {
		c.JSON(consts.StatusOK, utils.H{"message": "pong"})
	})
h.Spin()

1.1.2 注册路由

上述代码中注册了/ping"这个GET路由,需要传入handler进行处理,传入参数中需要2个上下文信息:ctx context.Context 和 c *app.RequestContext

1.1.3 启动服务

Spin 函数用于启动服务器,在使用 服务注册发现 的功能时,Spin 会在服务启动时将服务注册进入注册中心,并使用 signalWaiter 监测服务异常。 只有使用 Spin 来启动服务才能支持 优雅退出 的特性。

1.2 路由注册

1.2.1 路由方法

  • GET、POST、DELETE、PUT、PATCH、HEAD、OPTIONS 分别用于注册对应的HTTP方法。

  • Handle支持用户手动传入 HTTP Method 用来注册方法,也支持用于注册自定义 HTTP Method 方法。

  • StaticFile/Static/StaticFS用于注册静态文件。

以最常用的GET和POST代码进行实例说明:

	h.GET("/ping", func(ctx context.Context, c *app.RequestContext) {
		c.JSON(consts.StatusOK, utils.H{"message": "pong"})
	})

	h.POST("/pong", func(ctx context.Context, c *app.RequestContext) {
		c.String(consts.StatusOK, "ping")
	})

1.2.2 路由分组

Group函数可以实现路由分组,同路由组上也可以注册中间件。

v1 := h.Group("/v1")
	v1.GET("/get", func(ctx context.Context, c *app.RequestContext) {
		c.String(consts.StatusOK, "get")
	})

以上代码的路由经分组注册后为http://127.0.0.1:8888/v1/get

1.2.3 3种路由类型

Hertz 支持3种路由:静态路由、参数路由 (命名参数、通配参数)。其优先级顺序为:静态路由 > 命名参数路由 > 通配参数路

  • 静态路由 即前文中注册的最普通的路由。
  • 命名路由 命名路由就是将路由参数化,注意参数匹配的是单个路径,不支持夸路径匹配。命名路由的路径以开头,举例如下:
    h.GET("/v1/:id", func(ctx context.Context, c *app.RequestContext) {
      	version := c.Param("id")
    
    匹配该路由的:/v1/5, /v1/tmp 不匹配该路由的:/v1, /v1/tmp/2
  • 通配路由 通配路由就是将路由项匹配所有内容。通配路由的路径以*开头,举例如下:
    h.GET("/v2/*path", func(ctx context.Context, c *app.RequestContext) {
     	version := c.Param("id")
    
    匹配所有以/v2/开头的路由

1.3 参数绑定

可以使用结构体进行参数绑定,若字段不添加任何 tag 则会遍历所有 tag 并按照优先级绑定参数,添加 tag 则会根据对应的 tag 按照优先级去绑定参数。

tag说明
pathurl参路径参数,可获取其中的通配参数或命名参数
form绑定表单的键值对
query绑定请求的 query 参数
cookie绑定请求的 cookie 参数
header绑定请求的 header 参数
json绑定请求的 json 参数
vd参数校验
绑定优先级:path > form > query > cookie > header > json > raw_body
用法案例:
  • 先创建一个用于参数绑定的结构体
    type Args struct {
      Query      string   `query:"query"`
      QuerySlice []string `query:"q"`
      Path       string   `path:"path"`
      Header     string   `header:"header"`
      Form       string   `form:"form"`
      Json       string   `json:"json"`
      Vd         int      `query:"vd" vd:"$==0||$==1"`
      }
    
  • POST的handler中使用BindAndValidate进行校验与绑定
    h.POST("/bind", func(ctx context.Context, c *app.RequestContext) {
      	var arg Args
      	err := c.BindAndValidate(&arg)
      	if err != nil {
      		panic(err)
      	}
    
      	fmt.Printf("arg.Form = %+v\n", arg.Form)
    
      	c.String(consts.StatusOK, "bind")
      })
    

2 客户端基础操作

2.1 客户端建立

类似服务端建立,client 包提供了 NewClient 函数用于初始化服务。

c, err := client.NewClient()

2.2 客户端发送请求

2.2.1 使用GET方法和POST方法发送请求

Get 函数返回 URL 的状态码和响应体,需要传入具体的url。实例如下;

status, body, _ := c.Get(context.Background(), nil, "http://127.0.0.1:8888/ping")

POST函数需要额外传入表单等参数,可以借助protocol.Args实现:

var postArgs protocol.Args
	postArgs.Set("form", "v1")

	status, body, _ = c.Post(context.Background(), nil, "http://127.0.0.1:8888/v1/bind", &postArgs)
	fmt.Printf("status=%v body=%v\n", status, string(body))

2.2.2 使用DO方法发送请求

  1. 发送GET请求 Do 函数执行给定的 http 请求并填充给定的 http 响应。可以借助 protocol.Requestprotocol.Response构造请求和响应。Do函数执行前需要事先指定请求体的方法、url信息。实例如下:
req, res := &protocol.Request{}, &protocol.Response{}
req.SetMethod(consts.MethodGet)
req.SetRequestURI("http://127.0.0.1:8888/ping")

err = c.Do(context.Background(), req, res)
fmt.Printf("resp = %v,err = %+v\n", string(res.Body()), err)
  1. 发送POST请求 发送POST请求是,请求体的结构体需要额外使用SetFormData设置表单数据,传入参数要求为map[string]string,实例如下:
req.SetMethod(consts.MethodPost)
req.SetRequestURI("http://127.0.0.1:8888/v1/bind")
req.SetFormData(map[string]string{"form": "value1"})

err = c.Do(context.Background(), req, res)
fmt.Printf("resp = %v,err = %+v\n", string(res.Body()), err)

3 中间件注册

3.1 中间件调用规则

Hertz同时支持客户端和服务端的中间件注册,此处以服务端中间件为例进行说明。

服务端中间件可以在handler处理前后进行检查、过滤或别的操作。

中间件使用Next方法实现链式调用,如果不使用Next方法即终止调用。

3.2 编写中间件

中间件编写只需要实现一个返回值类型为app.HandlerFunc的函数即可,其返回的函数入参要求为ctx context.Contextc *app.RequestContext

下面为一个实例:

func MyMiddleware1() app.HandlerFunc {
	return func(ctx context.Context, c *app.RequestContext) {
		fmt.Println("pre-handle1")
		c.Next(ctx)
		fmt.Println("post-handle1")
	}
}

func MyMiddleware2() app.HandlerFunc {
	return func(ctx context.Context, c *app.RequestContext) {
		fmt.Println("pre-handle2")
		c.Next(ctx)
		fmt.Println("post-handle2")
	}
}

编写完中间件后,服务使用Use函数注册中间件:

h.Use(MyMiddleware1())
h.Use(MyMiddleware2())

注意,2个中间件都使用了Next方法,因此其调用顺序为:MyMiddleware1 -> MyMiddleware2 -> 业务 -> MyMiddleware2 -> MyMiddleware1

如果想快速终止中间件调用,可以使用Abort()终止后续调用,类似的函数包括:

  • AbortWithMsg(msg string, statusCode int):终止后续调用,并设置 response中body,和状态码
  • AbortWithStatus(code int):终止后续调用,并设置状态码