Compare commits

...

4 Commits

Author SHA1 Message Date
738da0aac6 Create axum router directly
All checks were successful
Push Workflows / rustfmt (push) Successful in 5s
Push Workflows / tailwind-build (push) Successful in 6s
Push Workflows / docs (push) Successful in 25s
Push Workflows / clippy (push) Successful in 23s
Push Workflows / test (push) Successful in 31s
Push Workflows / build (push) Successful in 57s
Push Workflows / nix-build (push) Successful in 5m11s
Bind to configured host/port
Warn if configured host/port differs from dx-specified host/port
Make main async
2026-07-14 20:13:07 -04:00
76fac9b4cb Add tokio on server 2026-07-14 20:11:19 -04:00
4836108926 Add HTTP server error variant 2026-07-14 20:11:04 -04:00
d66871f2f5 Add server host/port config 2026-07-14 20:10:04 -04:00
6 changed files with 67 additions and 21 deletions

1
Cargo.lock generated
View File

@@ -2428,6 +2428,7 @@ dependencies = [
"rand 0.10.1", "rand 0.10.1",
"serde", "serde",
"thiserror 2.0.18", "thiserror 2.0.18",
"tokio",
"tower-http 0.7.0", "tower-http 0.7.0",
"tower-sessions-redis-store", "tower-sessions-redis-store",
"tracing", "tracing",

View File

@@ -26,6 +26,7 @@ rand = "0.10.1"
serde = { version = "1.0.228", features = ["derive"] } serde = { version = "1.0.228", features = ["derive"] }
thiserror = "2.0.18" thiserror = "2.0.18"
tower-http = { version = "0.7.0", optional = true, features = ["fs"] } tower-http = { version = "0.7.0", optional = true, features = ["fs"] }
tokio = { version = "1.52.3", optional = true }
tower-sessions-redis-store = { version = "0.16.0", optional = true } tower-sessions-redis-store = { version = "0.16.0", optional = true }
tracing = "0.1.44" tracing = "0.1.44"
@@ -42,6 +43,7 @@ server = [
"dep:dotenvy", "dep:dotenvy",
"dep:fred", "dep:fred",
"dep:pbkdf2", "dep:pbkdf2",
"dep:tokio",
"dep:tower-http", "dep:tower-http",
"dep:tower-sessions-redis-store", "dep:tower-sessions-redis-store",
] ]

View File

@@ -28,10 +28,11 @@ fn main() {
} }
#[cfg(feature = "server")] #[cfg(feature = "server")]
fn main() -> std::process::ExitCode { #[tokio::main]
async fn main() -> std::process::ExitCode {
tracing_setup(); tracing_setup();
let Err(e) = server::main(); let Err(e) = server::main().await;
tracing::error!("Server main failed:\n{e}"); tracing::error!("Server main failed:\n{e}");
std::process::ExitCode::FAILURE std::process::ExitCode::FAILURE

View File

@@ -1,3 +1,4 @@
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
use std::path::PathBuf; use std::path::PathBuf;
use serde::Deserialize; use serde::Deserialize;
@@ -146,6 +147,14 @@ impl KeyValStoreConfig {
#[derive(Debug, Clone, Deserialize)] #[derive(Debug, Clone, Deserialize)]
pub struct ServerConfig { pub struct ServerConfig {
pub public_path: PathBuf, pub public_path: PathBuf,
pub host: IpAddr,
pub port: u16,
}
impl ServerConfig {
pub fn serve_addr(&self) -> SocketAddr {
SocketAddr::new(self.host, self.port)
}
} }
#[derive(Debug, Clone, Deserialize)] #[derive(Debug, Clone, Deserialize)]
@@ -164,7 +173,16 @@ pub fn load_config() -> Result<Config, config::ConfigError> {
let pkg_name = env!("CARGO_PKG_NAME"); let pkg_name = env!("CARGO_PKG_NAME");
config::Config::builder() config::Config::builder()
.set_default("server.port", 8080)? .set_default(
"server.port",
dioxus::cli_config::server_port().unwrap_or(8080),
)?
.set_default(
"server.host",
dioxus::cli_config::server_ip()
.unwrap_or(IpAddr::V4(Ipv4Addr::UNSPECIFIED))
.to_string(),
)?
.set_default("auth.open_signup", false)? .set_default("auth.open_signup", false)?
.set_default("auth.cookies_secure", DEFAULT_COOKIES_SECURE)? .set_default("auth.cookies_secure", DEFAULT_COOKIES_SECURE)?
.set_default("server.public_path", default_public_dir())? .set_default("server.public_path", default_public_dir())?

View File

@@ -1,20 +1,21 @@
use dioxus::{ use dioxus::{
fullstack::axum::{Router, middleware::from_fn}, cli_config,
fullstack::axum::{self, Router, middleware::from_fn},
prelude::{DioxusRouterExt, ServeConfig},
server::axum::Extension, server::axum::Extension,
}; };
use tokio::net::TcpListener;
use tower_http::services::ServeFile; use tower_http::services::ServeFile;
use crate::App; use crate::App;
use crate::app::LOGO_ICO; use crate::app::LOGO_ICO;
use crate::server::{ use crate::server::{
auth::build_auth_layer, auth::build_auth_layer, config, database, key_val_store,
config::{self, Config},
database, key_val_store,
require_auth_mw::require_auth_middleware, require_auth_mw::require_auth_middleware,
}; };
use crate::util::error::{Contextualize, Error, Result}; use crate::util::error::{Contextualize, Error, ErrorType, Result};
pub fn main() -> Result<std::convert::Infallible> { pub async fn main() -> Result<std::convert::Infallible> {
if let Err(e) = dotenvy::dotenv() { if let Err(e) = dotenvy::dotenv() {
tracing::warn!("Error reading .env: {e}"); tracing::warn!("Error reading .env: {e}");
} }
@@ -32,15 +33,6 @@ pub fn main() -> Result<std::convert::Infallible> {
std::env::set_var("DIOXUS_PUBLIC_PATH", config.server.public_path.clone()); std::env::set_var("DIOXUS_PUBLIC_PATH", config.server.public_path.clone());
} }
// `Ok(...?)` is because `dioxus::serve` expects an `anyhow::Result`
dioxus::serve(move || {
let config = config.clone();
async move { Ok(router_setup(config).await?) }
});
}
/// Set up the axum Router
async fn router_setup(config: Config) -> Result<Router> {
let db_pool = database::setup(config.database.connection_uri()) let db_pool = database::setup(config.database.connection_uri())
.await .await
.err_context("Failed database setup")?; .err_context("Failed database setup")?;
@@ -65,13 +57,42 @@ async fn router_setup(config: Config) -> Result<Router> {
let auth_layer = build_auth_layer(db_pool.clone(), key_val_pool, config.auth.cookies_secure); let auth_layer = build_auth_layer(db_pool.clone(), key_val_pool, config.auth.cookies_secure);
let router = dioxus::server::router(App) #[cfg(debug_assertions)]
if let Some(dx_port) = cli_config::server_port()
&& dx_port != config.server.port
{
tracing::warn!(
"Your configured server port ({}) doesn't match the one specified from environment \
variables set by the Dioxus CLI ({dx_port}). If you are intending to use the dx tool, \
please do not specify a port in the configuration.",
config.server.port
);
}
let addr = config.server.serve_addr();
tracing::info!("Setup complete, building router...");
let router = Router::new()
.serve_dioxus_application(ServeConfig::new(), App)
.layer(from_fn(require_auth_middleware)) .layer(from_fn(require_auth_middleware))
.layer(Extension(config)) .layer(Extension(config))
.layer(Extension(db_pool)) .layer(Extension(db_pool))
.layer(auth_layer) .layer(auth_layer)
.nest_service("/favicon.ico", ServeFile::new(favicon_path)); .nest_service("/favicon.ico", ServeFile::new(favicon_path));
tracing::info!("Setup complete, returning Router..."); tracing::info!("Listening on {addr}...");
Ok(router) let listener = TcpListener::bind(addr)
.await
.map_err(|e| ErrorType::HttpServer(e.to_string()))
.err_context(format!("Failed to bind to {addr}"))?;
axum::serve(listener, router)
.await
.map_err(|e| ErrorType::HttpServer(e.to_string()))
.err_context("HTTP server error")?;
Err(Error::new_here(ErrorType::HttpServer(
"axum::serve should never return".to_owned(),
)))
} }

View File

@@ -327,6 +327,9 @@ pub enum ErrorType {
// and Fred is only available on the server // and Fred is only available on the server
#[error("Key-value store error: {0}")] #[error("Key-value store error: {0}")]
KeyValStore(String), KeyValStore(String),
#[error("HTTP server error: {0}")]
HttpServer(String),
} }
impl From<ErrorType> for Error { impl From<ErrorType> for Error {