news 2026/9/14 6:22:19

如何用 hyper 低层 API 直接驱动 axum Router

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
如何用 hyper 低层 API 直接驱动 axum Router

如何用 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
  • autoBuilderserver::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 {}, } }

与基本接入的差异有三处,理解它们就能判断是否要采用这条变体:

  1. Router先经过into_make_service_with_connect_info::<SocketAddr>()转成MakeService,它接收remote_addr并产出把ConnectInfo存入请求 extension 的服务;
  2. 每个连接要用make_service.call(remote_addr).await拿到该连接专属的 tower 服务,返回值是Result<_, Infallible>,示例用unwrap_infallible解包;
  3. 调用时改用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-hyper

main函数用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_shutdownServe::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),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/14 6:21:44

Easy-Vibe 云计算 IAM 实战:身份与访问管理的权限治理指南

Easy-Vibe 云计算 IAM 实战&#xff1a;身份与访问管理的权限治理指南 【免费下载链接】easy-vibe &#x1f4bb; vibe coding 101&#xff5c;The first course for AI-native product builders. 项目地址: https://gitcode.com/GitHub_Trending/ea/easy-vibe 导读&…

作者头像 李华
网站建设 2026/9/14 6:21:31

Kubernetes Ingress-NGINX迁移至Gateway API实战指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/14 6:20:31

RBF神经网络PID自整定控制:MATLAB实现与梯度下降参数优化

简介&#xff1a;这是一份基于RBF神经网络优化PID控制器的MATLAB实现资源&#xff0c;适合自动化、智能控制方向的学生与工程师用于理解径向基函数与PID参数自整定结合的方法。资源包内只有1个m文件&#xff0c;整体大小约1KB&#xff0c;代码量精简&#xff0c;便于逐行阅读和…

作者头像 李华
网站建设 2026/9/14 6:20:30

Steam Deck帧生成技术:lsfg-vk与Vulkan底层优化实战

1. “小黄鸭”不是玩具&#xff0c;是Steam Deck上跑得最野的帧生成器你拆开Steam Deck&#xff0c;摸着那块AMD APU的散热铜管&#xff0c;心里清楚&#xff1a;这台掌机的硬件上限就摆在这儿——RDNA2架构、4核8线程Zen2 CPU、8GB LPDDR5内存。它不是为《赛博朋克2077》全高画…

作者头像 李华