如何用 hyper 低层 API 直接驱动 axum Router
【免费下载链接】axumHTTP routing and request-handling library for Rust that focuses on ergonomics and modularity项目地址: https://gitcode.com/GitHub_Trending/ax/axum
axum 默认通过axum::serve启动,但它的文档明确说明这种运行方式"刻意保持简单,不支持太多配置",连接层直接应用 hyper 的默认配置(包括超时),需要更多控制时应该改用 hyper 或 hyper-util(见 serve 模块文档)。当你不想依赖axum::serve的黑盒,而要自己接管连接接受、服务桥接和连接构造过程时,就可以用 hyper 的低层 API 直接驱动Router。仓库中的serve-with-hyper示例就是为此准备的,本文按照该示例还原完整操作路径。
准备条件:依赖与 feature
示例的完整源码见 examples/serve-with-hyper/src/main.rs,依赖声明见 examples/serve-with-hyper/Cargo.toml。在 axum 仓库内运行它不需要额外配置,因为examples是一个独立 workspace(见 examples/Cargo.toml)。
在独立项目中复刻这个接入方式时,示例的Cargo.toml给出了所需依赖版本与 feature:
[dependencies] hyper = { version = "1.0", features = [] } hyper-util = { version = "0.1", features = ["tokio", "server-auto", "http1"] } tokio = { version = "1.0", features = ["full"] } tower = { version = "0.5.2", features = ["util"] }注意示例中axum用的是path = "../../axum"指向仓库内源码,独立项目需要换成自己的 axum 依赖。示例文件头部还提示:hyper-utilcrate 虽然提供了高层工具,但"仍处于早期开发阶段"(见 示例源码注释)。
tower依赖的utilfeature 在这里是必需的:后面的代码要用ServiceExt::oneshot调用 tower 服务。
基本接入:接受连接并把 Router 桥接成 hyper Service
核心代码对应示例中的serve_plain函数,可直接从 main.rs 复制:
async fn serve_plain() { // 创建普通的 axum 应用 let app = Router::new().route("/", get(|| async { "Hello!" })); // 用 tokio 创建 TcpListener let listener = TcpListener::bind("0.0.0.0:3000").await.unwrap(); // 持续接受新连接 loop { // 本示例丢弃远端地址;如何暴露地址见 serve_with_connect_info let (socket, _remote_addr) = listener.accept().await.unwrap(); // Router 始终 ready,不需要调用 poll_ready let tower_service = app.clone(); // 每个连接 spawn 一个任务,从而并发处理多个连接 tokio::spawn(async move { // hyper 有自己的一套 AsyncRead/AsyncWrite trait,不用 tokio 的。 // TokioIo 负责在两者之间转换。 let socket = TokioIo::new(socket); // hyper 也有自己的 Service trait,不用 tower。 // 用 hyper::service::service_fn 创建一个 hyper Service, // 通过 tower::Service::call 调用 axum 应用。 let hyper_service = hyper::service::service_fn(move |request: Request<Incoming>| { // hyper 的 Service 用 &self,tower 的 Service 需要 &mut self, // 所以必须 clone tower_service。 // Router 始终 ready,不需要 poll_ready。 tower_service.clone().call(request) }); // server::conn::auto::Builder 同时支持 http1 和 http2。 // TokioExecutor 告诉 hyper 用 tokio::spawn 派生任务。 if let Err(err) = server::conn::auto::Builder::new(TokioExecutor::new()) // 处理 websocket 需要 serve_connection_with_upgrades; // 不需要的话可以用 serve_connection。 .serve_connection_with_upgrades(socket, hyper_service) .await { eprintln!("failed to serve connection: {err:#}"); } }); } }对应的导入部分(与示例一致):
use std::net::SocketAddr; use axum::extract::ConnectInfo; use axum::{extract::Request, routing::get, Router}; use hyper::body::Incoming; use hyper_util::rt::{TokioExecutor, TokioIo}; use hyper_util::server; use tokio::net::TcpListener; use tower::{Service, ServiceExt};这条接入路径的关键判断点:
TokioIo不可省略:hyper 的AsyncRead/AsyncWrite与 tokio 的不是同一套 trait,TokioIo::new(socket)是唯一的转换入口。service_fn是 tower 到 hyper 的桥:axum 的Router是 towerService,而serve_connection_with_upgrades要求 hyperService,桥接闭包里每次请求 clone 一次 tower 服务再call。- 两个 clone 各有原因:外层 clone 是为了拿到可
move进任务的服务;内层 clone 是因为 hyper 的Service基于&self、tower 的基于&mut self。示例注释同时说明Router始终 ready,因此全程不需要poll_ready。 autoBuilder:server::conn::auto::Builder同时支持 HTTP/1 和 HTTP/2,TokioExecutor::new()指定 hyper 内部任务用tokio::spawn派生。- 升级支持:需要 websocket 时用
serve_connection_with_upgrades,不需要时用serve_connection即可(见 示例注释)。
连接处理失败时示例只会eprintln!("failed to serve connection: {err:#}"),这是示例给出的唯一错误输出方式。
可选分支:让 handler 拿到客户端远端地址
基本接入里remote_addr被直接丢弃。如果 handler 需要客户端地址,用into_make_service_with_connect_info把远端地址交给ConnectInfoextractor。示例中的serve_with_connect_info展示了这条变体(main.rs):
async fn serve_with_connect_info() { let app = Router::new().route( "/", get( |ConnectInfo(remote_addr): ConnectInfo<SocketAddr>| async move { format!("Hello {remote_addr}") }, ), ); let mut make_service = app.into_make_service_with_connect_info::<SocketAddr>(); let listener = TcpListener::bind("0.0.0.0:3001").await.unwrap(); loop { let (socket, remote_addr) = listener.accept().await.unwrap(); // IntoMakeServiceWithConnectInfo 始终 ready,不需要 poll_ready let tower_service = unwrap_infallible(make_service.call(remote_addr).await); tokio::spawn(async move { let socket = TokioIo::new(socket); let hyper_service = hyper::service::service_fn(move |request: Request<Incoming>| { tower_service.clone().oneshot(request) }); if let Err(err) = server::conn::auto::Builder::new(TokioExecutor::new()) .serve_connection_with_upgrades(socket, hyper_service) .await { eprintln!("failed to serve connection: {err:#}"); } }); } } fn unwrap_infallible<T>(result: Result<T, Infallible>) -> T { match result { Ok(value) => value, Err(err) => match err {}, } }与基本接入的差异有三处,理解它们就能判断是否要采用这条变体:
Router先经过into_make_service_with_connect_info::<SocketAddr>()转成MakeService,它接收remote_addr并产出把ConnectInfo存入请求 extension 的服务;- 每个连接要用
make_service.call(remote_addr).await拿到该连接专属的 tower 服务,返回值是Result<_, Infallible>,示例用unwrap_infallible解包; - 调用时改用
ServiceExt::oneshot(request)而不是clone().call(request),因为MakeService产出的服务是单连接消费型的。
关于into_make_service_with_connect_info的用法和自定义Connected类型,可继续看 axum/src/docs/routing/into_make_service_with_connect_info.md。
运行与验证
示例的文件头注释给出了运行命令(在 axum 仓库根目录下执行):
cargo run -p example-serve-with-hypermain函数用tokio::join!(serve_plain(), serve_with_connect_info())同时启动两个服务,分别监听:
0.0.0.0:3000:基本接入,handler 固定返回Hello!;0.0.0.0:3001:ConnectInfo 变体,handler 返回Hello {remote_addr},即把实际客户端地址拼进响应。
按示例代码本身,验证方式就是对两个端口分别发起GET /请求:3000 端口应返回示例定义的字符串Hello!;3001 端口应返回Hello加上发起请求的客户端地址(具体地址值取决于你的客户端,文档未给出固定值)。如果连接处理出错,终端会打印failed to serve connection: {err:#},这是示例代码中唯一的错误输出路径。
边界与限制
- 为什么不用
axum::serve:serve 文档 说明它刻意简单,hyper 的默认配置(含header_read_timeout这类超时)直接生效,要自定义连接构建过程就用 hyper / hyper-util 低层 API——这正是本文路径的适用场景。 hyper-util的成熟度:示例文件头部注明该 crate "仍处于早期开发阶段",选择这条路径时应了解这一点。- HTTP 版本:本文示例的
hyper-utilfeature 只启用了http1(见 示例 Cargo.toml),但server::conn::auto::Builder本身同时支持 http1 和 http2;需要 HTTP/2 时按自己项目的需求配置对应 feature。 - 超时行为:连接层超时由 hyper 默认配置决定(
axum::serve文档将其指向hyper::server::conn::http1::Builder::header_read_timeout),示例代码没有做额外调整。
这条低层路径没有提供with_graceful_shutdown之类的收尾逻辑——那是axum::serve的 API,示例中没有对应的低层写法。需要优雅关停或自定义Executor时,回到 axum/src/serve/mod.rs 查看Serve::with_graceful_shutdown和Serve::with_executor的文档示例。
【免费下载链接】axumHTTP routing and request-handling library for Rust that focuses on ergonomics and modularity项目地址: https://gitcode.com/GitHub_Trending/ax/axum
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考