1. Quickstart:把第一个 Rocket 跑起来
安装 Rust(推荐 rustup):
rustup default stable
# 也可以用 nightly 获得更好的诊断体验
# rustup default nightly
创建工程并添加依赖:
cargo new hello-rocket –bin
cd hello-rocket
Cargo.toml:
[dependencies]
rocket = "0.5.1"
src/main.rs:
#[macro_use] extern crate rocket;
#[get("/")]
fn index() -> &'static str {
"Hello, world!"
}
#[launch]
fn rocket() -> _ {
rocket::build().mount("/", routes![index])
}
运行:
cargo run
你会看到 Rocket 启动日志、路由表,并能在 http://127.0.0.1:8000/ 访问到返回值。
2. Rocket 的生命周期:请求是怎么流转的
把 Rocket 的工作理解成四步会很清晰:
核心感受:Rocket 的很多“校验与分支”其实被你写进了函数签名和类型里。
3. Requests:路由声明 = 方法/路径 + 参数类型 + Guard
3.1 动态路径:类型不匹配就自动 forward
#[get("/hello/<name>/<age>/<cool>")]
fn hello(name: &str, age: u8, cool: bool) -> String {
if cool { format!("Cool {}: {}", age, name) } else { format!("{}…", name) }
}
age: u8、cool: bool 都是“参数守卫”:如果解析失败,Rocket 会把请求转发给下一个同路径的候选路由(由 rank 决定优先级),直到匹配成功或触发 catcher。
3.2 Rank:同一路径多版本匹配的“路由优先级”
典型用法:对 /user/<id> 同时支持整数与字符串版本,整数先尝试,失败再落到字符串。
3.3 Request Guards:把认证/授权/校验变成类型
只要一个类型实现了 FromRequest,它就能成为 request guard:
#[get("/admin")]
fn admin_panel(admin: AdminUser) -> &'static str {
"admin ok"
}
写接口只要把 AdminUser 放进参数列表,就能在“函数签名层面”保证“没有通过校验就不会进入业务逻辑”。
也可以用 Option<T> / Result<T, E> 捕获 guard 的失败或错误,把“失败即返回/转发”改成“业务内处理”。
3.4 Body Data:JSON / Form / TempFile / Streaming
JSON:
use rocket::serde::{Deserialize, json::Json};
#[derive(Deserialize)]
#[serde(crate="rocket::serde")]
struct Task<'r> { description: &'r str, complete: bool }
#[post("/todo", data="<task>")]
fn new(task: Json<Task<'_>>) { /* … */ }
Form、Multipart、TempFile、Data 流式处理这些都属于“数据守卫”(FromData)。你把类型写进签名,就定义了 Rocket 该如何解析与限制请求体。
4. Responses:Responder 让返回值“随便你定义”
Rocket 的 handler 返回类型看起来很自由,是因为只要实现 Responder 都能返回。
4.1 常用组合:status + content_type 覆盖器
use rocket::http::Status;
use rocket::response::{content, status};
#[get("/")]
fn json() -> status::Custom<content::RawJson<&'static str>> {
status::Custom(Status::ImATeapot, content::RawJson(r#"{ "hi": "world" }"#))
}
4.2 Option / Result:把“404/错误分支”变成类型结构
- Option<T>:Some → 正常响应;None → 自动 404
- Result<T, E>:Ok/Err 分别走不同 responder
适合写“查不到就 404”“失败返回结构化错误”这类逻辑。
4.3 自定义 Responder:统一错误结构/统一响应 Envelope
你可以用 derive Responder 或手写 Responder,把状态码、headers、content-type、body 固化为工程规范。生产项目里常见做法是统一一个 ApiError / ApiResponse<T>,并配合 catchers 做兜底。
5. State:全局状态、请求内缓存、数据库连接池
5.1 Managed State:每个类型全局最多一个实例
rocket::build().manage(MyConfig { … });
使用时在 handler 里注入:
use rocket::State;
#[get("/")]
fn handler(cfg: &State<MyConfig>) -> String { /* … */ }
前提:必须 Send + Sync,因为 Rocket 并发处理请求。
5.2 Request-Local State:每个请求的“缓存位”
request.local_cache(|| …) 很适合做“每个请求只计算一次”的昂贵逻辑,比如 request_id、鉴权解析结果、计时等。
5.3 rocket_db_pools:官方连接池方式(ORM 无关)
三步走:启用 driver feature → Rocket.toml 配数据库 → #[derive(Database)] + Connection<T> guard 注入。
这在工程化里很关键:你不需要自己管理连接生命周期,Rocket 用 guard 保证“拿到的就是可用连接”。
6. Fairings:Rocket 的结构化中间件(但要用对)
Fairing 能挂在生命周期的多个回调点上:Ignite、Liftoff、Request、Response、Shutdown。它适合做“全局横切关注点”:
- 访问日志、计时、Tracing
- 全局安全头、CORS
- 统一重写某些响应(例如把默认 404 改成 JSON)
不建议用 fairing 处理“局部鉴权”,因为 request guard 更干净、更可组合。
同时要记住两个点:
- fairing 不能直接终止请求并返回响应(它不是传统意义的 middleware)
- fairing 按 attach 顺序执行,顺序可能影响结果
7. Testing:Rocket 的测试体系怎么写才舒服
Rocket 的测试核心思路是:对一个“本地 Rocket 实例”做请求派发(local dispatch),完全不需要起真实端口。
7.1 推荐模式:把 build 与 launch 分离
这样测试能直接拿到 Rocket 实例:
use rocket::{Build, Rocket};
pub fn build_rocket() -> Rocket<Build> {
rocket::build().mount("/", routes![/*…*/])
}
#[launch]
fn rocket() -> _ {
build_rocket()
}
7.2 Blocking Client:单请求断言最省事
rocket::local::blocking::Client 是多数测试的首选:
- status / headers / content_type 断言简单
- body 用 into_string() / into_json() 一步读出
下面给你一组“工程级测试”例子,覆盖:正常响应、JSON、鉴权失败走 catcher、Fairing 注入头部。
把它放到 src/main.rs 里(或 src/tests.rs 引入):
#[cfg(test)]
mod tests {
use rocket::http::{Status, ContentType, Header};
use rocket::local::blocking::Client;
// 假设你的工程里有 build_rocket(),返回 Rocket<Build>
use super::build_rocket;
#[test]
fn ping_ok_and_has_trace_headers() {
let client = Client::tracked(build_rocket()).expect("valid rocket");
let mut resp = client.get("/api/ping").dispatch();
assert_eq!(resp.status(), Status::Ok);
assert_eq!(resp.content_type(), Some(ContentType::JSON));
// Fairing 注入的追踪头(比如 X-Request-Id / X-Response-Time-ms)
assert!(resp.headers().get_one("X-Request-Id").is_some());
assert!(resp.headers().get_one("X-Response-Time-ms").is_some());
let json: serde_json::Value = resp.into_json().expect("json body");
assert_eq!(json["ok"], true);
}
#[test]
fn create_task_requires_auth_and_returns_json_error() {
let client = Client::tracked(build_rocket()).expect("valid rocket");
// 不带 token 或带错 token,应该 forward 到 401 catcher(/api 作用域)
let mut resp = client.post("/api/tasks")
.header(ContentType::JSON)
.body(r#"{ "title": "x" }"#)
.dispatch();
assert_eq!(resp.status(), Status::Unauthorized);
assert_eq!(resp.content_type(), Some(ContentType::JSON));
let json: serde_json::Value = resp.into_json().expect("json body");
assert_eq!(json["code"], 401);
assert!(json["message"].as_str().unwrap_or("").contains("Unauthorized"));
}
#[test]
fn create_task_success() {
std::env::set_var("API_TOKEN", "devtoken");
let client = Client::tracked(build_rocket()).expect("valid rocket");
let mut resp = client.post("/api/tasks")
.header(ContentType::JSON)
.header(Header::new("Authorization", "Bearer devtoken"))
.body(r#"{ "title": "first" }"#)
.dispatch();
assert_eq!(resp.status(), Status::Created);
assert_eq!(resp.content_type(), Some(ContentType::JSON));
let json: serde_json::Value = resp.into_json().expect("json body");
assert_eq!(json["title"], "first");
assert_eq!(json["done"], false);
}
#[test]
fn cors_headers_present() {
let client = Client::tracked(build_rocket()).expect("valid rocket");
let resp = client.get("/api/ping").dispatch();
// 你的 CORS Fairing 若设置了这些头,这里就能直接断言
assert!(resp.headers().get_one("Access-Control-Allow-Origin").is_some());
assert!(resp.headers().get_one("Access-Control-Allow-Methods").is_some());
}
}
你会注意到:测试里大量使用 unwrap/expect 是完全合理的,因为测试失败就该 panic。
7.3 异步测试:当你需要“并发派发多个请求”时
Blocking API 不能同时派发多个请求。如果你要验证“并发请求互相影响”(比如 barrier、锁、队列消费),就用 asynchronous client。
Rocket 提供异步测试入口(常见写法之一是 rocket::local::asynchronous::Client + #[rocket::async_test]):
#[cfg(test)]
mod async_tests {
use rocket::local::asynchronous::Client;
use rocket::http::Status;
use super::build_rocket;
#[rocket::async_test]
async fn concurrent_requests_work() {
let client = Client::tracked(build_rocket()).await.expect("valid rocket");
let r1 = client.get("/api/ping").dispatch();
let r2 = client.get("/api/ping").dispatch();
let (mut a, mut b) = rocket::tokio::join!(r1, r2);
assert_eq!(a.status(), Status::Ok);
assert_eq!(b.status(), Status::Ok);
let ja: serde_json::Value = a.into_json().await.unwrap();
let jb: serde_json::Value = b.into_json().await.unwrap();
assert_eq!(ja["ok"], true);
assert_eq!(jb["ok"], true);
}
}
7.4 Codegen Debug:类型错误“看不懂”时的杀手锏
Rocket 大量依赖宏生成代码。遇到诡异类型报错时,打开代码生成调试输出往往能一眼看穿:
ROCKET_CODEGEN_DEBUG=1 cargo build
它会把路由宏生成的 facade handler、匹配逻辑等打印出来,帮你定位“到底是谁在推导哪个类型”。
8. 一份工程化 Checklist(照着做就很稳)
- 路由层:尽量把校验写进签名(类型 + guards),业务逻辑更干净
- 认证授权:优先 Request Guard;全局策略才上 Fairing
- 错误体系:API 路径作用域注册 JSON catchers;业务错误用统一 ApiError/ApiResponse
- 状态:全局用 Managed State;每请求缓存用 local_cache
- I/O:能 async 就 async;不得不阻塞就 spawn_blocking
- 测试:优先 blocking client;需要并发再用 async client
- 复杂报错:用 ROCKET_CODEGEN_DEBUG=1 直接看生成代码
网硕互联帮助中心





评论前必须登录!
注册