mod error;
pub mod settings;
use std::net;
use console::style;
pub use self::error::Error;
#[cfg(feature = "cors")]
use crate::http;
use crate::{routing, state};
pub const PORT_PLAINTEXT: u16 = 80;
pub const PORT_ENCRYPT: u16 = 443;
pub struct Server<UserState> {
pub state: crate::state::State<UserState>,
pub application_settings: crate::application::Settings,
pub(crate) settings: settings::Settings,
#[cfg(feature = "cors")]
pub(crate) cors_settings: Option<http::cors::Settings>,
global_router: axum::Router<()>,
pub(crate) router: axum::Router<crate::state::State<UserState>>,
pub(crate) routes: crate::routing::RouteCollection<UserState>,
static_resources: Vec<settings::SettingsStaticResource>,
}
impl<UserState> Server<UserState>
where
UserState: 'static,
UserState: crate::state::StateInterface,
{
pub async fn new(
application_settings: crate::application::Settings,
) -> Result<Self, Error> {
let state = crate::state::State {
#[cfg(feature = "cookies")]
cookie_key: None,
#[cfg(feature = "database-postgres")]
database_postgres: None,
user_state: None,
};
let settings = settings::Settings::fetch_or_default(
&application_settings.config_dir,
&application_settings.loader_extension,
);
let static_resources = settings
.static_resources
.iter()
.cloned()
.map(|mut sr| {
sr.dir_path = if sr.dir_path.is_relative() {
application_settings.root_dir.join(&sr.dir_path)
} else {
sr.dir_path.to_owned()
};
sr
})
.collect();
Ok(Self {
state,
#[cfg(feature = "cors")]
cors_settings: {
lexa_fs::load(
&application_settings.config_dir,
"cors",
application_settings.loader_extension,
)
.ok()
},
application_settings,
settings,
global_router: axum::Router::new(),
router: axum::Router::new(),
routes: routing::RouteCollection::new(),
static_resources,
})
}
pub fn layer<Layer>(mut self, layer: Layer) -> Self
where
Layer: Clone + Send + Sync + 'static,
Layer: tower_layer::Layer<axum::routing::Route>,
Layer::Service: tower_service::Service<
hyper::Request<hyper::Body>,
Error = std::convert::Infallible,
>,
Layer::Service: Clone + Send + 'static,
<Layer::Service as tower_service::Service<
hyper::Request<hyper::Body>,
>>::Response: axum::response::IntoResponse + 'static,
<Layer::Service as tower_service::Service<
hyper::Request<hyper::Body>,
>>::Future: Send + 'static,
{
self.router = self.router.layer(layer);
self
}
pub fn make_application<A>(mut self) -> Self
where
A: crate::Application<State = UserState>,
{
let routes = <A::Router as routing::RouterExt>::routes();
let routes_state =
<A::Router as routing::RouterExt>::routes_with_state(&self.state);
let mut scoped_router = axum::Router::<state::State<UserState>>::new();
for route in routes.all().chain(routes_state.all()) {
scoped_router =
scoped_router.route(&route.fullpath, route.action.to_owned());
}
scoped_router = A::register_extension(&self.state, scoped_router);
scoped_router = A::register_layer(&self.state, scoped_router);
scoped_router = A::register_middleware(&self.state, scoped_router);
self.router = self.router.merge(scoped_router);
self.routes.extend(routes);
self.routes.extend(routes_state);
A::register_service(self)
}
pub async fn run(mut self) -> Result<(), Error> {
self.display_all_routes();
self = self.define_static_resources();
#[cfg(feature = "cors")]
{
self = self.define_cors();
}
#[cfg(feature = "cookies")]
{
self = self.define_cookie_layer().await?;
}
self.launch_server().await?;
Ok(())
}
pub fn with_user_state(mut self, data: UserState::UserData) -> Self {
self.state.user_state.replace(UserState::new(data));
self
}
}
impl<UserState> Server<UserState>
where
UserState: 'static,
UserState: crate::state::StateInterface,
{
fn define_static_resources(mut self) -> Self {
for static_resource in self.static_resources.iter() {
self.global_router = self.global_router.nest_service(
&static_resource.url_path,
tower_http::services::ServeDir::new(&static_resource.dir_path),
);
}
self
}
fn display_all_routes(&self) {
println!();
println!("Liste des routes du serveur web:");
for route in self.routes.all() {
let methods = route
.methods
.iter()
.map(|m| style(&m).yellow().to_string())
.collect::<Vec<String>>()
.join(" | ");
println!(
"\t[{}]: {} {}",
methods,
style(&route.fullpath).bright().green(),
if let Some(name) = route.name.as_deref() {
format!("as {}", style(name).bright().black())
} else {
String::new()
}
);
}
println!();
for static_resource in self.static_resources.iter() {
println!(
"\t[{}]: {} -> {}",
style("STATIC RESOURCE").magenta(),
style(&static_resource.url_path).bright().green(),
style(static_resource.dir_path.display()).bright().black(),
);
}
}
async fn launch_server(mut self) -> Result<(), Error> {
log::info!("Tentative de lancement du serveur web...");
self.global_router =
self.global_router.merge(self.router.with_state(self.state));
let protocol = if self.settings.tls.is_some() {
"https"
} else {
"http"
};
let host = self.settings.host;
let port = self.settings.port;
let server_socket_addr =
<_ as Into<net::SocketAddr>>::into((host, port));
let port_with_prefix = if port == PORT_PLAINTEXT || port == PORT_ENCRYPT
{
String::new()
} else {
format!(":{}", port)
};
let url = format!("{protocol}://{host}{port_with_prefix}");
type S = net::SocketAddr;
println!("URL: {url}");
if let Some(settings_tls) = self.settings.tls {
let tls_config =
axum_server::tls_rustls::RustlsConfig::from_pem_file(
&settings_tls.cert,
&settings_tls.key,
)
.await?;
let http_config =
axum_server::HttpConfig::new().http2_only(true).build();
axum_server::bind_rustls(server_socket_addr, tls_config)
.http_config(http_config)
.serve(
self.global_router
.into_make_service_with_connect_info::<S>(),
)
.await?;
} else {
axum::Server::bind(&server_socket_addr)
.serve(
self.global_router
.into_make_service_with_connect_info::<S>(),
)
.await?;
}
Ok(())
}
}
#[cfg(feature = "encryption-argon2")]
impl<UserState> Server<UserState>
where
UserState: 'static,
UserState: crate::state::StateInterface,
{
pub async fn using_argon2_password_service(
mut self,
secret: &<
crate::security::password::argon2::Argon2PasswordService as
crate::application::Service
>::Payload,
) -> Result<Self, Error> {
use crate::application::Service;
use crate::security::password::argon2::Argon2PasswordService;
self.router = self.router.layer(axum::Extension(
Argon2PasswordService::subscribe(secret).await?,
));
Ok(self)
}
}
#[cfg(feature = "cookies")]
impl<UserState> Server<UserState>
where
UserState: 'static,
UserState: crate::state::StateInterface,
{
pub fn define_encrypt_key(mut self, key: &str) -> Self {
self.state
.cookie_key
.replace(tower_cookies::Key::from(key.as_bytes()));
self
}
async fn define_cookie_layer(mut self) -> Result<Self, Error> {
#[cfg(feature = "auth")]
{
self.router =
self.router.layer(crate::auth::AuthCookieLayer::store(
self.state.cookie_key.as_ref().unwrap(),
));
}
self.router =
self.router.layer(tower_cookies::CookieManagerLayer::new());
Ok(self)
}
}
#[cfg(feature = "database-postgres")]
impl<UserState> Server<UserState>
where
UserState: 'static,
UserState: crate::state::StateInterface,
{
pub async fn using_database_postgres_service(
mut self,
db_payload: &<
crate::database::services::postgres::PostgresService as
crate::application::Service
>::Payload,
) -> Result<Self, Error> {
use crate::application::Service;
use crate::database::services::postgres;
self.state
.database_postgres
.replace(postgres::PostgresService::subscribe(db_payload).await?);
Ok(self)
}
}
#[cfg(feature = "cors")]
impl<UserState> Server<UserState>
where
UserState: 'static,
UserState: crate::state::StateInterface,
{
fn define_cors(mut self) -> Self {
if let Some(cors_settings) = &self.cors_settings {
log::info!(
"Application du composant CORS... {:#?}",
&cors_settings
);
let mut cors_layer = match &cors_settings.preset {
| Some(preset) => {
match preset {
| http::cors::SettingsPreset::Default => {
http::cors::CorsLayer::new()
}
| http::cors::SettingsPreset::Permissive => {
http::cors::CorsLayer::permissive()
}
| http::cors::SettingsPreset::VeryPermissive => {
http::cors::CorsLayer::very_permissive()
}
}
}
| None => http::cors::CorsLayer::default(),
};
if let Some(b) = cors_settings.allow_credentials {
cors_layer = cors_layer.allow_credentials(b);
}
if let Some(h) = &cors_settings.allow_headers {
cors_layer = cors_layer.allow_headers(h);
}
if let Some(m) = &cors_settings.allow_methods {
cors_layer = cors_layer.allow_methods(m);
}
if let Some(o) = &cors_settings.allow_origin {
cors_layer = cors_layer.allow_origin(o);
}
if let Some(b) = cors_settings.allow_private_network {
cors_layer = cors_layer.allow_private_network(b);
}
if let Some(e) = &cors_settings.expose_headers {
cors_layer = cors_layer.expose_headers(e);
}
if let Some(m) = &cors_settings.max_age {
cors_layer = cors_layer.max_age(m);
}
if let Some(v) = &cors_settings.vary {
cors_layer = cors_layer.vary(v);
}
self.router = self.router.layer(cors_layer);
}
self
}
}