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