开始
和上篇一样,先在工作空间的 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
中间件。