Axum文档 ~ 2.路由
Struct Router(路由器结构体)
pub struct Router<S = ()> { /* private fields */ }
用于组合处理器(handler)和服务(service)的路由器类型。
Router<S>
表示一个 “缺少类型为 S
的状态才能处理请求” 的路由器。因此,只有 Router<()>
(即不缺少任何状态的路由器)才能传递给 serve
方法。更多详情请参阅 Router::with_state
方法。
Implementations(实现)
Source
impl<S> Router<S>
whereS: Clone + Send + Sync + 'static,
创建路由
pub fn new() -> Self
创建一个新的 Router
。
除非你添加额外的路由,否则它会对所有请求返回 404 Not Found
(未找到)响应。
关闭针对 0.7 版本路由匹配语法的兼容性检查
pub fn without_v07_checks(self) -> Self
关闭针对 0.7 版本路由匹配语法的兼容性检查。
这允许使用以冒号 :
或星号 *
开头的路径,否则这些路径是被禁止的。
示例
use axum::{routing::get,Router,
};let app = Router::<()>::new().without_v07_checks().route("/:colon", get(|| async {})).route("/*asterisk", get(|| async {}));// 我们的应用现在接受
// - GET /:colon
// - GET /*asterisk
如果不先调用此方法就添加此类路由,会触发 panic
(崩溃)。
use axum::{routing::get,Router,
};// 这会触发 panic...
let app = Router::<()>::new().route("/:colon", get(|| async {}));
Merging(合并)
当两个路由器合并时,如果两个路由器均已关闭 v0.7 版本检查,则合并后路由器上的路由注册也会禁用该检查。
Nesting(嵌套)
每个路由器都需要显式关闭该检查。嵌套一个已启用或已禁用该检查的路由器,对外部路由器没有影响。
添加另一条路由
pub fn route(self, path: &str, method_router: MethodRouter<S>) -> Self
向路由器中添加另一条路由。
path
是由 /
分隔的路径段组成的字符串。每个路径段可以是静态段、捕获段(capture)或通配符段(wildcard)。
如果路径与 path
匹配,method_router
是应接收请求的 MethodRouter
。通常,method_router
是一个被包裹在 get
等方法路由器中的处理器(handler)。有关处理器的更多详情,请参阅 handler
相关文档。
Static paths(静态路径)
示例:
/
/foo
/users/123
如果传入的请求与路径完全匹配,对应的服务(service)将被调用。
Captures(捕获段)
路径中可以包含 / {key}
这样的路径段,它能匹配任意单个路径段,并会将捕获到的值存储在 key
对应的位置。捕获到的值可以是空字符串,但无效路径 //
除外。
示例:
/ {key}
/users/{id}
/users/{id}/tweets
可以使用 Path
提取捕获到的值。有关更多详情,请参阅其文档。
无法创建仅匹配特定类型(如数字或正则表达式)的路径段。你必须在处理器中手动处理这类需求。
可以使用 MatchedPath
提取匹配到的路径,而非实际请求路径。
Wildcards(通配符段)
路径可以以 / {*key}
结尾,它能匹配所有后续路径段,并会将这些路径段存储在 key
对应的位置。
示例:
/ {*key}
/assets/{*path}
/{id}/{repo}/{*tree}
注意,/{*key}
不匹配空路径段。因此:
/{*key}
不匹配/
,但匹配/a
、/a/
等。
/x/{*key}
不匹配/x
或/x/
,但匹配/x/a
、/x/a/
等。
通配符捕获到的值也可以使用 Path
提取:
use axum::{Router,routing::get,extract::Path,
};let app: Router = Router::new().route("/{*key}", get(handler));async fn handler(Path(path): Path<String>) -> String {path
}
注意,结果中不包含开头的斜杠 /
,例如,对于路由 /foo/{*rest}
和请求路径 /foo/bar/baz
,rest
的值将是 bar/baz
。
Accepting multiple methods(接受多种请求方法)
若要为同一条路由接受多种请求方法,你可以一次性添加所有处理器:
use axum::{Router, routing::{get, delete}, extract::Path};
let app = Router::new().route("/",get(get_root).post(post_root).delete(delete_root),
);
async fn get_root() {}
async fn post_root() {}
async fn delete_root() {}
或者你也可以逐个添加:
let app = Router::new().route("/", get(get_root)).route("/", post(post_root)).route("/", delete(delete_root));
More examples(更多示例)
use axum::{Router, routing::{get, delete}, extract::Path};
let app = Router::new().route("/", get(root)).route("/users", get(list_users).post(create_user)).route("/users/{id}", get(show_user)).route("/api/{version}/users/{id}/action", delete(do_users_action)).route("/assets/{*path}", get(serve_asset));
async fn root() {}
async fn list_users() {}
async fn create_user() {}
async fn show_user(Path(id): Path<u64>) {}
async fn do_users_action(Path((version, id)): Path<(String, u64)>) {}
async fn serve_asset(Path(path): Path<String>) {}
Panics(崩溃场景)
如果路由与另一条路由重叠,会触发 panic
:
use axum::{routing::get, Router};let app = Router::new().route("/", get(|| async {})).route("/", get(|| async {}));
静态路由 /foo
和动态路由 /{key}
不被视为重叠,且 /foo
会拥有优先级。
如果 path
为空,也会触发 panic
。
添加另一条路由,调用 Service
(服务)
pub fn route_service<T>(self, path: &str, service: T) -> Self
whereT: Service<Request, Error = Infallible> + Clone + Send + Sync + 'static,T::Response: IntoResponse,T::Future: Send + 'static,
向路由器中添加另一条路由,该路由会调用一个 Service
(服务)。
示例
use axum::{Router,body::Body,routing::{any_service, get_service},extract::Request,http::StatusCode,error_handling::HandleErrorLayer,
};
use tower_http::services::ServeFile;
use http::Response;
use std::{convert::Infallible, io};
use tower::service_fn;let app = Router::new().route(// 所有对 `/` 的请求都会指向一个服务"/",// 响应体不是 `axum::body::BoxBody` 的服务// 可以被包裹在 `axum::routing::any_service`(或其他路由过滤器之一)中// 以实现响应体的映射any_service(service_fn(|_: Request| async {let res = Response::new(Body::from("Hi from `GET /`"));Ok::<_, Infallible>(res)}))).route_service("/foo",// 此服务的响应体是 `axum::body::BoxBody`,因此// 可以直接将路由指向它。service_fn(|req: Request| async move {let body = Body::from(format!("Hi from `{} /foo`", req.method()));let res = Response::new(body);Ok::<_, Infallible>(res)})).route_service(// GET 请求 `/static/Cargo.toml` 会指向 tower-http 中的一个服务"/static/Cargo.toml",ServeFile::new("Cargo.toml"),);
以这种方式将路由指向任意服务,在背压(backpressure)处理(即 Service::poll_ready
方法)方面会存在复杂性。有关更多详情,请参阅 “Routing to services and backpressure” 模块。
Panics(恐慌场景)
会因与 Router::route
相同的原因触发 panic
,或者当你尝试将路由指向另一个 Router
时也会触发 panic
:
use axum::{routing::get, Router};let app = Router::new().route_service("/",Router::new().route("/foo", get(|| async {})),
);
请改用 Router::nest
方法。
pub fn nest(self, path: &str, router: Router<S>) -> Self
在某个路径下嵌套(nest)一个 Router
。
这允许你将应用拆分为多个较小的模块,并将它们组合在一起。
示例
use axum::{routing::{get, post},Router,
};let user_routes = Router::new().route("/{id}", get(|| async {}));let team_routes = Router::new().route("/", post(|| async {}));let api_routes = Router::new().nest("/users", user_routes).nest("/teams", team_routes);let app = Router::new().nest("/api", api_routes);// 我们的应用现在接受
// - GET /api/users/{id}
// - POST /api/teams
URI 的变化
注意,嵌套路由无法获取原始请求 URI,而是会将匹配到的前缀路径移除。这对于静态文件服务等服务的正常工作是必需的。如果需要获取原始请求 URI,请使用 OriginalUri
。
来自外部路由的捕获值
当将 nest
与动态路由一起使用时需注意,嵌套路由也会捕获外部路由中的路径参数:
use axum::{extract::Path,routing::get,Router,
};
use std::collections::HashMap;async fn users_get(Path(params): Path<HashMap<String, String>>) {// 尽管 `users_api` 仅显式捕获了 `id`,但 `version` 和 `id` 均会被捕获。let version = params.get("version");let id = params.get("id");
}let users_api = Router::new().route("/users/{id}", get(users_get));let app = Router::new().nest("/{version}/api", users_api);
与通配符路由的区别
嵌套路由与通配符路由类似。不同之处在于,通配符路由仍能获取完整 URI,而嵌套路由会移除前缀路径:
use axum::{routing::get, http::Uri, Router};let nested_router = Router::new().route("/", get(|uri: Uri| async {// `uri` 将**不**包含 `/bar`}));let app = Router::new().route("/foo/{*rest}", get(|uri: Uri| async {// `uri` 将包含 `/foo`})).nest("/bar", nested_router);
此外,通配符路由 /foo/*rest
不会匹配 /foo
或 /foo/
路径,而在 /foo
路径下的嵌套路由器会匹配 /foo
(但不匹配 /foo/
),在 /foo/
路径下的嵌套路由器会匹配 /foo/
(但不匹配 /foo
)。
Fallbacks(兜底处理器)
如果嵌套路由器没有自己的兜底处理器(fallback),则会继承外部路由器的兜底处理器:
use axum::{routing::get, http::StatusCode, handler::Handler, Router};async fn fallback() -> (StatusCode, &'static str) {(StatusCode::NOT_FOUND, "Not Found")
}let api_routes = Router::new().route("/users", get(|| async {}));let app = Router::new().nest("/api", api_routes).fallback(fallback);
在此处,像 GET /api/not-found
这样的请求会进入 api_routes
,但由于 api_routes
中没有匹配的路由,也没有自己的兜底处理器,因此会调用外部路由器的兜底处理器(即 fallback
函数)。
如果嵌套路由器有自己的兜底处理器,则不会继承外部路由器的兜底处理器:
use axum::{routing::get,http::StatusCode,handler::Handler,Json,Router,
};async fn fallback() -> (StatusCode, &'static str) {(StatusCode::NOT_FOUND, "Not Found")
}async fn api_fallback() -> (StatusCode, Json<serde_json::Value>) {(StatusCode::NOT_FOUND,Json(serde_json::json!({ "status": "Not Found" })),)
}let api_routes = Router::new().route("/users", get(|| async {})).fallback(api_fallback);let app = Router::new().nest("/api", api_routes).fallback(fallback);
在此处,像 GET /api/not-found
这样的请求会指向 api_fallback
。
Nesting routers with state(嵌套带状态的路由器)
当使用此方法组合多个 Router
时,每个 Router
必须拥有相同类型的状态(state)。如果你的路由器类型不同,可以使用 Router::with_state
方法提供状态,使类型匹配:
use axum::{Router,routing::get,extract::State,
};#[derive(Clone)]
struct InnerState {}#[derive(Clone)]
struct OuterState {}async fn inner_handler(state: State<InnerState>) {}let inner_router = Router::new().route("/bar", get(inner_handler)).with_state(InnerState {});async fn outer_handler(state: State<OuterState>) {}let app = Router::new().route("/", get(outer_handler)).nest("/foo", inner_router).with_state(OuterState {});
注意,内部路由器仍会继承外部路由器的回退处理器。
Panics(崩溃场景)
- 如果路由与另一条路由重叠。有关更多详情,请参阅
Router::route
。 - 如果路径中包含通配符(
*
)。 - 如果
path
为空。
pub fn nest_service<T>(self, path: &str, service: T) -> Self
whereT: Service<Request, Error = Infallible> + Clone + Send + Sync + 'static,T::Response: IntoResponse,T::Future: Send + 'static,
与 nest
类似,但接受任意 Service
(服务)。
pub fn merge<R>(self, other: R) -> Self
whereR: Into<Router<S>>,
将两个路由器的路径和回退处理器合并为一个 Router
。
这对于将应用拆分为多个较小的模块并将它们组合成一个整体非常有用。
use axum::{routing::get,Router,
};// 单独定义一些路由
let user_routes = Router::new().route("/users", get(users_list)).route("/users/{id}", get(users_show));let team_routes = Router::new().route("/teams", get(teams_list));// 将它们组合成一个路由器
let app = Router::new().merge(user_routes).merge(team_routes);// 也可以写成 `user_routes.merge(team_routes)`// 我们的应用现在接受
// - GET /users
// - GET /users/{id}
// - GET /teams
Merging routers with state(合并带状态的路由器)
当使用此方法组合多个 Router
时,每个 Router
必须拥有相同类型的状态(state)。如果你的路由器类型不同,可以使用 Router::with_state
方法提供状态