首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >gin 源码阅读(4) - 友好的请求参数处理

gin 源码阅读(4) - 友好的请求参数处理

作者头像
haohongfan
发布于 2021-10-20 02:23:46
发布于 2021-10-20 02:23:46
1.1K00
代码可运行
举报
文章被收录于专栏:HHFCodeRvHHFCodeRv
运行总次数:0
代码可运行

gin 源码阅读系列文章列表:

gin 源码阅读(1) - gin 与 net/http 的关系

gin 源码阅读(2) - http请求是如何流入gin的?

gin 源码阅读(3) - gin 路由的实现剖析

hi,大家好,我是 haohongfan。

通过 gin 的路由,已经把请求分配到具体的函数里面里面了,下面就要开始处理具体的业务逻辑了。

这里就进入 gin 封装的非常重要的的功能,对请求参数快速解析,让我们不纠结于参数的繁琐处理。当然这是对于比较标准的参数处理才可以,对于那些自定义的参数格式只能自己处理了。

参数风格

对于 RESTful 风格的 http 请求来说,参数的表现会有下面几种方式:

URI 参数

什么是 URI 参数?RESTful 风格的请求,某些请求的参数会通过 URI 来表现。

举个简单的例子:张三通过网上银行给李四转了 500 元,这个路由可以这么设计:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
xxx.com/:name/transfer/:money/to/:name

非常具体的体现:
xxx.com/zhangsan/transfer/500/to/lisi

当然你会说这个路由设计会比较丑陋,不过在 URI 里面增加参数有的时候是比较方便的,gin 支持这种方式获取参数。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// This handler will match /user/john but will not match /user/ or /user
router.GET("/user/:name", uriFunc)

对于获取这种路由参数,gin 提供了两种方式去解析这种参数。

方式1:Param

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
func uriFunc(c *gin.Context) {
    name := c.Param("name")
    c.String(http.StatusOK, "Hello %s", name)
}

方式2:bindUri

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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)
}

其实现原理很简单,就是在创建路由树的时候,将路由参数以及对应的值放入一个特定的 map 中即可。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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 即路由的 ? 之后的所带的参数,这种方式是比较常见的。

例如:/welcome?firstname=Jane&lastname=Doe

这里要注意的是,不管是 GET 还是 POST 都可以带 queryString Parameter。我曾经遇到某公司所有的参数都挂在 query string 上,这样做其实是不建议的,不过大家都这么做,只能顺其自然了。这么做的缺点很明显:

  • 容易突破 URI 的长度限制,导致接口参数被截断。一般情况下服务器为了安全会对 URL 做长度限制,最大为2048
  • 同时服务器也会对传输的大小也是有限制的,一般是 2k
  • 当然这么做也是不安全的,都是明文的

这里就不具体罗列了,反正缺点挺多的。

这种参数也有两种获取方式:

方式1:Query

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
firstname := c.DefaultQuery("firstname", "Guest")
lastname := c.Query("lastname") // shortcut for c.Request.URL.Query().Get("lastname")

方式2:Bind

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
type Person struct {
   FirstName  string `form:"name"`
}

func queryFunc(c *gin.Context) {
    var person Person
    if c.ShouldBindQuery(&person) == nil {
        log.Println(person.Name)
    }
}

实现原理:其实很简单就是将请求参数解析出来而已,利用的 net/url 的相关函数。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
//net/url.go:L1109
func (u *URL) Query() Values {
    v, _ := ParseQuery(u.RawQuery)
    return v
}

Form

Form 一般还是更多用在跟前端的混合开发的情况下。Form 可以用于所有的方法 POST,GET,HEAD,PATCH ……

这种参数也有两种获取方式:

方式1:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
name := c.PostForm("name")

方式2:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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 是被使用最多的方式,基本上各种语言库对 json 格式的解析非常完善了,而且还在不断的推陈出新。

gin 对 json 的解析只有一种方式。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
type Person struct {
    Name string `json:"name"`
}

func jsonFunc(c *gin.Context) {
    var person Person
    if c.ShouldBind(&person) == nil {
        log.Println(person.Name)
    }
}

gin 默认是使用的 go 内置的 encoding/json 库,内置的 json 在 go 1.12 后性能得到了很大的提高。不过 Go 对接 PHP 的接口,如果用内置的 json 库简直就是一种折磨,gin 可以使用 jsoniter 来代替,只需要在编译的时候加上标志即可:"go build -tags=jsoniter .",强烈建议对接 PHP 接口的同学,尝试 jsoniter 这个库,让你不再受 PHP 接口参数类型不确定之苦。

当然 gin 还支持其他类型参数的解析,如 Header,XML,YAML,Msgpack,Protobuf 等,这里就不再具体介绍了。

Bind 系列函数的源码剖析

使用 gin 解析 request 的参数,按照我的实践来看,使用 Bind 系列函数还是比较好一点,因为这样请求的参数会比较好归档、分类,也有助于后续的接口升级,而不是将接口的请求参数分散不同的 handler 里面。

初始化 binding 相关对象

gin 在程序启动就会默认初始化好 binding 相关的变量

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 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{}
)

ShoudBind 与 MustBind 的区别

bind 相关的系列函数大体上分为两类 ShoudBind 和 MustBind。实现上基本一样,为了有区别的 MustBind 在解析失败的时候,返回 HTTP 400 状态。

MustBindWith:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
func (c *Context) ShouldBindWith(obj interface{}, b binding.Binding) error {
   return b.Bind(c.Request, obj)
}

匹配对应的参数 decoder

不管是 MustBind 还是 ShouldBind,总体上解析又可以分为两类:一种是让 gin 自己判断使用哪种 decoder,另外一种就是指定某种 decoder。自己判断使用哪种 decoder 比 指定 decoder 多了一步判断,其他的都是一样的。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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 会根据传入的 ContentType 来判断该使用哪种 decoder。不过对于 Header 和 Uri 方式的参数,只能用指定方式的decoder 了。

总结

本篇文章主要介绍了 gin 是如何快速处理客户端传递过的参数的。写文章不易请大家帮忙点击 在看,点赞,分享。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-10-13,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 HHFCodeRv 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
tmp临时文件目录自动清理
Linux操作系统的/tmp目录保存临时文件,那么这个目录下的临时文件究竟会保存多久,何时被清理,通过什么方式(哪个服务)来维护清理工作呢?
Erwin
2019/12/31
7.9K0
/tmp目录清理问题
2019年7月10日 ⋅ 浏览量: 4   
以谁为师
2019/07/11
3.3K0
Unit 3
/etc/at.allow        ##默认不存在。一旦存在,/etc/at.deny失效
py3study
2020/01/14
4020
Linux From Scratch(LFS11.0)构建 LFS 系统 - Systemd 使用和配置
/etc/systemd/system.conf 文件包含一组控制 systemd 基本功能的选项。默认文件中所有条目都被注释掉,并标明了默认值。可以在这里修改日志级别,以及其他一些基本日志设定。
Lucifer三思而后行
2021/10/08
4540
Linux 这50个 systemd 命令值得每位Linux运维工程师收藏!
systemd是一种Linux系统初始化和管理守护进程的系统和服务管理器。它引入了一组命令行工具,用于管理和监控系统状态、服务单元和日志。本文将介绍一些常用的systemd命令,包括systemdctl、journalctl、hostnamectl、timedatectl、localectl、loginctl、systemd-analyze、systemd-cgls、systemd-cgtop、systemd-delta、systemd-path、systemd-nspawn、systemd-machine-id-setup、systemd-escape、systemd-cat、systemd-notify、systemd-tmpfiles、systemd-run、systemd-mount、systemd-ask-password、systemd-inhibit、systemd-dissect、systemd-resolve和systemd-firstboot。
网络技术联盟站
2023/07/14
9230
Linux 这50个 systemd 命令值得每位Linux运维工程师收藏!
Test2 unit2+3
-rw-r--r--. 1 root root 0 Nov  7 09:14 file
py3study
2020/01/14
4320
SpringBoot文件上传异常之提示The temporary upload location xxx is not valid
SpringBoot搭建的应用,一直工作得好好的,突然发现上传文件失败,提示org.springframework.web.multipart.MultipartException: Failed to parse multipart servlet request; nested exception is java.io.IOException: The temporary upload location [/tmp/tomcat.6239989728636105816.19530/work/Tomcat/localhost/ROOT] is not valid目录非法,实际查看目录,结果还真没有,下面就这个问题的表现,分析下SpringBoot针对文件上传的处理过程
一灰灰blog
2019/05/26
3.3K0
关于 Linux 中 systemd 的一些笔记
傍晚时分,你坐在屋檐下,看着天慢慢地黑下去,心里寂寞而凄凉,感到自己的生命被剥夺了。当时我是个年轻人,但我害怕这样生活下去,衰老下去。在我看来,这是比死亡更可怕的事。--------王小波
山河已无恙
2023/01/30
7520
关于 Linux 中 systemd 的一些笔记
【LFS 系列】从零开始 DIY Linux 系统:(七)基本系统配置 - Systemd 的用法与配置
/etc/systemd/system.conf 文件包含了大量的 systemd 控制命令。
Lucifer三思而后行
2021/10/08
5540
【LFS 系列】从零开始 DIY Linux 系统:(六)构建 LFS 系统 - Systemd-219
首先创建文件使得当使用第五章中编译的 Util-Linux 以及停用默认的 LTO(链接时优化) 时能顺利编译 systemd:
Lucifer三思而后行
2021/10/08
8860
深入探究Linux树状目录结构
Linux 作为一款广泛使用的开源操作系统,其目录结构采用了树状设计,这种结构清晰、有条理,便于用户和系统进行文件管理与操作。
池央
2025/01/16
2860
深入探究Linux树状目录结构
Linxu学习(一)——Linux目录与文件
· 若是 c 则表示为装置文件里面的串行端口设备,例如键盘、鼠标(一次性读取装置)。
传说之下的花儿
2023/04/16
7370
Linxu学习(一)——Linux目录与文件
ELK-单机部署Elasticsearch
Elasticsearch(简称ES)是一个开源的分布式搜索引擎,Elasticsearch还是一个分布式文档数据库。所以它提供了大量数据的存储功能,快速的搜索与分析功能。
星哥玩云
2022/09/15
6150
ELK-单机部署Elasticsearch
Linux操作系统完整目录结构概览
Linux,作为一种开源的操作系统,其文件系统设计展现了极高的灵活性和效率。在每一个 Linux 系统中,都存在一套标准的目录结构,这套结构不仅映射了操作系统的运作机制,同时也为管理员与用户提供了一种便捷的文件管理方式。对于 Linux 的用户和管理者而言,掌握这些目录的知识是极其重要的一环。🧑‍💻
Lethehong
2025/03/20
1970
Linux操作系统完整目录结构概览
Springboot——报错总结
思索
2024/08/16
1190
Supervisord安装配置笔记 原
想要了解怎么配置需要管理的进程,只要打开 supervisord.conf 就可以了,里面有很详细的注释信息。
bdcn
2018/09/12
6990
Linux1_快捷键+目录结构
ctrl+p向前翻一个命令 ctrl+n向后翻一个命令 ctrl+b光标向前 ctrl+f光标向后 ctrl+a光标到行首 ctrl+e光标到行尾 ctrl+h或退格键 删除光标前的字符 ctrl+d删除光标前的字符(光标覆盖的字符) ctrl+u删除光标前的所有
Y大宽
2019/05/23
7850
这个python程序怎么就是没更新
把supervisor进程重启一把 supervisorctl status 服务又正常了 托管的代码也同样更新成功
莫韵
2018/06/07
9950
Linux 系统目录结构
在 Linux 系统中,有几个目录是比较重要的,平时需要注意不要误删除或者随意更改内部文件。
用户8682940
2021/12/02
2.4K0
关于Linux中作业调度 crond 和 systemd.timer 使用场景的一些笔记
crond 这里不多介绍,小伙伴们应该都接触过。所以直接来看 systemd.timer
山河已无恙
2023/01/30
1K0
推荐阅读
相关推荐
tmp临时文件目录自动清理
更多 >
LV.1
这个人很懒,什么都没有留下~
目录
  • 参数风格
    • URI 参数
    • QueryString Parameter
    • Form
    • Json Body
  • Bind 系列函数的源码剖析
    • 初始化 binding 相关对象
    • ShoudBind 与 MustBind 的区别
    • 匹配对应的参数 decoder
  • 总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档