#[macro_use]
mod unwrap;
#[cfg(feature = "watch")]
mod watch;
#[cfg(feature = "watch")]
pub use watch::watch;
use std::{convert::Infallible, fs, path::Path};
use http::{Method, Request, Response, StatusCode};
use hyper::{
service::{make_service_fn, service_fn},
Body, Server,
};
use crate::DEV_BUILD_DIR;
pub const SERVER_PORT: u16 = 3000;
#[cfg(not(feature = "watch"))]
pub const DEV_SCRIPT: &str = include_str!("client-script/dev.html");
#[cfg(feature = "watch")]
pub const DEV_SCRIPT: &str = include_str!("client-script/watch.html");
const FALLBACK_404: &str = const_str::concat!(include_str!("404.html"), "\n\n", DEV_SCRIPT);
pub fn listen() {
let runtime = unwrap!(
tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build(),
"Failed to build tokio runtime"
);
unwrap!(
runtime.block_on(async {
let make_svc =
make_service_fn(|_| async { Ok::<_, Infallible>(service_fn(server_router)) });
let addr = unwrap!(
const_str::concat!("127.0.0.1:", SERVER_PORT).parse(),
"Failed to parse constant IP address"
);
Server::bind(&addr).serve(make_svc).await
}),
err: "Error in server runtime: `{err:?}`"
);
}
async fn server_router(req: Request<Body>) -> Result<Response<Body>, Infallible> {
if req.method() == Method::GET {
if let Some(file) = get_best_possible_file(req.uri().path()) {
return Ok(Response::new(file));
}
}
Ok(unwrap!(
Response::builder().status(StatusCode::NOT_FOUND).body(
if let Some(file) = get_best_possible_file("404") {
file
} else {
Body::from(FALLBACK_404)
},
),
err: "Failed to build 404 page response `{err:?}`",
))
}
fn get_best_possible_file(path: &str) -> Option<Body> {
let possible_suffixes = possible_path_suffixes(path);
for suffix in possible_suffixes {
let path = &format!("{DEV_BUILD_DIR}/{path}/{suffix}");
if Path::new(path).is_file() {
return Some(Body::from(unwrap!(
fs::read(path),
"Could not read file '{}'",
path
)));
}
}
None
}
fn possible_path_suffixes(path: &str) -> &'static [&'static str] {
if path.ends_with(".html") || path.starts_with("/styles") || path.starts_with("/public") {
&[""]
} else {
&["", ".html", "/index.html"]
}
}