Gin源码分析 – Context功能概述

1 介绍

从前面的介绍可以了解到整个Gin由EngineRouteContext等几个主要组成。Context是Gin框架中非常重要的一点,它允许我们在中间件间共享变量管理整个流程获取请求参数渲染结果,通常情况下我们的业务逻辑处理也是在整个Context引用对象中进行实现的。Context的代码在context.go中实现。本文对Context的主要数据结构,主要的提供的方法进行概要的功能性介绍,后续章节则对每一部分进行详细的分析。

2 结构体说明

该Context主要实现了对Request和Response的封装以及一些参数的传递。其数据结构如下:

// 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.
// Context是gin中最重要的部分。它允许我们在中间件中共享变量,管理流程,对请求中的JSON进行有效性检查并且渲染一个JSON的响应。
type Context struct {
    writermem responseWriter // 响应处理
    Request   *http.Request  // 请求信息
    Writer    ResponseWriter // 响应处理接口

    Params   Params          // URL参数
    handlers HandlersChain   // 请求处理列表 
    index    int8            // 用于对中间件进行流程控制
    fullPath string          // http请求的全路径地址

    engine       *Engine     // gin框架的Engine结构体指针
    params       *Params
    skippedNodes *[]skippedNode

    // This mutex protect Keys map
    // 对下面的Keys字段进行写保护的锁
    mu sync.RWMutex

    // 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()
    // 管理解析后的Query参数
    queryCache url.Values

    // formCache use url.ParseQuery cached PostForm contains the parsed form data from POST, PATCH,
    // or PUT body parameters.
    // 管理解析后的Form参数
    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
}

3 错误处理

  • func(c *Context)Error(err error)*Error,将错误添加到上下文中

4 元数据管理

主要通过Keys map[string]interface{},在各个中间件中共享变量,提供的方法两类,Set和Get方法,只不过Get有很多变种,方便获取某种类型的数据。

函数

功能

Set(key string, value interface{})

给context设置一个新的键值对

Get(key string) (value interface{}, exists bool)

返回指定的key的值,以及是否存在

MustGet(key string) interface{}

返回指定key的值,不存在则panic

GetString(key string) (s string)

返回string类型

GetBool(key string) (b bool)

返回bool类型

GetInt(key string) (i int)

返回int类型

GetStringSlice(key string) (ss []string)

返回一个字符串切片

GetStringMap(key string) (sm map[string]interface{})

返回一个map结构,类型为map[string]interface{}

GetStringMapString(key string) (sms map[string]string)

返回一个map结构,类型为map[string]string

GetStringMapStringSlice(key string) (smss map[string][]string)

返回一个map结构,类型为map[string][]string

5 请求处理

5.1 获取参数

Context的主要功能之一就是对各种Request的参数进行解析和处理,当前Context主要支持URL Param、URL Query、PostForm、FormFile和MultipartForm等几类请求参数的类型,提供的主要方法如下表。

(1)URL Param,例如一个Restful API的定义为/user/:name,实际发送的请求是/user/john, 这个请求中name就是Param参数,参数值就是john

(2)URL Query,例如一个Restful API的定义为/getUserInfo?id=1234&age=18,这个请求中 id,age就是Query参数,参数值是1234,18

(3)PostForm,在POST请求的,请求体中发送的请求参数,例如下面是一个JSON格式的PostForm参数。

POST /post HTTP/1.1
Accept: application/json, */*;q=0.5
Accept-Encoding: gzip, deflate
Connection: keep-alive
Content-Length: 18
Content-Type: application/json
Host: pie.dev
User-Agent: HTTPie/3.1.0

{
    "id": "1234",
    "age": "18"
}

(4)FormFile,在POST请求中,主要用于上传文件,在Form中采用的是multipart/form-data编码方式,在下例子中上传了一个文件,参数为filename,值为py1.cfg,文件内容中也包含中请求体中。

POST /post HTTP/1.1
Accept: */*
Accept-Encoding: gzip, deflate
Connection: keep-alive
Content-Length: 437
Content-Type: multipart/form-data; boundary=55315202afc745a99261513f37e7ffa2
Host: pie.dev
User-Agent: HTTPie/3.1.0

--55315202afc745a99261513f37e7ffa2
Content-Disposition: form-data; name="filename"; filename="py1.cfg"

home = d:\python\python37
implementation = CPython
version_info = 3.7.6.final.0
virtualenv = 20.0.7
include-system-site-packages = false
base-prefix = d:\python\python37
base-exec-prefix = d:\python\python37
base-executable = d:\python\python37\python.exe
prompt = (python_study)

--55315202afc745a99261513f37e7ffa2--

(5)MultipartForm,在POST请求中,除了上传文件外,还可以存储其它的参数。

函数

功能

Param(key string) string

返回URL的Param参数值,
(1)路由:uri_patten: “/user/:id”,
(2)请求:url: “/user/john”
(3)使用:c.Param(“id”) = “john”

Query(key string) string

返回URL中的Query参数值
(1)路由:/path?id=1234&name=Manu
(2)使用:c.Query(“id”)=1234,
c.Query(“name”)=Manu

DefaultQuery(key, defaultValue string) string

返回URL中的查询参数值,但是提供了一个默认值

QueryArray(key string) []string

返回指定key的对应的数组切片

GetQueryArray(key string) ([]string, bool)

同上,会返回状态

QueryMap(key string) map[string]string

返回指定key对应map类型

GetQueryMap(key string) (map[string]string, bool)

同上,会返回状态

PostForm(key string) string

该方法返回一个从POST 请求的urlencode表单或者multipart表单数据,不存在时返回空字符串

DefaultPostForm(key, defaultValue string) string

同上,key不存在时返回默认值

GetPostForm(key string) (string, bool)

同PostForm()方法,并且会返回状态

PostFormArray(key string) []string

该方法返回指定key的字符串类型的slice

GetPostFormArray(key string) ([]string, bool)

同上,并返回状态

PostFormMap(key string) map[string]string

返回指定key的map类型

GetPostFormMap(key string) (map[string]string, bool)

同上,并返回状态

FormFile(name string) (*multipart.FileHeader, error)

返回指定key的第一个文件(用作文件上传)

MultipartForm() (*multipart.Form, error)

该方法解析multipart表单,包含file文件上传

SaveUploadedFile(file *multipart.FileHeader, dst string) error

该方法用来上传指定的文件头到目标路径(dst)

5.2 Bind函数

此组函数也是用于处理请求参数的,不过封装得更好,一方面能够直接将请求参数映射到一个结构体类型中,同时可以进行合法性检查,内置的有json, xml, protobuf, form, query, yaml。这些Bind极大的减少我们自己去解析各种个样的数据格式, 提高我们的开发速度,Bind的实现都在gin/binding里面。

  • 这些方法底层使用MustBindWith
  • 如果存在绑定错误,请求将被以下指令中止 c.AbortWithError(400, err).SetType(ErrorTypeBind),响应状态代码会被设置为400,请求头Content-Type被设置为text/plain; charset=utf-8;
  • 注意,如果你试图在此之后设置响应代码,将会发出一个警告 [GIN-debug] [WARNING] Headers were already written. Wanted to override status code 400 with 422,如果你希望更好地控制行为,请使用ShouldBind相关的方法。

函数

功能

Bind(obj interface{}) error

自动解析Content-Type并绑定到指定的binding引擎

BindJSON(obj interface{}) error

支持MIME为application/json的解析

BindXML(obj interface{}) error

支持MIME为application/xml的解析

BindQuery(obj interface{}) error

只支持QueryString的解析, 和Query()函数一样

BindYAML(obj interface{}) error

支持MIME为application/x-yaml的解析

BindHeader(obj interface{}) error

BindUri(obj interface{}) error

只支持路由变量的解析

MustBindWith(obj interface{}, b binding.Binding) error

使用指定的binding引擎来绑定传递的结构体指针(当有任何错误时,终止请求并返回400)

5.3 ShouldBind函数

类似于Bind函数,Context还提供了一组ShouldBind函数,这些方法底层使用ShouldBindWith,如果存在绑定错误,则返回错误,开发人员可以正确处理请求和错误。

6 Header处理

函数

功能

Status(code int)

设置响应码

Header(key, value string)

(1)如果value不为空,则在响应体重写入一个header;
(2)如果value为空,则删除响应体的中的header。

GetHeader(key string) string

返回请求体中的heade

7 Cookie处理

  • SetCookie(name, value string, maxAge int, path, domain string, secure, httpOnly bool): 该方法将设置一个Set-Cookie到响应头中;
  • Cookie(name string) (string, error): 返回名称为name的cookie。