类型安全的数据提取器
Actix Web 提供了一种类型安全的请求信息访问机制,称为 提取器(即 impl FromRequest
)。内置了许多提取器实现(参见实现者)。
提取器可以作为处理函数的参数访问。Actix Web 每个处理函数支持最多 12 个提取器。参数位置无关紧要。
async fn index(path: web::Path<(String, String)>, json: web::Json<MyInfo>) -> impl Responder {
let path = path.into_inner();
format!("{} {} {} {}", path.0, path.1, json.id, json.username)
}
Path(路径提取器)
Path 可以从请求路径中提取信息。可以提取的路径部分称为“动态段”,并用花括号标记。您可以从路径中反序列化任何变量段。
例如,对于注册了 /users/{user_id}/{friend}
路径的资源,可以反序列化两个段,user_id
和 friend
。这些段可以按照声明的顺序作为元组提取(例如,Path<(u32, String)>
)。
use actix_web::{get, web, App, HttpServer, Result};
/// extract path info from "/users/{user_id}/{friend}" url
/// {user_id} - deserializes to a u32
/// {friend} - deserializes to a String
#[get("/users/{user_id}/{friend}")] // <- define path parameters
async fn index(path: web::Path<(u32, String)>) -> Result<String> {
let (user_id, friend) = path.into_inner();
Ok(format!("Welcome {}, user_id {}!", friend, user_id))
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| App::new().service(index))
.bind(("127.0.0.1", 8080))?
.run()
.await
}
也可以将路径信息提取到实现了 serde
的 Deserialize
特质的 struct 中,通过将动态段名称与字段名称进行匹配。下面是一个使用 serde
而不是元组类型的等效示例。
use actix_web::{get, web, Result};
use serde::Deserialize;
#[derive(Deserialize)]
struct Info {
user_id: u32,
friend: String,
}
/// extract path info using serde
#[get("/users/{user_id}/{friend}")] // <- define path parameters
async fn index(info: web::Path<Info>) -> Result<String> {
Ok(format!(
"Welcome {}, user_id {}!",
info.friend, info.user_id
))
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
use actix_web::{App, HttpServer};
HttpServer::new(|| App::new().service(index))
.bind(("127.0.0.1", 8080))?
.run()
.await
}
作为一个非类型安全的替代方案,也可以在处理函数中按名称查询(参见 match_info
文档)请求的路径参数:
#[get("/users/{user_id}/{friend}")] // <- define path parameters
async fn index(req: HttpRequest) -> Result<String> {
let name: String = req.match_info().get("friend").unwrap().parse().unwrap();
let userid: i32 = req.match_info().query("user_id").parse().unwrap();
Ok(format!("Welcome {}, user_id {}!", name, userid))
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
use actix_web::{App, HttpServer};
HttpServer::new(|| App::new().service(index))
.bind(("127.0.0.1", 8080))?
.run()
.await
}
Query(查询参数提取器)
Query<T>
提供了请求查询参数的提取功能。它底层使用了 serde_urlencoded
crate。
use actix_web::{get, web, App, HttpServer};
use serde::Deserialize;
#[derive(Deserialize)]
struct Info {
username: String,
}
// this handler gets called if the query deserializes into `Info` successfully
// otherwise a 400 Bad Request error response is returned
#[get("/")]
async fn index(info: web::Query<Info>) -> String {
format!("Welcome {}!", info.username)
}
JSON(JSON格式的数据提取器)
Json<T>
允许将请求体反序列化为 struct。要从请求体中提取类型化的信息,类型 T
必须实现 serde::Deserialize
。
use actix_web::{post, web, App, HttpServer, Result};
use serde::Deserialize;
#[derive(Deserialize)]
struct Info {
username: String,
}
/// deserialize `Info` from request's body
#[post("/submit")]
async fn submit(info: web::Json<Info>) -> Result<String> {
Ok(format!("Welcome {}!", info.username))
}
一些提取器提供了对提取过程进行配置的方法。要配置提取器,需要将其配置对象传递给资源的 .app_data()
方法。在 Json 提取器的例子中,返回了一个 JsonConfig。你可以配置 JSON 负载的最大大小以及自定义错误处理程序函数。
下面的例子将请求体负载的大小限制为 4kb,并使用自定义错误处理程序。
use actix_web::{error, web, App, HttpResponse, HttpServer, Responder};
use serde::Deserialize;
#[derive(Deserialize)]
struct Info {
username: String,
}
/// deserialize `Info` from request's body, max payload size is 4kb
async fn index(info: web::Json<Info>) -> impl Responder {
format!("Welcome {}!", info.username)
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| {
let json_config = web::JsonConfig::default()
.limit(4096)
.error_handler(|err, _req| {
// create custom error response
error::InternalError::from_response(err, HttpResponse::Conflict().finish())
.into()
});
App::new().service(
web::resource("/")
// change json extractor configuration
.app_data(json_config)
.route(web::post().to(index)),
)
})
.bind(("127.0.0.1", 8080))?
.run()
.await
}
URL-Encoded Forms (URL编码表单提取器)
URL-encoded 的表单请求提可以被提取到一个 struct 中,就像 Json<T>
一样。这个类型必须实现 serde::Deserialize
。
可以使用 FormConfig 配置提取过程。
use actix_web::{post, web, App, HttpServer, Result};
use serde::Deserialize;
#[derive(Deserialize)]
struct FormData {
username: String,
}
/// extract form data using serde
/// this handler gets called only if the content type is *x-www-form-urlencoded*
/// and the content of the request could be deserialized to a `FormData` struct
#[post("/")]
async fn index(form: web::Form<FormData>) -> Result<String> {
Ok(format!("Welcome {}!", form.username))
}
Other(其他提取器)
Actix Web 也提供了许多其他提取器,这里有一些比较重要的:
Data
- 用于访问应用的状态。HttpRequest
-HttpRequest
本身就是一个提取器,可以访问请求的其他部分。String
- 可以将请求的负载转换为String
。在 rustdoc 中的 一个例子。Bytes
- 可以将请求的负载转换为 Bytes。在 rustdoc 中的 一个例子。Payload
- 底层的请求负载提取器,主要用于构建其他提取器。在 rustdoc 中的 一个例子。
Application State Extractor(应用状态提取器)
应用状态可以通过 web::Data
提取器访问。但是,状态只能作为只读引用访问。如果需要对状态进行可变访问,则必须实现。
下面是一个例子,展示了如何记录请求数:
use actix_web::{web, App, HttpServer, Responder};
use std::cell::Cell;
#[derive(Clone)]
struct AppState {
count: Cell<usize>,
}
async fn show_count(data: web::Data<AppState>) -> impl Responder {
format!("count: {}", data.count.get())
}
async fn add_one(data: web::Data<AppState>) -> impl Responder {
let count = data.count.get();
data.count.set(count + 1);
format!("count: {}", data.count.get())
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
let data = AppState {
count: Cell::new(0),
};
HttpServer::new(move || {
App::new()
.app_data(web::Data::new(data.clone()))
.route("/", web::to(show_count))
.route("/add", web::to(add_one))
})
.bind(("127.0.0.1", 8080))?
.run()
.await
}
尽管这个处理程序可以工作,但是 data.count
只会计算每个工作线程处理的请求数。要计算所有线程的总请求数,应该使用共享的 Arc
和 atomics。
use actix_web::{get, web, App, HttpServer, Responder};
use std::{
cell::Cell,
sync::atomic::{AtomicUsize, Ordering},
sync::Arc,
};
#[derive(Clone)]
struct AppState {
local_count: Cell<usize>,
global_count: Arc<AtomicUsize>,
}
#[get("/")]
async fn show_count(data: web::Data<AppState>) -> impl Responder {
format!(
"global_count: {}\nlocal_count: {}",
data.global_count.load(Ordering::Relaxed),
data.local_count.get()
)
}
#[get("/add")]
async fn add_one(data: web::Data<AppState>) -> impl Responder {
data.global_count.fetch_add(1, Ordering::Relaxed);
let local_count = data.local_count.get();
data.local_count.set(local_count + 1);
format!(
"global_count: {}\nlocal_count: {}",
data.global_count.load(Ordering::Relaxed),
data.local_count.get()
)
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
let data = AppState {
local_count: Cell::new(0),
global_count: Arc::new(AtomicUsize::new(0)),
};
HttpServer::new(move || {
App::new()
.app_data(web::Data::new(data.clone()))
.service(show_count)
.service(add_one)
})
.bind(("127.0.0.1", 8080))?
.run()
.await
}
Note: 如果要在所有线程之间共享整个状态,请使用 web::Data
和 app_data
,如共享可变状态中所述。
小心使用阻塞同步原语,如 Mutex
或 RwLock
。Actix Web 处理请求是异步的。如果您的处理程序中的 critical section 太大或包含 .await
,则会出现问题。如果这是一个问题,我们建议您也阅读 Tokio 关于在异步代码中使用阻塞 Mutex
的建议。