Rocket 0.5 上手到路由、请求校验、响应构建、状态管理与 Fairings 中间件
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 把一次请求处理抽象成四步:
- Routing:路由匹配
- Validation:各种 Guard 校验(参数、Header、Body、策略等)
- Processing:调用 handler 执行业务逻辑
- Response:把 handler 的返回值转换成 HTTP 响应并发回
理解这个顺序很关键:你写的 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 自动变 404Result<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 体系,但它有边界:
- 不能直接终止请求并返回响应
- 不能给单个请求注入“任意非请求数据”(这更像 Guard 的活)
- 可以阻止应用启动(ignite 阶段做校验)
- 可以检查/修改配置(ignite 阶段很常用)
经验法则: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 的“正确分层姿势”
你写业务时可以按这个优先级选工具:
- 路由匹配与参数类型校验:交给 route attribute + 参数类型
- 认证/授权/策略:优先 Request Guard(可组合、可测试、可局部使用)
- 入参解析:优先 Data Guard(Json/Form/TempFile/Data)
- 出参构建:Responder 组合(Option/Result/status/content/Json/Template/Stream)
- 全局通用逻辑:Fairing(统一头、trace、metrics、CORS、压缩、启动校验)
- 全局共享资源:Managed State(配置、HTTP client、队列、缓存、服务对象)
- 单请求缓存:Request-Local State(request id、鉴权结果、耗时统计上下文)
更多推荐

所有评论(0)