上一篇写完 Backblaze B2 接入,这次紧接着做了 Wasabi——同样是路线图里"复用 s3-core,只配 endpoint/region"的门面型厂商。代码层面这次比 B2 还顺:endpoint 格式(s3.<region>.wasabisys.com) 同样是 AWS 形状,门面 crate 照抄 backblaze-b2 的结构,几乎没有新决定要做。真正花时间的还是同一 件事——查文档、填能力矩阵。这次把两家的矩阵摆在一起看,比只看一家更能说明"S3 兼容"这四个字到底 兑现了多少承诺。

两张矩阵拼在一起

能力Backblaze B2Wasabi
生命周期规则
对象标签
对象级 ACL 写入✅(有账号等级限制,见下)
版本控制❌(语义不匹配)✅(标准语义)
CORS❌(用了另一套机制,见下)
静态网站托管
存储类型转换❌(未查到对应头)❌(只有一种存储类型)

七项里只有生命周期规则和静态网站托管两家表现一致,其它五项要么支持情况相反,要么支持的原因/限制 完全不是一回事。如果这次图省事把 B2 的 Capabilities 抄一份改个厂商名,Wasabi 账号上"打标签"、 “设为公开读”、"启用版本控制"这三个本来能用的功能都会被平白无故堵死;反过来如果拿 Wasabi 的矩阵去 套 B2,用户会在真实账号上看到一堆"不支持"的报错。两个方向的偷懒都会让功能表和真实能力对不上。

CORS 那一项,不是"支持"或"不支持"能说清的

B2 支持标准的 PUT/GET /?cors 配置接口,和 AWS 一样,Nebula 现有的"CORS 规则"面板(读一份规则 列表、编辑、整体保存)直接能用。

Wasabi 不支持这个接口——但不是因为它没有 CORS 能力,而是它用了另一套完全不需要显式配置的机制: 只要请求带了 Origin 头,Wasabi 服务端就会自动在响应里带上合适的 CORS 头,不需要用户预先声明"哪些 来源允许访问"。这对最终用户(浏览器里跑的前端应用)效果是一样的,但对 Nebula 这种要展示"当前配置了 哪些 CORS 规则、允许用户编辑"的管理界面来说,是两种不可调和的模型——没有规则列表可读,也没有规则 列表可写,这个能力位只能老实标 false,但标 false 的原因和 B2"服务端压根没实现这个功能"是两回事。 代码注释里把这个区别写清楚,不然以后有人看到"Wasabi CORS: false"会以为是调研没查全,重新查一遍 文档,查完发现"哦原来查过了,是真的没法做",白费一次功夫。

object_acltrue,但心里要有数

Wasabi 的官方文档提了一句容易被忽略的话:公开访问默认只对付费(非试用)账号开放。也就是说,免费或 试用账号调 PutObjectAcl 设置 public-read,请求本身可能语法完全正确、返回成功,但对象实际访问不了 ——这是账号等级带来的限制,Nebula 的 HTTP 客户端完全看不到"这个账号是不是付费账号"这个信息,没法 提前拦下来给个更友好的提示。这种"接口调用成功,但实际效果被账号外部因素限制"的情况,比"接口直接 不存在"更难处理,也更容易被忽略——这次选择照实标 object_acl: true(协议层面确实支持),把这个 限制写进代码注释里,而不是为了规避这个尴尬就整体标 false(那样会让真正的付费账号用户平白损失一个 能用的功能)。

小结

两轮做下来,"S3 兼容"这四个字唯一能确定保证的是:请求签名、路径风格、基础的 GET/PUT/DELETE 语义能 对上。除此之外的一切——标签、ACL、版本控制、CORS、网站托管、存储分层——都要单独验证。这不是说"S3 兼容"没用,它确实让新接一家云的协议层工作量降到几乎为零(这两轮加起来,门面 crate 的代码量比 一次生命周期规则改动还小);但协议层归零,不代表业务能力层也能归零,那部分工作量永远得留着,谁也 省不掉。