actix_chain/
factory.rs

1use std::rc::Rc;
2
3use actix_service::ServiceFactory;
4use actix_web::{
5    Error,
6    dev::{AppService, HttpServiceFactory, ResourceDef, ServiceRequest, ServiceResponse},
7    guard::Guard,
8};
9use futures_core::future::LocalBoxFuture;
10
11use crate::link::Link;
12
13use super::service::{ChainInner, ChainService};
14
15/// Actix-Web service chaining service.
16///
17/// The chain is constructed from a series of [`Link`](crate::Link)
18/// instances which encode when services should be run and when
19/// their responses should be reguarded in favor of running the next
20/// service.
21///
22/// `Chain` service must be registered with `App::service()` method.
23///
24/// # Examples
25///
26/// ```
27/// use actix_web::{App, HttpRequest, HttpResponse, Responder, web};
28/// use actix_chain::{Chain, Link};
29///
30/// async fn might_fail(req: HttpRequest) -> impl Responder {
31///     if !req.headers().contains_key("Required-Header") {
32///         return HttpResponse::NotFound().body("Request Failed");
33///     }
34///     HttpResponse::Ok().body("It worked!")
35/// }
36///
37/// async fn default() -> &'static str {
38///     "First link failed!"
39/// }
40///
41/// App::new().service(
42///     Chain::default()
43///         .link(Link::new(web::get().to(might_fail)))
44///         .link(Link::new(web::get().to(default)))
45/// );
46/// ```
47#[derive(Clone)]
48pub struct Chain {
49    mount_path: String,
50    links: Vec<Link>,
51    guards: Vec<Rc<dyn Guard>>,
52    body_buffer_size: usize,
53}
54
55impl Chain {
56    /// Creates new `Chain` instance.
57    ///
58    /// The first argument (`mount_path`) is the root URL at which the static files are served.
59    /// For example, `/assets` will serve files at `example.com/assets/...`.
60    pub fn new(mount_path: &str) -> Self {
61        Self {
62            mount_path: mount_path.to_owned(),
63            links: Vec::new(),
64            guards: Vec::new(),
65            body_buffer_size: 32 * 1024, // 32 kb default
66        }
67    }
68
69    /// Adds a routing guard.
70    ///
71    /// Use this to allow multiple chained services that respond to strictly different
72    /// properties of a request. Due to the way routing works, if a guard check returns true and the
73    /// request starts being handled by the file service, it will not be able to back-out and try
74    /// the next service, you will simply get a 404 (or 405) error response.
75    ///
76    /// # Examples
77    /// ```
78    /// use actix_web::{guard::Header, App};
79    /// use actix_chain::Chain;
80    ///
81    /// App::new().service(
82    ///     Chain::default()
83    ///         .guard(Header("Host", "example.com"))
84    /// );
85    /// ```
86    pub fn guard<G: Guard + 'static>(mut self, guards: G) -> Self {
87        self.guards.push(Rc::new(guards));
88        self
89    }
90
91    /// Add a new [`Link`] to the established chain.
92    #[inline]
93    pub fn link(mut self, link: Link) -> Self {
94        self.push_link(link);
95        self
96    }
97
98    /// Append a [`Link`] via mutable reference for dynamic assignment.
99    #[inline]
100    pub fn push_link(&mut self, link: Link) -> &mut Self {
101        self.links.push(link);
102        self
103    }
104}
105
106impl Default for Chain {
107    #[inline]
108    fn default() -> Self {
109        Self::new("")
110    }
111}
112
113impl HttpServiceFactory for Chain {
114    fn register(mut self, config: &mut AppService) {
115        let guards = if self.guards.is_empty() {
116            None
117        } else {
118            let guards = std::mem::take(&mut self.guards);
119            Some(
120                guards
121                    .into_iter()
122                    .map(|guard| -> Box<dyn Guard> { Box::new(guard) })
123                    .collect::<Vec<_>>(),
124            )
125        };
126
127        let rdef = if config.is_root() {
128            ResourceDef::root_prefix(&self.mount_path)
129        } else {
130            ResourceDef::prefix(&self.mount_path)
131        };
132
133        config.register_service(rdef, guards, self, None)
134    }
135}
136
137impl ServiceFactory<ServiceRequest> for Chain {
138    type Response = ServiceResponse;
139    type Error = Error;
140    type Config = ();
141    type Service = ChainService;
142    type InitError = ();
143    type Future = LocalBoxFuture<'static, Result<Self::Service, Self::InitError>>;
144
145    fn new_service(&self, _: ()) -> Self::Future {
146        let this = self.clone();
147        Box::pin(async move {
148            let mut links = vec![];
149            for link in this.links {
150                match link.into_inner().await {
151                    Ok(link) => links.push(link),
152                    Err(_) => return Err(()),
153                }
154            }
155            Ok(ChainService(Rc::new(ChainInner {
156                links,
157                body_buffer_size: this.body_buffer_size,
158            })))
159        })
160    }
161}