ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Axum 中 through `into_make_service_with_connect_info` 获取客户端连接信息:从 SocketAddr 到自定义 Connected 的完整指南

Axum 中 through `into_make_service_with_connect_info` 获取客户端连接信息:从 SocketAddr 到自定义 Connected 的完整指南 Axum 中 throughinto_make_service_with_connect_info获取客户端连接信息从 SocketAddr 到自定义 Connected 的完整指南【免费下载链接】axumHTTP routing and request-handling library for Rust that focuses on ergonomics and modularity项目地址: https://gitcode.com/GitHub_Trending/ax/axum导读在 Axum 中Router::into_make_service_with_connect_info是一个将路由转换为 towerMakeService的关键方法它会在每个新连接建立时把与该连接关联的连接信息ConnectInfo注入请求扩展extension从而让处理器通过ConnectInfo提取器在任意路由中读取客户端的远程地址等元数据。本文基于仓库中的官方文档axum/src/docs/routing/into_make_service_with_connect_info.md与其底层实现讲解开箱即用的SocketAddr提取方式、自定义Connectedtrait 实现方法以及如何利用它采集 Unix Domain Socket 的进程凭证等扩展信息。一、它解决什么问题为什么需要into_make_service_with_connect_infoHTTP 请求本身并不携带对端 IP 与端口信息这些信息属于 TCP 连接层。Axum 的Router是纯请求/响应服务ServiceRequest处理器默认无从得知谁连上了我。into_make_service_with_connect_info解决的正是在服务运行serve阶段与请求处理阶段之间的信息鸿沟它将Router转换为 tower 生态中的 [MakeService]tower::make::MakeService即每个连接都产出路由器服务实例的工厂服务在每一次连接建立时它根据连接对象构造出类型为C的连接信息并包装为ConnectInfoC写入请求扩展request extension处理函数只需要声明ConnectInfoC参数即可在任何路由处理器中提取这些信息。从源码看Router的实现位于 axum/src/routing/mod.rs#[doc include_str!(../docs/routing/into_make_service_with_connect_info.md)] #[cfg(feature tokio)] #[must_use] pub fn into_make_service_with_connect_infoC(self) - IntoMakeServiceWithConnectInfoSelf, C { // call Router::with_state such that everything is turned into Route eagerly // rather than doing that per request IntoMakeServiceWithConnectInfo::new(self.with_state(())) }注意其中的with_state(())调用它与into_make_service一样会在转换阶段提前把路由内部结构固化为Route而不是在每个请求到来时再做状态注入。依据 axum/src/docs/routing/with_state.md 的说明这会影响性能并减少分配may impact performance and reduce allocations。此外该方法标注了#[cfg(feature tokio)]使用前需要确保开启了tokiofeature。同一方法在不同入口的通用性into_make_service_with_connect_info并非Router独有同一套机制在仓库中面向三类服务入口均有提供源码位置如下Routeraxum/src/routing/mod.rsMethodRouteraxum/src/routing/method_routing.rs同样内部先调用self.with_state(())HandlerServiceaxum/src/handler/service.rs以及 axum/src/handler/mod.rs 中的HandlerWithoutStateExt/Handlertrait 方法。也就是说无论是整个Router、单独一个MethodRouter还是单个 handler都可以用相同的方式获得连接信息能力。二、开箱即用提取客户端SocketAddr官方文档给出的第一个场景是最常见的需求——获取客户端的远程地址。std::net::SocketAddr的开箱即用支持意味着你无需任何额外实现直接声明提取器即可use axum::{ extract::ConnectInfo, routing::get, Router, }; use std::net::SocketAddr; let app Router::new().route(/, get(handler)); async fn handler(ConnectInfo(addr): ConnectInfoSocketAddr) - String { format!(Hello {addr}) } # async { let listener tokio::net::TcpListener::bind(0.0.0.0:3000).await.unwrap(); axum::serve(listener, app.into_make_service_with_connect_info::SocketAddr()).await; # };关键点拆解泛型参数::SocketAddr必须显式给出into_make_service_with_connect_info::C中的C决定了将何种类型写入扩展也决定了处理器中ConnectInfoC的类型参数二者必须一致运行入口必须使用axum::serve且传入该方法的结果serve会为每个新连接调用一次这个MakeService见 axum/src/serve/mod.rs 中serve的签名M: fora ServiceIncomingStreama, L, Error Infallible, Response S提取器在处理器签名中的写法是ConnectInfo(addr)ConnectInfoT是元组结构体见 axum/src/extract/connect_info.rs 的pub struct ConnectInfoT(pub T)因此可以通过解构模式直接拿到内部值。底层原理连接信息如何流入请求扩展在 axum/src/serve/mod.rs 中serve每接受一个连接就调用一次make_servicemake_service .ready() .await .unwrap_or_else(|err| match err {}); let tower_service make_service .call(IncomingStream { io: io, remote_addr, }) .await .unwrap_or_else(|err| match err {}) .map_request(|req: RequestIncoming| req.map(Body::new));它传入的目标类型是IncomingStreama, L定义见 axum/src/serve/mod.rs其中携带io引用与remote_addr。而IntoMakeServiceWithConnectInfo对该类型的Service实现axum/src/extract/connect_info.rs正是数据流动的核心implS, C, T ServiceT for IntoMakeServiceWithConnectInfoS, C where S: Clone, C: ConnectedT, { type Response AddExtensionS, ConnectInfoC; type Error Infallible; // ... fn call(mut self, target: T) - Self::Future { let connect_info ConnectInfo(C::connect_info(target)); let svc Extension(connect_info).layer(self.svc.clone()); ResponseFuture::new(ready(Ok(svc))) } }流程可以概括为每连接一次→C::connect_info(target)从连接对象计算出C→ 包成ConnectInfoC→ 用Extension层把ConnectInfoC注入服务 → 路由处理器通过FromRequestParts从扩展中取出它见 axum/src/extract/connect_info.rs。也就是说ConnectInfo本质上就是被特殊包装过的Extension提取器。仓库测试 axum/src/extract/connect_info.rs 中的socket_addr测试验证了这条链路真实启动TcpListener后发起请求断言响应体以127.0.0.1:开头确认提取到的正是真实对端地址。重要约束不使用该方法时ConnectInfo提取会失败ConnectInfo提取器文档明确警告axum/src/extract/connect_info.rsNote this extractor requires you to useRouter::into_make_service_with_connect_infoto run your app otherwise it will fail at runtime.如果应用没有通过into_make_service_with_connect_info启动ConnectInfo提取器在运行时将返回拒绝rejection。因此一旦处理器声明了ConnectInfoC参数应用的启动方式就必须与之匹配。三、自定义连接信息实现Connectedtrait当SocketAddr不够用时可以为自己的类型实现Connectedtrait从而注入任意连接级元数据。Connected的定义位于 axum/src/extract/connect_info.rspub trait ConnectedT: Clone Send Sync static { /// Create type holding information about the connection. fn connect_info(stream: T) - Self; }注意它的约束实现类型必须满足Clone Send Sync static且只需实现一个关联函数connect_info(stream: T) - Self参数T通常是IncomingStream_, Listener或更底层的 IO 类型。官方文档给出的自定义示例use axum::{ extract::connect_info::{ConnectInfo, Connected}, routing::get, serve::IncomingStream, Router, }; use tokio::net::TcpListener; let app Router::new().route(/, get(handler)); async fn handler( ConnectInfo(my_connect_info): ConnectInfoMyConnectInfo, ) - String { format!(Hello {my_connect_info:?}) } #[derive(Clone, Debug)] struct MyConnectInfo { // ... } impl ConnectedIncomingStream_, TcpListener for MyConnectInfo { fn connect_info(target: IncomingStream_, TcpListener) - Self { MyConnectInfo { // ... } } } # async { let listener tokio::net::TcpListener::bind(0.0.0.0:3000).await.unwrap(); axum::serve(listener, app.into_make_service_with_connect_info::MyConnectInfo()).await; # };实现要点ConnectedIncomingStream_, TcpListener是典型的 TCP 场景实现IncomingStream提供io()与remote_addr()两个方法见 axum/src/serve/mod.rs分别返回底层 IO 引用与远端地址SocketAddr本身已经实现了Connected针对IncomingStream_, LL: ListenerAddr SocketAddr与Self两条实现分别位于 axum/src/extract/connect_info.rs这正是第二节开箱即用的来源同一类型可针对不同T实现多次从仓库测试代码 axum/src/extract/connect_info.rs 可以看到CustomAddr同时对IncomingStream_, TcpListener和IncomingStream_, CustomListener实现了Connected说明该 trait 是按连接类型参数化的天然支持自定义 Listener关联的ResponseFuture是std::future::ReadyResultAddExtensionS, ConnectInfoC, Infallible即转换过程是即时完成的、错误类型恒为Infallible。四、实战案例用 UDS 连接信息获取进程凭证官方文档明确推荐参考 Unix Domain Socket 示例来收集 UDS 连接信息See the unix domain socket example for an example of how to use this to collect UDS connection info。仓库中的完整实现位于 examples/unix-domain-socket/src/main.rs运行方式为cargo run -p example-unix-domain-socket其核心思路是TCP 场景下连接信息是SocketAddr而 UDS 场景下则替换为对端路径与进程凭证UCred即对端进程的 UID/GID/PIDuse axum::{ body::Body, extract::connect_info::{self, ConnectInfo}, http::{Request, StatusCode}, routing::get, serve::IncomingStream, Router, }; use std::{path::PathBuf, sync::Arc}; use tokio::net::{unix::UCred, UnixListener, UnixStream}; #[derive(Clone, Debug)] struct UdsConnectInfo { peer_addr: Arctokio::net::unix::SocketAddr, peer_cred: UCred, } impl connect_info::ConnectedIncomingStream_, UnixListener for UdsConnectInfo { fn connect_info(stream: IncomingStream_, UnixListener) - Self { let peer_addr stream.io().peer_addr().unwrap(); let peer_cred stream.io().peer_cred().unwrap(); Self { peer_addr: Arc::new(peer_addr), peer_cred, } } } let app Router::new() .route(/, get(handler)) .into_make_service_with_connect_info::UdsConnectInfo(); axum::serve(uds, app).await; async fn handler(ConnectInfo(info): ConnectInfoUdsConnectInfo) - static str { println!(new connection from {info:?}); Hello, World! }该示例体现的两个关键技巧借助stream.io()访问底层 IOIncomingStream::io()返回L::Io这里就是UnixStream从而可以调用peer_addr()与peer_cred()获取对端路径和进程凭证用Arc包裹地址UdsConnectInfo需要满足Clone Send Sync static而tokio::net::unix::SocketAddr并非Clone因此用Arc包装以满足 trait 约束peer_addr: Arctokio::net::unix::SocketAddr。五、测试与调试MockConnectInfo中间件生产环境使用真实监听器时连接信息来自网络栈而单元测试中如何模拟仓库提供了MockConnectInfoT中间件axum/src/extract/connect_info.rs它本质上就是注入一个ConnectInfoT扩展#[derive(Clone, Copy, Debug)] pub struct MockConnectInfoT(pub T); implS, T LayerS for MockConnectInfoT where T: Clone Send Sync static, { type Service ExtensionSelf as LayerS::Service; fn layer(self, inner: S) - Self::Service { Extension(self.clone()).layer(inner) } }典型用法取自该文件文档示例同一个 app 定义只写一次测试版用.layer(MockConnectInfo(...))包装即可use axum::{ Router, extract::connect_info::{MockConnectInfo, ConnectInfo}, body::Body, routing::get, http::{Request, StatusCode}, }; use std::net::SocketAddr; use tower::ServiceExt; async fn handler(ConnectInfo(addr): ConnectInfoSocketAddr) {} // 生产环境用 app.into_make_service_with_connect_info::SocketAddr() 启动 fn app() - Router { Router::new().route(/, get(handler)) } // 测试环境直接注入模拟连接信息 fn test_app() - Router { app().layer(MockConnectInfo(SocketAddr::from(([0, 0, 0, 0], 1337)))) } async fn some_test() { let app test_app(); let request Request::new(Body::empty()); let response app.oneshot(request).await.unwrap(); assert_eq!(response.status(), StatusCode::OK); }配套的ConnectInfo::from_request_parts实现axum/src/extract/connect_info.rs给出了两者的优先级语义先尝试从真实扩展取ConnectInfo失败后再回退到MockConnectInfo。因此当 Mock 与真实into_make_service_with_connect_info同时存在时真实连接信息优先——仓库测试both_mock_and_real_connect_infoaxum/src/extract/connect_info.rs验证了这一点即便先挂了MockConnectInfo层真实监听器下提取到的仍是127.0.0.1:真实地址。这保证了测试可以放心叠加 Mock而不会污染生产行为。六、常见误用与排查清单结合文档与源码整理使用into_make_service_with_connect_info时的注意事项检查项说明依据启动方式匹配处理器声明了ConnectInfoC就必须用into_make_service_with_connect_info或对应的 MethodRouter/Handler 版本启动否则运行时提取失败axum/src/extract/connect_info.rs泛型类型一致启动时的::C与处理器中的ConnectInfoC必须是同一类型官方文档两段示例tokiofeatureRouter上的该方法标注#[cfg(feature tokio)]需启用该 featureaxum/src/routing/mod.rstrait 约束自定义类型需满足Clone Send Sync static非Clone字段用Arc包装axum/src/extract/connect_info.rs与into_make_service的区别无连接信息需求时用into_make_service即可二者都会自动调用with_state(())做预转换优化axum/src/routing/mod.rs总结Router::into_make_service_with_connect_info将连接层的元数据安全地桥接进请求处理流程SocketAddr开箱即用满足绝大多数获取客户端 IP/端口的需求自定义Connectedtrait 则把能力延伸到 UDS 凭证、自定义 Listener 等场景。理解它的核心在于每连接一次、注入扩展、提取器取用这条链路配合MockConnectInfo还能让测试环境完全可控。相关实现细节可继续深入阅读 axum/src/extract/connect_info.rs 与 axum/src/serve/mod.rs并在 examples/unix-domain-socket/src/main.rs 中查看完整可运行示例。【免费下载链接】axumHTTP routing and request-handling library for Rust that focuses on ergonomics and modularity项目地址: https://gitcode.com/GitHub_Trending/ax/axum创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表