其实这一篇应该在golang的context包之前更新的。但是当我看见gin中有context,而golang自己也自带context的时候,下意识的感觉这两个context肯定有什么关联!
之后在看golang的context包后,还是很别扭的,因为这个包似乎更偏向于运行控制而不是一次请求的上下文。之后翻看了下gin的源码才发现,原来真是我想多了。。。。。。gin框架中的context就是很传统的一次请求的上下文嘛,是用来保存request和response信息的。
好了闲话不多说,开始阅读源码吧!
首先看看最重要的,type Context
// Context is the most important part of gin. It allows us to pass variables between middleware,
// manage the flow, validate the JSON of a request and render a JSON response for example.
type Context struct {
writermem responseWriter
Request *http.Request
Writer ResponseWriter
Params Params
handlers HandlersChain
index int8
fullPath string
engine *Engine
// Keys is a key/value pair exclusively for the context of each request.
Keys map[string]interface{}
// Errors is a list of errors attached to all the handlers/middlewares who used this context.
Errors errorMsgs
// Accepted defines a list of manually accepted formats for content negotiation.
Accepted []string
// queryCache use url.ParseQuery cached the param query result from c.Request.URL.Query()
queryCache url.Values
// formCache use url.ParseQuery cached PostForm contains the parsed form data from POST, PATCH,
// or PUT body parameters.
formCache url.Values
}
const abortIndex int8 = math.MaxInt8 / 2
c.index++
在gin框架中是使用变量池来保存Context的,变量池在 sync
包中有提到,使用变量池可以更好做gc。在gin的engine结构体中有一个用于存放上下文的变量池成员 pool sync.Pool
,在使用 engine.New()
方法实例化一个框架实例的时候设置了变量池的新增变量方法:
engine.pool.New = func() interface{} {
return engine.allocateContext()
}
func (engine *Engine) allocateContext() *Context {
return &Context{engine: engine}
}
allocateContext()
创建了一个上下文实体,并且在这里将框架实体注入到了上下文中。
在做响应请求回调的时候,在ServeHTTP
中向context内注入了request和response
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)
}
func (c *Context) reset()
用于重置一个上下文为默认状态
func (c *Context) Copy() *Context
用于在协程操作中,获得一个可操作的上下文的复制,在框架中直接操作原始的上下文是非常危险的,因为这个上下文是会贯穿始终的。如果要在协程中修改上下文的话,普通的变量是不安全的,最安全的方法是获取一份上下文的复制再进行操作。
func (c *Context) HandlerName() string
获取回调函数中真正处理请求的函数名(回调函数切片中的最后一个)
func (c *Context) HandlerNames() []string
获得所有的回调函数名
func (c *Context) Handler() HandlerFunc
获取回调函数中真正处理请求的函数(回调函数切片中的最后一个)
func (c *Context) FullPath() string
获取请求路径
func (c *Context) Next()
用于在中间件中手动开始处理下一个中间件,实际上这个操作是自动的,代码是这样的
func (c *Context) Next() {
c.index++
for c.index < int8(len(c.handlers)) {
c.handlers[c.index](c)
c.index++
}
}
执行一次Next()
方法就可以调用所有的中间件了
func (c *Context) IsAborted() bool
用于判断这次请求是否已取消
func (c *Context) Abort()
用于取消这次请求
func (c *Context) AbortWithStatus(code int)
用于取消这次请求并返回指定状态码
func (c *Context) AbortWithStatusJSON(code int, jsonObj interface{})
用于取消这次请求并返回指定状态码和json
func (c *Context) AbortWithError(code int, err error) *Error
用于取消这次请求并写入指定状态码,返回的是一个Error
func (c *Context) Error(err error) *Error
向上下文的Errors中写入一个error,Errors是一个Error切片,可以用来记录运行错误或者中间件处理的错误
Context.Keys
管理func (c *Context) Set(key string, value interface{})
向Context.Keys
中注入一个数据
func (c *Context) Get(key string) (value interface{}, exists bool)
从Context.Keys
中取出一个数据,值的注意的是,这里取出数据失败并不会返回error,成功返回一个 空接口和true
,失败返回 nil和false
func (c *Context) MustGet(key string) interface{}
取出一个必要的数据,存在返回数据,不存在抛出 panic
错误
func (c *Context) GetString(key string)
func (c *Context) GetBool(key string) (b bool)
func (c *Context) GetInt(key string) (i int)
func (c *Context) GetInt64(key string) (i64 int64)
func (c *Context) GetFloat64(key string) (f64 float64)
func (c *Context) GetTime(key string) (t time.Time)
func (c *Context) GetDuration(key string) (d time.Duration)
func (c *Context) GetStringSlice(key string) (ss []string)
func (c *Context) GetStringMap(key string) (sm map[string]interface{})
func (c *Context) GetStringMapString(key string) (sms map[string]string)
func (c *Context) GetStringMapStringSlice(key string) (smss map[string][]string)
以上方法不过多赘述,取得一个元数据并转换类型,不存在会返回这个数据类型的默认值
func (c *Context) Param(key string) string
从路由中中获取参数,这里的参数并不是从?foo=bar
中取得的get参数,而是路由绑定的参数,如:
router.GET("/user/:id", func(c *gin.Context) {
// a GET request to /user/john
id := c.Param("id") // id == "john"
})
func (c *Context) Query(key string) string
从路由中获取参数,这个是get请求的参数
func (c *Context) DefaultQuery(key, defaultValue string) string
从路由获取一个参数,没有返回默认值
func (c *Context) GetQuery(key string) (string, bool)
从路由中获取一个参数,和Query
不同的是,这里路由中不存在的参数会返回false来提示你没有这个参数:
// GetQuery is like Query(), it returns the keyed url query value
// if it exists `(value, true)` (even when the value is an empty string),
// otherwise it returns `("", false)`.
// It is shortcut for `c.Request.URL.Query().Get(key)`
// GET /?name=Manu&lastname=
// ("Manu", true) == c.GetQuery("name")
// ("", false) == c.GetQuery("id")
// ("", true) == c.GetQuery("lastname")
func (c *Context) QueryArray(key string) []string
获取get参数中某个参数的所有值。这个用到的地方应该会比较少,因为我从web开发很久了,没有见过这种同一个参数名传多个参数的用法。如:curl 127.0.0.1:8080/ping?arr=1&arr=2&arr=3
func (c *Context) GetQueryArray(key string) ([]string, bool)
和 QueryArray
不同的是,不存在参数会返回false
func (c *Context) QueryMap(key string) map[string]string
返回一个map的get参数,这个就更更更罕见了,可以获取这样的请求:http://127.0.0.1:8888/repeat?job[a]=周起&job[b]=card
func (c *Context) GetQueryMap(key string) (map[string]string, bool)
和 QueryMap
不同的是,不存在参数会返回false
func (c *Context) PostForm(key string) string
获取post请求表单的参数
func (c *Context) DefaultPostForm(key, defaultValue string) string
和 PostForm
不同的是,这里不存在会返回默认值
func (c *Context) GetPostForm(key string) (string, bool)
和 PostForm
不同的是,这里不存在会返回false
func (c *Context) PostFormArray(key string) []string
获取post表单的一个列表,这个还是比较常见的
func (c *Context) GetPostFormArray(key string) ([]string, bool)
和 PostFormArray
不同的是,这里不存在会返回false
func (c *Context) PostFormMap(key string) map[string]string
这个虽然比较罕见,但是还是能想象出来的,提交列表的时候会用:<input name="key[]">
而提交一个map的时候会用:input name="map[key]"
func (c *Context) GetPostFormMap(key string) (map[string]string, bool)
和 PostFormMap
不同的是,这里不存在会返回false
func (c *Context) FormFile(name string) (*multipart.FileHeader, error)
获取表单内的文件,没有会返回错误
func (c *Context) MultipartForm() (*multipart.Form, error)
获取整个post表单,包括上传的文件
func (c *Context) SaveUploadedFile(file *multipart.FileHeader, dst string) error
保存上传的文件
??????????????我是忘记保存了吗。。。白写了惊了。。再敲一遍好了
在刚开始看bind的时候一脸懵逼,这是什么玩意。。后来查阅文档后才发现,原来这个就是用来做验证的验证器(validator)
bind支持多种请求类型的参数验证:
var (
JSON = jsonBinding{}
XML = xmlBinding{}
Form = formBinding{}
Query = queryBinding{}
FormPost = formPostBinding{}
FormMultipart = formMultipartBinding{}
ProtoBuf = protobufBinding{}
MsgPack = msgpackBinding{}
YAML = yamlBinding{}
Uri = uriBinding{}
Header = headerBinding{}
)
不过不用担心,除了header和uri这样的特殊的参数来源,其他普通的参数可以不用管,gin底层有做推导请求类型自动增加对应的验证器:
func Default(method, contentType string) Binding {
if method == "GET" {
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
}
}
func (c *Context) Bind(obj interface{}) error
使用默认的推导验证器来验证,需要传入一个实例化的结构体obj,需要在结构体中打tag后,底层做了反射判断参数类型是否正确,必须的参数是否传递等等。
底层会根据请求方式推导出需要用到的bind类型并使用 MustBindWith
方法进行验证
func (c *Context) MustBindWith(obj interface{}, b binding.Binding) error
真正的验证用的方法,第一个参数是一个打好tag的结构体,第二个参数是验证用的bind类型
func (c *Context) BindJSON(obj interface{}) error
简写化的:c.MustBindWith(obj, binding.JSON)
func (c *Context) BindXML(obj interface{}) error
简写化的:c.MustBindWith(obj, binding.BindXML)
func (c *Context) BindQuery(obj interface{}) error
简写化的:c.MustBindWith(obj, binding.Query)
func (c *Context) BindYAML(obj interface{}) error
简写化的:c.MustBindWith(obj, binding.YAML)
func (c *Context) BindHeader(obj interface{}) error
简写化的:c.MustBindWith(obj, binding.Header)
func (c *Context) BindUri(obj interface{}) error
从uri中获取参数并验证绑定
func (c *Context) ShouldBindWith(obj interface{}, b binding.Binding) error
和 MustBindWith
类似,不过 MustBindWith
在验证失败后会自动返回一个400错误,ShouldBindWith
不会做任何操作,会返回一个error,但是程序可以进行下去
func (c *Context) ShouldBind(obj interface{}) error
func (c *Context) ShouldBindJSON(obj interface{}) error
func (c *Context) ShouldBindXML(obj interface{}) error
func (c *Context) ShouldBindQuery(obj interface{}) error
func (c *Context) ShouldBindYAML(obj interface{}) error
func (c *Context) ShouldBindHeader(obj interface{}) error
func (c *Context) ShouldBindUri(obj interface{}) error
这些和上面的bind系一致,不多写了(主要是丢档了一次懒了)
func (c *Context) ClientIP() string
获取客户端ip
func (c *Context) ContentType() string
获取请求的 Content-Type
func (c *Context) IsWebsocket() bool
用于判断是不是websocket请求
func (c *Context) Status(code int)
设置状态码
func (c *Context) Header(key, value string)
设置header,其中value设置为 ""
会清空这个key
func (c *Context) GetHeader(key string) string
获取当前响应的header详情
func (c *Context) GetRawData() ([]byte, error)
获取响应的原始数据
func (c *Context) SetCookie(name, value string, maxAge int, path, domain string, secure, httpOnly bool)
设置cookie
func (c *Context) Cookie(name string) (string, error)
获取cookie
func (c *Context) Render(code int, r render.Render)
一个渲染器,用于响应数据
func (c *Context) HTML(code int, name string, obj interface{})
响应html,其中name是一个模板文件的路径,obj是模板文件渲染的参数
func (c *Context) JSON(code int, obj interface{}
常用的返回json数据
func (c *Context) IndentedJSON(code int, obj interface{})
美化过的json数据,请只有在debug和开发的时候使用它,因为为了美化数据需要使用更多的内存
func (c *Context) SecureJSON(code int, obj interface{})
安全的json,为了防止json被人抓取,会在json前面增加一串字符串,默认是 while(1);
,设置的位置为框架的 Gin.New()
方法中:secureJsonPrefix: "while(1);",
func (c *Context) JSONP(code int, obj interface{})
jsonp格式响应,用于跨域
func (c *Context) AsciiJSON(code int, obj interface{})
响应一个ascii转码后的json
func (c *Context) PureJSON(code int, obj interface{})
响应一个原始的json,该json没有做任何处理,如html敏感字符转换等
func (c *Context) XML(code int, obj interface{})
func (c *Context) YAML(code int, obj interface{})
func (c *Context) ProtoBuf(code int, obj interface{})
func (c *Context) String(code int, format string, values ...interface{})
其他几种常见的响应格式
func (c *Context) Redirect(code int, location string)
重定向
func (c *Context) Data(code int, contentType string, data []byte)
直接操作数据进行响应
func (c *Context) DataFromReader(code int, contentLength int64, contentType string, reader io.Reader, extraHeaders map[string]string)
从render中获取内容写入body进行响应
func (c *Context) File(filepath string)
响应一个文件
func (c *Context) FileAttachment(filepath, filename string)
响应一个文件并重命名该文件
func (c *Context) Stream(step func(w io.Writer) bool) bool
响应一个文件流,客户端中断会返回false
其实这个上下文和传统的web框架上下文是一样的。。。然后。。因为回档了也不知道应该小结什么了,而且现在好冷的,脑袋和手冻僵了,偷个懒这次不小结了。下一篇使用的时候再说吧
本文为龚学鹏原创文章,转载无需和我联系,但请注明来自龚学鹏博客http://www.noobcoder.cn
最新评论