1use std::{net::SocketAddr, rc::Rc};
2
3use actix_service::{boxed, IntoServiceFactory, ServiceFactory, ServiceFactoryExt as _};
4
5use crate::{
6 data::Data,
7 dev::{Extensions, ResourceDef},
8 error::Error,
9 guard::Guard,
10 resource::Resource,
11 rmap::ResourceMap,
12 route::Route,
13 service::{
14 AppServiceFactory, BoxedHttpServiceFactory, HttpServiceFactory, ServiceFactoryWrapper,
15 ServiceRequest, ServiceResponse,
16 },
17};
18
19type Guards = Vec<Box<dyn Guard>>;
20
21pub struct AppService {
23 config: AppConfig,
24 root: bool,
25 default: Rc<BoxedHttpServiceFactory>,
26 #[allow(clippy::type_complexity)]
27 services: Vec<(
28 ResourceDef,
29 BoxedHttpServiceFactory,
30 Option<Guards>,
31 Option<Rc<ResourceMap>>,
32 )>,
33 #[cfg(feature = "experimental-introspection")]
34 pub current_prefix: String,
35 #[cfg(feature = "experimental-introspection")]
36 pub(crate) introspector:
37 std::rc::Rc<std::cell::RefCell<crate::introspection::IntrospectionCollector>>,
38 #[cfg(feature = "experimental-introspection")]
39 pub(crate) scope_id_stack: Vec<usize>,
40 #[cfg(feature = "experimental-introspection")]
41 pending_scope_id: Option<usize>,
42}
43
44impl AppService {
45 pub(crate) fn new(config: AppConfig, default: Rc<BoxedHttpServiceFactory>) -> Self {
47 AppService {
48 config,
49 default,
50 root: true,
51 services: Vec::new(),
52 #[cfg(feature = "experimental-introspection")]
53 current_prefix: "".to_string(),
54 #[cfg(feature = "experimental-introspection")]
55 introspector: std::rc::Rc::new(std::cell::RefCell::new(
56 crate::introspection::IntrospectionCollector::new(),
57 )),
58 #[cfg(feature = "experimental-introspection")]
59 scope_id_stack: Vec::new(),
60 #[cfg(feature = "experimental-introspection")]
61 pending_scope_id: None,
62 }
63 }
64
65 pub fn is_root(&self) -> bool {
67 self.root
68 }
69
70 #[allow(clippy::type_complexity)]
71 #[cfg(feature = "experimental-introspection")]
72 pub(crate) fn into_services(
73 self,
74 ) -> (
75 AppConfig,
76 Vec<(
77 ResourceDef,
78 BoxedHttpServiceFactory,
79 Option<Guards>,
80 Option<Rc<ResourceMap>>,
81 )>,
82 std::rc::Rc<std::cell::RefCell<crate::introspection::IntrospectionCollector>>,
83 ) {
84 (self.config, self.services, self.introspector)
85 }
86
87 #[allow(clippy::type_complexity)]
88 #[cfg(not(feature = "experimental-introspection"))]
89 pub(crate) fn into_services(
90 self,
91 ) -> (
92 AppConfig,
93 Vec<(
94 ResourceDef,
95 BoxedHttpServiceFactory,
96 Option<Guards>,
97 Option<Rc<ResourceMap>>,
98 )>,
99 ) {
100 (self.config, self.services)
101 }
102
103 pub(crate) fn clone_config(&self) -> Self {
106 AppService {
107 config: self.config.clone(),
108 default: Rc::clone(&self.default),
109 services: Vec::new(),
110 root: false,
111 #[cfg(feature = "experimental-introspection")]
112 current_prefix: self.current_prefix.clone(),
113 #[cfg(feature = "experimental-introspection")]
114 introspector: std::rc::Rc::clone(&self.introspector),
115 #[cfg(feature = "experimental-introspection")]
116 scope_id_stack: self.scope_id_stack.clone(),
117 #[cfg(feature = "experimental-introspection")]
118 pending_scope_id: None,
119 }
120 }
121
122 pub fn config(&self) -> &AppConfig {
124 &self.config
125 }
126
127 pub fn default_service(&self) -> Rc<BoxedHttpServiceFactory> {
129 Rc::clone(&self.default)
130 }
131
132 pub fn register_service<F, S>(
134 &mut self,
135 rdef: ResourceDef,
136 guards: Option<Vec<Box<dyn Guard>>>,
137 factory: F,
138 nested: Option<Rc<ResourceMap>>,
139 ) where
140 F: IntoServiceFactory<S, ServiceRequest>,
141 S: ServiceFactory<
142 ServiceRequest,
143 Response = ServiceResponse,
144 Error = Error,
145 Config = (),
146 InitError = (),
147 > + 'static,
148 {
149 #[cfg(feature = "experimental-introspection")]
150 {
151 use std::borrow::Borrow;
152
153 let guard_list: &[Box<dyn Guard>] = guards.borrow().as_ref().map_or(&[], |v| &v[..]);
155 let methods = guard_list
156 .iter()
157 .flat_map(|g| g.details().unwrap_or_default())
158 .flat_map(|d| {
159 if let crate::guard::GuardDetail::HttpMethods(v) = d {
160 v.into_iter()
161 .filter_map(|s| s.parse().ok())
162 .collect::<Vec<_>>()
163 } else {
164 Vec::new()
165 }
166 })
167 .collect::<Vec<_>>();
168 let guard_names = guard_list.iter().map(|g| g.name()).collect::<Vec<_>>();
169 let guard_details = crate::introspection::guard_reports_from_iter(guard_list.iter());
170
171 let is_resource = nested.is_none();
172 let full_paths = crate::introspection::expand_patterns(&self.current_prefix, &rdef);
173 let patterns = rdef
174 .pattern_iter()
175 .map(|pattern| pattern.to_string())
176 .collect::<Vec<_>>();
177 let resource_name = rdef.name().map(|name| name.to_string());
178 let is_prefix = rdef.is_prefix();
179 let scope_id = if nested.is_some() {
180 self.pending_scope_id.take()
181 } else {
182 None
183 };
184 let parent_scope_id = self.scope_id_stack.last().copied();
185
186 for full_path in full_paths {
187 let info = crate::introspection::RouteInfo::new(
188 full_path,
189 methods.clone(),
190 guard_names.clone(),
191 guard_details.clone(),
192 patterns.clone(),
193 resource_name.clone(),
194 );
195 self.introspector.borrow_mut().register_service(
196 info,
197 is_resource,
198 is_prefix,
199 scope_id,
200 parent_scope_id,
201 );
202 }
203 }
204
205 self.services
206 .push((rdef, boxed::factory(factory.into_factory()), guards, nested));
207 }
208
209 #[cfg(feature = "experimental-introspection")]
211 pub(crate) fn update_prefix(&mut self, prefix: &str) {
212 let next = ResourceDef::root_prefix(prefix);
213
214 if self.current_prefix.is_empty() {
215 self.current_prefix = next.pattern().unwrap_or("").to_string();
216 return;
217 }
218
219 let current = ResourceDef::root_prefix(&self.current_prefix);
220 let joined = current.join(&next);
221 self.current_prefix = joined.pattern().unwrap_or("").to_string();
222 }
223
224 #[cfg(feature = "experimental-introspection")]
225 pub(crate) fn prepare_scope_id(&mut self) -> usize {
226 let scope_id = self.introspector.borrow_mut().next_scope_id();
227 self.pending_scope_id = Some(scope_id);
228 scope_id
229 }
230}
231
232#[derive(Debug, Clone)]
234pub struct AppConfig {
235 secure: bool,
236 host: String,
237 addr: SocketAddr,
238}
239
240impl AppConfig {
241 pub(crate) fn new(secure: bool, host: String, addr: SocketAddr) -> Self {
242 AppConfig { secure, host, addr }
243 }
244
245 #[doc(hidden)]
247 pub fn __priv_test_new(secure: bool, host: String, addr: SocketAddr) -> Self {
248 AppConfig::new(secure, host, addr)
249 }
250
251 pub fn host(&self) -> &str {
259 &self.host
260 }
261
262 pub fn secure(&self) -> bool {
264 self.secure
265 }
266
267 pub fn local_addr(&self) -> SocketAddr {
269 self.addr
270 }
271
272 #[cfg(test)]
273 pub(crate) fn set_host(&mut self, host: &str) {
274 host.clone_into(&mut self.host);
275 }
276}
277
278impl Default for AppConfig {
279 fn default() -> Self {
290 AppConfig::new(
291 false,
292 "localhost:8080".to_owned(),
293 "127.0.0.1:8080".parse().unwrap(),
294 )
295 }
296}
297
298pub struct ServiceConfig {
319 pub(crate) services: Vec<Box<dyn AppServiceFactory>>,
320 pub(crate) external: Vec<ResourceDef>,
321 pub(crate) app_data: Extensions,
322 pub(crate) default: Option<Rc<BoxedHttpServiceFactory>>,
323}
324
325impl ServiceConfig {
326 pub(crate) fn new() -> Self {
327 Self {
328 services: Vec::new(),
329 external: Vec::new(),
330 app_data: Extensions::new(),
331 default: None,
332 }
333 }
334
335 #[deprecated(since = "4.0.0", note = "Use `.app_data(Data::new(val))` instead.")]
339 pub fn data<U: 'static>(&mut self, data: U) -> &mut Self {
340 self.app_data(Data::new(data));
341 self
342 }
343
344 pub fn app_data<U: 'static>(&mut self, ext: U) -> &mut Self {
348 self.app_data.insert(ext);
349 self
350 }
351
352 pub fn default_service<F, U>(&mut self, f: F) -> &mut Self
356 where
357 F: IntoServiceFactory<U, ServiceRequest>,
358 U: ServiceFactory<ServiceRequest, Config = (), Response = ServiceResponse, Error = Error>
359 + 'static,
360 U::InitError: std::fmt::Debug,
361 {
362 let svc = f
363 .into_factory()
364 .map_init_err(|err| log::error!("Can not construct default service: {:?}", err));
365
366 self.default = Some(Rc::new(boxed::factory(svc)));
367
368 self
369 }
370
371 pub fn configure<F>(&mut self, f: F) -> &mut Self
375 where
376 F: FnOnce(&mut ServiceConfig),
377 {
378 f(self);
379 self
380 }
381
382 pub fn route(&mut self, path: &str, mut route: Route) -> &mut Self {
386 self.service(
387 Resource::new(path)
388 .add_guards(route.take_guards())
389 .route(route),
390 )
391 }
392
393 pub fn service<F>(&mut self, factory: F) -> &mut Self
397 where
398 F: HttpServiceFactory + 'static,
399 {
400 self.services
401 .push(Box::new(ServiceFactoryWrapper::new(factory)));
402 self
403 }
404
405 pub fn external_resource<N, U>(&mut self, name: N, url: U) -> &mut Self
413 where
414 N: AsRef<str>,
415 U: AsRef<str>,
416 {
417 let mut rdef = ResourceDef::new(url.as_ref());
418 rdef.set_name(name.as_ref());
419 self.external.push(rdef);
420 self
421 }
422}
423
424#[cfg(test)]
425mod tests {
426 use actix_service::Service;
427 use bytes::Bytes;
428
429 use super::*;
430 use crate::{
431 http::{Method, StatusCode},
432 test::{assert_body_eq, call_service, init_service, read_body, TestRequest},
433 web, App, HttpRequest, HttpResponse,
434 };
435
436 #[allow(deprecated)]
438 #[actix_rt::test]
439 async fn test_data() {
440 let cfg = |cfg: &mut ServiceConfig| {
441 cfg.data(10usize);
442 cfg.app_data(15u8);
443 };
444
445 let srv = init_service(App::new().configure(cfg).service(web::resource("/").to(
446 |_: web::Data<usize>, req: HttpRequest| {
447 assert_eq!(*req.app_data::<u8>().unwrap(), 15u8);
448 HttpResponse::Ok()
449 },
450 )))
451 .await;
452 let req = TestRequest::default().to_request();
453 let resp = srv.call(req).await.unwrap();
454 assert_eq!(resp.status(), StatusCode::OK);
455 }
456
457 #[actix_rt::test]
458 async fn test_external_resource() {
459 let srv = init_service(
460 App::new()
461 .configure(|cfg| {
462 cfg.external_resource("youtube", "https://youtube.com/watch/{video_id}");
463 })
464 .route(
465 "/test",
466 web::get().to(|req: HttpRequest| {
467 HttpResponse::Ok()
468 .body(req.url_for("youtube", ["12345"]).unwrap().to_string())
469 }),
470 ),
471 )
472 .await;
473 let req = TestRequest::with_uri("/test").to_request();
474 let resp = call_service(&srv, req).await;
475 assert_eq!(resp.status(), StatusCode::OK);
476 let body = read_body(resp).await;
477 assert_eq!(body, Bytes::from_static(b"https://youtube.com/watch/12345"));
478 }
479
480 #[actix_rt::test]
481 async fn registers_default_service() {
482 let srv = init_service(
483 App::new()
484 .configure(|cfg| {
485 cfg.default_service(
486 web::get().to(|| HttpResponse::NotFound().body("four oh four")),
487 );
488 })
489 .service(web::scope("/scoped").configure(|cfg| {
490 cfg.default_service(
491 web::get().to(|| HttpResponse::NotFound().body("scoped four oh four")),
492 );
493 })),
494 )
495 .await;
496
497 let req = TestRequest::with_uri("/path/i/did/not-configure").to_request();
499 let resp = call_service(&srv, req).await;
500 assert_eq!(resp.status(), StatusCode::NOT_FOUND);
501 let body = read_body(resp).await;
502 assert_eq!(body, Bytes::from_static(b"four oh four"));
503
504 let req = TestRequest::with_uri("/scoped/path/i/did/not-configure").to_request();
506 let resp = call_service(&srv, req).await;
507 assert_eq!(resp.status(), StatusCode::NOT_FOUND);
508 let body = read_body(resp).await;
509 assert_eq!(body, Bytes::from_static(b"scoped four oh four"));
510 }
511
512 #[actix_rt::test]
513 async fn test_service() {
514 let srv = init_service(App::new().configure(|cfg| {
515 cfg.service(web::resource("/test").route(web::get().to(HttpResponse::Created)))
516 .route("/index.html", web::get().to(HttpResponse::Ok));
517 }))
518 .await;
519
520 let req = TestRequest::with_uri("/test")
521 .method(Method::GET)
522 .to_request();
523 let resp = call_service(&srv, req).await;
524 assert_eq!(resp.status(), StatusCode::CREATED);
525
526 let req = TestRequest::with_uri("/index.html")
527 .method(Method::GET)
528 .to_request();
529 let resp = call_service(&srv, req).await;
530 assert_eq!(resp.status(), StatusCode::OK);
531 }
532
533 #[actix_rt::test]
534 async fn nested_service_configure() {
535 fn cfg_root(cfg: &mut ServiceConfig) {
536 cfg.configure(cfg_sub);
537 }
538
539 fn cfg_sub(cfg: &mut ServiceConfig) {
540 cfg.route("/", web::get().to(|| async { "hello world" }));
541 }
542
543 let srv = init_service(App::new().configure(cfg_root)).await;
544
545 let req = TestRequest::with_uri("/").to_request();
546 let res = call_service(&srv, req).await;
547 assert_eq!(res.status(), StatusCode::OK);
548 assert_body_eq!(res, b"hello world");
549 }
550}