1

我正在开发一个带有框架的应用程序,rust并且actix-web我在应用程序中插入了一些中间件实例。

我计划中间件会修改响应的正文并在 call() 方法中返回响应,但我找不到解决方案。我找不到可以从中获取文本ServiceResponse并对其进行修改的示例代码。

你们能帮我一些获取响应正文并修改它的示例代码吗?

以下是我使用的示例代码。我添加了一条评论以告知我想要SayHiMiddleware->call()的“sample.rs”

// 示例.rs

use actix_service::{Service, Transform};
use actix_web::{dev::ServiceRequest, dev::ServiceResponse, Error};
use futures::future::{ok, FutureResult};
use futures::{Future, Poll};

// There are two steps in middleware processing.
// 1. Middleware initialization, middleware factory gets called with
//    next service in chain as parameter.
// 2. Middleware's call method gets called with normal request.
pub struct SayHi;

// Middleware factory is `Transform` trait from actix-service crate
// `S` - type of the next service
// `B` - type of response's body
impl<S, B> Transform<S> for SayHi
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 = SayHiMiddleware<S>;
    type Future = FutureResult<Self::Transform, Self::InitError>;

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

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

impl<S, B> Service for SayHiMiddleware<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 {
        println!("Hi from start. You requested: {}", req.path());

        Box::new(self.service.call(req).and_then(|res| {
            // I want to get response body text and print the text.
            // But I couldnt get the text from ServiceResponse instance..... help me guys T.T
            // And is there way to combine with previous response body text and new text?????
            // example (res->body->text is "aaaaa")) and I want to append new text to the string ( "aaaaa" + "bbbbb" )
            println!("Hi from response");
            Ok(res)
        }))
    }
}

// main.rs

use actix_web::{web, App, HttpServer};
use actix_service::Service;
use futures::future::Future;

#[allow(dead_code)]
mod redirect;
#[allow(dead_code)]
mod read_request_body;
#[allow(dead_code)]
mod read_response_body;
#[allow(dead_code)]
mod simple;

fn main() -> std::io::Result<()> {
    std::env::set_var("RUST_LOG", "actix_web=debug");
    env_logger::init();

    HttpServer::new(|| {
        App::new()
            .wrap(redirect::CheckLogin)
            .wrap(read_request_body::Logging)
            .wrap(read_response_body::Logging)
            .wrap(simple::SayHi)
            .wrap_fn(|req, srv| {
                println!("Hi from start. You requested: {}", req.path());

                srv.call(req).map(|res| {
                    println!("Hi from response");
                    res
                })
            })
            .service(web::resource("/login").to(|| {
                "You are on /login. Go to src/redirect.rs to change this behavior."
            }))
            .service(
                web::resource("/").to(|| {
                    "Hello, middleware! Check the console where the server is run."
                }),
            )
    })
    .bind("127.0.0.1:8080")?
    .run()
}

谢谢...

4

1 回答 1

0

这就是我的工作。我认为可能有更好的方法,但例子很简单。

    /// Extracts a response body of type T from `input`
    /// Example:
    /// 
    /// let result: MyResponseBodyContract = TestContext::map_body(&mut resp).await;
    pub async fn map_body<T>(input: &mut ServiceResponse<Body>) -> T
        where T: DeserializeOwned
    {
        let mut body = input.take_body();
        let mut bytes = BytesMut::new();
        while let Some(item) = body.next().await {
            bytes.extend_from_slice(&item.unwrap());
        }

        let result: T = serde_json::from_slice(&bytes)
            .unwrap_or_else(|_| panic!("map_body failed during deserialization"));
        return result;
    }

我的好友@stephaneyfx 帮助我将其重构为更好的东西。

#[async_trait(?Send)]
pub trait ParseResponse {
    /// Extracts a response body of type T from `input`
    /// Example:
    /// 
    /// let result: MyContractType = resp.parse().await.unwrap();
    async fn parse<T: DeserializeOwned>(&mut self) -> Result<T, Box<dyn std::error::Error>>;
}

#[async_trait(?Send)]
impl ParseResponse for ServiceResponse<Body> {
    async fn parse<T>(&mut self) -> Result<T, Box<dyn std::error::Error>>
    where
        T: DeserializeOwned,
    {
        let bytes = self.take_body().try_fold(Vec::new(), |mut acc, chunk| async {
            acc.extend(chunk);
            Ok(acc)
        });
        Ok(serde_json::from_slice(&bytes.await?)?)
    }
}
于 2020-09-02T20:13:16.643 回答