
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導(dǎo)讀在 Axum 中Router::into_make_service_with_connect_info是一個將路由轉(zhuǎn)換為 towerMakeService的關(guān)鍵方法它會在每個新連接建立時把與該連接關(guān)聯(lián)的連接信息ConnectInfo注入請求擴展extension從而讓處理器通過ConnectInfo提取器在任意路由中讀取客戶端的遠程地址等元數(shù)據(jù)。本文基于倉庫中的官方文檔axum/src/docs/routing/into_make_service_with_connect_info.md與其底層實現(xiàn)講解開箱即用的SocketAddr提取方式、自定義Connectedtrait 實現(xiàn)方法以及如何利用它采集 Unix Domain Socket 的進程憑證等擴展信息。一、它解決什么問題為什么需要into_make_service_with_connect_infoHTTP 請求本身并不攜帶對端 IP 與端口信息這些信息屬于 TCP 連接層。Axum 的Router是純請求/響應(yīng)服務(wù)ServiceRequest處理器默認無從得知誰連上了我。into_make_service_with_connect_info解決的正是在服務(wù)運行serve階段與請求處理階段之間的信息鴻溝它將Router轉(zhuǎn)換為 tower 生態(tài)中的 [MakeService]tower::make::MakeService即每個連接都產(chǎn)出路由器服務(wù)實例的工廠服務(wù)在每一次連接建立時它根據(jù)連接對象構(gòu)造出類型為C的連接信息并包裝為ConnectInfoC寫入請求擴展request extension處理函數(shù)只需要聲明ConnectInfoC參數(shù)即可在任何路由處理器中提取這些信息。從源碼看Router的實現(xiàn)位于 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(())調(diào)用它與into_make_service一樣會在轉(zhuǎn)換階段提前把路由內(nèi)部結(jié)構(gòu)固化為Route而不是在每個請求到來時再做狀態(tài)注入。依據(jù) axum/src/docs/routing/with_state.md 的說明這會影響性能并減少分配may impact performance and reduce allocations。此外該方法標注了#[cfg(feature tokio)]使用前需要確保開啟了tokiofeature。同一方法在不同入口的通用性into_make_service_with_connect_info并非Router獨有同一套機制在倉庫中面向三類服務(wù)入口均有提供源碼位置如下Routeraxum/src/routing/mod.rsMethodRouteraxum/src/routing/method_routing.rs同樣內(nèi)部先調(diào)用self.with_state(())HandlerServiceaxum/src/handler/service.rs以及 axum/src/handler/mod.rs 中的HandlerWithoutStateExt/Handlertrait 方法。也就是說無論是整個Router、單獨一個MethodRouter還是單個 handler都可以用相同的方式獲得連接信息能力。二、開箱即用提取客戶端SocketAddr官方文檔給出的第一個場景是最常見的需求——獲取客戶端的遠程地址。std::net::SocketAddr的開箱即用支持意味著你無需任何額外實現(xiàn)直接聲明提取器即可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; # };關(guān)鍵點拆解泛型參數(shù)::SocketAddr必須顯式給出into_make_service_with_connect_info::C中的C決定了將何種類型寫入擴展也決定了處理器中ConnectInfoC的類型參數(shù)二者必須一致運行入口必須使用axum::serve且傳入該方法的結(jié)果serve會為每個新連接調(diào)用一次這個MakeService見 axum/src/serve/mod.rs 中serve的簽名M: fora ServiceIncomingStreama, L, Error Infallible, Response S提取器在處理器簽名中的寫法是ConnectInfo(addr)ConnectInfoT是元組結(jié)構(gòu)體見 axum/src/extract/connect_info.rs 的pub struct ConnectInfoT(pub T)因此可以通過解構(gòu)模式直接拿到內(nèi)部值。底層原理連接信息如何流入請求擴展在 axum/src/serve/mod.rs 中serve每接受一個連接就調(diào)用一次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實現(xiàn)axum/src/extract/connect_info.rs正是數(shù)據(jù)流動的核心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注入服務(wù) → 路由處理器通過FromRequestParts從擴展中取出它見 axum/src/extract/connect_info.rs。也就是說ConnectInfo本質(zhì)上就是被特殊包裝過的Extension提取器。倉庫測試 axum/src/extract/connect_info.rs 中的socket_addr測試驗證了這條鏈路真實啟動TcpListener后發(fā)起請求斷言響應(yīng)體以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.如果應(yīng)用沒有通過into_make_service_with_connect_info啟動ConnectInfo提取器在運行時將返回拒絕rejection。因此一旦處理器聲明了ConnectInfoC參數(shù)應(yīng)用的啟動方式就必須與之匹配。三、自定義連接信息實現(xiàn)Connectedtrait當SocketAddr不夠用時可以為自己的類型實現(xiàn)Connectedtrait從而注入任意連接級元數(shù)據(jù)。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; }注意它的約束實現(xiàn)類型必須滿足Clone Send Sync static且只需實現(xiàn)一個關(guān)聯(lián)函數(shù)connect_info(stream: T) - Self參數(shù)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; # };實現(xiàn)要點ConnectedIncomingStream_, TcpListener是典型的 TCP 場景實現(xiàn)IncomingStream提供io()與remote_addr()兩個方法見 axum/src/serve/mod.rs分別返回底層 IO 引用與遠端地址SocketAddr本身已經(jīng)實現(xiàn)了Connected針對IncomingStream_, LL: ListenerAddr SocketAddr與Self兩條實現(xiàn)分別位于 axum/src/extract/connect_info.rs這正是第二節(jié)開箱即用的來源同一類型可針對不同T實現(xiàn)多次從倉庫測試代碼 axum/src/extract/connect_info.rs 可以看到CustomAddr同時對IncomingStream_, TcpListener和IncomingStream_, CustomListener實現(xiàn)了Connected說明該 trait 是按連接類型參數(shù)化的天然支持自定義 Listener關(guān)聯(lián)的ResponseFuture是std::future::ReadyResultAddExtensionS, ConnectInfoC, Infallible即轉(zhuǎn)換過程是即時完成的、錯誤類型恒為Infallible。四、實戰(zhàn)案例用 UDS 連接信息獲取進程憑證官方文檔明確推薦參考 Unix Domain Socket 示例來收集 UDS 連接信息See the unix domain socket example for an example of how to use this to collect UDS connection info。倉庫中的完整實現(xiàn)位于 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! }該示例體現(xiàn)的兩個關(guān)鍵技巧借助stream.io()訪問底層 IOIncomingStream::io()返回L::Io這里就是UnixStream從而可以調(diào)用peer_addr()與peer_cred()獲取對端路徑和進程憑證用Arc包裹地址UdsConnectInfo需要滿足Clone Send Sync static而tokio::net::unix::SocketAddr并非Clone因此用Arc包裝以滿足 trait 約束peer_addr: Arctokio::net::unix::SocketAddr。五、測試與調(diào)試MockConnectInfo中間件生產(chǎn)環(huán)境使用真實監(jiān)聽器時連接信息來自網(wǎng)絡(luò)棧而單元測試中如何模擬倉庫提供了MockConnectInfoT中間件axum/src/extract/connect_info.rs它本質(zhì)上就是注入一個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) {} // 生產(chǎn)環(huán)境用 app.into_make_service_with_connect_info::SocketAddr() 啟動 fn app() - Router { Router::new().route(/, get(handler)) } // 測試環(huán)境直接注入模擬連接信息 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實現(xiàn)axum/src/extract/connect_info.rs給出了兩者的優(yōu)先級語義先嘗試從真實擴展取ConnectInfo失敗后再回退到MockConnectInfo。因此當 Mock 與真實into_make_service_with_connect_info同時存在時真實連接信息優(yōu)先——倉庫測試both_mock_and_real_connect_infoaxum/src/extract/connect_info.rs驗證了這一點即便先掛了MockConnectInfo層真實監(jiān)聽器下提取到的仍是127.0.0.1:真實地址。這保證了測試可以放心疊加 Mock而不會污染生產(chǎn)行為。六、常見誤用與排查清單結(jié)合文檔與源碼整理使用into_make_service_with_connect_info時的注意事項檢查項說明依據(jù)啟動方式匹配處理器聲明了ConnectInfoC就必須用into_make_service_with_connect_info或?qū)?yīng)的 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的區(qū)別無連接信息需求時用into_make_service即可二者都會自動調(diào)用with_state(())做預(yù)轉(zhuǎn)換優(yōu)化axum/src/routing/mod.rs總結(jié)Router::into_make_service_with_connect_info將連接層的元數(shù)據(jù)安全地橋接進請求處理流程SocketAddr開箱即用滿足絕大多數(shù)獲取客戶端 IP/端口的需求自定義Connectedtrait 則把能力延伸到 UDS 憑證、自定義 Listener 等場景。理解它的核心在于每連接一次、注入擴展、提取器取用這條鏈路配合MockConnectInfo還能讓測試環(huán)境完全可控。相關(guān)實現(xiàn)細節(jié)可繼續(xù)深入閱讀 axum/src/extract/connect_info.rs 與 axum/src/serve/mod.rs并在 examples/unix-domain-socket/src/main.rs 中查看完整可運行示例?!久赓M下載鏈接】axumHTTP routing and request-handling library for Rust that focuses on ergonomics and modularity項目地址: https://gitcode.com/GitHub_Trending/ax/axum創(chuàng)作聲明:本文部分內(nèi)容由AI輔助生成(AIGC),僅供參考