如何在 actix-web 1.0 中间件中读取请求的主体?

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

我想在actix-web 1.0 的中间件中读出正文。我正在使用 wrap_fn.

的闭包式中间件

我的基本设置是这样的:

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

我需要那个 magical_body_read_function(),遗憾的是它不存在。

我通过阅读示例和使用 take_payload() 拼凑了一些看起来可行的东西,但不幸的是它没有用:

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

给我

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`

然后我尝试了一种使用完整中间件的方法:

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

遗憾的是,这也导致了看起来非常相似的错误:

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`

Payload 实现了 Stream<Item = Bytes, Error = _>,因此您没有理由不能使用与其他框架相同的技巧:

req_
    .take_payload().concat2()
    .and_then(|bytes| {
         info!("request body: {:?}", bytes);
    });

也就是说,如果您从 POST/PUT 请求中获得了正确的 Payload。由于您使用了 wrap_fn(),您已经有效地设置了一个中间件。那些 运行 跨所有请求,并且不允许您访问 Payload(部分原因是您只能接受一次)。

因此,我认为你运气不好。

在 actix-web Gitter 频道中的好人的帮助下,我找到了这个解决方案,我也为此做了 a PR

完整的解决方案是:

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

在 svenstaro 的解决方案的基础上,您可以执行类似以下操作以在克隆剥离的字节后重建请求。

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

                    let mut payload = actix_http::h1::Payload::empty();
                    payload.unread_data(bytes.into());
                    req.set_payload(payload.into());

                    svc.call(req).and_then(|res| Ok(res))
                }),
        )
    }