题干与适用场景
这道通用技术题考察 HTTP 表示、内容协商和共享缓存的边界。重点不是罗列 Accept 头,而是证明代理不会把一种语言或编码的响应错误复用给另一种请求,同时让回退和调试行为可解释。
面试官考察什么
- 能否区分资源 URI 与同一资源的不同表示,并理解主动协商的请求头。
- 能否用 Vary 声明影响响应的请求字段,推导缓存键和命中率取舍。
- 能否设计语言、媒体类型和压缩编码的确定性回退,避免错误的 406 或串内容。
- 能否识别 Cookie、Authorization、用户特征和高基数头带来的共享缓存与隐私风险。
回答前需要澄清的问题
先确认资源是否公开可共享,变体维度有哪些,是否允许服务端驱动协商,客户端能否显式选择语言,以及 CDN 是否支持按 Vary 分片。还要确认未支持语言的产品回退、压缩算法白名单、响应是否含个性化数据和可接受的缓存命中率。
30 秒回答框架
我会把资源 URI 与表示选择分开建模,按 Accept、Accept-Language 和 Accept-Encoding 的权重做确定性选择,并在响应中声明 Content-Type、Content-Language、Content-Encoding 与准确的 Vary。共享缓存只复用同一变体键;个性化或高基数请求绕过共享缓存。无法满足时按产品规则回退并记录协商结果,随后用带请求头的缓存命中、内容校验和隐私测试验证。
分步骤深入解答
1. 先定义表示与协商维度
把同一 URI 看成一个资源的多种表示,例如 HTML 与 JSON、中文与英文、gzip 与 br。只把真正影响响应字节或语义的请求字段纳入协商;不要因为实现方便把任意 Cookie 或完整 User-Agent 都放入维度。服务端应记录选择结果,使相同输入得到可预测表示。
2. 设计权重、回退与错误
解析 Accept 系列中的质量因子和通配符,定义服务器支持列表与优先级。语言可按区域到基础语言再到默认语言回退,媒体类型可提供明确的默认表示;只有没有可接受表示且产品确实需要拒绝时才返回 406。对压缩编码只选择允许的编码,未知或不安全的值不应绕过白名单。
3. 让缓存变体可计算
响应的 Vary 列出参与选择的请求头,例如 Accept, Accept-Language, Accept-Encoding。缓存用 URI、方法和这些字段的规范化值形成变体键;规范化包括大小写、空白和排序,不能把不同写法无意间扩成无限键。若响应依赖授权、会话或用户 Cookie,应使用私有缓存或明确禁止共享缓存。
4. 控制命中率、隐私与资源数量
每增加一个维度都会拆分缓存并降低命中率,Accept-Language 还可能成为浏览器指纹信号。对公开页面可只支持有限语言集合,把长尾映射到默认语言;对个性化数据采用 private、短 TTL 或不缓存。不要用 Vary: * 掩盖不清楚的依赖,因为它会让共享缓存无法复用。
5. 设计可观测性与验证
日志记录规范化协商输入、选中的表示、Vary、缓存命中状态和回退原因,但不要记录完整敏感 Cookie。测试每个变体的响应头、压缩解码、跨 CDN 命中和未命中,特别验证先请求中文再请求英文不会串内容。线上排查时对比 Age、缓存状态、Content-* 头和实际字节,并用固定请求矩阵重放。
高质量示范回答
我会先列出有限的表示集合,再根据 Accept、Accept-Language 和 Accept-Encoding 的质量因子做确定性选择。响应同时返回 Content-Type、Content-Language、Content-Encoding 和准确的 Vary,缓存键只包含这些规范化维度。授权、会话和高基数特征不进入共享缓存;公开页面的语言长尾回退到默认语言。没有可接受表示时按产品规则返回 406 或明确默认值。日志记录选择、回退和命中状态而不记录敏感 Cookie,并用“先中文后英文、先 br 后 gzip”等矩阵验证 CDN 不会复用错误变体。
常见错误
- 只设置 Content-Language,不在响应中声明真正影响选择的 Vary。
- 把完整 Cookie、User-Agent 或任意请求头放进缓存维度,造成命中率崩溃。
- 看到不支持的语言就返回 406,没有定义区域到基础语言和默认语言回退。
- 让共享缓存复用带 Authorization 或会话数据的响应。
- 忽略压缩编码协商,返回客户端无法解码的 Content-Encoding。
- 只看状态码,不对比 Content-* 头、缓存状态和实际响应字节。
追问及应对
为什么 Vary 不能只写 Accept-Language?
如果响应也受媒体类型或压缩编码影响,缓存还需要知道这些维度。遗漏会让共享缓存把一个表示交给不兼容的请求,产生串内容或解码失败。
Vary: * 什么时候有用?
它表示选择依赖未列出的信息,通常会阻止共享缓存复用。它可以作为保守保护,但不能替代梳理真实依赖和改用 private 缓存。
如何降低 Accept-Language 带来的键爆炸?
把客户端语言映射到有限支持集合,按区域、基础语言和默认语言回退;必要时用显式 URL 或用户选择覆盖自动协商,并监控每种变体的命中率。
协商失败时应该返回 406 还是默认表示?
取决于接口契约。机器接口通常需要明确错误,网页可回退到默认语言或媒体类型;两者都应在文档、响应头和日志中保持一致,不能静默返回错误语义。