概念:
定义:Axum是Rust生态的一个现代web框架,基于Tokio、Tower、Hyper这些库。
Tokio(异步运行时)、Tower(模块化中间件系统)、Hyper(HTTP实现)
特点:异步支持、类型安全、中间件系统,最重要的是类型安全,Rust 给予编译时检查减少运行时错误。
核心功能:
- 异步优先:完全基于async/await语法、充分利用Rust的异步生态(Tokio)。
- 类型安全:通过编译期间的类型检查错误(路由参数不匹配、请求数据格式错误),减少运行时的问题。
- 模块中间件:没有自己的中间件系统,是基于Tower中间件系统,轻松集成日志、CORS、认证、限流等,且支持自定义中间件。
- 提取器(Extractors):可以灵活地从请求中提取数据进行声明式解析(路径参数、查询参数、请求头、请求体等),且支持自定义提取器。
- 路由简洁:直观的语法定义路由,支持嵌套路由、通配符,容易维护。
- Rust生态兼容:无缝集成序列化和反序列化(Serde)、数据库交互(sqlx)等主流库。
用途:通过组合组件构建web应用,构建REST API、微服务以及其他HTTP服务。
一个Hello World的入门示例:
[dependencies] axum = "0.6.16" tokio = {version = "1.0", features = ["full"]} //full意味着开启tokio库所有可选功能
添加上面的依赖项,就可以编码了
use axum::{ //定义HTTP GET请求的路由处理函数 routing::get, //构建路由表,将传入的路径映射到相应的处理函数 Router, }; //网络套接字地址,指定服务器监听的ip和port use std::net::SocketAddr; //宏,将主函数转换成异步函数,使用异步运行时 #[tokio::main] async fn main() { //创建一个Router实例,将跟路径"/"的请求映射给root函数 let app = Router::new().route("/", get(root)); //创建套接字实例,绑定到指定的ip和port let addr = SocketAddr::from(([127, 0, 0, 1], 3000)); //启动HTTP服务器,监听指定的地址和端口,并处理传入的请求 axum::Server::bind(&addr) .serve(app.into_make_service()) .await //异步等待服务器启动 .unwrap(); //处理可能出现的错误 } //异步处理函数,当客户端访问跟路径时,返回静态字符串 async fn root() -> &'static str { "Hello, World!" }
使用命令cargo run启动后,浏览器跑一下或像下图另开终端输入命令:curl -X GET http://127.0.0.1:3000,可以看到hello,world!的输出
路由(Routers)
Router设置了哪些路径指向哪些服务
use axum::routing::{get, post}; use axum::{Router, Server}; //宏,将主函数转换成异步函数,使用异步运行时 #[tokio::main] async fn main() { //创建路由实例,进行路由配置:路径+方法对应的处理函数 let app = Router::new() .route("/", get(root)) //http://127.0.0.1:3000/ .route("/foo", get(get_foo).post(post_foo)) //http://127.0.0.1:3000/foo .route("/foo/bar", get(get_foo_bar)); //http://127.0.0.1:3000/foo/bar //启动HTTP服务器,监听指定的地址和端口,并处理传入的请求 axum::Server::bind(&"127.0.0.1:3000".parse().unwrap()) .serve(app.into_make_service()) .await //异步等待服务器启动 .unwrap(); //处理可能出现的错误 } async fn root() -> String { String::from("Hello axum!") } async fn get_foo() -> String { String::from("get请求的foo") } async fn post_foo() -> String { String::from("post请求的foo") } async fn get_foo_bar() -> String { String::from("get请求的foo/bar") }
cargo run 运行后,另开终端输入curl -X GET http://127.0.0.1/foo或curl -X POST http://127.0.0.1/foo进行试验
处理器(Handler)
在axum中,处理器就是一个异步函数或异步代码块,接收着提取器的产物作为参数,并返回一些可以转换成IntoResponse的内容。
处理器是应用程序逻辑存在的地方,而应用程序是通过处理器之间的路由构建的。
提取器(Extractor)
使用提取器可以把一个请求的数据提取出来,采用声明式解析,作用是解析出来的数据用于处理程序所需的部分(例如:解析异步函数的参数,请求的URL匹配,就会运行该异步函数)
use axum::extract::{Path, Query, Json}; use std::collections::HashMap; // Path路径,eg. /users/<id> async fn path(Path(user_id): Path<u32>) {} // Query参数,eg. /users?id=123&name=jim async fn query(Query(params): Query<HashMap<String, String>>) {} // Json 格式参数,一般用于 POST 请求 async fn json(Json(payload): Json<serde_json::Value>) {}
例如:Extrator中的Json就是一个提取器,异步处理函数接受请求后解析成JSON
[dependencies] serde = { version = "1.0", features = ["derive"] } serde_json = "1.0"
添加如上依赖后,可以编写一个HTTP POST请求给服务器,服务器解析请求中的请求体,返回JSON反序列化后的Rust结构体。
use axum::{ routing::post, http::StatusCode, Router, extract::Json, //消耗主体并解析成JSON }; use serde::{Deserialize,Serialize}; //将Json数据反序列化成Rust结构体 #[derive(Deserialize,Serialize,Debug)] struct CreateUser { username: String, // 接收创建用户的请求名 } //参数:从请求中提取JSON数据反序列化成结构体 async fn create_user(Json(payload):Json<CreateUser>) ->(StatusCode,Json<CreateUser>){ //响应内容是Json格式 (StatusCode::CREATED,Json(payload)) } #[tokio::main] async fn main(){ let app =Router::new(). route("/users",post(create_user)); axum::Server::bind(&"127.0.0.1:3000".parse().unwrap()) .serve(app.into_make_service()) .await .unwrap(); }
在终端上cargo run启动后,执行命令curl -H "Content-Type: application/json" -d '{"username":"TXS"}' -X POST 或 浏览器输入URL:http://127.0.0.1:3000/users
注意:浏览器默认发送的都是GET请求,所以在浏览器输入URL:http://127.0.0.1:3000/users 不会显现POST请求的页面,因为此代码也没有发送一个get请求,光可以发送post请求。
axum
提供了许多有用的提取器,例如:
Bytes
,String
,Body
, 和BodyStream
用于获取请求正文Method
,HeaderMap
, 和Uri
用于获取请求的特定部分Form
,Query
,UrlParams
, 和UrlParamsMap
用于更高级别的请求解析Extension
用于跨处理程序共享状态的扩展Request<hyper::Body>
如果你想完全控制Result<T, E>
andOption<T>
使提取器成为可选
你也可以通过实现 FromRequest
来定义你自己的提取器。
构建响应(IntoResponse)
处理器返回的任何类型的值,只要是有关实现了IntoResponse的值,Axum就可以自动把这些值转换成HTTP响应。
意味着在实践中,很少需要建立响应,或许也可以实现IntoResponse创建自己的特定响应
//引入的依赖 use http::StatusCode; use axum::response::{Html, Json}; //创建HTML响应,创建JSON响应 use serde_json::{json, Value}; //返回静态的字符串会自动转换成状态码200的响应 async fn text() -> &'static str { "Hello, World!" } //同理 async fn string() -> String { "Hello, World!".to_string() } //返回一个元组,也会转换成一个状态码为404的响应 async fn not_found() -> (StatusCode, &'static str) { (StatusCode::NOT_FOUND, "not found") } //返回的HTML,转换成200响应 async fn html() -> Html<&'static str> { Html("<h1>Hello, World!</h1>") } //返回JSON对象,也会返回200响应 async fn json() -> Json<Value> { Json(json!({ "data": 42 })) }
错误处理(Error handing)
auxm提供了一个错误处理模型,那么错误转换成响应就非常简单了,并且保证所有错误都可以得到处理。
use std::time::Duration; use axum::{ body::Body, error_handling::{HandleError, HandleErrorLayer}, http::{Method, Response, StatusCode, Uri}, response::IntoResponse, routing::get, BoxError, Router, }; use tower::ServiceBuilder; #[tokio::main] async fn main() { let app = Router::new() .merge(router_fallible_service()) // 模拟使用 Service的错误处理 .merge(router_fallible_middleware()) // 模拟使用中间件的错误处理 .merge(router_fallible_extractor()); // 模拟使用提取器的错误处理 let addr = "127.0.0.1:3000"; println!("listening on {}", addr); axum::Server::bind(&addr.parse().unwrap()) .serve(app.into_make_service()) .await .unwrap(); } // 错误处理方式1: 模拟使用 Service的错误处理 fn router_fallible_service() -> Router { // 这个 Service 可能出现任何错误 let some_fallible_service = tower::service_fn(|_req| async { thing_that_might_fail().await?; Ok::<_, anyhow::Error>(Response::new(Body::empty())) }); Router::new().route_service( "/", // Service 适配器通过将错误转换为响应来处理错误。 HandleError::new(some_fallible_service, handle_anyhow_error), ) } // 业务处理逻辑,可能出现失败而抛出 Error async fn thing_that_might_fail() -> Result<(), anyhow::Error> { // 模拟一个错误 anyhow::bail!("thing_that_might_fail") } // 把错误转化为 IntoResponse async fn handle_anyhow_error(err: anyhow::Error) -> (StatusCode, String) { ( StatusCode::INTERNAL_SERVER_ERROR, format!("Something went wrong: {}", err), ) } // 处理器:模拟超时 async fn handler_timeout() -> impl IntoResponse { println!("sleep 3 seconds"); tokio::time::sleep(Duration::from_secs(3)).await; // 休眠3秒,模拟超时 format!("Hello Error Handling !!!") } // 错误处理方式2 : 用中间件处理错误的路由 fn router_fallible_middleware() -> Router { Router::new() .route("/fallible_middleware", get(handler_timeout)) .layer( ServiceBuilder::new() // `timeout` will produce an error if the handler takes // too long so we must handle those .layer(HandleErrorLayer::new(handler_timeout_error)) .timeout(Duration::from_secs(1)), ) } // 用中间件处理错误 async fn handler_timeout_error(err: BoxError) -> (StatusCode, String) { if err.is::<tower::timeout::error::Elapsed>() { ( StatusCode::REQUEST_TIMEOUT, "Request time too long, Timeout!!!".to_string(), ) } else { ( StatusCode::INTERNAL_SERVER_ERROR, format!("Unhandled internal error: {}", err), ) } } // 错误处理方式3: 用运行时提取器处理错误的路由 fn router_fallible_extractor() -> Router { Router::new() .route("/fallible_extractor", get(handler_timeout)) .layer( ServiceBuilder::new() // `timeout` will produce an error if the handler takes // too long so we must handle those .layer(HandleErrorLayer::new(handler_timeout_fallible_extractor)) .timeout(Duration::from_secs(1)), ) } // 用运行时提取器处理错误 async fn handler_timeout_fallible_extractor( // `Method` and `Uri` are extractors so they can be used here method: Method, uri: Uri, // the last argument must be the error itself err: BoxError, ) -> (StatusCode, String) { ( StatusCode::INTERNAL_SERVER_ERROR, format!("`{} {}` failed with {}", method, uri, err), ) }