Context 是 gin 中最重要的部分。
一、数据结构
type Context struct {
writermem responseWriter //相应处理
Request *http.Request //请求报文的抽象
Writer ResponseWriter //响应报文的抽象
Params Params //记录了url参数,是一组键值对
handlers HandlersChain //定义一个 HandlerFunc 数组
index int8 //定义一个HandlerFunc数组的索引。next() / abort() 会用到
fullPath string //请求地址
engine *Engine //gin 框架实例对象,gin.New()返回的就是一个 *Engine 实例
params *Params //URL 参数的切片
skippedNodes *[]skippedNode
mu sync.RWMutex //读写锁,用来保护Keys
Keys map[string]any //是专门针对每个请求的上下文的键/值对
Errors errorMsgs //使用此上下文的所有处理程序或者中间件附带的错误列表
Accepted []string //自定义请求接收的内容类型格式
queryCache url.Values //底层数据类型是 map[string][]string 。使用url.ParseQuery()从c.Request.URL.Query()缓存了参数查询结果
formCache url.Values //使用url.ParseQuery缓存的PostForm包含来自POST,PATCH或者PUT请求参数
sameSite http.SameSite //允许服务器定义cookie属性。用来防止 CSRF 攻击和用户追踪
}
二、元数据管理
这类的数据管理专门用于为此上下文存储新的键值对。存储在Context中的Keys数据字段中,如果以前没有使用过,它会延迟初始化。
提供两个基本函数Get()、Set()完成对Keys的读写操作,都有读写锁以实现并发安全。
1. Set()
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()
}
2. Get()
// 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
}
3. 基于Get()的不同特定数据类型函数
// MustGet(key string) interface{} 返回给定键的值(如果存在),否则会panic。
// GetString(key string) (s string)
// GetBool(key string) (b bool)
// GetInt(key string) (i int)
// GetInt64(key string) (i64 int64)
// GetUint(key string) (ui uint)
// GetUint64(key string) (ui64 uint64)
// GetFloat64(key string) (f64 float64)
// GetTime(key string) (t time.Time)
// GetDuration(key string) (d time.Duration)
// GetStringSlice(key string) (ss []string)
// GetStringMap(key string) (sm map[string]interface{})
// GetStringMapString(key string) (sms map[string]string)
// GetStringMapStringSlice(key string) (smss map[string][]string)
//举例
func (c *Context) GetInt64(key string) (i64 int64) {
if val, ok := c.Get(key); ok && val != nil {
i64, _ = val.(int64) //通过添加类型断言来完成指定想要的值
}
return
}
三、获取请求数据
1. Param 数据
-
gin 源码
func (c *Context) Param(key string) string {
return c.Params.ByName(key)
}
当路由是
router.GET("/user/:id", func(c *gin.Context) {})
这种形式时,可以通过 id := c.Param("id")从 Context 对象的Params字段来获取到id的参数值。Params保存了若干 key-value 对。其中
- key 是一条 route 的 url 所设定的
:后的单词,本例中就是id - value 是实际传来的请求 url 的实际数据。下例为
5
GETlocalhost:3000/user/5
-
实例
- 实例代码
engine.GET("/test/:name/:age/:height", func(c *gin.Context) {
// 接收参数
`name := c.Param("name")`
`age := c.Param("age")`
`height := c.Param("height")`
c.JSON(200, gin.H{
"msg": "success",
"name": name,
"phone":age,
"height":height,
})
})
- 响应
➜ curl -X GET http://127.0.0.1:8080/test/张三/18/170
{"height":"170","msg":"success","name":"张三","phone":"18"}
2. Get 数据
(1) 接收单值
在
Gin框架中,Get请求的参数都保存在Context结构体中的queryCache字段中;
在Gin框架中可以通过Query、DefaultQuery、GetQuery来获取Get参数信息,而Query、DefaultQuery是对GetQuery的二次封装, 而GetQuery的底层最终调用了GetQueryArray。
-
gin源码
// 两个返回值的使用示例:
// GET /?name=Manu&lastname=
// ("Manu", true) == c.GetQuery("name")
// ("", false) == c.GetQuery("id")
// ("", true) == c.GetQuery("lastname")
底层调用 `GetQueryArray()`,返回 Array 的第[0]个值
func (c *Context) GetQuery(key string) (string, bool) {
if values, ok := c.GetQueryArray(key); ok {
return values[0], ok
}
return "", false
}
// 根据所得的 query key 返回一个string slice
底层调用 `initQueryCache()`
func (c *Context) GetQueryArray(key string) (values []string, ok bool) {
c.initQueryCache()
values, ok = c.queryCache[key]
return
}
//将本次 context 中的请求报文的 url 数据赋给 queryCache 字段,完成初始化
func (c *Context) initQueryCache() {
if c.queryCache == nil {
if c.Request != nil {
c.queryCache = c.Request.URL.Query()
} else {
c.queryCache = url.Values{}
}
}
}
-
实例
- 实例代码
func testReceiveGetParam( engine *gin.Engine) {
engine.GET("/receive", func(c *gin.Context) {
// 如果不存在或为空,则返回:""
`name := c.Query("name")`
// 如果不存在或为空,则返回默认值
`age := c.DefaultQuery("age","18")`
// 直接使用GetQuery
`home, ok := c.GetQuery("home")`
c.PureJSON(200,gin.H{
"msg":"success",
"c.Query->name":name,
"c.DefaultQuery->age":age,
"c.GetQuery->home":home,
"c.GetQuery->ok":ok,
})
})
}
- 响应
➜ curl -X GET http://127.0.0.1:8080/receive?age=23&home=北京&name=小明
{"c.DefaultQuery->age":"23","c.GetQuery->ok":true,"c.GetQuery->home":"北京","c.Query->name":"小明","msg":"success"}
(2)接收数组
在
Gin框架中可以通过
QueryArray("param[]")和GetQueryArray("param[]")
获取GET方式提交中的数组值信息,而QueryArray是对GetQueryArray二次封装
-
gin 源码
func (c *Context) QueryArray(key string) (values []string) {
values, _ = c.GetQueryArray(key)
return
}
-
实例
- 实例代码
//------ API 层 -------
// 接收数组
func TestReceiveGetArrayParam(engine *gin.Engine) {
engine.GET("/getArr", func(context *gin.Context) {
// 接收GET数组:/getArr?name[]=张三&name[]=李四
`nameList := context.QueryArray("name[]")`
context.JSON(200,gin.H{
"arr": nameList,
})
})
}
- 响应
➜ curl -X GET http://127.0.0.1:8080/getArr?name[]=张三&name[]=李四
{ name:[张三,李四] }
(3)接收Map
在
Gin框架中可以通过
QueryMap("param")和GetQueryMap("param")
获取GET方式提交中的map值信息,而QueryMap是对GetQueryMap二次封装
-
gin 源码
// QueryMap returns a map for a given query key.
func (c *Context) QueryMap(key string) (dicts map[string]string) {
dicts, _ = c.GetQueryMap(key)
return
}
// GetQueryMap returns a map for a given query key, plus a boolean value
// whether at least one value exists for the given key.
func (c *Context) GetQueryMap(key string) (map[string]string, bool) {
c.initQueryCache()
return c.get(c.queryCache, key)
}
-
实例
- 实例代码
// 接收map
func TestRecGetMapParam(engine *gin.Engine) {
engine.GET("/getMap", func(context *gin.Context) {
//接收GET map:/getMap?score[语文]=95&score[数学]=100
`queryMap := context.QueryMap("score")`
context.JSON(200,gin.H{
"map":queryMap,
})
})
}
- 响应
➜ curl -X GET http://127.0.0.1:8080/getMap?score[语文]=95&score[数学]=100
{"map":{{语文:95},{数学:100}}}
3. POST 数据
在
Gin框架中,POST请求的参数都保存在Context结构体中的formCache字段中。
在Gin框架中可以通过PostForm、DefaultPostForm、GetPostForm来获取Post提交的参数信息,而PostForm、DefaultPostForm同样是对GetPostForm的二次封装。
-
gin 源码
//获取表单
调用`GetPostFormArray`,获取Array[0]
func (c *Context) GetPostForm(key string) (string, bool) {
if values, ok := c.GetPostFormArray(key); ok {
return values[0], ok
}
return "", false
}
//获取表单数组
调用`initFormCache`
func (c *Context) GetPostFormArray(key string) (values []string, ok bool) {
c.initFormCache()
values, ok = c.formCache[key]
return
}
// 初始化 formCache
func (c *Context) initFormCache() {
if c.formCache == nil {
c.formCache = make(url.Values)
req := c.Request
if err := req.ParseMultipartForm(c.engine.MaxMultipartMemory); err != nil {
if !errors.Is(err, http.ErrNotMultipart) {
debugPrint("error on parse multipart form array: %v", err)
}
}
c.formCache = req.PostForm
}
}
-
实例
- 实例代码
//传单个表单
func TestRecPostSingleValue(engine *gin.Engine) {
engine.POST("/postSingle", func(context *gin.Context) {
name := context.PostForm("name")
age := context.DefaultQuery("age", "22")
home, ok := context.GetPostForm("home") `
context.JSON(200, gin.H{
"postForm": name,
"DefaultQuery": age,
"GetPostForm.home": home,
"GetPostForm.ok": ok,
})
})
}
- 响应
# 不传任何参数时,看接收情况
➜ curl -X POST http://127.0.0.1:8080/postSingle
{"DefaultQuery":"22","GetPostForm.home":"","GetPostForm.ok":false,"postForm":""}
# 传任何参数时,看接收情况
➜ curl -X POST http://127.0.0.1:8080/postSingle -d "age=40&home=南京&name=张三"
{"DefaultQuery":"22","GetPostForm.home":"南京","GetPostForm.ok":true,"postForm":"张三"}}
- 实例代码
//传表单数组
func TestRecPostArrValue(engine *gin.Engine) {
engine.POST("/postArr", func(context *gin.Context) {
`arr := context.PostFormArray("name")`
context.JSON(200, gin.H{
"postArr": arr,
})
})
}
- 响应
➜ curl -X POST http://127.0.0.1:8080/postArr -d "name[]=张三&name[]=李东"\
{"postArr":["张三","李东"]}
4. Header 数据
主要调用了
Context中的Request来实现获取头部字段,
Context的Request字段实际就是Go 标准库的net/http的Requeststruct
func (c *Context) GetHeader(key string) string {
return c.requestHeader(key)
}
func (c *Context) requestHeader(key string) string {
return c.Request.Header.Get(key)
}
5. File 数据
在
Gin框架中,在处理File请求时,分为单个文件上传和多个文件上传 ,处理完成后再来实现文件的移动保存。
5-1 单文件上传
gin源码
func (c *Context) FormFile(name string) (*multipart.FileHeader, error) {
if c.Request.MultipartForm == nil {
if err := c.Request.ParseMultipartForm(c.engine.MaxMultipartMemory); err != nil {
return nil, err
}
}
f, fh, err := c.Request.FormFile(name)
if err != nil {
return nil, err
}
f.Close()
return fh, err
}
- 实例
// ------API 层------
func TestRecFile(engine *gin.Engine) {
// 设置内存限制为8M, 默认是32MiB
engine.MaxMultipartMemory = 8 << 20
engine.POST("/file", func(c *gin.Context) {
`file, err := c.FormFile("img")`
if err != nil {
c.JSON(500, gin.H{"err": err})
return
}
// 文件重命名
dst := "./tmp/"+file.Filename
fmt.Println(dst)
// 保存图片
err = context.SaveUploadedFile(file, dst)
if err != nil {
c.JSON(500, gin.H{"err": "文件保存失败: " + err.Error()})
return
}
c.JSON(200, gin.H{
"msg": "success",
"name": file.Filename,
"size": file.Size,
})
})
}
5-2 多文件上传
gin源码
func (c *Context) MultipartForm() (*multipart.Form, error) {
err := c.Request.ParseMultipartForm(c.engine.MaxMultipartMemory)
return c.Request.MultipartForm, err
}
- 实例
//---API 层 -------
// 接收多个文件
func TestRecFiles(engine *gin.Engine) {
// 设置内存限制为8M, 默认是32MiB
engine.MaxMultipartMemory = 8 << 20
engine.POST("/files", func(c *gin.Context) {
// 接收图片
`form, _ := c.MultipartForm()`
files := form.File["imgList[]"]
// 遍历保存
for _, file := range files {
_ = c.SaveUploadedFile(file, "./tmp/"+file.Filename)
}
context.String(200,"保存成功!")
})
}
四、绑定请求数据
在
Gin框架中, 参数不但能通过指定key接收,也可以直接绑定到结构体中
1. 两种绑定方法
Gin提供了Must bind 和 Should bind两种类型的绑定方法,这两种类型对应的方法如下:
| 功能 | Must bind方法 | Should bind方法 |
|---|---|---|
Bind | ShouldBind | |
绑定JSON | BindJSON | ShouldBindJSON |
绑定XML | BindXML | ShouldBindXML |
绑定GET | BindQuery | ShouldBindQuery |
绑定YAML | BindYAML | ShouldBindYAML |
MustBindWith 和 ShouldBindWith
Bind*类型的方法是对MustBindWith封装;如果MustBind绑定发生了错误,则请求终止,并响应400状态码;实际上MustBindWith在底层调用了ShouldBindWith并添加了错误 http 响应。
ShouldBind*类型的方法是对ShouldBindWith的封装。如果ShouldBind发生了绑定错误,Gin会返回错误并由开发者处理错误和请求。
2. 绑定 JSON
使用函数
BindJSON和ShouldBindJSON来绑定提交的JSON参数信息。
- Model 层
- struct 的每个字段的
tag添加json:"jsonName"- API 层
- 定义一个
Struct 实例对象 s- 将
s的引用传入c.ShouldBindJSON(&s), http 请求中的JSON数据将注入到s中
2-1 代码
//------ Model 层 -------
// 定义待绑定的JSON结构体
type Param struct {
Name string `json:"name"`
Age int `json:"age"`
Likes []string `json:"likes"`
}
//------ API 层 -------
func TestBindJson(engine *gin.Engine) {
engine.POST("/bindJson", func(context *gin.Context) {
var jsonParam Param
var err error
bindType := context.Query("type")
fmt.Println(bindType)
if bindType == "1" {
err = context.BindJSON(&jsonParam)
} else {
err = context.ShouldBindJSON(&jsonParam)
}
if err != nil {
context.JSON(500, gin.H{"error": err})
return
}
context.JSON(200, gin.H{"result": jsonParam})
})
}
3. 绑定 URL 参数
通过使用函数
BindQuery和ShouldBindQuery,用来只绑定GET请求中的uri参数,如:/funcName?a=x&b=x中的a和b。
3-1 代码
//------ Model 层 -------
type UriParam struct {
Name string `form:"name" binding:"required"`
Age int `form:"age"`
Home string `form:"home"`
}
//------ API 层 -------
func TestBindQuery(engine *gin.Engine) {
engine.GET("/bindQuery", func(context *gin.Context) {
bindType := context.Query("type")
var uriParam UriParam
var err error
if bindType == "1" {
fmt.Println("BindQuery")
err = context.BindQuery(&uriParam)
} else {
fmt.Println("ShouldBindQuery")
err = context.ShouldBindQuery(&uriParam)
}
if err != nil {
context.JSON(500, gin.H{"error": err.Error()})
return
}
fmt.Printf("uriParam:%+v\n", uriParam)
context.JSON(200, gin.H{"result": uriParam})
})
}
3-2 请求
Bash
# 参数都传
➜ curl -X GET http://127.0.0.1:8080/bindQuery?age=24&name=张三&home=北京&type=1
{"result":{"Name":"张三","Age":24,"Home":"北京"}}
➜ curl -X GET http://127.0.0.1:8080/bindQuery?age=24&name=张三&home=北京&type=2
{"result":{"Name":"张三","Age":24,"Home":"北京"}}
# 必填参数name不填时,都会报错
➜ curl -X GET http://127.0.0.1:8080/bindQuery?age=24&home=北京&type=2
{"error":"Key: 'UriParam.Name' Error:Field validation for 'Name' failed on the 'required' tag"}
五、响应数据
Gin支持这几种渲染:JSON,IndentedJson,SecureJSON,XML,String,Data,Redirect,HTML,HTMLDebug,HTMLProduction,YAML,Reader,ProtoBuf,AsciiJSON
它们都实现了Render接口中的Render;也实现了Header的写入。
// JSON 序列化了传入的 object ,转换为 JSON 格式传到响应体中
// 同时也将 Content-Type 设为 "application/json".
func (c *Context) JSON(code int, obj any) {
c.Render(code, render.JSON{Data: obj})
}
// Render 写入到响应头,并调用 render.Render 来渲染数据.
func (c *Context) Render(code int, r render.Render) {
c.Status(code)
if !bodyAllowedForStatus(code) {
r.WriteContentType(c.Writer)
c.Writer.WriteHeaderNow()
return
}
if err := r.Render(c.Writer); err != nil {
panic(err)
}
}