use crate::RocketRoute;
use alux_http::{HttpServerAlg, HttpServerSetup};
use rocket::config::{LogLevel, Shutdown as ShutdownConfig};
use rocket::fairing::AdHoc;
#[cfg(unix)]
use std::collections::HashSet;
use std::io;
use tokio::sync::oneshot;
use tokio::task::JoinHandle;
type RocketServerError = Box<dyn std::error::Error + Send + Sync>;
const GRACE: u32 = 5;
const MERCY: u32 = 0;
#[derive(Debug, Default)]
pub struct RocketServer;
pub struct RocketOpen {
shutdown: rocket::Shutdown,
task: JoinHandle<Result<(), RocketServerError>>,
}
impl Drop for RocketOpen {
fn drop(&mut self) {
self.shutdown.clone().notify();
self.task.abort();
}
}
struct Launching(Option<rocket::Shutdown>);
impl Launching {
fn launched(&mut self) {
self.0 = None;
}
}
impl Drop for Launching {
fn drop(&mut self) {
if let Some(shutdown) = self.0.take() {
shutdown.notify();
}
}
}
fn rocket_error(error: &rocket::Error) -> RocketServerError {
io::Error::other(error.to_string()).into()
}
impl HttpServerAlg for RocketServer {
type Program = RocketRoute;
type Open = RocketOpen;
type Error = RocketServerError;
async fn open(&mut self, setup: HttpServerSetup<Self::Program>) -> Result<Self::Open, Self::Error> {
let (bind, route) = setup.into_parts();
let (listening, mut bound) = oneshot::channel();
let rocket = route
.mount(rocket::custom(rocket::Config {
address: bind.address().ip(),
port: bind.address().port(),
log_level: LogLevel::Off,
shutdown: ShutdownConfig {
grace: GRACE,
mercy: MERCY,
ctrlc: false,
#[cfg(unix)]
signals: HashSet::new(),
..ShutdownConfig::default()
},
..rocket::Config::default()
}))
.attach(AdHoc::on_liftoff("alux-http-rocket bound", move |_| {
Box::pin(async move {
let _ = listening.send(());
})
}))
.ignite()
.await
.map_err(|error| rocket_error(&error))?;
let shutdown = rocket.shutdown();
let mut launching = Launching(Some(shutdown.clone()));
let mut task =
tokio::task::spawn_local(async move { rocket.launch().await.map(|_| ()).map_err(|e| rocket_error(&e)) });
let open = tokio::select! {
result = &mut task => match result? {
Ok(()) => Err(io::Error::other("Rocket stopped before binding").into()),
Err(error) => Err(error),
},
Ok(()) = &mut bound => Ok(RocketOpen { shutdown, task }),
};
launching.launched();
open
}
async fn close(&mut self, open: &mut Self::Open) -> Result<(), Self::Error> {
open.shutdown.clone().notify();
if !open.task.is_finished() {
let _ = (&mut open.task).await;
}
Ok(())
}
async fn end(&mut self, open: &mut Self::Open) -> Result<(), Self::Error> {
self.close(open).await
}
}