数据解析过程分析
本文通过阅读Golang的Gin框架源码,分析了Gin框架接收前端传来POST请求中携带数据的过程。
这边我们以一个简化的登录验证操作为例,获取前端POST请求的data中JSON对象,如果符合要求则登录。
package main
import (
"fmt"
"github.com/gin-gonic/gin"
)
type LoginData struct {
User string `form:"user" binding:"required"`
Password string `form:"password" binding:"required"`
}
func main() {
router := gin.Default()
router.POST("/login", func(c *gin.Context) {
// 使用 ShouldBind 方法自动绑定:
var data LoginData
// 在这种情况下,将自动选择合适的绑定
if c.ShouldBind(&data) == nil {
fmt.Println(data.User, data.Password)
if data.User == "root" && data.Password == "123456" {
c.JSON(200, gin.H{"status": "you are logged in"})
} else {
c.JSON(401, gin.H{"status": "unauthorized"})
}
}
})
err := router.Run(":8080")
if err != nil {
return
}
}
发送的POST请求中,携带的数据
传输正确的user和password后,返回成功登录。
上面的代码中,接收前端传来的数据并解析,使用的是ShouldBind
函数,下面将对这个函数进行深入分析。
func (c *Context) ShouldBind(obj any) error {
b := binding.Default(c.Request.Method, c.ContentType())
return c.ShouldBindWith(obj, b)
}
这个函数首先会通过c.ContentType()
函数,查询Request
的“Content-Type”
字段,获取c中携带数据的类型,是JSON、xml、form
或是其他,具体能解析的种类下所示。
var (
JSON BindingBody = jsonBinding{}
XML BindingBody = xmlBinding{}
Form Binding = formBinding{}
Query Binding = queryBinding{}
FormPost Binding = formPostBinding{}
FormMultipart Binding = formMultipartBinding{}
ProtoBuf BindingBody = protobufBinding{}
MsgPack BindingBody = msgpackBinding{}
YAML BindingBody = yamlBinding{}
Uri BindingUri = uriBinding{}
Header Binding = headerBinding{}
TOML BindingBody = tomlBinding{}
)
解析出来对应的种类后,会创建该数据类型的解析工具对象,该对象满足下面的Binding接口需求,必然有函数Bind(*http.Request, any) error
。
// Binding describes the interface which needs to be implemented for binding the
// data present in the request such as JSON request body, query parameters or
// the form POST.
type Binding interface {
Name() string
Bind(*http.Request, any) error
}
程序通过调用Bind
函数对传过来的值进行解析。调用对应数据类型的解析函数,我们假设上面的程序接收的数据为JSON,则会解析JSON格式的数据到data中。通过调用下面这个函数进行解析。
// 下面的代码来自Gin源码,位置"github.com/gin-gonic/gin/internal/json"
func (jsonBinding) Bind(req *http.Request, obj any) error {
if req == nil || req.Body == nil {
return errors.New("invalid request")
}
return decodeJSON(req.Body, obj)
}
func decodeJSON(r io.Reader, obj any) error {
decoder := json.NewDecoder(r)
if EnableDecoderUseNumber {
decoder.UseNumber()
}
if EnableDecoderDisallowUnknownFields {
decoder.DisallowUnknownFields()
}
if err := decoder.Decode(obj); err != nil {
return err
}
return validate(obj)
}
也就是说,最后将request中的JSON数据进行映射,调用的其实就是下面这个函数
// 简化过了
func decodeJSON(r io.Reader, obj any) error {
decoder := json.NewDecoder(r)
if err := decoder.Decode(obj); err != nil {
return err
}
return nil
}
decodeJSON(c.Request.Body, &form)
为了验证,我们将代码修改为如下的形式。
package main
import (
"encoding/json"
"fmt"
"github.com/gin-gonic/gin"
"io"
)
type LoginData struct {
User string `form:"user" binding:"required"`
Password string `form:"password" binding:"required"`
}
func decodeJSON(r io.Reader, obj any) error {
decoder := json.NewDecoder(r)
if err := decoder.Decode(obj); err != nil {
return err
}
return nil
}
func main() {
router := gin.Default()
router.POST("/login", func(c *gin.Context) {
var data LoginData
// 将c.Request.Body中的数据解析为data
err := decodeJSON(c.Request.Body, &data)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(data.User, data.Password)
})
err := router.Run(":8080")
if err != nil {
return
}
}
依旧可以解析出结果。
这是传的post请求
总结
GIN框架解析POST请求携带数据过程,可以简单总结为:从HTTP请求中读取数据,然后调用对应格式的解析函数,对其进行解析。