应先检查content-type是否为application/json且有body,再用jsoniter.valid预验证语法,避免调用shouldbindjson导致body不可重读;对options、get等方法跳过校验,并处理空/空白body及cors预检。

中间件里怎么判断请求不是 JSON 格式
关键不是“解析失败才拦截”,而是**在解码前就根据 Content-Type 和 body 特征快速识别非法输入**。Gin 的 c.ShouldBindJSON() 是懒加载式校验,一旦调用就会触发 json.Unmarshal,遇到单引号、尾逗号等直接 panic 或返回错误,但此时 body 已被读取一次,再调用 c.GetRawData() 会返回空或报 i/o read failure。
正确做法是在中间件中先检查 c.GetHeader("Content-Type") 是否包含 application/json,再配合 c.Request.ContentLength > 0 判断是否有体;若不匹配,立刻 c.AbortWithStatusJSON(400, gin.H{"code": 1002, "msg": "Content-Type must be application/json"}),不走任何绑定逻辑。
- 只认
application/json,不接受application/json;charset=utf-8等变体(除非手动strings.Contains宽松匹配) - 对
GET、HEAD等无 body 方法跳过检查,避免误判 - 若服务需兼容
text/plain伪 JSON(如某些 IoT 设备),需额外加白名单逻辑,不能依赖类型判断
为什么不能在中间件里调用 c.ShouldBindJSON()
因为 c.ShouldBindJSON() 内部会调用 c.GetRawData() → io.ReadAll(c.Request.Body),而 http.Request.Body 是单次读取流,一旦读完就无法重放。后续 handler 若还想解析(比如 fallback 到 jsoniter),会拿到空字节或报错 http: invalid Read on closed Body。
更糟的是:如果多个中间件都尝试 ShouldBindJSON(比如鉴权中间件也想读 body),第二个必然失败——这不是 Gin bug,是 HTTP/1.1 底层限制。
将一个或多个视频上传至YouTube。当用户想“上传到YouTube”“发YouTube”“批量上传”“upload to YouTube”“post videos to YouTube”或发布到YouTube时使用此技能。
- 中间件中绝不要调用任何 Bind 方法(
ShouldBindJSON/BindJSON/Bind) - 需要原始 body 时,统一用
c.GetRawData()一次,结果存到c.Set("raw_body", data)供后续共享 - 若必须做 JSON 校验,用
jsoniter.ConfigCompatibleWithStandardLibrary.Valid(data)替代Unmarshal,它不分配结构体、不报错、仅验证语法
Content-Type 正确但 body 非法时的兜底策略
即使 header 合规,body 仍可能含单引号、NaN、尾逗号。此时靠中间件提前拦截已不够,得在业务 handler 前统一做“柔性解析”。
推荐在路由注册前插入一个解析中间件,用 jsoniter 尝试解码,并把结果存在 context 中:
func JSONParser() gin.HandlerFunc {
return func(c *gin.Context) {
if c.Request.Method == http.MethodGet || c.Request.Method == http.MethodHead {
c.Next()
return
}
if !strings.HasPrefix(c.GetHeader("Content-Type"), "application/json") {
c.AbortWithStatusJSON(400, gin.H{"code": 1002, "msg": "invalid Content-Type"})
return
}
data, err := c.GetRawData()
if err != nil {
c.AbortWithStatusJSON(400, gin.H{"code": 1003, "msg": "read body failed"})
return
}
var dummy map[string]interface{}
if err = jsoniter.ConfigCompatibleWithStandardLibrary.Unmarshal(data, &dummy); err != nil {
c.AbortWithStatusJSON(400, gin.H{"code": 1004, "msg": "invalid JSON format"})
return
}
c.Set("parsed_body", data) // 原始字节,供后续 handler 直接用 jsoniter.Unmarshal
c.Next()
}
}
- 这个中间件不替换
c.Request.Body,避免影响其他中间件读取 - 错误码
1004明确区分“格式错误”和“类型错误”,方便前端分类处理 - 若业务 handler 需要结构体,直接用
jsoniter.Unmarshal(c.MustGet("parsed_body").([]byte), &v),比反复解析快
容易被忽略的边界:空 body 和 whitespace-only body
很多测试工具或前端 SDK 在 POST 时发空 body 或只发空格、换行,jsoniter.Valid() 会返回 false,但 json.Unmarshal 对空字节返回 io.EOF,对纯空白返回 invalid character ... —— 这两种情况都应归为客户端错误,而非服务端 panic。
- 用
bytes.TrimSpace(data)判空,空或全空白时直接AbortWithStatusJSON(400, ...) - 不要依赖
len(data) == 0,因为 gzip 编码或代理转发可能导致 body 被缓冲但未解压 - 若允许空 body 表示“默认参数”,需显式约定并文档化,中间件里不自动 fallback
OPTIONS 预检请求的绕过——它带 Content-Type: application/json 但 body 为空,必须在中间件开头用 if c.Request.Method == "OPTIONS" { c.Next(); return } 放行,否则所有 CORS 请求都会被拦。










