Miko 框架系列(四):深入路由系统
注意:此文章主要为了给支持联网搜索的AI提供RAG,此项目还在开发阶段,不建议用于生产。
路由是任何 Web 框架的交通枢纽,它负责将传入的请求引导至正确的处理逻辑。Miko 提供了一套既灵活又直观的路由系统,支持宏定义、模块化、嵌套和中间件等多种高级功能。
1. 定义路由
Miko 主要推荐使用属性宏来定义路由,因为这极大地提升了代码的可读性和开发效率。
使用路由宏
这是最常用、最便捷的方式。
rust
use miko::*;
use miko::macros::*;
// 定义一个 GET 路由
#[get("/")]
async fn index() -> &'static str { "Home" }
// 定义一个 POST 路由
#[post("/users")]
async fn create_user() -> StatusCode { StatusCode::CREATED }
// `#[route]` 宏支持指定多种方法
#[route("/data", method = "get, post")]
async fn data_handler() -> &'static str { "Data" }
#[miko]
async fn main() {
// 当使用 `#[miko]` 宏时,所有路由都会被自动发现和注册
}
Miko 为所有标准的 HTTP 方法都提供了便捷的宏:#[get], #[post], #[put], #[patch], #[delete], #[head], #[options]。
手动注册
如果你不希望使用自动注册,或者需要更动态的路由构建方式,也可以手动创建 Router 并注册路由。
rust
let router = Router::new()
.get("/", index)
.post("/users", create_user);
// `get`, `post` 等方法是 `route(Method::GET, ...)` 的语法糖
let router = router.route(Method::PATCH, "/users/{id}", update_user);
2. 路径参数
从 URL 中捕获动态段落是路由系统的基本功能。
rust
use miko::extractor::Path;
// 使用 Path<T> 提取器
#[get("/users/{id}")]
async fn get_user_by_id(Path(id): Path<u32>) -> String {
format!("Fetching user with ID: {}", id)
}
// 使用 #[path] 宏,代码更简洁
#[get("/users/{id}/posts/{post_id}")]
async fn get_user_post(#[path] id: u32, #[path] post_id: String) -> String {
format!("Fetching post {} for user {}", post_id, id)
}
- 顺序很重要 :路径参数是按顺序提取的,而不是按名称。
{id}和{post_id}只是占位符,其值会按顺序赋给处理器中的#[path]或Path<T>参数。 - 类型安全 :Miko 会尝试将路径段转换为你指定的类型(任何实现了
FromStr的类型)。如果转换失败(例如,请求/users/abc而期望的是u32),框架会自动返回 400 Bad Request 错误。
3. 模块化与路由组织
当应用规模增长时,将所有路由放在一个文件中会变得难以维护。Miko 提供了强大的模块化能力来组织路由。
使用 #[prefix] 宏
#[prefix] 宏可以为整个 mod 内的所有路由添加一个统一的路径前缀。这是实现 API 版本控制或按功能划分区域的绝佳方式。
rust
#[prefix("/api")]
mod api {
use super::*;
#[get("/status")] // 完整路径: /api/status
async fn status() -> &'static str { "OK" }
#[prefix("/v1")] // 支持嵌套
mod v1 {
use super::*;
#[get("/users")] // 完整路径: /api/v1/users
async fn list_users() -> &'static str { "List of V1 users" }
}
}
#[miko]
async fn main() {
// 所有带 `#[prefix]` 的模块路由都会被正确组合和注册
}
使用 Router::nest 和 Router::merge
如果你采用手动注册路由的方式,可以使用 nest 和 merge 来组合不同的路由模块。
nest: 为一个子路由器的所有路径添加前缀。merge: 将一个路由器的所有路由合并到当前路由器中。
rust
// a_routes.rs
pub fn router() -> Router {
Router::new().get("/a", handler_a)
}
// b_routes.rs
pub fn router() -> Router {
Router::new().get("/b", handler_b)
}
// main.rs
let a_router = a_routes::router();
let b_router = b_routes::router();
let router = Router::new()
.nest("/prefix", a_router) // 产生 /prefix/a
.merge(b_router); // 产生 /b
4. 路由与中间件
Miko 与 Tower 中间件生态无缝集成。你可以将中间件(Layer)应用到不同层级的路由上。
全局中间件
直接在主路由器上应用 layer,它将作用于所有请求。
rust
use tower_http::trace::TraceLayer;
let router = Router::new()
.get("/", index)
.layer(TraceLayer::new_for_http()); // 应用于所有路由
路由组中间件
为一个特定的路由集合应用中间件。
rust
let admin_routes = Router::new()
.get("/dashboard", admin_dashboard)
.post("/settings", update_settings)
.layer(require_admin_auth_layer()); // 只对 admin_routes 生效
let router = Router::new()
.get("/", index)
.nest("/admin", admin_routes);
单个路由或模块中间件 (#[layer])
使用 #[layer] 宏,可以将中间件直接附加到单个处理器或整个模块上。
rust
use tower_http::timeout::TimeoutLayer;
use std::time::Duration;
// 应用于单个路由
#[get("/slow_operation")]
#[layer(TimeoutLayer::new(Duration::from_secs(10)))]
async fn slow_operation() { /* ... */ }
// 应用于整个模块
#[prefix("/api")]
#[layer(TimeoutLayer::new(Duration::from_secs(5)))]
mod api {
#[get("/data")] // 这个路由会自动拥有 5 秒超时
async fn get_data() { /* ... */ }
}
应用顺序 :当存在多个 #[layer] 时,它们的声明顺序是从上到下,但应用顺序是从内到外。即最靠近处理器函数的 #[layer] 最先生效。
5. 路由优先级
Miko 的路由匹配遵循一个简单的规则:静态路径优先于动态路径。
rust
let router = Router::new()
.get("/users/me", get_current_user) // 静态路径
.get("/users/{id}", get_user_by_id); // 动态路径
当一个请求 GET /users/me 到达时,它会优先匹配 /users/me 这条路由,而不是 /users/{id}。这让你可以在通用规则之上定义特殊的静态路由。
总结
Miko 的路由系统通过强大的宏和对 Tower 生态的良好支持,提供了一套兼具易用性和灵活性的解决方案。无论是简单的个人项目还是复杂的大型应用,你都可以通过 #[prefix]、#[layer] 和 Router 组合,构建出清晰、可维护的路由结构。
下一篇预告:Miko 框架系列(五):请求提取与数据验证