【Gin】源码阅读:传统的上下文Context

其实这一篇应该在golang的context包之前更新的。但是当我看见gin中有context,而golang自己也自带context的时候,下意识的感觉这两个context肯定有什么关联!

之后在看golang的context包后,还是很别扭的,因为这个包似乎更偏向于运行控制而不是一次请求的上下文。之后翻看了下gin的源码才发现,原来真是我想多了。。。。。。gin框架中的context就是很传统的一次请求的上下文嘛,是用来保存request和response信息的。

好了闲话不多说,开始阅读源码吧!

Context

首先看看最重要的,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
}
  1. context的作用是在中间件之间传递变量,管理数据流,验证和输出数据的(和其他语言中的web框架中的上下文是一样的)
  2. writermem:gin内部使用的输出实例
  3. Writer:gin对外暴露的输出实例
  4. 在gin内部中的关系是writermem中包含一个成员Writer
  5. 也就是说gin对外暴露了一个响应实例,但是并未向外暴露响应配置的设置,算是对响应实例做了一层封装
  6. Request:请求实例
  7. Params:路由中的参数
  8. handlers:回调函数切片,在上一篇gin源码中有说过HandlersChain,里面包含了一系列请求回调,其中最后一个是真正的请求回调,其他的都是中间件
  9. index:用来记录这次的请求经过的中间件索引和是否取消响应(请求中间件验证出错)
  10. 已取消的值为:const abortIndex int8 = math.MaxInt8 / 2
  11. 每通过一层中间件 c.index++
  12. fullPath:完整的请求url
  13. engine:注入的引擎实例(指针,也就是单例化)
  14. Keys:上下文中保存的变量
  15. Errors:所有中间件的错误切片
  16. Accepted:可接收的请求方式列表
  17. queryCache:url请求参数,为了防止多次解析url,在这里做了个冗余
  18. formCache:表单的参数

Context是怎么运作的

在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)
}

Context.methods

Context实体相关
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 错误

  1. func (c *Context) GetString(key string)
  2. func (c *Context) GetBool(key string) (b bool)
  3. func (c *Context) GetInt(key string) (i int)
  4. func (c *Context) GetInt64(key string) (i64 int64)
  5. func (c *Context) GetFloat64(key string) (f64 float64)
  6. func (c *Context) GetTime(key string) (t time.Time)
  7. func (c *Context) GetDuration(key string) (d time.Duration)
  8. func (c *Context) GetStringSlice(key string) (ss []string)
  9. func (c *Context) GetStringMap(key string) (sm map[string]interface{})
  10. func (c *Context) GetStringMapString(key string) (sms map[string]string)
  11. 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)

在刚开始看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框架上下文是一样的。。。然后。。因为回档了也不知道应该小结什么了,而且现在好冷的,脑袋和手冻僵了,偷个懒这次不小结了。下一篇使用的时候再说吧

程序幼儿员-龚学鹏
请先登录后发表评论
  • latest comments
  • 总共0条评论