#![deny(warnings)]
#![deny(clippy::all, clippy::pedantic, clippy::nursery)]
#![deny(missing_debug_implementations, rust_2018_idioms)]
#![forbid(unsafe_code, missing_docs)]
#![allow(clippy::module_name_repetitions)]
mod context;
mod executor;
pub mod middleware;
use {
async_channel::{Receiver, Sender},
async_net::TcpListener,
std::{collections::HashMap, io, net::ToSocketAddrs, sync::Arc},
};
pub use {
async_trait::async_trait,
context::Context,
executor::{BuiltInExecutor, Executor},
http_types::{Method, Mime, Request, Response, StatusCode},
middleware::Middleware,
};
pub type Result<T = ()> = http_types::Result<T>;
pub type Error = http_types::Error;
type MiddlewareList<Ex> = Vec<Arc<dyn Middleware<Ex>>>;
#[must_use]
pub fn new() -> Amiya<BuiltInExecutor, ()> {
Amiya::default()
}
#[must_use]
pub fn with_ex<Ex>() -> Amiya<BuiltInExecutor, Ex> {
Amiya::default()
}
#[allow(missing_debug_implementations)]
pub struct Amiya<Exec, Ex = ()> {
executor: Exec,
middleware_list: MiddlewareList<Ex>,
}
impl<Ex> Default for Amiya<BuiltInExecutor, Ex> {
fn default() -> Self {
Self::new()
}
}
impl<Ex> Amiya<BuiltInExecutor, Ex> {
#[must_use]
pub fn new() -> Self {
Self { executor: BuiltInExecutor, middleware_list: MiddlewareList::default() }
}
}
#[allow(clippy::use_self)]
impl<Exec, Ex> Amiya<Exec, Ex>
where
Ex: Send + Sync + 'static,
{
pub fn uses<M: Middleware<Ex> + 'static>(mut self, middleware: M) -> Self {
self.middleware_list.push(Arc::new(middleware));
self
}
pub fn executor<NewExec>(self, executor: NewExec) -> Amiya<NewExec, Ex> {
Amiya { executor, middleware_list: self.middleware_list }
}
}
impl<Exec, Ex> Amiya<Exec, Ex>
where
Exec: Executor + 'static,
Ex: Default + Send + Sync + 'static,
{
async fn serve(tail: Arc<MiddlewareList<Ex>>, mut req: Request) -> Result<Response> {
let mut ex = Ex::default();
let mut resp = Response::new(StatusCode::Ok);
let mut router_matches = HashMap::new();
let mut body = Some(req.take_body());
let mut ctx = Context {
req: &req,
body: &mut body,
resp: &mut resp,
ex: &mut ex,
tail: &tail,
remain_path: req.url().path(),
router_matches: &mut router_matches,
};
ctx.next().await?;
Ok(resp)
}
async fn accepter(
listener: TcpListener, executor: Arc<Exec>, middleware_list: MiddlewareList<Ex>,
stop: Receiver<()>,
) {
let middleware_list = Arc::new(middleware_list);
let mut forever = false;
loop {
let check_stop = if forever {
Ok(listener.accept().await)
} else {
let stop_fut = async { Err(stop.recv().await) };
let accept_fut = async { Ok(listener.accept().await) };
futures_lite::future::or(stop_fut, accept_fut).await
};
match check_stop {
Ok(listener_result) => match listener_result {
Ok((stream, client_addr)) => {
let middleware_list = Arc::clone(&middleware_list);
let serve = async_h1::accept(stream, move |mut req| {
req.set_peer_addr(Some(client_addr));
Self::serve(Arc::clone(&middleware_list), req)
});
executor.spawn(async move {
if let Err(e) = serve.await {
log::error!(
"Request handle error: code = {}, type = {}, detail = {}",
e.status(),
e.type_name().unwrap_or("Unknown"),
e,
);
}
});
}
Err(e) => {
log::warn!("Accept connection failed: {:?}", e);
}
},
Err(signal) => {
if signal.is_err() {
forever = true;
} else {
log::info!("Amiya server stop listening {:?}", listener.local_addr());
return;
}
}
}
}
}
pub fn listen<A: ToSocketAddrs>(self, addr: A) -> io::Result<Sender<()>> {
let addr = addr.to_socket_addrs()?.next().unwrap();
let listener = self.executor.block_on(TcpListener::bind(addr))?;
log::info!("Amiya server start listening {:?}", listener.local_addr());
let executor = Arc::new(self.executor);
let (tx, rx) = async_channel::bounded::<()>(1);
executor.spawn(Self::accepter(listener, Arc::clone(&executor), self.middleware_list, rx));
Ok(tx)
}
}
#[async_trait]
impl<Exec, Ex> Middleware<Ex> for Amiya<Exec, Ex>
where
Exec: Send + Sync,
Ex: Send + Sync + 'static,
{
async fn handle(&self, mut ctx: Context<'_, Ex>) -> Result {
let mut self_ctx = Context {
req: ctx.req,
body: ctx.body,
resp: ctx.resp,
ex: ctx.ex,
tail: &self.middleware_list[..],
remain_path: ctx.remain_path,
router_matches: ctx.router_matches,
};
self_ctx.next().await?;
ctx.next().await
}
}