use hyper::server::conn::http1;
use hyper_util::rt::{TokioIo, TokioTimer};
use hyper_util::server::graceful::GracefulShutdown;
use mlua::AnyUserData;
use std::future::Future;
use std::path::PathBuf;
use std::sync::Arc;
use std::time::Duration;
use tokio::net::TcpListener;
use crate::config::Config;
use crate::error::{Error, Result};
use crate::lua::Builtins;
use crate::runtime::{Runtime, RuntimePool};
use crate::service::Svc;
const HEADER_READ_TIMEOUT: Duration = Duration::from_secs(30);
const SHUTDOWN_GRACE: Duration = Duration::from_secs(10);
type SetupFn = Box<dyn Fn(&mlua::Lua) -> mlua::Result<()> + Send + Sync>;
pub struct Server {
cfg: Config,
pool: Arc<RuntimePool>,
}
#[derive(Default)]
pub struct ServerBuilder {
cfg: Config,
builtins: Option<Builtins>,
setup_fns: Vec<SetupFn>,
}
impl Server {
pub fn builder() -> ServerBuilder {
ServerBuilder::default()
}
pub fn pool(&self) -> &Arc<RuntimePool> {
&self.pool
}
pub async fn serve(self) -> Result {
self.serve_with_shutdown(async {
let _ = tokio::signal::ctrl_c().await;
})
.await
}
pub async fn serve_with_shutdown(self, shutdown: impl Future<Output = ()>) -> Result {
let listener = TcpListener::bind(self.cfg.listen).await.map_err(|err| {
Error::Config(format!("unable to listen on {}: {err}", self.cfg.listen))
})?;
tracing::info!(
"listening on http://{} with {} Lua state(s)",
self.cfg.listen,
self.pool.size()
);
let graceful = GracefulShutdown::new();
let mut shutdown = std::pin::pin!(shutdown);
loop {
tokio::select! {
accepted = listener.accept() => {
let (stream, peer_addr) = match accepted {
Ok(x) => x,
Err(err) => {
tracing::error!("failed to accept connection: {err}");
continue;
}
};
let _ = stream.set_nodelay(true);
let svc = Svc::new(self.pool.clone(), peer_addr);
let conn = http1::Builder::new()
.timer(TokioTimer::new())
.header_read_timeout(HEADER_READ_TIMEOUT)
.serve_connection(TokioIo::new(stream), svc);
let conn = graceful.watch(conn);
tokio::spawn(async move {
if let Err(err) = conn.await {
tracing::error!("error serving connection: {err}");
}
});
}
_ = &mut shutdown => break,
}
}
tracing::info!("shutting down, waiting for in-flight requests");
tokio::select! {
_ = graceful.shutdown() => {}
_ = tokio::time::sleep(SHUTDOWN_GRACE) => {
tracing::warn!(
"graceful shutdown timed out after {SHUTDOWN_GRACE:?}, aborting connections"
);
}
}
Ok(())
}
}
impl ServerBuilder {
pub fn config(mut self, cfg: Config) -> Self {
self.cfg = cfg;
self
}
pub fn listen(mut self, addr: std::net::SocketAddr) -> Self {
self.cfg.listen = addr;
self
}
pub fn handler_script(mut self, path: impl Into<PathBuf>) -> Self {
self.cfg.handler_script = path.into();
self
}
pub fn config_script(mut self, path: impl Into<PathBuf>) -> Self {
self.cfg.config_script = Some(path.into());
self
}
pub fn templates_dir(mut self, path: impl Into<PathBuf>) -> Self {
self.cfg.templates_dir = Some(path.into());
self
}
pub fn database(mut self, path: impl Into<PathBuf>) -> Self {
self.cfg.database = Some(path.into());
self
}
pub fn builtins(mut self, builtins: Builtins) -> Self {
self.builtins = Some(builtins);
self
}
pub fn workers(mut self, n: usize) -> Self {
self.cfg.workers = n;
self
}
pub fn dev_mode(mut self, on: bool) -> Self {
self.cfg.dev_mode = on;
self
}
pub fn setup<F>(mut self, f: F) -> Self
where
F: Fn(&mlua::Lua) -> mlua::Result<()> + Send + Sync + 'static,
{
self.setup_fns.push(Box::new(f));
self
}
pub async fn build(self) -> Result<Server> {
let cfg = self.cfg;
let builtins = match self.builtins {
Some(b) => b,
None => cfg.builtins()?,
};
let workers = cfg.workers.max(1);
let mut bootstrap = new_runtime(&cfg, builtins, &self.setup_fns)?;
let snapshot = match &cfg.config_script {
Some(conf_src) => {
let db_name = Builtins::DATABASE
.global_name()
.expect("DATABASE is a single builtin flag");
let db = bootstrap.get_global::<Option<AnyUserData>>(db_name)?;
bootstrap.register_cfg_fn(conf_src, db).await?;
bootstrap.cfg_snapshot()?
}
None => None,
};
bootstrap.register_http_fn(&cfg.handler_script)?;
let mut runtimes = Vec::with_capacity(workers);
runtimes.push(bootstrap);
for _ in 1..workers {
let mut rt = new_runtime(&cfg, builtins, &self.setup_fns)?;
if let Some(snapshot) = &snapshot {
rt.set_cfg_snapshot(snapshot)?;
}
rt.register_http_fn(&cfg.handler_script)?;
runtimes.push(rt);
}
Ok(Server {
cfg,
pool: Arc::new(RuntimePool::new(runtimes)),
})
}
}
fn new_runtime(cfg: &Config, builtins: Builtins, setup_fns: &[SetupFn]) -> Result<Runtime> {
let rt = Runtime::new_with(cfg.runtime_opts()?)?;
rt.register_builtins(builtins, cfg)?;
for setup in setup_fns {
setup(rt.lua())?;
}
Ok(rt)
}