目录
- 1.context creation
- 2.flow control-流控制
- 3.error management
- 4.metadata management
- 5.input data
- 6.response rendring
- 7.context.Context()的实现
gin的context封装了request和response,gin框架在处理具体的请求时,也都是以context作为载体,gin的context的覆盖了很多功能,在gin的源码中其实已经很简单明了了,接下来讲分享个人对gin.context的一些理解。
首先看看gin中对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
params *Params
skippedNodes *[]skippedNode
// This mutex protects Keys map.
mu sync.RWMutex
// Keys is a key/value pair exclusively for the context of each request.
Keys map[string]any
// 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 caches the query result from c.Request.URL.Query().
queryCache url.Values
// formCache caches c.Request.PostForm, which contains the parsed form data from POST, PATCH,
// or PUT body parameters.
formCache url.Values
// SameSite allows a server to define a cookie attribute making it impossible for
// the browser to send this cookie along with cross-site requests.
sameSite http.SameSite
}
1.context creation
顾名思义,就是与context的创建复制相关的方法。
Context.reset()
此方法用来重置context,具体来说就是将Writer/cache/Keys/handlers全部重置,具体主要用在2各方面,一是test用,二是处理具体请求(如Engine.ServeHTTP(),每次处理新请求,pool中取context,重置,使用,再放回)。
Context.Copy()
新建一个context,原有context内的数据复制一份到新context中。在gin中,如果需要重开goroutine时,官方建议使用此方法,借此传递Context。
Context.HandlerName()
用以返回当前context的handler名,是handlers的最后一个,因为按照gin的路由机制,每个路由下对应的handlers其实就是一个handler的切片,其中最后一个是真正请求处理器,其余都看作中间件。
Context.HandlerNames()/Handler()
前者返回当前的handlers的切片,后者返回处理器函数。
总体来说,这块的功能相对简单,也很好理解,大家稍微看看即懂。
2.flow control-流控制
Context.Next()
该方法仅用在中间件中,在正常的业务逻辑中不用,可以看看源码:
func (c *Context) Next() {
c.index++
for c.index < int8(len(c.handlers)) {
c.handlers[c.index](c)
c.index++
}
}
我们知道c.handlers实际是HandlerChain切片,就是每个路由对应的处理器组,所以就是遍历所有的handlers函数,挨个处理。
Context.Abort()
此方法相当于阻断当前数据流,不再向下传递请求,比如我们可以在认证的中间件中使用,如果需要认证的请求但未认证,我们可以在此使用,不再传递,提前返回,或者是在业务逻辑中,dao层处理返回某些错误,提前返回。
Context.AbortWithStatus()/AbortWithStatusJSON()/AbortWithError()
此类方法即相当于Abort()时,加入code或者是正确错误返回,c.Writer中加入相关响应。
3.error management
直接上段源码看看:
func (c *Context) Error(err error) *Error {
if err == nil {
panic("err is nil")
}
var parsedError *Error
ok := errors.As(err, &parsedError)
if !ok {
parsedError = &Error{
Err: err,
Type: ErrorTypePrivate,
}
}
c.Errors = append(c.Errors, parsedError)
return parsedError
}
gin本身有这个初衷很好,看注释官方是想着,这样的应用场景,用一个中间件收集error,遇到error就放到c.Errors切片中,最后可以通过打印log或者记录到数据库中,实现error的记录。
不过我在自己的项目中并没有用到这个方法。
4.metadata management
从context结构体的定义可以看到,Context.Keys其实定义了一个map结构,很多时候我们可以直接用到这个属性,比如控制器函数的前置后置处理,这个使用gin为我们提供了相应的Get()/Set()方法。
Context.Set()
// Set is used to store a new key/value pair exclusively for this context.
// It also lazy initializes c.Keys if it was not used previously.
func (c *Context) Set(key string, value any) {
c.mu.Lock()
if c.Keys == nil {
c.Keys = make(map[string]any)
}
c.Keys[key] = value
c.mu.Unlock()
}
考虑到并发,gin为我们实现了并发安全地设置map,也不限type,只要key是string即可。
Context.Get()
// Get returns the value for the given key, ie: (value, true).
// If the value does not exist it returns (nil, false)
func (c *Context) Get(key string) (value any, exists bool) {
c.mu.RLock()
value, exists = c.Keys[key]
c.mu.RUnlock()
return
}
获取context中设置的内容也很简单,该方法返回两个值,一个是具体的value,一个是是否存在的bool值,具体的应用就看业务逻辑怎样用了。
除了Get()这个方法外,gin还贴心地提供了n多Getxxx()方法,比如不存在就panic的MustGet(),根据value的type的GetString/GetInt/GetBool/GetTime/GetStringSlice等,基本都用到Get()方法,然后在做个类型断言。
举个例子,比如我们调用了c.Set("key-xxx", []string{""}),然后调用c.GetStringSlice("key-xxx"):
// GetStringSlice returns the value associated with the key as a slice of strings.
func (c *Context) GetStringSlice(key string) (ss []string) {
if val, ok := c.Get(key); ok && val != nil {
ss, _ = val.([]string)
}
return
}
5.input data
gin框架设计的api是Restful风格的,我们拿到请求的传入参数不外乎这样几种途径:
- query参数
- form表单
- 路由的可变参数,或者是动态路由
- json/yaml/toml/xml格式的request body
动态路由参数
比如我们注册了这样一个路由:router.Get("/app/v1/user/:userId", xxx),其中userId这个参数就是一个动态的url参数,我们此时就可以使用c.Param("userId")获取对应参数,默认""。
query参数
看看源码的具体实现:
// Query returns the keyed url query value if it exists,
// otherwise it returns an empty string `("")`.
// It is shortcut for `c.Request.URL.Query().Get(key)`
// GET /path?id=1234&name=Manu&value=
// c.Query("id") == "1234"
// c.Query("name") == "Manu"
// c.Query("value") == ""
// c.Query("wtf") == ""
func (c *Context) Query(key string) (value string) {
value, _ = c.GetQuery(key)
return
}
可以看到,直接调用c.Query("key-name")即可,是不是很简便呢?
当然gin也提供了带有默认值的DefaultQuery(key, defaultValue string) string以及带有判断的GetQuery(key string) (string, bool),如果query参数的array或者map,也都不在话下,因为gin提供了QueryArray、QueryMap等。
form表单
gin在表单参数方面也提供了类似query参数提取的方法:
- PostForm(key string) (value string),根据可以提取val,限定string类型,默认没有即返回“”
- DefaultPostForm(key, defaultValue string) string,没有即返回default值
- GetPostForm(key string) (string, bool),带判断的返回
- PostFormArray(key string) (values []string)/PostFormMap,解析array/map类型的form参数
或者当涉及到文件上传时,gin提供了FormFile(name string) (*multipart.FileHeader, error),gin的context甚至还提供了文件上传的实现-SaveUploadedFile(file *multipart.FileHeader, dst string) error,比如我们通过FormFile()拿到fh,然后调用SaveUploadedFile()即可实现文件的上传。
json/yaml/toml/xml格式的request body
我们日常的api开发中最常用的就是通过json格式的数据作为body的传递格式,这里以json格式的body为例说明。
通常在请求体中的首部header中,Content-Type:application/json,这是较为常见的。问题来了,我们在具体的业务逻辑中又怎么获取到这些body呢。
gin的context为我们提供一系列的BindXXX方法:
- Bind()/BindJSON()/BindXML()/BindYAML()
- MustBindWith(obj any, b binding.Binding) error
- ShouldBind()/ShouldBindJSON()...
- ShouldBindWith()/ShouldBindBodyWith()
这是最基础的两个Bindxxx方法,根据官方的注解,如果追求性能,就用ShouldBindWith(),如果多次解析body,考虑重用,就用ShouldBindBodyWith()。其中ShouldBind()实际调用ShouldBindWith(),ShouldBindJSON()只是指明binding.type,MustBindWith()的解析则带有异常解析提前返回的处理,实际也是调用ShouldBindWith(),此外所有的Bindxxx()都是MustBindWith()加对应type的解析。
建议:如果项目中的context涉及重用context,就用ShouldBindBodyWith(),否则用ShouldBindWith()。
其他
- ClientIP() string,解析客户端IP
- RemoteIP() string,通过Request.RemoteAddr解析
6.response rendring
这里涉及的功能都是为了写入响应,比如写入StatusCode,Header,或者是通过HTML/String/Json等格式写到响应体中。
Header/GetHeader
Header()是写入到响应首部,对应签名:Header(key, value string) {}。
GetHeader()则是从请求中获取请求首部的信息,对应签名:GetHeader(key string) string {}。
Cookie/SetCookie
Cookie()是为了获取cookie,SetCookie则是设置cookie,具体这里详细叙述,有兴趣可自行搜索。
JSON/XML/HTML/String
这类的方法都是Render功能,只是序列化格式差异,比如我们在api中可以直接:
ctx.JSON(200, gin.H{})
ctx.String(200, gin.H{})
使用也比较简单,就是不同序列化格式而已,具体使用需要和前端同事协定好格式。
7.context.Context()的实现
gin.Context也实现了标准库的context.Context()接口,但一般没见很多应用,我的项目中也没有用到。
总结:
gin.ConText主要为我们实现了request/response的封装,最典型的就是请求的解析,响应的写入,前置后置处理的map写入,方法很全,实际用的时候根据自己项目实际情况而定。
标签:context,Context,value,源码,key,gin,string From: https://www.cnblogs.com/davis12/p/16992333.html