1. 快速跑起来:Hello Rocket
初始化项目:
cargo new hello-rocket –bin
cd hello-rocket
Cargo.toml 添加依赖:
[dependencies]
rocket = "0.5.1"
main.rs:
#[macro_use] extern crate rocket;
#[get("/")]
fn index() -> &'static str {
"Hello, world!"
}
#[launch]
fn rocket() -> _ {
rocket::build().mount("/", routes![index])
}
运行:
cargo run
访问 http://127.0.0.1:8000/ 即可看到返回。
你会注意到 Rocket 的“入口”不是写一个 main 然后手工起 server,而是用 #[launch] 生成 main 并启动异步运行时(tokio)。
2. Rocket 的生命周期:从 Request 到 Response
Rocket 把一次请求处理抽象成四步:
理解这个顺序很关键:你写的 handler 其实只负责第 3 步;第 1/2/4 步主要由 Rocket 通过宏生成代码来完成。
3. Routing:声明式路由与 Mount
最基础的路由:
#[get("/world")]
fn world() -> &'static str {
"hello, world!"
}
Mount 到 base path:
rocket::build().mount("/hello", routes![world]);
请求 GET /hello/world 才会命中。
Mount 可以链式调用,同一个 route 也可以挂多个前缀:
rocket::build()
.mount("/hello", routes![world])
.mount("/hi", routes![world]);
4. Requests:把“校验”写进类型系统
Rocket 最舒服的地方在于:路由属性(路径/方法/format/data 等)+ 函数签名(参数类型、guard 类型)一起描述“什么请求才算合法”。
4.1 方法与动态路径
动态路径参数:
#[get("/hello/<name>")]
fn hello(name: &str) -> String {
format!("Hello, {}!", name)
}
多个动态参数,直接用 Rust 类型做校验:
#[get("/hello/<name>/<age>/<cool>")]
fn hello(name: &str, age: u8, cool: bool) -> String {
if cool {
format!("You're a cool {} year old, {}!", age, name)
} else {
format!("{}, we need to talk about your coolness.", name)
}
}
age 不是 u8、cool 不是 bool 时会发生什么?Rocket 会把请求“转发”(forward)给下一个同样能匹配路径的方法更低优先级的路由。
4.2 Rank:解决路由碰撞与转发链
同一路径不同参数类型的典型写法:
#[get("/user/<id>")]
fn user(id: usize) { /* … */ }
#[get("/user/<id>", rank = 2)]
fn user_int(id: isize) { /* … */ }
#[get("/user/<id>", rank = 3)]
fn user_str(id: &str) { /* … */ }
命中顺序:rank 越小越优先(默认 rank 是负数范围,越小越优先)。参数解析失败会 forward 到下一个候选路由,直到成功或最终触发 catcher。
4.3 Request Guards:把鉴权/策略封装成类型
Request Guard 的本质:实现 FromRequest 的类型。只要把它放进 handler 参数列表里,Rocket 就会自动执行校验。
#[get("/admin")]
fn admin_panel(admin: AdminUser) -> &'static str {
"admin"
}
建议:鉴权这种“不是所有路由都需要”的事情,优先用 Guard,而不是 Fairing(除非你真的需要全站强制鉴权)。
4.4 Body Data:Json/Form/TempFile/Data
JSON 入参:开启 rocket 的 json feature,然后用 Json<T>(T 需 Deserialize):
use rocket::serde::{Deserialize, json::Json};
#[derive(Deserialize)]
#[serde(crate = "rocket::serde")]
struct Task<'r> {
description: &'r str,
complete: bool
}
#[post("/todo", data = "<task>", format = "json")]
fn new(task: Json<Task<'_>>) { /* … */ }
表单:Form<T> + FromForm 派生(支持 multipart / x-www-form-urlencoded):
use rocket::form::Form;
#[derive(FromForm)]
struct Task<'r> {
complete: bool,
r#type: &'r str,
}
#[post("/todo", data = "<task>")]
fn new(task: Form<Task<'_>>) { /* … */ }
上传文件:TempFile 直接落临时文件,想持久化就 persist_to()。
流式读取 body:用 Data,并且必须显式设置读取上限(防止 DoS)。
4.5 Query Strings:像表单一样强大
Query 参数其实就是“URL 上的表单字段”,支持嵌套/集合。基本形态:
#[get("/?<name>&<color>&<other>")]
fn hello(name: &str, color: Vec<Color>, other: Option<usize>) { /* … */ }
4.6 Error Catchers:统一错误出口
use rocket::Request;
#[catch(404)]
fn not_found(req: &Request) -> String {
format!("'{}' not found", req.uri())
}
#[launch]
fn rocket() -> _ {
rocket::build().register("/", catchers![not_found])
}
catcher 有 scope(register 的 base path),base 越长越优先。
5. Responses:返回值“看起来随便”,其实很讲究
Rocket 允许 handler 返回任何实现了 Responder 的类型:&str、String、Json<T>、NamedFile、Redirect、Status、Result/Option 组合等。
5.1 包装 Responder:status/content 的组合
比如设置状态码 + Content-Type:
use rocket::http::{Status, ContentType};
#[get("/")]
fn json() -> (Status, (ContentType, &'static str)) {
(Status::ImATeapot, (ContentType::JSON, r#"{ "hi": "world" }"#))
}
也可以 derive 一个自定义 responder(更可复用)。
5.2 Option / Result:把“找不到/失败”变成语义
Option<T>:None 自动变 404
Result<T, E>:Ok/Err 各自响应(E 也得是 Responder)
这让你写“文件服务器”这类逻辑非常简洁。
5.3 Async Streams / WebSockets / Templates / uri!
Rocket 0.5 在流式响应(SSE/ReaderStream/TextStream)和 WebSocket(rocket_ws)上都挺顺手。模板用 rocket_dyn_templates::Template,URI 生成用 uri!,能在编译期校验参数/类型,避免手拼字符串出 bug。
6. State:全局状态与请求级状态
Rocket 把 state 分两类:Managed State(全局)与 Request-Local State(请求内缓存)。
6.1 Managed State:全局共享、按类型唯一
注册:
rocket::build().manage(MyState { /* … */ })
使用:在 handler 里加 &State<T>:
use rocket::State;
#[get("/count")]
fn count(st: &State<HitCount>) -> String { /* … */ }
注意:必须 Send + Sync,因为 Rocket 多线程并发访问。
6.2 Request-Local State:一次请求内生成一次、复用多次
request.local_cache(|| …):同类型在一个请求里只会初始化一次,后续复用,非常适合“请求级 ID”“鉴权结果缓存”等。
7. Fairings:Rocket 的结构化中间件
Fairings 是 Rocket 的 middleware 体系,但它有边界:
经验法则:Fairing 做“全局性事情”,例如统一安全头、统一日志、统计、压缩、CORS、请求耗时、全站 trace id、启动时配置校验。鉴权优先用 Guard,除非你就是要全站强制鉴权。
7.1 五类回调(按生命周期)
- on_ignite:点火阶段,能改 Rocket 实例,常用来解析/校验配置、塞进 Managed State
- on_liftoff:启动后立即触发,适合启动伴生服务、打日志
- on_request:请求刚到,能改 request、看部分 data,但不能直接响应
- on_response:响应即将发出,可重写响应、加 header、把 404 改成你想要的样子
- on_shutdown:触发优雅停机时调用,常用清理资源/通知外部系统
Fairing 的执行顺序:按 attach 顺序,越先 attach 越先执行。
7.2 一个实用的 Fairing:全站 RequestId + 响应头注入
这就是“Fairing + Request-Local State”最常见的组合:每个请求生成一次 request id,并在 response 里注入 X-Request-Id。
核心思路:
- 在 on_request 里触发 local_cache 创建 ID
- 在 on_response 里读出同一个 ID 写到 header
(你之前我给的 demo 骨架里已经包含了这段实现。)
7.3 统计计数器:用 Fairing 避免污染 handler 签名
像 GET/POST 次数统计这种“全局行为”,Fairing 比 Guard 更合适,因为你不想每个路由都加参数。
请求阶段统计,响应阶段把某个 404 路径改写成统计结果,是一个很典型的“response fairing 重写 404”用法。
7.4 AdHoc Fairings:轻量版本
当你只是想挂一个小闭包做点事,AdHoc 会比手写 Fairing trait 省事很多:
- AdHoc::on_ignite(…)
- AdHoc::on_liftoff(…)
- AdHoc::on_request(…)
- AdHoc::on_response(…)
- AdHoc::on_shutdown(…)
非常适合:启动打印、简单 header 注入、临时调试、配置检查等。
8. 一个落地建议:Rocket 的“正确分层姿势”
你写业务时可以按这个优先级选工具:



