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}
34
35impl AppService {
36 pub(crate) fn new(config: AppConfig, default: Rc<BoxedHttpServiceFactory>) -> Self {
38 AppService {
39 config,
40 default,
41 root: true,
42 services: Vec::new(),
43 }
44 }
45
46 pub fn is_root(&self) -> bool {
48 self.root
49 }
50
51 #[allow(clippy::type_complexity)]
52 pub(crate) fn into_services(
53 self,
54 ) -> (
55 AppConfig,
56 Vec<(
57 ResourceDef,
58 BoxedHttpServiceFactory,
59 Option<Guards>,
60 Option<Rc<ResourceMap>>,
61 )>,
62 ) {
63 (self.config, self.services)
64 }
65
66 pub(crate) fn clone_config(&self) -> Self {
69 AppService {
70 config: self.config.clone(),
71 default: Rc::clone(&self.default),
72 services: Vec::new(),
73 root: false,
74 }
75 }
76
77 pub fn config(&self) -> &AppConfig {
79 &self.config
80 }
81
82 pub fn default_service(&self) -> Rc<BoxedHttpServiceFactory> {
84 Rc::clone(&self.default)
85 }
86
87 pub fn register_service<F, S>(
89 &mut self,
90 rdef: ResourceDef,
91 guards: Option<Vec<Box<dyn Guard>>>,
92 factory: F,
93 nested: Option<Rc<ResourceMap>>,
94 ) where
95 F: IntoServiceFactory<S, ServiceRequest>,
96 S: ServiceFactory<
97 ServiceRequest,
98 Response = ServiceResponse,
99 Error = Error,
100 Config = (),
101 InitError = (),
102 > + 'static,
103 {
104 self.services
105 .push((rdef, boxed::factory(factory.into_factory()), guards, nested));
106 }
107}
108
109#[derive(Debug, Clone)]
111pub struct AppConfig {
112 secure: bool,
113 host: String,
114 addr: SocketAddr,
115}
116
117impl AppConfig {
118 pub(crate) fn new(secure: bool, host: String, addr: SocketAddr) -> Self {
119 AppConfig { secure, host, addr }
120 }
121
122 #[doc(hidden)]
124 pub fn __priv_test_new(secure: bool, host: String, addr: SocketAddr) -> Self {
125 AppConfig::new(secure, host, addr)
126 }
127
128 pub fn host(&self) -> &str {
136 &self.host
137 }
138
139 pub fn secure(&self) -> bool {
141 self.secure
142 }
143
144 pub fn local_addr(&self) -> SocketAddr {
146 self.addr
147 }
148
149 #[cfg(test)]
150 pub(crate) fn set_host(&mut self, host: &str) {
151 host.clone_into(&mut self.host);
152 }
153}
154
155impl Default for AppConfig {
156 fn default() -> Self {
167 AppConfig::new(
168 false,
169 "localhost:8080".to_owned(),
170 "127.0.0.1:8080".parse().unwrap(),
171 )
172 }
173}
174
175pub struct ServiceConfig {
196 pub(crate) services: Vec<Box<dyn AppServiceFactory>>,
197 pub(crate) external: Vec<ResourceDef>,
198 pub(crate) app_data: Extensions,
199 pub(crate) default: Option<Rc<BoxedHttpServiceFactory>>,
200}
201
202impl ServiceConfig {
203 pub(crate) fn new() -> Self {
204 Self {
205 services: Vec::new(),
206 external: Vec::new(),
207 app_data: Extensions::new(),
208 default: None,
209 }
210 }
211
212 #[deprecated(since = "4.0.0", note = "Use `.app_data(Data::new(val))` instead.")]
216 pub fn data<U: 'static>(&mut self, data: U) -> &mut Self {
217 self.app_data(Data::new(data));
218 self
219 }
220
221 pub fn app_data<U: 'static>(&mut self, ext: U) -> &mut Self {
225 self.app_data.insert(ext);
226 self
227 }
228
229 pub fn default_service<F, U>(&mut self, f: F) -> &mut Self
233 where
234 F: IntoServiceFactory<U, ServiceRequest>,
235 U: ServiceFactory<ServiceRequest, Config = (), Response = ServiceResponse, Error = Error>
236 + 'static,
237 U::InitError: std::fmt::Debug,
238 {
239 let svc = f
240 .into_factory()
241 .map_init_err(|err| log::error!("Can not construct default service: {:?}", err));
242
243 self.default = Some(Rc::new(boxed::factory(svc)));
244
245 self
246 }
247
248 pub fn configure<F>(&mut self, f: F) -> &mut Self
252 where
253 F: FnOnce(&mut ServiceConfig),
254 {
255 f(self);
256 self
257 }
258
259 pub fn route(&mut self, path: &str, mut route: Route) -> &mut Self {
263 self.service(
264 Resource::new(path)
265 .add_guards(route.take_guards())
266 .route(route),
267 )
268 }
269
270 pub fn service<F>(&mut self, factory: F) -> &mut Self
274 where
275 F: HttpServiceFactory + 'static,
276 {
277 self.services
278 .push(Box::new(ServiceFactoryWrapper::new(factory)));
279 self
280 }
281
282 pub fn external_resource<N, U>(&mut self, name: N, url: U) -> &mut Self
290 where
291 N: AsRef<str>,
292 U: AsRef<str>,
293 {
294 let mut rdef = ResourceDef::new(url.as_ref());
295 rdef.set_name(name.as_ref());
296 self.external.push(rdef);
297 self
298 }
299}
300
301#[cfg(test)]
302mod tests {
303 use actix_service::Service;
304 use bytes::Bytes;
305
306 use super::*;
307 use crate::{
308 http::{Method, StatusCode},
309 test::{assert_body_eq, call_service, init_service, read_body, TestRequest},
310 web, App, HttpRequest, HttpResponse,
311 };
312
313 #[allow(deprecated)]
315 #[actix_rt::test]
316 async fn test_data() {
317 let cfg = |cfg: &mut ServiceConfig| {
318 cfg.data(10usize);
319 cfg.app_data(15u8);
320 };
321
322 let srv = init_service(App::new().configure(cfg).service(web::resource("/").to(
323 |_: web::Data<usize>, req: HttpRequest| {
324 assert_eq!(*req.app_data::<u8>().unwrap(), 15u8);
325 HttpResponse::Ok()
326 },
327 )))
328 .await;
329 let req = TestRequest::default().to_request();
330 let resp = srv.call(req).await.unwrap();
331 assert_eq!(resp.status(), StatusCode::OK);
332 }
333
334 #[actix_rt::test]
335 async fn test_external_resource() {
336 let srv = init_service(
337 App::new()
338 .configure(|cfg| {
339 cfg.external_resource("youtube", "https://youtube.com/watch/{video_id}");
340 })
341 .route(
342 "/test",
343 web::get().to(|req: HttpRequest| {
344 HttpResponse::Ok()
345 .body(req.url_for("youtube", ["12345"]).unwrap().to_string())
346 }),
347 ),
348 )
349 .await;
350 let req = TestRequest::with_uri("/test").to_request();
351 let resp = call_service(&srv, req).await;
352 assert_eq!(resp.status(), StatusCode::OK);
353 let body = read_body(resp).await;
354 assert_eq!(body, Bytes::from_static(b"https://youtube.com/watch/12345"));
355 }
356
357 #[actix_rt::test]
358 async fn registers_default_service() {
359 let srv = init_service(
360 App::new()
361 .configure(|cfg| {
362 cfg.default_service(
363 web::get().to(|| HttpResponse::NotFound().body("four oh four")),
364 );
365 })
366 .service(web::scope("/scoped").configure(|cfg| {
367 cfg.default_service(
368 web::get().to(|| HttpResponse::NotFound().body("scoped four oh four")),
369 );
370 })),
371 )
372 .await;
373
374 let req = TestRequest::with_uri("/path/i/did/not-configure").to_request();
376 let resp = call_service(&srv, req).await;
377 assert_eq!(resp.status(), StatusCode::NOT_FOUND);
378 let body = read_body(resp).await;
379 assert_eq!(body, Bytes::from_static(b"four oh four"));
380
381 let req = TestRequest::with_uri("/scoped/path/i/did/not-configure").to_request();
383 let resp = call_service(&srv, req).await;
384 assert_eq!(resp.status(), StatusCode::NOT_FOUND);
385 let body = read_body(resp).await;
386 assert_eq!(body, Bytes::from_static(b"scoped four oh four"));
387 }
388
389 #[actix_rt::test]
390 async fn test_service() {
391 let srv = init_service(App::new().configure(|cfg| {
392 cfg.service(web::resource("/test").route(web::get().to(HttpResponse::Created)))
393 .route("/index.html", web::get().to(HttpResponse::Ok));
394 }))
395 .await;
396
397 let req = TestRequest::with_uri("/test")
398 .method(Method::GET)
399 .to_request();
400 let resp = call_service(&srv, req).await;
401 assert_eq!(resp.status(), StatusCode::CREATED);
402
403 let req = TestRequest::with_uri("/index.html")
404 .method(Method::GET)
405 .to_request();
406 let resp = call_service(&srv, req).await;
407 assert_eq!(resp.status(), StatusCode::OK);
408 }
409
410 #[actix_rt::test]
411 async fn nested_service_configure() {
412 fn cfg_root(cfg: &mut ServiceConfig) {
413 cfg.configure(cfg_sub);
414 }
415
416 fn cfg_sub(cfg: &mut ServiceConfig) {
417 cfg.route("/", web::get().to(|| async { "hello world" }));
418 }
419
420 let srv = init_service(App::new().configure(cfg_root)).await;
421
422 let req = TestRequest::with_uri("/").to_request();
423 let res = call_service(&srv, req).await;
424 assert_eq!(res.status(), StatusCode::OK);
425 assert_body_eq!(res, b"hello world");
426 }
427}