Search code examples
rustactix-web

How in actix middleware access response body and modify headers?


I need to insert special header X-CRC32 on each response. This header should contain CRC32 checksum of a response body. I think middleware is the best way to do this.

The problem is - I can't access body bytes.

async fn add_crc32_header_middleware(
    req: ServiceRequest,
    next: Next<impl MessageBody + 'static>,
) -> Result<ServiceResponse<impl MessageBody>, Error> {
    let mut res = next.call(req).await?;
    let body = res.response().body();
    let body_bytes = match body.try_into_bytes() {
        Ok(bytes) => bytes,
        _ => return Err(actix_web::error::ErrorImATeapot(""))
    };
    let crc = format!("{:08x}", CRC32.checksum(&body_bytes));
    let hdrs = res.headers_mut();
    hdrs.insert( HeaderName::from_static("x-crc32"), HeaderValue::from_str(&crc).unwrap());
    Ok(res)
}

Here is error:

error[E0507]: cannot move out of `*body` which is behind a shared reference
   --> src/main.rs:33:28
    |
33  |     let body_bytes = match body.try_into_bytes() {
    |                            ^^^^^----------------
    |                            |    |
    |                            |    `*body` moved due to this method call
    |                            move occurs because `*body` has type `impl MessageBody + 'static`, which does not implement the `Copy` trait
    |
note: `try_into_bytes` takes ownership of the receiver `self`, which moves `*body`

code: https://github.com/olekhov/actix-middleware-test/blob/main/src/main.rs

I think I miss some important concepts. The message body is BoxBody, behind a shared reference, and I can not figure out how to copy it or access inner type.


Solution

  • If you read the documentation on ServiceResponse you'll find 3 methods that can give you access to an owned body: map_body, into_body and into_parts, of which into_body is obviously not what you want since it drops all other data. You can use either of the other two methods for example with into_parts:

    async fn add_crc32_header_middleware(
        req: ServiceRequest,
        next: Next<impl MessageBody + 'static>,
    ) -> Result<ServiceResponse<impl MessageBody>, Error> {
        let res = next.call(req).await?;
        let (req, res) = res.into_parts();
        let (mut res, body) = res.into_parts();
    
        let body_bytes = match body.try_into_bytes() {
            Ok(bytes) => bytes,
            _ => return Err(actix_web::error::ErrorImATeapot("teapot")),
        };
        let crc = format!("{:08x}", CRC32.checksum(&body_bytes));
        let hdrs = res.headers_mut();
        hdrs.insert(
            HeaderName::from_static("x-crc32"),
            HeaderValue::from_str(&crc).unwrap(),
        );
        let res = res.set_body(body_bytes);
        Ok(ServiceResponse::new(req, res))
    }