From e08c368365d411f05db7550dbe3277f4a360b828 Mon Sep 17 00:00:00 2001 From: "Dawid J. Kubis" Date: Fri, 26 May 2023 16:21:30 +0200 Subject: [PATCH] Improved thread performance, database performance, isolated database interacion into specific functions, added ability to parse POST data efficiently, support for binary data (images), removed the url crate, added better errors, implemented From for Response, better http request parsing, commented out useless pieces of code --- Cargo.lock | 84 ++----------- Cargo.toml | 2 - README.md | 6 - src/errors.rs | 57 ++++++++- src/http.rs | 67 ++++++---- src/main.rs | 341 ++++++++++++++++++++++++++++++-------------------- src/post.rs | 50 ++++---- 7 files changed, 339 insertions(+), 268 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 674cb57..c9c49bd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -101,15 +101,6 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "form_urlencoded" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" -dependencies = [ - "percent-encoding", -] - [[package]] name = "fs2" version = "0.4.3" @@ -165,16 +156,6 @@ dependencies = [ "utf8-width", ] -[[package]] -name = "idna" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" -dependencies = [ - "unicode-bidi", - "unicode-normalization", -] - [[package]] name = "instant" version = "0.1.12" @@ -195,7 +176,6 @@ name = "kchan" version = "0.1.0" dependencies = [ "bincode", - "byteorder", "html-escape", "lazy_static", "log", @@ -206,7 +186,6 @@ dependencies = [ "structopt", "thiserror", "threadpool", - "url", "urlencoding", ] @@ -294,12 +273,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "percent-encoding" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" - [[package]] name = "proc-macro-error" version = "1.0.4" @@ -326,18 +299,18 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.58" +version = "1.0.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa1fb82fc0c281dd9671101b66b771ebbe1eaf967b96ac8740dcba4b70005ca8" +checksum = "6aeca18b86b413c660b781aa319e4e2648a3e6f9eadc9b47e9038e6fe9f3451b" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.27" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f4f29d145265ec1c483c7c654450edde0bfe043d3938d6972630663356d9500" +checksum = "1b9ab9c7eadfd8df19006f1cf1a4aed13540ed5cbc047010ece5826e10825488" dependencies = [ "proc-macro2", ] @@ -374,7 +347,7 @@ checksum = "8c805777e3930c8883389c602315a24224bcc738b63905ef87cd1420353ea93e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.16", + "syn 2.0.17", ] [[package]] @@ -453,9 +426,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.16" +version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6f671d4b5ffdb8eadec19c0ae67fe2639df8684bd7bc4b83d986b8db549cf01" +checksum = "45b6ddbb36c5b969c182aec3c4a0bce7df3fbad4b77114706a49aacc80567388" dependencies = [ "proc-macro2", "quote", @@ -497,7 +470,7 @@ checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.16", + "syn 2.0.17", ] [[package]] @@ -538,42 +511,12 @@ dependencies = [ "time-core", ] -[[package]] -name = "tinyvec" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" -dependencies = [ - "tinyvec_macros", -] - -[[package]] -name = "tinyvec_macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" - -[[package]] -name = "unicode-bidi" -version = "0.3.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" - [[package]] name = "unicode-ident" version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b15811caf2415fb889178633e7724bad2509101cde276048e013b9def5e51fa0" -[[package]] -name = "unicode-normalization" -version = "0.1.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" -dependencies = [ - "tinyvec", -] - [[package]] name = "unicode-segmentation" version = "1.10.1" @@ -586,17 +529,6 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" -[[package]] -name = "url" -version = "2.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" -dependencies = [ - "form_urlencoded", - "idna", - "percent-encoding", -] - [[package]] name = "urlencoding" version = "2.1.2" diff --git a/Cargo.toml b/Cargo.toml index 8de4b8c..58a6501 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,8 +18,6 @@ log = "0.4.17" simplelog = "0.12.1" html-escape = "0.2.13" urlencoding = "2.1.2" -byteorder = "1.4.3" -url = "2.3.1" #ctrlc = "3.3.1" #image = "0.24.6" diff --git a/README.md b/README.md index 66daa78..e22004d 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,4 @@ ### TODO: -1. proper logging -2. working threads and posts -3. add authorization and ip whitelist -4. parse http form `multipart/form-data` -5. add images 6. limit storage, maybe with pseudorandomness, find a way to clean old threads 7. better html and css 8. optimize/clean code, improve memory usage @@ -14,7 +9,6 @@ capacity - the least active thread will then be removed. + id's will be attached to every thread and every response based on the order of creation. -+ specific posts will be searchable under the `//` uri. ### POST usage diff --git a/src/errors.rs b/src/errors.rs index b548c14..4278819 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -1,3 +1,5 @@ +use crate::http::{Response, Status}; + use std::string::FromUtf8Error; use thiserror::Error; // TODO add NotFound and InternalServerError response here, @@ -9,26 +11,71 @@ pub enum RequestError { NotAForm, #[error("resource not found")] NotFound, + #[error("unused method")] + UnusedMethod, #[error("request not authorized, incorred hash: {0}")] NotAuthorized(String), #[error("urldecoding error")] UrlDecodeErr(#[from] FromUtf8Error), #[error("bad request")] BadRequest, + #[error("missing body in POST request")] + MissingBody, } #[derive(Debug, Error)] -pub enum InternalError { +pub enum DatabaseError { #[error("failed to read from database")] - DatabaseReadError, - #[error("failed to write to database")] - DatabaseWriteError, + SledError(#[from] sled::Error), + #[error("not found in database")] + NotInDb, + #[error("serialization error")] + SerError, } +pub enum InternalError {} + #[derive(Debug, Error)] pub enum HandlingError { #[error("client error: {0}")] ClientError(#[from] RequestError), #[error("server error: {0}")] - ServerError(#[from] InternalError), + ServerError(#[from] DatabaseError), + #[error("problem reading stream")] + Io(#[from] std::io::Error), +} + +impl From for Status { + fn from(err: DatabaseError) -> Self { + match err { + DatabaseError::SledError(e) => Status::InternalServerError, + DatabaseError::NotInDb => Status::BadRequest, + DatabaseError::SerError => Status::InternalServerError, + } + } +} + +impl From for Status { + fn from(err: RequestError) -> Self { + match err { + RequestError::NotAForm => Status::BadRequest, + RequestError::NotFound => Status::BadRequest, + RequestError::UnusedMethod => Status::BadRequest, + RequestError::NotAuthorized(_) => Status::Unauthorized, + RequestError::UrlDecodeErr(_) => Status::BadRequest, + RequestError::BadRequest => Status::BadRequest, + RequestError::MissingBody => Status::BadRequest, + } + } +} + +impl From for Response { + fn from(err: HandlingError) -> Self { + let status = match err { + HandlingError::ClientError(e) => Status::from(e), + HandlingError::ServerError(e) => Status::from(e), + HandlingError::Io(e) => Status::InternalServerError, + }; + Response::new(status, vec![], status.message().to_string()) + } } diff --git a/src/http.rs b/src/http.rs index 1f6d17c..6fc526c 100644 --- a/src/http.rs +++ b/src/http.rs @@ -8,17 +8,17 @@ const HTTP_VERSION: &'static str = "HTTP/1.1"; #[derive(Debug)] pub struct Request { - pub method: String, + pub method: Method, pub uri: String, pub headers: Vec<(String, String)>, - pub body: String, + pub body: Option>, } #[derive(Debug)] pub struct Response { pub status: Status, pub headers: Vec<(String, String)>, - pub body: String, + pub body: String, // make into Option> } #[derive(Debug, Clone, Copy)] @@ -31,6 +31,25 @@ pub enum Status { InternalServerError = 500, } +#[derive(Debug)] +pub enum Method { + Get, + Post, + Delete, +} + +impl FromStr for Method { + type Err = RequestError; + fn from_str(s: &str) -> Result { + match s { + "GET" => Ok(Self::Get), + "POST" => Ok(Self::Post), + "DELETE" => Ok(Self::Delete), + _ => Err(Self::Err::UnusedMethod), + } + } +} + impl Status { pub fn message(&self) -> &'static str { match self { @@ -58,34 +77,38 @@ impl Status { // } //} -impl FromStr for Request { - type Err = RequestError; +impl Request { + pub fn add_body(&mut self, body: Vec) { + self.body = Some(body); + } +} - fn from_str(s: &str) -> Result { +impl TryFrom> for Request { + type Error = RequestError; + + fn try_from(s: Vec) -> Result { //dbg!(&s); - let mut iter = s.lines(); + let mut iter = s.iter(); let mut first = iter .next() .ok_or(RequestError::BadRequest)? .split_whitespace(); - let method = first.next().ok_or(RequestError::BadRequest)?; - let uri = first.next().ok_or(RequestError::BadRequest)?; + let method: Method = first.next().ok_or(RequestError::BadRequest)?.parse()?; + let uri = first.next().ok_or(RequestError::BadRequest)?.to_string(); let mut headers: Vec<(String, String)> = vec![]; - for line in &mut iter { - if line.is_empty() { - break; - } - let line: Vec<&str> = line.split(":").take(2).collect(); - headers.push((line[0].into(), line[1].into())); + for line in iter { + let mut line = line.split(":").take(2); + let left = line.next().ok_or(RequestError::BadRequest)?.trim(); + let right = line.next().ok_or(RequestError::BadRequest)?.trim(); + headers.push((left.to_string(), right.to_string())); } - let body: String = iter.fold(String::new(), |a, b| format!("{}{}", a, b)); Ok(Self { - method: method.to_string(), - uri: uri.to_string(), + method, + uri, headers, - body: body.to_string(), + body: None, }) } } @@ -103,12 +126,6 @@ impl Response { } } -impl From for Response { - fn from(err: HandlingError) -> Self { - todo!(); - } -} - impl fmt::Display for Response { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let headers: String = self diff --git a/src/main.rs b/src/main.rs index 208a569..3d75b69 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,22 +4,24 @@ mod http; mod post; use crate::errors::{HandlingError, InternalError, RequestError}; -use crate::html::{FAQ, INDEX, STYLE, FAVICON}; +use crate::html::{FAQ, FAVICON, INDEX, STYLE}; use crate::http::{Request, Response, Status}; use crate::post::{Post, Thread}; use std::error::Error; -use std::io::{Read, Write}; +use std::io; +use std::io::{BufRead, BufReader, Read, Write}; use std::net::{SocketAddr, TcpListener, TcpStream}; use std::path::{Path, PathBuf}; use std::str; use std::str::from_utf8; -use std::sync::{Arc, Mutex, MutexGuard}; +use std::sync::{Arc, RwLock}; use std::thread::JoinHandle; use bincode::{deserialize, serialize}; -use byteorder::{BigEndian, LittleEndian, ReadBytesExt, WriteBytesExt}; +use errors::DatabaseError; use html_escape::encode_text; +use http::Method; use lazy_static::lazy_static; use log::*; use simplelog::*; @@ -27,6 +29,9 @@ use structopt::StructOpt; use threadpool::ThreadPool; use urlencoding::decode; +// use this instead of hard-coding +type ID_TYPE = u32; + // represents command line arguments #[derive(StructOpt, Debug)] struct Opt { @@ -45,129 +50,217 @@ struct Opt { lazy_static! { // parse command line arguments static ref OPT: Opt = Opt::from_args(); + static ref DB: sled::Db = sled::open(&OPT.database).expect("failed to open db"); + static ref THREADS: sled::Tree = DB.open_tree(b"threads").expect("failed to initialize threads"); + static ref POSTS: sled::Tree = DB.open_tree(b"posts").expect("failed to intialize posts"); } -#[derive(Debug)] -struct State { - db: sled::Db, - id_counter: u32, - // cache here? +/// increments the id stored inside the db +fn increment(old: Option<&[u8]>) -> Option> { + let number = match old { + Some(s) => { + let buf: [u8; 4] = s.try_into().unwrap(); + u32::from_be_bytes(buf) + 1 + // FIXME + } + None => 0, + }; + Some(number.to_be_bytes().to_vec()) } -unsafe impl Send for State {} -unsafe impl Sync for State {} +// DATABASE ACCESS FUNCTIONS +/// returns post from id +fn get_post(id: u32) -> Result { + POSTS + .get(id.to_be_bytes())? + .ok_or(DatabaseError::NotInDb) + .and_then(|x| deserialize::(&x).map_err(|_| DatabaseError::SerError)) +} -impl State { - fn new(db_name: impl AsRef) -> Self { - let mut s = Self { - db: sled::open(db_name).unwrap(), - id_counter: 0, - }; - s.init_id(); - s - } +/// returns thread from id +fn get_thread(id: u32) -> Result, DatabaseError> { + THREADS + .get(id.to_be_bytes())? + .ok_or(DatabaseError::NotInDb) + .and_then(|x| deserialize::(&x).map_err(|_| DatabaseError::SerError))? + .into_iter() + .map(|x| get_post(x)) + .collect() + // let thread = THREADS.get(id.to_be_bytes()).and_then() +} - fn next_id(&mut self) -> String { - // TODO improve this - // means the highest possible id is 999_999_999, then it loops back to zero - // NOTE this could technically be used to limit the storage required, - // might possibly be a good feature - self.id_counter = (self.id_counter + 1) % 1_000_000_000; - let width = u32::MAX.ilog10() as usize; // happens to be 9 in this case - //let mut v = vec![]; - //v.write_u32::(self.id_counter).unwrap() - //self.db.insert(b"id", v); - format!("{:0width$}", self.id_counter) - } - fn init_id(&mut self) { - self.id_counter = match self.db.get(b"id").unwrap() { - Some(s) => (&(*s)).read_u32::().unwrap(), - None => 0, - } - } +/// generates next id +fn next_id() -> Result { + DB.fetch_and_update(b"id", increment) + .map(|x| match x { + Some(s) => { + let buf: [u8; 4] = (*s).try_into().unwrap(); + u32::from_be_bytes(buf) + } + None => 0u32, + }) + .map_err(|e| DatabaseError::from(e)) } -/// top level handling of requests -fn handle(request: &str, state: Arc>) -> Response { - let request = match request.parse::() { - Ok(s) => s, - Err(e) => return Response::new(Status::BadRequest, vec![], e.to_string()), - }; - let state = state.lock().unwrap(); +/// returns current id +/// not used +fn get_id() -> Result { + let s = DB.get(b"id")?.unwrap(); + let buf: [u8; 4] = (*s).try_into().unwrap(); + Ok(u32::from_be_bytes(buf)) +} - match request.method.as_str() { - "GET" => match get(&request.uri, state) { - Ok(s) => s, - Err(e) => Response::new(Status::NotFound, vec![], dbg!(e).to_string()), - }, - "POST" => match post(request, state) { - Ok(s) => s, - Err(e) => Response::new(Status::Ok, vec![], "".to_string()), - }, - // check admin hash - "DELETE" => Response::new(Status::Ok, vec![], "".to_string()), - _ => Response::new(Status::Ok, vec![], RequestError::BadRequest.to_string()), +/// lists all threads +fn list_threads() -> Result, DatabaseError> { + THREADS + .iter() + .take_while(|x| x.is_ok()) + .map(|x| x.unwrap().0) + .map(|x| { + POSTS.get(x).map(|x| match x { + Some(s) => s, + None => { + error!("couldn't find thread op among posts"); + panic!("unrecoverable error"); + } + }) + }) + .map(|x| { + x.map_err(|e| DatabaseError::from(e)) + .and_then(|i| deserialize::(&i).map_err(|_| DatabaseError::SerError)) + }) + .collect() +} + +// NOTE worst out of everything, but I guess it's not so bad +fn add_post(mut post: Post, thread_id: u32) -> Result<(), DatabaseError> { + let id = next_id()?; + post.id = id; + let mut thread = deserialize::( + &THREADS + .get(thread_id.to_be_bytes())? + .ok_or(DatabaseError::NotInDb)?, + ) + .map_err(|_| DatabaseError::SerError)?; + thread.push(post.id); + let thread = serialize(&thread).map_err(|_| DatabaseError::SerError)?; + THREADS.insert(thread_id.to_be_bytes(), thread)?; + + POSTS.insert( + id.to_be_bytes(), + serialize(&post).map_err(|_| DatabaseError::SerError)?, + )?; + Ok(()) +} + +fn add_thread(mut post: Post) -> Result<(), DatabaseError> { + let id = next_id()?; + let thread = Thread::new(id); + post.id = id; + THREADS.insert( + id.to_be_bytes(), + serialize(&thread).map_err(|_| DatabaseError::SerError)?, + )?; + POSTS.insert( + id.to_be_bytes(), + serialize(&post).map_err(|_| DatabaseError::SerError)?, + )?; + Ok(()) +} + +// END DATABASE ACCESS FUNCTIONS + +/// top level handling of requests +fn handle(mut reader: BufReader<&mut TcpStream>) -> Result { + let mut request: Request = reader + .by_ref() + .lines() + .take_while(|l| l.is_ok()) + .map(|l| l.unwrap()) + .take_while(|l| !l.is_empty()) + .collect::>() + .try_into()?; + + if let Some(s) = request + .headers + .iter() + .find(|(a, b)| a.to_lowercase() == "content-length") + { + let body: Vec = reader + .bytes() + .take(s.1.parse::().unwrap()) + .collect::, io::Error>>() + .unwrap(); + // TODO test if it works + // FIXME deunwrap + // TODO handle body too large + request.add_body(body); + } + dbg!(&request); + + match request.method { + Method::Get => get(&request.uri), + Method::Post => post(request), + // TODO check admin hash + _ => Ok(Response::from(HandlingError::from( + RequestError::BadRequest, + ))), } } /// get "/" returns head of every thread /// or returns posts from thread with id in uri -fn get(path: &str, state: MutexGuard) -> Result { +fn get(path: &str) -> Result { match path { // list threads in this case "/" => { - let content = content( - "index", - &state - .db - .iter() - .map(|x| x.unwrap()) //FIXME unwraps - .map(|x| deserialize::(&x.1).unwrap().head()) // FIXME here too - .fold(String::from(""), |a, b| format!("{}{}", a, b)), - ); - - Ok(Response::new(Status::Ok, vec![], content)) + // FIXME absolute unwrap hell + let ops = list_threads()? + .iter() + .fold(String::from(""), |a, b| format!("{a}\n{b}")); + let c = content("index", &ops); + + Ok(Response::new(Status::Ok, vec![], c)) } // TODO favicon.ico "/css" => Ok(Response::new(Status::Ok, vec![], String::from(STYLE))), "/faq" => Ok(Response::new(Status::Ok, vec![], String::from(FAQ))), - //"/favicon.ico" => Ok(Response::new(Status::Ok, vec![("content-type", "image/x-icon")], FAVICON)), + // TODO favicon // list specific thread here // FIXME unwrap hell s => { - let content = content( - s, - &deserialize::( - &state - .db - .get(&s[1..]) - .map_err(|_| HandlingError::ServerError(InternalError::DatabaseReadError))? - .ok_or(HandlingError::ClientError(RequestError::NotFound))?, - ) + let id = s + .trim_start_matches("/") + .split("/") + .next() .unwrap() - .to_string(), - ); - - Ok(Response::new(Status::Ok, vec![], content)) + .parse::() + .map_err(|_| RequestError::NotFound)?; + let c = get_thread(id)? + .iter() + .fold(String::from(""), |a, b| format!("{a}\n{b}")); + let c = content(&id.to_string(), &c); + + Ok(Response::new(Status::Ok, vec![], c)) } } } -fn post(request: Request, mut state: MutexGuard) -> Result { - let content = encode_text(&request.body).into_owned(); +fn post(request: Request) -> Result { + let binding = &request.body.ok_or(RequestError::MissingBody)?; + let c = String::from_utf8_lossy(&binding); + let c = encode_text(&c).into_owned(); + + let post = Post::new(0, c); + match request.uri.as_str() { // means we wish to create a new thread "/" => { - // TODO fix unwrap - let id = state.next_id(); - let thread = Thread(vec![Post::new(&id, content)]); - - state.db.insert(&id, serialize(&thread).unwrap()).unwrap(); - - info!("Created new thread, id: {}", id); + add_thread(post)?; + info!("Created new thread"); // TODO add redirect Ok(Response::new( @@ -179,41 +272,33 @@ fn post(request: Request, mut state: MutexGuard) -> Result { - // first we increment id - let id = state.next_id(); - let post = Post::new(&id, content); - - let mut thread = deserialize::( - &state - .db - .remove(&s[1..]) - .map_err(|_| HandlingError::ServerError(InternalError::DatabaseWriteError))? - .ok_or(HandlingError::ClientError(RequestError::NotFound))?, - ) - .unwrap(); - - thread.0.push(post); + let id = s + .trim_start_matches("/") + .split("/") + .next() + .unwrap() + .parse::() + .map_err(|_| RequestError::NotFound)?; + add_post(post, id)?; - state - .db - .insert(&s[1..], serialize(&thread).unwrap()) - .unwrap(); - // make this less weird - info!("Added new post to thread {}", s); + info!("Added new post to thread {id}"); // TODO add redirect - Ok(Response::new(Status::SeeOther, vec![("location", s)], String::from(""))) + Ok(Response::new( + Status::SeeOther, + vec![("location", s)], + String::from(""), + )) } } } -fn delete(path: &str, database: Arc>) -> Response { +fn delete(path: &str) -> Response { todo!(); } -#[inline] fn content(name: &str, main: &str) -> String { - INDEX.replace("{name}", &name).replace("{}", &main) + INDEX.replace("{name}", name).replace("{}", main) } fn main() { @@ -229,10 +314,7 @@ fn main() { TerminalMode::Mixed, ColorChoice::Auto, ) - .unwrap(); - // open database - let state = Arc::new(Mutex::new(State::new(&OPT.database))); - // TODO setup cache + .expect("failed to setup logger"); // wait for requests for stream in listener.incoming() { @@ -244,23 +326,16 @@ fn main() { } }; - let state = Arc::clone(&state); pool.execute(move || { - let ip = stream.peer_addr().unwrap(); - // TODO check if allowed ip before reading request - let mut buffer = [0; 1 << 10]; // 2 to the power of 10 - // how do I implement images with this? - - stream.read(&mut buffer).unwrap(); + let reader = BufReader::new(&mut stream); - // NOTE possibly problematic when we cosider images - let text = str::from_utf8(&buffer).unwrap().trim_matches(0 as char); - // ^-- gets rid of null at the end of buffer + let response = match handle(reader) { + Ok(s) => s, + Err(e) => Response::from(dbg!(e)), + }; // handle request - stream - .write(handle(text, state).to_string().as_bytes()) - .unwrap(); + stream.write(response.to_string().as_bytes()).unwrap(); }); } } diff --git a/src/post.rs b/src/post.rs index eddf6d2..304f88a 100644 --- a/src/post.rs +++ b/src/post.rs @@ -10,29 +10,47 @@ use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Post { - pub id: String, + pub id: u32, // technically reduntant but whatever //pub img: Option, pub body: String, } #[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Thread(pub Vec); +pub struct Thread(Vec); + +impl IntoIterator for Thread { + type Item = u32; + type IntoIter = std::vec::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.0.into_iter() + } +} impl Thread { - pub fn head(&self) -> String { - let first = &self.0[0]; - format!( - "
{}
{}
", - first.id, - first.to_string(), - ) + pub fn new(op: u32) -> Self { + Thread(vec![op]) + } + pub fn push(&mut self, thing: u32) { + self.0.push(thing) } } +//impl Thread { +// pub fn head(&self) -> String { +// let first = &self.0[0]; +// format!( +// "
{}
{}
", +// first.id, +// first.to_string(), +// ) +// } +//} + impl Post { - pub fn new(id: &str, body: String) -> Self { + pub fn new(id: u32, body: String) -> Self { Self { - id: id.to_string(), + id, //img, body, } @@ -75,13 +93,3 @@ impl fmt::Display for Post { ) } } - -impl fmt::Display for Thread { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let posts: String = self - .0 - .iter() - .fold(String::from(""), |a, b| format!("{}{}", a, b)); - write!(f, "{}", posts) - } -}