hertz框架初体验二之解析请求参数 | 青训营

565 阅读5分钟

1.6 获取请求参数

我们思考一下一个http请求他的请求参数(请求信息)会放到哪里?

1.请求链接的url中,例如:www.register.com/?account=12…

2.请求头中,通常为token保存的位置

image.png

3.请求体中,例如json格式的请求体,和form格式的请求体中。

那么我们就可以从这三方面下手获取请求参数。

1.6.1 url中的请求信息

func Register(ctx context.Context, c *app.RequestContext) {
    acc := c.Query("account")
    pwd := c.DefaultQuery("pwd", "nopasswd")
    c.JSON(consts.StatusOK, utils.H{
        "account": acc,
        "passwd":  pwd,
    })
}

image.png

可以通过Query这系列函数来获取/xxx?yyy=abc&zzz=abc的参数,其中Query不存在参数时会返回空字符串,DefaultQuery第二个参数可以指定参数不存在时返回对应第二个参数的字符串。

当然如果请求参数比较固定的话,我们可以通过结构体来绑定对应的参数,需要打query标签。

type Acc struct {
    Account string `query:"account"`
    Passwd  int    `query:"passwd"`
}
​
func Register1(ctx context.Context, c *app.RequestContext) {
    var acc Acc
    c.Bind(&acc)
    c.JSON(consts.StatusOK, acc)
}

image.png

Restful风格请求是我们平时最常用的请求方式。例如:

xxx.com/phone/1

其中参数完全融入到请求连接中,例如上述请求可以理解为获取商品phone的第一页商品。这种参数我们也可以获取。方法如下: 注意注册路由信息的时候,路径信息表示方法。

r.GET("/:item/:page", handler.GetItem)
func GetItem(ctx context.Context, c *app.RequestContext) {
    item := c.Param("item")
    page := c.Param("page")
    fmt.Println(item, page)
}

基本操作就是将路径上的参数用:标记,然后在Param方法来获取对应的参数。

同样也可以用结构体来表示参数。

type GoodsWithPage struct {
    Item string `path:"item"`
    Page int    `path:"page"`
}
​
func GetItem1(ctx context.Context, c *app.RequestContext) {
    var gwp GoodsWithPage
    c.Bind(&gwp)
    fmt.Println(gwp)
}

可以看出无论是那种url参数都可以用Bind函数来从url中将参数映射到结构体上。唯一的区别就是tag标签不同。

其中支持的tag如下:

image.png

其中vd和raw_body不常用,后面有自动生成代码来完成这两项绑定。

1.6.2 Header参数

在hz框架中使用c.GetHeader("key")

func GetHeader(ctx context.Context, c *app.RequestContext) {
    token := c.GetHeader("token")
    c.String(http.StatusOK, string(token))
}

设置响应头可以通过c.Response中的方法来添加响应头。

c.Response.Header.Set("acc", "ok")

image.png

1.6.3 body中的参数

body中的参数一般使用的为json格式或者postform格式。

1.PostFrom格式解析

func HandlerPostForm(ctx context.Context, c *app.RequestContext) {
    acc, ok := c.GetPostForm("account")
    fmt.Println("acc :", acc, ok)
    c.String(http.StatusOK, acc)
}

表单的获取除了GetPostForm之外,还有PostForm和DefaultPostForm两种方法。使用方式与处理url中Query系列方法相同。

也可以使用form的tag配合结构体解析数据

type MyForm struct {
    Account string `form:"account"`
}
​
func HandlerPostFrom2(ctx context.Context, c *app.RequestContext) {
    var mf MyForm
    c.Bind(&mf)
    c.JSON(http.StatusOK, mf)
}

image.png

2.json格式解析

建议结构体绑定json tag获取。

type JsonReq struct {
    Name string `json:"name"`
    Age  int    `json:"age"`
}
​
func HandlerJson(ctx context.Context, c *app.RequestContext) {
    var jsonreq JsonReq
    c.Bind(&jsonreq)
    fmt.Println(jsonreq)
}

1.6.4.PostForm文件传输

见文档:

www.cloudwego.io/zh/docs/her…

无论什么参数都可以使用结构体加标签来实现请求参数映射,调用的方法是c.Bind,因为需要写操作,传入参数是结构体指针。

1.7 中间件

上面操作我们学习了hz框架如何处理请求和响应。我们可以依照现在知识我们可以完成所有的http请求。但是大多数请求处理函数会有许多重复的动作,例如:验证token操作,我们在每一个handler中都要写如对token的验证操作,显然这是不合理的。而中间件就是为了简化这部分操作。

中间件是什么?中间件就是一个函数,对应javaweb中的过滤器和拦截器。

中间件和前面写的Handler形式相同都是一个函数。其实在hz和gin框架中,我们之前学的handler都是一个中间件。

func CheckToken(ctx context.Context, c *app.RequestContext) {
    token := c.GetHeader("token")
    if token != nil {
        fmt.Println("xxxx")
        c.Abort()
    } else {
        fmt.Println("aaa")
        c.Next(ctx)
    }
}

我们可以全局注册一个中间件

func main() {
    h := server.Default()
    //使用use方法调全局注册中间件
    h.Use(middleware.CheckToken)
    register(h)
    h.Spin()
}

也可以在函数注册路由时注册中间件,因为我们注册路由的方法是一个可变参数,以GET方法为例

func (group *RouterGroup) GET(relativePath string, handlers ...app.HandlerFunc)

而中间件和我们handler函数形式是一致的。我们可以向下面操作一般,在注册路由的时候注册中间件。

r.GET("/registerMiddleware",[]app.HandlerFunc{
        middleware.CheckToken,
        handler.Ping,
        handler.XXX,
    }...)

中间件原理,中间件和handler本质上都是app.HanderFunc函数。当一条请求发送到服务器时,会按照注册顺序的队列先进先调用(洋葱模型),如果我们想提前终止整个调用过程,并响应请求。只需要在中间件中使用c.xxx设置好返回值(也可以不设置),然后调用c.Abort方法提前终止调用。当然我们可以在中间件中提前调用下一个中间件方法,即使用c.Next(ctx),来在当前中间件中调用下个方法。

例如我们使用next方法,在本中间件中尚未结束时,调用下一个方法

func Middle1(ctx context.Context, c *app.RequestContext) {
    fmt.Println("middle 1 start")
    c.Next(ctx)
    fmt.Println("middle 1 end")
}
​
func Middle2(ctx context.Context, c *app.RequestContext) {
    fmt.Println("middle 2 start")
    c.Next(ctx)
    fmt.Println("middle 2 end")
}
​
func Middle3(ctx context.Context, c *app.RequestContext) {
    fmt.Println("middle 3 start")
    c.Next(ctx)
    fmt.Println("middle 3 end")
}
​

调用结果:

image.png

提前终止中间件:

func Middle1(ctx context.Context, c *app.RequestContext) {
    fmt.Println("middle 1 start")
    c.Next(ctx)
    fmt.Println("middle 1 end")
}
//相较于上一个在第二中间件被调用后,就停止调用后续的中间件。
func Middle2(ctx context.Context, c *app.RequestContext) {
    fmt.Println("middle 2 start")
    c.Abort()
    fmt.Println("middle 2 end")
}
​
func Middle3(ctx context.Context, c *app.RequestContext) {
    fmt.Println("middle 3 start")
    c.Next(ctx)
    fmt.Println("middle 3 end")
}
​

image.png 可以看出中间件被提前停止了,当调用到中间件2后并为调用中间件3。同时全局注册(h.Use注册的中间件)的中间件也生效了,打印出aaa。