use std::{ future::{ready, Future, Ready}, marker::PhantomData, pin::Pin, task::{Context, Poll}, }; use actix_web::{ body::{BodySize, MessageBody}, dev::{self, Service, ServiceRequest, ServiceResponse, Transform}, web::{Bytes, BytesMut}, Error, }; pub struct Logging; impl Transform for Logging where S: Service, Error = Error>, B: MessageBody + 'static, { type Response = ServiceResponse>; type Error = Error; type InitError = (); type Transform = LoggingMiddleware; type Future = Ready>; fn new_transform(&self, service: S) -> Self::Future { ready(Ok(LoggingMiddleware { service })) } } pub struct LoggingMiddleware { service: S, } impl Service for LoggingMiddleware where S: Service, Error = Error>, B: MessageBody, { type Response = ServiceResponse>; type Error = Error; type Future = WrapperStream; dev::forward_ready!(service); fn call(&self, req: ServiceRequest) -> Self::Future { WrapperStream { fut: self.service.call(req), _t: PhantomData, } } } #[pin_project::pin_project] pub struct WrapperStream where B: MessageBody, S: Service, { #[pin] fut: S::Future, _t: PhantomData<(B,)>, } impl Future for WrapperStream where B: MessageBody, S: Service, Error = Error>, { type Output = Result>, Error>; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let res = futures_util::ready!(self.project().fut.poll(cx)); Poll::Ready(res.map(|res| { res.map_body(move |_, body| BodyLogger { body, body_accum: BytesMut::new(), }) })) } } #[pin_project::pin_project(PinnedDrop)] pub struct BodyLogger { #[pin] body: B, body_accum: BytesMut, } #[pin_project::pinned_drop] impl PinnedDrop for BodyLogger { fn drop(self: Pin<&mut Self>) { println!("response body: {:?}", self.body_accum); } } impl MessageBody for BodyLogger { type Error = B::Error; fn size(&self) -> BodySize { self.body.size() } fn poll_next( self: Pin<&mut Self>, cx: &mut Context<'_>, ) -> Poll>> { let this = self.project(); match this.body.poll_next(cx) { Poll::Ready(Some(Ok(chunk))) => { this.body_accum.extend_from_slice(&chunk); Poll::Ready(Some(Ok(chunk))) } Poll::Ready(Some(Err(e))) => Poll::Ready(Some(Err(e))), Poll::Ready(None) => Poll::Ready(None), Poll::Pending => Poll::Pending, } } }