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
19pub struct Route {
24 service: BoxedHttpServiceFactory,
25 guards: Rc<Vec<Box<dyn Guard>>>,
26 wrapped: bool,
27}
28
29impl Route {
30 #[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 #[doc(alias = "middleware")]
60 #[doc(alias = "use")] 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 #[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 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 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 #[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 #[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 .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}