AWS 的 API 为何返回 XML - 从 Query API 到 REST JSON 的演进历史

解析 S3 和 EC2 的 API 返回 XML 响应的历史原因、Query API 与 REST API 的区别、从 Signature V2 到 V4 的认证方式演进,以及 SDK 所隐藏的复杂性。

2006 年的 API 设计 - XML 理所当然的时代

S3 和 EC2 于 2006 年推出时,Web API 领域中 XML 是标准数据格式。SOAP(Simple Object Access Protocol)是企业级 Web 服务的主流,通过 XML Schema 进行严格类型定义和 WSDL(Web Services Description Language)进行服务描述被视为「正确的」API 设计。JSON 是 Douglas Crockford 在 2000 年代初提出的轻量格式,但到 2006 年尚未广泛普及,被评价为「轻量但缺乏类型安全性」。AWS 的早期 API 反映了这个时代的设计思想。EC2 的 API 采用「Query API」形式,在 HTTP GET 请求的查询参数中指定操作名和参数,以 XML 接收响应。例如,获取 EC2 实例列表的请求格式为 https://ec2.amazonaws.com/?Action=DescribeInstances&Version=2016-11-15。S3 的 API 采用 REST 风格,通过 HTTP 方法(GET、PUT、DELETE)和路径操作资源,但响应仍为 XML。

向后兼容性的束缚 - 为何无法废弃 XML

AWS 最重要的设计原则之一是「已发布的 API 原则上长期保持兼容」。EC2 的 Query API 自 2006 年发布以来持续运行至今,XML 响应格式也未改变。数百万客户的应用依赖于此 API,将响应格式改为 JSON 将是大规模破坏性变更。AWS 通过在新服务中采用 JSON、同时保持旧服务 API 不变的方式解决了这个问题。2012 年以后推出的服务(DynamoDBLambdaAPI Gateway 等)几乎全部采用 JSON。但并非所有使用 JSON 的服务都是 REST 风格。DynamoDB 的 API 不是用 URL 路径表示资源的 REST,而是对单一端点调用并通过头部指定操作名的 JSON-RPC 形式(在 Smithy 的协议名称中为 awsJson1_0)。Content-Type 为 application/x-amz-json-1.0,要执行的操作通过 X-Amz-Target 头部指定。Lambda 的 API 是使用资源路径和 HTTP 方法的 REST 风格,正文为 JSON。而 EC2、S3、SNS 等早期服务至今仍返回 XML 响应。不过,早期服务并非永远固定在 XML 上。SQS 于 2023 年 11 月支持了 JSON protocol,对于使用最新 AWS SDK 的新用户,JSON 已成为默认协议(现有基于 XML 的调用仍可照常运行)。AWS SDK 在内部吸收了这些差异,开发者使用 SDK 时无需关心 XML 和 JSON 的区别。

Signature V4 - 对所有请求进行签名的机制

AWS API 的另一个特点是所有请求都需要加密签名。当前标准的 Signature Version 4(SigV4)将请求的 HTTP 方法、URL、头部和正文连接后进行哈希,使用密钥访问密钥生成 HMAC-SHA256 签名。该签名包含在 Authorization 头部中,由 AWS 端进行验证。SigV4 的签名过程由 4 个步骤组成。第一,创建规范请求(Canonical Request)。将 HTTP 方法、URI、查询字符串、头部规范化为字符串。第二,创建待签名字符串(String to Sign)。连接日期、区域、服务名和规范请求的哈希。第三,派生签名密钥。从密钥访问密钥逐步派生按日期、区域、服务的签名密钥。第四,计算签名。用签名密钥对待签名字符串进行 HMAC-SHA256 签名。需要注意的是,SigV4 是按日期、区域、服务派生签名密钥的对称密钥方式,因此生成的签名只能针对单一区域进行验证。跨多个区域的请求(如 Amazon S3 的多区域接入点)需要使用 SigV4a,即基于椭圆曲线加密的非对称签名扩展,AWS SDK 和 AWS CLI 在此类调用中会自动切换到 SigV4a。SigV4a 不按日期或区域派生密钥,而是使用从密钥访问密钥派生的密钥对进行签名,AWS 端仅用公钥进行验证。手动实现这一复杂过程不现实,AWS SDK 在内部自动处理。不使用 SDK 而用 curl 调用 AWS API 时,需要自行实现这一签名过程,调试极为困难。

SDK 所隐藏的复杂性

AWS SDK 是将 API 复杂性对开发者隐藏的巨大抽象层。SDK 在内部处理的事项涵盖多个方面:请求签名(SigV4)、XML/JSON 的序列化与反序列化、重试逻辑(带指数退避)、错误处理(区分临时错误和永久错误)、分页(自动通过多次请求获取大量结果)、区域端点解析、临时凭证的自动刷新(IAM 角色的 AssumeRole)等。SDK v3(JavaScript)和 boto3(Python)将这些处理实现为中间件管道,开发者也可以添加自定义中间件。SDK 的重试逻辑尤为重要。AWS 的 API 实施限流(速率限制),短时间内发送大量请求时调用会被拒绝。此时返回的错误代码因服务而异。EC2 的 API 在超过请求数上限时返回 RequestLimitExceeded,许多服务则使用 ThrottlingException。HTTP 状态码也因服务和操作而异,因此应避免只凭单一状态码判断限流的实现。SDK 会自动区分这类限流错误与服务端的临时失败(5xx),并以带抖动(随机波动)的指数退避进行重试。等待时间的基准值为毫秒级,重试次数上限和最大等待时间也作为 SDK 的默认值定义,因此开发者无需自行实现重试逻辑。

API 的演进 - 从 Query API 到 GraphQL 与事件驱动

AWS 的 API 设计从 2006 年 S3 和 EC2 推出至今经历了巨大演进。从早期的 Query API + XML,到 REST + JSON,再到 2018 年正式可用的 AWS AppSync 的 GraphQL 以及 EventBridge 的事件驱动,API 范式本身在多样化。AWS API 设计中始终一贯的是「已发布的 API 原则上长期保持兼容」的态度。新功能以不破坏现有调用的方式,作为新的 API 版本或附加参数提供,旧版本也可以长期照常使用。这种向后兼容性的维护是 AWS 可靠性的根基。企业花费数年构建的系统基本上不会因 API 变更而突然无法运行。但这并不是「绝对不会废弃」的保证。实际例子是,S3 的 SOAP API 已不再向新用户提供,并于 2025 年 8 月 31 日终止提供(End of Life)。长期兼容只是原则,事先通知并伴随迁移期的例外是存在的。另一方面,这一原则也产生了技术债务。2006 年当时的设计决策(XML 响应、Query API 形式)至今仍在维护,新开发者会产生「为何还用这么古老的格式」的疑问。答案是「为了向后兼容性」,这体现了 AWS 将客户信任置于最优先的态度。

参考资料(AWS 官方)

本页的第一手信息来源是 AWS 官方网站及官方文档。最新的规格与价格请以下列官方页面为准。

如本页内容与官方文档不一致,请以官方文档为准。