use axum::Router; use axum::routing::{get, post}; use axum::serve::Listener as AxumListener; use minijinja::Environment; #[cfg(not(feature = "embed"))] use minijinja::path_loader; #[cfg(feature = "embed")] use static_serve::embed_assets; #[cfg(not(feature = "embed"))] use tower_http::services::ServeDir; use crate::db::{Database, DatabaseInterface}; use crate::listener::{Listener, ListenerKind}; use crate::stream::{AbstractSocketAddr, AbstractStreamKind}; mod home; mod login; mod logout; mod session; use session::HttpSessionManager; impl AxumListener for Listener { type Io = AbstractStreamKind; type Addr = AbstractSocketAddr; async fn accept(&mut self) -> (Self::Io, Self::Addr) { loop { let res = match &self.kind { ListenerKind::Tcp(l) => l .accept() .await .map(|(stream, remote_addr)| (stream.into(), remote_addr.into())), ListenerKind::Uds(l) => l .accept() .await .map(|(stream, remote_addr)| (stream.into(), remote_addr.into())), }; match res { Ok((stream, remote_addr)) => return (stream, remote_addr), Err(e) => { // Here the error could be fatal, or could simply be that a client aborted the connected, // in which case we don't want to crash the server, simply skip this client connection. // https://doc.rust-lang.org/stable/std/net/struct.TcpListener.html#errors match e.kind() { std::io::ErrorKind::ConnectionAborted => {} _ => panic!("Unrecoverable HTTP client connection error: {e}"), } } } } } fn local_addr(&self) -> std::io::Result { match &self.kind { ListenerKind::Tcp(l) => l.local_addr().map(Into::into), ListenerKind::Uds(l) => l.local_addr().map(Into::into), } } } #[derive(Clone)] pub struct HttpState { pub db: Database, pub sessions: HttpSessionManager, pub templates: Environment<'static>, } impl HttpState { pub fn new(db: Database) -> Self { let mut templates = Environment::new(); #[cfg(feature = "embed")] minijinja_embed::load_templates!(&mut templates); #[cfg(not(feature = "embed"))] templates.set_loader(path_loader("templates")); Self { db, sessions: HttpSessionManager::new(), templates, } } } pub async fn http_listen(listener: Listener, db: Database) { #[cfg(all(feature = "embed", feature = "noembed"))] compile_error!("You cannot have `embed` and `noembed` features enabled at the same time."); #[cfg(not(any(feature = "embed", feature = "noembed")))] compile_error!("You must have `embed` or `noembed` feature enabled."); #[cfg(feature = "embed")] let app = { embed_assets!("assets"); Router::new().nest("/assets", static_router()) }; #[cfg(not(feature = "embed"))] let app = { Router::new().nest_service("/assets", ServeDir::new("assets")) }; let app = app .route("/", get(home::home)) .route("/login", get(home::home)) .route("/login", post(login::post_login)) .route("/logout", get(logout::logout)) .with_state(HttpState::new(db)); axum::serve(listener, app).await.unwrap(); }