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 把一次请求处理抽象成四步:

  1. Routing:路由匹配
  2. Validation:各种 Guard 校验(参数、Header、Body、策略等)
  3. Processing:调用 handler 执行业务逻辑
  4. 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 不是 u8cool 不是 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 的类型:&strStringJson<T>NamedFileRedirectStatusResult/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 体系,但它有边界:

  1. 不能直接终止请求并返回响应
  2. 不能给单个请求注入“任意非请求数据”(这更像 Guard 的活)
  3. 可以阻止应用启动(ignite 阶段做校验)
  4. 可以检查/修改配置(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 的“正确分层姿势”

你写业务时可以按这个优先级选工具:

  1. 路由匹配与参数类型校验:交给 route attribute + 参数类型
  2. 认证/授权/策略:优先 Request Guard(可组合、可测试、可局部使用)
  3. 入参解析:优先 Data Guard(Json/Form/TempFile/Data)
  4. 出参构建:Responder 组合(Option/Result/status/content/Json/Template/Stream)
  5. 全局通用逻辑:Fairing(统一头、trace、metrics、CORS、压缩、启动校验)
  6. 全局共享资源:Managed State(配置、HTTP client、队列、缓存、服务对象)
  7. 单请求缓存:Request-Local State(request id、鉴权结果、耗时统计上下文)
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐