gin的url查询参数解析
gin作为go语言最知名的网络库,在这里我简要介绍一下url的查询参数解析。主要是这里面存在一些需要注意的地方。这里,直接给出代码,和运行结果,在必要的地方进行分析。
代码1:
type structa struct {
fielda string `form:"field_a"`
}
type structb struct {
nestedstruct structa
fieldb string `form:"field_b"`
}
type structc struct {
nestedstructpointer *structa
fieldc string `form:"field_c"`
}
func getdatab(c *gin.context) {
var b structb
c.bind(&b)
c.json(200, gin.h{
"a": b.nestedstruct,
"b": b.fieldb,
})
}
func getdatac(c *gin.context) {
var b structc
c.bind(&b)
c.json(200, gin.h{
"a": b.nestedstructpointer,
"c": b.fieldc,
})
}
func main() {
r := gin.default()
r.get("/getb", getdatab)
r.get("/getc", getdatac)
r.run()
}
测试结果:
$ curl "http://localhost:8080/getb?field_a=hello&field_b=world" {"a":{"fielda":"hello"},"b":"world"} $ curl "http://localhost:8080/getc?field_a=hello&field_c=world" {"a":{"fielda":"hello"},"c":"world"}
上述结果显示gin的query解析可以嵌套赋值,只需要form tag和传入的参数一致。
再看下面的代码:
代码2:
package main
import (
"github.com/gin-gonic/gin"
)
type structa struct {
fielda string `form:"field_a"`
}
type structb struct {
structa
fieldb string `form:"field_b"`
}
type structc struct {
*structa
fieldc string `form:"field_c"`
}
func getdatab(c *gin.context) {
var b structb
c.bind(&b)
c.json(200, gin.h{
"a": b.fielda,
"b": b.fieldb,
})
}
func getdatac(c *gin.context) {
var b structc
c.bind(&b)
c.json(200, gin.h{
"a": b.fielda,
"c": b.fieldc,
})
}
func main() {
r := gin.default()
r.get("/getb", getdatab)
r.get("/getc", getdatac)
r.run()
}
输出结果:
curl "http://localhost:8080/getb?field_a=hello&field_b=world"
{"a":"hello","b":"world"}
curl "http://localhost:8080/getc?field_a=hello&field_c=world"
{"a":"hello","c":"world"}
结果显示,gin的url查询参数解析可以正常处理嵌套的结构体,只需要form tag和传入的参数一致。
再看下面代码:
代码3:
package main
import (
"github.com/gin-gonic/gin"
)
type structa struct {
fielda string `form:"field_a"`
}
type structb struct {
structa
fieldb string `form:"field_b"`
}
type structc struct {
*structa
fieldc string `form:"field_c"`
}
func getdatab(c *gin.context) {
var b structb
c.bind(&b)
c.json(200, gin.h{
"a": b.fielda,
"b": b.fieldb,
})
}
func getdatac(c *gin.context) {
var b structc
c.bind(&b)
c.json(200, gin.h{
"a": b.fielda,
"c": b.fieldc,
})
}
func main() {
r := gin.default()
r.get("/getb", getdatab)
r.get("/getc", getdatac)
r.run()
}
注意,上述代码只是将structa改为structa,也就是说大小写变化。测试结果如下:
curl "http://localhost:8080/getb?field_a=hello&field_b=world"
{"a":"","b":"world"}
curl "http://localhost:8080/getc?field_a=hello&field_c=world"
客户端显示为空,服务器打印一个panic语句,错误类型是runtime error: invalid memory address or nil pointer dereference,一系列panic堆栈,然后是:
[gin] 2019/04/11 - 22:07:01 | 500 | 2.403482ms | 127.0.0.1 | get /getc?field_a=hello&field_c=world,显示状态码是500。
可见,对于结构体嵌套解析,只有结构体是大写的情况下才可以有效解析,小写的情况下,要么不解析,要么解析出现异常。
下面再看一段代码,官方提示的错误:
代码4:
package main
import (
"github.com/gin-gonic/gin"
)
type structa struct {
fielda string
}
type structb struct {
nestedstruct structa `form:"field_a"`
fieldb string `form:"field_b"`
}
type structc struct {
nestedstructpointer *structa `form:"field_a"`
fieldc string `form:"field_c"`
}
func getdatab(c *gin.context) {
var b structb
c.bind(&b)
c.json(200, gin.h{
"a": b.nestedstruct,
"b": b.fieldb,
})
}
func getdatac(c *gin.context) {
var b structc
c.bind(&b)
c.json(200, gin.h{
"a": b.nestedstructpointer,
"c": b.fieldc,
})
}
func main() {
r := gin.default()
r.get("/getb", getdatab)
r.get("/getc", getdatac)
r.run()
}
这里注意structa结构体的tag的位置发生了变化。结果如下:
curl "http://localhost:8080/getb?field_a=hello&field_b=world"
{"a":{"fielda":""},"b":""}
curl "http://localhost:8080/getc?field_a=hello&field_c=world"
{"a":null,"c":""}
可见,这种书写tag的方式存在问题,对应的结构体成员不能正确的解析。)
关于其他情况, 经过测试
(1)对于代码1,将其中的structa改写外structa,参数可以正确解析。
(2)对于代码1,将nestedstruct改为nestedstruct, nestedstructpointer改为nestedstructpointer,对应字段不再解析。
关于tag中的binding:"required"参数,我有一点需要补充的,下面为实例代码:
代码5:
package main
import (
"github.com/gin-gonic/gin"
)
type structa struct {
fielda int `form:"field_a" binding:"required"`
}
type structb struct {
nestedstruct structa
fieldb string `form:"field_b" binding:"required"`
}
func getdatab(c *gin.context) {
var b structb
c.bind(&b)
c.json(200, gin.h{
"a": b.nestedstruct,
"b": b.fieldb,
})
}
func main() {
r := gin.default()
r.get("/getb", getdatab)
r.run()
}
注意fielda的类型和tag,类型由string改为int, tag添加了`bind:"required"`。测试结果如下:
curl "http://localhost:8080/getb?field_a=hello&field_b=world"
{"a":{"fielda":0},"b":""}
服务端有一个额外的打印:
[gin-debug] [warning] headers were already written. wanted to override status code 400 with 200
curl "http://localhost:8080/getb?field_a=0&field_b=world"
{"a":{"fielda":0},"b":"world"}
服务端有一个额外的打印:
[gin-debug] [warning] headers were already written. wanted to override status code 400 with 200
curl "http://localhost:8080/getb?field_b=world"
{"a":{"fielda":0},"b":"world"}
服务端有一个额外的打印:
[gin-debug] [warning] headers were already written. wanted to override status code 400 with 200
curl "http://localhost:8080/getb?field_a=1&field_b=world"
{"a":{"fielda":1},"b":"world"}
服务端没有额外的打印。
curl "http://localhost:8080/getb?field_a=1&field_b="
{"a":{"fielda":1},"b":""}
服务端有一个额外的打印:
[gin-debug] [warning] headers were already written. wanted to override status code 400 with 200
可见,gin对于bind:"required"的判断非常简单,对于int类型,是判断这个int类型是否为0,无论是否显示设置的0,都视为参数错误,对于字符串参数来说,是判断是否为空字符串。