如何用PHP区分用户直接请求与浏览器自动发起的请求?
这个问题确实很常见——在路由驱动的静态框架里,无效资源请求触发完整框架加载的404,确实会拖慢页面,尤其是开发阶段。你提到的HTTP_REFERER不可靠、加标记容易出错、路径识别不够优雅的痛点,我完全认同。下面分享几个更靠谱的方案,组合起来用效果最好:
1. 利用现代浏览器的Sec-Fetch-Dest请求头
这是目前最准确的方案之一。现代浏览器(Chrome、Firefox、Edge等)发起请求时,会自动带上Sec-Fetch-Dest头,明确标记请求的目的:
- 用户直接在地址栏输入URL、点击链接跳转的导航请求,这个头的值是
document - 浏览器自动加载的图片、JS、CSS等资源请求,值会是
image、script、style等
在PHP里可以直接读取这个头来判断:
function isDirectNavigationRequest() { return isset($_SERVER['HTTP_SEC_FETCH_DEST']) && $_SERVER['HTTP_SEC_FETCH_DEST'] === 'document'; }
优点:几乎不会出错,完全不需要修改页面代码;缺点:不兼容非常老旧的浏览器(比如IE),所以需要搭配回退方案。
2. 回退方案:检查Accept请求头
对于不支持Sec-Fetch-Dest的旧浏览器,我们可以用Accept头来判断:
- 用户直接请求页面时,浏览器的
Accept头会包含text/html(因为它期望得到HTML文档) - 浏览器请求资源时,
Accept头会对应资源类型,比如图片是image/*,CSS是text/css,JS是application/javascript
PHP代码示例:
function acceptsHtml() { return isset($_SERVER['HTTP_ACCEPT']) && str_contains($_SERVER['HTTP_ACCEPT'], 'text/html'); }
这个方案兼容所有浏览器,唯一的小例外是用户直接在地址栏输入资源URL(比如http://myhost.local/nonexist.js),这时候Accept头可能同时包含text/html和资源类型,但这种场景非常少见,而且即使误判,返回自定义404也不会有太大问题——或者我们可以再结合扩展名过滤来避免。
3. 补充:结合文件扩展名快速过滤
虽然你觉得路径识别不够优雅,但作为辅助判断非常实用。如果请求的URL带有.js、.css、.png、.jpg等资源扩展名,不管其他头信息如何,直接判定为资源请求,跳过框架的自定义404逻辑:
function isResourceExtension() { $path = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH); $ext = strtolower(pathinfo($path, PATHINFO_EXTENSION)); $resourceExts = ['js', 'css', 'png', 'jpg', 'jpeg', 'gif', 'svg', 'ico']; return in_array($ext, $resourceExts); }
综合使用方案
把上面三个方案组合起来,就能覆盖绝大多数场景,而且逻辑清晰:
function shouldServeCustom404() { // 如果是资源扩展名,直接返回false,不加载自定义404 if (isResourceExtension()) { return false; } // 优先检查现代浏览器的导航标记 if (isDirectNavigationRequest()) { return true; } // 回退到Accept头判断 return acceptsHtml(); } // 你的路由逻辑中使用 $requestedFile = getRequestedFilePath(); // 你的现有文件匹配逻辑 if (file_exists($requestedFile)) { // 加载对应页面文件 include $requestedFile; } else { if (shouldServeCustom404()) { // 只有用户直接请求不存在的页面时,才加载框架返回自定义404 include 'path/to/your/custom_404_framework.php'; } else { // 资源请求,直接返回原生404,立即终止脚本,不加载框架 http_response_code(404); exit; } }
为什么这比你提到的方案好?
- 不需要依赖
HTTP_REFERER:这个头受隐私设置、跨域规则影响很大,确实不可靠 - 不需要修改页面标记加特殊参数:完全后端解决,页面开发时不需要额外注意
- 逻辑优雅:利用浏览器的原生请求特性,不需要硬编码大量路径规则
这样处理后,开发阶段的无效资源请求只会返回简单的404,不会触发框架加载,页面速度会快很多,同时用户直接请求不存在的页面时,依然能看到你的自定义404页面。
内容的提问来源于stack exchange,提问作者STORMS-MEDIA

