and he stood there, atop a hill, and he heard the screams of threads panicking,

seen he saw the sweating of poisoned mutexes,
and he felt the smell of reset connections.
that's when he knew he was in hell.
doctorpavel
Dawid J. Kubis 3 years ago
parent d8761eee20
commit 3a738ba31f

@ -1,3 +1,4 @@
use std::collections::HashMap;
use std::fmt; use std::fmt;
use std::str::{FromStr, Lines}; use std::str::{FromStr, Lines};
@ -33,11 +34,25 @@ impl Status {
} }
} }
impl Request {
pub fn form(&self) -> HashMap<&str, &str> {
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
}
}
// TODO proper errors // TODO proper errors
impl FromStr for Request { impl FromStr for Request {
type Err = String; type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> { fn from_str(s: &str) -> Result<Self, Self::Err> {
dbg!(&s);
let mut iter = s.lines(); let mut iter = s.lines();
let mut first = iter.next().unwrap().split_whitespace(); let mut first = iter.next().unwrap().split_whitespace();
let method = first.next().unwrap(); let method = first.next().unwrap();
@ -91,3 +106,4 @@ impl fmt::Display for Response {
) )
} }
} }

@ -20,6 +20,8 @@ use threadpool::ThreadPool;
// statically linked index and favicon // statically linked index and favicon
const INDEX: &'static str = include_str!("www/index.html"); const INDEX: &'static str = include_str!("www/index.html");
const FAVICON: &'static [u8] = include_bytes!("www/favicon.ico"); const FAVICON: &'static [u8] = include_bytes!("www/favicon.ico");
const STYLE: &'static str = include_str!("www/style.css");
const FAQ: &'static str = include_str!("www/faq.html");
// represents command line arguments // represents command line arguments
#[derive(StructOpt, Debug)] #[derive(StructOpt, Debug)]
@ -44,15 +46,29 @@ lazy_static! {
static ref OPT: Opt = Opt::from_args(); static ref OPT: Opt = Opt::from_args();
} }
#[derive(Debug)]
struct State {
db: sled::Db,
id_counter: u64,
// cache here?
}
impl State {
fn next_id(&mut self) -> u64 {
let id = self.id_counter;
self.id_counter += 1;
id
}
}
/// top level handling of requests /// top level handling of requests
fn handle(request: Request, database: Arc<Mutex<sled::Db>>, ip: SocketAddr) -> Response { fn handle(request: Request, state: Arc<Mutex<State>>) -> Response {
let database = database.lock().unwrap(); let state = state.lock().unwrap();
match request.method.as_str() { match request.method.as_str() {
"GET" => get(&request.uri, database), "GET" => get(&request.uri, state),
"POST" => { "POST" => {
println!("{:?}", request); post(request, state)
Response::new(Status::Ok, vec![], "".to_string())
}, },
// check admin hash // check admin hash
"DELETE" => Response::new(Status::Ok, vec![], "".to_string()), "DELETE" => Response::new(Status::Ok, vec![], "".to_string()),
@ -62,13 +78,14 @@ fn handle(request: Request, database: Arc<Mutex<sled::Db>>, ip: SocketAddr) -> R
/// get "/" returns head of every thread /// get "/" returns head of every thread
/// or returns posts from thread with id in uri /// or returns posts from thread with id in uri
fn get(path: &str, database: MutexGuard<sled::Db>) -> Response { fn get(path: &str, state: MutexGuard<State>) -> Response {
match path { match path {
// list threads in this case // list threads in this case
"/" => { "/" => {
let content = content( let content = content(
"index", "index",
&database &state
.db
.iter() .iter()
.map(|x| x.unwrap()) .map(|x| x.unwrap())
.map(|x| (x.0, deserialize::<Thread>(&x.1).unwrap())) .map(|x| (x.0, deserialize::<Thread>(&x.1).unwrap()))
@ -78,12 +95,18 @@ fn get(path: &str, database: MutexGuard<sled::Db>) -> Response {
Response::new(Status::Ok, vec![], content) Response::new(Status::Ok, vec![], content)
} }
// TODO /css /faq /favicon.ico
"/css" => Response::new(Status::Ok, vec![], String::from(STYLE)),
"/faq" => Response::new(Status::Ok, vec![], String::from(FAQ)),
// list specific thread here // list specific thread here
// FIXME unwrap hell // FIXME unwrap hell
s => { s => {
let content = content( let content = content(
s, s,
&deserialize::<Thread>(&database.get(&s.as_bytes()).unwrap().unwrap()) &deserialize::<Thread>(&state.db.get(&s.as_bytes()).unwrap().unwrap())
.unwrap() .unwrap()
.to_string() .to_string()
); );
@ -93,17 +116,30 @@ fn get(path: &str, database: MutexGuard<sled::Db>) -> Response {
} }
} }
fn post(path: &str, database: MutexGuard<sled::Db>) -> Response { fn post(request: Request, mut state: MutexGuard<State>) -> Response {
match path { match request.uri.as_str() {
// means we wish to create a new thread // means we wish to create a new thread
"/" => { "/" => {
// first we generate unique id // 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()))
]);
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 // means we wish to post in specific thread
s => { s => {
Response::new(Status::Ok, vec![], String::from(""))
}, },
} }
} }
@ -112,6 +148,7 @@ fn delete(path: &str, database: Arc<Mutex<sled::Db>>) -> Response {
todo!(); todo!();
} }
#[inline]
fn content(name: &str, main: &str) -> String { fn content(name: &str, main: &str) -> String {
INDEX INDEX
.replace("{name}", &name) .replace("{name}", &name)
@ -125,17 +162,21 @@ fn main() {
// create threadpool for incoming requests // create threadpool for incoming requests
let pool = ThreadPool::new(num_cpus::get()); let pool = ThreadPool::new(num_cpus::get());
// open database // open database
let db = Arc::new(Mutex::new(sled::open(&OPT.database).unwrap())); let state = Arc::new(Mutex::new(
State {
db: sled::open(&OPT.database).unwrap(),
id_counter: 0
}
));
// TODO setup cache // TODO setup cache
//let cache = Arc::new(Cache(""));
// wait for requests // wait for requests
for stream in listener.incoming() { for stream in listener.incoming() {
let mut stream = stream.unwrap(); let mut stream = stream.unwrap();
let database = Arc::clone(&db); let state = Arc::clone(&state);
pool.execute(move || { pool.execute(move || {
let ip = stream.peer_addr().unwrap(); let ip = stream.peer_addr().unwrap();
// TODO check if valid ip before reading request // TODO check if allowed ip before reading request
let mut buffer = [0; 1 << 10]; // 2 to the power of 10 let mut buffer = [0; 1 << 10]; // 2 to the power of 10
// how do I implement images with this? // how do I implement images with this?
@ -146,8 +187,7 @@ fn main() {
let request: Request = text.parse().unwrap(); let request: Request = text.parse().unwrap();
// handle request // handle request
// add database and cache here stream.write(handle(request, state).to_string().as_bytes()).unwrap();
stream.write(handle(request, database, ip).to_string().as_bytes()).unwrap();
}); });
} }
} }

@ -11,18 +11,18 @@ pub struct Image {
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Post { pub struct Post {
pub id: u64, pub id: u64,
pub ip: [u8; 4],
pub date: String,
pub img: Option<Image>, pub img: Option<Image>,
pub body: String, pub body: String,
} }
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Thread(Vec<Post>); pub struct Thread(
pub Vec<Post>
);
impl Thread { impl Thread {
pub fn head(&self) -> String { pub fn head(&self) -> String {
let first = self.0[0]; let first = &self.0[0];
format!( format!(
"<article><div><span class=\"info\">{}</span></div>{}</article>", "<article><div><span class=\"info\">{}</span></div>{}</article>",
first.id, first.id,
@ -32,11 +32,9 @@ impl Thread {
} }
impl Post { impl Post {
fn new(id: u64, ip: [u8; 4], date: String, img: Option<Image>, body: String) -> Self { pub fn new(id: u64, img: Option<Image>, body: String) -> Self {
Self { Self {
id, id,
ip,
date,
img, img,
body, body,
} }
@ -65,8 +63,8 @@ impl fmt::Display for Post {
{}\ {}\
</div>\ </div>\
</article>", </article>",
self.id, self.date, self.id, self.id, "", self.id,
if let Some(s) = self.img {s.filename} else {"".to_string()}, if let Some(s) = &self.img {&s.filename} else {""},
self.body, self.body,
) )
} }

@ -3,7 +3,7 @@
<head> <head>
<title>kchan - {name}</title> <title>kchan - {name}</title>
<link rel="icon" type="image/x-icon" href="favicon.ico"> <link rel="icon" type="image/x-icon" href="favicon.ico">
<link rel="stylesheet" href="style.css"> <link rel="stylesheet" href="css">
</head> </head>
<body> <body>
<header> <header>
@ -17,6 +17,14 @@
</div> </div>
</header> </header>
<main> <main>
<div class="wrap slim">
<h1>Create new thread</h1>
<form method="post">
<textarea name=":D" rows="10" placeholder="Content" required></textarea>
<input type="submit" value="Create">
</form>
</div>
<hr>
<div class="wrap"> <div class="wrap">
{} {}
</div> </div>

Loading…
Cancel
Save