axum 嵌套路由会剥离前缀,如何用 OriginalUri 取回原始 URI

axum 嵌套路由会剥离前缀,如何用 OriginalUri 取回原始 URI axum 嵌套路由会剥离前缀如何用 OriginalUri 取回原始 URI【免费下载链接】axumHTTP routing and request-handling library for Rust that focuses on ergonomics and modularity项目地址: https://gitcode.com/GitHub_Trending/ax/axum用 axum 的nest把多个Router组合起来后你会遇到一个不直观的行为嵌套层里的 handler 提取到Uri或调用req.uri()时看到的是剥掉了嵌套前缀的路径而不是客户端实际请求的完整 URI。nest.md文档直接说明了这一点嵌套路由看到的不是原始请求 URI而是have the matched prefix stripped匹配到的前缀被剥离这样做是必要的否则静态文件服务等依赖路径前缀的服务无法工作。如果你需要拿到原始请求 URI例如记录完整访问路径、做重定向或鉴权文档给出的答案是使用OriginalUri提取器。先确认你遇到的是剥离行为而不是路由没匹配上仓库测试 axum/src/routing/tests/nest.rs 中有两组对照测试可以直接用来判断Uri提取器在嵌套层里看到的内容nested_url_extractor测试把三级Router分别嵌套在/foo、/bar下内部路由是/bazhandler 提取Uri并把uri.to_string()作为响应体let app Router::new().nest( /foo, Router::new().nest( /bar, Router::new() .route(/baz, get(|uri: Uri| async move { uri.to_string() })) .route( /qux, get(|req: Request| async move { req.uri().to_string() }), ), ), ); let client TestClient::new(app); let res client.get(/foo/bar/baz).await; assert_eq!(res.status(), StatusCode::OK); assert_eq!(res.text().await, /baz); let res client.get(/foo/bar/qux).await; assert_eq!(res.status(), StatusCode::OK); assert_eq!(res.text().await, /qux);文档中的断言结果是请求/foo/bar/baz时handler 里的Uri只得到/bazRequest上的req.uri()同样只有/qux。这是 axum 的既有设计不是 bug。Uri提取器本身的实现就是从请求 parts 里原样克隆当前 URI见 axum-core/src/extract/request_parts.rs 中impl FromRequestParts for Uri而嵌套服务拿到时 parts 中的 URI 已经是剥离后的值。另外注意与通配符路由的区别nest.md 指出通配符路由如/foo/{*rest}仍然能看到完整 URI而嵌套路由会被剥离前缀。如果你的场景只是想看到完整路径且不需要前缀剥离语义也可以考虑用通配符路由替代nest。在 handler 中用 OriginalUri 提取原始 URIOriginalUri的定义和文档在 axum/src/extract/original_uri.rspub struct OriginalUri(pub Uri);它是一个FromRequestParts提取器Rejection类型为Infallible——只要请求进入了 axum 的路由体系这个提取就永远不会失败。使用OriginalUri依赖original-urifeature。根据 axum/Cargo.toml该 feature 已包含在默认 feature 列表里正常cargo add axum引入的依赖无需额外配置只有在自行裁剪 feature 时才需要显式开启。文档给出的标准用法是把OriginalUri作为 handler 参数和Uri并排对比use axum::{ routing::get, Router, extract::OriginalUri, http::Uri }; let api_routes Router::new() .route( /users, get(|uri: Uri, OriginalUri(original_uri): OriginalUri| async { // uri is /users // original_uri is /api/users }), ); let app Router::new().nest(/api, api_routes);这段文档示例对应的事实是嵌套在/api下的/users路由Uri参数得到/users而OriginalUri得到客户端真实请求的/api/users。多套几层nest时同理——OriginalUri始终是最外层收到的完整路径。它从哪里拿到原始 URI提取器的实现original_uri.rs 中的from_request_parts是先尝试从请求扩展里取Extension::OriginalUri取不到时才退回当前parts.uri.clone()。而 axum/src/routing/path_router.rs 中的路由处理逻辑会在original-urifeature 开启时于请求进入路由前检查扩展里是否已有OriginalUri没有就插入一份当时的req.uri()。也就是说只要请求经过顶层Router扩展里就已经存好了完整 URI深层嵌套路由的 handler 再提取时拿到的自然是未剥离的值。在 middleware 或 trace 日志中通过请求扩展取回handler 之外OriginalUri还能从请求扩展直接读取。文档给出的场景是配合tower_http::trace::TraceLayer创建包含完整路径的 span你的服务可能会被嵌套此时req.uri()只有剥离后的路径use axum::{ Router, extract::OriginalUri, http::Request, routing::get, }; use tower_http::trace::TraceLayer; let api_routes Router::new() .route(/users/{id}, get(|| async { /* ... */ })) .layer( TraceLayer::new_for_http().make_span_with(|req: Request_| { let path if let Some(path) req.extensions().get::OriginalUri() { // This will include /api path.0.path().to_owned() } else { // The OriginalUri extension will always be present if using // Router unless another extractor or middleware has removed it req.uri().path().to_owned() }; tracing::info_span!(http-request, %path) }), ); let app Router::new().nest(/api, api_routes);关键点path.0是OriginalUri内部的Uri.path()取路径部分。文档明确说明只要使用Router构建服务OriginalUri扩展will always be present总是存在除非其他提取器或 middleware 把它移除了——所以示例中保留了else分支回退到req.uri().path()。仓库自带的日志示例 examples/tracing-aka-logging/src/main.rs 也是同一思路它用MatchedPath记录匹配到的路由模板并在注释中提示Use request.uri() or OriginalUri if you want the real path。用仓库测试验证取回的值验证方式可以直接照搬 axum/src/routing/tests/nest.rs 中的nested_url_original_extractor测试它断言了OriginalUri在两层嵌套下的取回结果let app Router::new().nest( /foo, Router::new().nest( /bar, Router::new().route( /baz, get(|uri: extract::OriginalUri| async move { uri.0.to_string() }), ), ), ); let client TestClient::new(app); let res client.get(/foo/bar/baz).await; assert_eq!(res.status(), StatusCode::OK); assert_eq!(res.text().await, /foo/bar/baz);按文档中的断言GET /foo/bar/baz的响应体应当是/foo/bar/baz——完整 URI 被原样取回而前面nested_url_extractor测试里同样的请求在Uri提取器下只得到/baz。你可以把这两段代码放进自己的工程用TestClient跑一遍TestClient是 axum 的测试辅助工具dev-dependency 中自带作为改造 handler 前后行为的对照。边界与限制只有经过 axumRouter的请求才会自动带上OriginalUri扩展。文档的 middleware 示例中对没有扩展的情况做了回退处理说明脱离Router体系或扩展被移除时读不到原始 URI。OriginalUri本身没有拒绝类型Rejection Infallible不存在提取失败导致的响应分支需要处理。前缀剥离是nest的固有语义OriginalUri只负责取回原始值不会改变嵌套路由的匹配行为nest与通配符路由的匹配差异例如/foo/{*rest}不匹配/foo本身而嵌套在/foo的Router匹配/foo但不匹配/foo/以 nest.md 的说明为准。【免费下载链接】axumHTTP routing and request-handling library for Rust that focuses on ergonomics and modularity项目地址: https://gitcode.com/GitHub_Trending/ax/axum创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考