适合读者:第一次听说
rusty-cat、刚开始学习 Rust 异步编程、想在项目里做大文件上传下载但不知道从哪里下手的同学。
1. 先说痛点:为什么大文件传输不能只用一次 HTTP 请求?
很多小白第一次做文件上传下载时,最容易写出这样的逻辑:
rust
// 伪代码:一次性把整个文件发出去
let bytes = tokio::fs::read("video.mp4").await?;
client.post(url).body(bytes).send().await?;
这个写法在小文件上看起来没问题,但一旦文件变大,就会暴露很多问题:
- 文件太大时,一次性读入内存会占用大量 RAM。
- 网络中断后,只能从头再传,用户体验很差。
- 传输进度不好管理,界面上很难显示准确进度。
- 多个文件同时上传下载时,需要自己写队列、并发控制、取消、暂停、恢复。
- 对接阿里云 OSS、Azure Blob 这类对象存储时,还要处理分片、签名、合并、Range 下载等细节。
rusty-cat 要解决的就是这类问题。它是一个 Rust 异步文件传输 SDK,核心能力是:把大文件拆成多个分片,放到后台调度器中执行,并通过回调告诉业务层当前进度和状态。
一句话介绍:
rusty-cat是一个基于 Tokio 的 Rust 异步 SDK,用来做可恢复的大文件上传和下载,支持普通 HTTP、阿里云 OSS、阿里云预签名 URL、Azure Blob、Azure SAS URL 等接入方式。
项目地址:https://github.com/0barman/rusty-cat
2. rusty-cat 适合解决什么问题?
它最适合这些场景:
- 桌面客户端上传大文件到后端或对象存储。
- CLI 工具下载大模型、安装包、日志归档。
- 后台服务把本地文件上传到阿里云 OSS 或 Azure Blob。
- IM、网盘、素材管理、备份工具等需要显示传输进度的应用。
- 需要暂停、恢复、取消任务的文件传输系统。
- 业务层有自己的数据库,希望 SDK 只负责传输,不强行绑定某个数据库。
它不适合这些场景:
- 跨进程消息队列,例如 Kafka、RabbitMQ 那类系统。
- 超高频低延迟交易系统。
- 浏览器内直接运行的 WASM 文件传输。
- 需要 SDK 内置数据库、账号系统、权限系统的一站式产品。
rusty-cat 的定位比较清晰:它不是"网盘系统",而是"文件传输执行层"。
3. 安装 rusty-cat
在你的 Rust 项目里加入依赖:
toml
[dependencies]
rusty-cat = "0.2.2"
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
说明一下:
- crate 名是
rusty-cat,但 Rust 代码里导入时要写rusty_cat,因为 Rust 模块名不能直接使用连字符。 - 本文示例以
0.2.2API 为基础;正式接入时建议先查看 https://crates.io/crates/rusty-cat 和 https://docs.rs/rusty-cat,确认依赖版本仍为当前最新稳定版本。 - 如果你使用的是更新版本,请以对应版本的 docs.rs 文档为准核对 API 名称和 feature flag。
- 推荐统一从
rusty_cat::api::*导入常用类型,这样对小白更友好。
最常用导入方式:
rust
use rusty_cat::api::*;
4. 30 秒看懂最小下载 Demo
下面这个例子演示一个 HTTP 断点下载任务。它创建配置、创建客户端、构造下载任务、提交任务并等待完成。
rust
use rusty_cat::api::{DownloadPounceBuilder, MeowClient, MeowConfig};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let client = MeowClient::new(MeowConfig::default());
let task = DownloadPounceBuilder::new(
"demo.bin",
"./downloads/demo.bin",
1024 * 1024,
"https://example.com/demo.bin",
)
.build();
let outcome = client
.enqueue_and_wait(task, |record| {
println!(
"文件={} 进度={:.2}% 状态={:?}",
record.file_name(),
record.progress() * 100.0,
record.status(),
);
})
.await?;
println!("任务完成:task_id={} payload={:?}", outcome.task_id, outcome.payload);
client.close().await?;
Ok(())
}
注意:上面的 https://example.com/demo.bin 只是占位地址。真正运行时,请替换成一个真实可访问、并且支持 HEAD 和 Range GET 的下载地址;否则示例会因为远程服务不支持断点下载或文件不存在而失败。
这里小白最需要理解 4 个对象:
MeowConfig:SDK 配置,例如并发数、HTTP 超时、队列容量。MeowClient:SDK 的主入口,用来提交、暂停、恢复、取消任务。DownloadPounceBuilder:下载任务构造器,用来填写文件名、保存路径、分片大小和下载 URL。FileTransferRecord:进度回调里的状态快照,里面有任务 ID、文件名、总大小、进度、状态等信息。
5. 下载 Demo 每个参数是什么意思?
这行代码最重要:
rust
let task = DownloadPounceBuilder::new(
"demo.bin",
"./downloads/demo.bin",
1024 * 1024,
"https://example.com/demo.bin",
)
.build();
逐个解释:
| 参数 | 示例 | 作用 |
|---|---|---|
file_name |
"demo.bin" |
展示用文件名,会出现在日志和进度回调里。 |
file_path |
"./downloads/demo.bin" |
下载到本地的目标路径。 |
chunk_size |
1024 * 1024 |
每个分片大小,单位是字节。这里是 1 MiB。传 0 时 SDK 会默认使用 1 MiB。 |
url |
"https://example.com/demo.bin" |
远程下载地址。普通 HTTP 下载要求服务端支持 HEAD 和 Range。 |
下载时,SDK 会先用 HEAD 获取文件大小,再用带 Range 请求头的 GET 分片下载。例如一个 10 MiB 的文件,如果 chunk_size = 1 MiB,理论上会拆成 10 个分片。
为什么推荐 1 MiB 到 8 MiB?
- 太小:HTTP 请求太多,浪费网络和服务端资源。
- 太大:失败后重试成本高,进度更新也不够细。
- 1 MiB 到 8 MiB 对多数对象存储和普通 HTTP 服务是比较容易接受的起点。
6. 最小上传 Demo
上传任务使用 UploadPounceBuilder。如果是普通 HTTP 上传,默认协议会按 multipart/form-data 形式上传分片。
rust
use rusty_cat::api::{MeowClient, MeowConfig, UploadPounceBuilder};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let client = MeowClient::new(MeowConfig::default());
let task = UploadPounceBuilder::new(
"demo.bin",
"./demo.bin",
1024 * 1024,
)
.with_url("https://upload.example.com/files")
.with_max_chunk_retries(3)
.build()?;
let outcome = client
.enqueue_and_wait(task, |record| {
println!(
"上传进度={:.2}% 状态={:?}",
record.progress() * 100.0,
record.status(),
);
})
.await?;
println!("上传完成:task_id={} payload={:?}", outcome.task_id, outcome.payload);
client.close().await?;
Ok(())
}
注意:上面的 https://upload.example.com/files 也是占位上传接口。真实运行前,你需要准备一个能够接收分片 multipart/form-data 请求的后端接口,或者改用阿里云 OSS、Azure Blob、预签名 URL 等 provider 插件。
上传参数解释:
| 参数或方法 | 作用 |
|---|---|
UploadPounceBuilder::new(file_name, file_path, chunk_size) |
创建基于本地文件路径的上传任务。 |
file_name |
展示用文件名,也会进入默认 multipart 表单字段。 |
file_path |
本地源文件路径。build() 时会读取文件 metadata,所以路径不存在会报 std::io::Error。 |
chunk_size |
分片大小。传 0 会变成默认 1 MiB。 |
with_url(url) |
设置上传目标 URL。普通 HTTP 上传一般是后端接口地址;OSS/Azure 直传通常是对象最终 URL。 |
with_max_chunk_retries(3) |
每个分片失败后最多额外重试 3 次。传 0 表示不重试。 |
build() |
生成真正提交给 SDK 的 PounceTask。 |
7. 进度回调里能拿到什么?
enqueue_and_wait 和 try_enqueue 都会接收进度回调。回调参数是 FileTransferRecord。
常用方法:
| 方法 | 含义 |
|---|---|
record.task_id() |
当前任务 ID,后续暂停、恢复、取消都需要它。 |
record.file_sign() |
文件签名。上传通常由 SDK 计算,下载可以通过业务参数设置。 |
record.file_name() |
文件展示名。 |
record.total_size() |
文件总大小,单位字节。 |
record.progress() |
进度比例,范围通常是 0.0..=1.0。显示百分比时乘以 100。 |
record.status() |
当前状态,例如 Pending、Transmission、Complete。 |
record.direction() |
方向,上传或下载。 |
常见状态:
| 状态 | 说明 |
|---|---|
None |
初始占位状态。 |
Pending |
已进入队列,等待执行。 |
Transmission |
正在传输。 |
Paused |
已暂停。 |
Complete |
成功完成。 |
Failed(err) |
失败,并携带错误。 |
Canceled |
已取消。 |
新手注意:回调里不要做很慢的事情。比如不要在每次进度更新时都同步写数据库。更好的做法是把记录发送到你自己的队列,由后台任务批量写入。
8. enqueue_and_wait 和 try_enqueue 有什么区别?
小白建议先用 enqueue_and_wait:
rust
let outcome = client.enqueue_and_wait(task, |record| {
println!("progress={}", record.progress());
}).await?;
它会一直等待任务进入最终状态:
- 成功:返回
Ok(TaskOutcome)。 - 失败:返回
Err(MeowError)。 - 取消:返回
TaskCanceled相关错误。
如果你正在做 GUI、服务端任务队列、批量上传下载,更常用的是 try_enqueue:
rust
let task_id = client
.try_enqueue(
task,
|record| {
println!("progress={:.2}%", record.progress() * 100.0);
},
|task_id, payload| {
println!("任务 {task_id} 完成:{payload:?}");
},
)
.await?;
它只负责"提交任务",不会等完整传输结束。返回的 TaskId 要保存起来,用于暂停、恢复、取消。
try_enqueue 名字里的 try_ 很重要:它采用快速失败的背压语义。如果命令队列瞬间满了,它会返回错误,而不是一直等待。大量任务同时入队时,可以调大 command_queue_capacity,或者在业务层做重试和限速。
9. 为什么 rusty-cat 比手写 reqwest 更省心?
如果你直接用 reqwest,需要自己处理:
- 文件切片读取。
- HTTP Range 下载。
- 上传分片请求。
- 每个分片失败重试。
- 上传前 prepare。
- 上传完成 complete。
- 任务状态机。
- 进度回调。
- 任务暂停、恢复、取消。
- 多任务并发控制。
- 阿里云 OSS 或 Azure Blob 的签名和分片协议。
rusty-cat 把这些拆成几个清晰层次:
| 层次 | 负责什么 |
|---|---|
MeowClient |
SDK 入口、提交任务、生命周期控制。 |
MeowConfig |
并发、队列、HTTP 超时等全局配置。 |
UploadPounceBuilder / DownloadPounceBuilder |
把业务参数组装成任务。 |
| 后台调度器 | 分片调度、重试、状态变化、回调分发。 |
BreakpointUpload / BreakpointDownload |
自定义上传/下载协议,例如 OSS、Azure、预签名 URL。 |
也就是说,你不需要一上来就研究内部调度器。新手先会用 Builder + Client,就能完成大多数接入。
10. 和常见方案简单对比
| 方案 | 优点 | 不足 |
|---|---|---|
直接 reqwest 上传下载 |
灵活、依赖少 | 断点续传、重试、进度、暂停恢复都要自己写。 |
tokio::fs + 手写 Range |
可控性强 | 很容易写出复杂状态机,新手维护成本高。 |
| 云厂商官方 SDK | 云能力完整 | 不一定统一上传下载生命周期,也不一定适配你的本地任务队列。 |
rusty-cat |
聚焦大文件断点上传下载,统一进度和生命周期 | 不是数据库、不是权限系统,业务持久化需要你自己做。 |
11. 安全性和使用边界
rusty-cat 的一个重要设计是:不内置数据库,也不持久化你的云密钥。
这对小白来说可能一开始不太习惯,但它有好处:
- SDK 不决定你用 SQLite、PostgreSQL、Redis 还是别的数据库。
- SDK 不把 AccessKey、Account Key、SAS URL 等敏感数据写进本地文件。
- 你可以把业务记录、权限、用户 ID、凭证引用放在自己的系统里。
- 进程重启后,你可以从自己的数据库读取未完成任务,再重新构造任务入队。
如果是桌面端、移动端、公开客户端,不建议把阿里云 AccessKey Secret 或 Azure Account Key 放到客户端。更安全的方式是让后端生成短期有效的预签名 URL 或 SAS URL。
12. 本篇小结
rusty-cat 的核心价值不是"帮你发一个 HTTP 请求",而是把大文件传输中最容易写乱的部分统一起来:
- 分片。
- 断点续传。
- 失败重试。
- 进度回调。
- 后台调度。
- 暂停、恢复、取消。
- 普通 HTTP、阿里云 OSS、Azure Blob、多种预签名模式。
如果你是 Rust 新手,可以先从 MeowConfig::default()、MeowClient::new(...)、DownloadPounceBuilder 和 UploadPounceBuilder 开始。等你理解了任务和回调,再进入下一篇,系统学习每个配置参数应该怎么填。