diff --git a/Cargo.lock b/Cargo.lock index bebc514..6dbf748 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -108,6 +108,19 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chrono" +version = "0.4.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" +dependencies = [ + "libc", + "num-integer", + "num-traits", + "time", + "winapi", +] + [[package]] name = "clap" version = "2.33.3" @@ -278,9 +291,11 @@ dependencies = [ "bincode", "cached", "lazy_static", + "log", "num_cpus", "rand", "serde", + "simplelog", "sled", "structopt", "thiserror", @@ -326,6 +341,25 @@ dependencies = [ "autocfg", ] +[[package]] +name = "num-integer" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" +dependencies = [ + "autocfg", +] + [[package]] name = "num_cpus" version = "1.13.0" @@ -496,6 +530,17 @@ dependencies = [ "syn", ] +[[package]] +name = "simplelog" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1348164456f72ca0116e4538bdaabb0ddb622c7d9f16387c725af3e96d6001c" +dependencies = [ + "chrono", + "log", + "termcolor", +] + [[package]] name = "sled" version = "0.34.7" @@ -565,6 +610,15 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "termcolor" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" +dependencies = [ + "winapi-util", +] + [[package]] name = "textwrap" version = "0.11.0" @@ -603,6 +657,16 @@ dependencies = [ "num_cpus", ] +[[package]] +name = "time" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "tokio" version = "1.17.0" @@ -677,6 +741,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" diff --git a/Cargo.toml b/Cargo.toml index 6bd75f8..81ede53 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,3 +16,6 @@ sled = "0.34.7" bincode = "1.3.3" serde = { version = "1.0.136", features = ["derive"] } cached = "0.33.0" +log = "0.4.14" +simplelog = "0.11.2" + diff --git a/src/errors.rs b/src/errors.rs index e69de29..544e615 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -0,0 +1,25 @@ +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum RequestError { + #[error("request unable to be parsed as a form")] + NotAForm, + #[error("request not authorized, incorred hash: {0}")] + NotAuthorized(String), +} + +#[derive(Debug, Error)] +pub enum InternalError { + #[error("failed to read from database")] + DatabaseReadError, + #[error("failed to write to database")] + DatabaseWriteError, +} + +#[derive(Debug, Error)] +pub enum HandlingError { + #[error("client error")] + ClientError(#[from] RequestError), + #[error("server error")] + ServerError(#[from] InternalError), +} diff --git a/src/http.rs b/src/http.rs index 8b75b3f..61d732d 100644 --- a/src/http.rs +++ b/src/http.rs @@ -2,6 +2,8 @@ use std::collections::HashMap; use std::fmt; use std::str::{FromStr, Lines}; +use crate::errors::RequestError; + const HTTP_VERSION: &'static str = "HTTP/1.1"; #[derive(Debug)] @@ -23,6 +25,9 @@ pub struct Response { pub enum Status { Ok = 200, NotFound = 404, + BadRequest = 400, + Unauthorized = 401, + InternalServerError = 500, } impl Status { @@ -30,20 +35,22 @@ impl Status { match self { Self::Ok => "OK", Self::NotFound => "NOT FOUND", + Self::BadRequest => "BAD REQUEST", + Self::Unauthorized => "UNAUTHORIZED", + Self::InternalServerError => "INTERNAL SERVER ERROR", } } } impl Request { - pub fn form(&self) -> HashMap<&str, &str> { + pub fn form(&self) -> Result, RequestError> { let mut hashmap = HashMap::new(); - self.body.split("&") - .map(|x| x.split("=")) - .for_each( - |mut x| {hashmap.insert(x.next().unwrap(), x.next().unwrap());} - // fix unwrap hell - ); - hashmap + self.body.split("&").map(|x| x.split("=")).for_each( + |mut x| { + hashmap.insert(x.next().unwrap(), x.next().unwrap()); + }, // fix unwrap hell + ); + Ok(hashmap) } } @@ -106,4 +113,3 @@ impl fmt::Display for Response { ) } } - diff --git a/src/main.rs b/src/main.rs index 4947e62..bedb90b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,11 +1,13 @@ +mod errors; mod http; mod post; +use crate::errors::RequestError; use crate::http::{Request, Response, Status}; use crate::post::{Post, Thread}; use std::io::{Read, Write}; -use std::net::{TcpListener, TcpStream, SocketAddr}; +use std::net::{SocketAddr, TcpListener, TcpStream}; use std::path::PathBuf; use std::str; use std::str::from_utf8; @@ -32,17 +34,14 @@ struct Opt { #[structopt(short, long)] admin_hash: Option, - #[structopt(short, long, default_value = "data.sled")] + #[structopt(short, long, default_value = "data")] database: PathBuf, - - #[structopt(short, long, default_value = "10000")] - max_posts: usize, } // get command line arguments and make them static // safe because they're read-only lazy_static! { - // first parse command line arguments + // parse command line arguments static ref OPT: Opt = Opt::from_args(); } @@ -66,10 +65,11 @@ fn handle(request: Request, state: Arc>) -> Response { let state = state.lock().unwrap(); match request.method.as_str() { - "GET" => get(&request.uri, state), - "POST" => { - post(request, state) + "GET" => match get(&request.uri, state) { + Ok(s) => s, + Err(e) => Response::new(Status::NotFound, vec![], "".to_string()), }, + "POST" => post(request, state), // check admin hash "DELETE" => Response::new(Status::Ok, vec![], "".to_string()), _ => Response::new(Status::Ok, vec![], "".to_string()), @@ -78,7 +78,7 @@ fn handle(request: Request, state: Arc>) -> Response { /// get "/" returns head of every thread /// or returns posts from thread with id in uri -fn get(path: &str, state: MutexGuard) -> Response { +fn get(path: &str, state: MutexGuard) -> Result { match path { // list threads in this case "/" => { @@ -93,13 +93,12 @@ fn get(path: &str, state: MutexGuard) -> Response { .fold(String::from(""), |a, b| format!("{}{}", a, b)), ); - Response::new(Status::Ok, vec![], content) + Ok(Response::new(Status::Ok, vec![], content)) } // TODO /css /faq /favicon.ico + "/css" => Ok(Response::new(Status::Ok, vec![], String::from(STYLE))), - "/css" => Response::new(Status::Ok, vec![], String::from(STYLE)), - - "/faq" => Response::new(Status::Ok, vec![], String::from(FAQ)), + "/faq" => Ok(Response::new(Status::Ok, vec![], String::from(FAQ))), // list specific thread here // FIXME unwrap hell @@ -108,39 +107,36 @@ fn get(path: &str, state: MutexGuard) -> Response { s, &deserialize::(&state.db.get(&s.as_bytes()).unwrap().unwrap()) .unwrap() - .to_string() + .to_string(), ); - Response::new(Status::Ok, vec![], content) - }, + Ok(Response::new(Status::Ok, vec![], content)) + } } } fn post(request: Request, mut state: MutexGuard) -> Response { match request.uri.as_str() { - // means we wish to create a new thread - "/" => { + "/" => { // first we increment id let id = state.next_id(); let content = dbg!(request.form()); - let thread = Thread(vec![ - Post::new(id, None, String::from(*content.get(":D").unwrap())) - ]); + let thread = Thread(vec![Post::new( + id, + None, + String::from(*content.get(":D").unwrap()), + )]); - state.db.insert( - id.to_ne_bytes(), - serialize(&thread) - .unwrap() - ); + state + .db + .insert(id.to_ne_bytes(), serialize(&thread).unwrap()); Response::new(Status::Ok, vec![], String::from("")) - }, + } // means we wish to post in specific thread - s => { - Response::new(Status::Ok, vec![], String::from("")) - }, + s => Response::new(Status::Ok, vec![], String::from("")), } } @@ -150,29 +146,33 @@ fn delete(path: &str, database: Arc>) -> Response { #[inline] fn content(name: &str, main: &str) -> String { - INDEX - .replace("{name}", &name) - .replace("{}", &main) + INDEX.replace("{name}", &name).replace("{}", &main) } fn main() { - // TODO do this without unwrap // bind listener to local adress and port - let listener = TcpListener::bind(("127.0.0.1", OPT.port)).unwrap(); + let listener = + TcpListener::bind(("127.0.0.1", OPT.port)).expect("Cannot bind to specified port."); // create threadpool for incoming requests let pool = ThreadPool::new(num_cpus::get()); + // setup logger + // open database - let state = Arc::new(Mutex::new( - State { - db: sled::open(&OPT.database).unwrap(), - id_counter: 0 - } - )); + let state = Arc::new(Mutex::new(State { + db: sled::open(&OPT.database).unwrap(), + id_counter: 0, + })); // TODO setup cache // wait for requests for stream in listener.incoming() { - let mut stream = stream.unwrap(); + let mut stream = match stream { + Ok(s) => s, + Err(e) => { + eprintln!("failed connection: {}", e); + continue; + } + }; let state = Arc::clone(&state); pool.execute(move || { let ip = stream.peer_addr().unwrap(); @@ -187,7 +187,9 @@ fn main() { let request: Request = text.parse().unwrap(); // handle request - stream.write(handle(request, state).to_string().as_bytes()).unwrap(); + stream + .write(handle(request, state).to_string().as_bytes()) + .unwrap(); }); } } diff --git a/src/post.rs b/src/post.rs index f70dac3..33b09c0 100644 --- a/src/post.rs +++ b/src/post.rs @@ -16,9 +16,7 @@ pub struct Post { } #[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Thread( - pub Vec -); +pub struct Thread(pub Vec); impl Thread { pub fn head(&self) -> String { @@ -33,11 +31,7 @@ impl Thread { impl Post { pub fn new(id: u64, img: Option, body: String) -> Self { - Self { - id, - img, - body, - } + Self { id, img, body } } } @@ -63,8 +57,14 @@ impl fmt::Display for Post { {}\ \ ", - self.id, "", self.id, - if let Some(s) = &self.img {&s.filename} else {""}, + self.id, + "", + self.id, + if let Some(s) = &self.img { + &s.filename + } else { + "" + }, self.body, ) } @@ -79,4 +79,3 @@ impl fmt::Display for Thread { write!(f, "{}", posts) } } -