Added simple multithreading
This commit is contained in:
parent
fdfeae97b2
commit
1b039b0494
393
src/main.rs
393
src/main.rs
@ -5,6 +5,8 @@ use std::{
|
|||||||
io::{prelude::*, BufReader, Error, ErrorKind, Result},
|
io::{prelude::*, BufReader, Error, ErrorKind, Result},
|
||||||
net::{TcpListener, TcpStream},
|
net::{TcpListener, TcpStream},
|
||||||
path::Path,
|
path::Path,
|
||||||
|
sync::{Arc, Mutex},
|
||||||
|
thread,
|
||||||
};
|
};
|
||||||
|
|
||||||
use data_stream::{
|
use data_stream::{
|
||||||
@ -43,12 +45,13 @@ fn fail(mut stream: TcpStream) {
|
|||||||
let _ = writeln!(stream, "Page not found!");
|
let _ = writeln!(stream, "Page not found!");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
struct Comment {
|
struct Comment {
|
||||||
name: Box<str>,
|
name: Box<str>,
|
||||||
text: Box<str>,
|
text: Box<str>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default, Clone)]
|
||||||
struct SiteInfo {
|
struct SiteInfo {
|
||||||
comments: Vec<Comment>,
|
comments: Vec<Comment>,
|
||||||
visits: usize,
|
visits: usize,
|
||||||
@ -111,7 +114,7 @@ impl<S: SizeSettings> FromStream<S> for SiteInfo {
|
|||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
struct Context {
|
struct Context {
|
||||||
infos: HashMap<Box<str>, SiteInfo>,
|
infos: HashMap<Box<str>, Arc<Mutex<SiteInfo>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Context {
|
impl Context {
|
||||||
@ -192,205 +195,223 @@ impl Context {
|
|||||||
|
|
||||||
use Entry::*;
|
use Entry::*;
|
||||||
let info = match self.infos.entry(relative_path.clone().into_boxed_str()) {
|
let info = match self.infos.entry(relative_path.clone().into_boxed_str()) {
|
||||||
Occupied(o) => o.into_mut(),
|
Occupied(o) => o.get().clone(),
|
||||||
Vacant(v) => v.insert(
|
Vacant(v) => v
|
||||||
File::open(&data_path)
|
.insert(Arc::new(Mutex::new(
|
||||||
.map(|mut file| {
|
File::open(&data_path)
|
||||||
from_stream::<PortableSettings, _, _>(&mut file).unwrap_or_default()
|
.map(|mut file| {
|
||||||
})
|
from_stream::<PortableSettings, _, _>(&mut file).unwrap_or_default()
|
||||||
.unwrap_or_default(),
|
})
|
||||||
),
|
.unwrap_or_default(),
|
||||||
|
)))
|
||||||
|
.clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
info.handle_connection(
|
thread::spawn(move || {
|
||||||
stream,
|
handle_relative_connection(
|
||||||
&request.body,
|
info,
|
||||||
&relative_path,
|
stream,
|
||||||
&path,
|
&request.body,
|
||||||
&pk_path,
|
&relative_path,
|
||||||
pki_path.as_ref().map(|path| path.as_ref()),
|
&path,
|
||||||
&data_path,
|
&pk_path,
|
||||||
start_level,
|
pki_path.as_ref().map(|path| path.as_ref()),
|
||||||
)
|
&data_path,
|
||||||
|
start_level,
|
||||||
|
)
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SiteInfo {
|
fn handle_relative_connection(
|
||||||
fn handle_connection(
|
info: Arc<Mutex<SiteInfo>>,
|
||||||
&mut self,
|
mut stream: TcpStream,
|
||||||
mut stream: TcpStream,
|
body: &str,
|
||||||
body: &str,
|
relative_path: &str,
|
||||||
relative_path: &str,
|
path: &Path,
|
||||||
path: &Path,
|
pk_path: &Path,
|
||||||
pk_path: &Path,
|
pki_path: Option<&Path>,
|
||||||
pki_path: Option<&Path>,
|
data_path: &Path,
|
||||||
data_path: &Path,
|
start_level: usize,
|
||||||
start_level: usize,
|
) {
|
||||||
) {
|
let mut name = None;
|
||||||
let mut name = None;
|
let mut text = None;
|
||||||
let mut text = None;
|
|
||||||
|
|
||||||
for entry in body.split('&') {
|
let mut up = false;
|
||||||
let Some((key, input)) = entry.split_once('=') else {
|
let mut down = false;
|
||||||
continue;
|
|
||||||
};
|
|
||||||
|
|
||||||
match key {
|
for entry in body.split('&') {
|
||||||
"name" => {
|
let Some((key, input)) = entry.split_once('=') else {
|
||||||
let name_value = input.replace('+', " ");
|
continue;
|
||||||
let decoded_name = percent_decode_str(&name_value).decode_utf8_lossy();
|
|
||||||
eprintln!("Received comment by \"{decoded_name}\":");
|
|
||||||
name = Some(
|
|
||||||
decoded_name
|
|
||||||
.replace('&', "&")
|
|
||||||
.replace('<', "<")
|
|
||||||
.replace('>', ">")
|
|
||||||
.into(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
"text" => {
|
|
||||||
let text_value = input.replace('+', " ");
|
|
||||||
let decoded_text = percent_decode_str(&text_value).decode_utf8_lossy();
|
|
||||||
for line in decoded_text.lines() {
|
|
||||||
eprintln!(" {line}");
|
|
||||||
}
|
|
||||||
eprintln!();
|
|
||||||
let mut text_buf = Vec::new();
|
|
||||||
convert(decoded_text.lines(), &mut text_buf);
|
|
||||||
text = Some(
|
|
||||||
std::str::from_utf8(text_buf.as_slice())
|
|
||||||
.unwrap_or_default()
|
|
||||||
.into(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
"up" => self.up += 1,
|
|
||||||
"down" => self.down += 1,
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let (Some(name), Some(text)) = (name, text) {
|
|
||||||
self.comments.push(Comment { name, text });
|
|
||||||
}
|
|
||||||
|
|
||||||
self.visits += 1;
|
|
||||||
|
|
||||||
if let Ok(mut file) = File::create(data_path) {
|
|
||||||
if to_stream::<PortableSettings, _, _>(self, &mut file).is_err() {
|
|
||||||
eprintln!("Error saving data!");
|
|
||||||
eprintln!();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let _ = write!(stream, "HTTP/1.1 200 OK\r\n");
|
|
||||||
let _ = write!(stream, "Content-Type: text/html; charset=\"utf-8\"\r\n");
|
|
||||||
let _ = write!(stream, "\r\n");
|
|
||||||
|
|
||||||
let _ = writeln!(
|
|
||||||
stream,
|
|
||||||
"<p>👁️{} 💖️{} 💔️{}</p>",
|
|
||||||
self.visits, self.up, self.down
|
|
||||||
);
|
|
||||||
|
|
||||||
let title = relative_path
|
|
||||||
.rsplit_once('/')
|
|
||||||
.map(|(_, title)| title)
|
|
||||||
.unwrap_or(relative_path);
|
|
||||||
if !title.is_empty() {
|
|
||||||
let _ = writeln!(stream, "<h1>{title}</h1>");
|
|
||||||
}
|
|
||||||
|
|
||||||
let handle_entry = |mut entry: &str, output: &mut TcpStream, level: usize| {
|
|
||||||
let level = level + 1;
|
|
||||||
let mut path = path.to_path_buf();
|
|
||||||
if let Some((real_entry, _)) = entry.split_once(':') {
|
|
||||||
entry = real_entry
|
|
||||||
}
|
|
||||||
path.push(if relative_path.is_empty() {
|
|
||||||
format!("{entry}.pki")
|
|
||||||
} else {
|
|
||||||
format!("{relative_path}/{entry}.pki")
|
|
||||||
});
|
|
||||||
|
|
||||||
let Ok(file) = File::open(path) else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
let _ = writeln!(
|
|
||||||
output,
|
|
||||||
"<h{level}><a href=\"{relative_path}/{entry}\">{entry}</a></h{level}>"
|
|
||||||
);
|
|
||||||
convert_subheader(
|
|
||||||
BufReader::new(file)
|
|
||||||
.lines()
|
|
||||||
.map(|line| line.unwrap_or_default()),
|
|
||||||
output,
|
|
||||||
level,
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Ok(pk_file) = File::open(pk_path) {
|
match key {
|
||||||
convert_extended(
|
"name" => {
|
||||||
BufReader::new(pk_file)
|
let name_value = input.replace('+', " ");
|
||||||
|
let decoded_name = percent_decode_str(&name_value).decode_utf8_lossy();
|
||||||
|
eprintln!("Received comment by \"{decoded_name}\":");
|
||||||
|
name = Some(
|
||||||
|
decoded_name
|
||||||
|
.replace('&', "&")
|
||||||
|
.replace('<', "<")
|
||||||
|
.replace('>', ">")
|
||||||
|
.into(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
"text" => {
|
||||||
|
let text_value = input.replace('+', " ");
|
||||||
|
let decoded_text = percent_decode_str(&text_value).decode_utf8_lossy();
|
||||||
|
for line in decoded_text.lines() {
|
||||||
|
eprintln!(" {line}");
|
||||||
|
}
|
||||||
|
eprintln!();
|
||||||
|
let mut text_buf = Vec::new();
|
||||||
|
convert(decoded_text.lines(), &mut text_buf);
|
||||||
|
text = Some(
|
||||||
|
std::str::from_utf8(text_buf.as_slice())
|
||||||
|
.unwrap_or_default()
|
||||||
|
.into(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
"up" => up = true,
|
||||||
|
"down" => down = true,
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let info = if let Ok(mut info) = info.lock() {
|
||||||
|
if let (Some(name), Some(text)) = (name, text) {
|
||||||
|
info.comments.push(Comment { name, text });
|
||||||
|
}
|
||||||
|
if up {
|
||||||
|
info.up += 1;
|
||||||
|
}
|
||||||
|
if down {
|
||||||
|
info.down += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
info.visits += 1;
|
||||||
|
|
||||||
|
info.clone()
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Ok(mut file) = File::create(data_path) {
|
||||||
|
if to_stream::<PortableSettings, _, _>(&info, &mut file).is_err() {
|
||||||
|
eprintln!("Error saving data!");
|
||||||
|
eprintln!();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let _ = write!(stream, "HTTP/1.1 200 OK\r\n");
|
||||||
|
let _ = write!(stream, "Content-Type: text/html; charset=\"utf-8\"\r\n");
|
||||||
|
let _ = write!(stream, "\r\n");
|
||||||
|
|
||||||
|
let _ = writeln!(
|
||||||
|
stream,
|
||||||
|
"<p>👁️{} 💖️{} 💔️{}</p>",
|
||||||
|
info.visits, info.up, info.down
|
||||||
|
);
|
||||||
|
|
||||||
|
let title = relative_path
|
||||||
|
.rsplit_once('/')
|
||||||
|
.map(|(_, title)| title)
|
||||||
|
.unwrap_or(relative_path);
|
||||||
|
if !title.is_empty() {
|
||||||
|
let _ = writeln!(stream, "<h1>{title}</h1>");
|
||||||
|
}
|
||||||
|
|
||||||
|
let handle_entry = |mut entry: &str, output: &mut TcpStream, level: usize| {
|
||||||
|
let level = level + 1;
|
||||||
|
let mut path = path.to_path_buf();
|
||||||
|
if let Some((real_entry, _)) = entry.split_once(':') {
|
||||||
|
entry = real_entry
|
||||||
|
}
|
||||||
|
path.push(if relative_path.is_empty() {
|
||||||
|
format!("{entry}.pki")
|
||||||
|
} else {
|
||||||
|
format!("{relative_path}/{entry}.pki")
|
||||||
|
});
|
||||||
|
|
||||||
|
let Ok(file) = File::open(path) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let _ = writeln!(
|
||||||
|
output,
|
||||||
|
"<h{level}><a href=\"{relative_path}/{entry}\">{entry}</a></h{level}>"
|
||||||
|
);
|
||||||
|
convert_subheader(
|
||||||
|
BufReader::new(file)
|
||||||
|
.lines()
|
||||||
|
.map(|line| line.unwrap_or_default()),
|
||||||
|
output,
|
||||||
|
level,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Ok(pk_file) = File::open(pk_path) {
|
||||||
|
convert_extended(
|
||||||
|
BufReader::new(pk_file)
|
||||||
|
.lines()
|
||||||
|
.map(|line| line.unwrap_or_default()),
|
||||||
|
&mut stream,
|
||||||
|
Settings::default()
|
||||||
|
.with_handler(handle_entry)
|
||||||
|
.with_start_level(start_level)
|
||||||
|
.with_use_textboxes(true),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
unreachable!();
|
||||||
|
}
|
||||||
|
|
||||||
|
let parent_path = relative_path
|
||||||
|
.rsplit_once('/')
|
||||||
|
.map(|(path, _)| path)
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
let _ = writeln!(stream, "<hr>");
|
||||||
|
let _ = writeln!(stream, "<a href=\"/{parent_path}\"><< Back</a>");
|
||||||
|
|
||||||
|
if let Some(pki_path) = pki_path {
|
||||||
|
if let Ok(pki_file) = File::open(pki_path) {
|
||||||
|
let _ = writeln!(stream, "<h1>Description</h1>");
|
||||||
|
|
||||||
|
convert_subheader(
|
||||||
|
BufReader::new(pki_file)
|
||||||
.lines()
|
.lines()
|
||||||
.map(|line| line.unwrap_or_default()),
|
.map(|line| line.unwrap_or_default()),
|
||||||
&mut stream,
|
&mut stream,
|
||||||
Settings::default()
|
1,
|
||||||
.with_handler(handle_entry)
|
|
||||||
.with_start_level(start_level)
|
|
||||||
.with_use_textboxes(true),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let _ = writeln!(stream, "<hr>");
|
||||||
|
let _ = writeln!(stream, "<a href=\"/{parent_path}\"><< Back</a>");
|
||||||
} else {
|
} else {
|
||||||
unreachable!();
|
unreachable!();
|
||||||
}
|
}
|
||||||
|
|
||||||
let parent_path = relative_path
|
|
||||||
.rsplit_once('/')
|
|
||||||
.map(|(path, _)| path)
|
|
||||||
.unwrap_or_default();
|
|
||||||
|
|
||||||
let _ = writeln!(stream, "<hr>");
|
|
||||||
let _ = writeln!(stream, "<a href=\"/{parent_path}\"><< Back</a>");
|
|
||||||
|
|
||||||
if let Some(pki_path) = pki_path {
|
|
||||||
if let Ok(pki_file) = File::open(pki_path) {
|
|
||||||
let _ = writeln!(stream, "<h1>Description</h1>");
|
|
||||||
|
|
||||||
convert_subheader(
|
|
||||||
BufReader::new(pki_file)
|
|
||||||
.lines()
|
|
||||||
.map(|line| line.unwrap_or_default()),
|
|
||||||
&mut stream,
|
|
||||||
1,
|
|
||||||
);
|
|
||||||
|
|
||||||
let _ = writeln!(stream, "<hr>");
|
|
||||||
let _ = writeln!(stream, "<a href=\"/{parent_path}\"><< Back</a>");
|
|
||||||
} else {
|
|
||||||
unreachable!();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let html = html! {
|
|
||||||
h1 { "Comments" }
|
|
||||||
form method="POST" {
|
|
||||||
input type="text" name="name" value="anon" placeholder="Name";
|
|
||||||
br;
|
|
||||||
textarea rows="5" cols="60" name="text" placeholder="Enter comment..." {}
|
|
||||||
br;
|
|
||||||
input type="submit" value="Send!";
|
|
||||||
}
|
|
||||||
form method="POST" {
|
|
||||||
input type="submit" value="💖️" name="up";
|
|
||||||
input type="submit" value="💔️" name="down";
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let _ = stream.write_all(html.into_string().as_bytes());
|
|
||||||
|
|
||||||
for Comment { name, text } in &self.comments {
|
|
||||||
let _ = writeln!(stream, "<fieldset><legend>{name}</legend>{text}</fieldset>");
|
|
||||||
}
|
|
||||||
|
|
||||||
let _ = writeln!(stream, "<hr>");
|
|
||||||
let _ = writeln!(stream, "<a href=\"/{parent_path}\"><< Back</a>");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let html = html! {
|
||||||
|
h1 { "Comments" }
|
||||||
|
form method="POST" {
|
||||||
|
input type="text" name="name" value="anon" placeholder="Name";
|
||||||
|
br;
|
||||||
|
textarea rows="5" cols="60" name="text" placeholder="Enter comment..." {}
|
||||||
|
br;
|
||||||
|
input type="submit" value="Send!";
|
||||||
|
}
|
||||||
|
form method="POST" {
|
||||||
|
input type="submit" value="💖️" name="up";
|
||||||
|
input type="submit" value="💔️" name="down";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let _ = stream.write_all(html.into_string().as_bytes());
|
||||||
|
|
||||||
|
for Comment { name, text } in &info.comments {
|
||||||
|
let _ = writeln!(stream, "<fieldset><legend>{name}</legend>{text}</fieldset>");
|
||||||
|
}
|
||||||
|
|
||||||
|
let _ = writeln!(stream, "<hr>");
|
||||||
|
let _ = writeln!(stream, "<a href=\"/{parent_path}\"><< Back</a>");
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user