应在校验 content-type 时使用 mime.parsemediatype 解析媒体类型,仅允许 application/json、application/x-www-form-urlencoded 和 multipart/form-data;必须在任何 body 读取操作(如 c.bind()、c.formvalue())前完成校验,避免时机错位。

中间件里怎么读取并校验 Content-Type 头
Echo 的中间件中不能直接用 c.Request().Header.Get("Content-Type") 就完事——因为如果请求体是 application/json 或 application/x-www-form-urlencoded,Echo 可能已在后续 handler 中触发了自动绑定(如 c.Bind()),此时再读原始 header 虽然可行,但校验时机容易错位。真正安全的检查点是在解析前、且确保未触发任何 body 读取操作。
正确做法是:在中间件中调用 c.Request().Header.Get("Content-Type"),然后手动解析 media type,忽略参数(如 charset=utf-8):
func contentTypeCheck(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
ct := c.Request().Header.Get("Content-Type")
mediaType, _, _ := mime.ParseMediaType(ct)
switch mediaType {
case "application/json", "application/x-www-form-urlencoded", "multipart/form-data":
return next(c)
default:
return echo.NewHTTPError(http.StatusBadRequest, "unsupported Content-Type")
}
}
}
注意:mime.ParseMediaType 是标准库函数,能正确剥离 ; charset=utf-8 等参数,比字符串 strings.HasPrefix 更可靠。
为什么不能只用 strings.Contains(ct, "json")
这种写法看似简单,但会误判或漏判:
-
ct = "text/json"→ 匹配成功,但不是合法 JSON 类型 -
ct = "application/json;charset=utf-8"→ 如果没做 trim 或解析,可能因空格或大小写失败 -
ct = "application/vnd.api+json"→ 符合 JSON API 规范,但被粗暴过滤
更关键的是,Content-Type 的语义由 media type 主类型(type/subtype)决定,参数只是修饰。用 mime.ParseMediaType 提取主类型才是符合 HTTP/1.1 RFC 7231 的做法。
POST 表单和 JSON 混用时的陷阱
当路由同时支持 application/x-www-form-urlencoded 和 application/json,但业务逻辑不同(比如前者走表单验证,后者走结构体绑定),仅靠中间件检查 Content-Type 不够——你还得防止用户伪造 header 后发错格式数据。
Echo框架 5.1.0 版本源码包下载,适合关注 RealIP 行为变化、StartConfig.Listener、NewDefaultFS 和观测性中间件入口的开发团队。
建议组合使用:
- 中间件做
Content-Type白名单拦截 - 后续 handler 中,对
application/json显式调用c.Bind(&v);对application/x-www-form-urlencoded用c.FormValue()或c.Bind()(Echo 默认支持) - 避免在中间件里提前调用
c.Request().Body或c.Bind(),否则 body 会被消耗,后续 handler 读不到
另外,multipart/form-data 通常伴随文件上传,它本身不携带 JSON 数据,若业务允许混合字段,需额外用 c.MultipartForm() 分开处理,不能指望 Bind() 自动解析。
要不要在中间件里强制设置 Content-Type 响应头
不需要。Echo 默认响应头不含 Content-Type,除非你显式调用 c.JSON()、c.String() 等方法,它们内部会设。中间件只负责入向校验,出向头应由具体 handler 控制。
唯一例外是统一错误响应:如果你在中间件里返回 echo.NewHTTPError,它默认用 text/plain。若想统一为 JSON 错误格式,可自定义 HTTP 错误处理器:
e.HTTPErrorHandler = func(err error, c echo.Context) {
if he, ok := err.(*echo.HTTPError); ok {
c.Response().Header().Set("Content-Type", "application/json; charset=UTF-8")
c.JSON(he.Code, map[string]string{"error": he.Message})
return
}
c.String(http.StatusInternalServerError, err.Error())
}
这个配置要放在所有中间件注册之后、启动服务器之前。
真正容易被忽略的是:Content-Type 校验必须在任何可能触发 body 解析的操作之前完成,包括 c.FormValue()、c.MultipartForm()、c.Bind() —— 它们都会隐式读取并缓冲 request body,一旦 body 被读过,再检查 header 就失去了“前置防护”的意义。










