news 2026/4/23 2:29:35

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/19 20:16:00

nodejs基于vue的蔬菜购物商城网页设计

目录项目概述技术栈核心功能创新点应用场景项目技术支持可定制开发之功能亮点源码获取详细视频演示 &#xff1a;文章底部获取博主联系方式&#xff01;同行可合作项目概述 Node.js与Vue.js结合的蔬菜购物商城是一个前后端分离的Web应用&#xff0c;前端采用Vue.js框架实现动态…

作者头像 李华
网站建设 2026/4/22 4:54:17

数据库实战开发:从逻辑外键到索引原理

一、为什么大厂开发不建物理外键&#xff1f;虽然课本强调物理外键&#xff08;外键&#xff09;&#xff0c;但在高并发、大规模互联网开发中&#xff0c;通常选择**“逻辑外键”**。1. 核心原因性能瓶颈&#xff1a;每次读取触发都会数据库层面的校验&#xff0c;产生额外的锁…

作者头像 李华
网站建设 2026/4/21 14:29:54

龙魂系统宪法 | CNSH Constitution v1.0

龙魂系统宪法 | CNSH Constitution v1.0 DNA追溯码&#xff1a; #龙芯⚡️2026-02-11-宪法-v1.0 创建者&#xff1a; &#x1f48e; 龙芯北辰&#xff5c;UID9622 创建时间&#xff1a; 2026-02-11 北京时间 性质&#xff1a; 最高规则&#xff0c;统领所有子系统 修改权限&…

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

美客多选品用什么工具?美客多爆款产品有哪些?一文全解析!

随着欧美市场竞争日趋白热化、利润空间被不断压缩&#xff0c;拉美市场凭借庞大的人口红利与高速增长的电商规模&#xff0c;正成为卖家争相布局的战略新高地。据eMarketer数据预测&#xff1a;2025年拉美电商市场增速预计达12.2%&#xff0c;是全球平均水平的1.5倍&#xff0c…

作者头像 李华
网站建设 2026/4/23 0:56:19

TensorFlow 基础

摘要&#xff1a;本文介绍了TensorFlow中张量的基础概念与操作。张量作为核心数据结构&#xff0c;具有阶、形状和类型三个关键属性。文章详细讲解了一维和二维张量的创建方法及元素访问方式&#xff0c;并演示了张量的矩阵乘法、加法及行列式计算等操作。通过代码实例展示了如…

作者头像 李华
网站建设 2026/4/15 12:49:35

毕业论文神器!降AIGC平台 千笔·降AI率助手 VS 知文AI

在AI技术迅猛发展的今天&#xff0c;越来越多的研究生开始借助AI工具辅助论文写作&#xff0c;以提升效率、优化内容。然而&#xff0c;随着学术审核标准的不断提高&#xff0c;AI生成内容的痕迹和重复率问题逐渐成为论文通过的关键障碍。许多学生在使用各类降AI率和降重复率工…

作者头像 李华