- 数据存储
- 后端
【免费下载链接】opendal
Apache OpenDAL: One Layer, All Storage.
本篇技术指南以 Apache OpenDAL 的 RFC 0413: presign 为核心脉络,讲解 OpenDAL 如何让用户在不泄露secret_key的前提下生成跨存储服务的预签名 URL(Pre-signed URL),覆盖下载、上传、stat、delete 四种操作场景。读完本文,你将掌握Operator::presign_read/presign_write等 API 的完整用法、能力探测机制、底层签名调用链,以及 S3 等服务的具体实现细节,可直接用于构建"客户端直传/直下对象存储"的实战方案。
一、背景与动机:为什么需要预签名 URL
在对象存储生态中,"对象默认私有"是常见的默认安全模型。以 S3 为例,所有对象默认只有属主有权限访问;属主若想与第三方共享对象,需要利用自己的安全凭证生成一个预签名 URL,以授予限时访问权限。
OpenDAL 引入 presign 支持的动机非常直接:
- 服务端直传下载:在 URL 有效期内,任何人都可以直接从 bucket 下载对象,无需持有任何凭证;
- 客户端直传上传:客户端可以直接把内容上传到 bucket,数据流不经过应用服务器,极大缓解服务端带宽压力。
RFC 中给出的整体流程如下:
┌────────────┐ │ User ├─────────────────────┐ └──┬─────▲───┘ │ │ │ 4. Send Request to S3 Directly 1. Request Resource │ │ │ │ │ │ │ ▼ │ 3. Return Request ┌────────────┐ │ │ │ │ │ │ │ S3 │ ┌──▼─────┴───┐ │ │ │ │ └────────────┘ │ App │ │ ┌────────┐ │ │ │ OpenDAL│ │ │ ├────────┤ │ └─┴──┼─────┴─┘ │ ▲ └──────┘ 2. Generate Request用户向 App 请求资源,App 内的 OpenDAL 根据凭证生成签名请求(不真正发送),把带签名的 URL 返回给用户,用户随后绕过 App 直接向 S3 发起访问。OpenDAL 的意义在于:把"不同存储服务各不相同的签名协议"统一收敛为一套 API,让用户跨服务生成预签名 URL 更加容易。
二、Guide 层面:下载与上传两分钟上手
RFC 为普通用户设计了两条核心 API,均返回一个PresignedRequest对象。
生成下载用的预签名 URL
let req = op.presign_read("path/to/file")?; // req.method: GET // req.url: https://s3.amazonaws.com/examplebucket/test.txt?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=access_key_id/20130721/us-east-1/s3/aws4_request&X-Amz-Date=20130721T201207Z&X-Amz-Expires=86400&X-Amz-SignedHeaders=host&X-Amz-Signature=<signature-value>拿到 URL 后,用户无需任何凭证即可直接从 S3 bucket 下载对象:
curl <generated_url> -O test.txt生成上传用的预签名 URL
let req = op.presign_write("path/to/file")?; // req.method: PUT // req.url: https://s3.amazonaws.com/examplebucket/test.txt?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=access_key_id/20130721/us-east-1/s3/aws4_request&X-Amz-Date=20130721T201207Z&X-Amz-Expires=86400&X-Amz-SignedHeaders=host&X-Amz-Signature=<signature-value>用户可直接向 bucket 上传内容:
curl -X PUT <generated_url> -T "/tmp/test.txt"从 URL 结构可以看出,签名基于 AWS Signature Version 4 的 Query String 认证方式,关键参数包括X-Amz-Algorithm(签名算法)、X-Amz-Credential(访问密钥与作用域)、X-Amz-Date(签名时间)、X-Amz-Expires(有效期秒数)、X-Amz-SignedHeaders(被签名的请求头)与X-Amz-Signature(签名值)。RFC 中示例的有效期为86400秒(24 小时),实际使用中由调用方通过expire参数自由控制。
三、Reference 层面:核心类型与 trait 设计
RFC 在参考层面设计了三个关键构件:Accessor::presigntrait 方法、OpPresign参数结构、PresignedRequest返回结构。
3.1 服务侧的presignAPI
Accessor(在现在的代码库中已演进为Servicetrait)新增presign方法:
pub trait Accessor { fn presign(&self, args: &OpPresign) -> Result<PresignedRequest> {..} }该方法接收OpPresign,返回Result<PresignedRequest>:
struct OpPresign { path: String, op: Operation, expire: time::Duration, } struct PresignedRequest {} impl PresignedRequest { pub fn method(&self) -> &http::Method {..} pub fn url(&self) -> &http::Uri {..} }RFC 特别强调:新建PresignedRequest结构体是为了避免把底层实现(如hyper::Request<T>)泄露给用户,从而保证 API 的稳定与跨实现的可替换性。
3.2 公共 API 的导出
基于Accessor::presign,RFC 在Operator上导出公共 API:
impl Operator { fn presign_read(&self, path: &str) -> Result<PresignedRequest> {} fn presign_write(&self, path: &str) -> Result<PresignedRequest> {} }3.3 能力声明:与 RFC-0409 的联动
RFC 明确要求该特性作为AccessorCapability中的一项新能力,对应的设计文档是 RFC-0409: Accessor Capabilities。该 RFC 为 OpenDAL 引入了能力(Capability)机制,用户可以通过Operator::metadata()查询某个服务是否具备某项能力:
let meta = op.metadata(); let _: bool = meta.can_presign(); let _: bool = meta.can_multipart();同时约定:服务对不支持的操作应返回io::ErrorKind::Unsupported,而不是以unimplemented()直接 panic。用户既可以在操作前预先检查能力,也可以在操作后根据Unsupported错误做兜底处理。值得说明的是,RFC-0409 的动机部分直接引用了 Add presign url support 这一 issue,可见 presign 正是推动能力系统诞生的核心需求之一。
3.4 设计取舍
RFC 明确指出:虽然技术上可以为create、delete、stat、list生成 URL,但当时没有明显的使用场景,因此不在本提案中实现,仅保留为未来可能性。这一取舍保持了 API 面的克制。
四、源码级实现:RFC 落地后的完整形态
RFC 距今已多年,presign 能力在 core/core/src 中完成了完整落地,且范围比 RFC 初稿更大。以下逐一对照源码剖析。
4.1 参数与返回类型(OpPresign/PresignedRequest)
当前实现位于 raw/ops.rs 与 raw/rps.rs:
OpPresign由expire: Duration与op: PresignOperation构成,其中PresignOperation是一个#[non_exhaustive]枚举,已覆盖四种操作:
pub enum PresignOperation { Stat(OpStat), Read(BytesRange, OpRead), Write(OpWrite), Delete(OpDelete), }与 RFC 初稿相比,实际落地时不仅实现了Read/Write,还追加了Stat与Delete(对应 RFC 的 "Future possibilities" 中提到的stat/delete支持)。
PresignedRequest在 RFC 的method/url基础上增加了headers字段,并提供三个只读访问器:
pub struct PresignedRequest { method: http::Method, uri: http::Uri, headers: http::HeaderMap, } impl PresignedRequest { pub fn method(&self) -> &http::Method {..} pub fn uri(&self) -> &http::Uri {..} pub fn header(&self) -> &http::HeaderMap {..} }- 同时提供了
impl<T: Default> From<PresignedRequest> for Request<T>转换(见 raw/rps.rs),可以便捷地把PresignedRequest转换回http::Request<T>,方便接入任意 HTTP 客户端。rps.rs 中还带有test_presigned_request_convert单测验证该转换逻辑。
4.2 服务 trait 与统一调用链
raw/accessor.rs 中定义了Service::presign异步方法,并通过ServiceDyn的presign_dyn完成类型擦除,最终以Servicer = Arc<dyn ServiceDyn>的形式被 Operator 持有。因此所有 Operator 层的 presign 方法最终都会收敛到srv.presign(&ctx, &path, op).await这一条调用链上,再通过rp.into_presigned_request()取出PresignedRequest。
4.3 Operator 公共 API:从两个到八个
当前 types/operator/operator.rs 中,异步 API 已覆盖四种操作:
| API | 对应 HTTP 方法 | 说明 |
|---|---|---|
presign_stat | HEAD | 预签名 stat(head)请求 |
presign_read | GET | 预签名下载请求 |
presign_write | PUT | 预签名上传请求 |
presign_delete | DELETE | 预签名删除请求 |
每个 API 还提供两种进阶形态:
presign_xxx_with:返回可链式调用的 Future(如FuturePresignRead),支持.override_content_type(...)、.override_content_disposition(...)等附加选项,定义见 types/operator/operator_futures.rs;presign_xxx_options:直接传入结构化 Options(如options::ReadOptions、options::WriteOptions、options::StatOptions)。
这些选项的底层定义在 types/options.rs,包括override_content_type、override_cache_control、override_content_disposition等字段,注释明确标注"该选项仅在配合 presign 使用时才有意义"——它们会被编码进签名,服务端在响应时按签名值返回对应的响应头。
此外还有同步版本:blocking API 位于 blocking/operator.rs,提供presign_stat、presign_read、presign_write、presign_delete及其_options变体,适合非异步场景。
4.4 能力字段:Capability中的 presign 系列
types/capability.rs 中定义了 5 个 presign 相关能力位:
pub presign: bool, // 是否支持生成预签名 URL pub presign_read: bool, // 是否支持预签名读 pub presign_stat: bool, // 是否支持预签名 stat pub presign_write: bool, // 是否支持预签名写 pub presign_delete: bool, // 是否支持预签名删除能力位按"复合操作以下划线分隔"的命名约定组织(如presign_read)。用户可以在调用前通过 Operator 元数据探测服务能力,避免对不支持的服务发起无效调用。
4.5 S3 服务的完整实现链路
S3 是 presign 能力最完整的服务之一。在 services/s3/src/backend.rs 中,其能力声明为:
presign: true, presign_stat: true, presign_read: true, presign_write: true, presign_delete: true,其presign实现(见 services/s3/src/backend.rs)展示了清晰的分层逻辑:
- 解构
OpPresign得到(expire, op); - 根据
PresignOperation变体构造不会被真正发送的请求:s3_head_object_request(stat)、s3_get_object_request(read,可携带BytesRange范围参数)、s3_put_object_request(write)、s3_delete_object_request(delete); - 调用
self.core.sign_query(ctx, req, expire).await完成查询串签名; - 将签名后的请求拆解为
(parts, _),用 method、uri、headers 组装PresignedRequest返回。
底层签名由reqsign生态完成。在 services/s3/src/core.rs 的sign_query中,请求会经由self.signers.iam()提供的签名器执行.sign(&mut parts, Some(duration)),把X-Amz-*系列签名参数写入查询串;签名完成后还会主动移除host请求头,交由用户侧客户端按 HTTP 版本自行设置(避免协议错误)。这一细节与 RFC 示例 URL 中X-Amz-SignedHeaders=host的语义相呼应。
仓库中还配套了多个针对 presign 行为的测试用例(见 services/s3/src/backend.rs):
test_presign_write_preserves_content_type:验证 write 预签名请求保留Content-Type头;test_presign_stat_encodes_version_id与test_presign_read_encodes_version_id:验证 stat/read 预签名时版本 ID 被正确编码进 URL。
这些测试印证了 presign 与版本管理、响应头覆盖等高级特性的协同工作方式。
4.6 服务支持范围:能力差异化的实证
并非所有服务都支持 presign。以本地内存服务为例,services/memory/backend.rs 的presign直接返回ErrorKind::Unsupported,其服务文档 services/memory/docs.md 的能力清单中presign项为未勾选状态。这正是 RFC-0409 能力机制的意义:同一套 API 在不同服务上有不同支持度,用户应通过能力探测来区分。
从 docs/rfcs/0413_presign.md 的调研看,RFC 制定时各主流对象存储的 Query 签名支持情况为:
- s3:支持 AWS Signature Version 4 的 Query String 认证;
- azblob:支持共享访问签名(Shared Access Signature)委托访问;
- gcs:支持 Signed URLs(仅限 XML API)。
五、先行实践参考:awscli 的 presign
RFC 将 AWS CLI 的原生 presign 支持作为 prior art 参考。AWS CLI 用法如下:
> aws s3 presign s3://DOC-EXAMPLE-BUCKET/test2.txt https://DOC-EXAMPLE-BUCKET.s3.us-west-2.amazonaws.com/key?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAEXAMPLE123456789%2F20210621%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20210621T041609Z&X-Amz-Expires=3600&X-Amz-SignedHeaders=host&X-Amz-Signature=EXAMBLE1234494d5fba3fed607f98018e1dfc62e2529ae96d844123456对比可见:OpenDAL 的 presign 与 awscli 在签名格式上一致(同为 SigV4 Query 串),但 OpenDAL 的价值在于用统一的 API 抽象覆盖了多家存储服务,且以库的形式嵌入应用,无需额外安装 CLI 工具。
六、演进与展望
RFC 的 "Future possibilities" 中规划了为stat/list/delete增加 presign 支持。从当前代码看,stat与delete已经落地(PresignOperation::Stat、PresignOperation::Delete,且Capability中已有presign_stat/presign_delete能力位),而list的预签名仍不在实现范围内——这与对象存储服务的签名协议现状相符,可以推断主要受限于各服务对 List 类操作 Query 签名的支持程度参差不齐。
对于开发者而言,判断某个服务是否支持某项 presign 子能力,只需读取其Capability声明(例如 S3 服务在 services/s3/src/backend.rs 的capability()实现中逐项声明),或运行时通过Operator::metadata()探测。这套"能力声明 + 统一 API + 分层实现"的设计,正是 OpenDAL 让预签名 URL 生成"一处编写、多处生效"的关键所在。
- 数据存储
- 后端
【免费下载链接】opendal
Apache OpenDAL: One Layer, All Storage.
相关推荐
Apache OpenDAL options-based API:从 RFC-6213 设计到 ReadOptions/WriteOptions 的源码级解析
Apache OpenDAL options based API:从 RFC 6213 设计到 ReadOptions/WriteOptions 的源码级解析
数据存储后端lakeFS Rust SDK 中 UploadTo 模型详解:Presign 分片上传流程与预签名 URL 实现原理
lakeFS Rust SDK 中 UploadTo 模型详解:Presign 分片上传流程与预签名 URL 实现原理 本文围绕 lakeFS Rust API
数据工程数据湖大数据对象存储后端N_m3u8DL-RE 源码编译:3 条命令拿到任意平台的可执行文件
N_m3u8DL RE 源码编译:3 条命令拿到任意平台的可执行文件 N_m3u8DL RE 源码编译就是为这个场景准备的:官方 Release 版本落后于代码
CLI音视频
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考