统一响应中间件必须在ctx.Next()前接管或在ctx.Next()后检查ctx.Response().Written(),因Iris响应流单向不可覆写;handler已写响应时中间件修改无效,需约定handler仅存数据至ctx.Values(),由中间件统一输出。

统一响应中间件在 Iris 中不能靠“自动包装返回值”实现,必须手动在中间件里拦截 ctx.Next() 后的状态与响应体,并用 ctx.ResponseWriter().Write() 或 ctx.JSON() 重写输出;否则 handler 已写入的响应无法被覆盖,中间件只起旁观作用。
为什么 ctx.Next() 后改状态码/内容无效
Iris 的响应流是单向写入的:一旦 handler 调用了 ctx.JSON()、ctx.Text() 或直接写了 ResponseWriter,底层 http.ResponseWriter 的缓冲区就已提交。中间件在 ctx.Next() 后调用 ctx.StatusCode(200) 或 ctx.JSON() 不会覆盖已发出的内容,只会静默失败——前端收到的是 handler 写的原始响应,中间件的修改完全丢失。
常见错误现象:中间件里加了日志和统一格式封装,但 /user 返回 {“name”:”a”},/order 却还是 {“id”:123},结构不一致;查日志发现中间件确实执行了,但没生效。
- 必须在 handler 执行前(即 ctx.Next() 前)决定是否接管响应,或在 ctx.Next() 后检查是否已写响应
- ctx.Response().Written() 返回 true 表示响应已发出,此时不能再调 ctx.JSON()
- 若要强制统一格式,handler 应只返回数据结构(如 map[string]interface{}),不调任何 ctx.XXX 方法
如何安全地封装标准响应结构
推荐 handler 只做业务逻辑,把响应控制权完全交给中间件。关键在于约定:所有 handler 必须把响应数据存入 ctx.Values(),并显式标记状态码,中间件统一读取、组装、写出。
实操建议:
- handler 中用
ctx.Values().Set("response", data)和ctx.Values().Set("status", 200)存结果,不调ctx.JSON() - 中间件在
ctx.Next()后检查:if !ctx.Response().Written() && data := ctx.Values().Get("response"); data != nil - 取出 status 和 data,调
ctx.StatusCode(status)+ctx.JSON(data)输出 - 若 handler 已自行响应(
ctx.Response().Written() == true),中间件跳过,避免冲突
怎么处理错误响应与 4xx/5xx 分离
统一响应中间件必须区分“业务成功响应”和“错误中断流”。Iris 不会自动把 error 映射为 HTTP 状态码,所以不能依赖 ctx.GetErr() 判断错误类型——它只返回 handler 内部 panic 捕获后的 error,不包含状态码语义。
正确做法是让 handler 主动设置标识:
- 成功路径:仍走
ctx.Values().Set("response", ...) - 失败路径:调
ctx.StopWithStatus(400)+ctx.JSON(&AppError{Code: 400, Message: "xxx"}),并确保不再执行后续逻辑 - 中间件只需检查
ctx.Response().Written()—— 若为 true,说明 handler 已完成响应(无论成功或失败),不干预;若为 false,才按“成功路径”兜底输出
这样既保留 handler 对错误响应的完全控制权,又保证所有未显式响应的成功请求都被统一格式化。
别忽略 OPTIONS 请求和预检响应
CORS 预检(OPTIONS)请求也会经过你的统一响应中间件。如果中间件只对非 OPTIONS 方法生效,或在 ctx.Next() 后才写响应,那 OPTIONS 响应就会漏掉你设的 Content-Type、统一字段等,导致前端跨域失败。
正确做法:
- 在中间件开头就统一设置基础头:
ctx.Header("Content-Type", "application/json; charset=utf-8") - 不要在
ctx.Next()后才设置响应头或内容 - 对
ctx.Method() == "OPTIONS"的情况,提前ctx.StatusCode(204)+ctx.StopExecution(),避免进入 handler 再绕一圈
最易被忽略的点:统一响应中间件必须放在 CORS 中间件之后注册,否则 CORS 头可能被你后续写的 Content-Type 覆盖;但又必须在鉴权类中间件之前——因为鉴权失败要直接终止,不该走统一响应流程。


















