1.3 状态

2022-10-14

开始

和上篇一样,先在工作空间的 Cargo.toml 中添加 "ch01/state",添加之后文件内容如下:

[workspace]

members = [
    "ch01/helloworld",
    "ch01/error-handling",
    "ch01/state"
]

然后新建项目,将项目依赖和 main.rs 的内容编写好。

项目 Cargo.toml:

[dependencies]
actix-web = "4.2.1"

main.rs:

use actix_web::{HttpServer, App};

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    HttpServer::new(|| {
        App::new()
    })
    .bind(("127.0.0.1", 8080))?
    .run()
    .await
}

了解 State

在敲代码之前,可以先到官网查看这个 State 是什么。

官网上的描述是:

Application state is shared with all routes and resources within the same scope. State can be accessed with the web::Data extractor where T is the type of the state. State is also accessible for middleware.

在相同的中,所有的路由和资源可以共享应用状态(Application state)。状态(State)可以通过 web::Data<T> 来访问,其中 T 为状态的类型。状态也能通过中间件访问。

可以理解为在访问网站的时候,站内链接共享的一个变量或者对象。状态也拥有可变状态。添加状态需要在 HttpServer::new() 中,通过 App::new().app_data() 添加。

添加状态

看向官方源码,在 HttpServer::new() 前,新建了两个状态,在其中又新建了一个状态,类型分别为 Data<Mutex<usize>>Data<AtomicUsize>Cell<u32>,即线程互斥共享状态,线程安全共享整型和内部可变整型引用,初始值都为 0。在外面创建的两个状态,属于全局状态,而在内部创建的属于线程内状态。

在 main.rs 中添加代码,将新建的状态添加到应用中。

use std::{sync::{Mutex, atomic::AtomicUsize}, cell::Cell};
use actix_web::{HttpServer, App, web::Data};

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    #[allow(clippy::mutex_atomic)]
    let counter_mutex = Data::new(Mutex::new(0usize));
    let counter_atomic = Data::new(AtomicUsize::new(0usize));

    HttpServer::new(move || {
        let counter_cell = Cell::new(0u32);

        App::new()
            .app_data(counter_mutex.clone())
            .app_data(counter_atomic.clone())
            .app_data(Data::new(counter_cell))
    })
    .bind(("127.0.0.1", 8080))?
    .run()
    .await
}

在官方源码中,在 counter_mutex 上面有一行属性 #[allow(clippy::mutex_atomic)],是诊断属性,按照本人理解,将 counter_mutex 标记为检查其会执行的原子操作。不过具体作用还是有点模糊。

添加服务资源

上述三个状态,会在每次访问网址时,自增 1。按照官方例子添加函数 index。函数体内只有 todo!(),方便观察函数而不被报错的划线干扰。

use std::{sync::{Mutex, atomic::AtomicUsize}, cell::Cell};
use actix_web::{HttpServer, App, web::Data, HttpResponse, HttpRequest};

async fn index(
    counter_mutex: Data<Mutex<usize>>,
    counter_cell: Data<Cell<u32>>,
    counter_atomic: Data<AtomicUsize>,
    req: HttpRequest
) -> HttpResponse {
    todo!()
}

看向参数部分,前三个参数是我们添加到应用中的状态,第四个 req 的类型为 HttpRequest。而返回值类型为 HttpResponse

之前没有详细了解过将要访问的函数体,作为句柄添加到路由时,有哪些具体内容。首先可以在官网中的句柄章节中看到,请求句柄可以接受实现了 FromRequest trait 的参数,而 HttpRequest实现了,所以可以作为请求句柄中传入的参数。同理,web::Data 也是实现了 FromRequest trait,所以能够作为请求句柄的参数。

接下来根据不同的引用类型,来将引用的整型数字加 1。

use std::{sync::{Mutex, atomic::{AtomicUsize, Ordering}}, cell::Cell};
use actix_web::{HttpServer, App, web::Data, HttpResponse, HttpRequest};

async fn index(
    counter_mutex: Data<Mutex<usize>>,
    counter_cell: Data<Cell<u32>>,
    counter_atomic: Data<AtomicUsize>,
    req: HttpRequest
) -> HttpResponse {
    println!("{req:?}");

    *counter_mutex.lock().unwrap() += 1;
    counter_cell.set(counter_cell.get() + 1);
    counter_atomic.fetch_add(1, Ordering::SeqCst);

    todo!()
}

然后将增加后的内容,作为返回的内容。使用下面的代码替换掉 todo!() 的地方。

    let body = format!(
        "global mutex counter: {}, local counter: {}, global atomic counter: {}",
        *counter_mutex.lock().unwrap(),
        counter_cell.get(),
        counter_atomic.load(Ordering::SeqCst)
    );
    
    HttpResponse::Ok().body(body)

index 函数的内容编写完成了,现在将其配置到应用中,指定到根目录。

use std::{sync::{Mutex, atomic::{AtomicUsize, Ordering}}, cell::Cell};
use actix_web::{HttpServer, App, web::{Data, self}, HttpResponse, HttpRequest};

async fn index(...) -> HttpResponse {...}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    #[allow(clippy::mutex_atomic)]
    let counter_mutex = Data::new(Mutex::new(0usize));
    let counter_atomic = Data::new(AtomicUsize::new(0usize));

    HttpServer::new(move || {
        let counter_cell = Cell::new(0u32);

        App::new()
            .app_data(counter_mutex.clone())
            .app_data(counter_atomic.clone())
            .app_data(Data::new(counter_cell))
            // add here
            .service(web::resource("/").to(index))
    })
    .bind(("127.0.0.1", 8080))?
    .run()
    .await
}

尝试运行一下,看看效果。在每一次刷新后,可以看到数字增加了 1。这时回到控制台,可以看到我们在请求句柄中打印的请求内容了,发现在 HttpRequest 中包含了很多信息,如 Http 请求代码、请求 UA、host等。

添加日志打印功能

终止程序,并将 index 函数中,打印请求内容的函数注释掉。在 Cargo.toml 中添加 env_logger 的依赖。版本保证与例子的一致。

[dependencies]
actix-web = "4.2.1"
env_logger = "0.9.0"

然后在主函数的 HttpServer::new() 前,初始化 env_logger

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    // add here
    env_logger::init();

    #[allow(clippy::mutex_atomic)]
    let counter_mutex = Data::new(Mutex::new(0usize));
    let counter_atomic = Data::new(AtomicUsize::new(0usize));

    HttpServer::new(move || {...})
    .bind(("127.0.0.1", 8080))?
    .run()
    .await
}

接下来添加 actix-web 自带的 Logger 中间件。

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    env_logger::init();

    #[allow(clippy::mutex_atomic)]
    let counter_mutex = Data::new(Mutex::new(0usize));
    let counter_atomic = Data::new(AtomicUsize::new(0usize));

    HttpServer::new(move || {
        let counter_cell = Cell::new(0u32);

        App::new()
            .app_data(counter_mutex.clone())
            .app_data(counter_atomic.clone())
            .app_data(Data::new(counter_cell))
            // add here
            .wrap(middleware::Logger::default())
            .service(web::resource("/").to(index))
    })
    .bind(("127.0.0.1", 8080))?
    .run()
    .await
}

中间件的作用很多,actix-web 中也提供了一些中间件,可以在官网上或者文档中查看。此处添加的 Logger 中间件,可以将请求和回应的简洁描述打印到控制台上。要启用 Logger,需要提前初始化 env_logger 或者相似的 crate。

但是如果直接这样运行,会发现控制台上什么也不会打印。查看官方例子的代码,发现在初始化 env_logger 前,设置了一个临时环境变量 RUST_LOG,内容为 actix_web=info。那么我们同样的在 env_logger 前添加代码 std::env::set_var("RUST_LOG", "actix_web=info");。这时再次运行,发现访问网址时,控制台打印出日志了。

在后面的例子代码中,也可以通过 env_logger::init_from_env(env_logger::Env::new().default_filter_or("info")); 来初始化,就不需要使用 std::env::set_var 了。

结束

这一次,了解到了可以在应用域中访问的状态。通过 App::new().app_data()添加状态,而且在请求句柄中添加对应的参数,就可以访问了。

要能成为请求句柄的参数,参数对应的类型需要实现 FromRequest trait。

除此之外,还初次了解了 Logger 中间件。

actix-web学习笔记Rustactix-web学习笔记

学少何

不求上进的社畜……

1.4 嵌套路由

1.2 异常处理