wip http frontend code

This commit is contained in:
2026-05-07 00:54:25 +00:00
parent ca7691ee18
commit f173393f19
3 changed files with 433 additions and 438 deletions
+243 -228
View File
@@ -20,18 +20,24 @@
use httparse::{self}; use httparse::{self};
use itertools::Itertools; use itertools::Itertools;
use typed_path::{Utf8Component, Utf8UnixComponent, Utf8UnixPath, Utf8UnixPathBuf};
use std::{ use std::{
error::Error, fmt, io::{self, BufRead, BufReader, Read}, net::{Incoming, SocketAddr, TcpListener, TcpStream, ToSocketAddrs}, ops::Deref, pin::Pin, process::exit, str::FromStr, time::Duration error::Error,
fmt::{self},
io::{BufRead, BufReader, Read},
net::TcpStream,
ops::Deref,
str::FromStr,
time::Duration,
}; };
use typed_path::{Utf8Component, Utf8UnixComponent, Utf8UnixPath, Utf8UnixPathBuf};
use mintee::util::yapper::{yap, eyap}; use mintee::util::yapper::{eyap, yap};
pub use super::manager::{Frontend, FrontendImpl}; pub use super::manager::{Frontend, FrontendImpl};
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
#[non_exhaustive] #[non_exhaustive]
pub enum HttpMethod { pub enum HttpMethod<'a> {
GET, GET,
POST, POST,
HEAD, HEAD,
@@ -41,11 +47,11 @@ pub enum HttpMethod {
OPTIONS, OPTIONS,
TRACE, TRACE,
PATCH, PATCH,
Unknown, Other(&'a str),
} }
impl From<&str> for HttpMethod { impl<'a> From<&'a str> for HttpMethod<'a> {
fn from(val: &str) -> Self { fn from(val: &'a str) -> Self {
use HttpMethod::*; use HttpMethod::*;
match val { match val {
"GET" => GET, "GET" => GET,
@@ -57,13 +63,13 @@ impl From<&str> for HttpMethod {
"OPTIONS" => OPTIONS, "OPTIONS" => OPTIONS,
"TRACE" => TRACE, "TRACE" => TRACE,
"PATCH" => PATCH, "PATCH" => PATCH,
_ => Unknown, other => Other(other),
} }
} }
} }
impl From<HttpMethod> for &'static str { impl<'a> From<HttpMethod<'a>> for &'a str {
fn from(val: HttpMethod) -> Self { fn from(val: HttpMethod<'a>) -> Self {
use HttpMethod::*; use HttpMethod::*;
match val { match val {
GET => "GET", GET => "GET",
@@ -75,15 +81,15 @@ impl From<HttpMethod> for &'static str {
OPTIONS => "OPTIONS", OPTIONS => "OPTIONS",
TRACE => "TRACE", TRACE => "TRACE",
PATCH => "PATCH", PATCH => "PATCH",
Unknown => "?", Other(other) => other,
} }
} }
} }
impl From<String> for HttpMethod { impl<'a> From<&'a String> for HttpMethod<'a> {
fn from(val: String) -> Self { fn from(val: &'a String) -> Self {
use HttpMethod::*; use HttpMethod::*;
match val.as_str() { match &**val {
"GET" => GET, "GET" => GET,
"POST" => POST, "POST" => POST,
"HEAD" => HEAD, "HEAD" => HEAD,
@@ -93,53 +99,43 @@ impl From<String> for HttpMethod {
"OPTIONS" => OPTIONS, "OPTIONS" => OPTIONS,
"TRACE" => TRACE, "TRACE" => TRACE,
"PATCH" => PATCH, "PATCH" => PATCH,
_ => Unknown, _ => Other(val),
} }
} }
} }
impl From<HttpMethod> for String { impl<'a> From<HttpMethod<'a>> for String {
fn from(val: HttpMethod) -> Self { fn from(val: HttpMethod) -> Self {
use HttpMethod::*; use HttpMethod::*;
match val { match val {
GET => "GET".to_string(), GET => "GET".to_owned(),
POST => "POST".to_string(), POST => "POST".to_owned(),
HEAD => "HEAD".to_string(), HEAD => "HEAD".to_owned(),
PUT => "PUT".to_string(), PUT => "PUT".to_owned(),
DELETE => "DELETE".to_string(), DELETE => "DELETE".to_owned(),
CONNECT => "CONNECT".to_string(), CONNECT => "CONNECT".to_owned(),
OPTIONS => "OPTIONS".to_string(), OPTIONS => "OPTIONS".to_owned(),
TRACE => "TRACE".to_string(), TRACE => "TRACE".to_owned(),
PATCH => "PATCH".to_string(), PATCH => "PATCH".to_owned(),
Unknown => "?".to_string(), Other(other) => other.to_owned(),
} }
} }
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
#[non_exhaustive] #[non_exhaustive]
pub enum ResponseStatus { pub enum ResponseStatus<'a> {
Okay, Okay,
Created, Created,
MovedPermanently { MovedPermanently { location: String },
location: String, SeeOther { location: String },
}, TemporaryRedirect { location: String },
SeeOther { PermanentRedirect { location: String },
location: String,
},
TemporaryRedirect {
location: String,
},
PermanentRedirect {
location: String,
},
BadRequest, BadRequest,
Unauthorized, Unauthorized,
Forbidden, Forbidden,
NotFound, NotFound,
MethodNotAllowed { MethodNotAllowed { allow: Vec<HttpMethod<'a>> },
allow: Vec<HttpMethod>,
},
UriTooLong, UriTooLong,
ImATeapot, ImATeapot,
InternalServerError, InternalServerError,
@@ -147,7 +143,7 @@ pub enum ResponseStatus {
HttpVersionNotSupported, HttpVersionNotSupported,
} }
impl ResponseStatus { impl ResponseStatus<'_> {
fn as_code(&self) -> usize { fn as_code(&self) -> usize {
use ResponseStatus::*; use ResponseStatus::*;
match self { match self {
@@ -191,223 +187,110 @@ impl ResponseStatus {
HttpVersionNotSupported => "HTTP Version Not Supported", HttpVersionNotSupported => "HTTP Version Not Supported",
} }
} }
}
#[derive(Debug, Clone)] fn to_headers(&self) -> Vec<(&'static str, String)> {
pub struct HttpError { use ResponseStatus::*;
kind: ResponseStatus, match self {
} MovedPermanently { location } => vec![("location", location.clone())],
SeeOther { location } => vec![("location", location.clone())],
impl HttpError { TemporaryRedirect { location } => vec![("location", location.clone())],
pub fn new(kind: ResponseStatus) -> Self { PermanentRedirect { location } => vec![("location", location.clone())],
Self { kind } MethodNotAllowed { allow } => vec![(
"allow",
allow.iter().map(|x| Into::<String>::into(*x)).join(", "),
)],
_ => vec![],
}
} }
} }
impl fmt::Display for HttpError { impl fmt::Display for ResponseStatus<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!( write!(f, "{} {}", self.as_code(), self.as_description())
f,
"HTTP/1.1 {} {}",
self.kind.as_code(),
self.kind.as_description()
)
}
}
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(_: httparse::Error) -> Self {
HttpError::new(ResponseStatus::BadRequest)
} }
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Response<'a> { pub struct Response<'a> {
pub status: ResponseStatus, pub status: Option<ResponseStatus<'a>>,
pub headers: Vec<(&'a str, String)>, pub headers: Vec<(&'a str, String)>,
pub body: Option<&'a [u8]>, pub body: Option<&'a [u8]>,
} }
impl<'a> From<Response<'a>> for Vec<u8> { impl Response<'_> {
fn from(val: Response<'a>) -> Self { fn default() -> Self {
[ Self {
"HTTP/1.1 ".as_bytes(), status: None,
val.status.as_code().to_string().as_bytes(), headers: Default::default(),
b" ", body: None,
val.status.as_description().as_bytes(),
b"\r\n",
&val.headers.into_iter().fold(
Default::default(),
|mut acc: Vec<u8>, e: (&str, String)| -> Vec<u8> {
acc.append(&mut [e.0.as_bytes(), b": ", e.1.as_bytes(), b"\r\n"].concat());
acc
} }
),
b"\r\n",
val.body.unwrap_or_default(),
].concat()
} }
} }
impl<'a> From<HttpError> for Response<'a> { impl<'a> From<&Response<'a>> for Vec<u8> {
fn from(err: HttpError) -> Self { fn from(val: &Response<'a>) -> Self {
let status = err.kind.clone(); let status = val
let headers = match err.kind { .status
ResponseStatus::MovedPermanently { location } .as_ref()
| ResponseStatus::SeeOther { location } .unwrap_or(&ResponseStatus::InternalServerError);
| ResponseStatus::TemporaryRedirect { location } let headers: &Vec<(&'a str, String)> = val.headers.as_ref();
| ResponseStatus::PermanentRedirect { location } => vec![("location", location)], [
ResponseStatus::MethodNotAllowed { allow } => vec![( "HTTP/1.1 ".as_bytes(),
"allow", status.as_code().to_string().as_bytes(),
allow.iter().map(|x| Into::<String>::into(*x)).join(", ") b" ",
)], status.as_description().as_bytes(),
_ => vec![], b"\r\n",
}; &headers.iter().chain(&status.to_headers()).fold(
Response { Default::default(),
status, |mut acc: Vec<u8>, e: &(&str, String)| -> Vec<u8> {
headers, acc.append(&mut [e.0.as_bytes(), b": ", e.1.as_bytes(), b"\r\n"].concat());
body: None acc
} },
),
b"\r\n",
val.body.unwrap_or_default(),
]
.concat()
} }
} }
pub struct FeConfig { pub struct FeConfig {
bind_address: SocketAddr,
read_timeout: Duration, read_timeout: Duration,
write_timeout: Duration, write_timeout: Duration,
} }
impl FeConfig { impl FeConfig {
pub fn init<A: ToSocketAddrs>( pub fn init(read_timeout: Duration, write_timeout: Duration) -> Result<Self, Box<dyn Error>> {
bind_address: A,
read_timeout: Duration,
write_timeout: Duration,
) -> Result<Self, Box<dyn Error>> {
Ok(FeConfig { Ok(FeConfig {
bind_address: bind_address.to_socket_addrs()?.collect::<Vec<_>>()[0], // bind_address: bind_address.to_socket_addrs()?.collect::<Vec<_>>()[0],
read_timeout, read_timeout,
write_timeout, write_timeout,
}) })
} }
} }
pub struct FeStorage { #[derive(Debug, Clone)]
listener: TcpListener, struct Request<'a> {
// TODO: tera template store method: HttpMethod<'a>,
}
impl Frontend<FeStorage, FeConfig> {
fn router(
method: HttpMethod,
path: Utf8UnixPathBuf, path: Utf8UnixPathBuf,
params: Option<Vec<(&str, &str)>>, headers: Vec<(&'a str, &'a str)>,
headers: &[httparse::Header], params: Option<Vec<(&'a str, &'a str)>>,
) -> Result<<Frontend<FeStorage, FeConfig> as FrontendImpl<TcpStream>>::Response, Box<dyn Error>> {
use HttpMethod::*;
use ResponseStatus::*;
// unwrapping is safe here because the resource path it came from is a valid UTF-8 &str
match (method, path.components().map(|c| c.as_str()).collect::<Vec<&str>>().deref(), params, headers) {
(method, ["/", "index.html"], _, _) => {
if matches!(method, GET) {
Ok(Response {
status: ResponseStatus::Okay,
headers: vec![("x-test1", "test1".to_string()), ("x-test2", "test2".to_string())],
body: Some(b"totally cool and swag homepage"),
}.into())
} else {
Err(Box::new(HttpError::new(MethodNotAllowed { allow: vec![GET] })))
}
}
(method, ["/", "login"], _, _) => {
if matches!(method, GET | POST) {
todo!()
} else {
Err(Box::new(HttpError::new(MethodNotAllowed { allow: vec![GET, POST] })))
}
}
// oh how i long for inline const patterns
(method, ["/", user], _, _) if let Some(user) = user.strip_prefix('~') => {
if matches!(method, GET) {
todo!()
} else {
Err(Box::new(HttpError::new(MethodNotAllowed { allow: vec![GET] })))
}
}
(method, ["/", user, repo], _, _) if let Some(user) = user.strip_prefix('~') => {
if matches!(method, GET) {
todo!()
} else {
Err(Box::new(HttpError::new(MethodNotAllowed { allow: vec![GET] })))
}
}
(method, ["/", project], _, _) if let Some(project) = project.strip_prefix('+') => {
if matches!(method, GET) {
todo!()
} else {
Err(Box::new(HttpError::new(MethodNotAllowed { allow: vec![GET] })))
}
}
(method, ["/", project, repo], _, _) if let Some(project) = project.strip_prefix('+') => {
if matches!(method, GET) {
todo!()
} else {
Err(Box::new(HttpError::new(MethodNotAllowed { allow: vec![GET] })))
}
}
_ => Err(Box::new(HttpError::new(ResponseStatus::NotFound))),
}
}
} }
impl Iterator for Frontend<FeStorage, FeConfig> { trait ReqResp<'a> {
type Item = Incoming<'static>; fn new(buf: &'a [u8]) -> (Option<Request<'a>>, Response<'a>);
fn route(&mut self) -> &Self;
fn next(&mut self) -> Option<Self::Item> {
todo!()
}
} }
impl FrontendImpl<TcpStream> for Frontend<FeStorage, FeConfig> { impl<'a> ReqResp<'a> for (Request<'a>, Response<'_>) {
type FeConfig = FeConfig; fn new(buf: &'a [u8]) -> (Option<Request<'a>>, Response<'a>) {
type Request = TcpStream;
type Response = Vec<u8>;
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::Request) -> Result<Self::Response, 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 headers = [httparse::EMPTY_HEADER; 32];
let mut req = httparse::Request::new(&mut headers); let mut req = httparse::Request::new(&mut headers);
let buf: &mut Vec<u8> = &mut vec![]; let mut resp = Response::default();
// TODO: validate more of the request before sending to the router
stream_read.take(8192).read_until(b'\n', buf)?;
let res = req.parse(buf); let res = req.parse(buf);
Ok(match (res, req) { match (res, req) {
// Presumably well-formed enough to get sent off to the route handler // Presumably well-formed enough to get sent off to the route handler
( (
Ok(httparse::Status::Partial), Ok(httparse::Status::Partial),
@@ -434,32 +317,164 @@ impl FrontendImpl<TcpStream> for Frontend<FeStorage, FeConfig> {
); );
if path.is_absolute() { if path.is_absolute() {
// context-valid lexical normalization without da feature // context-valid lexical normalization without da feature
let path = Utf8UnixPathBuf::from_iter(path.components().try_fold(Vec::<Utf8UnixComponent>::new(), |mut acc, item| -> Result<Vec<Utf8UnixComponent<'_>>, Box<dyn Error>> { let path = Utf8UnixPathBuf::from_iter(path.components().fold(
Vec::<Utf8UnixComponent>::new(),
|mut acc, item| -> Vec<Utf8UnixComponent<'_>> {
match item { match item {
Utf8UnixComponent::CurDir => Ok(acc), Utf8UnixComponent::CurDir => acc,
Utf8UnixComponent::RootDir => {acc.push(item); Ok(acc)}, Utf8UnixComponent::RootDir => { acc.push(item); acc }
Utf8UnixComponent::Normal(_) => {acc.push(item); Ok(acc)}, Utf8UnixComponent::Normal(_) => { acc.push(item); acc }
Utf8UnixComponent::ParentDir => {acc.pop_if(|c| c != &Utf8UnixComponent::RootDir); Ok(acc)}, Utf8UnixComponent::ParentDir => { acc.pop_if(|c| c != &Utf8UnixComponent::RootDir); acc }
} }
})?); },
));
Self::router(method.into(), path, params, headers) let headers = headers
.iter()
.filter_map(|h| str::from_utf8(h.value).map(|v| (h.name, v)).ok())
.collect();
(
Some(Request {
method: method.into(),
path,
headers,
params,
}),
resp,
)
} else { } else {
Err(Box::new(HttpError::new(ResponseStatus::BadRequest)) as Box<dyn Error>) resp.status = Some(ResponseStatus::BadRequest);
(None, resp)
} }
} }
// Malformed request lines and HTTP/1.1 requests without a Host header // Malformed request lines and HTTP/1.1 requests without a Host header
(Ok(httparse::Status::Partial), _) | (Ok(httparse::Status::Complete(_)), _) => { (Ok(httparse::Status::Partial), _) | (Ok(httparse::Status::Complete(_)), _) => {
Err(Box::new(HttpError::new(ResponseStatus::BadRequest)) as Box<dyn Error>) resp.status = Some(ResponseStatus::BadRequest);
(None, resp)
} }
// Fatal parsing error; obvious bad request // Fatal parsing error; obvious bad request
(Err(e), _) => Err(Box::new(e) as Box<dyn Error>), (Err(e), _) => {
}?) eyap!(e);
resp.status = Some(ResponseStatus::BadRequest);
(None, resp)
}
}
} }
fn handle_error(&mut self, res: Result<Self::Response, Box<dyn Error>>) -> Vec<u8> { fn route(&mut self) -> &Self {
let (request, response) = self;
use HttpMethod::*;
use ResponseStatus::*;
match (
request.method,
request.path.components().map(|c| c.as_str()).collect::<Vec<&str>>().deref(),
&request.params,
&request.headers,
) {
(method, ["/", "index.html"], _, _) => {
if matches!(method, GET) {
response.status = Some(ResponseStatus::Okay);
response.headers = vec![
("x-test1", "test1".to_string()),
("x-test2", "test2".to_string()),
];
response.body = Some(b"totally cool and swag homepage");
self
} else {
response.status = Some(MethodNotAllowed { allow: vec![GET] });
self
}
}
(method, ["/", "login"], _, _) => {
if matches!(method, GET | POST) {
todo!() todo!()
} else {
response.status = Some(MethodNotAllowed {
allow: vec![GET, POST],
});
self
}
}
// oh how i long for inline const patterns
(method, ["/", user], _, _) if let Some(user) = user.strip_prefix('~') => {
if matches!(method, GET) {
todo!()
} else {
response.status = Some(MethodNotAllowed { allow: vec![GET] });
self
}
}
(method, ["/", user, repo], _, _) if let Some(user) = user.strip_prefix('~') => {
if matches!(method, GET) {
todo!()
} else {
response.status = Some(MethodNotAllowed { allow: vec![GET] });
self
}
}
(method, ["/", project], _, _) if let Some(project) = project.strip_prefix('+') => {
if matches!(method, GET) {
todo!()
} else {
response.status = Some(MethodNotAllowed { allow: vec![GET] });
self
}
}
(method, ["/", project, repo], _, _)
if let Some(project) = project.strip_prefix('+') =>
{
if matches!(method, GET) {
todo!()
} else {
response.status = Some(MethodNotAllowed { allow: vec![GET] });
self
}
}
_ => {
self.1.status = Some(NotFound);
self
}
}
}
}
pub struct FeStorage {
// TODO: tera template cache
}
impl Frontend<FeStorage, FeConfig> {}
impl FrontendImpl<TcpStream> for Frontend<FeStorage, FeConfig> {
type FeConfig = FeConfig;
type Request = TcpStream;
type Response = Vec<u8>;
fn init(config: FeConfig) -> Self {
// TODO: load tera templates into FeStorage
Frontend {
state: self::FeStorage {},
config: config,
}
}
async fn handle_request(&self, subj: Self::Request) -> Self::Response {
subj.set_read_timeout(Some(self.config.read_timeout))
.and_then(|_| subj.set_write_timeout(Some(self.config.write_timeout))).unwrap(/* TODO: what do we wanna do here? */);
eyap!("handling");
let stream_read = BufReader::new(subj);
let buf: &mut Vec<u8> = &mut vec![];
stream_read.take(8192).read_until(b'\n', buf).unwrap(/*TODO*/);
let (request, response) = match <(Request, Response)>::new(buf) {
(Some(request), response) => (request, response),
(None, response) => return (&response).into(),
};
let mut pair = (request, response);
let (_request, response) = pair.route();
response.into()
} }
} }
+20 -14
View File
@@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2025 silt <silt@tebibyte.media> * Copyright (c) 2025, 2026 silt <silt@tebibyte.media>
* SPDX-License-Identifier: AGPL-3.0-or-later * SPDX-License-Identifier: AGPL-3.0-or-later
* *
* This file is part of Mintee. * This file is part of Mintee.
@@ -18,30 +18,20 @@
* along with Mintee. If not, see https://www.gnu.org/licenses/. * along with Mintee. If not, see https://www.gnu.org/licenses/.
*/ */
use std::{error::Error, thread::available_parallelism, time::Duration}; use std::{error::Error, io::Write, net::TcpListener, process::exit, sync::Arc, thread::available_parallelism, time::Duration};
use futures::{self, channel::mpsc, executor::ThreadPool}; use futures::{channel::mpsc, executor::ThreadPool};
mod manager; mod manager;
use manager::Pool;
mod http_fe; mod http_fe;
use http_fe::FrontendImpl; use http_fe::FrontendImpl;
mod gem_fe; mod gem_fe;
// mod util;
// use crate::;
use mintee::util::yapper::eyap; use mintee::util::yapper::eyap;
fn main() -> Result<(), Box<dyn Error>> { 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();
let pool = ThreadPool::builder() let pool = ThreadPool::builder()
.pool_size(available_parallelism()?.into()) // TODO: or optional value from config .pool_size(available_parallelism()?.into()) // TODO: or optional value from config
.name_prefix("mintfe-worker:") .name_prefix("mintfe-worker:")
@@ -53,10 +43,26 @@ fn main() -> Result<(), Box<dyn Error>> {
}) })
.create()?; .create()?;
// let (tx, rx) = mpsc::unbounded::<todo!()>(); let (tx, rx) = mpsc::unbounded::<i32>();
let http_listener = TcpListener::bind("0.0.0.0:8080").unwrap_or_else(|e| {
eyap!(&e);
exit(1)
});
eyap!("http_listener bound to tcp 0.0.0.0:8080");
let http_fe = Arc::new(http_fe::Frontend::init(http_fe::FeConfig::init(Duration::new(2, 0), Duration::new(2, 0))?)) ;
eyap!("initialized http_fe");
for mut conn in http_listener.incoming().map(|x| x.unwrap()) {
let http_fe = http_fe.clone();
pool.spawn_ok(async move {
eyap!("incoming request from {}", conn.peer_addr().unwrap());
let response = http_fe.handle_request(conn.try_clone().unwrap()).await;
let a = conn.write_all(&response);
eyap!("handled!");
});
}
Ok(()) Ok(())
} }
+6 -32
View File
@@ -19,49 +19,23 @@
*/ */
use std::{ use std::{
array,
error::Error,
io::{Read, Write}, io::{Read, Write},
thread::{self, JoinHandle},
}; };
use futures::executor;
use mintee::util::yapper::{yap, eyap};
pub struct Frontend<S, C> { pub struct Frontend<S, C> {
/// Holds data necessary for and private to the implementor. /// Data necessary for and private to the implementor.
pub storage: S, pub state: S,
/// Holds data to be set during initialization. /// Data to be set during initialization.
pub config: C, pub config: C,
} }
pub trait FrontendImpl<Request>: Iterator where Request: Read, Self::Response: Into<Vec<u8>> { pub trait FrontendImpl<Request> where Self::Request: Read, Self::Response: Into<Vec<u8>> {
type FeConfig: ?Sized; type FeConfig: ?Sized;
type Request: Read; type Request: Read;
type Response: Write; type Response: Write;
fn init(storage: Self::FeConfig) -> Self;
fn handle_request(&self, subj: Request) -> Result<Self::Response, Box<dyn Error>>;
// fn send_reply(&self, subj: Self::Response) -> Result<(), Box<dyn Error>>;
// NOTE: handle_request().or_else(handle_error())
fn handle_error(&mut self, res: Result<Self::Response, Box<dyn Error>>) -> Self::Response;
}
// TODO: split frontend management code and Frontend trait stuff into diff files fn init(config: Self::FeConfig) -> Self;
async fn handle_request(&self, subj: Request) -> Self::Response;
#[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);
})
}),
})
}
} }