Actix_web 4 , get HttpRequest body in FromRequest Trait implementation

Hi there!
My goal is to create an Actix Web Extractor similar to built-in Form Extractor but deserializing with serde_qs crate instead.

My problem is how to read the HttpRequest body from the extractor.

In the web documentation says that the Payload Extractor is used for building other extractors, in the Doc for the Payload there is a link to an example but is not working

See here for example of usage as an extractor.

Any help is welcome!

I did something similar recently. It looked like this:

pub async fn route_handler(
    state: web::Data<AppState>,
    payload: web::Payload,
) -> Result<impl Responder> {
    let body = accumulate_payload(payload).await?;
    let x: StructX = serde_json::from_slice(&body).expect("JSON was not well-formatted");
    // ...
}

pub(crate) async fn accumulate_payload(mut payload: web::Payload) -> Result<web::BytesMut> {
    const MAX_PAYLOAD: usize = 256_000;
    let mut body = web::BytesMut::new();
    while let Some(chunk) = payload.next().await {
        let chunk = chunk?;
        if (body.len() + chunk.len()) > MAX_PAYLOAD {
            return Err(error::ErrorBadRequest("Request overflow"));
        }
        body.extend_from_slice(&chunk);
    }
    Ok(body)
}

Sorry for my late response, originally wanted to create an extractor and pass it as a parameter in the handler function, but I read the implementation of FromRequest for String and it's about 300 lines of code. So I did it following your recommendation
Final code:

async fn route_handler(body: String, ...) -> Result<impl Responder, AppResponseError> {
    let req_signin = form_parameters::<ReqSignIn>(&body, &req).await.unwrap();
    ...
}
/// Extract and deserialize typed data `T` from a request body. The inner type `T` must implement the
/// [`DeserializeOwned`] trait.
/// 
/// Returns error if:
/// - content type is not `application/x-www-form-urlencoded`
/// - fail to deseralize to `T` 
pub async fn form_parameters<T: DeserializeOwned + 'static>(body: &str, req: &HttpRequest) -> Result<T, AppError> {
    
    if req.content_type().to_lowercase() != "application/x-www-form-urlencoded" {
        return Err(AppError::BadRequest(UrlencodedError::ContentType.to_string()));
    }
        
    let qs_config = serde_qs::Config::new(2, false);
    match qs_config.deserialize_str::<T>(body) {
        Ok(params) => Ok(params),
        Err(err) =>  return Err(AppError::BadRequest(err.to_string())),
    }
}

Thanks!

1 Like