题干与适用场景
一个公共 API 需要增加 Features 响应字段,返回能力名称、优先级和实验参数。客户端、CDN、网关和多个语言的 SDK 都会读取它;历史客户端只能忽略未知字段。请设计字段格式、发送与解析规则、兼容策略及验证方案。
这是一道后端 API 契约题,核心是把“看起来像字符串的头部”变成可互操作的协议。RFC 8941 定义 Item、List、Dictionary 及参数的通用模型,RFC 9651 是其后续修订;RFC 9110 则要求新字段明确语法,并防止控制字符和错误的多值合并。答案不要求实现完整 RFC 解析器,但必须说明边界。
面试官考察点
面试官会观察你是否先定义字段语义,再选择 List、Dictionary 或 Item,而不是直接拼逗号字符串。强回答会说明发送方序列化、接收方严格解析、未知成员处理、重复字段合并、大小上限和观测指标。
普通回答只展示一个示例值;高质量回答能解释为什么能力集合适合 Dictionary、为什么参数键必须小写、为什么非 ASCII 文本不应偷偷塞进 String,以及解析失败时如何安全降级而不误开实验功能。API 面试资料也把稳定契约、向后兼容和失败处理视为核心评分点。
回答前需要澄清的问题
字段语义和可信边界
确认该字段是提示、授权还是业务事实。若它影响权限、计费或安全策略,不能只依赖客户端回传;服务端必须保留权威状态。还要问字段是否允许缓存,以及不同用户的值是否不同。
类型与兼容范围
确认值是无序能力集合、需保持顺序的优先级列表,还是单个版本标识。询问旧客户端是否必须继续工作、是否允许新增参数、网关是否会合并重复字段。答案会决定选择 Dictionary、List 或单 Item,以及未知参数的处理规则。
失败和容量预算
确认非法字段是忽略整字段、忽略单个成员还是拒绝响应。设定字段字节上限、成员数量上限和解析耗时预算;这些约束会影响拒绝策略与 DoS 防护。
30 秒回答框架
“我先把字段定义成可版本化的机器协议,而不是 JSON 藏在字符串里。能力集合用 Dictionary,每个键的值是布尔值或带参数的 Item;键和参数按 RFC 规则序列化,未知成员默认忽略。服务端只发送 ASCII、限制总长度和成员数,接收方使用同一套规范解析,遇到非法语法就按字段缺失处理,不把猜测结果当成已启用。网关要测试重复字段合并和缓存变化,发布时先影子解析,再比较解析失败率、字段大小和功能误开率。”
分步骤深入解答
第一步:先建模再选容器
如果只表达能力开关,Dictionary 能将键映射到布尔值,例如 search; beta=?1。如果每项需要顺序和参数,List 更合适,例如每个成员是带参数的 Item。单个版本或策略名称才使用 Item。不要为了方便把结构化值编码成 JSON,因为代理和不同语言 SDK 仍会面对自定义解析器。
第二步:定义可扩展的字段
假设响应字段是能力字典,定义 search、upload 等键;参数如 v=2、tier="pro" 只承载协议允许的类型。参数键使用小写,非 ASCII 产品名称放在独立的响应体或明确的 Display String 扩展中。把每个成员的语义、默认值和非法值后果写进契约,而不是依赖实现猜测。
Features: search;v=2, upload=?1发送方必须稳定序列化:同一个抽象值应产生可预测的字节表示,避免缓存键和签名输入因空格或参数顺序产生无意义差异。接收方不能用普通字符串 split 代替语法解析,因为逗号、括号、引号和参数边界有明确规则。
第三步:规定重复字段和未知成员
先确认字段是否允许多行。若定义为 Dictionary,多个字段行可能被中间件合并,服务端应在规范中声明合并后的语义,并拒绝会产生冲突的重复键。未知键和未知参数默认忽略,已知键的非法类型则忽略该成员或整个字段,具体选择要写死。安全开关不应因为解析失败而默认打开。
第四步:建立严格但可用的解析边界
解析前限制总字节数、成员数、嵌套深度和 CPU 时间;拒绝 CR、LF、NUL 及超出字段语法的控制字符。对外部输入使用常量时间并不重要,但要避免异常路径把大字段反复重解析。记录失败原因类别,不记录完整的可能含敏感值的字段。
第五步:处理缓存、签名和演进
如果字段因用户或实验分组变化,响应必须带正确的 Vary 或使用私有缓存,否则 CDN 会把一个用户的能力暴露给另一个用户。若字段参加 HTTP Message Signatures,签名方和验签方必须对结构化值采用同一规范化规则。新增键和可选参数应保持旧客户端可忽略;移除或改变现有语义需要新版本或迁移窗口。
第六步:用灰度和反例证明方案
先在影子模式解析但不改变业务,再对少量内部客户端启用。构造重复键、空列表、错误引号、未知参数、超长字段、代理合并和缓存错配样例。比较有无字段时的解析成功率、误开率、响应体积、CPU、缓存命中和 SDK 版本分布。任何解析失败都应回到安全默认值。
高质量示范回答
我会把它当成协议设计题。先问字段是否只是能力提示;如果它决定权限,我会把权威判断留在服务端。对于一组能力,我选择 Structured Field Dictionary,并为每个键定义布尔值或带参数的 Item;顺序不重要的能力不使用 List。规范明确序列化、参数类型、重复字段、未知成员和非法值后果。
发送方只产生受限 ASCII,并限制字段大小和成员数量。接收方使用 RFC 兼容解析器,不用逗号切分;它拒绝控制字符,未知键忽略,已知键类型错误按缺失处理。多个字段行的合并规则在网关契约中固定,冲突键不会随机取最后一个。
我还会检查缓存和签名:用户相关能力必须进入 Vary 或私有缓存,签名两端必须使用同一规范化。先影子解析,再灰度启用,监控失败率、误开功能、字段大小和解析耗时。旧客户端继续忽略该字段,只有明确支持版本的客户端才启用新能力;这样新增字段不会把解析差异变成线上事故。
常见错误
- 把 JSON 放进一个字符串头部 → 代理和 SDK 仍需自定义解析,转义与重复字段语义不一致 → 使用 RFC 定义的 Item、List 或 Dictionary,并写出成员语义。
- 用
split(',')解析 → 引号、内层列表和参数中的分隔符会被错误切开 → 使用规范解析器并加入非法语法测试。 - 未知参数直接报错 → 新发送方无法与旧客户端共存 → 对可扩展参数采用忽略策略,只有已知安全约束失败才拒绝。
- 解析失败默认开启能力 → 中间件篡改或截断字段可能误开实验或权限 → 失败回到安全默认值,并记录分类指标。
- 忽略缓存变化 → 个性化能力被 CDN 复用给其他用户 → 按字段变化设置
Vary、私有缓存或完全不缓存。
追问及应对
追问一:为什么不用响应 JSON?
如果能力只在响应体中使用,JSON 可以更适合业务数据。选择 Structured Fields 的理由是它属于 HTTP 元数据,网关、缓存策略和通用客户端可以在读取正文前处理。两者不能混用语义;同一能力若同时出现,必须规定哪个是权威并检测不一致。
追问二:多个代理把字段合并了怎么办?
先在契约中规定字段是否允许列表式重复,并在入口把多行规范化成单一解析输入。对 Dictionary 的重复键采用拒绝或明确冲突规则,不依赖“最后一个获胜”。集成测试覆盖 HTTP/1.1 多行、HTTP/2 字段表示和 CDN 实际行为。
追问三:新参数需要非 ASCII 文本怎么办?
不要把 UTF-8 直接塞进只支持 ASCII 的 String。可以定义 RFC 允许的 Display String 扩展,或把展示文本留在响应体并让结构化字段只携带稳定 ID。先确认所有中间件和 SDK 都支持该类型,再通过版本能力协商启用。
追问四:解析器出现高 CPU 峰值怎么处理?
立即收紧字节、成员、嵌套和参数数量上限,并对超限字段按缺失处理。保留原始输入哈希和失败类别用于定位,不记录完整内容。将解析移到受限线程或进程只能缓解资源问题,不能替代语法上限和灰度回退。