ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

axum Handler 完全指南:理解 Rust 请求处理函数的核心概念、提取器与源码实现

axum Handler 完全指南:理解 Rust 请求处理函数的核心概念、提取器与源码实现 axum Handler 完全指南理解 Rust 请求处理函数的核心概念、提取器与源码实现【免费下载链接】axumHTTP routing and request-handling library for Rust that focuses on ergonomics and modularity项目地址: https://gitcode.com/GitHub_Trending/ax/axum本篇技术指南围绕 axum 官方文档中关于Handler请求处理函数的核心定义展开深入讲解Handler 是接受零个或多个提取器Extractor参数、返回可转换为响应的值的异步函数这一基础概念并结合 axum 仓库源码、模块文档与示例剖析 Handler 如何承载应用逻辑、如何通过路由组织、如何处理错误以及如何转换为Service。读完本文你将掌握 axum Handler 的完整使用方式并能从源码层面理解HandlerT, Strait、提取器参数顺序约束与#[axum::debug_handler]调试技巧。一、什么是 Handleraxum 的核心定义axum 官方文档axum/src/docs/handlers_intro.md对 Handler 给出了精确定义In axum a handler is an async function that accepts zero or more extractors as arguments and returns something that can be converted into a response.翻译过来即Handler 是一个异步函数async fn它接受零个或多个提取器Extractor作为参数并返回一个可以转换为响应Response的值。这一定义拆解出三个关键要素必须是异步函数async fn或者是返回Future的闭包参数是提取器参数类型实现FromRequest或FromRequestPartstrait见 axum/src/extract/mod.rs返回值可转换为响应返回值类型实现IntoResponsetrait见 axum/src/response/mod.rs。文档进一步强调Handlers are where your application logic lives and axum applications are built by routing between handlers.也就是说Handler 是承载你应用逻辑的地方axum 应用正是通过路由routing将请求分发给各个 Handler 而构建起来的。一个 axum 应用本质上就是一张路由表Router把不同路径/方法映射到不同的 HandlerHandler 内部完成业务逻辑并生成响应。二、最简单的 Handler三个入门示例在 axum/src/handler/mod.rs 的模块文档中官方给出了三个循序渐进的 Handler 示例直观展示了返回值可转换为响应这一原则use axum::{body::Bytes, http::StatusCode}; // Handler that immediately returns an empty 200 OK response. async fn unit_handler() {} // Handler that immediately returns a 200 OK response with a plain text // body. async fn string_handler() - String { Hello, World!.to_string() } // Handler that buffers the request body and returns it. // // This works because Bytes implements FromRequest // and therefore can be used as an extractor. // // String and StatusCode both implement IntoResponse and // therefore ResultString, StatusCode also implements IntoResponse async fn echo(body: Bytes) - ResultString, StatusCode { if let Ok(string) String::from_utf8(body.to_vec()) { Ok(string) } else { Err(StatusCode::BAD_REQUEST) } }这三个例子分别说明了unit_handler返回()()实现了IntoResponse会生成一个空的200 OK响应string_handler返回String字符串直接作为纯文本响应体返回echo演示了提取器Bytes消费请求体与ResultString, StatusCode由于String和StatusCode都实现了IntoResponseResultString, StatusCode也自动实现了IntoResponse——Ok时返回正常文本Err时返回对应状态码。一个完整的可运行示例参见 examples/hello-world/src/main.rs它展示了如何用Router::new().route(/, get(handler))将 Handler 挂载到路由上并通过axum::serve启动服务use axum::{response::Html, routing::get, Router}; #[tokio::main] async fn main() { // build our application with a route let app Router::new().route(/, get(handler)); // run it let listener tokio::net::TcpListener::bind(127.0.0.1:3000) .await .unwrap(); println!(listening on {}, listener.local_addr().unwrap()); axum::serve(listener, app).await; } async fn handler() - Htmlstatic str { Html(h1Hello, World!/h1) }三、提取器ExtractorHandler 参数的底层机制Handler 的参数被称为提取器因为它们负责从 HTTP 请求中提取数据。axum 文档axum/src/docs/extract.md明确A handler function is an async function that takes any number of extractors as arguments. An extractor is a type that implementsFromRequestorFromRequestParts.axum 内置了丰富的提取器常用的有提取器作用Path提取路径参数并反序列化如/users/{user_id}Query提取并反序列化查询字符串参数HeaderMap获取全部请求头String消费请求体并确保其是合法 UTF-8 文本Bytes获取原始请求体字节Json将请求体按 JSON 反序列化为目标类型Request获取完整请求对象获得最大控制力Extension从请求扩展extensions中提取数据常用于共享状态State提取应用状态axum 0.7 推荐用于共享状态3.1 参数顺序约束请求体只能被消费一次提取器的一个重要约束是执行顺序严格遵循函数参数从左到右的顺序。更关键的是请求体是一个异步流只能被消费一次。因此 axum 强制要求会消费请求体的提取器如String、Json、Bytes必须是 Handler 的最后一个参数其余不消费请求体的提取器如Method、HeaderMap、State可以放在前面任意位置一个 Handler 中不能同时使用两个会消费请求体的提取器。axum 通过 trait 设计在编译期强制执行这一规则详见 axum-core/src/extract/mod.rs最后一个参数必须实现FromRequest其余参数必须实现FromRequestParts。3.2 提取器失败的处理每个提取器都有自己的拒绝类型Rejection。如果提取器失败请求会被拒绝且 Handler 不会被调用。若要针对特定 Handler 定制失败处理可以把提取器包在Result中例如use axum::{ extract::{Json, rejection::JsonRejection}, routing::post, Router, }; use serde_json::Value; async fn create_user(payload: ResultJsonValue, JsonRejection) { match payload { Ok(payload) { /* 拿到合法 JSON */ } Err(JsonRejection::MissingJsonContentType(_)) { /* 缺少 Content-Type 头 */ } Err(JsonRejection::JsonDataError(_)) { /* 无法反序列化 */ } Err(JsonRejection::JsonSyntaxError(_)) { /* 语法错误 */ } Err(_) { /* JsonRejection 是 #[non_exhaustive]需兜底 */ } } }四、返回值与错误处理IntoResponse 与 ResultHandler 的返回值必须实现IntoResponse。axum 为大量类型提供了IntoResponse实现()、String、static str、StatusCode、(StatusCode, String)元组、ResultT, E当T和E都实现IntoResponse时等。官方模块文档特别建议Instead of a directStatusCode, it makes sense to use intermediate error type that can ultimately be converted toResponse. This allows using?operator in handlers.即与其直接返回StatusCode作为错误不如定义一个中间错误类型让它最终能转换为Response这样就能在 Handler 中使用?运算符写出简洁的快速失败风格代码。文档给出了两个官方示例作为参考examples/anyhow-error-response/src/main.rs适用于泛化的 boxed 错误。示例定义了一个包装anyhow::Error的AppError类型并为其实现IntoResponse// Make our own error that wraps anyhow::Error. struct AppError(anyhow::Error); // Tell axum how to convert AppError into a response. impl IntoResponse for AppError { fn into_response(self) - Response { ( StatusCode::INTERNAL_SERVER_ERROR, format!(Something went wrong: {}, self.0), ) .into_response() } } // 通过 From 实现使 ? 能自动把 anyhow::Error 转成 AppError implE FromE for AppError where E: Intoanyhow::Error, { fn from(err: E) - Self { Self(err.into()) } } async fn handler() - Result(), AppError { try_thing()?; // 这里可以直接使用 ? Ok(()) }examples/error-handling/src/main.rs适用于应用特定、携带详细错误信息的场景演示了如何把ResultT, AppError与?结合并通过自定义AppJson提取器统一格式化输入错误、用from_fn(log_app_errors)中间件记录 5xx 错误日志。五、源码剖析HandlerT, Strait 与 blanket 实现从源码层面看Handler 的背后是一个 trait。在 axum/src/handler/mod.rs 中定义pub trait HandlerT, S: Clone Send Sync Sized static { /// The type of future calling this handler returns. type Future: FutureOutput Response Send static; /// Call the handler with the given request. fn call(self, req: Request, state: S) - Self::Future; /// Apply a [tower::Layer] to the handler. fn layerL(self, layer: L) - LayeredL, Self, T, S { ... } /// Convert the handler into a [Service] by providing the state fn with_state(self, state: S) - HandlerServiceSelf, T, S { HandlerService::new(self, state) } }官方文档说明通常你不需要直接依赖这个 trait它由 axum 自动为符合要求的函数/闭包实现。5.1 类型参数T的作用关于 trait 的类型参数T模块文档解释得十分透彻T是绕过 Rust trait 相干性规则coherence rules的变通手段。它允许 axum 为不同参数个数的 Handler 函数编写 blanket 实现而不会因为同一个类型F理论上既能实现Fn(A) - X又能实现Fn(A, B) - Y而被编译器禁止。T是一个占位符代表 Handler 函数参数集合的某种表示从而让编译器能为每种函数签名选择唯一的Handler实现。在你平时的应用代码中无需关心T调用routing::get、post等方法时T会被自动推断。5.2 通过宏批量生成实现axum/src/handler/mod.rs 中的impl_handler!宏配合all_the_tuples!为 1 到 16 个参数的 Handler 批量生成实现。核心逻辑清晰展现了提取器执行流程macro_rules! impl_handler { ( [$($ty:ident),*], $last:ident ) { implF, Fut, S, Res, M, $($ty,)* $last Handler(M, $($ty,)* $last,), S for F where F: FnOnce($($ty,)* $last,) - Fut Clone Send Sync static, Fut: FutureOutput Res Send, S: Send Sync static, Res: IntoResponse, $( $ty: FromRequestPartsS Send, )* $last: FromRequestS, M Send, { fn call(self, req: Request, state: S) - Self::Future { let (mut parts, body) req.into_parts(); Box::pin(async move { // 1. 依次对除最后一个外的参数调用 from_request_parts // 2. 用剩余 parts 重新组装 Request // 3. 对最后一个参数调用 from_request可消费 body // 4. 调用用户函数 self(...)并把返回值 into_response() }) } } }; }这段代码印证了前面的两个约束除最后一个参数外其余参数必须实现FromRequestPartsS不消费 body最后一个参数必须实现FromRequestS, M允许消费 body返回值Res: IntoResponse最终统一转换为Response提取器任一步失败时rejection.into_response()直接生成错误响应返回不会调用用户函数。另外Handlertrait 还为T: IntoResponse的类型提供了实现见 axum/src/handler/mod.rs这意味着非函数的值也能直接作为 Handler方便为路由返回固定数据use axum::{ Router, routing::{get, post}, Json, http::StatusCode, }; use serde_json::json; let app Router::new() // respond with a fixed string .route(/, get(Hello, World!)) // or return some mock data .route(/users, post(( StatusCode::CREATED, Json(json!({ id: 1, username: alice })), )));六、Handler 与 Service 的转换with_state、layer 与中间件axum 的 Handler 建立在 tower 的Service抽象之上。模块文档提供了将 Handler 转换为 Service的标准途径use tower::Service; use axum::{ extract::{State, Request}, body::Body, handler::{HandlerWithoutStateExt, Handler}, }; // this handler doesnt require any state async fn one() {} // so it can be converted to a service with HandlerWithoutStateExt::into_service assert_service(one.into_service()); // this handler requires state async fn two(_: StateString) {} // so we have to provide it let handler_with_state two.with_state(String::new()); // which gives us a Service assert_service(handler_with_state); // helper to check that a value implements Service fn assert_serviceS(service: S) where S: ServiceRequest, {}三种主要转换方式Handler::with_state(state)为需要状态的 Handler 提供状态返回HandlerService见 axum/src/handler/service.rsHandlerWithoutStateExt::into_service()无状态 Handler 直接转换为ServiceHandlerWithoutStateExt::into_make_service()/into_make_service_with_connect_info()转换为MakeService可直接用于axum::serve甚至配合ConnectInfo获取连接信息如客户端SocketAddr。HandlerService实现了tower_service::ServiceRequestB其poll_ready恒为就绪因为异步函数总是 readyLayered则在call内部缓冲Error类型为Infallible。6.1layer为单个 Handler 附加中间件Handler::layer可以为单个 Handler附加 tower 中间件这与Router::layer作用于一组路由不同。官方示例use axum::{ routing::get, handler::Handler, Router, }; use tower::limit::{ConcurrencyLimitLayer, ConcurrencyLimit}; async fn handler() { /* ... */ } let layered_handler handler.layer(ConcurrencyLimitLayer::new(64)); let app Router::new().route(/, get(layered_handler));如果中间件会产生错误你需要处理这些错误并把它们转换为响应详见 axum/src/docs/error_handling.md。七、调试 Handler 类型错误#[axum::debug_handler]Handler 对函数形态有严格要求。官方文档axum/src/docs/debugging_handler_type_errors.md列出了函数可作为 Handler 的全部条件是async fn参数不超过 16 个且全部实现Send除最后一个参数外均实现FromRequestParts最后一个参数实现FromRequest返回值实现IntoResponse若使用闭包则必须实现Clone Send且为static返回的 future 必须Send最常见的意外使 future 不Send的方式是在await期间持有!Send类型。问题在于Rust 编译器对不符合要求的函数会给出非常糟糕的错误信息。例如你可能会看到error[E0277]: the trait bound fn(bool) - impl Future {handler}: Handler_, _ is not satisfied -- src/main.rs:13:44 | 13 | let app Router::new().route(/, get(handler)); | ^^^^^^^ the trait Handler_, _ is not implemented for fn(bool) - impl Future {handler}这个错误不会告诉你为什么你的函数不满足Handler。解决办法是使用#[axum::debug_handler]过程宏来自 axum-macros crate实现见 axum-macros/src/debug_handler.rs它能在编译期生成更精确、可读的错误信息。使用方式是在有问题的 Handler 函数上加一行属性即可use axum::debug_handler; #[axum::debug_handler] async fn handler(arg: SomeExtractor) { /* ... */ }这也是Handlertrait 上#[diagnostic::on_unimplemented]注解所提示的做法#[diagnostic::on_unimplemented( note Consider using #[axum::debug_handler] to improve the error message )] pub trait HandlerT, S: Clone Send Sync Sized static { ... }axum-macros 的测试目录axum-macros/tests/debug_handler/fail/中存放了大量反例与对应的.stderr期望输出例如argument_not_extractor.rs、multiple_request_consumers.rs、not_async.rs、not_send.rs等是理解debug_handler各类诊断信息的绝佳学习材料。八、结语Handler 是 axum 应用的最小业务单元一个接受提取器参数、返回可转换为响应值的异步函数。通过Router的路由分发多个 Handler 组织成完整的 Web 应用。理解 Handler 需要把握三条主线参数侧提取器体系FromRequestParts/FromRequest、参数顺序约束、请求体只能消费一次返回值侧IntoResponse体系、用中间错误类型配合?运算符的错误处理模式底层机制HandlerT, Strait 的 blanket 实现、T的相干性变通设计、与 towerService的互转以及#[axum::debug_handler]这一调试利器。深入阅读建议继续查看 axum/src/handler/mod.rs含模块文档与 trait 定义、axum/src/handler/service.rsHandlerService实现、axum/src/docs/extract.md提取器完整指南以及 examples/error-handling/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),仅供参考
返回列表