use axum::{
Router,
extract::{Request, State},
handler::HandlerWithoutStateExt,
http::StatusCode,
response::{Html, IntoResponse},
routing::{get, get_service},
};
use site::{env::Env, state::AppState};
use std::{path::PathBuf, time::Duration};
use tokio::net::TcpListener;
use tower::util::ServiceExt;
use tower_http::{
compression::CompressionLayer,
services::{ServeDir, ServeFile},
timeout::TimeoutLayer,
trace::TraceLayer,
};
const ERR: (StatusCode, Html<&'static str>) =
(StatusCode::NOT_FOUND, Html("
404 Not Found
"));
async fn not_found() -> impl IntoResponse {
ERR
}
async fn static_files(State(state): State, req: Request) -> impl IntoResponse {
ServeDir::new(state.get_dist_dir().as_ref())
.precompressed_br()
.precompressed_gzip()
.fallback(not_found.into_service())
.oneshot(req)
.await
.map_err(|_| ERR)
}
async fn html_files(State(state): State, mut req: Request) -> impl IntoResponse {
let mut file_path = PathBuf::from(req.uri().path());
if file_path.extension().is_none() {
file_path.set_extension("html");
}
*req.uri_mut() = file_path
.to_str()
// if this were to run on a windows machine
.map(|s| s.replace("\\", "/"))
.ok_or(ERR)?
.parse()
.map_err(|_| ERR)?;
ServeDir::new(state.get_dist_dir().as_ref())
.fallback(not_found.into_service())
.oneshot(req)
.await
.map_err(|_| ERR)
}
#[tokio::main]
async fn main() -> Result<(), Box> {
tracing_subscriber::fmt()
.with_max_level(tracing::Level::DEBUG)
.init();
let Env {
site_addr,
dist_dir,
} = Env::get_or_default();
let listener = TcpListener::bind(&site_addr).await?;
tracing::info!("Listening on http://{site_addr}/");
let router = Router::new()
.route(
"/",
get_service(ServeFile::new(dist_dir.join("index.html"))),
)
.route("/assets/{*any}", get(static_files))
.fallback(html_files)
.layer(CompressionLayer::new().gzip(true))
.layer(TimeoutLayer::with_status_code(
StatusCode::REQUEST_TIMEOUT,
Duration::from_secs(30),
))
.layer(TraceLayer::new_for_http())
.with_state(AppState::new(dist_dir));
axum::serve(listener, router).await?;
Ok(())
}