Skip to main content

actix_web/middleware/
from_fn.rs

1use std::{future::Future, marker::PhantomData, rc::Rc};
2
3use actix_service::boxed::{self, BoxFuture, RcService};
4#[expect(deprecated, reason = "preserve the public ready future type")]
5use actix_utils::future::{ready, Ready};
6use futures_core::future::LocalBoxFuture;
7
8use crate::{
9    body::MessageBody,
10    dev::{forward_ready, Service, ServiceRequest, ServiceResponse, Transform},
11    Error, FromRequest,
12};
13
14/// Wraps an async function to be used as a middleware.
15///
16/// # Examples
17///
18/// The wrapped function should have the following form:
19///
20/// ```
21/// # use actix_web::{
22/// #     App, Error,
23/// #     body::MessageBody,
24/// #     dev::{ServiceRequest, ServiceResponse, Service as _},
25/// # };
26/// use actix_web::middleware::{self, Next};
27///
28/// async fn my_mw(
29///     req: ServiceRequest,
30///     next: Next<impl MessageBody>,
31/// ) -> Result<ServiceResponse<impl MessageBody>, Error> {
32///     // pre-processing
33///     next.call(req).await
34///     // post-processing
35/// }
36/// # App::new().wrap(middleware::from_fn(my_mw));
37/// ```
38///
39/// Then use in an app builder like this:
40///
41/// ```
42/// use actix_web::{
43///     App, Error,
44///     dev::{ServiceRequest, ServiceResponse, Service as _},
45/// };
46/// use actix_web::middleware::from_fn;
47/// # use actix_web::middleware::Next;
48/// # async fn my_mw<B>(req: ServiceRequest, next: Next<B>) -> Result<ServiceResponse<B>, Error> {
49/// #     next.call(req).await
50/// # }
51///
52/// App::new()
53///     .wrap(from_fn(my_mw))
54/// # ;
55/// ```
56///
57/// It is also possible to write a middleware that automatically uses extractors, similar to request
58/// handlers, by declaring them as the first parameters. As usual, **take care with extractors that
59/// consume the body stream**, since handlers will no longer be able to read it again without
60/// putting the body "back" into the request object within your middleware.
61///
62/// ```
63/// # use std::collections::HashMap;
64/// # use actix_web::{
65/// #     App, Error,
66/// #     body::MessageBody,
67/// #     dev::{ServiceRequest, ServiceResponse},
68/// #     http::header::{Accept, Date},
69/// #     web::{Header, Query},
70/// # };
71/// use actix_web::middleware::Next;
72///
73/// async fn my_extracting_mw(
74///     accept: Header<Accept>,
75///     query: Query<HashMap<String, String>>,
76///     req: ServiceRequest,
77///     next: Next<impl MessageBody>,
78/// ) -> Result<ServiceResponse<impl MessageBody>, Error> {
79///     // pre-processing
80///     next.call(req).await
81///     // post-processing
82/// }
83/// # App::new().wrap(actix_web::middleware::from_fn(my_extracting_mw));
84pub fn from_fn<F, Es>(mw_fn: F) -> MiddlewareFn<F, Es> {
85    MiddlewareFn {
86        mw_fn: Rc::new(mw_fn),
87        _phantom: PhantomData,
88    }
89}
90
91/// Middleware transform for [`from_fn`].
92#[allow(missing_debug_implementations)]
93pub struct MiddlewareFn<F, Es> {
94    mw_fn: Rc<F>,
95    _phantom: PhantomData<Es>,
96}
97
98#[expect(deprecated, reason = "preserve the public ready future type")]
99impl<S, F, Fut, B, B2> Transform<S, ServiceRequest> for MiddlewareFn<F, ()>
100where
101    S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error> + 'static,
102    F: Fn(ServiceRequest, Next<B>) -> Fut + 'static,
103    Fut: Future<Output = Result<ServiceResponse<B2>, Error>>,
104    B2: MessageBody,
105{
106    type Response = ServiceResponse<B2>;
107    type Error = Error;
108    type Transform = MiddlewareFnService<F, B, ()>;
109    type InitError = ();
110    type Future = Ready<Result<Self::Transform, Self::InitError>>;
111
112    fn new_transform(&self, service: S) -> Self::Future {
113        ready(Ok(MiddlewareFnService {
114            service: boxed::rc_service(service),
115            mw_fn: Rc::clone(&self.mw_fn),
116            _phantom: PhantomData,
117        }))
118    }
119}
120
121/// Middleware service for [`from_fn`].
122#[allow(missing_debug_implementations)]
123pub struct MiddlewareFnService<F, B, Es> {
124    service: RcService<ServiceRequest, ServiceResponse<B>, Error>,
125    mw_fn: Rc<F>,
126    _phantom: PhantomData<(B, Es)>,
127}
128
129impl<F, Fut, B, B2> Service<ServiceRequest> for MiddlewareFnService<F, B, ()>
130where
131    F: Fn(ServiceRequest, Next<B>) -> Fut,
132    Fut: Future<Output = Result<ServiceResponse<B2>, Error>>,
133    B2: MessageBody,
134{
135    type Response = ServiceResponse<B2>;
136    type Error = Error;
137    type Future = Fut;
138
139    forward_ready!(service);
140
141    fn call(&self, req: ServiceRequest) -> Self::Future {
142        (self.mw_fn)(
143            req,
144            Next::<B> {
145                service: Rc::clone(&self.service),
146            },
147        )
148    }
149}
150
151macro_rules! impl_middleware_fn_service {
152    ($($ext_type:ident),*) => {
153        #[expect(deprecated, reason = "preserve the public ready future type")]
154        impl<S, F, Fut, B, B2, $($ext_type),*> Transform<S, ServiceRequest> for MiddlewareFn<F, ($($ext_type),*,)>
155        where
156            S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error> + 'static,
157            F: Fn($($ext_type),*, ServiceRequest, Next<B>) -> Fut + 'static,
158            $($ext_type: FromRequest + 'static,)*
159            Fut: Future<Output = Result<ServiceResponse<B2>, Error>> + 'static,
160            B: MessageBody + 'static,
161            B2: MessageBody + 'static,
162        {
163            type Response = ServiceResponse<B2>;
164            type Error = Error;
165            type Transform = MiddlewareFnService<F, B, ($($ext_type,)*)>;
166            type InitError = ();
167            type Future = Ready<Result<Self::Transform, Self::InitError>>;
168
169            fn new_transform(&self, service: S) -> Self::Future {
170                ready(Ok(MiddlewareFnService {
171                    service: boxed::rc_service(service),
172                    mw_fn: Rc::clone(&self.mw_fn),
173                    _phantom: PhantomData,
174                }))
175            }
176        }
177
178        impl<F, $($ext_type),*, Fut, B: 'static, B2> Service<ServiceRequest>
179            for MiddlewareFnService<F, B, ($($ext_type),*,)>
180        where
181            F: Fn(
182                $($ext_type),*,
183                ServiceRequest,
184                Next<B>
185            ) -> Fut + 'static,
186            $($ext_type: FromRequest + 'static,)*
187            Fut: Future<Output = Result<ServiceResponse<B2>, Error>> + 'static,
188            B2: MessageBody + 'static,
189        {
190            type Response = ServiceResponse<B2>;
191            type Error = Error;
192            type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;
193
194            forward_ready!(service);
195
196            #[allow(nonstandard_style)]
197            fn call(&self, mut req: ServiceRequest) -> Self::Future {
198                let mw_fn = Rc::clone(&self.mw_fn);
199                let service = Rc::clone(&self.service);
200
201                Box::pin(async move {
202                    let ($($ext_type,)*) = req.extract::<($($ext_type,)*)>().await?;
203
204                    (mw_fn)($($ext_type),*, req, Next::<B> { service }).await
205                })
206            }
207        }
208    };
209}
210
211impl_middleware_fn_service!(E1);
212impl_middleware_fn_service!(E1, E2);
213impl_middleware_fn_service!(E1, E2, E3);
214impl_middleware_fn_service!(E1, E2, E3, E4);
215impl_middleware_fn_service!(E1, E2, E3, E4, E5);
216impl_middleware_fn_service!(E1, E2, E3, E4, E5, E6);
217impl_middleware_fn_service!(E1, E2, E3, E4, E5, E6, E7);
218impl_middleware_fn_service!(E1, E2, E3, E4, E5, E6, E7, E8);
219impl_middleware_fn_service!(E1, E2, E3, E4, E5, E6, E7, E8, E9);
220
221/// Wraps the "next" service in the middleware chain.
222#[allow(missing_debug_implementations)]
223pub struct Next<B> {
224    service: RcService<ServiceRequest, ServiceResponse<B>, Error>,
225}
226
227impl<B> Next<B> {
228    /// Equivalent to `Service::call(self, req)`.
229    pub fn call(&self, req: ServiceRequest) -> <Self as Service<ServiceRequest>>::Future {
230        Service::call(self, req)
231    }
232}
233
234impl<B> Service<ServiceRequest> for Next<B> {
235    type Response = ServiceResponse<B>;
236    type Error = Error;
237    type Future = BoxFuture<Result<Self::Response, Self::Error>>;
238
239    forward_ready!(service);
240
241    fn call(&self, req: ServiceRequest) -> Self::Future {
242        self.service.call(req)
243    }
244}
245
246#[cfg(test)]
247mod tests {
248    use super::*;
249    use crate::{
250        http::header::{self, HeaderValue},
251        middleware::{Compat, Logger},
252        test, web, App, HttpResponse,
253    };
254
255    async fn noop<B>(req: ServiceRequest, next: Next<B>) -> Result<ServiceResponse<B>, Error> {
256        next.call(req).await
257    }
258
259    async fn add_res_header<B>(
260        req: ServiceRequest,
261        next: Next<B>,
262    ) -> Result<ServiceResponse<B>, Error> {
263        let mut res = next.call(req).await?;
264        res.headers_mut()
265            .insert(header::WARNING, HeaderValue::from_static("42"));
266        Ok(res)
267    }
268
269    async fn mutate_body_type(
270        req: ServiceRequest,
271        next: Next<impl MessageBody + 'static>,
272    ) -> Result<ServiceResponse<impl MessageBody>, Error> {
273        let res = next.call(req).await?;
274        Ok(res.map_into_left_body::<()>())
275    }
276
277    struct MyMw(bool);
278
279    impl MyMw {
280        async fn mw_cb(
281            &self,
282            req: ServiceRequest,
283            next: Next<impl MessageBody + 'static>,
284        ) -> Result<ServiceResponse<impl MessageBody>, Error> {
285            let mut res = match self.0 {
286                true => req.into_response("short-circuited").map_into_right_body(),
287                false => next.call(req).await?.map_into_left_body(),
288            };
289            res.headers_mut()
290                .insert(header::WARNING, HeaderValue::from_static("42"));
291            Ok(res)
292        }
293
294        pub fn into_middleware<S, B>(
295            self,
296        ) -> impl Transform<
297            S,
298            ServiceRequest,
299            Response = ServiceResponse<impl MessageBody>,
300            Error = Error,
301            InitError = (),
302        >
303        where
304            S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error> + 'static,
305            B: MessageBody + 'static,
306        {
307            let this = Rc::new(self);
308            from_fn(move |req, next| {
309                let this = Rc::clone(&this);
310                async move { Self::mw_cb(&this, req, next).await }
311            })
312        }
313    }
314
315    #[actix_rt::test]
316    async fn compat_compat() {
317        let _ = App::new().wrap(Compat::new(from_fn(noop)));
318        let _ = App::new().wrap(Compat::new(from_fn(mutate_body_type)));
319    }
320
321    #[actix_rt::test]
322    async fn permits_different_in_and_out_body_types() {
323        let app = test::init_service(
324            App::new()
325                .wrap(from_fn(mutate_body_type))
326                .wrap(from_fn(add_res_header))
327                .wrap(Logger::default())
328                .wrap(from_fn(noop))
329                .default_service(web::to(HttpResponse::NotFound)),
330        )
331        .await;
332
333        let req = test::TestRequest::default().to_request();
334        let res = test::call_service(&app, req).await;
335        assert!(res.headers().contains_key(header::WARNING));
336    }
337
338    #[actix_rt::test]
339    async fn closure_capture_and_return_from_fn() {
340        let app = test::init_service(
341            App::new()
342                .wrap(Logger::default())
343                .wrap(MyMw(true).into_middleware())
344                .wrap(Logger::default()),
345        )
346        .await;
347
348        let req = test::TestRequest::default().to_request();
349        let res = test::call_service(&app, req).await;
350        assert!(res.headers().contains_key(header::WARNING));
351    }
352}