Hyperf控制器未匹配路由时默认返回空响应或JSON 404,需通过自定义异常处理器handle方法判断Accept头是否含text/html,再调用view渲染HTML模板并显式设置404状态码。

Hyperf控制器没匹配到路由时,默认返回的是空响应或 JSON 格式的 404,不是 HTML 页面。要让它返回自定义的 404 页面(比如带样式、导航、搜索框的 HTML),关键不是“改控制器”,而是接管未匹配路由的兜底逻辑。
为什么直接在控制器里 throw new NotFoundException 不生效
Hyperf 没有内置 NotFoundException 类,也不像 Laravel 那样把 404 当作可抛异常来统一处理。你手动 throw 的任何异常,都会走全局异常处理器(ExceptionHandler),而默认的 HttpExceptionHandler 对非 HTTP 异常只返回 JSON 或空白响应,不会渲染视图。
- throw new \Exception('not found') → 被
HttpExceptionHandler捕获,返回{"message":"not found"},状态码可能是 500 - throw new \Hyperf\HttpServer\Exception\NotFoundException() → 这个类不存在,会报 fatal error
- 即使你自定义了
PageNotFoundException,若没在exceptions.php中显式注册 handler,它也会被上层 handler 吞掉或降级
真正起作用的是全局异常处理器 + HTML 请求判断
必须在 app/Exception/Handler/AppExceptionHandler.php 的 handle() 方法里,根据请求头是否接受 HTML 来决定返回 JSON 还是视图。Hyperf 默认不区分请求类型,这一步得自己写。
- 用
$request?->header('accept')判断是否含text/html,避免 API 请求也被套上 HTML 模板 - 确保
view组件已安装且路径可写:composer require hyperf/view,并检查config/autoload/view.php中path指向真实目录 - 模板里不要用
exit或提前输出,否则 Swoole 流会中断;用return $response->withStatus(404)->withBody(...) - 示例片段:
if ($throwable instanceof \Hyperf\HttpServer\Exception\HttpException && $throwable->getStatusCode() === 404) {
$isHtmlRequest = str_contains((string) $request?->header('accept'), 'text/html');
if ($isHtmlRequest) {
$html = app('view')->fetch('error/404', ['url' => (string) $request?->getUri()]);
return $response->withStatus(404)->withBody(new SwooleStream($html));
}
}
别漏掉中间件顺序和环境开关
如果你在开发环境想看到美化后的 404 页面,但生产环境只返回纯文本,就得靠 APP_ENV 控制逻辑分支,而不是靠中间件顺序“抢答”。Hyperf 的异常处理链是线性的,handler 数组里越靠前的越先执行。
- 确保你的自定义 handler 在
config/autoload/exceptions.php中排在HttpExceptionHandler之后、其他业务 handler 之前 - 不要在
DevExceptionPageMiddleware里做 404 处理——那个中间件只捕获未被 handler 接管的 Throwable,而 404 是框架内部抛出的,早被HttpExceptionHandler拦住了 - 检查
config/autoload/middlewares.php是否误加了全局路由匹配中间件,它可能提前返回 404 并跳过后续 handler
最易忽略的一点:Hyperf 的 404 不是“控制器没找到”,而是“路由没匹配上”。哪怕控制器文件存在、注解也写了,只要路径、方法、参数格式或扫描路径配置不对,就进不到控制器里——这时候你写的任何控制器内逻辑都执行不到。所以先用 curl -v 确认是真 404,再决定该修路由还是修异常处理。


















