news 2026/4/3 4:13:31

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/3/11 12:56:21

输入书店图书借阅频率,按热度排序,推荐最优书架摆放位置。

1️⃣ 实际应用场景描述 & 痛点引入在书店或图书馆中&#xff0c;图书的借阅频率差异很大。热门书如果被放在角落或高层&#xff0c;读者寻找困难&#xff0c;会降低借阅率&#xff0c;也影响书店销售额。痛点&#xff1a;- 热门书找不到 → 用户体验差。- 冷门书占据黄金位…

作者头像 李华
网站建设 2026/4/1 13:25:59

好写作AI:理科的严谨+文科的飘逸?你的学科偏好AI都懂!

导语&#xff1a;当理科生的图表被说“像说明书”&#xff0c;文科生的论述被批“缺乏证据链”你是否也受过这种“学科误解”的苦&#xff1f;把工科那套“模型-实验-数据”写法用到人类学论文&#xff0c;导师说“毫无人文关怀”用人文学科的“娓娓道来”风格写计算机论文&…

作者头像 李华
网站建设 2026/3/13 1:36:42

COMSOL中的散射体与超表面调控对比:一场电磁波的“舞会”

comsol散射体与超表面的调控对比。 引言 嗯&#xff0c;最近在研究电磁波调控的时候&#xff0c;发现了一个挺有意思的话题&#xff1a;散射体与超表面&#xff08;metasurface&#xff09;在调控电磁波方面的对比。这两者看似都是用来改变电磁波传播特性的工具&#xff0c;但…

作者头像 李华
网站建设 2026/3/30 23:37:47

WebGIS开发智慧校园 | 7.开发准备

智慧校园视频 往期内容&#xff1a; WebGIS开发智慧校园&#xff08;1&#xff09;GIS开发的基本概念 WebGIS开发智慧校园笔记 | 2.WebGIS开发平台介绍 WebGIS开发智慧校园笔记 | 3.开发环境搭建 WebGIS开发智慧校园笔记 | 4.Web开发HTML WebGIS开发智慧校园 | 5.Web开发CSS We…

作者头像 李华
网站建设 2026/3/6 0:54:28

年前AI营销浪潮下,DooTask如何借力AI重塑项目管理新范式

最近&#xff0c;互联网行业掀起了一场以AI为核心的营销革命。腾讯、阿里、百度等巨头投入超45亿元&#xff0c;通过“AI红包”“智能免单”“AI特效互动”等创新玩法&#xff0c;将AI技术深度嵌入消费场景。这场战役不仅验证了AI技术的商业化潜力&#xff0c;更揭示了项目管理…

作者头像 李华