提取器

类型安全的信息提取

Actix-web提供了一种用于类型安全的访问请求信息的工具,称为提取器(即impl FromRequest)。 默认情况下,actix-web提供了几种提取器实现。

提取器可以作为handler函数的参数进行访问。 Actix-web每个handler函数最多支持10个提取器。 参数位置无关紧要。

async fn index(
    path: web::Path<(String, String)>,
    json: web::Json<MyInfo>,
) -> impl Responder {
    format!("{} {} {} {}", path.0, path.1, json.id, json.username)
}

Path 路径

Path提供可以从请求路径中提取的信息。 您可以从path反序列化任何变量段。

例如,对于/users/{userid}/{friend}路径注册的resource(资源),可以反序列化两个segments(段),即useridfriend。 这些段可以提取到一个元组中,即Path<(u32, String)>或从serde crate实现Deserialize特性的任何structure(结构体)。

use actix_web::{web, Result};

/// extract path info from "/users/{userid}/{friend}" url
/// {userid} -  - deserializes to a u32
/// {friend} - deserializes to a String
async fn index(info: web::Path<(u32, String)>) -> Result<String> {
    Ok(format!("Welcome {}, userid {}!", info.1, info.0))
}

#[actix_rt::main]
async fn main() -> std::io::Result<()> {
    use actix_web::{App, HttpServer};

    HttpServer::new(|| {
        App::new().route(
            "/users/{userid}/{friend}", // <- define path parameters
            web::get().to(index),
        )
    })
    .bind("127.0.0.1:8088")?
    .run()
    .await
}

也有可能从serde提取path信息到实现Deserialize(反序列化)特征的特定类型。 这是一个使用*serde*而不是*tuple*类型的等效示例。

use actix_web::{web, Result};
use serde::Deserialize;

#[derive(Deserialize)]
struct Info {
    userid: u32,
    friend: String,
}

/// extract path info using serde
async fn index(info: web::Path<Info>) -> Result<String> {
    Ok(format!("Welcome {}, userid {}!", info.friend, info.userid))
}

#[actix_rt::main]
async fn main() -> std::io::Result<()> {
    use actix_web::{App, HttpServer};

    HttpServer::new(|| {
        App::new().route(
            "/users/{userid}/{friend}", // <- define path parameters
            web::get().to(index),
        )
    })
    .bind("127.0.0.1:8088")?
    .run()
    .await
}

也可以通过name来getquery路径参数的请求:

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("userid").parse().unwrap();

    Ok(format!("Welcome {}, userid {}!", name, userid))
}

#[actix_rt::main]
async fn main() -> std::io::Result<()> {
    use actix_web::{App, HttpServer};

    HttpServer::new(|| {
        App::new().route(
            "/users/{userid}/{friend}", // <- define path parameters
            web::get().to(index),
        )
    })
    .bind("127.0.0.1:8088")?
    .run()
    .await
}

Query

Query类型为请求的查询参数提供提取功能。 它的下面使用了serde_urlencoded crate。

use actix_web::web;
use serde::Deserialize;

#[derive(Deserialize)]
struct Info {
    username: String,
}

// this handler get called only if the request's query contains `username` field
async fn index(info: web::Query<Info>) -> String {
    format!("Welcome {}!", info.username)
}

Json

Json允许将请求body反序列化为struct(结构)。 要从请求的正文中(body)提取类型化的信息,类型T必须从*serde*实现Deserialize(反序列化) trait(特征)。

use actix_web::{web, Result};
use serde::Deserialize;

#[derive(Deserialize)]
struct Info {
    username: String,
}

/// deserialize `Info` from request's body
async fn index(info: web::Json<Info>) -> Result<String> {
    Ok(format!("Welcome {}!", info.username))
}

一些提取程序提供了一种配置提取过程的方法。 Json提取器的JsonConfig类型用于configuration(配置)。 要配置提取程序,请将其配置对象传递到resource的.data()方法。 如果是Json提取器,它将返回*JsonConfig*。 您可以配置json payload的最大大小以及自定义错误处理函数。

以下示例将payload的大小限制为4kb,并使用自定义错误处理程序。

use actix_web::{error, web, FromRequest, HttpResponse, 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_rt::main]
async fn main() -> std::io::Result<()> {
    use actix_web::{App, HttpServer};

    HttpServer::new(|| {
        App::new().service(
            web::resource("/")
                // change json extractor configuration
                .app_data(web::Json::<Info>::configure(|cfg| {
                    cfg.limit(4096).error_handler(|err, _req| {
                        // create custom error response
                        error::InternalError::from_response(
                            err,
                            HttpResponse::Conflict().finish(),
                        )
                        .into()
                    })
                }))
                .route(web::post().to(index)),
        )
    })
    .bind("127.0.0.1:8088")?
    .run()
    .await
}

Form 表单

目前仅支持url-encoded形式。 可以将url-encoded的正文提取为特定类型。 此类型必须从serde crate中执行Deserialize trait。

FormConfig 允许配置提取过程。

use actix_web::{web, 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
async fn index(form: web::Form<FormData>) -> Result<String> {
    Ok(format!("Welcome {}!", form.username))
}

其他

Actix-web还提供了其他几种提取器:

  • Data - 如果您需要访问应用程序状态。
  • HttpRequest - HttpRequest本身是一个提取程序,如果需要访问request,它会返回self。
  • String - 您可以将request(请求)的payload转换为字符串。文档strings中提供了示例
  • bytes::Bytes - 您可以将request(请求)的payload转换为Bytes(字节)。文档strings中提供了示例
  • Payload - 您可以访问request的payload。示例payloadexample

Application state extractor 应用状态提取器

可通过web::Data extractor(提取器)从处理程序访问应用程序状态; 但是,状态可以作为只读引用进行访问。 如果您需要对状态的可变访问,则必须实现它。

当心,actix将创建应用程序状态和handlers(处理程序)的多个副本,这对于每个线程都是唯一的。 如果您在多个线程中运行应用程序,actix将创建与应用程序state对象和handler对象的线程数相同的数量。

这是存储处理的请求数的处理程序的示例:

use actix_web::{web, Responder};
use std::cell::Cell;

#[derive(Clone)]
struct AppState {
    count: Cell<i32>,
}

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_rt::main]
async fn main() -> std::io::Result<()> {
    use actix_web::{App, HttpServer};

    let data = AppState {
        count: Cell::new(0),
    };

    HttpServer::new(move || {
        App::new()
            .data(data.clone())
            .route("/", web::to(show_count))
            .route("/add", web::to(add_one))
    })
    .bind("127.0.0.1:8088")?
    .run()
    .await
}

尽管此handler将起作用,但self.0会有所不同,具体取决于线程数和每个线程处理的请求数。 适当的implementation(实现)将使用ArcAtomicUsize

use actix_web::{web, Responder};
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;

#[derive(Clone)]
struct AppState {
    count: Arc<AtomicUsize>,
}

async fn show_count(data: web::Data<AppState>) -> impl Responder {
    format!("count: {}", data.count.load(Ordering::Relaxed))
}

async fn add_one(data: web::Data<AppState>) -> impl Responder {
    data.count.fetch_add(1, Ordering::Relaxed);

    format!("count: {}", data.count.load(Ordering::Relaxed))
}

#[actix_rt::main]
async fn main() -> std::io::Result<()> {
    use actix_web::{App, HttpServer};

    let data = AppState {
        count: Arc::new(AtomicUsize::new(0)),
    };

    HttpServer::new(move || {
        App::new()
            .data(data.clone())
            .route("/", web::to(show_count))
            .route("/add", web::to(add_one))
    })
    .bind("127.0.0.1:8088")?
    .run()
    .await
}

注意诸如MutexRwLock之类的同步原语。 actix-web框架异步处理请求。 通过阻止线程执行,所有并发请求处理过程都将被阻止。 如果需要从多个线程共享或更新某些状态,请考虑使用tokio synchronization primitives (同步原语)。

接下来: 错误处理