1

I'm writing a web application in Rust using Actix-Web v1.0.7. I'm trying to create a route handler that could access a path parameter and also parse the JSON body of the request. However, so far I've failed to do so.

I tried declaring a handler function that would accept actix_web::HttpRequest as a parameter. This gives me access to the path parameter using match_info method without any problems. I then tried parsing the JSON body by using actix_web::web::Json::from_request method (from actix_web::FromRequest trait implementation), which requires 2 arguments:

  1. &actix_web::HttpRequest
  2. &mut actix_web::dev::Payload

My problem is getting the actix_web::dev::Payload. I tried using the actix_web::HttpRequest::take_payload method (from actix_web::HttpMessage trait implementation) but the trait implementation declares a unit value as the Stream inside the payload, which means I'm stuck. I assume this is due to the asynchronous nature of the framework, and perhaps the request body has not yet been received when the handler is invoked. However, that's just an assumption.

#[derive(Deserialize)]
struct Thing {
    // ...
}

type JsonThing = Json<Thing>;

fn handle(req: HttpRequest) -> impl IntoFuture {
    let id = req.match_info().query("id").parse::<usize>().unwrap();
    let mut payload = req.take_payload();
    JsonThing::from_request(&req, &mut payload)
        .and_then(|thing| {
            // ... 
            HttpResponse::Ok().finish()
        })
}

fn main() {
    let address = String::from("127.0.0.1:8080");
    let scope = web::scope("/scope")
        .route("/{id:\\d+}/path", web::put().to_async(handle));
    HttpServer::new(|| App::new().service(scope))
        .bind(address)
        .unwrap()
        .run()
        .unwrap();
}

I know that the snippet above does not compile and contains other issues, however it conveys the general idea. I expect there is a way to get the value of ID parameter from the path and to also be able to parse the JSON body of the same request, but so far I didn't find one.

2 Answers2

0

Actix uses extractors for this. Define what you want extracted in the handler:

#[derive(Deserialize, Serialize)] // <-- Note the Serialize for the json echo response
struct Thing {
    //...
}

type JsonThing = Json<Thing>;

fn handle(thing: JsonThing) -> impl Reponse {
    let id = req.match_info().query("id").parse::<usize>().unwrap();

    // echo the body back
    HttpResponse::Ok().json(thing)
}

You could do the same with the id, using web::Query.

arve0
  • 3,424
  • 26
  • 33
0

You will want to use the web::Path extractor to get id from the path. Multiple extractors can be used in the same handler function. The example below will work with actix-web >= 3.0.

use actix_web::{error, web, App, HttpResponse, HttpServer, Responder};
use serde::Deserialize;

#[derive(Deserialize)]
struct Thing {
    name: String, // required string member.  This must exist in the request body's json.
    some_value: Option<String>, // optional member, can be missing from the request body.
    // ...
}

/// deserialize `Thing` from request's body and get `id` from path.
#[put("/{id:\\d+}/path")]
async fn handle(
  web::Path(id): web::Path<usize>, // web::Path extractor for 'id'
  web::Json(thing): web::Json<Thing> // web::Json extractor for json body.
) -> impl IntoFuture {
    // `web::Path(id)` uses Rust's pattern matching to "unwrap" the `id` value, so `id` is type `usize` here.
    // `web::Json(thing)` does the same "unwrapping", so `thing` is of type `Thing` here.

    // TODO: use 'id' and 'thing'
    HttpResponse::Ok().finish()
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    let address = String::from("127.0.0.1:8080");
    HttpServer::new(|| {
        App::new()
          .service(handle)
    })
    .bind(address)?
    .run()
    .await
}
Neopallium
  • 1,419
  • 9
  • 18