Могу ли я получить основной текст ответа и изменить его из промежуточного программного обеспечения Actix-Web?

Я разрабатываю приложение с rust и actix-web фреймворком, и я вставил в приложение некоторый экземпляр промежуточного программного обеспечения.

Я планировал, что промежуточное ПО изменит текст ответа и вернет ответ в методе call (), но я не смог найти решения. Я не могу найти образец кода, который позволяет получать текст из ServiceResponse и изменять его.

Не могли бы вы помочь мне с образцом кода, который получает текст ответа и изменяет его?

Ниже приведен пример кода, который я использовал. Я добавил комментарий, чтобы сообщить, что мне нужно в SayHiMiddleware->call() "sample.rs"

// sample.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()
}

Спасибо...


person user1848258    schedule 19.09.2019    source источник


Ответы (1)


Это то, что у меня получилось. Я думаю, что может быть способ получше, но примеры легкие.

    /// 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?)?)
    }
}
person Chris McKenzie    schedule 02.09.2020