use std::collections::HashMap; use std::sync::Arc; use http::code::Code; use http::header::Header; use http::request::Request; use http::response::Response; use tokio::net::{TcpListener, TcpStream}; use tokio::io::{AsyncReadExt, AsyncWriteExt}; use crate::bash::handle_script; mod http; mod bash; async fn handle_response(mut socket: TcpStream, code: Code, body: String) { let mut res = Response::new(); res.headers.put(Header::new("Content-Type", "text/plain")); res.status = code; res.body = Some(body); let res_str = res.deserialize(); let _ = socket.write(res_str.as_bytes()).await; } async fn handle_connection(mut socket: TcpStream, config: Arc>) { let mut buf = [0; 1204]; let n: usize = match socket.read(&mut buf).await { Ok(n) if n == 0 => return, Ok(n) => n as usize, Err(e) => { eprintln!("failed to read from socket; err = {:?}", e); return } }; let str = String::from_utf8_lossy(&buf[0..n]); let Some(req) = Request::serialize(&str) else { return }; let Some(script) = config.get(&req.uri.route) else { handle_response(socket, Code::MethodNotAllowed, "Method Not Allowed".to_owned()).await; return }; match handle_script(script, req.body.as_ref()) { Ok(out) => { handle_response(socket, Code::Success, out).await; }, Err(err) => { handle_response(socket, Code::MethodNotAllowed, err).await; }, } } #[tokio::main] async fn main() { let config = Arc::new(bash::load_config()); let port = std::env::var("PORT") .unwrap_or_else(|_| String::from("8080")) .parse::() .unwrap_or_else(|_| 8080); let addr = format!("127.0.0.1:{port}"); let Ok(listener) = TcpListener::bind(&addr).await else { println!("failed to bind {addr}"); return }; println!("listening to tcp requests on {addr}"); loop { let Ok((socket, _)) = listener.accept().await else { println!("failed to accept new connection"); continue }; let config = config.clone(); tokio::spawn(async move { handle_connection(socket, config).await; }); } }