摘要

  • RFC 9996 登记 application/protobuf 与 application/protobuf+json,分别约束二进制和 ProtoJSON 的表示方式;它处理的是序列化对象,不是 IDL 或对象定义本身。
  • 可选的 version 参数为未来的线编码版本预留,明确不指 Proto2、Proto3 或任何 Protobuf Edition。线格式兼容也不等于语义一致。
  • Daniel Kade 建议在依赖点保存一份“模式上下文凭证”,分别记录消息选择依据、模式摘要、发布权限、兼容性判断、解码策略、格式转换和最终处置。

最危险的结果不是报错,而是顺利生成了错误对象

设想一个网关收到 application/protobuf 响应。Content-Type 合法,二进制结构也合法,库顺利把字段 1、字段 2 和字段 7装入对象。监控没有异常,自动化继续执行。

问题可能藏在网关看不到的前提里:生产者用一份定义编码,消费者用另一份定义解码。同一个编号在两端属于不同字段,或者同一枚举值在不同语言版本下有不同处理。字节仍然像 Protobuf,解析仍然成功,但业务层得到的并不是发送方打算表达的对象。

RFC 9996并未承诺解决这一问题。它于 2026 年 7 月以 IETF 共识形成的 Informational RFC 发布,登记两个媒体类型:二进制表示使用 application/protobuf,ProtoJSON 使用 application/protobuf+json。过去多个 x- 别名因此有了正式替代,协议、文档和中间件可以使用共同词汇。

RFC 对边界写得很清楚:这些媒体类型只用于序列化对象。IDL 和对象定义如果需要传输,应使用合适的文本媒体类型。换言之,外层标签帮助选择处理器,内层语义仍由别处的模式决定。

这种分离并不是登记工作的缺陷。一个通用媒体类型不应替全世界的私有 API 决定模式治理。真正需要防止的,是本地系统把外层事实扩大成内层授权。

version=1 不是“模式第一版”

RFC 9996 为两个媒体类型规定了可选 encoding 参数。application/protobuf 默认是 binary;application/protobuf+json 默认是 json,而且必须带 charset=utf-8。如果参数与子类型冲突,接收方必须报错。+json 后缀还让浏览器和通用处理器知道它面对的是纯 JSON。

另一个可选参数叫 version,默认值为 1。名字很容易诱使日志和控制台把它展示成“schema version”。但 RFC 明示:这里的版本指线编码规范,不指模式语言。当前 Protobuf 线编码本身没有需要由该参数区分的多个版本;这个入口是为未来扩展预留的。

Proto2、Proto3、Edition 2023 与 Edition 2024 属于 IDL 的演进。它们生成的序列化对象可以共享兼容线格式,语义却并非处处相同。RFC 用未知枚举值说明差异:某一代可能把它视为无效,另一代则保留它。

因此,application/protobuf; version=1 无法证明正在使用“模式 v1”,更不能证明正在使用组织批准的模式。它没有给出 .proto 文件、包名、消息类型、修订、摘要、发布者或批准记录。参数名称相似,不能成为越权解释的依据。

线兼容只说明可以经过,不说明应当依赖

Proto3 语言指南指出,精简的线格式无法检测“按一种定义编码、按另一种定义解码”的情况。重用字段编号可能导致解析或合并错误,也可能带来个人信息泄露和数据损坏。错误不一定会以显眼的失败形式出现。

Protobuf 的演进机制当然真实有效。删除字段后保留编号与名称、在适当的二进制消息流程中保留未知字段、限制类型变更,都能让分布式系统逐步升级,而不必让所有节点同一秒切换。

但兼容性规则没有回答治理问题:谁有权更改定义,使用了哪套检查规则,谁批准例外,哪些生产者和消费者受影响,何时可以停止兼容旧版。“parser 返回成功”是运行结果,不是批准记录。

JSON 路径更能说明问题。ProtoJSON 文档明确说,它的模式演进保证弱于二进制格式。JSON 不支持未知字段,字段名和枚举名会进入线表示。把二进制消息转成 JSON 还可能丢失未知字段。最终的 application/protobuf+json 完全合规,却无法从标签看出中间有多少信息被省略。

一个负责的系统需要记录转换发生在哪里、使用哪份定义、未知字段被拒绝还是忽略、输出是否成为新的事实来源。只保留末端媒体类型,会把这段关键历史抹掉。

IANA 协调公共名称,不为每一份载荷背书

IANA 媒体类型注册表提供稳定的公共协调面。登记项写明子类型、参数、规范、预期用途与变更控制方。RFC 9996 同时把 application/x-protobuf、application/x-protobuffer 和 application/x-protobuf+json 标为弃用别名。

这项整理有直接价值。内容协商更一致,API 文档不必各造名称,安全设备能够区分二进制与 JSON,浏览器也能根据 +json 做正确分类。

注册表的权限同时是有限的。二进制登记没有 magic number,也没有文件扩展名。两个登记都不要求模式 URI、模式摘要、消息类型、生产者身份、签名或授权状态。IETF 是登记项的变更控制方,并不因此成为每一份带有该 Content-Type 的响应的签发者。

注册回答“这种表示可以使用什么公共名称”。它没有回答“这些字节是谁造的”“应用了哪份模式”“谁批准了模式”“内容是否被更改”“接收方是否可以据此执行”。若把这些结论借给 IANA,等于把名称协调机构的公信力移植到它从未审核的私有操作上。

安全控制也不能互相代替

RFC 9996 还明确说,Protobuf 本身不提供安全、隐私、完整性或压缩服务。TLS 可以保护一个连接,但不会自动给出该连接上应使用的模式摘要。资源限制可以缓解恶意输入的内存消耗,却不能修正合法字段的错误含义。string 与 bytes 内嵌内容仍需应用层验证,UTF-8 也可能需要额外检查。

在 Web 环境中,防止内容嗅探、正确使用 +json 与 UTF-8 可以减少浏览器把载荷误当成可执行内容或触发旁路风险。这是处理边界,不是模式来源证明。对象签名可以把字节绑定到密钥,也不等于接收方加载了签名者所依据的同一份定义。

Any 类型提供了更具体的 type URL,但也不是通用答案。RFC 9996 说明,这个链接最初被设想为可解引用以获取模式,而广泛使用的实现并不支持这种做法。type URL 可以成为某一应用合同的选择器,单凭 URL 本身却不是全球模式目录和信任根。

模式权力实际存在于解析器之外

严肃的服务总会在某处决定“哪份定义算数”。它可能是一座源码仓库、包注册表、编译规则、API 目录、私有模式服务或部署清单。有人拥有模式,有人审查兼容性,有人发布构件,有人批准上线。

RFC 9996 没有替应用选择其中一种,这是正确的克制。风险来自组织自己没有留下选择记录。此时,容器镜像里恰好存在的库就成为事实上的权威;一次依赖升级可以改变含义,而业务负责人甚至不知道合同发生了变化。

这是隐藏在便利路径里的代理问题。业务主体承担错误动作的损失,平台、构建链或库维护者却可能实际决定模式。没有可查的连接记录,任何审计者都无法区分“正式授权”与“碰巧可用”。

Heng Lu 强调应把可观察事实与制度性主张分开。媒体类型、成功解析、经过验证的连接、获批模式分别提供不同证据。它们在一次请求里相邻发生,并不意味着彼此等价。

保存一份模式上下文凭证

解决办法不是把私有 .proto 全部公开,也不是给媒体类型塞进不断膨胀的参数。Daniel Kade 建议在系统决定依赖解码结果的那个节点,保存一份最小化的模式上下文凭证。

第一部分原样记录收到的媒体类型和所有参与判断的参数,说明二进制或 ProtoJSON 分支是否匹配。第二部分记录消息类型如何被选中:API endpoint、RPC 方法、消息队列 topic、Any type URL 或其他本地约定。不得把这个选择误写成 Content-Type 自带的信息。

第三部分绑定模式包、语言代际或 Edition、修订与不可变摘要。人类可读的版本名帮助运维,摘要则识别实际审核过的字节。模式若是机密,可以只公开摘要与受控引用。

第四部分记录来源与发布权限:仓库或 namespace、负责主体、release 事件、签名或完整性证据、撤销与替代状态。熟悉的网址不等于持续有效的控制权。

第五部分单列兼容性判断:旧新模式身份、使用的规则和工具、破坏性变更、例外、批准者与受影响群体。解析成功不能填写这一栏。

第六部分固定解码器版本和策略,包括未知字段、枚举、UTF-8、资源上限以及任何会改变解释的选项。第七部分记录二进制转 JSON、逐字段复制、脱敏、归一化与重新序列化,并标明已知损失;条件允许时绑定输入输出摘要。

第八部分将 TLS、签名或对象完整性结果与模式身份分列。最后写入依赖结果:接受、隔离、拒绝、回滚或替代;责任人、作用范围、理由和纠正链接。凭证无需存储正文载荷。

这是 Daniel Kade 的编辑建议,不是 RFC 9996、IANA 或 Protobuf 项目的规范要求。它不集中模式所有权,也不让媒体注册表监管应用。它只阻止一个运输标签被动继承本应由本地责任主体行使的权力。

证据边界

已核查资料证明登记内容、规范行为与官方记录的兼容风险。它们没有证明新媒体类型的部署比例、任何具体事故、某个服务的错误实践,也没有选出唯一正确的模式注册架构。

RFC 9996 是 Informational,不是 Standards Track。准确说明状态不等于否认其协调价值。本文也不声称 Protobuf 缺少演进能力、JSON 天生不安全、type URL 毫无用途或私有模式都应公开。

结论只到这里:媒体类型正确、线格式兼容和模式权威是三个不同事实。缺失的连接必须由可问责的应用流程建立,不能从“解析成功”中倒推。

来源