mod_negotiation 默认未启用,需手动加载并配置MultiViews、MIME类型顺序及环境变量等才能实现内容协商。

Apache 的 mod_negotiation 为什么默认不生效
直接启用 Options +MultiViews 并不能让内容协商按预期工作,因为 Apache 默认禁用 mod_negotiation 模块,且 MultiViews 仅在启用该模块后才具备完整协商能力。常见现象是:浏览器发送 Accept: application/json,但服务器始终返回 text/html;或 Accept-Language: zh-CN 被完全忽略。
必须确认模块已加载:
sudo a2enmod negotiation
然后确保目标目录允许协商:
-
Directory配置中包含Options +MultiViews - 同时显式启用
AllowOverride All(若使用.htaccess)或直接在主配置中写规则 - 禁用
Content-Type硬编码响应头(如 PHP 中的header('Content-Type: text/html')会覆盖协商结果)
用 MultiViews 实现基于 Accept 的格式自动路由
文件命名需严格遵循 Apache 协商约定:同一资源用不同后缀区分类型,例如 api.v1 对应多个变体:
api.v1.json<br>api.v1.xml<br>api.v1.html
请求 GET /api.v1 时,Apache 根据客户端 Accept 头自动选择匹配文件。但要注意:
- 后缀顺序影响优先级:Apache 按
TypesConfig(通常为/etc/mime.types)中定义的 MIME 类型顺序匹配,不是按文件名字母序 -
Accept: application/json, text/html;q=0.9会选api.v1.json;但Accept: */*会回退到字典序最小的文件(如api.v1.html),不可控 - 若需强制 JSON 优先,应在
mime.types中把application/json行移到text/html前,或用AddType重定义
用 SetEnvIf + RewriteCond 做版本路由(绕过 MultiViews 局限)
MultiViews 对路径中带版本号(如 /v2/users)或自定义头(如 X-API-Version: 2)无能为力,此时需手动解析请求头做重写:
SetEnvIfNoCase X-API-Version ^1$ API_VERSION_1<br>SetEnvIfNoCase X-API-Version ^2$ API_VERSION_2<br><br>RewriteCond %{ENV:API_VERSION_1} ^1$<br>RewriteRule ^/users$ /v1/users.php [L]<br><br>RewriteCond %{ENV:API_VERSION_2} ^1$<br>RewriteRule ^/users$ /v2/users.php [L]
关键细节:
-
SetEnvIfNoCase匹配的是原始请求头值,不经过 Apache 内部转换,大小写敏感需加NoCase - 环境变量名不能含点号或斜杠,
API-VERSION是非法的,必须用下划线 -
RewriteRule目标路径必须是真实存在的文件或由后续处理器(如 PHP-FPM)支持的端点,不能是纯逻辑路径
协商失败时的 fallback 和调试方法
当没有匹配文件或协商歧义时,Apache 默认返回 406 Not Acceptable,而非降级。要避免生产环境暴露此错误,需显式定义 fallback:
AddHandler default-handler .fallback<br>RewriteCond %{REQUEST_FILENAME}.fallback -f<br>RewriteRule ^(.*)$ $1.fallback [L]
调试建议:
- 用
curl -H 'Accept: application/json' -I http://localhost/api.v1查看响应头中的Vary和Content-Type - 开启
LogLevel negotiation:trace5可看到协商每一步的匹配过程(日志量大,仅临时启用) - 注意 CDN 或反向代理可能缓存了协商结果,导致后续请求拿到错误的
Vary响应,务必检查Vary: Accept, Accept-Language是否被透传
真正难处理的是多维协商叠加——比如同时按语言、格式、压缩方式和 API 版本选资源,这时候 Apache 原生能力很快见底,不如把协商逻辑收归应用层统一处理。

















