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 | 说明 |
|---|---|
path | url参路径参数,可获取其中的通配参数或命名参数 |
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方法发送请求
- 发送GET请求
Do 函数执行给定的 http 请求并填充给定的 http 响应。可以借助
protocol.Request和protocol.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)
- 发送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.Context 和 c *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):终止后续调用,并设置状态码