Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to read a request's body in an actix-web 1.0 middleware?

I want to read out the body in a middleware in actix-web 1.0. I'm using the closure-style middleware using wrap_fn.

My basic setup is this:

let mut server = HttpServer::new(move || {
    ActixApp::new()
        .wrap_fn(|req, srv| {
            srv.call(req).map(|res| {
                let req_ = res.request();
                let body = req_.magical_body_read_function();
                dbg!(body);
                res
            })
        })
});

I need that magical_body_read_function() which sadly doesn't exist.

I hacked together something that looks like it could work by reading the examples and using take_payload() but it didn't work, sadly:

let mut server = HttpServer::new(move || {
    ActixApp::new()
        .wrap_fn(|req, srv| {
            srv.call(req).map(|res| {
                let req_ = res.request();
                req_.take_payload()
                    .fold(BytesMut::new(), move |mut body, chunk| {
                        body.extend_from_slice(&chunk);
                        Ok::<_, PayloadError>(body)
                    })
                    .and_then(|bytes| {
                        info!("request body: {:?}", bytes);
                    });
                res
            })
        })
});

Gives me

error[E0599]: no method named `fold` found for type `actix_http::payload::Payload<()>` in the current scope    --> src/main.rs:209:26
    | 209 |                         .fold(BytesMut::new(), move |mut body, chunk| {
    |                          ^^^^
    |
    = note: the method `fold` exists but the following trait bounds were not satisfied:
            `&mut actix_http::payload::Payload<()> : std::iter::Iterator`

I then tried an approach using the full middleware:

pub struct Logging;

impl<S, B> Transform<S> for Logging
where
    S: Service<Request = ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
    S::Future: 'static,
    B: 'static,
{
    type Request = ServiceRequest;
    type Response = ServiceResponse<B>;
    type Error = Error;
    type InitError = ();
    type Transform = LoggingMiddleware<S>;
    type Future = FutureResult<Self::Transform, Self::InitError>;

    fn new_transform(&self, service: S) -> Self::Future {
        ok(LoggingMiddleware { service })
    }
}

pub struct LoggingMiddleware<S> {
    service: S,
}

impl<S, B> Service for LoggingMiddleware<S>
where
    S: Service<Request = ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
    S::Future: 'static,
    B: 'static,
{
    type Request = ServiceRequest;
    type Response = ServiceResponse<B>;
    type Error = Error;
    type Future = Box<dyn Future<Item = Self::Response, Error = Self::Error>>;

    fn poll_ready(&mut self) -> Poll<(), Self::Error> {
        self.service.poll_ready()
    }

    fn call(&mut self, req: ServiceRequest) -> Self::Future {
        Box::new(self.service.call(req).and_then(|res| {
            let req_ = res.request();
            req_.take_payload()
                .fold(BytesMut::new(), move |mut body, chunk| {
                    body.extend_from_slice(&chunk);
                    Ok::<_, PayloadError>(body)
                })
                .and_then(|bytes| {
                    info!("request body: {:?}", bytes);
                });
            Ok(res)
        }))
    }
}

which sadly also resulted in the very similar looking error:

error[E0599]: no method named `fold` found for type `actix_http::payload::Payload<()>` in the current scope
   --> src/main.rs:204:18
    |
204 |                 .fold(BytesMut::new(), move |mut body, chunk| {
    |                  ^^^^
    |
    = note: the method `fold` exists but the following trait bounds were not satisfied:
            `&mut actix_http::payload::Payload<()> : futures::stream::Stream`
            `&mut actix_http::payload::Payload<()> : std::iter::Iterator`
            `actix_http::payload::Payload<()> : futures::stream::Stream`
like image 883
svenstaro Avatar asked Mar 04 '23 11:03

svenstaro


1 Answers

With the help of the fine people in the actix-web Gitter channel, I came to this solution which I also made a PR for.

Full solution is:

pub struct Logging;

impl<S: 'static, B> Transform<S> for Logging
where
    S: Service<Request = ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
    S::Future: 'static,
    B: 'static,
{
    type Request = ServiceRequest;
    type Response = ServiceResponse<B>;
    type Error = Error;
    type InitError = ();
    type Transform = LoggingMiddleware<S>;
    type Future = FutureResult<Self::Transform, Self::InitError>;

    fn new_transform(&self, service: S) -> Self::Future {
        ok(LoggingMiddleware {
            service: Rc::new(RefCell::new(service)),
        })
    }
}

pub struct LoggingMiddleware<S> {
    // This is special: We need this to avoid lifetime issues.
    service: Rc<RefCell<S>>,
}

impl<S, B> Service for LoggingMiddleware<S>
where
    S: Service<Request = ServiceRequest, Response = ServiceResponse<B>, Error = Error>
        + 'static,
    S::Future: 'static,
    B: 'static,
{
    type Request = ServiceRequest;
    type Response = ServiceResponse<B>;
    type Error = Error;
    type Future = Box<dyn Future<Item = Self::Response, Error = Self::Error>>;

    fn poll_ready(&mut self) -> Poll<(), Self::Error> {
        self.service.poll_ready()
    }

    fn call(&mut self, mut req: ServiceRequest) -> Self::Future {
        let mut svc = self.service.clone();

        Box::new(
            req.take_payload()
                .fold(BytesMut::new(), move |mut body, chunk| {
                    body.extend_from_slice(&chunk);
                    Ok::<_, PayloadError>(body)
                })
                .map_err(|e| e.into())
                .and_then(move |bytes| {
                    println!("request body: {:?}", bytes);
                    svc.call(req).and_then(|res| Ok(res))
                }),
        )
    }
}
like image 187
svenstaro Avatar answered Mar 17 '23 10:03

svenstaro