#[macro_use]
mod unwrap;
mod files;
#[cfg(feature = "watch")]
mod 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::{Port, DEV_BUILD_DIR};
pub use files::{dev_script, fallback_404};
#[cfg(feature = "watch")]
pub use watch::watch;
pub fn listen(port: Port, public: &str, port_ws: Port) {
let runtime = unwrap!(
tokio::runtime::Builder::new_current_thread()
.enable_io()
.build(),
"Failed to build tokio runtime"
);
let public = public.to_string();
unwrap!(
runtime.block_on(async {
let make_svc =
make_service_fn(move |_| {
let public = public.clone();
async move {
Ok::<_, Infallible>(service_fn(move |req| {
server_router(req, public.clone(), port_ws)
}))
}
});
let addr = unwrap!(
format!("127.0.0.1:{}", 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>,
public: String,
port_ws: Port,
) -> Result<Response<Body>, Infallible> {
if req.method() == Method::GET {
let path = req.uri().path();
if path.starts_with("/public/") {
let path = path.replacen("/public", &public, 1);
return Ok(Response::new(read_and_unwrap(&path)));
}
if let Some(file) = get_best_possible_file(path) {
return Ok(Response::new(file));
}
}
Ok(unwrap!(
Response::builder().status(StatusCode::NOT_FOUND).body(
if let Some(file) = get_best_possible_file("/404.html") {
file
} else {
Body::from(fallback_404(port_ws))
},
),
err: "Failed to build 404 route 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(read_and_unwrap(path));
}
}
None
}
fn read_and_unwrap(path: &str) -> Body {
Body::from(unwrap!(
fs::read(path),
"Could not read file '{}'",
path
))
}
fn possible_path_suffixes(path: &str) -> &'static [&'static str] {
if path.ends_with(".html") || path.starts_with("/styles/") {
&[""]
} else {
&["", ".html", "/index.html"]
}
}