Axum web框架【实习】

发布于:2025-09-10 ⋅ 阅读:(15) ⋅ 点赞:(0)

概念:

定义:Axum是Rust生态的一个现代web框架,基于Tokio、Tower、Hyper这些库。

Tokio(异步运行时)、Tower(模块化中间件系统)、Hyper(HTTP实现)

特点:异步支持、类型安全、中间件系统,最重要的是类型安全,Rust 给予编译时检查减少运行时错误。

核心功能:

  1. 异步优先:完全基于async/await语法、充分利用Rust的异步生态(Tokio)。
  2. 类型安全:通过编译期间的类型检查错误(路由参数不匹配、请求数据格式错误),减少运行时的问题。
  3. 模块中间件:没有自己的中间件系统,是基于Tower中间件系统,轻松集成日志、CORS、认证、限流等,且支持自定义中间件。
  4. 提取器(Extractors):可以灵活地从请求中提取数据进行声明式解析(路径参数、查询参数、请求头、请求体等),且支持自定义提取器。
  5. 路由简洁:直观的语法定义路由,支持嵌套路由、通配符,容易维护。
  6. 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 提供了许多有用的提取器,例如:

  • BytesStringBody, 和 BodyStream 用于获取请求正文
  • MethodHeaderMap, 和 Uri 用于获取请求的特定部分
  • FormQueryUrlParams, 和 UrlParamsMap 用于更高级别的请求解析
  • Extension 用于跨处理程序共享状态的扩展
  • Request<hyper::Body> 如果你想完全控制
  • Result<T, E> and Option<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),
    )
}

网站公告

今日签到

点亮在社区的每一天
去签到