【问题标题】:How to read a request's body in an actix-web 1.0 middleware?如何在 actix-web 1.0 中间件中读取请求正文?
【发布时间】:2019-09-10 12:00:51
【问题描述】:

我想在 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`

【问题讨论】:

    标签: rust middleware actix-web


    【解决方案1】:

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

    【讨论】:

    • 我试过这个,但似乎身体被中间件消耗掉了。你知道是否可以只读取中间件中的主体,同时让它传递给服务?
    • 我相信这是不可能的,因为 actix-web 的设计方式。基本上,为了阅读身体,你必须让它进入你的身体。您可以尝试以某种方式将其放回请求中。
    • 你可以重建payload并将其添加到ServiceRequest中
    【解决方案2】:

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

    【讨论】:

      【解决方案3】:

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

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

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

      因此,我认为你不走运。

      【讨论】:

      • 没有骰子,遗憾的是:错误[E0599]:在当前范围内找不到类型actix_http::payload::Payload&lt;()&gt; 的名为concat2 的方法--> src/main.rs:209:26 | 209 | .concat2() | ^^^^^^^
      • 这个错误甚至没有告诉你在它下面导入一个特征?
      • 不,我已经知道这些,编译器在那里很有帮助。可悲的是,我认为take_payload() 产生的类型不是我所期望的Payload&lt;Stream&gt;
      • @Svenstaro 是的,我认为wrap_fn 会做得更多,但它所做的只是创建一个中间件,因为其中有效负载的最低公分母是(),所以你被困住了@ 987654334@。您需要明确定义您的服务:-(
      • Payload 实现 Stream, Payload&lt;S&gt; 仅当 S 实现 Stream 时才实现 Stream,在 OP 的情况下S()() 不是 Stream 的实现者,please see
      猜你喜欢
      • 1970-01-01
      • 2023-02-23
      • 2018-04-07
      • 2021-08-18
      • 2018-10-30
      • 1970-01-01
      • 2021-08-02
      • 2019-06-23
      • 1970-01-01
      相关资源
      最近更新 更多