actix_web/middleware/
compat.rs

1//! For middleware documentation, see [`Compat`].
2
3use std::{
4    future::Future,
5    pin::Pin,
6    task::{Context, Poll},
7};
8
9use futures_core::{future::LocalBoxFuture, ready};
10use pin_project_lite::pin_project;
11
12use crate::{
13    body::{BoxBody, MessageBody},
14    dev::{Service, Transform},
15    error::Error,
16    service::ServiceResponse,
17};
18
19/// Middleware for enabling any middleware to be used in [`Resource::wrap`](crate::Resource::wrap),
20/// and [`Condition`](super::Condition).
21///
22/// # Examples
23/// ```
24/// use actix_web::middleware::{Logger, Compat};
25/// use actix_web::{App, web};
26///
27/// let logger = Logger::default();
28///
29/// // this would not compile because of incompatible body types
30/// // let app = App::new()
31/// //     .service(web::scope("scoped").wrap(logger));
32///
33/// // by using this middleware we can use the logger on a scope
34/// let app = App::new()
35///     .service(web::scope("scoped").wrap(Compat::new(logger)));
36/// ```
37pub struct Compat<T> {
38    transform: T,
39}
40
41impl<T> Compat<T> {
42    /// Wrap a middleware to give it broader compatibility.
43    pub fn new(middleware: T) -> Self {
44        Self {
45            transform: middleware,
46        }
47    }
48}
49
50impl<S, T, Req> Transform<S, Req> for Compat<T>
51where
52    S: Service<Req>,
53    T: Transform<S, Req>,
54    T::Future: 'static,
55    T::Response: MapServiceResponseBody,
56    T::Error: Into<Error>,
57{
58    type Response = ServiceResponse<BoxBody>;
59    type Error = Error;
60    type Transform = CompatMiddleware<T::Transform>;
61    type InitError = T::InitError;
62    type Future = LocalBoxFuture<'static, Result<Self::Transform, Self::InitError>>;
63
64    fn new_transform(&self, service: S) -> Self::Future {
65        let fut = self.transform.new_transform(service);
66        Box::pin(async move {
67            let service = fut.await?;
68            Ok(CompatMiddleware { service })
69        })
70    }
71}
72
73pub struct CompatMiddleware<S> {
74    service: S,
75}
76
77impl<S, Req> Service<Req> for CompatMiddleware<S>
78where
79    S: Service<Req>,
80    S::Response: MapServiceResponseBody,
81    S::Error: Into<Error>,
82{
83    type Response = ServiceResponse<BoxBody>;
84    type Error = Error;
85    type Future = CompatMiddlewareFuture<S::Future>;
86
87    actix_service::forward_ready!(service);
88
89    fn call(&self, req: Req) -> Self::Future {
90        let fut = self.service.call(req);
91        CompatMiddlewareFuture { fut }
92    }
93}
94
95pin_project! {
96    pub struct CompatMiddlewareFuture<Fut> {
97        #[pin]
98        fut: Fut,
99    }
100}
101
102impl<Fut, T, E> Future for CompatMiddlewareFuture<Fut>
103where
104    Fut: Future<Output = Result<T, E>>,
105    T: MapServiceResponseBody,
106    E: Into<Error>,
107{
108    type Output = Result<ServiceResponse<BoxBody>, Error>;
109
110    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
111        let res = match ready!(self.project().fut.poll(cx)) {
112            Ok(res) => res,
113            Err(err) => return Poll::Ready(Err(err.into())),
114        };
115
116        Poll::Ready(Ok(res.map_body()))
117    }
118}
119
120/// Convert `ServiceResponse`'s `ResponseBody<B>` generic type to `ResponseBody<Body>`.
121pub trait MapServiceResponseBody {
122    fn map_body(self) -> ServiceResponse<BoxBody>;
123}
124
125impl<B> MapServiceResponseBody for ServiceResponse<B>
126where
127    B: MessageBody + 'static,
128{
129    #[inline]
130    fn map_body(self) -> ServiceResponse<BoxBody> {
131        self.map_into_boxed_body()
132    }
133}
134
135#[cfg(test)]
136mod tests {
137    // easier to code when cookies feature is disabled
138    #![allow(unused_imports)]
139
140    use actix_service::IntoService;
141
142    use super::*;
143    use crate::{
144        dev::ServiceRequest,
145        http::StatusCode,
146        middleware::{self, Condition, Identity, Logger},
147        test::{self, call_service, init_service, TestRequest},
148        web, App, HttpResponse,
149    };
150
151    #[actix_rt::test]
152    #[cfg(all(feature = "cookies", feature = "__compress"))]
153    async fn test_scope_middleware() {
154        use crate::middleware::Compress;
155
156        let logger = Logger::default();
157        let compress = Compress::default();
158
159        let srv = init_service(
160            App::new().service(
161                web::scope("app")
162                    .wrap(logger)
163                    .wrap(Compat::new(compress))
164                    .service(web::resource("/test").route(web::get().to(HttpResponse::Ok))),
165            ),
166        )
167        .await;
168
169        let req = TestRequest::with_uri("/app/test").to_request();
170        let resp = call_service(&srv, req).await;
171        assert_eq!(resp.status(), StatusCode::OK);
172    }
173
174    #[actix_rt::test]
175    #[cfg(all(feature = "cookies", feature = "__compress"))]
176    async fn test_resource_scope_middleware() {
177        use crate::middleware::Compress;
178
179        let logger = Logger::default();
180        let compress = Compress::default();
181
182        let srv = init_service(
183            App::new().service(
184                web::resource("app/test")
185                    .wrap(Compat::new(logger))
186                    .wrap(Compat::new(compress))
187                    .route(web::get().to(HttpResponse::Ok)),
188            ),
189        )
190        .await;
191
192        let req = TestRequest::with_uri("/app/test").to_request();
193        let resp = call_service(&srv, req).await;
194        assert_eq!(resp.status(), StatusCode::OK);
195    }
196
197    #[actix_rt::test]
198    async fn test_condition_scope_middleware() {
199        let srv = |req: ServiceRequest| {
200            Box::pin(
201                async move { Ok(req.into_response(HttpResponse::InternalServerError().finish())) },
202            )
203        };
204
205        let logger = Logger::default();
206
207        let mw = Condition::new(true, Compat::new(logger))
208            .new_transform(srv.into_service())
209            .await
210            .unwrap();
211        let resp = call_service(&mw, TestRequest::default().to_srv_request()).await;
212        assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);
213    }
214
215    #[actix_rt::test]
216    async fn compat_noop_is_noop() {
217        let srv = test::ok_service();
218
219        let mw = Compat::new(Identity)
220            .new_transform(srv.into_service())
221            .await
222            .unwrap();
223
224        let resp = call_service(&mw, TestRequest::default().to_srv_request()).await;
225        assert_eq!(resp.status(), StatusCode::OK);
226    }
227}