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}
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    #[cfg(feature = "experimental-introspection")]
163    pub(crate) fn guards(&self) -> &Vec<Box<dyn Guard>> {
164        &self.guards
165    }
166
167    /// Set handler function, use request extractors for parameters.
168    ///
169    /// # Examples
170    /// ```
171    /// use actix_web::{web, http, App};
172    /// use serde::Deserialize;
173    ///
174    /// #[derive(Deserialize)]
175    /// struct Info {
176    ///     username: String,
177    /// }
178    ///
179    /// /// extract path info using serde
180    /// async fn index(info: web::Path<Info>) -> String {
181    ///     format!("Welcome {}!", info.username)
182    /// }
183    ///
184    /// let app = App::new().service(
185    ///     web::resource("/{username}/index.html") // <- define path parameters
186    ///         .route(web::get().to(index))        // <- register handler
187    /// );
188    /// ```
189    ///
190    /// It is possible to use multiple extractors for one handler function.
191    /// ```
192    /// # use std::collections::HashMap;
193    /// # use serde::Deserialize;
194    /// use actix_web::{web, App};
195    ///
196    /// #[derive(Deserialize)]
197    /// struct Info {
198    ///     username: String,
199    /// }
200    ///
201    /// /// extract path info using serde
202    /// async fn index(
203    ///     path: web::Path<Info>,
204    ///     query: web::Query<HashMap<String, String>>,
205    ///     body: web::Json<Info>
206    /// ) -> String {
207    ///     format!("Welcome {}!", path.username)
208    /// }
209    ///
210    /// let app = App::new().service(
211    ///     web::resource("/{username}/index.html") // <- define path parameters
212    ///         .route(web::get().to(index))
213    /// );
214    /// ```
215    pub fn to<F, Args>(mut self, handler: F) -> Self
216    where
217        F: Handler<Args>,
218        Args: FromRequest + 'static,
219        F::Output: Responder + 'static,
220    {
221        self.service = handler_service(handler);
222        self
223    }
224
225    /// Set raw service to be constructed and called as the request handler.
226    ///
227    /// # Examples
228    /// ```
229    /// # use std::convert::Infallible;
230    /// # use futures_util::future::LocalBoxFuture;
231    /// # use actix_web::{*, dev::*, http::header};
232    /// struct HelloWorld;
233    ///
234    /// impl Service<ServiceRequest> for HelloWorld {
235    ///     type Response = ServiceResponse;
236    ///     type Error = Infallible;
237    ///     type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;
238    ///
239    ///     dev::always_ready!();
240    ///
241    ///     fn call(&self, req: ServiceRequest) -> Self::Future {
242    ///         let (req, _) = req.into_parts();
243    ///
244    ///         let res = HttpResponse::Ok()
245    ///             .insert_header(header::ContentType::plaintext())
246    ///             .body("Hello world!");
247    ///
248    ///         Box::pin(async move { Ok(ServiceResponse::new(req, res)) })
249    ///     }
250    /// }
251    ///
252    /// App::new().route(
253    ///     "/",
254    ///     web::get().service(fn_factory(|| async { Ok(HelloWorld) })),
255    /// );
256    /// ```
257    pub fn service<S, E>(mut self, service_factory: S) -> Self
258    where
259        S: ServiceFactory<
260                ServiceRequest,
261                Response = ServiceResponse,
262                Error = E,
263                InitError = (),
264                Config = (),
265            > + 'static,
266        E: Into<Error> + 'static,
267    {
268        self.service = boxed::factory(service_factory.map_err(Into::into));
269        self
270    }
271}
272
273#[cfg(test)]
274mod tests {
275    use std::{convert::Infallible, time::Duration};
276
277    use actix_rt::time::sleep;
278    use bytes::Bytes;
279    use futures_core::future::LocalBoxFuture;
280    use serde::Serialize;
281
282    use crate::{
283        dev::{always_ready, fn_factory, fn_service, Service},
284        error,
285        http::{header, Method, StatusCode},
286        middleware::{DefaultHeaders, Logger},
287        service::{ServiceRequest, ServiceResponse},
288        test::{call_service, init_service, read_body, TestRequest},
289        web, App, HttpResponse,
290    };
291
292    #[derive(Serialize, PartialEq, Debug)]
293    struct MyObject {
294        name: String,
295    }
296
297    #[actix_rt::test]
298    async fn test_route() {
299        let srv =
300            init_service(
301                App::new()
302                    .service(
303                        web::resource("/test")
304                            .route(web::get().to(HttpResponse::Ok))
305                            .route(web::put().to(|| async {
306                                Err::<HttpResponse, _>(error::ErrorBadRequest("err"))
307                            }))
308                            .route(web::post().to(|| async {
309                                sleep(Duration::from_millis(100)).await;
310                                Ok::<_, Infallible>(HttpResponse::Created())
311                            }))
312                            .route(web::delete().to(|| async {
313                                sleep(Duration::from_millis(100)).await;
314                                Err::<HttpResponse, _>(error::ErrorBadRequest("err"))
315                            })),
316                    )
317                    .service(web::resource("/json").route(web::get().to(|| async {
318                        sleep(Duration::from_millis(25)).await;
319                        web::Json(MyObject {
320                            name: "test".to_string(),
321                        })
322                    }))),
323            )
324            .await;
325
326        let req = TestRequest::with_uri("/test")
327            .method(Method::GET)
328            .to_request();
329        let resp = call_service(&srv, req).await;
330        assert_eq!(resp.status(), StatusCode::OK);
331
332        let req = TestRequest::with_uri("/test")
333            .method(Method::POST)
334            .to_request();
335        let resp = call_service(&srv, req).await;
336        assert_eq!(resp.status(), StatusCode::CREATED);
337
338        let req = TestRequest::with_uri("/test")
339            .method(Method::PUT)
340            .to_request();
341        let resp = call_service(&srv, req).await;
342        assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
343
344        let req = TestRequest::with_uri("/test")
345            .method(Method::DELETE)
346            .to_request();
347        let resp = call_service(&srv, req).await;
348        assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
349
350        let req = TestRequest::with_uri("/test")
351            .method(Method::HEAD)
352            .to_request();
353        let resp = call_service(&srv, req).await;
354        assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED);
355
356        let req = TestRequest::with_uri("/json").to_request();
357        let resp = call_service(&srv, req).await;
358        assert_eq!(resp.status(), StatusCode::OK);
359
360        let body = read_body(resp).await;
361        assert_eq!(body, Bytes::from_static(b"{\"name\":\"test\"}"));
362    }
363
364    #[actix_rt::test]
365    async fn route_middleware() {
366        let srv = init_service(
367            App::new()
368                .route("/", web::get().to(HttpResponse::Ok).wrap(Logger::default()))
369                .service(
370                    web::resource("/test")
371                        .route(web::get().to(HttpResponse::Ok))
372                        .route(
373                            web::post()
374                                .to(HttpResponse::Created)
375                                .wrap(DefaultHeaders::new().add(("x-test", "x-posted"))),
376                        )
377                        .route(
378                            web::delete()
379                                .to(HttpResponse::Accepted)
380                                // logger changes body type, proving Compat is not needed
381                                .wrap(Logger::default()),
382                        ),
383                ),
384        )
385        .await;
386
387        let req = TestRequest::get().uri("/test").to_request();
388        let res = call_service(&srv, req).await;
389        assert_eq!(res.status(), StatusCode::OK);
390        assert!(!res.headers().contains_key("x-test"));
391
392        let req = TestRequest::post().uri("/test").to_request();
393        let res = call_service(&srv, req).await;
394        assert_eq!(res.status(), StatusCode::CREATED);
395        assert_eq!(res.headers().get("x-test").unwrap(), "x-posted");
396
397        let req = TestRequest::delete().uri("/test").to_request();
398        let res = call_service(&srv, req).await;
399        assert_eq!(res.status(), StatusCode::ACCEPTED);
400    }
401
402    #[actix_rt::test]
403    async fn test_service_handler() {
404        struct HelloWorld;
405
406        impl Service<ServiceRequest> for HelloWorld {
407            type Response = ServiceResponse;
408            type Error = crate::Error;
409            type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;
410
411            always_ready!();
412
413            fn call(&self, req: ServiceRequest) -> Self::Future {
414                let (req, _) = req.into_parts();
415
416                let res = HttpResponse::Ok()
417                    .insert_header(header::ContentType::plaintext())
418                    .body("Hello world!");
419
420                Box::pin(async move { Ok(ServiceResponse::new(req, res)) })
421            }
422        }
423
424        let srv = init_service(
425            App::new()
426                .route(
427                    "/hello",
428                    web::get().service(fn_factory(|| async { Ok(HelloWorld) })),
429                )
430                .route(
431                    "/bye",
432                    web::get().service(fn_factory(|| async {
433                        Ok::<_, ()>(fn_service(|req: ServiceRequest| async {
434                            let (req, _) = req.into_parts();
435
436                            let res = HttpResponse::Ok()
437                                .insert_header(header::ContentType::plaintext())
438                                .body("Goodbye, and thanks for all the fish!");
439
440                            Ok::<_, Infallible>(ServiceResponse::new(req, res))
441                        }))
442                    })),
443                ),
444        )
445        .await;
446
447        let req = TestRequest::get().uri("/hello").to_request();
448        let resp = call_service(&srv, req).await;
449        assert_eq!(resp.status(), StatusCode::OK);
450        let body = read_body(resp).await;
451        assert_eq!(body, Bytes::from_static(b"Hello world!"));
452
453        let req = TestRequest::get().uri("/bye").to_request();
454        let resp = call_service(&srv, req).await;
455        assert_eq!(resp.status(), StatusCode::OK);
456        let body = read_body(resp).await;
457        assert_eq!(
458            body,
459            Bytes::from_static(b"Goodbye, and thanks for all the fish!")
460        );
461    }
462}