Extractors

Type-safe information extraction

Actix-web provides a facility for type-safe request information access called extractors (ie, impl FromRequest). By default, actix-web provides several extractor implementations.

Extractors Within Handler Functions

An extractor can be accessed in a few different ways.

Option 1 - passed as a parameter to a handler function:

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

Option 2 - accessed by calling extract() on the Extractor

fn extract(req: HttpRequest) -> impl Responder {
    let params = web::Path::<(String, String)>::extract(&req).unwrap();

    let info = web::Json::<MyInfo>::extract(&req)
        .wait()
        .expect("Err with reading json.");

    format!("{} {} {} {}", params.0, params.1, info.username, info.id)
}

Path

Path provides information that can be extracted from the Request’s path. You can deserialize any variable segment from the path.

For instance, for resource that registered for the /users/{userid}/{friend} path two segments could be deserialized, userid and friend. These segments could be extracted into a tuple, i.e. Path<(u32, String)> or any structure that implements the Deserialize trait from the serde crate.

use actix_web::{web, Result};

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

pub fn main() {
    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")
    .unwrap()
    .run()
    .unwrap();
}

It is also possible to extract path information to a specific type that implements the Deserialize trait from serde. Here is an equivalent example that uses serde instead of a tuple type.

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

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

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

pub fn main() {
    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")
    .unwrap()
    .run()
    .unwrap();
}

It is also possible to get or query the request for path parameters by name:

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))
}

pub fn main() {
    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")
    .unwrap()
    .run()
    .unwrap();
}

Query

The Query type provides extraction functionality for the request’s query parameters. Underneath it uses 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
fn index(info: web::Query<Info>) -> String {
    format!("Welcome {}!", info.username)
}

Json

Json allows to deserialize a request body into a struct. To extract typed information from a request’s body, the type T must implement the Deserialize trait from serde.

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

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

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

Some extractors provide a way to configure the extraction process. Json extractor JsonConfig type for configuration. To configure an extractor, pass it’s configuration object to the resource’s .data() method. In case of a Json extractor it returns a JsonConfig. You can configure the maximum size of the json payload as well as a custom error handler function.

The following example limits the size of the payload to 4kb and uses a custom error handler.

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
fn index(info: web::Json<Info>) -> impl Responder {
    format!("Welcome {}!", info.username)
}

pub fn main() {
    use actix_web::{App, HttpServer};

    HttpServer::new(|| {
        App::new().service(
            web::resource("/")
                .data(
                    // change json extractor configuration
                    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")
    .unwrap()
    .run()
    .unwrap();
}

Form

At the moment only url-encoded forms are supported. The url-encoded body could be extracted to a specific type. This type must implement the Deserialize trait from the serde crate.

FormConfig allows configuring the extraction process.

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

Multiple extractors

Actix-web provides extractor implementations for tuples (up to 10 elements) whose elements implement FromRequest.

For example we can use a path extractor and a query extractor at the same time.

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

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

fn index((path, query): (web::Path<(u32, String)>, web::Query<Info>)) -> String {
    format!(
        "Welcome {}, friend {}, userid {}!",
        query.username, path.1, path.0
    )
}

pub fn main() {
    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")
    .unwrap()
    .run()
    .unwrap();
}

Other

Actix-web also provides several other extractors:

  • Data - If you need access to an application state.
  • HttpRequest - HttpRequest itself is an extractor which returns self, in case you need access to the request.
  • String - You can convert a request’s payload to a String. Example is available in doc strings.
  • bytes::Bytes - You can convert a request’s payload into Bytes. Example is available in doc strings.
  • Payload - You can access a request’s payload. Example

Async Data Access

Application state is accessible from the handler with the web::Data extractor; however, state is accessible as a read-only reference. If you need mutable access to state, it must be implemented.

Beware, actix creates multiple copies of the application state and the handlers, unique for each thread. If you run your application in several threads, actix will create the same amount as number of threads of application state objects and handler objects.

Here is an example of a handler that stores the number of processed requests:

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

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

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

fn add_one(data: web::Data<AppState>) -> impl Responder {
    let count = data.count.get();
    data.count.set(count + 1);

    format!("count: {}", data.count.get())
}

fn main() {
    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")
    .unwrap()
    .run()
    .unwrap();
}

Although this handler will work, self.0 will be different depending on the number of threads and number of requests processed per thread. A proper implementation would use Arc and AtomicUsize.

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

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

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

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

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

pub fn main() {
    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")
    .unwrap()
    .run()
    .unwrap();
}

Be careful with synchronization primitives like Mutex or RwLock. The actix-web framework handles requests asynchronously. By blocking thread execution, all concurrent request handling processes would block. If you need to share or update some state from multiple threads, consider using the actix actor system.

Next up: Errors