rusty-cat × Azure Blob SAS 实战(后端签 URL 版)
本文是 rusty-cat 分片并发文档 的「云存储实战」系列之一,教你用 SAS(Shared Access Signature) 的方式做 Azure Blob 分片并发上传与下载:账号密钥留在后端,客户端只拿短期有效的 SAS URL。 先读概念请看:《分片并发上传指南》、《分片并发下载指南》。 需要的 feature:
azure-blob-sas。
1. 适用场景与密钥模型
「SAS」的安全模型:你的后端持有账号密钥,签发短期有效的 blob SAS URL,客户端用 rusty-cat 对着 SAS URL 上传 / 下载,不接触账号密钥。
🖼️ 【配图占位 1|SAS 密钥模型(三方时序)】 建议配图:三列时序——客户端、后端(🔑 持 Account Key)、Azure。①客户端向后端要 SAS URL;②后端签发短期 blob SAS URL;③客户端拿 SAS URL 并发上传 / 下载。图注:账号密钥只在后端。 临时 ASCII 草图:
客户端 ──①请求 SAS URL──▶ 后端(🔑AccountKey) 客户端 ◀──②短期 blob SAS URL── 后端 客户端 ═══③并发 Put Block / Range GET═══▶ Azure Blob
- ✅ 适合:手机 App、桌面客户端、浏览器等不可信环境(推荐默认方案)。
2. 加依赖
[dependencies]
rusty-cat = { version = "0.2.4", features = ["azure-blob-sas"] }
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
3. 上传:完整可运行程序
和 OSS 预签名不同,Azure SAS 上传必须带一个「完成请求」(Put Block List),SDK 才能在最后把块列表提交。所以除了每片一个 Put Block,还要额外准备一个有序的 block-id 列表和 put_block_list_request:
use std::sync::Arc;
use rusty_cat::api::{
MeowClient, MeowConfig, PresignedMultipartUpload, PresignedMultipartUploadPlan,
UploadPounceBuilder,
};
use rusty_cat::azure_blob_sas as azure;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
// 后端签发的、有时效的 blob SAS URL
let blob_sas_url = "https://myaccount.blob.core.windows.net/container/blob.bin?sv=...&sig=...";
let one_mb: u64 = 1024 * 1024;
let total: u64 = 5 * one_mb;
// 每个分片一个 Put Block(从 SAS URL 派生),下标从 0 开始
let parts = (0..5)
.map(|i| azure::put_block_from_blob_url(blob_sas_url, i, i as u64 * one_mb, one_mb))
.collect::<Result<Vec<_>, _>>()?;
// 提交用的、有序的 block-id 列表
let block_ids = (0..5).map(azure::block_id_by_index).collect::<Vec<_>>();
let block_id_refs = block_ids.iter().map(String::as_str).collect::<Vec<_>>();
let complete_request = azure::put_block_list_request(blob_sas_url, block_id_refs)?;
let plan = PresignedMultipartUploadPlan::new(total, one_mb, parts)
.with_complete_request(complete_request); // Azure SAS 必需!
let protocol = PresignedMultipartUpload::new(plan);
let client = MeowClient::new(MeowConfig::default());
let task = UploadPounceBuilder::new("azure-sas.bin", "./azure-sas.bin", one_mb)
.with_url(blob_sas_url)
.with_breakpoint_upload(Arc::new(protocol))
.with_max_parts_in_flight(4) // 4 块并发
.build()?;
client
.enqueue_and_wait(task, |r| println!("{:.1}%", r.progress() * 100.0))
.await?;
client.close().await?;
Ok(())
}
🖼️ 【配图占位 2|Azure SAS 并发上传数据流】 建议配图:客户端并发发 4 个
Put Block(block id = base64(下标));末尾发一次Put Block List(携带有序 block-id 列表)提交。图注:为什么 SAS 必须带 complete_request——因为 SDK 需要在所有块上传后一次性提交块列表,缺了它 blob 不会真正生成。 临时 ASCII 草图:客户端 ═══Put Block #0═══▶ ┐ ═══Put Block #1═══▶ ├─ 4 块并行 ═══Put Block #2═══▶ │ ═══Put Block #3═══▶ ┘ ──Put Block List(有序 block-id)──▶ 提交 ✅ ← with_complete_request 必需
4. 下载:串行 vs 并发
方式一(串行):SAS range 下载协议
用通用的 PresignedRangeDownload + PresignedRangeDownloadPlan(这里的 with_total_size 是 Plan 上的、给串行协议用的):
use std::sync::Arc;
use rusty_cat::api::{
DownloadPounceBuilder, MeowClient, MeowConfig, PresignedRangeDownload,
PresignedRangeDownloadPlan,
};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let sas_get_url = "https://myaccount.blob.core.windows.net/container/blob.bin?sv=...&sig=...";
let total_size: u64 = 5 * 1024 * 1024;
let client = MeowClient::new(MeowConfig::default());
let plan = PresignedRangeDownloadPlan::new(sas_get_url).with_total_size(total_size);
let protocol = PresignedRangeDownload::new(plan);
let task = DownloadPounceBuilder::new("blob.bin", "./downloads/blob.bin", 1024 * 1024, sas_get_url)
.with_breakpoint_download(Arc::new(protocol))
.build();
client.enqueue_and_wait(task, |_r| {}).await?;
client.close().await?;
Ok(())
}
方式二(推荐,可并发):SAS GET URL + 默认下载器
同一个 SAS GET URL,不挂 provider 协议、直接喂默认下载器 + with_total_size + with_max_parts_in_flight(4):
use rusty_cat::api::{DownloadPounceBuilder, MeowClient, MeowConfig};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let sas_get_url = "https://myaccount.blob.core.windows.net/container/blob.bin?sv=...&sig=...";
let total_size: u64 = 5 * 1024 * 1024;
let client = MeowClient::new(MeowConfig::default());
let task = DownloadPounceBuilder::new("blob.bin", "./downloads/blob.bin", 1024 * 1024, sas_get_url)
.with_total_size(total_size) // 任务构建器上的 with_total_size:开启并发的关键
.with_max_parts_in_flight(4) // 4 个 Range GET 并发
.build();
client.enqueue_and_wait(task, |r| println!("{:.1}%", r.progress() * 100.0)).await?;
client.close().await?;
Ok(())
}
⚠️ 两个
with_total_size别搞混:任务构建器的会开启并发;PresignedRangeDownloadPlan的是给串行协议用的。⚠️ 稳定 URL 规则:并发下载复用一个 URL 且无刷新,SAS GET URL 有效期要覆盖整个下载(含续传)。若网关需要
x-ms-version等头,用with_headers(...)加,别改 URL。详见 《分片并发下载指南》。
5. 断点续传与清理
- 续传:Azure 无 upload id,续传靠恢复服务端「未提交 block 列表」,重启后重建任务再
try_enqueue。 - 未提交 block 会计费到 Azure GC(约 7 天),配生命周期规则清理。
6. 进度回调与任务控制(暂停 / 恢复 / 取消)
前面用的 enqueue_and_wait 会一直等到任务结束,适合脚本或一次性任务。做 UI、或要在后台并发跑并随时控制任务时,改用底层的 try_enqueue:它立刻返回 TaskId,让你分别处理进度回调和完成回调。把上文构建好的 task 传进去即可:
use rusty_cat::api::{
FileTransferRecord, MeowClient, MeowError, PounceTask, TaskId, TransferStatus,
};
// task 按上文「上传」或「下载」小节构建好后传进来
async fn enqueue_with_control(
client: &MeowClient,
task: PounceTask,
) -> Result<TaskId, MeowError> {
let task_id = client
.try_enqueue(
task,
// 进度回调:每次进度更新都会调用
move |record: FileTransferRecord| match record.status() {
TransferStatus::Failed(err) => eprintln!("失败:{err}"),
TransferStatus::Canceled => println!("已取消"),
_ => println!("进度 {:.1}%", record.progress() * 100.0),
},
// 完成回调:成功到达 Complete 时调用一次
move |task_id, payload| println!("任务 {task_id} 完成 {payload:?}"),
)
.await?;
Ok(task_id)
}
拿到 TaskId 后就能随时控制任务:
client.pause(task_id).await?; // 暂停
client.resume(task_id).await?; // 从断点继续(TaskId 不变)
client.cancel(task_id).await?; // 取消(Azure 会停止累积未提交的 block)
record.status()返回&TransferStatus,取值:Pending/Transmission/Paused/Complete/Failed(err)/Canceled;进度是0.0~1.0的比例(f32)。完整语义见《分片并发上传指南》第 8 节与《分片并发下载指南》第 9 节。
7. 相关阅读
- 《分片并发上传指南》、《分片并发下载指南》 — 通用机制与避坑。
- 《Azure Blob 直传直下实战》 — 可信服务器上的 Shared Key 直连方案。
- 文档总索引:doc/README.md。
本文基于
rusty-cat0.2.4,代码片段均经过cargo check --features all编译验证。