use std::future::Future; use std::marker::PhantomData; use std::pin::Pin; use std::task::{Context, Poll}; use actix_service::{Service, Transform}; use actix_web::body::{BodySize, MessageBody, ResponseBody}; use actix_web::{dev::ServiceRequest, dev::ServiceResponse, Error}; use bytes::{Bytes, BytesMut}; use futures::future::{ok, Ready}; pub struct Logging; impl Transform for Logging where S: Service, Error = Error>, B: MessageBody + 'static, { type Request = ServiceRequest; type Response = ServiceResponse>; type Error = Error; type InitError = (); type Transform = LoggingMiddleware; type Future = Ready>; fn new_transform(&self, service: S) -> Self::Future { ok(LoggingMiddleware { service }) } } pub struct LoggingMiddleware { service: S, } impl Service for LoggingMiddleware where S: Service, Error = Error>, B: MessageBody, { type Request = ServiceRequest; type Response = ServiceResponse>; type Error = Error; type Future = WrapperStream; fn poll_ready(&mut self, cx: &mut Context) -> Poll> { self.service.poll_ready(cx) } fn call(&mut 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::ready!(self.project().fut.poll(cx)); Poll::Ready(res.map(|res| { res.map_body(move |_, body| { ResponseBody::Body(BodyLogger { body, body_accum: BytesMut::new(), }) }) })) } } pub struct BodyLogger { body: ResponseBody, body_accum: BytesMut, } impl Drop for BodyLogger { fn drop(&mut self) { println!("response body: {:?}", self.body_accum); } } impl MessageBody for BodyLogger { fn size(&self) -> BodySize { self.body.size() } fn poll_next(&mut self, cx: &mut Context<'_>) -> Poll>> { match self.body.poll_next(cx) { Poll::Ready(Some(Ok(chunk))) => { self.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, } } }