
本文详解如何在 FastRoute 中区分 /api 前缀与普通路径的 404 响应:对 /api/** 返回 JSON 格式错误(含深层嵌套路径),其余路径返回纯文本或 HTML 404,提供正则通配与 URI 分析两种生产级方案。
本文详解如何在 fastroute 中区分 `/api` 前缀与普通路径的 404 响应:对 `/api/**` 返回 json 格式错误(含深层嵌套路径),其余路径返回纯文本或 html 404,提供正则通配与 uri 分析两种生产级方案。
在基于 FastRoute 构建的轻量 PHP 应用中,统一处理不同上下文下的 404 响应是常见需求。尤其当项目同时暴露 RESTful API(如 /api/login)和面向用户的 Web 页面(如 /about)时,API 客户端期望获得结构化 JSON 错误(含 Content-Type: application/json 和 HTTP 状态码 404),而浏览器用户则更适合接收简洁的 HTML 或纯文本提示。FastRoute 本身不内置路径前缀感知的 404 分发机制,需开发者主动设计策略。以下是两种经过验证、可直接投入生产的实现方式。
✅ 方案一:API 组内使用正则通配路由(推荐用于明确隔离场景)
核心思路是在 /api 路由组中,显式声明一个捕获全部未匹配子路径的兜底路由,利用 FastRoute 的正则参数能力匹配任意深度路径:
$dispatcher = \FastRoute\simpleDispatcher(function (\FastRoute\RouteCollector $r) {
// 普通 Web 路由(无特殊前缀)
$r->addRoute('GET', '/hello[/]', function () {
echo 'Hello, World!';
});
// API 路由组
$r->addGroup('/api', function (\FastRoute\RouteCollector $r) {
$r->addRoute('GET', '/hello[/]', function () {
header('Content-Type: application/json; charset=utf-8');
http_response_code(200);
echo json_encode(['msg' => 'hello API'], JSON_UNESCAPED_UNICODE);
});
// ⚠️ 关键:使用正则通配捕获所有未定义的 /api/** 路径(包括多层嵌套)
$r->addRoute('*', '/{any:.*}', function () {
header('Content-Type: application/json; charset=utf-8');
http_response_code(404);
echo json_encode(['error' => 'Not Found', 'path' => $_SERVER['REQUEST_URI']], JSON_UNESCAPED_UNICODE);
});
});
});? 说明:/{any:.*} 是 FastRoute 的命名正则参数语法,其中 .* 可匹配 /api/v1/users/123/settings 这类任意长度、任意层级的路径片段。* 方法标识表示该路由响应所有 HTTP 方法(GET/POST/PUT/DELETE 等),确保方法无关的 404 也能被捕获。
✅ 测试效果:
立即学习“PHP免费学习笔记(深入)”;
- GET /api → JSON { "error": "Not Found", ... }
- GET /api/nonexistent → 同上
- POST /api/v2/items/create → 同上(因未定义该路由)
- GET /nonexistent → 进入全局 NOT_FOUND 分支,返回普通 404
⚠️ 注意事项:
- 此兜底路由必须置于组内最后,否则会覆盖其他已定义的 API 路由;
- 若需保留原始请求路径用于日志或调试,可从 $_SERVER['REQUEST_URI'] 获取(注意其含查询参数,实际路由匹配前已剥离);
- 不建议在此处执行重定向或复杂逻辑,应保持 404 处理的轻量性。
✅ 方案二:全局 NOT_FOUND 分支动态解析 URI(推荐用于灵活/统一管控场景)
若项目路由结构复杂,或希望将 404 逻辑集中管控(避免在每个分组中重复声明兜底路由),可在主 dispatch 分支中统一判断:
$routeInfo = $dispatcher->dispatch($httpMethod, $uri);
switch ($routeInfo[0]) {
case FastRoute\Dispatcher::NOT_FOUND:
http_response_code(404);
// 解析路径段,判断是否为 API 请求
$pathSegments = array_filter(explode('/', trim($uri, '/')));
// ? 关键判断:首段为 'api' 即视为 API 上下文(适配 /api、/api/xxx、/api/v1/xxx)
if (!empty($pathSegments) && $pathSegments[0] === 'api') {
header('Content-Type: application/json; charset=utf-8');
echo json_encode([
'success' => false,
'message' => 'Endpoint not found',
'requested_path' => $uri
], JSON_UNESCAPED_UNICODE);
} else {
// 普通 404 响应(可输出 HTML 模板、静态页面等)
echo '<h1>404 Not Found</h1><p>The requested page does not exist.</p>';
}
break;
case FastRoute\Dispatcher::METHOD_NOT_ALLOWED:
http_response_code(405);
header('Allow: GET, POST, PUT, DELETE');
echo 'Method Not Allowed';
break;
case FastRoute\Dispatcher::FOUND:
$handler = $routeInfo[1];
$vars = $routeInfo[2];
$handler($vars);
break;
}✅ 优势:
- 逻辑完全解耦,无需修改路由定义即可调整 404 行为;
- 易于扩展(例如后续增加 /admin JSON 404、或按域名/Host 切换策略);
- 天然支持 /api/、/api/v1/、/api/v2/xxx/yyy 等所有合法变体。
⚠️ 注意事项:
- explode('/', trim($uri, '/')) 确保去除首尾 / 后分割,避免空字符串干扰索引;
- 在某些部署环境(如 Nginx + PHP-FPM 子目录),$_SERVER['REQUEST_URI'] 可能包含 base path,建议在解析前做标准化(参考原答案中的 $base 处理逻辑);
- 生产环境建议将 JSON 响应封装为复用函数,避免重复 header() 和 json_encode()。
? 总结与选型建议
| 方案 | 适用场景 | 维护性 | 扩展性 | 推荐指数 |
|---|---|---|---|---|
| 组内正则通配 | API 路由高度独立、数量可控、需强隔离 | 中(需注意路由顺序) | 低(每新增前缀需复制逻辑) | ⭐⭐⭐⭐ |
| 全局 URI 解析 | 多前缀(API/Admin/Health)、需统一错误规范、CI/CD 自动化部署 | 高(单点控制) | 高(条件可无限叠加) | ⭐⭐⭐⭐⭐ |
无论选择哪种方式,请务必:
- 始终设置正确的 Content-Type 和 HTTP status code;
- 对 JSON 响应启用 JSON_UNESCAPED_UNICODE 避免中文乱码;
- 在开发阶段开启 error_reporting(E_ALL) 并检查 FastRoute 版本兼容性(本文示例基于 v2.x+);
- 将 404 响应内容纳入 API 文档(如 OpenAPI Spec),保障前后端契约清晰。
通过以上任一方案,你都能优雅地实现「API 返回 JSON 404,Web 返回友好提示」的差异化体验,显著提升接口专业度与用户体验一致性。



















