routerless http frontend and frontend manager
This commit is contained in:
parent
d0e556ec3c
commit
20b113d30d
11
Cargo.lock
generated
11
Cargo.lock
generated
@ -272,6 +272,12 @@ dependencies = [
|
|||||||
"walkdir",
|
"walkdir",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "httparse"
|
||||||
|
version = "1.10.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "humansize"
|
name = "humansize"
|
||||||
version = "2.1.3"
|
version = "2.1.3"
|
||||||
@ -535,6 +541,7 @@ name = "mintee"
|
|||||||
version = "0.0.1"
|
version = "0.0.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"git2",
|
"git2",
|
||||||
|
"httparse",
|
||||||
"serde",
|
"serde",
|
||||||
"tera",
|
"tera",
|
||||||
]
|
]
|
||||||
@ -1362,9 +1369,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zerovec"
|
name = "zerovec"
|
||||||
version = "0.11.3"
|
version = "0.11.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bdbb9122ea75b11bf96e7492afb723e8a7fbe12c67417aa95e7e3d18144d37cd"
|
checksum = "e7aa2bd55086f1ab526693ecbe444205da57e25f4489879da80635a46d90e73b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"yoke",
|
"yoke",
|
||||||
"zerofrom",
|
"zerofrom",
|
||||||
|
@ -3,6 +3,10 @@ name = "mintee"
|
|||||||
version = "0.0.1"
|
version = "0.0.1"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "frontend"
|
||||||
|
path = "src/frontend.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
git2 = "0.20.2"
|
git2 = "0.20.2"
|
||||||
serde = { version = "1.0.219", default-features = false, features = ["derive"] }
|
serde = { version = "1.0.219", default-features = false, features = ["derive"] }
|
||||||
@ -10,3 +14,4 @@ serde = { version = "1.0.219", default-features = false, features = ["derive"] }
|
|||||||
#inkjet = "0.11.1"
|
#inkjet = "0.11.1"
|
||||||
#markdown = "1.0.0"
|
#markdown = "1.0.0"
|
||||||
tera = { version = "1.20.0", default-features = false, features = ["builtins"] }
|
tera = { version = "1.20.0", default-features = false, features = ["builtins"] }
|
||||||
|
httparse = "1.10.1"
|
||||||
|
40
src/frontend.rs
Normal file
40
src/frontend.rs
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2025 silt <silt@tebibyte.media>
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
*
|
||||||
|
* This file is part of Mintee.
|
||||||
|
*
|
||||||
|
* Mintee is free software: you can redistribute it and/or modify it under the
|
||||||
|
* terms of the GNU Affero General Public License as published by the Free
|
||||||
|
* Software Foundation, either version 3 of the License, or (at your option)
|
||||||
|
* any later version.
|
||||||
|
*
|
||||||
|
* Mintee is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||||
|
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||||
|
* details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with Mintee. If not, see https://www.gnu.org/licenses/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
use std::{error::Error, time::Duration};
|
||||||
|
|
||||||
|
mod server;
|
||||||
|
use server::Pool;
|
||||||
|
|
||||||
|
|
||||||
|
mod http_fe;
|
||||||
|
use http_fe::FrontendImpl;
|
||||||
|
|
||||||
|
mod gem_fe;
|
||||||
|
|
||||||
|
mod yapper;
|
||||||
|
|
||||||
|
fn main() -> Result<(), Box<dyn Error>> {
|
||||||
|
let http_fe = http_fe::Frontend::init(http_fe::FeConfig::init("0.0.0.0:8080", Duration::new(2, 0), Duration::new(2, 0))?);
|
||||||
|
|
||||||
|
let pool = Pool::<32>::new();
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
217
src/http_fe.rs
Normal file
217
src/http_fe.rs
Normal file
@ -0,0 +1,217 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2025 silt <silt@tebibyte.media>
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
*
|
||||||
|
* This file is part of Mintee.
|
||||||
|
*
|
||||||
|
* Mintee is free software: you can redistribute it and/or modify it under the
|
||||||
|
* terms of the GNU Affero General Public License as published by the Free
|
||||||
|
* Software Foundation, either version 3 of the License, or (at your option)
|
||||||
|
* any later version.
|
||||||
|
*
|
||||||
|
* Mintee is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||||
|
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||||
|
* details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with Mintee. If not, see https://www.gnu.org/licenses/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
use httparse;
|
||||||
|
use std::{
|
||||||
|
error::Error,
|
||||||
|
fmt,
|
||||||
|
io::{self, BufRead, BufReader, Read},
|
||||||
|
net::{Incoming, SocketAddr, TcpListener, TcpStream, ToSocketAddrs},
|
||||||
|
process::exit,
|
||||||
|
time::Duration,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{eyap, yap};
|
||||||
|
|
||||||
|
pub use super::server::{Frontend, FrontendImpl};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub enum HttpErrorKind {
|
||||||
|
BadRequest,
|
||||||
|
Unauthorized,
|
||||||
|
Forbidden,
|
||||||
|
NotFound,
|
||||||
|
MethodNotAllowed,
|
||||||
|
UriTooLong,
|
||||||
|
ImATeapot,
|
||||||
|
InternalServerError,
|
||||||
|
NotImplemented,
|
||||||
|
HttpVersionNotSupported,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<HttpErrorKind> for usize {
|
||||||
|
fn from(val: HttpErrorKind) -> Self {
|
||||||
|
use HttpErrorKind::*;
|
||||||
|
match val {
|
||||||
|
BadRequest => 400,
|
||||||
|
Unauthorized => 401,
|
||||||
|
Forbidden => 403,
|
||||||
|
NotFound => 404,
|
||||||
|
MethodNotAllowed => 405,
|
||||||
|
UriTooLong => 414,
|
||||||
|
ImATeapot => 418,
|
||||||
|
InternalServerError => 500,
|
||||||
|
NotImplemented => 501,
|
||||||
|
HttpVersionNotSupported => 505,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<HttpErrorKind> for &str {
|
||||||
|
fn from(val: HttpErrorKind) -> Self {
|
||||||
|
use HttpErrorKind::*;
|
||||||
|
match val {
|
||||||
|
BadRequest => "Bad Request",
|
||||||
|
Unauthorized => "Unauthorized",
|
||||||
|
Forbidden => "Forbidden",
|
||||||
|
NotFound => "NotFound",
|
||||||
|
MethodNotAllowed => "Method Not Allowed",
|
||||||
|
UriTooLong => "URI Too Long",
|
||||||
|
ImATeapot => "I'm A Teapot",
|
||||||
|
InternalServerError => "Internal Server Error",
|
||||||
|
NotImplemented => "Not Implemented",
|
||||||
|
HttpVersionNotSupported => "HTTP Version Not Supported",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub struct HttpError {
|
||||||
|
kind: HttpErrorKind,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for HttpError {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"HTTP/1.1 {} {}",
|
||||||
|
Into::<usize>::into(self.kind),
|
||||||
|
Into::<&str>::into(self.kind)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Error for HttpError {}
|
||||||
|
|
||||||
|
impl From<HttpError> for io::Error {
|
||||||
|
fn from(val: HttpError) -> Self {
|
||||||
|
io::Error::other(val.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<httparse::Error> for HttpError {
|
||||||
|
fn from(value: httparse::Error) -> Self {
|
||||||
|
HttpError {
|
||||||
|
kind: match value {
|
||||||
|
_ => HttpErrorKind::BadRequest,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct FeConfig {
|
||||||
|
bind_address: SocketAddr,
|
||||||
|
read_timeout: Duration,
|
||||||
|
write_timeout: Duration,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FeConfig {
|
||||||
|
pub fn init<A: ToSocketAddrs>(
|
||||||
|
bind_address: A,
|
||||||
|
read_timeout: Duration,
|
||||||
|
write_timeout: Duration,
|
||||||
|
) -> Result<Self, Box<dyn Error>> {
|
||||||
|
Ok(FeConfig {
|
||||||
|
bind_address: bind_address.to_socket_addrs()?.collect::<Vec<_>>()[0],
|
||||||
|
read_timeout,
|
||||||
|
write_timeout,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct FeStorage {
|
||||||
|
listener: TcpListener,
|
||||||
|
// TODO: tera template store
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Frontend<FeStorage, FeConfig> {}
|
||||||
|
|
||||||
|
impl Iterator for Frontend<FeStorage, FeConfig> {
|
||||||
|
type Item = Incoming<'static>;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FrontendImpl for Frontend<FeStorage, FeConfig> {
|
||||||
|
type FeConfig = FeConfig;
|
||||||
|
type RequestSubject = TcpStream;
|
||||||
|
type ReplySubject = TcpStream;
|
||||||
|
|
||||||
|
fn init(config: FeConfig) -> Self {
|
||||||
|
// TODO: load tera templates into FeStorage
|
||||||
|
Frontend {
|
||||||
|
storage: self::FeStorage {
|
||||||
|
listener: TcpListener::bind(config.bind_address).unwrap_or_else(|e| {
|
||||||
|
eyap!(&e);
|
||||||
|
exit(1)
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
config: config,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_request(&self, subj: Self::RequestSubject) -> Result<(), Box<dyn Error>> {
|
||||||
|
subj.set_read_timeout(Some(self.config.read_timeout))
|
||||||
|
.and_then(|_| subj.set_write_timeout(Some(self.config.write_timeout)))?;
|
||||||
|
|
||||||
|
let stream_read = BufReader::new(subj);
|
||||||
|
let mut headers = [httparse::EMPTY_HEADER; 32];
|
||||||
|
let mut req = httparse::Request::new(&mut headers);
|
||||||
|
let buf: &mut Vec<u8> = &mut vec![];
|
||||||
|
|
||||||
|
stream_read.take(8192).read_until(b'\n', buf)?;
|
||||||
|
let res = req.parse(buf);
|
||||||
|
|
||||||
|
match (res, req) {
|
||||||
|
// Presumably well-formed enough to get sent off to the route handler
|
||||||
|
(
|
||||||
|
Ok(httparse::Status::Partial),
|
||||||
|
httparse::Request {
|
||||||
|
method: Some(method),
|
||||||
|
path: Some(path),
|
||||||
|
version: Some(1),
|
||||||
|
headers: _,
|
||||||
|
},
|
||||||
|
) => todo!(),
|
||||||
|
|
||||||
|
// Malformed request lines and HTTP/1.1 requests without a Host header
|
||||||
|
(Ok(httparse::Status::Partial), _) | (Ok(httparse::Status::Complete(_)), _) => {
|
||||||
|
Err(Box::new(HttpError {
|
||||||
|
kind: HttpErrorKind::BadRequest,
|
||||||
|
}) as Box<dyn Error>)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fatal parsing error; obvious bad request
|
||||||
|
(Err(e), _) => Err(Box::new(e) as Box<dyn Error>),
|
||||||
|
}?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn send_reply(&self, subj: Self::ReplySubject) -> Result<(), Box<dyn Error>> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_error(&self, res: Result<(), Box<dyn Error>>) {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
69
src/server.rs
Normal file
69
src/server.rs
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2025 silt <silt@tebibyte.media>
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
*
|
||||||
|
* This file is part of Mintee.
|
||||||
|
*
|
||||||
|
* Mintee is free software: you can redistribute it and/or modify it under the
|
||||||
|
* terms of the GNU Affero General Public License as published by the Free
|
||||||
|
* Software Foundation, either version 3 of the License, or (at your option)
|
||||||
|
* any later version.
|
||||||
|
*
|
||||||
|
* Mintee is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||||
|
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||||
|
* details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with Mintee. If not, see https://www.gnu.org/licenses/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
use std::{
|
||||||
|
array,
|
||||||
|
error::Error,
|
||||||
|
io::{self, BufRead, BufReader, BufWriter, Read, Write},
|
||||||
|
net::{SocketAddr, TcpListener, TcpStream, ToSocketAddrs},
|
||||||
|
num::NonZeroUsize,
|
||||||
|
os::{linux::net::TcpStreamExt, unix::thread::JoinHandleExt},
|
||||||
|
thread::{self, JoinHandle, Thread},
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{eyap, yap};
|
||||||
|
|
||||||
|
pub struct Frontend<S, C> {
|
||||||
|
/// Holds data necessary for and private to the implementor.
|
||||||
|
pub storage: S,
|
||||||
|
|
||||||
|
/// Holds data to be set during initialization.
|
||||||
|
pub config: C,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait FrontendImpl: Iterator {
|
||||||
|
type FeConfig: ?Sized;
|
||||||
|
type RequestSubject: Read;
|
||||||
|
type ReplySubject: Write;
|
||||||
|
fn init(storage: Self::FeConfig) -> Self;
|
||||||
|
fn handle_request(&self, subj: Self::RequestSubject) -> Result<(), Box<dyn Error>>;
|
||||||
|
fn send_reply(&self, subj: Self::ReplySubject) -> Result<(), Box<dyn Error>>;
|
||||||
|
// NOTE: handle_request.or_else(handle_error)
|
||||||
|
fn handle_error(&self, res: Result<(), Box<dyn Error>>);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: split frontend management code and Frontend trait stuff into diff files
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Pool<const N: usize> {
|
||||||
|
threads: [JoinHandle<()>; N],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const N: usize> Pool<N> {
|
||||||
|
pub fn new() -> Result<Self, Box<dyn Error>> {
|
||||||
|
Ok(Pool {
|
||||||
|
threads: array::from_fn(|id| {
|
||||||
|
thread::spawn(move || {
|
||||||
|
eyap!("started thread #{:?}", id);
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
77
src/yapper.rs
Normal file
77
src/yapper.rs
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
pub mod _inner {
|
||||||
|
macro_rules! _yap_inner {
|
||||||
|
($m:expr) => {{
|
||||||
|
println!("[{}:{}] {}", file!(), line!(), $m);
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
pub(crate) use _yap_inner;
|
||||||
|
|
||||||
|
macro_rules! _eyap_inner {
|
||||||
|
($m:expr) => {{
|
||||||
|
println!("[{}:{}] {}", file!(), line!(), $m);
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
pub(crate) use _eyap_inner;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! yap {
|
||||||
|
($m:ident) => {{
|
||||||
|
let v = $m;
|
||||||
|
crate::yapper::_inner::_yap_inner!(format!("{}", $m));
|
||||||
|
v
|
||||||
|
}};
|
||||||
|
($f:literal , $m:ident) => {{
|
||||||
|
let v = $m;
|
||||||
|
crate::yapper::_inner::_yap_inner!(format!($f, $m));
|
||||||
|
v
|
||||||
|
}};
|
||||||
|
|
||||||
|
($m:literal) => {{
|
||||||
|
let v = $m;
|
||||||
|
crate::yapper::_inner::_yap_inner!(format!($m));
|
||||||
|
v
|
||||||
|
}};
|
||||||
|
|
||||||
|
($f:literal , $m:expr) => {{
|
||||||
|
let v = $m;
|
||||||
|
crate::yapper::_inner::_yap_inner!(format!($f, v));
|
||||||
|
v
|
||||||
|
}};
|
||||||
|
($m:expr) => {{
|
||||||
|
let v = $m;
|
||||||
|
crate::yapper::_inner::_yap_inner!(format!("{}", v));
|
||||||
|
v
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! eyap {
|
||||||
|
($m:ident) => {{
|
||||||
|
let v = $m;
|
||||||
|
crate::yapper::_inner::_eyap_inner!(format!("{}", $m));
|
||||||
|
v
|
||||||
|
}};
|
||||||
|
($f:literal , $m:ident) => {{
|
||||||
|
let v = $m;
|
||||||
|
crate::yapper::_inner::_eyap_inner!(format!($f, $m));
|
||||||
|
v
|
||||||
|
}};
|
||||||
|
|
||||||
|
($m:literal) => {{
|
||||||
|
let v = $m;
|
||||||
|
crate::yapper::_inner::_eyap_inner!(format!($m));
|
||||||
|
v
|
||||||
|
}};
|
||||||
|
|
||||||
|
($f:literal , $m:expr) => {{
|
||||||
|
let v = $m;
|
||||||
|
crate::yapper::_inner::_eyap_inner!(format!($f, v));
|
||||||
|
v
|
||||||
|
}};
|
||||||
|
($m:expr) => {{
|
||||||
|
let v = $m;
|
||||||
|
crate::yapper::_inner::_eyap_inner!(format!("{}", v));
|
||||||
|
v
|
||||||
|
}};
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user