diff options
Diffstat (limited to 'src/main.rs')
| -rw-r--r-- | src/main.rs | 189 |
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:?}"); + } + }); + } +} |
