1. Quickstart:把第一个 Rocket 跑起来
安装 Rust(推荐 rustup):
rustup default stable# 也可以用 nightly 获得更好的诊断体验# rustup default nightly创建工程并添加依赖:
cargo new hello-rocket --bincdhello-rocketCargo.toml:
[dependencies] rocket = "0.5.1"src/main.rs:
#[macro_use]externcraterocket;#[get("/")]fnindex()->&'staticstr{"Hello, world!"}#[launch]fnrocket()->_{rocket::build().mount("/",routes![index])}运行:
cargo run你会看到 Rocket 启动日志、路由表,并能在http://127.0.0.1:8000/访问到返回值。
2. Rocket 的生命周期:请求是怎么流转的
把 Rocket 的工作理解成四步会很清晰:
- Routing:根据方法 + path + query + format 等匹配路由
- Validation:对动态参数、请求体、guard 做类型与策略校验(失败可 forward)
- Processing:调用你的 handler(业务逻辑),返回一个实现了 Responder 的类型
- Response:Rocket 生成最终 HTTP Response(状态码、头、body)
核心感受:Rocket 的很多“校验与分支”其实被你写进了函数签名和类型里。
3. Requests:路由声明 = 方法/路径 + 参数类型 + Guard
3.1 动态路径:类型不匹配就自动 forward
#[get("/hello/<name>/<age>/<cool>")]fnhello(name:&str,age:u8,cool:bool)->String{ifcool{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")]fnadmin_panel(admin:AdminUser)->&'staticstr{"admin ok"}写接口只要把AdminUser放进参数列表,就能在“函数签名层面”保证“没有通过校验就不会进入业务逻辑”。
也可以用Option<T>/Result<T, E>捕获 guard 的失败或错误,把“失败即返回/转发”改成“业务内处理”。
3.4 Body Data:JSON / Form / TempFile / Streaming
JSON:
userocket::serde::{Deserialize,json::Json};#[derive(Deserialize)]#[serde(crate="rocket::serde")]structTask<'r>{description:&'rstr,complete:bool}#[post("/todo", data="<task>")]fnnew(task:Json<Task<'_>>){/* ... */}Form、Multipart、TempFile、Data 流式处理这些都属于“数据守卫”(FromData)。你把类型写进签名,就定义了 Rocket 该如何解析与限制请求体。
4. Responses:Responder 让返回值“随便你定义”
Rocket 的 handler 返回类型看起来很自由,是因为只要实现Responder都能返回。
4.1 常用组合:status + content_type 覆盖器
userocket::http::Status;userocket::response::{content,status};#[get("/")]fnjson()->status::Custom<content::RawJson<&'staticstr>>{status::Custom(Status::ImATeapot,content::RawJson(r#"{ "hi": "world" }"#))}4.2 Option / Result:把“404/错误分支”变成类型结构
Option<T>:Some → 正常响应;None → 自动 404Result<T, E>:Ok/Err 分别走不同 responder
适合写“查不到就 404”“失败返回结构化错误”这类逻辑。
4.3 自定义 Responder:统一错误结构/统一响应 Envelope
你可以用 deriveResponder或手写Responder,把状态码、headers、content-type、body 固化为工程规范。生产项目里常见做法是统一一个ApiError/ApiResponse<T>,并配合 catchers 做兜底。
5. State:全局状态、请求内缓存、数据库连接池
5.1 Managed State:每个类型全局最多一个实例
rocket::build().manage(MyConfig{...});使用时在 handler 里注入:
userocket::State;#[get("/")]fnhandler(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 实例:
userocket::{Build,Rocket};pubfnbuild_rocket()->Rocket<Build>{rocket::build().mount("/",routes![/*...*/])}#[launch]fnrocket()->_{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)]modtests{userocket::http::{Status,ContentType,Header};userocket::local::blocking::Client;// 假设你的工程里有 build_rocket(),返回 Rocket<Build>usesuper::build_rocket;#[test]fnping_ok_and_has_trace_headers(){letclient=Client::tracked(build_rocket()).expect("valid rocket");letmutresp=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());letjson:serde_json::Value=resp.into_json().expect("json body");assert_eq!(json["ok"],true);}#[test]fncreate_task_requires_auth_and_returns_json_error(){letclient=Client::tracked(build_rocket()).expect("valid rocket");// 不带 token 或带错 token,应该 forward 到 401 catcher(/api 作用域)letmutresp=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));letjson:serde_json::Value=resp.into_json().expect("json body");assert_eq!(json["code"],401);assert!(json["message"].as_str().unwrap_or("").contains("Unauthorized"));}#[test]fncreate_task_success(){std::env::set_var("API_TOKEN","devtoken");letclient=Client::tracked(build_rocket()).expect("valid rocket");letmutresp=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));letjson:serde_json::Value=resp.into_json().expect("json body");assert_eq!(json["title"],"first");assert_eq!(json["done"],false);}#[test]fncors_headers_present(){letclient=Client::tracked(build_rocket()).expect("valid rocket");letresp=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)]modasync_tests{userocket::local::asynchronous::Client;userocket::http::Status;usesuper::build_rocket;#[rocket::async_test]asyncfnconcurrent_requests_work(){letclient=Client::tracked(build_rocket()).await.expect("valid rocket");letr1=client.get("/api/ping").dispatch();letr2=client.get("/api/ping").dispatch();let(muta,mutb)=rocket::tokio::join!(r1,r2);assert_eq!(a.status(),Status::Ok);assert_eq!(b.status(),Status::Ok);letja:serde_json::Value=a.into_json().await.unwrap();letjb: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=1cargo 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直接看生成代码