diff --git a/Cargo.lock b/Cargo.lock index c360f4e..bebc514 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11,6 +11,25 @@ dependencies = [ "winapi", ] +[[package]] +name = "async-mutex" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479db852db25d9dbf6204e6cb6253698f175c15726470f78af0d918e99d6156e" +dependencies = [ + "event-listener", +] + +[[package]] +name = "async-rwlock" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "261803dcc39ba9e72760ba6e16d0199b1eef9fc44e81bffabbebb9f5aea3906c" +dependencies = [ + "async-mutex", + "event-listener", +] + [[package]] name = "atty" version = "0.2.14" @@ -49,6 +68,40 @@ version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" +[[package]] +name = "cached" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f66c8f6ae13abbdc12268879b8070a441777b69157fb7653b4c7da7f2610c25d" +dependencies = [ + "async-mutex", + "async-rwlock", + "cached_proc_macro", + "cached_proc_macro_types", + "hashbrown", + "once_cell", + "thiserror", + "tokio", +] + +[[package]] +name = "cached_proc_macro" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc8de7b947777686ee8f3c11f4c3e58c296d6bc598d9b2286a80a9404a538ff8" +dependencies = [ + "cached_proc_macro_types", + "darling", + "quote", + "syn", +] + +[[package]] +name = "cached_proc_macro_types" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a4f925191b4367301851c6d99b09890311d74b0d43f274c0b34c86d308a3663" + [[package]] name = "cfg-if" version = "1.0.0" @@ -64,7 +117,7 @@ dependencies = [ "ansi_term", "atty", "bitflags", - "strsim", + "strsim 0.8.0", "textwrap", "unicode-width", "vec_map", @@ -102,6 +155,53 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "darling" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0d720b8683f8dd83c65155f0530560cba68cd2bf395f6513a483caee57ff7f4" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a340f241d2ceed1deb47ae36c4144b2707ec7dd0b649f894cb39bb595986324" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim 0.10.0", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72c41b3b7352feb3211a0d743dc5700a4e3b60f51bd2b368892d1e0f9a95f44b" +dependencies = [ + "darling_core", + "quote", + "syn", +] + +[[package]] +name = "event-listener" +version = "2.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77f3309417938f28bf8228fcff79a4a37103981e3e186d2ccd19c74b38f4eb71" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + [[package]] name = "fs2" version = "0.4.3" @@ -132,6 +232,12 @@ dependencies = [ "wasi", ] +[[package]] +name = "hashbrown" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c21d40587b92fa6a6c6e3c1bdbf87d75511db5672f9c93175574b3a00df1758" + [[package]] name = "heck" version = "0.3.3" @@ -150,6 +256,12 @@ dependencies = [ "libc", ] +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + [[package]] name = "instant" version = "0.1.12" @@ -164,6 +276,7 @@ name = "kchan" version = "0.1.0" dependencies = [ "bincode", + "cached", "lazy_static", "num_cpus", "rand", @@ -223,6 +336,12 @@ dependencies = [ "libc", ] +[[package]] +name = "once_cell" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87f3e037eac156d1775da914196f0f37741a274155e34a0b7e427c35d2a2ecb9" + [[package]] name = "parking_lot" version = "0.11.2" @@ -248,6 +367,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "pin-project-lite" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e280fbe77cc62c91527259e9442153f4688736748d24660126286329742b4c6c" + [[package]] name = "ppv-lite86" version = "0.2.15" @@ -399,6 +524,12 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + [[package]] name = "structopt" version = "0.3.25" @@ -472,6 +603,28 @@ dependencies = [ "num_cpus", ] +[[package]] +name = "tokio" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2af73ac49756f3f7c01172e34a23e5d0216f6c32333757c2c61feb2bbff5a5ee" +dependencies = [ + "num_cpus", + "pin-project-lite", + "tokio-macros", +] + +[[package]] +name = "tokio-macros" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b557f72f448c511a979e2564e55d74e6c4432fc96ff4f6241bc6bded342643b7" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "unicode-segmentation" version = "1.8.0" diff --git a/Cargo.toml b/Cargo.toml index e12b350..6bd75f8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,3 +15,4 @@ lazy_static = "1.4.0" sled = "0.34.7" bincode = "1.3.3" serde = { version = "1.0.136", features = ["derive"] } +cached = "0.33.0" diff --git a/src/http.rs b/src/http.rs index d37e990..81b38c5 100644 --- a/src/http.rs +++ b/src/http.rs @@ -1,9 +1,8 @@ use std::fmt; -use std::str::FromStr; +use std::str::{FromStr, Lines}; -const HTTP_VERSION: &str = "HTTP/1.1"; +const HTTP_VERSION: &'static str = "HTTP/1.1"; -// TODO use &str #[derive(Debug)] pub struct Request { pub method: String, @@ -26,7 +25,7 @@ pub enum Status { } impl Status { - pub fn get_message(&self) -> &'static str { + pub fn message(&self) -> &'static str { match self { Self::Ok => "OK", Self::NotFound => "NOT FOUND", @@ -34,16 +33,15 @@ impl Status { } } -// TODO implement this with display and enums // TODO proper errors impl FromStr for Request { type Err = String; fn from_str(s: &str) -> Result { - let mut iter = dbg!(s).lines(); - let mut first = iter.nth(0).unwrap().split_whitespace(); - let method = first.next().unwrap().to_string(); - let uri = first.next().unwrap().to_string(); + let mut iter = s.lines(); + let mut first = iter.next().unwrap().split_whitespace(); + let method = first.next().unwrap(); + let uri = first.next().unwrap(); let mut headers: Vec<(String, String)> = vec![]; for line in &mut iter { @@ -56,10 +54,10 @@ impl FromStr for Request { let body: String = iter.fold(String::new(), |a, b| format!("{}{}", a, b)); Ok(Self { - method, - uri, - headers, - body, + method: method.to_string(), + uri: uri.to_string(), + headers: headers, + body: body.to_string(), }) } } @@ -87,7 +85,7 @@ impl fmt::Display for Response { "{} {} {}\n{}\n\n{}", HTTP_VERSION, self.status as i32, - self.status.get_message(), + self.status.message(), headers, self.body, ) diff --git a/src/main.rs b/src/main.rs index 47c3c7d..bcd4460 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,20 +5,23 @@ use crate::http::{Request, Response, Status}; use crate::post::{Post, Thread}; use std::io::{Read, Write}; -use std::net::{TcpListener, TcpStream}; +use std::net::{TcpListener, TcpStream, SocketAddr}; use std::path::PathBuf; use std::str; use std::str::from_utf8; use std::sync::{Arc, Mutex, MutexGuard}; use bincode::{deserialize, serialize}; +use cached::proc_macro::cached; use lazy_static::lazy_static; use structopt::StructOpt; use threadpool::ThreadPool; +// statically linked index and favicon const INDEX: &'static str = include_str!("www/index.html"); const FAVICON: &'static [u8] = include_bytes!("www/favicon.ico"); +// represents command line arguments #[derive(StructOpt, Debug)] struct Opt { #[structopt(short, long, default_value = "8000")] @@ -41,43 +44,80 @@ lazy_static! { static ref OPT: Opt = Opt::from_args(); } -// TODO cache -//#[derive(Debug)] -//struct Cache<'a>(&'a str); - -fn handle(request: Request, database: Arc>) -> String { +/// top level handling of requests +fn handle(request: Request, database: Arc>, ip: SocketAddr) -> Response { let database = database.lock().unwrap(); match request.method.as_str() { "GET" => get(&request.uri, database), - "POST" => "HTTP/1.1 200 OK".to_string(), + "POST" => { + println!("{:?}", request); + Response::new(Status::Ok, vec![], "".to_string()) + }, // check admin hash - "DELETE" => "HTTP/1.1 200 OK".to_string(), - _ => "".to_string(), + "DELETE" => Response::new(Status::Ok, vec![], "".to_string()), + _ => Response::new(Status::Ok, vec![], "".to_string()), } } -fn get(path: &str, database: MutexGuard) -> String { +/// get "/" returns head of every thread +/// or returns posts from thread with id in uri +fn get(path: &str, database: MutexGuard) -> Response { match path { // list threads in this case "/" => { - let content = INDEX.to_string().replace( - "{}", - &*database + let content = content( + "index", + &database .iter() .map(|x| x.unwrap()) .map(|x| (x.0, deserialize::(&x.1).unwrap())) - .map(|x| x.1.head(from_utf8(&x.0).unwrap())) + .map(|x| x.1.head()) .fold(String::from(""), |a, b| format!("{}{}", a, b)), ); - Response::new(Status::Ok, vec![], content).to_string() + Response::new(Status::Ok, vec![], content) } // list specific thread here - s => "".to_string(), + // FIXME unwrap hell + s => { + let content = content( + s, + &deserialize::(&database.get(&s.as_bytes()).unwrap().unwrap()) + .unwrap() + .to_string() + ); + + Response::new(Status::Ok, vec![], content) + }, + } +} + +fn post(path: &str, database: MutexGuard) -> Response { + match path { + + // means we wish to create a new thread + "/" => { + // first we generate unique id + }, + + // means we wish to post in specific thread + s => { + + }, } } +fn delete(path: &str, database: Arc>) -> Response { + todo!(); +} + +fn content(name: &str, main: &str) -> String { + INDEX + .replace("{name}", &name) + .replace("{}", &main) +} + fn main() { // TODO do this without unwrap // bind listener to local adress and port @@ -94,7 +134,7 @@ fn main() { let mut stream = stream.unwrap(); let database = Arc::clone(&db); pool.execute(move || { - let ip = stream.peer_addr(); + let ip = stream.peer_addr().unwrap(); // TODO check if valid ip before reading request let mut buffer = [0; 1 << 10]; // 2 to the power of 10 // how do I implement images with this? @@ -107,7 +147,7 @@ fn main() { // handle request // add database and cache here - stream.write(handle(request, database).as_bytes()).unwrap(); + stream.write(handle(request, database, ip).to_string().as_bytes()).unwrap(); }); } } diff --git a/src/post.rs b/src/post.rs index fdc4301..01dc290 100644 --- a/src/post.rs +++ b/src/post.rs @@ -2,47 +2,83 @@ use std::fmt; use serde::{Deserialize, Serialize}; +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Image { + pub filename: String, + pub img: Box<[u8]>, +} + #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Post { pub id: u64, pub ip: [u8; 4], pub date: String, + pub img: Option, pub body: String, } +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Thread(Vec); + +impl Thread { + pub fn head(&self) -> String { + let first = self.0[0]; + format!( + "
{}
{}
", + first.id, + first.to_string(), + ) + } +} + +impl Post { + fn new(id: u64, ip: [u8; 4], date: String, img: Option, body: String) -> Self { + Self { + id, + ip, + date, + img, + body, + } + } +} + impl fmt::Display for Post { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, - "
{} {}

{}

", - self.id, self.date, self.body, + "
\ + \"img\"\ +
\ +
\ +
\ + {} \ + {} \ +
\ + \ +
\ +
{}
\ + {}\ +
\ +
", + self.id, self.date, self.id, + if let Some(s) = self.img {s.filename} else {"".to_string()}, + self.body, ) } } -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Thread { - pub topic: String, - pub posts: Vec, -} - impl fmt::Display for Thread { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let posts: String = self - .posts + .0 .iter() .fold(String::from(""), |a, b| format!("{}{}", a, b)); write!(f, "{}", posts) } } -impl Thread { - pub fn head(&self, id: &str) -> String { - format!( - "
{} {}
{}
", - id, - self.topic, - self.posts[0].to_string(), - ) - } -} diff --git a/src/www/index.html b/src/www/index.html index 33a5612..0510b7c 100644 --- a/src/www/index.html +++ b/src/www/index.html @@ -18,44 +18,7 @@
-
- img -
-
-
- { post_datetime }  - { post_id }  -
- -
-
{ image_filename }
-
{ post_title }
- { post_content } -
-
-
- img -
-
-
- { post_datetime }  - { post_id }  -
- -
-
{ image_filename }
-
{ post_title }
- { post_content } -
-
+ {}