|
|
@ -1,8 +1,12 @@
|
|
|
|
|
|
|
|
#![feature(int_log)]
|
|
|
|
|
|
|
|
|
|
|
|
mod errors;
|
|
|
|
mod errors;
|
|
|
|
|
|
|
|
mod html;
|
|
|
|
mod http;
|
|
|
|
mod http;
|
|
|
|
mod post;
|
|
|
|
mod post;
|
|
|
|
|
|
|
|
|
|
|
|
use crate::errors::{HandlingError, InternalError, RequestError};
|
|
|
|
use crate::errors::{HandlingError, InternalError, RequestError};
|
|
|
|
|
|
|
|
use crate::html::{FAQ, INDEX, STYLE};
|
|
|
|
use crate::http::{Request, Response, Status};
|
|
|
|
use crate::http::{Request, Response, Status};
|
|
|
|
use crate::post::{Post, Thread};
|
|
|
|
use crate::post::{Post, Thread};
|
|
|
|
|
|
|
|
|
|
|
@ -13,21 +17,15 @@ use std::path::PathBuf;
|
|
|
|
use std::str;
|
|
|
|
use std::str;
|
|
|
|
use std::str::from_utf8;
|
|
|
|
use std::str::from_utf8;
|
|
|
|
use std::sync::{Arc, Mutex, MutexGuard};
|
|
|
|
use std::sync::{Arc, Mutex, MutexGuard};
|
|
|
|
|
|
|
|
use std::thread::JoinHandle;
|
|
|
|
|
|
|
|
|
|
|
|
use bincode::{deserialize, serialize};
|
|
|
|
use bincode::{deserialize, serialize};
|
|
|
|
use cached::proc_macro::cached;
|
|
|
|
|
|
|
|
use lazy_static::lazy_static;
|
|
|
|
use lazy_static::lazy_static;
|
|
|
|
use log::*;
|
|
|
|
use log::*;
|
|
|
|
use simplelog::*;
|
|
|
|
use simplelog::*;
|
|
|
|
use structopt::StructOpt;
|
|
|
|
use structopt::StructOpt;
|
|
|
|
use threadpool::ThreadPool;
|
|
|
|
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");
|
|
|
|
|
|
|
|
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)]
|
|
|
|
struct Opt {
|
|
|
|
struct Opt {
|
|
|
@ -51,15 +49,20 @@ lazy_static! {
|
|
|
|
#[derive(Debug)]
|
|
|
|
#[derive(Debug)]
|
|
|
|
struct State {
|
|
|
|
struct State {
|
|
|
|
db: sled::Db,
|
|
|
|
db: sled::Db,
|
|
|
|
id_counter: u64,
|
|
|
|
id_counter: u32,
|
|
|
|
// cache here?
|
|
|
|
// cache here?
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
impl State {
|
|
|
|
impl State {
|
|
|
|
fn next_id(&mut self) -> u64 {
|
|
|
|
fn next_id(&mut self) -> String {
|
|
|
|
let id = self.id_counter;
|
|
|
|
let id = self.id_counter;
|
|
|
|
self.id_counter += 1;
|
|
|
|
// TODO improve this
|
|
|
|
id
|
|
|
|
// 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.log10() as usize; // happens to be 9 in this case
|
|
|
|
|
|
|
|
format!("{id:0width$}")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
@ -74,12 +77,15 @@ fn handle(request: &str, state: Arc<Mutex<State>>) -> Response {
|
|
|
|
match request.method.as_str() {
|
|
|
|
match request.method.as_str() {
|
|
|
|
"GET" => match get(&request.uri, state) {
|
|
|
|
"GET" => match get(&request.uri, state) {
|
|
|
|
Ok(s) => s,
|
|
|
|
Ok(s) => s,
|
|
|
|
Err(e) => Response::new(Status::NotFound, vec![], e.to_string()),
|
|
|
|
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()),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
"POST" => post(request, state),
|
|
|
|
|
|
|
|
// check admin hash
|
|
|
|
// check admin hash
|
|
|
|
"DELETE" => Response::new(Status::Ok, vec![], "".to_string()),
|
|
|
|
"DELETE" => Response::new(Status::Ok, vec![], "".to_string()),
|
|
|
|
_ => Response::new(Status::Ok, vec![], "".to_string()),
|
|
|
|
_ => Response::new(Status::Ok, vec![], RequestError::BadRequest.to_string()),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
@ -115,7 +121,7 @@ fn get(path: &str, state: MutexGuard<State>) -> Result<Response, HandlingError>
|
|
|
|
&deserialize::<Thread>(
|
|
|
|
&deserialize::<Thread>(
|
|
|
|
&state
|
|
|
|
&state
|
|
|
|
.db
|
|
|
|
.db
|
|
|
|
.get(&s.as_bytes())
|
|
|
|
.get(&s[1..])
|
|
|
|
.map_err(|_| HandlingError::ServerError(InternalError::DatabaseReadError))?
|
|
|
|
.map_err(|_| HandlingError::ServerError(InternalError::DatabaseReadError))?
|
|
|
|
.ok_or(HandlingError::ClientError(RequestError::NotFound))?,
|
|
|
|
.ok_or(HandlingError::ClientError(RequestError::NotFound))?,
|
|
|
|
)
|
|
|
|
)
|
|
|
@ -128,29 +134,54 @@ fn get(path: &str, state: MutexGuard<State>) -> Result<Response, HandlingError>
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fn post(request: Request, mut state: MutexGuard<State>) -> Response {
|
|
|
|
fn post(request: Request, mut state: MutexGuard<State>) -> Result<Response, HandlingError> {
|
|
|
|
match request.uri.as_str() {
|
|
|
|
match request.uri.as_str() {
|
|
|
|
// means we wish to create a new thread
|
|
|
|
// means we wish to create a new thread
|
|
|
|
"/" => {
|
|
|
|
"/" => {
|
|
|
|
// first we increment id
|
|
|
|
// TODO fix unwrap
|
|
|
|
let id = state.next_id();
|
|
|
|
|
|
|
|
let content = dbg!(request.form().unwrap());
|
|
|
|
let content = dbg!(request.form().unwrap());
|
|
|
|
|
|
|
|
let id = state.next_id();
|
|
|
|
let thread = Thread(vec![Post::new(
|
|
|
|
let thread = Thread(vec![Post::new(
|
|
|
|
id,
|
|
|
|
&id,
|
|
|
|
None,
|
|
|
|
|
|
|
|
String::from(*content.get("content").unwrap()),
|
|
|
|
String::from(*content.get("content").unwrap()),
|
|
|
|
)]);
|
|
|
|
)]);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
state.db.insert(&id, serialize(&thread).unwrap()).unwrap();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
info!("Created new thread, id: {}", id);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// TODO add redirect
|
|
|
|
|
|
|
|
Ok(Response::new(Status::Ok, vec![], String::from("")))
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// means we wish to post in specific thread
|
|
|
|
|
|
|
|
s => {
|
|
|
|
|
|
|
|
// first we increment id
|
|
|
|
|
|
|
|
let id = state.next_id();
|
|
|
|
|
|
|
|
let content = dbg!(request.form().unwrap());
|
|
|
|
|
|
|
|
let post = Post::new(&id, String::from(*content.get("content").unwrap()));
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let mut thread = deserialize::<Thread>(
|
|
|
|
|
|
|
|
&state
|
|
|
|
|
|
|
|
.db
|
|
|
|
|
|
|
|
.remove(&s[1..])
|
|
|
|
|
|
|
|
.map_err(|_| HandlingError::ServerError(InternalError::DatabaseWriteError))?
|
|
|
|
|
|
|
|
.ok_or(HandlingError::ClientError(RequestError::NotFound))?,
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
thread.0.push(post);
|
|
|
|
|
|
|
|
|
|
|
|
state
|
|
|
|
state
|
|
|
|
.db
|
|
|
|
.db
|
|
|
|
.insert(id.to_ne_bytes(), serialize(&thread).unwrap())
|
|
|
|
.insert(&s[1..], serialize(&thread).unwrap())
|
|
|
|
.unwrap();
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
// make this less weird
|
|
|
|
|
|
|
|
info!("Added new post to thread {}", s);
|
|
|
|
|
|
|
|
|
|
|
|
Response::new(Status::Ok, vec![], String::from(""))
|
|
|
|
// TODO add redirect
|
|
|
|
|
|
|
|
Ok(Response::new(Status::Ok, vec![], String::from("")))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// means we wish to post in specific thread
|
|
|
|
|
|
|
|
s => Response::new(Status::Ok, vec![], String::from("")),
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
@ -171,7 +202,7 @@ fn main() {
|
|
|
|
let pool = ThreadPool::new(num_cpus::get());
|
|
|
|
let pool = ThreadPool::new(num_cpus::get());
|
|
|
|
// setup logger
|
|
|
|
// setup logger
|
|
|
|
TermLogger::init(
|
|
|
|
TermLogger::init(
|
|
|
|
LevelFilter::Warn,
|
|
|
|
LevelFilter::Info,
|
|
|
|
Config::default(),
|
|
|
|
Config::default(),
|
|
|
|
TerminalMode::Mixed,
|
|
|
|
TerminalMode::Mixed,
|
|
|
|
ColorChoice::Auto,
|
|
|
|
ColorChoice::Auto,
|
|
|
@ -189,10 +220,11 @@ fn main() {
|
|
|
|
let mut stream = match stream {
|
|
|
|
let mut stream = match stream {
|
|
|
|
Ok(s) => s,
|
|
|
|
Ok(s) => s,
|
|
|
|
Err(e) => {
|
|
|
|
Err(e) => {
|
|
|
|
eprintln!("failed connection: {}", e);
|
|
|
|
warn!("Failed connection: {}", e);
|
|
|
|
continue;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
let state = Arc::clone(&state);
|
|
|
|
let state = Arc::clone(&state);
|
|
|
|
pool.execute(move || {
|
|
|
|
pool.execute(move || {
|
|
|
|
let ip = stream.peer_addr().unwrap();
|
|
|
|
let ip = stream.peer_addr().unwrap();
|
|
|
@ -202,6 +234,7 @@ fn main() {
|
|
|
|
|
|
|
|
|
|
|
|
stream.read(&mut buffer).unwrap();
|
|
|
|
stream.read(&mut buffer).unwrap();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// NOTE possibly problematic when we cosider images
|
|
|
|
let text = str::from_utf8(&buffer).unwrap().trim_matches(0 as char);
|
|
|
|
let text = str::from_utf8(&buffer).unwrap().trim_matches(0 as char);
|
|
|
|
// ^-- gets rid of null at the end of buffer
|
|
|
|
// ^-- gets rid of null at the end of buffer
|
|
|
|
|
|
|
|
|
|
|
|