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<HandlingError> for Response, better http request parsing,
commented out useless pieces of code
doctorpavel
Dawid J. Kubis 2 years ago
parent 12635886f9
commit e08c368365

84
Cargo.lock generated

@ -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"

@ -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"

@ -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 `/<thread_id>/<response_id>` uri.
### POST usage

@ -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<DatabaseError> for Status {
fn from(err: DatabaseError) -> Self {
match err {
DatabaseError::SledError(e) => Status::InternalServerError,
DatabaseError::NotInDb => Status::BadRequest,
DatabaseError::SerError => Status::InternalServerError,
}
}
}
impl From<RequestError> 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<HandlingError> 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())
}
}

@ -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<Vec<u8>>,
}
#[derive(Debug)]
pub struct Response {
pub status: Status,
pub headers: Vec<(String, String)>,
pub body: String,
pub body: String, // make into Option<Vec<u8>>
}
#[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<Self, Self::Err> {
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<u8>) {
self.body = Some(body);
}
}
fn from_str(s: &str) -> Result<Self, Self::Err> {
impl TryFrom<Vec<String>> for Request {
type Error = RequestError;
fn try_from(s: Vec<String>) -> Result<Self, Self::Error> {
//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;
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 line: Vec<&str> = line.split(":").take(2).collect();
headers.push((line[0].into(), line[1].into()));
}
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<HandlingError> 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

@ -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<Vec<u8>> {
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<Post, DatabaseError> {
POSTS
.get(id.to_be_bytes())?
.ok_or(DatabaseError::NotInDb)
.and_then(|x| deserialize::<Post>(&x).map_err(|_| DatabaseError::SerError))
}
impl State {
fn new(db_name: impl AsRef<Path>) -> 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<Vec<Post>, DatabaseError> {
THREADS
.get(id.to_be_bytes())?
.ok_or(DatabaseError::NotInDb)
.and_then(|x| deserialize::<Thread>(&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::<BigEndian>(self.id_counter).unwrap()
//self.db.insert(b"id", v);
format!("{:0width$}", self.id_counter)
/// generates next id
fn next_id() -> Result<u32, DatabaseError> {
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)
}
fn init_id(&mut self) {
self.id_counter = match self.db.get(b"id").unwrap() {
Some(s) => (&(*s)).read_u32::<BigEndian>().unwrap(),
None => 0,
None => 0u32,
})
.map_err(|e| DatabaseError::from(e))
}
/// returns current id
/// not used
fn get_id() -> Result<u32, DatabaseError> {
let s = DB.get(b"id")?.unwrap();
let buf: [u8; 4] = (*s).try_into().unwrap();
Ok(u32::from_be_bytes(buf))
}
/// lists all threads
fn list_threads() -> Result<Vec<Post>, 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::<Post>(&i).map_err(|_| DatabaseError::SerError))
})
.collect()
}
/// top level handling of requests
fn handle(request: &str, state: Arc<Mutex<State>>) -> Response {
let request = match request.parse::<Request>() {
Ok(s) => s,
Err(e) => return Response::new(Status::BadRequest, vec![], e.to_string()),
};
let state = state.lock().unwrap();
// 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::<Thread>(
&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(())
}
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()),
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<Response, HandlingError> {
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::<Vec<_>>()
.try_into()?;
if let Some(s) = request
.headers
.iter()
.find(|(a, b)| a.to_lowercase() == "content-length")
{
let body: Vec<u8> = reader
.bytes()
.take(s.1.parse::<usize>().unwrap())
.collect::<Result<Vec<u8>, 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<State>) -> Result<Response, HandlingError> {
fn get(path: &str) -> Result<Response, HandlingError> {
match path {
// list threads in this case
"/" => {
let content = content(
"index",
&state
.db
// FIXME absolute unwrap hell
let ops = list_threads()?
.iter()
.map(|x| x.unwrap()) //FIXME unwraps
.map(|x| deserialize::<Thread>(&x.1).unwrap().head()) // FIXME here too
.fold(String::from(""), |a, b| format!("{}{}", a, b)),
);
.fold(String::from(""), |a, b| format!("{a}\n{b}"));
let c = content("index", &ops);
Ok(Response::new(Status::Ok, vec![], content))
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::<Thread>(
&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(),
);
.parse::<u32>()
.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![], content))
Ok(Response::new(Status::Ok, vec![], c))
}
}
}
fn post(request: Request, mut state: MutexGuard<State>) -> Result<Response, HandlingError> {
let content = encode_text(&request.body).into_owned();
fn post(request: Request) -> Result<Response, HandlingError> {
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<State>) -> Result<Response, Hand
// means we wish to post in specific thread
s => {
// first we increment id
let id = state.next_id();
let post = Post::new(&id, content);
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);
let id = s
.trim_start_matches("/")
.split("/")
.next()
.unwrap()
.parse::<u32>()
.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<Mutex<sled::Db>>) -> 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();
});
}
}

@ -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<Image>,
pub body: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Thread(pub Vec<Post>);
pub struct Thread(Vec<u32>);
impl IntoIterator for Thread {
type Item = u32;
type IntoIter = std::vec::IntoIter<Self::Item>;
fn into_iter(self) -> Self::IntoIter {
self.0.into_iter()
}
}
impl Thread {
pub fn head(&self) -> String {
let first = &self.0[0];
format!(
"<article><div><span class=\"info\">{}</span></div>{}</article>",
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!(
// "<article><div><span class=\"info\">{}</span></div>{}</article>",
// 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)
}
}

Loading…
Cancel
Save