List of articles in the gin source code reading series:
Gin source code reading (1) - the relationship between gin and net/httpGin source code reading (2) - how do HTTP requests flow into gin?Gin source code reading (3) - Analysis of the implementation of gin routing
hi, Hello, I'm haohongfan.
Through the routing of gin, the request has been allocated to the specific function. Now we will start to process the specific business logic.
Here we go to the very important function of gin encapsulation to quickly parse the request parameters, so that we don't worry about the cumbersome processing of parameters. Of course, this is only possible for standard parameter processing, and only for those user-defined parameter formats.
Parameter style
For RESTful http requests, parameters can be expressed in the following ways:
URI parameter
What are URI parameters? For RESTful style requests, some request parameters are represented by URIs.
Take a simple example: Zhang San transferred 500 yuan to Li Si through online banking. This route can be designed as follows:
xxx.com/:name/transfer/:money/to/:name
Very specific embodiment:
xxx.com/zhangsan/transfer/500/to/lisi
Of course, you will say that this routing design will be ugly, but it is sometimes convenient to add parameters to the URI. gin supports this way to obtain parameters.
// This handler will match /user/john but will not match /user/ or /user
router.GET("/user/:name", uriFunc)
For obtaining this routing parameter, gin provides two ways to resolve this parameter.
Mode 1: Param
func uriFunc(c *gin.Context) {
name := c.Param("name")
c.String(http.StatusOK, "Hello %s", name)
}
Method 2: bindUri
type Person struct {
Name string `uri:"name" binding:"required"`
}
func uriFunc(c *gin.Context) {
var person Person
if err := c.ShouldBindUri(&person); err != nil {
c.JSON(400, gin.H{"msg": err.Error()})
return
}
c.JSON(200, gin.H{"name": person.Name)
}
The implementation principle is very simple. When creating a routing tree, you can put the routing parameters and corresponding values into a specific map.
func (ps Params) Get(name string) (string, bool) {
for _, entry := range ps {
if entry.Key == name {
return entry.Value, true
}
}
return "", false
}
QueryString Parameter
query String is the name of the route ? This method is more common for the parameters carried by the later.
For example: / welcome? Firstname = Jane & LastName = doe
It should be noted here that queryString Parameter can be brought whether it is GET or POST. I once encountered a company where all parameters are hung on the query string. In fact, it is not recommended to do so, but we can only let it go. The disadvantages of this are obvious:
- It is easy to break through the length limit of URI, resulting in the truncation of interface parameters. In general, the server will limit the length of the URL to 2048 for security
- At the same time, the server also limits the size of the transmission, usually 2k
- Of course, it's not safe to do so. It's all in writing
It's not specifically listed here. Anyway, there are many shortcomings.
This parameter can also be obtained in two ways:
Method 1: Query
firstname := c.DefaultQuery("firstname", "Guest")
lastname := c.Query("lastname") // shortcut for c.Request.URL.Query().Get("lastname")
Mode 2: Bind
type Person struct {
FirstName string `form:"name"`
}
func queryFunc(c *gin.Context) {
var person Person
if c.ShouldBindQuery(&person) == nil {
log.Println(person.Name)
}
}
Implementation principle: in fact, it is very simple to parse the request parameters, using the related functions of net/url.
//net/url.go:L1109
func (u *URL) Query() Values {
v, _ := ParseQuery(u.RawQuery)
return v
}
Form
Form is generally used for mixed development with the front end. Form can be used for all methods POST,GET,HEAD,PATCH
This parameter can also be obtained in two ways:
Mode 1:
name := c.PostForm("name")
Mode 2:
type Person struct {
Name string `form:"name"`
}
func formFunc(c *gin.Context) {
var person Person
if c.ShouldBind(&person) == nil {
log.Println(person.Name)
}
}
Json Body
Json Body is the most used method. Basically, the parsing of json format in various language libraries is very perfect, and it is constantly pushing through the old and bringing forth the new.
gin has only one way to parse json.
type Person struct {
Name string `json:"name"`
}
func jsonFunc(c *gin.Context) {
var person Person
if c.ShouldBind(&person) == nil {
log.Println(person.Name)
}
}
By default, gin uses go's built-in encoding/json library. The performance of built-in json has been greatly improved after go 1.12. However, if you use the built-in json library for the interface between go and PHP, it is a torture. Gin can use jsoniter instead. You only need to add a flag when compiling: "go build -tags=jsoniter." it is strongly recommended that students who connect with PHP interface try jsoniter, so that you will no longer suffer from the uncertainty of PHP interface parameter types.
Of course, gin also supports the parsing of other types of parameters, such as Header, XML, YAML, Msgpack, Protobuf, etc., which will not be introduced in detail here.
Source code analysis of Bind series functions
Using gin to parse the request parameters, according to my practice, it is better to use Bind series functions, because the requested parameters will be better archived and classified, which is also helpful for subsequent interface upgrading, rather than dispersing the request parameters of the interface into different handler s.
Initializing binding related objects
gin initializes the binding related variables by default when the program starts
// binding:L74
var (
JSON = jsonBinding{}
XML = xmlBinding{}
Form = formBinding{}
Query = queryBinding{}
FormPost = formPostBinding{}
FormMultipart = formMultipartBinding{}
ProtoBuf = protobufBinding{}
MsgPack = msgpackBinding{}
YAML = yamlBinding{}
Uri = uriBinding{}
Header = headerBinding{}
)
The difference between ShoudBind and MustBind
The series of functions related to bind are generally divided into two types: ShoudBind and MustBind. The implementation is basically the same. For differentiated MustBind, when parsing fails, it returns the HTTP 400 status.
MustBindWith:
func (c *Context) MustBindWith(obj interface{}, b binding.Binding) error {
if err := c.ShouldBindWith(obj, b); err != nil {
c.AbortWithError(http.StatusBadRequest, err).SetType(ErrorTypeBind) // nolint: errcheck
return err
}
return nil
}
ShoudBindWith:
func (c *Context) ShouldBindWith(obj interface{}, b binding.Binding) error {
return b.Bind(c.Request, obj)
}
Match the corresponding parameter decoder
Whether MustBind or ShouldBind, generally speaking, parsing can be divided into two categories: one is to let gin judge which decoder to use, and the other is to specify a decoder. It is one step more to judge which decoder to use than the specified decoder, and the others are the same.
func (c *Context) ShouldBind(obj interface{}) error {
b := binding.Default(c.Request.Method, c.ContentType())
return c.ShouldBindWith(obj, b)
}
func Default(method, contentType string) Binding {
if method == http.MethodGet {
return Form
}
switch contentType {
case MIMEJSON:
return JSON
case MIMEXML, MIMEXML2:
return XML
case MIMEPROTOBUF:
return ProtoBuf
case MIMEMSGPACK, MIMEMSGPACK2:
return MsgPack
case MIMEYAML:
return YAML
case MIMEMultipartPOSTForm:
return FormMultipart
default: // case MIMEPOSTForm:
return Form
}
}
ShouldBind/MustBind will judge which decoder to use according to the passed in ContentType. However, for Header and Uri parameters, you can only use the decoder of the specified method.
summary
This article mainly introduces how gin can quickly handle the parameters passed by the client. It's not easy to write articles. Please click to see, like and share