1use std::borrow::Cow;
2
3use actix_files::{Files, NamedFile};
4use actix_service::fn_service;
5use actix_web::dev::{HttpServiceFactory, ResourceDef, ServiceRequest, ServiceResponse};
6use tracing::trace;
7
8#[derive(Debug, Clone)]
27pub struct Spa {
28 index_file: Cow<'static, str>,
29 static_resources_mount: Cow<'static, str>,
30 static_resources_location: Cow<'static, str>,
31}
32
33impl Spa {
34 pub fn index_file(mut self, index_file: impl Into<Cow<'static, str>>) -> Self {
44 self.index_file = index_file.into();
45 self
46 }
47
48 pub fn static_resources_mount(
52 mut self,
53 static_resources_mount: impl Into<Cow<'static, str>>,
54 ) -> Self {
55 self.static_resources_mount = static_resources_mount.into();
56 self
57 }
58
59 pub fn static_resources_location(
64 mut self,
65 static_resources_location: impl Into<Cow<'static, str>>,
66 ) -> Self {
67 self.static_resources_location = static_resources_location.into();
68 self
69 }
70
71 pub fn finish(self) -> impl HttpServiceFactory {
73 let index_file = self.index_file.into_owned();
74 let static_resources_location = self.static_resources_location.into_owned();
75 let static_resources_mount = self.static_resources_mount.into_owned();
76
77 let files = {
78 let index_file = index_file.clone();
79 Files::new(&static_resources_mount, static_resources_location)
80 .index_file("extremely-unlikely-to-exist-!@$%^&*.txt")
83 .default_handler(move |req| serve_index(req, index_file.clone()))
84 };
85
86 SpaService { index_file, files }
87 }
88}
89
90#[derive(Debug)]
91struct SpaService {
92 index_file: String,
93 files: Files,
94}
95
96impl HttpServiceFactory for SpaService {
97 fn register(self, config: &mut actix_web::dev::AppService) {
98 self.files.register(config);
100
101 let rdef = ResourceDef::root_prefix("");
103 config.register_service(
104 rdef,
105 None,
106 fn_service(move |req| serve_index(req, self.index_file.clone())),
107 None,
108 );
109 }
110}
111
112async fn serve_index(
113 req: ServiceRequest,
114 index_file: String,
115) -> Result<ServiceResponse, actix_web::Error> {
116 trace!("serving default SPA page");
117 let (req, _) = req.into_parts();
118 let file = NamedFile::open_async(&index_file).await?;
119 let res = file.into_response(&req);
120 Ok(ServiceResponse::new(req, res))
121}
122
123impl Default for Spa {
124 fn default() -> Self {
125 Self {
126 index_file: Cow::Borrowed("./index.html"),
127 static_resources_mount: Cow::Borrowed("/"),
128 static_resources_location: Cow::Borrowed("./"),
129 }
130 }
131}