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}