news 2026/4/18 2:53:54

Rocket 75 行写一个 Pastebin(顺便把类型安全与安全边界一次讲透)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Rocket 75 行写一个 Pastebin(顺便把类型安全与安全边界一次讲透)

1、目标与路由设计

最终我们有 3 个路由:

  • GET /:返回使用说明
  • POST /:接收原始 body,保存成文件,返回可访问的 URL
  • GET /<id>:根据 id 取回内容(不存在就 404)

存储策略:把每次上传保存到项目根目录的upload/目录里;文件名就是 paste 的id(一串可读的随机字符)。

目录结构大致是:

. ├── Cargo.toml ├── src │ ├── main.rs │ └── paste_id.rs └── upload

2、Cargo.toml:最小依赖

[dependencies] rocket = "0.5.1" rand = "0.8"

3、PasteId:把“合法 ID 的规则”收敛成一个类型

我们不想在每个路由里手写一堆校验逻辑,所以用一个PasteId类型集中定义策略:

  • 生成:从 base62(0-9A-Za-z)里挑字符
  • 落盘:只允许在upload/目录里构造路径
  • 校验:只接受 ASCII 字母数字(你也可以加长度限制等)

src/paste_id.rs

usestd::borrow::Cow;usestd::path::{Path,PathBuf};userand::{self,Rng};userocket::request::FromParam;#[derive(rocket::http::uri::UriDisplayPath)]pubstructPasteId<'a>(Cow<'a,str>);implPasteId<'_>{pubfnnew(size:usize)->PasteId<'static>{constBASE62:&[u8]=b"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";letmutid=String::with_capacity(size);letmutrng=rand::thread_rng();for_in0..size{id.push(BASE62[rng.gen::<usize>()%62]aschar);}PasteId(Cow::Owned(id))}pubfnfile_path(&self)->PathBuf{letroot=concat!(env!("CARGO_MANIFEST_DIR"),"/","upload");Path::new(root).join(self.0.as_ref())}}/// 把不可信的 path segment 变成可信的 PasteId:/// 只允许字母数字(可按需加长度上限/下限)impl<'a>FromParam<'a>forPasteId<'a>{typeError=&'astr;fnfrom_param(param:&'astr)->Result<Self,Self::Error>{letok_chars=param.chars().all(|c|c.is_ascii_alphanumeric());(ok_chars).then(||PasteId(param.into())).ok_or(param)}}

为什么必须做 FromParam?
如果你在retrieve(id: &str)里直接拿用户输入拼路径,用户完全可以请求/_credentials.txt之类的敏感文件名(或更复杂的变种),导致你把不该暴露的文件读出来。这类问题常被归为路径相关的文件泄露/穿越风险。
PasteId+FromParam后,Rocket 会先校验<id>,不合法就根本不会进入你的 handler,从入口把攻击面切断,而且策略集中维护。

4、main.rs:三条路由,流式上传与类型安全 URI

src/main.rs

#[macro_use]externcraterocket;modpaste_id;usepaste_id::PasteId;userocket::data::{Data,ToByteUnit};userocket::http::uri::Absolute;userocket::tokio::fs::{self,File};constID_LENGTH:usize=3;// 实际生产建议从配置读取;这里只是演示constHOST:Absolute<'static>=uri!("http://localhost:8000");#[get("/")]fnindex()->&'staticstr{r#" USAGE POST / accepts raw data in the body of the request and responds with a URL of a page containing the body's content GET /<id> retrieves the content for the paste with id `<id>` "#}#[get("/<id>")]asyncfnretrieve(id:PasteId<'_>)->Option<File>{File::open(id.file_path()).await.ok()}#[post("/", data ="<paste>")]asyncfnupload(paste:Data<'_>)->std::io::Result<String>{// 确保 upload/ 存在(避免首次运行忘建目录)letupload_dir=concat!(env!("CARGO_MANIFEST_DIR"),"/","upload");fs::create_dir_all(upload_dir).await?;letid=PasteId::new(ID_LENGTH);// 128KiB 只是示例:限制请求体大小,防止被大包打爆磁盘/内存/IOpaste.open(128.kibibytes()).into_file(id.file_path()).await?;// 生成绝对 URL:类型安全、路由变更能编译期兜底Ok(uri!(HOST,retrieve(id)).to_string())}#[launch]fnrocket()->_{rocket::build().mount("/",routes![index,retrieve,upload])}

这里顺手把几个关键点都用上了:

  • Data<'_>:代表“未打开的请求体流”,适合大文件/流式写入
  • paste.open(128.kibibytes()):给上传设上限(默认你不设就可能被打穿)
  • into_file(path):把请求体流直接落盘,不用你手写循环读写
  • PasteId: FromParam:动态路径参数的类型化校验
  • PasteId: UriDisplayPath+uri!:构造 URL 时类型安全、自动编码、路由签名变更可编译期报错

5、跑起来:curl 上传与取回

项目根目录先确保有upload/(代码里也会自动建):

cargo run

另开一个终端上传:

echo"Hello, Rocket!"|curl--data-binary @- http://localhost:8000

会返回类似:

http://localhost:8000/eGs

再 GET 一下:

curlhttp://localhost:8000/eGs

你也可以直接看磁盘:

lsuploadcatupload/*

6、这套写法为什么“工程上更靠谱”

1)安全策略集中化
ID 的合法性只在PasteId::from_param定义一次,任何用到PasteId的路由都自动继承这套策略,后续加DELETE /<id>PUT /<id>也不容易漏。

2)类型安全的 URL 生成
uri!(HOST, retrieve(id))会检查路由参数匹配与类型转换;你改了路由签名,编译器会提醒所有构造 URL 的地方一起改。

3)流式落盘 + 明确限制
Data的模式天然适合大 body,配合上限避免资源型攻击或误操作(比如误传超大文件)。

7、可以继续增强的方向(很适合当练手清单)

  • 更严格的PasteId校验:长度范围、黑名单文件名、甚至检查文件是否存在
  • 返回不同状态码:比如上传达到限制时返回 206 Partial Content,否则 201 Created
  • retrieve/upload返回text/plain(用content::RawText或自定义 Responder)
  • 删除与权限:上传返回一个 key,DELETE /<id>必须带正确 key
  • 支持PUT /<id>覆盖内容(同样要 key)
  • 新增GET /<id>/<lang>:做语法高亮(lang也用FromParam校验)
  • 用 Rocket 的 local client 写单元/集成测试
  • 增加定时清理:启动前/启动后起一个任务,清理过期 paste(注意配合优雅停机)
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/11 15:37:57

互联网政务如何利用CKEditor实现微信公众号公式Word导入?

咱是西安一Java程序员&#xff0c;最近接了个CMS企业官网外包&#xff0c;客户突然甩来个“文档导入”的硬需求——要在后台新闻编辑器里加Word/Excel/PPT/PDF导入功能&#xff0c;还要支持Word一键粘贴&#xff01;客户说“高龄编辑敲键盘手酸&#xff0c;直接从Word复制能多活…

作者头像 李华
网站建设 2026/4/18 8:55:48

好写作AI:答辩前夜还在背稿?让AI当你的“模拟评审团”吧!

导语&#xff1a;当你站上答辩讲台&#xff0c;发现台下坐着的导师表情比论文数据还复杂每个经历过答辩的人都懂那种感觉&#xff1a;准备了三个月的讲稿&#xff0c;一开口就忘词以为万无一失&#xff0c;结果被问了个从没想过的问题台下老师皱下眉头&#xff0c;你心跳能漏三…

作者头像 李华
网站建设 2026/4/18 8:51:08

独立开发者的痛点反思:网站太土,信任就没了

一、一个独立开发者的长期主义 几年前&#xff0c;我在业余时间做了一个决定&#xff1a;写一款属于自己的产品。 它不是跟风项目&#xff0c;也不是为了蹭风口。只是单纯地觉得&#xff0c;既然每天都在为别人写系统、做项目&#xff0c;为什么不能做一个真正属于自己的产品&a…

作者头像 李华
网站建设 2026/4/18 8:18:39

TensorFlow - 卷积神经网络

摘要&#xff1a;本文介绍了使用TensorFlow实现卷积神经网络(CNN)的方法。CNN通过局部感受野、卷积和池化三个核心思想处理二维图像数据&#xff0c;广泛应用于图像识别任务。文章详细展示了构建CNN的完整流程&#xff1a;从导入模块、定义参数、创建卷积层和全连接层&#xff…

作者头像 李华
网站建设 2026/3/16 6:56:30

2026中专大数据与会计专业数据分析发展路径

专业背景与行业需求大数据与会计专业的融合已成为现代职业教育的重要方向。会计行业数字化转型推动了对数据分析技能的迫切需求&#xff0c;从传统账务处理转向数据驱动的决策支持。2026年行业岗位预计要求从业者具备财务数据清洗、预测建模及自动化报表生成能力&#xff0c;中…

作者头像 李华