summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/bash.rs41
-rw-r--r--src/http/code.rs8
-rw-r--r--src/http/header.rs7
-rw-r--r--src/http/method.rs16
-rw-r--r--src/http/mod.rs1
-rw-r--r--src/http/request.rs4
-rw-r--r--src/http/response.rs8
-rw-r--r--src/main.rs70
-rw-r--r--src/script.rs94
-rw-r--r--src/server.rs51
10 files changed, 181 insertions, 119 deletions
diff --git a/src/bash.rs b/src/bash.rs
deleted file mode 100644
index 059909f..0000000
--- a/src/bash.rs
+++ /dev/null
@@ -1,41 +0,0 @@
-use std::{env, collections::HashMap, fs::read_to_string, process::{exit, Command}};
-
-pub fn load_config() -> HashMap<String, String> {
-
- let config_path = env::var("CONFIG_PATH").unwrap_or_else(|_| String::from("config"));
- let config = match read_to_string(&config_path) {
- Ok(data) => data,
- Err(err) => {
- eprintln!("cannot load '{config_path}': {err}");
- exit(1);
- },
- };
-
- let mut map = HashMap::new();
- let lines = config.split("\n").into_iter();
-
- for line in lines {
- let mut parts = line.trim().split(" ");
- let Some(route) = parts.next() else { continue };
- let Some(script) = parts.next() else { continue };
-
- println!("adding entry {route} => {script}");
- map.insert(route.to_owned(), script.to_owned());
- }
-
- map
-}
-
-pub fn handle_script(script: &str, body: Option<&String>) -> Result<String, String> {
- let mut command = Command::new(script);
- if let Some(body) = body {
- command.args([body]);
- }
-
- let output = match command.output() {
- Ok(o) => o,
- Err(err) => return Err(format!("{err}")),
- };
-
- Ok(String::from_utf8_lossy(&output.stdout).into_owned())
-}
diff --git a/src/http/code.rs b/src/http/code.rs
deleted file mode 100644
index ba47282..0000000
--- a/src/http/code.rs
+++ /dev/null
@@ -1,8 +0,0 @@
-#[derive(Debug, Clone)]
-#[allow(dead_code)]
-pub enum Code {
- Success = 200,
- MethodNotAllowed = 405,
- TooManyRequests = 429,
- InternalServerError = 500,
-}
diff --git a/src/http/header.rs b/src/http/header.rs
index e6fc552..03e4303 100644
--- a/src/http/header.rs
+++ b/src/http/header.rs
@@ -57,9 +57,7 @@ impl HeaderMap {
string
}
- pub fn serialize(lines: &mut Split<char>) -> Self {
-
- let mut headers = Self::new();
+ pub fn serialize(&mut self, lines: &mut Split<char>) {
loop {
let Some(header) = lines.next() else { break };
@@ -69,10 +67,9 @@ impl HeaderMap {
let Some(key) = parts.next() else { continue };
let Some(value) = parts.next() else { continue };
- headers.put(Header::new(key.trim(), value.trim()));
+ self.put(Header::new(key.trim(), value.trim()));
}
- headers
}
pub fn new() -> Self {
diff --git a/src/http/method.rs b/src/http/method.rs
index 55cea65..cecafbf 100644
--- a/src/http/method.rs
+++ b/src/http/method.rs
@@ -1,4 +1,4 @@
-#[derive(Debug, Clone)]
+#[derive(Debug, Clone, Hash, Eq, PartialEq)]
pub enum Method {
Get,
Head,
@@ -26,5 +26,19 @@ impl Method {
_ => None
}
}
+
+ pub fn deserialize(&self) -> &str {
+ match self {
+ Method::Get => "GET",
+ Method::Head => "HEAD",
+ Method::Post => "POST",
+ Method::Put => "PUT",
+ Method::Delete => "DELETE",
+ Method::Connect => "CONNECT",
+ Method::Options => "OPTIONS",
+ Method::Trace => "TRACE",
+ Method::Patch => "PATCH",
+ }
+ }
}
diff --git a/src/http/mod.rs b/src/http/mod.rs
index 62151bb..02ba89d 100644
--- a/src/http/mod.rs
+++ b/src/http/mod.rs
@@ -1,4 +1,3 @@
-pub mod code;
pub mod method;
pub mod uri;
pub mod request;
diff --git a/src/http/request.rs b/src/http/request.rs
index 5ba72c9..286efa3 100644
--- a/src/http/request.rs
+++ b/src/http/request.rs
@@ -39,7 +39,9 @@ impl Request {
return None
};
- let headers = HeaderMap::serialize(&mut lines);
+ let mut headers = HeaderMap::new();
+ headers.serialize(&mut lines);
+
let body: String = lines.collect();
Some(Self {
diff --git a/src/http/response.rs b/src/http/response.rs
index 850f41e..85430a4 100644
--- a/src/http/response.rs
+++ b/src/http/response.rs
@@ -1,8 +1,8 @@
-use super::{code::Code, header::{HeaderMap, Header}};
+use super::{header::{HeaderMap, Header}};
#[derive(Debug, Clone)]
pub struct Response {
- pub status: Code,
+ pub status: u16,
pub headers: HeaderMap,
pub body: Option<String>
}
@@ -20,7 +20,7 @@ impl Response {
headers.put(Header::new("Server", "bashttp"));
return Self {
- status: Code::Success,
+ status: 200,
headers,
body: None
}
@@ -29,7 +29,7 @@ impl Response {
pub fn deserialize(&self) -> String {
let mut string = String::new();
- string += &format!("HTTP/1.1 {}\n", self.status.clone() as u16);
+ string += &format!("HTTP/1.1 {}\n", self.status);
string += &self.headers.deserialize();
if let Some(body) = &self.body {
diff --git a/src/main.rs b/src/main.rs
index 9cf8e90..0f7dc59 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,69 +1,23 @@
-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;
+use std::{env, sync::Arc};
+use tokio::net::TcpListener;
+use crate::server::handle_connection;
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;
-}
+mod script;
+mod server;
-async fn handle_connection(mut socket: TcpStream, config: Arc<HashMap<String, String>>) {
-
- let mut buf = [0; 1204];
+#[tokio::main]
+async fn main() {
- 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);
+ let config = match script::Config::load() {
+ Ok(config) => Arc::new(config),
+ Err(err) => {
+ eprintln!("failed to load config: {err}");
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")
+ let port = env::var("PORT")
.unwrap_or_else(|_| String::from("8080"))
.parse::<u16>()
.unwrap_or_else(|_| 8080);
diff --git a/src/script.rs b/src/script.rs
new file mode 100644
index 0000000..369fbf8
--- /dev/null
+++ b/src/script.rs
@@ -0,0 +1,94 @@
+use std::{env, collections::HashMap, fs::read_to_string, process::Command, result::Result, error::Error};
+
+use crate::http::{response::Response, method::Method};
+
+pub struct Script {
+ path: String
+}
+
+impl Script {
+ fn new(path: String) -> Self {
+ Self { path }
+ }
+
+ pub fn run(&self, body: Option<&String>) -> Result<Response, std::io::Error> {
+ let mut res = Response::new();
+
+ let mut command = Command::new(&self.path);
+ if let Some(body) = body {
+ command.args([body]);
+ }
+
+ let output = match command.output() {
+ Ok(output) => output,
+ Err(err) => return Err(err)
+ };
+
+ let status = output.status.code().unwrap_or(500) as u16;
+
+ let response = String::from_utf8_lossy(&output.stdout).into_owned();
+ let mut lines = response.split('\n').into_iter();
+
+ res.headers.serialize(&mut lines);
+
+ let body: String = lines.collect::<Vec<&str>>().join("\n");
+ if body.len() > 0 {
+ res.body = Some(body);
+ }
+
+ res.status = status;
+
+ Ok(res)
+ }
+}
+
+pub struct Config {
+ map: HashMap<String, Script>
+}
+
+impl Config {
+
+ fn key(method: &Method, route: &str) -> String {
+ format!("{}:{}", method.deserialize(), route)
+ }
+
+ pub fn load() -> Result<Self, Box<dyn Error>> {
+
+ let config_path = env::var("CONFIG_PATH").unwrap_or_else(|_| String::from("config"));
+ let config = read_to_string(&config_path)?;
+
+ let mut map = HashMap::new();
+ let lines = config.split("\n").into_iter();
+
+ for (i, line) in lines.enumerate() {
+ let mut parts = line.split_whitespace();
+
+ let Some(method_str) = parts.next() else { continue };
+ let method = Method::serialize(&method_str)
+ .ok_or(format!("config line {i} has invalid http method: {method_str}"))?;
+
+ let route = parts.next()
+ .ok_or(format!("config line {i} missing http route"))?;
+
+ let script = parts.next()
+ .ok_or(format!{"config line {i} missing script path"})?;
+
+ let key = Self::key(&method, route);
+ let value = Script::new(script.to_owned());
+
+ println!("adding script {script} for {} {route}", method.deserialize());
+ map.insert(key, value);
+ }
+
+ if map.len() < 1 {
+ return Err("cannot run server, config is empty".into())
+ }
+
+ Ok(Self { map })
+ }
+
+ pub fn get(&self, method: &Method, route: &str) -> Option<&Script> {
+ self.map.get(&Self::key(method, route))
+ }
+
+}
diff --git a/src/server.rs b/src/server.rs
new file mode 100644
index 0000000..4b1f2b3
--- /dev/null
+++ b/src/server.rs
@@ -0,0 +1,51 @@
+use std::sync::Arc;
+use tokio::{net::TcpStream, io::{AsyncWriteExt, AsyncReadExt}};
+use crate::{http::{header::Header, response::Response, request::Request}, script::Config};
+
+async fn send_response(mut socket: TcpStream, code: u16, 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;
+}
+
+pub async fn handle_connection(mut socket: TcpStream, config: Arc<Config>) {
+
+ 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.method, &req.uri.route) else {
+ send_response(socket, 405, "Method Not Allowed".to_owned()).await;
+ return
+ };
+
+ match script.run(req.body.as_ref()) {
+ Ok(res) => {
+ let res_str = res.deserialize();
+ let _ = socket.write(res_str.as_bytes()).await;
+ },
+ Err(err) => {
+ println!("{err}");
+ send_response(socket, 500, format!("{err}")).await;
+ },
+ }
+}