#![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( handlers: Table, request: Request, ) -> Result { let method = request.inner().method().as_str().to_string(); let function = handlers .get::(method.as_str()) .map_err(HandleError::Function)?; let handler = function .call::(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::("http") { Ok(http) => http, Err(e) => exit!("failed to load table 'http': {}", e), }; let bind = match http.get::("bind") { Ok(bind) => bind, Err(e) => exit!("failed to load string 'http.bind': {}", e), }; let handlers = match http.get::
("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:?}"); } }); } }