用 Vary: Accept-Language 避免多语言 API 响应串语言
用 Vary: Accept-Language 避免多语言 API 响应串语言
问题场景
多语言 API 通常让同一个 URL 根据 Accept-Language 返回不同文案。若响应允许共享缓存(CDN、反向代理、网关缓存等)且没有在响应里声明 Vary,缓存可能把第一次请求的响应体存到仅以 URL 为键的条目中。后续不同语言请求命中同一缓存条目,就出现中文请求返回英文、英文请求返回中文的现象。
根因不是语言解析失败,而是缓存键没有包含语言协商所依赖的请求头。HTTP 的 Vary 响应头用于告诉缓存:选择该响应时还参考了哪些请求头。对内容协商响应设置 Vary: Accept-Language,可以让支持 Vary 的共享缓存按 Accept-Language 字段值区分缓存条目,从而避免不同语言响应互相复用。
最小可运行示例
下面是一个 Express 应用。中间件取 Accept-Language 中第一个语言片段,按前缀返回 zh 或 en,并设置 Vary、Cache-Control 与 Content-Language。
const express = require('express');
const app = express();
function pickLanguage(header) {
if (!header) return 'en';
const first = header.split(',')[0].trim().toLowerCase();
return first.startsWith('zh') ? 'zh' : 'en';
}
app.use((req, res, next) => {
const lang = pickLanguage(req.headers['accept-language']);
res.set('Vary', 'Accept-Language');
res.set('Cache-Control', 'public, max-age=60');
res.set('Content-Language', lang === 'zh' ? 'zh-CN' : 'en');
res.locals.lang = lang;
next();
});
app.get('/api/hello', (req, res) => {
const lang = res.locals.lang;
const message = lang === 'zh' ? '你好,这是中文响应' : 'Hello, this is an English response';
res.json({ lang, message });
});
app.listen(3000, () => {
console.log('API listening on http://localhost:3000');
});
这里 Vary 告诉缓存:同一 URL 的响应还要按 Accept-Language 区分。Cache-Control: public, max-age=60 允许共享缓存保存 60 秒;若响应包含用户私有数据,应改为 private 或 no-store。Content-Language 不是缓存键的一部分,但便于调试和客户端识别。
运行与验证
初始化并运行:
npm init -y
npm install express
node app.js
另开终端,用两次不同语言请求并查看响应头:
curl -i -H 'Accept-Language: zh-CN' http://localhost:3000/api/hello
curl -i -H 'Accept-Language: en-US' http://localhost:3000/api/hello
预期两次响应都包含 Vary: Accept-Language,Content-Language 分别为 zh-CN 和 en,响应体文案不同。若经过支持 Vary 的共享缓存,第一次请求的缓存键包含 URL 与 zh-CN 这个 Accept-Language 值,第二次请求的缓存键包含 URL 与 en-US,不会复用同一缓存条目。
可以用只看响应头的命令复核:
curl -s -D - -o /dev/null -H 'Accept-Language: zh-CN' http://localhost:3000/api/hello
curl -s -D - -o /dev/null -H 'Accept-Language: en-US' http://localhost:3000/api/hello
重点检查 Vary 是否原样出现在响应头中,以及两次 Content-Language 是否不同。若在 CDN 或反向代理后测试,还需确认该层是否尊重并透传 Vary;仅设置源站响应头并不自动保证所有缓存都按 Vary 处理。
边界与限制
- Vary 依赖缓存实现。若中间缓存忽略 Vary 或配置为按 URL 强制缓存,仍可能串语言;需要检查 CDN 与网关缓存策略。
Accept-Language是复合值,zh-CN、zh-TW、zh;q=0.9会被视为不同请求头值,形成不同缓存条目。命中率可能下降,但这是标准 Vary 的粒度;若业务只支持少数语言,可在应用层规范化选择语言,但 Vary 仍按请求头整体值区分。若需要更粗粒度缓存键,可考虑自定义请求头或把语言放入路径、查询参数,并同步调整缓存规则。- 同一 URL 的不同语言响应应保持正确的内容类型和编码;不要仅依赖 Vary 解决业务逻辑错误。
- 对包含用户身份或隐私数据的响应不要使用 public 共享缓存;应使用 private 或 no-store,并仍按需设置 Vary。
Vary: *会阻止缓存复用,不适合本场景。设置 Vary 时注意不要覆盖其他必要字段;若需要多个字段,应合并为一个 Vary 值列表。
小结
多语言 API 的缓存串语言问题,核心是缓存键没有体现内容协商。通过在 Express 中间件中解析 Accept-Language、返回对应文案,并设置 Vary: Accept-Language 与合适的 Cache-Control,再用 curl 对比两次响应头,可以验证不同语言请求的缓存键差异。上线前应结合具体缓存层配置做验证。
- 点赞
- 收藏
- 关注作者
评论(0)