lexa_framework/server/
mod.rs

1// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
2// ┃ Copyright: (c) 2023, Mike 'PhiSyX' S. (https://github.com/PhiSyX)         ┃
3// ┃ SPDX-License-Identifier: MPL-2.0                                          ┃
4// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
5// ┃                                                                           ┃
6// ┃  This Source Code Form is subject to the terms of the Mozilla Public      ┃
7// ┃  License, v. 2.0. If a copy of the MPL was not distributed with this      ┃
8// ┃  file, You can obtain one at https://mozilla.org/MPL/2.0/.                ┃
9// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
10
11mod 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
31// -------- //
32// Constant //
33// -------- //
34
35pub const PORT_PLAINTEXT: u16 = 80;
36pub const PORT_ENCRYPT: u16 = 443;
37
38// --------- //
39// Structure //
40// --------- //
41
42pub 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
56// -------------- //
57// Implémentation //
58// -------------- //
59
60impl<UserState> Server<UserState>
61where
62	UserState: 'static,
63	UserState: crate::state::StateInterface,
64{
65	/// Instancie un serveur.
66	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	/// Crée une application qui est scopée pour le serveur.
137	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	/// Démarre un serveur WEB avec ses composants. En se basant sur les
164	/// configurations de l'utilisateur.
165	///
166	/// À terme:
167	/// Pendant la phase de développement, une interface textuelle interactive y
168	/// est également lancée, afin de:
169	///  1) Apporter un peu d'aide visuelle à l'utilisateur.
170	///  2) Afin d'accéder à la base de données pour y faire des modifications.
171	///  3) Afin de pouvoir tester les requêtes HTTP.
172	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	/// Définit les paramètres utilisateur de l'état.
193	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}