Gin
封装的最好的地方就是context
和对response
的处理. github
的README
的介绍,基本就是对这两个东西的解释. 本篇文章主要解释context
的使用方法, 以及其设计原理
Request
的处理封装到Context
中在阅读gin
的源码时, 请求的处理是使用type HandlerFunc func(*Context)
来处理的. 也就是
func(context *gin.Context) {
context.String(http.StatusOK, "some post")
}
参数是gin.Context
, 但是查看源码发现其实gin.Context
在整个框架处理的地方只有下面这段:
func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
c := engine.pool.Get().(*Context)
c.writermem.reset(w)
c.Request = req
c.reset()
engine.handleHTTPRequest(c)
engine.pool.Put(c)
}
那为什么还要利用Context
来处理呢. gin
的context
实现了的context.Context Interface
.
经过查看context.Context
相关资料, Context
的最佳运用场景就是对Http
的处理. 封装成Conetxt
另外的好处就是WithCancel
, WithDeadline
, WithTimeout
, WithValue
这些context
包衍生的子Context
就可以直接来使用. 目前我能想到的地方就这么多, 以后发现gin.Context
其他的优点再补充.
gin.Context
的设计gin.Context
主要由下面几部分组成(这里沿用源代码里面的注释)
Metadata Management
(我自己叫法:Key-Value
)这个模块比较简单, 就是从gin.Context
中Set Key-Value
, 以及各种个样的Get
方法, 如GetBool
, GetString
等实现这些功能也很简单, 其实就是一个map
// Keys is a key/value pair exclusively for the context of each request.
Keys map[string]interface{}
Input Data
这个模块相当重要了, gin
的README
基本上都在介绍这个模块的用法.
Param
(我自己的叫法: 路由变量)gin
的标准叫法是Parameters in path
. restful
风格api
如/user/john
, 这个路由在gin
里面是/user/:name
, 要获取john
就需要使用Param
函数
name := c.Param("name")
这个方法实现也很简单, 就是在tree.go
里面根据路由相关规则解析出来然后赋值给gin.Context
的Params
.
handlers, params, tsr := root.getValue(path, c.Params, unescape)
Query
/welcome?firstname=Jane&lastname=Doe
这样一个路由, first
, last
即是Querystring parameters
, 要获取他们就需要使用Query
相关函数.
c.Query("first") // Jane
c.Query("last") // Doe
当然还有其他相关函数:
QueryMap
DefaultQuery
这个默认值的实现更加简单, 当QueryString
中不包含这个值, 直接返回填入的值这些方法是的实现是利用net/http
的Request
的方法实现的
PostForm
对于POST
, PUT
等这些能够传递参数Body
的请求, 要获取其参数, 需要使用PostForm
POST /user/1
{
"name":manu,
"message":this_is_great
}
name := c.PostForm("name")
message := c.PostForm("message")
其他相关函数
DefaultPostForm
这些相关的方法是实现还是利用net/http
的Request
的方法实现的
FormFile
对于文件相关的操作, 一般生产情况下不建议这样使用, 因为把文件上传到服务器磁盘, 还得磁盘相关的监控. 我觉得最好利用云服务商相关的对象存储, 如:阿里云OSS, 七牛云对象存储, AWS的对象存储等来做文件的相关操作
Bind
内置的有json
, xml
, protobuf
, form
, query
, yaml
. 这些Bind
极大的减少我们自己去解析各种个样的数据格式, 提高我们的开发速度
Bind
的实现都在gin/binding
里面. 这些内置的Bind
都实现了Binding
接口, 主要是Bind()
函数.
context.BindJSON()
支持MIME为application/json
的解析context.BindXML()
支持MIME为application/xml
的解析context.BindYAML()
支持MIME为application/x-yaml
的解析context.BindQuery()
只支持QueryString
的解析, 和Query()
函数一样context.BindUri()
只支持路由变量的解析Context.Bind()
支持所有的类型的解析, 这个函数尽量还是少用(当QueryString
, PostForm
, 路由变量在一块同时使用时会产生意想不到的效果), 目前测试Bind
不支持路由变量的解析, Bind()
函数的解析比较复杂, 这部分代码后面再看Response
Header
的支持Header
GetHeader
这里的Header
是写到Response
里面的Header
. 对于客户端发的请求的Header
可以通过context.Request.Header.Get("Content-Type")
获取
Cookie
提供对session
, cookie
的支持
render
做api
常用到的其实就是gin
封装的各种render
. 目前支持的有:
func (c *Context) JSON(code int, obj interface{})
func (c *Context) Protobuf(code int, obj interface{})
func (c *Context) YAML(code int, obj interface{}) ...
当然我们可以自定义渲染, 只要实现func (c *Context) Render(code int, r render.Render)
即可.
这里我们常用的是一个方法是: gin.H{"error": 111}
. 这个结构相当实用, 各种render
都支持. 其实这个结构很简单就是type H map[string]interface{}
, 当我们要从map转换各种各样结构时, 不妨参考gin
这里的代码
Context
说到这里基本就说完了, 这里介绍的方法都是开发中特别实用的方法. context
的代码实现也特别有条理, 建议可以看看这部分代码.