概念:
**定义:**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),
)
}