Skip to main content

actix_web/
route.rs

1use std::{mem, rc::Rc};
2
3use actix_http::{body::MessageBody, Method};
4use actix_service::{
5    apply,
6    boxed::{self, BoxService},
7    fn_service, Service, ServiceFactory, ServiceFactoryExt, Transform,
8};
9use futures_core::future::LocalBoxFuture;
10
11use crate::{
12    guard::{self, Guard},
13    handler::{handler_service, Handler},
14    middleware::Compat,
15    service::{BoxedHttpServiceFactory, ServiceRequest, ServiceResponse},
16    Error, FromRequest, HttpResponse, Responder,
17};
18
19/// A request handler with [guards](guard).
20///
21/// Route uses a builder-like pattern for configuration. If handler is not set, a `404 Not Found`
22/// handler is used.
23pub struct Route {
24    service: BoxedHttpServiceFactory,
25    guards: Rc<Vec<Box<dyn Guard>>>,
26    wrapped: bool,
27}
28
29impl Route {
30    /// Create new route which matches any request.
31    #[allow(clippy::new_without_default)]
32    pub fn new() -> Route {
33        Route {
34            service: boxed::factory(fn_service(|req: ServiceRequest| async {
35                Ok(req.into_response(HttpResponse::NotFound()))
36            })),
37            guards: Rc::new(Vec::new()),
38            wrapped: false,
39        }
40    }
41
42    /// Registers a route middleware.
43    ///
44    /// `mw` is a middleware component (type), that can modify the requests and responses handled by
45    /// this `Route`.
46    ///
47    /// This middleware wraps the currently configured route service. Call this method after
48    /// [`Route::to`] or [`Route::service`] so the middleware is applied to the final handler.
49    ///
50    /// # Examples
51    /// ```
52    /// # use actix_web::{web, HttpResponse, middleware};
53    /// web::get()
54    ///     .to(|| async { HttpResponse::Ok() })
55    ///     .wrap(middleware::Logger::default());
56    /// ```
57    ///
58    /// See [`App::wrap`](crate::App::wrap) for more details.
59    #[doc(alias = "middleware")]
60    #[doc(alias = "use")] // nodejs terminology
61    pub fn wrap<M, B>(self, mw: M) -> Route
62    where
63        M: Transform<
64                BoxService<ServiceRequest, ServiceResponse, Error>,
65                ServiceRequest,
66                Response = ServiceResponse<B>,
67                Error = Error,
68                InitError = (),
69            > + 'static,
70        B: MessageBody + 'static,
71    {
72        Route {
73            service: boxed::factory(apply(Compat::new(mw), self.service)),
74            guards: self.guards,
75            wrapped: true,
76        }
77    }
78
79    pub(crate) fn take_guards(&mut self) -> Vec<Box<dyn Guard>> {
80        mem::take(Rc::get_mut(&mut self.guards).unwrap())
81    }
82
83    #[cold]
84    #[inline(never)]
85    #[track_caller]
86    fn panic_after_wrap(replaced: &str, example: &str) -> ! {
87        panic!(
88            "Route middleware was already registered with `.wrap()`. \
89             Calling `.{replaced}()` now would replace the wrapped service and silently drop middleware. \
90             Call `.{replaced}()` before `.wrap()` (for example: `{example}`)."
91        );
92    }
93}
94
95impl ServiceFactory<ServiceRequest> for Route {
96    type Response = ServiceResponse;
97    type Error = Error;
98    type Config = ();
99    type Service = RouteService;
100    type InitError = ();
101    type Future = LocalBoxFuture<'static, Result<Self::Service, Self::InitError>>;
102
103    fn new_service(&self, _: ()) -> Self::Future {
104        let fut = self.service.new_service(());
105        let guards = Rc::clone(&self.guards);
106
107        Box::pin(async move {
108            let service = fut.await?;
109            Ok(RouteService { service, guards })
110        })
111    }
112}
113
114pub struct RouteService {
115    service: BoxService<ServiceRequest, ServiceResponse, Error>,
116    guards: Rc<Vec<Box<dyn Guard>>>,
117}
118
119impl RouteService {
120    // TODO(breaking): remove pass by ref mut
121    #[allow(clippy::needless_pass_by_ref_mut)]
122    pub fn check(&self, req: &mut ServiceRequest) -> bool {
123        let guard_ctx = req.guard_ctx();
124
125        for guard in self.guards.iter() {
126            if !guard.check(&guard_ctx) {
127                return false;
128            }
129        }
130        true
131    }
132}
133
134impl Service<ServiceRequest> for RouteService {
135    type Response = ServiceResponse;
136    type Error = Error;
137    type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;
138
139    actix_service::forward_ready!(service);
140
141    fn call(&self, req: ServiceRequest) -> Self::Future {
142        self.service.call(req)
143    }
144}
145
146impl Route {
147    /// Add method guard to the route.
148    ///
149    /// # Examples
150    /// ```
151    /// # use actix_web::*;
152    /// # fn main() {
153    /// App::new().service(web::resource("/path").route(
154    ///     web::get()
155    ///         .method(http::Method::CONNECT)
156    ///         .guard(guard::Header("content-type", "text/plain"))
157    ///         .to(|req: HttpRequest| HttpResponse::Ok()))
158    /// );
159    /// # }
160    /// ```
161    pub fn method(mut self, method: Method) -> Self {
162        Rc::get_mut(&mut self.guards)
163            .unwrap()
164            .push(Box::new(guard::Method(method)));
165        self
166    }
167
168    /// Add guard to the route.
169    ///
170    /// # Examples
171    /// ```
172    /// # use actix_web::*;
173    /// # fn main() {
174    /// App::new().service(web::resource("/path").route(
175    ///     web::route()
176    ///         .guard(guard::Get())
177    ///         .guard(guard::Header("content-type", "text/plain"))
178    ///         .to(|req: HttpRequest| HttpResponse::Ok()))
179    /// );
180    /// # }
181    /// ```
182    pub fn guard<F: Guard + 'static>(mut self, f: F) -> Self {
183        Rc::get_mut(&mut self.guards).unwrap().push(Box::new(f));
184        self
185    }
186
187    #[cfg(feature = "experimental-introspection")]
188    pub(crate) fn guards(&self) -> &Vec<Box<dyn Guard>> {
189        &self.guards
190    }
191
192    /// Set handler function, use request extractors for parameters.
193    ///
194    /// # Examples
195    /// ```
196    /// use actix_web::{web, http, App};
197    /// use serde::Deserialize;
198    ///
199    /// #[derive(Deserialize)]
200    /// struct Info {
201    ///     username: String,
202    /// }
203    ///
204    /// /// extract path info using serde
205    /// async fn index(info: web::Path<Info>) -> String {
206    ///     format!("Welcome {}!", info.username)
207    /// }
208    ///
209    /// let app = App::new().service(
210    ///     web::resource("/{username}/index.html") // <- define path parameters
211    ///         .route(web::get().to(index))        // <- register handler
212    /// );
213    /// ```
214    ///
215    /// It is possible to use multiple extractors for one handler function.
216    /// ```
217    /// # use std::collections::HashMap;
218    /// # use serde::Deserialize;
219    /// use actix_web::{web, App};
220    ///
221    /// #[derive(Deserialize)]
222    /// struct Info {
223    ///     username: String,
224    /// }
225    ///
226    /// /// extract path info using serde
227    /// async fn index(
228    ///     path: web::Path<Info>,
229    ///     query: web::Query<HashMap<String, String>>,
230    ///     body: web::Json<Info>
231    /// ) -> String {
232    ///     format!("Welcome {}!", path.username)
233    /// }
234    ///
235    /// let app = App::new().service(
236    ///     web::resource("/{username}/index.html") // <- define path parameters
237    ///         .route(web::get().to(index))
238    /// );
239    /// ```
240    ///
241    /// # Panics
242    /// Panics if called after [`Route::wrap`], since this would replace the wrapped service and
243    /// silently discard middleware.
244    #[track_caller]
245    pub fn to<F, Args>(mut self, handler: F) -> Self
246    where
247        F: Handler<Args>,
248        Args: FromRequest + 'static,
249        F::Output: Responder + 'static,
250    {
251        if self.wrapped {
252            Self::panic_after_wrap("to", "web::get().to(handler).wrap(mw)");
253        }
254
255        self.service = handler_service(handler);
256        self
257    }
258
259    /// Set raw service to be constructed and called as the request handler.
260    ///
261    /// # Examples
262    /// ```
263    /// # use std::convert::Infallible;
264    /// # use futures_util::future::LocalBoxFuture;
265    /// # use actix_web::{*, dev::*, http::header};
266    /// struct HelloWorld;
267    ///
268    /// impl Service<ServiceRequest> for HelloWorld {
269    ///     type Response = ServiceResponse;
270    ///     type Error = Infallible;
271    ///     type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;
272    ///
273    ///     dev::always_ready!();
274    ///
275    ///     fn call(&self, req: ServiceRequest) -> Self::Future {
276    ///         let (req, _) = req.into_parts();
277    ///
278    ///         let res = HttpResponse::Ok()
279    ///             .insert_header(header::ContentType::plaintext())
280    ///             .body("Hello world!");
281    ///
282    ///         Box::pin(async move { Ok(ServiceResponse::new(req, res)) })
283    ///     }
284    /// }
285    ///
286    /// App::new().route(
287    ///     "/",
288    ///     web::get().service(fn_factory(|| async { Ok(HelloWorld) })),
289    /// );
290    /// ```
291    ///
292    /// # Panics
293    /// Panics if called after [`Route::wrap`], since this would replace the wrapped service and
294    /// silently discard middleware.
295    #[track_caller]
296    pub fn service<S, E>(mut self, service_factory: S) -> Self
297    where
298        S: ServiceFactory<
299                ServiceRequest,
300                Response = ServiceResponse,
301                Error = E,
302                InitError = (),
303                Config = (),
304            > + 'static,
305        E: Into<Error> + 'static,
306    {
307        if self.wrapped {
308            Self::panic_after_wrap("service", "web::get().service(factory).wrap(mw)");
309        }
310
311        self.service = boxed::factory(service_factory.map_err(Into::into));
312        self
313    }
314}
315
316#[cfg(test)]
317mod tests {
318    use std::{convert::Infallible, time::Duration};
319
320    use actix_rt::time::sleep;
321    use bytes::Bytes;
322    use futures_core::future::LocalBoxFuture;
323    use serde::Serialize;
324
325    use crate::{
326        dev::{always_ready, fn_factory, fn_service, Service},
327        error,
328        http::{header, Method, StatusCode},
329        middleware::{DefaultHeaders, Logger},
330        service::{ServiceRequest, ServiceResponse},
331        test::{call_service, init_service, read_body, TestRequest},
332        web, App, HttpResponse,
333    };
334
335    #[derive(Serialize, PartialEq, Debug)]
336    struct MyObject {
337        name: String,
338    }
339
340    #[actix_rt::test]
341    async fn test_route() {
342        let srv =
343            init_service(
344                App::new()
345                    .service(
346                        web::resource("/test")
347                            .route(web::get().to(HttpResponse::Ok))
348                            .route(web::put().to(|| async {
349                                Err::<HttpResponse, _>(error::ErrorBadRequest("err"))
350                            }))
351                            .route(web::post().to(|| async {
352                                sleep(Duration::from_millis(100)).await;
353                                Ok::<_, Infallible>(HttpResponse::Created())
354                            }))
355                            .route(web::delete().to(|| async {
356                                sleep(Duration::from_millis(100)).await;
357                                Err::<HttpResponse, _>(error::ErrorBadRequest("err"))
358                            })),
359                    )
360                    .service(web::resource("/json").route(web::get().to(|| async {
361                        sleep(Duration::from_millis(25)).await;
362                        web::Json(MyObject {
363                            name: "test".to_string(),
364                        })
365                    }))),
366            )
367            .await;
368
369        let req = TestRequest::with_uri("/test")
370            .method(Method::GET)
371            .to_request();
372        let resp = call_service(&srv, req).await;
373        assert_eq!(resp.status(), StatusCode::OK);
374
375        let req = TestRequest::with_uri("/test")
376            .method(Method::POST)
377            .to_request();
378        let resp = call_service(&srv, req).await;
379        assert_eq!(resp.status(), StatusCode::CREATED);
380
381        let req = TestRequest::with_uri("/test")
382            .method(Method::PUT)
383            .to_request();
384        let resp = call_service(&srv, req).await;
385        assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
386
387        let req = TestRequest::with_uri("/test")
388            .method(Method::DELETE)
389            .to_request();
390        let resp = call_service(&srv, req).await;
391        assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
392
393        let req = TestRequest::with_uri("/test")
394            .method(Method::HEAD)
395            .to_request();
396        let resp = call_service(&srv, req).await;
397        assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED);
398
399        let req = TestRequest::with_uri("/json").to_request();
400        let resp = call_service(&srv, req).await;
401        assert_eq!(resp.status(), StatusCode::OK);
402
403        let body = read_body(resp).await;
404        assert_eq!(body, Bytes::from_static(b"{\"name\":\"test\"}"));
405    }
406
407    #[actix_rt::test]
408    async fn route_middleware() {
409        let srv = init_service(
410            App::new()
411                .route("/", web::get().to(HttpResponse::Ok).wrap(Logger::default()))
412                .service(
413                    web::resource("/test")
414                        .route(web::get().to(HttpResponse::Ok))
415                        .route(
416                            web::post()
417                                .to(HttpResponse::Created)
418                                .wrap(DefaultHeaders::new().add(("x-test", "x-posted"))),
419                        )
420                        .route(
421                            web::delete()
422                                .to(HttpResponse::Accepted)
423                                // logger changes body type, proving Compat is not needed
424                                .wrap(Logger::default()),
425                        ),
426                ),
427        )
428        .await;
429
430        let req = TestRequest::get().uri("/test").to_request();
431        let res = call_service(&srv, req).await;
432        assert_eq!(res.status(), StatusCode::OK);
433        assert!(!res.headers().contains_key("x-test"));
434
435        let req = TestRequest::post().uri("/test").to_request();
436        let res = call_service(&srv, req).await;
437        assert_eq!(res.status(), StatusCode::CREATED);
438        assert_eq!(res.headers().get("x-test").unwrap(), "x-posted");
439
440        let req = TestRequest::delete().uri("/test").to_request();
441        let res = call_service(&srv, req).await;
442        assert_eq!(res.status(), StatusCode::ACCEPTED);
443    }
444
445    #[actix_rt::test]
446    async fn test_service_handler() {
447        struct HelloWorld;
448
449        impl Service<ServiceRequest> for HelloWorld {
450            type Response = ServiceResponse;
451            type Error = crate::Error;
452            type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;
453
454            always_ready!();
455
456            fn call(&self, req: ServiceRequest) -> Self::Future {
457                let (req, _) = req.into_parts();
458
459                let res = HttpResponse::Ok()
460                    .insert_header(header::ContentType::plaintext())
461                    .body("Hello world!");
462
463                Box::pin(async move { Ok(ServiceResponse::new(req, res)) })
464            }
465        }
466
467        let srv = init_service(
468            App::new()
469                .route(
470                    "/hello",
471                    web::get().service(fn_factory(|| async { Ok(HelloWorld) })),
472                )
473                .route(
474                    "/bye",
475                    web::get().service(fn_factory(|| async {
476                        Ok::<_, ()>(fn_service(|req: ServiceRequest| async {
477                            let (req, _) = req.into_parts();
478
479                            let res = HttpResponse::Ok()
480                                .insert_header(header::ContentType::plaintext())
481                                .body("Goodbye, and thanks for all the fish!");
482
483                            Ok::<_, Infallible>(ServiceResponse::new(req, res))
484                        }))
485                    })),
486                ),
487        )
488        .await;
489
490        let req = TestRequest::get().uri("/hello").to_request();
491        let resp = call_service(&srv, req).await;
492        assert_eq!(resp.status(), StatusCode::OK);
493        let body = read_body(resp).await;
494        assert_eq!(body, Bytes::from_static(b"Hello world!"));
495
496        let req = TestRequest::get().uri("/bye").to_request();
497        let resp = call_service(&srv, req).await;
498        assert_eq!(resp.status(), StatusCode::OK);
499        let body = read_body(resp).await;
500        assert_eq!(
501            body,
502            Bytes::from_static(b"Goodbye, and thanks for all the fish!")
503        );
504    }
505
506    #[test]
507    #[should_panic(expected = "Route middleware was already registered with `.wrap()`")]
508    fn wrap_before_to_panics() {
509        web::get()
510            .wrap(DefaultHeaders::new().add(("x-test", "x-value")))
511            .to(HttpResponse::Ok);
512    }
513
514    #[test]
515    #[should_panic(expected = "Route middleware was already registered with `.wrap()`")]
516    fn wrap_before_service_panics() {
517        web::get()
518            .wrap(DefaultHeaders::new().add(("x-test", "x-value")))
519            .service(fn_factory(|| async {
520                Ok::<_, ()>(fn_service(|req: ServiceRequest| async {
521                    let (req, _) = req.into_parts();
522                    Ok::<_, Infallible>(ServiceResponse::new(req, HttpResponse::Ok().finish()))
523                }))
524            }));
525    }
526}