域名 AXUM.RS 将于2025年10月到期。我们无意再对其进行续费,我们希望你能够接续这个域名,让更多 AXUM 开发者继续受益。现在,我们已启用新域名 AXUM.EU.ORG
  • 方案AXUM.RS 域名 = 3000
如果你有意接续这份 AXUM 情怀,请与我们取得联系。
说明:
  1. 如果有人购买 AXUM.RS 域名,或者该域名到期,本站将使用免费域名 AXUM.EU.ORG 继续提供服务。

axum的状态共享

状态共享是指,在整个应用或不同路由之间,共享一份数据。axum 提供了方便的状态共享机制,但可能也会踩坑。本章将带你学习如何在 axum web 应用中共享状态。

如何进行状态共享

axum 使用 Layer 来实现状态共享。

定义路由时,使用 layer() 加入要共享的数据,在需要获取该共享数据的 handler 里,使用 Extension 这个 extract 来获取。

什么样的数据才能进行共享

出于并发原因,只有实现了 Clone trait 的数据类型才能进行共享。

下面模拟一个场景:在所有 handler 里都要获取用户信息。

定义数据结构

#[derive(Clone)]
pub struct UserInfo {
    pub username: String,
}

在路由中添加共享数据

let app = Router::new()
        .route("/user", routing::get(show_user_info))
        .layer(AddExtensionLayer::new(UserInfo {
            username: "axum.rs".to_string(),
        }));

我们通过在路由列表之后,使用 layer() 添加共享数据。注意,共享数据不是直接添加进去的,而是通过 AddExtensionLayer::new() 来实现。

在 handler 中获取共享数据

async fn show_user_info(Extension(info): Extension<UserInfo>) -> String {
    format!("Sigined User: {}", info.username)
}

本部分代码可以在这里找到。

本部分代码可以在这里找到。

如何让那些“顽固分子”也能进行共享

对于我们自己定义的数据结构来说,可以很明确的实现 Clone,而在实际开发中,你可能需要共享第三方库里的数据结构,而这个数据结构并没有实现 Clone,这时候该怎么办?

答案是使用 std::sync::Arc 这个智能指针。你现在脑海里应该明确两点:

  • axum 共享的数据必须是 Clone 的,也就是说不 move 它的所有权

  • Arc 是“线程安全”的引用计数智能指针,它只会读取值,不会转移所有权

Arc 是“线程安全”的引用计数智能指针,它只会读取值,不会转移所有权

定义数据结构

下面,我们将上例中的数据结构中实现 Clone 的部分删除:

pub struct UserInfo {
    pub username: String,
}

在路由中添加共享数据

let app = Router::new()
        .route("/user", routing::get(show_user_info))
        .layer(AddExtensionLayer::new(Arc::new(UserInfo {
            username: "axum.rs".to_string(),
        })));

在 handler 中获取共享数据

async fn show_user_info(Extension(info): Extension<Arc<UserInfo>>) -> String {
    format!("Sigined User: {}", info.username)
}

本部分代码可以在这里找到。

本部分代码可以在这里找到。

共享复合数据

日常开发中,要共享状态的往往不只一个结构体。而且,这些要共享的结构体也可能出现有些实现了 Clone,有些没有实现。

解决方案是,将这些共享状态封装到一个结构体中。对于那些没有实现 Clone 的,可以:

  • 在这个封装的结构体中,使用 Arc 进行包装,而这个封装的结构体本身要实现 Clone

  • 使用常规方式封装结构体,并且不实现 Clone。通过 Arc 共享这个封装的结构体

在这个封装的结构体中,使用 Arc 进行包装,而这个封装的结构体本身要实现 Clone

使用常规方式封装结构体,并且不实现 Clone。通过 Arc 共享这个封装的结构体

下面以第一种方式来演示。

#[derive(Clone)]
pub struct DatabaseClient {
    pub dsn: String,
}
pub struct RedisClient {
    pub host: String,
}

#[derive(Clone)]
pub struct AppState {
    pub db: DatabaseClient,
    pub rdb: Arc<RedisClient>,
}

在路由中添加共享数据

let db_client = DatabaseClient {
        dsn: "host=pg.axum.rs port=5432 user=axum_rs password=axum.rs sslmode=disable".to_string(),
    };
    let redis_client = Arc::new(RedisClient {
        host: "redis.axum.rs".to_string(),
    });

    let app = Router::new()
        .route("/status", routing::get(status))
        .layer(AddExtensionLayer::new(AppState {
            db: db_client,
            rdb: redis_client,
        }));

在 handler 中获取共享数据

本部分代码可以在这里找到。

本部分代码可以在这里找到。

本章对 axum 中共享状态进行了讨论,相关源代码可以在代码库找到。

试着自己实现第二种方式,即:

使用常规方式封装结构体,并且不实现 Clone。通过 Arc 共享这个封装的结构体

使用常规方式封装结构体,并且不实现 Clone。通过 Arc 共享这个封装的结构体

参考答案:

定义数据结构

#[derive(Clone)]
pub struct DatabaseClient {
    pub dsn: String,
}
pub struct RedisClient {
    pub host: String,
}

pub struct AppState {
    pub db: DatabaseClient,
    pub rdb: RedisClient,
}

在路由中添加共享数据

在 handler 中获取共享数据

async fn status(Extension(state): Extension<Arc<AppState>>) -> String {
    format!(
        "database dsn: {}, redis host: {}",
        state.db.dsn, state.rdb.host
    )
}

本部分代码可以在这里找到。

本部分代码可以在这里找到。

要查看完整内容,请先登录