improve pipeline

This commit is contained in:
Tove 2025-09-07 17:42:08 +02:00
parent 06561f13d9
commit f98e689ead
Signed by: TudbuT
GPG key ID: B3CF345217F202D3
4 changed files with 72 additions and 41 deletions

2
Cargo.lock generated
View file

@ -4,7 +4,7 @@ version = 4
[[package]]
name = "horrorhttp"
version = "0.1.1"
version = "0.1.2"
dependencies = [
"readformat",
]

View file

@ -3,7 +3,7 @@ name = "horrorhttp"
description = "A perhaps too flexible HTTP library based on a state machine."
license = "MIT"
repository = "https://git.tudbut.de/tudbut/horrorhttp"
version = "0.1.1"
version = "0.1.2"
edition = "2024"
[dependencies]

11
README.md Normal file
View file

@ -0,0 +1,11 @@
# HorrorHTTP
**Please consider not using this library. It is not
meant for general use in e.g. a website, but it is very very flexible**
HorrorHTTP is an extremely light HTTP library meant to allow for complete
flexibility in what you want to do with the connection.
To start, feed a TcpStream and handler state into [`handle`]. This creates
a state machine which allows for each state to transition into any other state.

View file

@ -20,23 +20,26 @@ use std::{
use readformat::readf;
pub trait ConnectionState {
fn handle(self: Box<Self>, client: &mut ClientHolder) -> Option<Box<dyn ConnectionState>>;
fn handle(self: Box<Self>, connection: &mut Connection) -> Option<Box<dyn ConnectionState>>;
}
impl<T> ConnectionState for T
where
T: FnMut(&mut ClientHolder) -> Option<Box<dyn ConnectionState>>,
T: FnMut(&mut Connection) -> Option<Box<dyn ConnectionState>>,
{
fn handle(mut self: Box<Self>, client: &mut ClientHolder) -> Option<Box<dyn ConnectionState>> {
self(client)
fn handle(
mut self: Box<Self>,
connection: &mut Connection,
) -> Option<Box<dyn ConnectionState>> {
self(connection)
}
}
pub struct CombinedState(Box<dyn ConnectionState>, Box<dyn ConnectionState>);
impl ConnectionState for CombinedState {
fn handle(self: Box<Self>, client: &mut ClientHolder) -> Option<Box<dyn ConnectionState>> {
CombinedState::new(self.0.handle(client), self.1.handle(client))
fn handle(self: Box<Self>, connection: &mut Connection) -> Option<Box<dyn ConnectionState>> {
CombinedState::new(self.0.handle(connection), self.1.handle(connection))
}
}
@ -66,16 +69,16 @@ impl Add for Box<dyn ConnectionState> {
pub struct BeginState;
impl ConnectionState for BeginState {
fn handle(self: Box<Self>, client: &mut ClientHolder) -> Option<Box<dyn ConnectionState>> {
fn handle(self: Box<Self>, connection: &mut Connection) -> Option<Box<dyn ConnectionState>> {
let mut read_so_far = String::new();
let mut buf = [0u8; 1024];
while let Ok(n) = client.socket.read(&mut buf[..]) {
while let Ok(n) = connection.socket.read(&mut buf[..]) {
let index_of_current_buf = read_so_far.len();
let s = String::from_utf8_lossy(&buf[..n]);
read_so_far += &s;
if let Some(end) = read_so_far.find("\r\n\r\n") {
let index_in_buf = (end as isize - index_of_current_buf as isize + 4) as usize;
client.body.extend_from_slice(&buf[index_in_buf..n]);
connection.body.extend_from_slice(&buf[index_in_buf..n]);
let _ = read_so_far.split_off(end);
break;
}
@ -88,7 +91,7 @@ impl ConnectionState for BeginState {
pub struct ParseHeadState(String);
impl ConnectionState for ParseHeadState {
fn handle(self: Box<Self>, client: &mut ClientHolder) -> Option<Box<dyn ConnectionState>> {
fn handle(self: Box<Self>, connection: &mut Connection) -> Option<Box<dyn ConnectionState>> {
let head = self.0.replace("\r\n", "\n");
let mut lines = head.lines();
let head_line = lines.next()?;
@ -100,19 +103,16 @@ impl ConnectionState for ParseHeadState {
.filter(|(a, b)| a.is_some() && b.is_some())
.map(|(a, b)| (a.unwrap(), b.unwrap()))
{
client.headers.insert(name.to_owned(), value.to_owned());
connection.headers.insert(name.to_owned(), value.to_owned());
}
let head_line = readf("{} {} HTTP/{}", head_line)?;
let [m, p, h] = head_line.as_slice() else {
return None;
};
client.method = m.to_owned();
client.path = p.to_owned();
client.http_version = h.to_owned();
client
.body_reader
.take()
.or_else(|| client.custom_handler_state.pop_front())
connection.method = m.to_owned();
connection.path = p.to_owned();
connection.http_version = h.to_owned();
connection.body_reader.take().or_else(|| connection.next())
}
}
@ -122,8 +122,8 @@ pub trait BodyReader: ConnectionState {}
pub struct BodyReaderDoNothing;
impl ConnectionState for BodyReaderDoNothing {
fn handle(self: Box<Self>, client: &mut ClientHolder) -> Option<Box<dyn ConnectionState>> {
client.custom_handler_state.pop_front()
fn handle(self: Box<Self>, connection: &mut Connection) -> Option<Box<dyn ConnectionState>> {
connection.next()
}
}
@ -133,19 +133,19 @@ impl BodyReader for BodyReaderDoNothing {}
pub struct BodyReaderHeader;
impl ConnectionState for BodyReaderHeader {
fn handle(self: Box<Self>, client: &mut ClientHolder) -> Option<Box<dyn ConnectionState>> {
let n = client
fn handle(self: Box<Self>, connection: &mut Connection) -> Option<Box<dyn ConnectionState>> {
let n = connection
.headers
.get("Content-Length")
.map(|x| x.as_str())
.unwrap_or_else(|| "0")
.parse::<usize>()
.ok()?;
let n = n - client.body.len();
let n = n - connection.body.len();
let mut buf = vec![0u8; n];
client.socket.read_exact(&mut buf).ok()?;
client.body.append(&mut buf);
client.custom_handler_state.pop_front()
connection.socket.read_exact(&mut buf).ok()?;
connection.body.append(&mut buf);
connection.next()
}
}
@ -155,9 +155,9 @@ impl BodyReader for BodyReaderHeader {}
pub struct BodyReaderEOF;
impl ConnectionState for BodyReaderEOF {
fn handle(self: Box<Self>, client: &mut ClientHolder) -> Option<Box<dyn ConnectionState>> {
client.socket.read_to_end(&mut client.body).ok()?;
client.custom_handler_state.pop_front()
fn handle(self: Box<Self>, connection: &mut Connection) -> Option<Box<dyn ConnectionState>> {
connection.socket.read_to_end(&mut connection.body).ok()?;
connection.next()
}
}
@ -217,7 +217,7 @@ impl ResponseWriter {
}
impl ConnectionState for ResponseWriter {
fn handle(self: Box<Self>, client: &mut ClientHolder) -> Option<Box<dyn ConnectionState>> {
fn handle(self: Box<Self>, client: &mut Connection) -> Option<Box<dyn ConnectionState>> {
let mut buf = Vec::new();
buf.append(
&mut format!(
@ -241,14 +241,14 @@ impl ConnectionState for ResponseWriter {
buf.append(&mut body);
}
client.socket.write_all(&buf).ok()?;
client.custom_handler_state.pop_front()
client.next()
}
}
pub struct ClientHolder {
pub struct Connection {
pub body_reader: Option<Box<dyn ConnectionState>>,
/// whenever giving control to a horrorhttp state, this will eventually be returned to.
pub custom_handler_state: VecDeque<Box<dyn ConnectionState>>,
custom_handler_state: VecDeque<Box<dyn ConnectionState>>,
pub socket: TcpStream,
pub method: String,
pub path: String,
@ -257,6 +257,27 @@ pub struct ClientHolder {
pub body: Vec<u8>,
}
impl Connection {
pub fn add_next(&mut self, state: Box<dyn ConnectionState>) {
self.custom_handler_state.push_back(state);
}
pub fn add_next_optional(&mut self, state: Option<Box<dyn ConnectionState>>) {
if let Some(state) = state {
self.custom_handler_state.push_back(state);
}
}
/// Continues processing the request. If directing to a specific state
/// is wished, return that state instead.
pub fn next(&mut self) -> Option<Box<dyn ConnectionState>> {
self.custom_handler_state.pop_front()
}
/// Denies further processing of the request
pub fn deny(&mut self) -> Option<Box<dyn ConnectionState>> {
None
}
}
const EMPTY: String = String::new();
/// Begins handling a TCP connection. You must choose
@ -268,24 +289,23 @@ const EMPTY: String = String::new();
/// or [`BodyReaderEOF`], which reads until the input
/// closes. Custom BodyReaders may also be used.
///
/// The handler is written into the custom_handler_state
/// field of the ClientHolder, which will be popped and
/// called after the body reader. Each time
/// The handler is written into queue and called after
/// the body reader calls connection.next(). Each time
/// control is given to HorrorHTTP, it will at some point
/// return to custom_handler_state, if it has an item at
/// return to connection.next(), if it has an item at
/// that point - including after [`ResponseWriter`].
///
/// Control flow for a correct request and HorrorHTTP body
/// reader:
/// [`BeginState`] -> [`ParseHeadState`] -> dyn [`BodyReader`] ->
/// *handler* (-> [`ResponseWriter`] -> *custom_handler_state* ...)
/// *handler* (-> [`ResponseWriter`] -> *connection.next()* ...)
pub fn handle(
stream: TcpStream,
body_reader: impl BodyReader + Send + 'static,
handler: impl ConnectionState + Send + 'static,
) -> JoinHandle<()> {
thread::spawn(move || {
let mut client = ClientHolder {
let mut client = Connection {
body_reader: Some(Box::new(body_reader)),
custom_handler_state: VecDeque::new(),
socket: stream,