improve pipeline
This commit is contained in:
parent
06561f13d9
commit
f98e689ead
4 changed files with 72 additions and 41 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
|
@ -4,7 +4,7 @@ version = 4
|
|||
|
||||
[[package]]
|
||||
name = "horrorhttp"
|
||||
version = "0.1.1"
|
||||
version = "0.1.2"
|
||||
dependencies = [
|
||||
"readformat",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -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
11
README.md
Normal 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.
|
||||
|
||||
98
src/lib.rs
98
src/lib.rs
|
|
@ -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,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue