代码之家  ›  专栏  ›  技术社区  ›  Patryk

如何在actix_web中间件中打印响应体?

  •  0
  • Patryk  · 技术社区  · 4 年前

    我想用以下代码编写一个非常简单的中间件 actix_web 框架,但到目前为止,它在各个方面都打败了我。

    我有一个这样的骨架:

    let result = actix_web::HttpServer::new(move || {
        actix_web::App::new()
            .wrap_fn(move |req, srv| {
                srv.call(req).map(move|res| {
                    println!("Got response");
                    // let s = res.unwrap().response().body();
                    // ???
                    res
                })
            })
    })
    .bind("0.0.0.0:8080")?
    .run()
    .await;
    

    我可以访问 ResponseBody 类型via res.unwrap().response().body() 但我不知道该怎么办。

    有什么想法吗?

    1 回复  |  直到 4 年前
        1
  •  1
  •   Chris Andrew    3 年前

    这是一个例子,说明我是如何做到这一点的 4.0.0-14 :

    use std::cell::RefCell;
    use std::pin::Pin;
    use std::rc::Rc;
    use std::collections::HashMap;
    use std::str;
    
    use erp_contrib::{actix_http, actix_web, futures, serde_json};
    
    use actix_web::dev::{Service,  ServiceRequest, ServiceResponse, Transform};
    use actix_web::{HttpMessage, body, http::StatusCode, error::Error ,HttpResponseBuilder};
    use actix_http::{h1::Payload, header};
    use actix_web::web::{BytesMut};
    
    use futures::future::{ok, Future, Ready};
    use futures::task::{Context, Poll};
    use futures::StreamExt;
    
    use crate::response::ErrorResponse;
    
    pub struct UnhandledErrorResponse;
    
    impl<S: 'static> Transform<S, ServiceRequest> for UnhandledErrorResponse
        where
            S: Service<ServiceRequest, Response = ServiceResponse, Error = Error>,
            S::Future: 'static,
    {
        type Response = ServiceResponse;
        type Error = Error;
        type Transform = UnhandledErrorResponseMiddleware<S>;
        type InitError = ();
        type Future = Ready<Result<Self::Transform, Self::InitError>>;
    
        fn new_transform(&self, service: S) -> Self::Future {
            ok(UnhandledErrorResponseMiddleware { service: Rc::new(RefCell::new(service)), })
        }
    }
    
    pub struct UnhandledErrorResponseMiddleware<S> {
        service: Rc<RefCell<S>>,
    }
    
    impl<S> Service<ServiceRequest> for UnhandledErrorResponseMiddleware<S>
        where
            S: Service<ServiceRequest, Response = ServiceResponse, Error = Error> + 'static,
            S::Future: 'static,
    {
        type Response = ServiceResponse;
        type Error = Error;
        type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>>>>;
    
        fn poll_ready(&self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
            self.service.poll_ready(cx)
        }
    
        fn call(&self, mut req: ServiceRequest) -> Self::Future {
            let svc = self.service.clone();
    
            Box::pin(async move {
    
                /* EXTRACT THE BODY OF REQUEST */
                let mut request_body = BytesMut::new();
                while let Some(chunk) = req.take_payload().next().await {
                    request_body.extend_from_slice(&chunk?);
                }
    
                let mut orig_payload = Payload::empty();
                orig_payload.unread_data(request_body.freeze());
                req.set_payload(actix_http::Payload::from(orig_payload));
    
                /* now process the response */
                let res: ServiceResponse = svc.call(req).await?;
    
                let content_type = match res.headers().get("content-type") {
                    None => { "unknown"}
                    Some(header) => {
                        match header.to_str() {
                            Ok(value) => {value}
                            Err(_) => { "unknown"}
                        }
                    }
                };
    
                return match res.response().error() {
                    None => {
                        Ok(res)
                    }
                    Some(error) => {
                        if content_type.to_uppercase().contains("APPLICATION/JSON") {
                            Ok(res)
                        } else {
    
                            let error = error.to_string();
                            let new_request = res.request().clone();
    
                            /* EXTRACT THE BODY OF RESPONSE */
                            let _body_data =
                                match str::from_utf8(&body::to_bytes(res.into_body()).await?){
                                Ok(str) => {
                                    str
                                }
                                Err(_) => {
                                    "Unknown"
                                }
                            };
    
                            let mut errors = HashMap::new();
                            errors.insert("general".to_string(), vec![error]);
    
                            let new_response = match ErrorResponse::new(&false, errors) {
                                Ok(response) => {
                                    HttpResponseBuilder::new(StatusCode::BAD_REQUEST)
                                        .insert_header((header::CONTENT_TYPE, "application/json"))
                                        .body(serde_json::to_string(&response).unwrap())
                                }
                                Err(_error) => {
                                    HttpResponseBuilder::new(StatusCode::BAD_REQUEST)
                                        .insert_header((header::CONTENT_TYPE, "application/json"))
                                        .body("An unknown error occurred.")
                                }
                            };
    
                            Ok(ServiceResponse::new(
                                new_request,
                                new_response
                            ))
                        }
                    }
                }
            })
        }
    }
    
    

    提取 请求正文 这很简单,与Actix示例的说明类似。然而,随着Beta 14版本的更新,直接从 AnyBody 随着 BoxedBody 幸运的是,我找到了一个实用函数 body::to_bytes ( use actix_web::body::to_bytes )这做得很好。当前的实现如下:

    pub async fn to_bytes<B: MessageBody>(body: B) -> Result<Bytes, B::Error> {
        let cap = match body.size() {
            BodySize::None | BodySize::Sized(0) => return Ok(Bytes::new()),
            BodySize::Sized(size) => size as usize,
            // good enough first guess for chunk size
            BodySize::Stream => 32_768,
        };
    
        let mut buf = BytesMut::with_capacity(cap);
    
        pin!(body);
    
        poll_fn(|cx| loop {
            let body = body.as_mut();
    
            match ready!(body.poll_next(cx)) {
                Some(Ok(bytes)) => buf.extend_from_slice(&*bytes),
                None => return Poll::Ready(Ok(())),
                Some(Err(err)) => return Poll::Ready(Err(err)),
            }
        })
        .await?;
    
        Ok(buf.freeze())
    }
    

    我认为以这种方式提取尸体应该没问题,因为尸体是通过以下方式从体液中提取出来的 to_bytes() .

    如果有人有更好的方法,请告诉我,但这有点痛苦,我最近才决定如何在Beta 13中切换到Beta 14。

    这个特殊的例子拦截错误,如果错误不是JSON格式,则将其重写为JSON格式。例如,如果在处理程序之外发生错误,例如在处理程序函数本身中解析JSON,则会出现这种情况 _data: web::Json<Request<'a, LoginRequest>> 而不是在处理器主体中。 提取请求体和响应体不是实现目标所必需的,这里只是为了说明。

    推荐文章