summaryrefslogtreecommitdiff
path: root/src/main.rs
diff options
context:
space:
mode:
authorJohn Turner <jturner.usa@gmail.com>2026-03-01 17:34:34 -0500
committerJohn Turner <jturner.usa@gmail.com>2026-03-01 17:34:34 -0500
commit3c4208abd325d317c7524ba0dc3b701edfa9ebf8 (patch)
tree197a3d46fcad3ed8d7f3b3d0d0c0fc3f79e3a204 /src/main.rs
parent06476ea44ff27bd9b4af2aa5bcf715fe9034db9a (diff)
downloadhttpd-master.tar.gz
impl basic static file serverHEADmaster
Diffstat (limited to 'src/main.rs')
-rw-r--r--src/main.rs189
1 files changed, 189 insertions, 0 deletions
diff --git a/src/main.rs b/src/main.rs
new file mode 100644
index 0000000..ea459d9
--- /dev/null
+++ b/src/main.rs
@@ -0,0 +1,189 @@
+#![allow(dead_code)]
+
+use std::process::ExitCode;
+
+use mlua::{Function, Lua, Table};
+
+use tokio::{
+ io::{self, BufReader, BufWriter},
+ net::{TcpListener, TcpStream},
+};
+
+use crate::{
+ client::Client,
+ handlers::{Handle, Handlers},
+ request::Request,
+ response::Response,
+};
+
+mod client;
+mod handlers;
+mod request;
+mod response;
+
+macro_rules! exit {
+ ($fmt:literal, $($s:expr),*) => {
+ {
+ eprintln!($fmt, $($s),*);
+
+ return ::std::process::ExitCode::FAILURE
+ }
+ }
+}
+
+#[derive(Debug, thiserror::Error)]
+enum HandleError {
+ #[error("error reading handler function: {0}")]
+ Function(mlua::Error),
+
+ #[error("error calling handler: {0}")]
+ InvokeHandler(mlua::Error),
+
+ #[error("handler error: {0}")]
+ Handler(#[from] handlers::Error),
+}
+
+#[derive(Debug, thiserror::Error)]
+enum ResponseError {
+ #[error("error reading request: {0}")]
+ Request(client::Error),
+
+ #[error("error sending response: {0}")]
+ Response(io::Error),
+}
+
+#[derive(Debug, thiserror::Error)]
+enum InitLuaError {
+ #[error("failed to create variable: {0}")]
+ CreateTable(mlua::Error),
+
+ #[error("failed to set variable {1}: {0}")]
+ SetVar(mlua::Error, String),
+
+ #[error("failed to load variable {1}: {0}")]
+ LoadVar(mlua::Error, String),
+
+ #[error("failed to load config: {0}")]
+ LoadConfig(std::io::Error),
+
+ #[error("failed to eval config: {0}")]
+ EvalConfig(mlua::Error),
+}
+
+async fn handle<T: Clone + 'static>(
+ handlers: Table,
+ request: Request<T>,
+) -> Result<Response, HandleError> {
+ let method = request.inner().method().as_str().to_string();
+
+ let function = handlers
+ .get::<Function>(method.as_str())
+ .map_err(HandleError::Function)?;
+
+ let handler = function
+ .call::<Handlers>(request.clone())
+ .map_err(HandleError::InvokeHandler)?;
+
+ match handler {
+ Handlers::StaticFile(staticfile) => Ok(staticfile.handle(request).await?),
+ }
+}
+
+async fn response(handlers: Table, stream: TcpStream) -> Result<(), ResponseError> {
+ let mut client = {
+ let (r, w) = stream.into_split();
+
+ Client::new(BufReader::new(r), BufWriter::new(w))
+ };
+
+ while let Some(request) = client
+ .read_request()
+ .await
+ .map_err(ResponseError::Request)?
+ {
+ let response = match handle(handlers.clone(), request).await {
+ Ok(response) => response,
+ Err(e) => {
+ eprintln!("failed to handle request: {e:?}");
+
+ Response::new(
+ http::Response::builder()
+ .status(http::StatusCode::INTERNAL_SERVER_ERROR)
+ .body(response::Body::Empty)
+ .unwrap(),
+ )
+ }
+ };
+
+ client
+ .send_response(response)
+ .await
+ .map_err(ResponseError::Response)?;
+ }
+
+ Ok(())
+}
+
+fn init_lua(lua: Lua) -> Result<(), InitLuaError> {
+ let http = lua.create_table().map_err(InitLuaError::CreateTable)?;
+
+ lua.globals()
+ .set("http", http.clone())
+ .map_err(|e| InitLuaError::SetVar(e, "http".to_string()))?;
+
+ let chunk = lua.load(std::fs::read_to_string("config.lua").map_err(InitLuaError::LoadConfig)?);
+
+ chunk.eval::<()>().map_err(InitLuaError::EvalConfig)?;
+
+ Ok(())
+}
+
+#[allow(unexpected_cfgs)]
+#[tokio::main(flavor = "local")]
+async fn main() -> ExitCode {
+ let lua = Lua::new();
+
+ if let Err(e) = init_lua(lua.clone()) {
+ exit!("failed to init lua: {}", e);
+ }
+
+ let http = match lua.globals().get::<Table>("http") {
+ Ok(http) => http,
+ Err(e) => exit!("failed to load table 'http': {}", e),
+ };
+
+ let bind = match http.get::<String>("bind") {
+ Ok(bind) => bind,
+ Err(e) => exit!("failed to load string 'http.bind': {}", e),
+ };
+
+ let handlers = match http.get::<Table>("handlers") {
+ Ok(handlers) => handlers,
+ Err(e) => exit!("failed to load 'http.handlers': {}", e),
+ };
+
+ let listener = match TcpListener::bind(&bind).await {
+ Ok(listener) => listener,
+ Err(e) => exit!("failed to bind to {}: {}", bind, e),
+ };
+
+ loop {
+ let (stream, addr) = match listener.accept().await {
+ Ok((stream, addr)) => (stream, addr),
+ Err(e) => {
+ eprintln!("failed to accept connection: {e}");
+ continue;
+ }
+ };
+
+ eprintln!("accepted connection from {addr}");
+
+ let future = response(handlers.clone(), stream);
+
+ tokio::task::spawn_local(async {
+ if let Err(e) = future.await {
+ eprintln!("response failure: {e:?}");
+ }
+ });
+ }
+}