mod app;
mod debug;
mod assets;
mod config;
mod templates;
use clap::Parser;
use std::error::Error;
use std::path::PathBuf;
use axum::http::Method;
use axum::extract::Path;
use std::net::SocketAddr;
use crate::assets::Assets;
use crate::config::Config;
use std::collections::HashMap;
use crate::app::{AppState, handler};
use axum::http::header::HeaderValue;
use axum::routing::{Router, get, on};
use tower_http::cors::{Any, CorsLayer};
use axum_server::tls_openssl::OpenSSLConfig;
#[derive(Parser)]
#[command(author, version, about, long_about = None)]
struct Cli {
#[clap(short='f', long)]
config: Option<PathBuf>,
#[clap()]
assets: Option<PathBuf>,
#[clap(short, long)]
port: Option<u16>,
#[clap(short, long)]
cert: Option<PathBuf>,
#[clap(short, long)]
key: Option<PathBuf>,
#[clap(short='o', long)]
allow_cors: bool,
#[clap(short, long)]
all: bool,
#[clap(short, long)]
ignore: Option<String>,
}
fn init () -> Result<(Router, u16, Option<OpenSSLConfig>), Box<dyn Error>> {
let cli = Cli::parse();
let config = Config::new(cli.config.as_deref())?;
let mut app = Router::new();
if let Some(assets) = cli.assets.or(config.assets) {
let mut ignore: Vec<String> = Vec::new();
let has_home = assets.as_path().join("index.html").is_file();
if let Some(glob) = cli.ignore {
ignore.push(glob);
if let Some(globs) = config.ignore {
ignore.append(&mut globs.clone());
}
}
let loader = Assets::new(cli.all || config.all.unwrap_or(false), assets, ignore)?;
if has_home {
let loader2 = loader.clone();
app = app.route("/", get(|| async move {
loader2.get("")
}));
}
app = app.route("/*file", get(|
Path(params): Path<HashMap<String, String>>,
| async move {
loader.get(params.get("file").map_or("", |v| v))
}));
}
if let (Some(templates), Some(routes)) = (config.templates, config.routes) {
let env = templates::new(templates, config.data)?;
for route in &routes {
app = app.route(&route.path, on(
Method::from_bytes(route.method.as_bytes())?.try_into()?,
handler
).with_state(AppState::new(&env, &route.template)));
}
}
let cors = if cli.allow_cors {Some(Vec::new())} else {config.cors};
if let Some(origins) = cors {
let mut layer = CorsLayer::new().allow_methods(Any);
if origins.is_empty() {
layer = layer.allow_origin(Any);
}
for origin in origins {
layer = layer.allow_origin(origin.parse::<HeaderValue>()?);
}
app = app.layer(layer);
}
let port = cli.port.unwrap_or(config.port.unwrap_or(3000));
let mut ssl: Option<OpenSSLConfig> = None;
if let (Some(cert), Some(key)) = (
cli.cert.or(config.cert), cli.key.or(config.key)
) {
ssl = Some(OpenSSLConfig::from_pem_file(cert, key)?);
}
Ok((app, port, ssl))
}
#[tokio::main]
async fn main() {
let (app, port, ssl) = match init() {
Ok(server) => server,
Err(err) => {
println!("{}", err);
return;
}
};
let addr = SocketAddr::from(([127, 0, 0, 1], port));
let server = match ssl {
Some(ssl) => {
println!("Server started at https://localhost:{}", port);
axum_server::bind_openssl(addr, ssl)
.serve(app.into_make_service()).await
},
None => {
println!("Server started at http://localhost:{}", port);
axum_server::bind(addr)
.serve(app.into_make_service()).await
}
};
match server {
Ok(_) => (),
Err(err) => {
println!("Fail to start server!\n{}", err);
}
}
}