Go-gin
- gin.Context 的 Get / Set / GetString / GetInt
- 用途:在同一次请求的处理流程中,跨中间件或处理函数传递数据(键值对)。
- Set(key string, value interface{})
- Get(key string) (value interface{}, exists bool)
- 便利方法:GetString(key), GetInt(key), GetBool(key) 等 — 这些从内部类型断言并返回零值或默认。
- 注意:这些键值对仅在当前请求上下文生存周期内有效,且 gin.Context 不是并发安全的(不要在未同步情况下跨 goroutine 读写 Context)。
// language: go
c.Set("userID", 123)
if v, ok := c.Get("userID"); ok {
uid := v.(int)
// use uid
}
uidStr := c.GetString("userID") // 若不是 string,会返回 ""(空串)
- 请求绑定(binding)与 ShouldBindJSON / BindJSON 的差别
- ShouldBindJSON(obj interface{}) error
- 根据 JSON body 将请求体绑定到 obj,并返回 error;不会自动写入响应或结束请求(调用者需自己处理错误)。
- BindJSON(obj interface{}) error
- 和 ShouldBindJSON 功能相同,但当绑定出错时,Gin 会自动写一个 400 响应并结束请求处理(内部会调用 c.ShouldBindWith 并在失败时 AbortWithError)。因此 Bind* 通常用于快速示例,但不够灵活。
- ShouldBind(obj interface{})
- 根据 Content-Type 自动选择绑定器(JSON、form、xml 等)。
- ShouldBindQuery(obj interface{})
- 仅绑定 Query 参数(URL ?a=b&c=d)。
- 关键规则:ShouldBindXxx 返回错误但不写响应;BindXxx 在出错时会自动写 400 并 abort(注意版本差异,建议以 ShouldBindXxx + 自己处理为主)。
常见绑定标签:
- json:"fieldname" — 用于 JSON 编码/解码;
- form:"fieldname" — 用于表单 / query 绑定;
- binding:"required" — 强制字段必须存在(validator.v10 生效);
- 其它 validator 标签:min,max,email,oneof,len,uuid 等。
- c.Get / c.Set:是对 gin.Context 的键值存取,属于“上下文内存”——用于中间件与处理器之间传递数据(例如认证中间件把 userID 放进去)。它不从 HTTP 请求体里解析数据。
- c.ShouldBind / c.ShouldBindJSON / c.ShouldBindQuery / c.ShouldBindUri 等:是“从 HTTP 请求中解析(绑定)数据到结构体”的方法。它们从请求的不同位置(body、query、form、path)读取数据并做可选验证。
两者用途完全不同:一个是上下文内临时存储,一个是把请求数据解析到结构体中
- 文件上传:FormFile / SaveUploadedFile / 流式处理
- c.FormFile("file") (*multipart.FileHeader, error)
- 获取一个上传文件的 FileHeader(单文件)。通常来源于 multipart/form-data 表单。
- c.SaveUploadedFile(fileHeader, dst) error
- 便捷保存文件到目标路径(内部会打开文件并复制)。
- 打开并流式读取:
- fh, _ := c.FormFile("file")
- src, err := fh.Open() // 返回 multipart.File (实现 io.Reader)
- 然后可 stream 到磁盘、云存储、或进行处理。
- MaxMultipartMemory
- Gin 默认 router.MaxMultipartMemory = 32 << 20 // 32 MiB
- 超过此内存限制,文件会被存成临时文件(仍可上传),但为了限制整体接收大小,可以在路由层或 net/http 层使用 http.MaxBytesReader。
// language: go
func uploadHandler(c *gin.Context) {
// 限制请求体大小(保护)
c.Request.Body = http.MaxBytesReader(c.Writer, c.Request.Body, 10<<20) // 10MB
fh, err := c.FormFile("file")
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "file required"})
return
}
// 简单检查 mime (不要完全相信客户端)
if fh.Size > 5<<20 { // 5MB
c.JSON(http.StatusBadRequest, gin.H{"error": "file too large"})
return
}
dst := path.Join("/tmp", fh.Filename)
if err := c.SaveUploadedFile(fh, dst); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"status": "uploaded", "path": dst})
}
注意点:
- FormFile 只是将数据解析为 FileHeader,它本身不检查业务限制(size、type),需要手动检查。
- 文件名不能直接信任:避免目录遍历、使用安全的文件名或随机名。
- 如果想把文件直接上传到云存储(S3/OSS),使用 fh.Open() 取得 io.Reader,然后将数据流式传到云 SDK,避免在本地完整保存
- GetRawData 与 一次性读取 body
- c.GetRawData() 返回请求体的全部字节,但注意:请求体在被读取一次后就被耗尽(io.ReadCloser)。
- 如果你需要在中间件读取 body 并在下游处理器再绑定,需要把 body 重新塞回去:
// language: go
data, _ := c.GetRawData()
c.Request.Body = io.NopCloser(bytes.NewBuffer(data))
// 现在下游可以再读取
- body 被读取一次的问题与恢复
如果你在中间件读取了 body(比如记录日志),下游的 ShouldBindJSON 就会失败(body 已读尽)。恢复方法:
// language: go
data, _ := c.GetRawData()
// 做日志等
c.Request.Body = io.NopCloser(bytes.NewBuffer(data))
// 现在下游可以再次读取或 ShouldBindJSON
- 安全与并发注意点
- gin.Context 不是并发安全的:不要在没有同步的情况下在多个 goroutine 中同时读写 c(例如你若要在 goroutine 中使用 c,请先复制需要的值,或使用 c.Copy())。
- 在接受文件或大请求体时,务必限制大小(http.MaxBytesReader + MaxMultipartMemory)以防 DDOS 或 OOM。
- 不信任客户端的 filename 或 content-type;对文件类型做服务器端检测(如读取文件头魔术字)。
- 验证器注入与用户错误信息不要暴露内部实现细节。
- 绑定优先级与选择
- c.ShouldBind 会根据请求 Content-Type 自动选择 binder(JSON、XML、form、multipart)。
- 如果你确定要解析 JSON,请用 ShouldBindJSON(强制 JSON)。
- 参数来源示例:
- JSON body -> ShouldBindJSON
- Query 参数 -> ShouldBindQuery
- form (application/x-www-form-urlencoded 或 multipart/form-data) -> ShouldBindWith or ShouldBind
- 若在单个 handler 同时想解析多种来源(如 URL param + JSON body),分别调用对应绑定方法。