lexa_framework/server/
mod.rs1mod error;
12pub mod settings;
13mod components {
14 #[cfg(feature = "cookies")]
15 pub(super) mod cookies;
16 #[cfg(feature = "cors")]
17 pub(super) mod cors;
18 #[cfg(feature = "database-postgres")]
19 pub(super) mod database_postgres;
20 #[cfg(feature = "encryption-argon2")]
21 pub(super) mod encryption_argon2;
22}
23
24use std::net;
25
26use console::style;
27
28pub use self::error::Error;
29use crate::{routing, state};
30
31pub const PORT_PLAINTEXT: u16 = 80;
36pub const PORT_ENCRYPT: u16 = 443;
37
38pub struct Server<UserState> {
43 pub state: crate::state::State<UserState>,
44
45 pub application_settings: crate::application::Settings,
46 pub(crate) settings: settings::Settings,
47 #[cfg(feature = "cors")]
48 pub(crate) cors_settings: Option<crate::http::cors::Settings>,
49
50 global_router: axum::Router<()>,
51 pub(crate) router: axum::Router<crate::state::State<UserState>>,
52 pub(crate) routes: crate::routing::RouteCollection<UserState>,
53 static_resources: Vec<settings::SettingsStaticResource>,
54}
55
56impl<UserState> Server<UserState>
61where
62 UserState: 'static,
63 UserState: crate::state::StateInterface,
64{
65 pub async fn new(
67 application_settings: crate::application::Settings,
68 ) -> Result<Self, Error> {
69 let state = crate::state::State {
70 #[cfg(feature = "cookies")]
71 cookie_key: None,
72 #[cfg(feature = "database-postgres")]
73 database_postgres: None,
74 user_state: None,
75 };
76
77 let settings = settings::Settings::fetch_or_default(
78 &application_settings.config_dir,
79 &application_settings.loader_extension,
80 );
81
82 let static_resources = settings
83 .static_resources
84 .iter()
85 .cloned()
86 .map(|mut sr| {
87 sr.dir_path = if sr.dir_path.is_relative() {
88 application_settings.root_dir.join(&sr.dir_path)
89 } else {
90 sr.dir_path.to_owned()
91 };
92 sr
93 })
94 .collect();
95
96 Ok(Self {
97 state,
98 #[cfg(feature = "cors")]
99 cors_settings: {
100 lexa_fs::load(
101 &application_settings.config_dir,
102 "cors",
103 application_settings.loader_extension,
104 )
105 .ok()
106 },
107 application_settings,
108 settings,
109 global_router: axum::Router::new(),
110 router: axum::Router::new(),
111 routes: routing::RouteCollection::new(),
112 static_resources,
113 })
114 }
115
116 pub fn layer<Layer>(mut self, layer: Layer) -> Self
117 where
118 Layer: Clone + Send + Sync + 'static,
119 Layer: tower_layer::Layer<axum::routing::Route>,
120 Layer::Service: tower_service::Service<
121 hyper::Request<hyper::Body>,
122 Error = std::convert::Infallible,
123 >,
124 Layer::Service: Clone + Send + 'static,
125 <Layer::Service as tower_service::Service<
126 hyper::Request<hyper::Body>,
127 >>::Response: axum::response::IntoResponse + 'static,
128 <Layer::Service as tower_service::Service<
129 hyper::Request<hyper::Body>,
130 >>::Future: Send + 'static,
131 {
132 self.router = self.router.layer(layer);
133 self
134 }
135
136 pub fn make_application<A>(mut self) -> Self
138 where
139 A: crate::Application<State = UserState>,
140 {
141 let routes = <A::Router as routing::RouterExt>::routes();
142 let routes_state =
143 <A::Router as routing::RouterExt>::routes_with_state(&self.state);
144
145 let mut scoped_router = axum::Router::<state::State<UserState>>::new();
146
147 for route in routes.all().chain(routes_state.all()) {
148 scoped_router =
149 scoped_router.route(&route.fullpath, route.action.to_owned());
150 }
151
152 scoped_router = A::register_extension(&self.state, scoped_router);
153 scoped_router = A::register_layer(&self.state, scoped_router);
154 scoped_router = A::register_middleware(&self.state, scoped_router);
155
156 self.router = self.router.merge(scoped_router);
157 self.routes.extend(routes);
158 self.routes.extend(routes_state);
159
160 A::register_service(self)
161 }
162
163 pub async fn run(mut self) -> Result<(), Error> {
173 self.display_all_routes();
174
175 self = self.define_static_resources();
176
177 #[cfg(feature = "cors")]
178 {
179 self = self.using_cors_layer();
180 }
181
182 #[cfg(feature = "cookies")]
183 {
184 self = self.using_cookie_layer().await?;
185 }
186
187 self.launch_server().await?;
188
189 Ok(())
190 }
191
192 pub fn with_user_state(mut self, data: UserState::UserData) -> Self {
194 self.state.user_state.replace(UserState::new(data));
195 self
196 }
197}
198
199impl<UserState> Server<UserState>
200where
201 UserState: 'static,
202 UserState: crate::state::StateInterface,
203{
204 fn define_static_resources(mut self) -> Self {
205 for static_resource in self.static_resources.iter() {
206 self.global_router = self.global_router.nest_service(
207 &static_resource.url_path,
208 tower_http::services::ServeDir::new(&static_resource.dir_path),
209 );
210 }
211
212 self
213 }
214
215 fn display_all_routes(&self) {
216 println!();
217 println!("Liste des routes du serveur web:");
218 for route in self.routes.all() {
219 let methods = route
220 .methods
221 .iter()
222 .map(|m| style(&m).yellow().to_string())
223 .collect::<Vec<String>>()
224 .join(" | ");
225
226 println!(
227 "\t[{}]: {} {}",
228 methods,
229 style(&route.fullpath).bright().green(),
230 if let Some(name) = route.name.as_deref() {
231 format!("as {}", style(name).bright().black())
232 } else {
233 String::new()
234 }
235 );
236 }
237 println!();
238
239 for static_resource in self.static_resources.iter() {
240 println!(
241 "\t[{}]: {} -> {}",
242 style("STATIC RESOURCE").magenta(),
243 style(&static_resource.url_path).bright().green(),
244 style(static_resource.dir_path.display()).bright().black(),
245 );
246 }
247 }
248
249 async fn launch_server(mut self) -> Result<(), Error> {
250 log::info!("Tentative de lancement du serveur web...");
251
252 self.global_router =
253 self.global_router.merge(self.router.with_state(self.state));
254
255 let protocol = if self.settings.tls.is_some() {
256 "https"
257 } else {
258 "http"
259 };
260 let host = self.settings.host;
261 let port = self.settings.port;
262
263 let server_socket_addr =
264 <_ as Into<net::SocketAddr>>::into((host, port));
265
266 let port_with_prefix = if port == PORT_PLAINTEXT || port == PORT_ENCRYPT
267 {
268 String::new()
269 } else {
270 format!(":{}", port)
271 };
272
273 let url = format!("{protocol}://{host}{port_with_prefix}");
274 type S = net::SocketAddr;
275 println!("URL: {url}");
276
277 if let Some(settings_tls) = self.settings.tls {
278 let tls_config =
279 axum_server::tls_rustls::RustlsConfig::from_pem_file(
280 &settings_tls.cert,
281 &settings_tls.key,
282 )
283 .await?;
284
285 let http_config =
286 axum_server::HttpConfig::new().http2_only(true).build();
287
288 axum_server::bind_rustls(server_socket_addr, tls_config)
289 .http_config(http_config)
290 .serve(
291 self.global_router
292 .into_make_service_with_connect_info::<S>(),
293 )
294 .await?;
295 } else {
296 axum::Server::bind(&server_socket_addr)
297 .serve(
298 self.global_router
299 .into_make_service_with_connect_info::<S>(),
300 )
301 .await?;
302 }
303
304 Ok(())
305 }
306}