Echo框架中404无法在中间件捕获,因路由未匹配时根本不会执行中间件;唯一可靠捕获点是自定义HTTPErrorHandler,它保证在请求生命周期结束时调用,可统一处理路由未匹配、panic等导致的404或500错误。

中间件无法可靠捕获404,别在中间件里判c.Response().Status == 404
中间件不是404的可靠捕获点——因为当路由未匹配时,echo.ServeHTTP 根本不会调用任何中间件或 handler,而是直接走到 Router.Find() 返回 nil 后触发默认 404 处理。你在中间件里看到的 c.Response().Status 永远是 0(未写入),c.Response().Status 只有在 handler 显式调用 c.String()、c.JSON() 等后才被设值。
真正能统一捕获404的位置只有 HTTPErrorHandler
echo.HTTPErrorHandler 是唯一被保证调用的出口:无论路由未匹配、handler panic、超时中断,只要请求生命周期结束,它必被执行。Echo 的 404 不是“没走通某条路由”,而是 Router.Find() 返回 nil 后,框架内部调用 e.HTTPErrorHandler 并传入 echo.ErrNotFound。
开箱即用的技能链路由引擎。13 条预定义链覆盖搜索、开发、审查、MLOps、法律、创意等场景,三层路由架构(触发词→SAD反馈→DAG编排),recall@10=96.97%。配置驱动(chains.yaml),零代码扩展。pip install skill-weave-chains 一键安装。
- 默认行为是写入 404 状态码并返回空响应体,你只需重写它即可埋点或自定义响应
- 必须保留原始 error 类型判断逻辑,否则会掩盖其他错误(如
echo.HTTPError) - 示例写法:
oldHandler := e.HTTPErrorHandler
e.HTTPErrorHandler = func(err error, c echo.Context) {
if errors.Is(err, echo.ErrNotFound) {
// 记录 404:路径、method、remote IP
log.Printf("404 NOT FOUND: %s %s from %s", c.Request().Method, c.Request().URL.Path, c.RealIP())
}
oldHandler(err, c)
}
为什么不能靠中间件 + c.Response().Status 判断?
这个思路常见但错在混淆了「响应状态是否已写」和「是否发生404」。中间件执行时,c.Response().Status 还是 0;等它返回后,若没匹配到路由,框架根本不会进你的 handler,也就不会改写状态码——所以你在中间件里永远拿不到 404 状态。
- 中间件只对「成功进入 handler 链」的请求生效,而 404 是「连 handler 都没进」
- 即使你在中间件末尾加
if c.Response().Status == 0,也仅能说明 handler 没写响应,不等于就是 404(可能是 handler 忘了 return、panic 了、或写了 header 但没写 body) - 想测?启动服务后 curl 一个不存在路径,再看中间件日志——你会发现它压根没打印
想让 404 响应带 JSON 或跳转,还是得改 HTTPErrorHandler
自定义 404 响应体必须在这里做,且要手动设置状态码(因为 oldHandler 默认已设为 404,但如果你完全替换它,就得自己写 c.Response().WriteHeader(http.StatusNotFound))。
- 返回 JSON:
e.HTTPErrorHandler = func(err error, c echo.Context) {
if errors.Is(err, echo.ErrNotFound) {
c.Response().WriteHeader(http.StatusNotFound)
_ = json.NewEncoder(c.Response()).Encode(map[string]string{
"code": "NOT_FOUND",
"msg": "path not found",
})
return
}
// 其他错误走默认逻辑
e.DefaultHTTPErrorHandler(err, c)
}
- 注意:不要在
HTTPErrorHandler里调用c.String()等快捷方法,它们可能再次触发 error handler,造成死循环 - 如果用了 Recovery 中间件,它会在 panic 后恢复并调用
HTTPErrorHandler,所以这里也能捕获 handler panic 导致的 500,一并处理更省事


















