use core::time::Duration;
use std::{cell::Cell, env, sync::Arc};
use humantime::format_duration;
use tokio::{net::ToSocketAddrs, signal};
use crate::{
catcher::Catcher,
dyn_mod::{DmRouter, Module, ModuleBuilder},
http::{Mime, uri::Scheme},
mode::{Mode, current_mode},
mw::*,
otel,
prelude::*,
};
#[macro_export]
macro_rules! new_app {
($($module:ty $(, $module_more:ty)*)*) => {
{
$crate::dyn_mod::dyn_modules![$($module $(, $module_more)*)*];
App::<DynModules>::new(
env!("CARGO_PKG_NAME"),
env!("CARGO_PKG_VERSION"),
DynModules::builder(),
)
}
};
}
pub const ENV_DIPPER_WORKER_ID: &str = "DIPPER_WORKER_ID";
pub const ENV_DIPPER_SHUTDOWN_SECS: &str = "DIPPER_SHUTDOWN_SECS";
pub struct App<M: Module> {
name: &'static str,
version: &'static str,
router: Router,
module_builder: Cell<Option<ModuleBuilder<M>>>,
service: Service,
unroutable_scribe: Arc<dyn UnroutableScribe>,
routable_preset: bool,
router_path_prefix: String,
}
impl<M: Module> App<M> {
pub fn new(name: &'static str, version: &'static str, module_builder: ModuleBuilder<M>) -> Self {
Self {
name,
version,
router: Router::new(),
module_builder: Cell::new(Some(module_builder)),
service: Service::new(Router::new()),
unroutable_scribe: Arc::new(DefaultUnroutableScribe),
routable_preset: false,
router_path_prefix: String::new(),
}
}
pub fn with_component_parameters<C: Component<M>>(self, params: C::Parameters) -> Self
where
M: HasComponent<C::Interface>,
{
self.set_module_builder(self.take_module_builder().with_component_parameters::<C>(params));
self
}
pub fn with_component_override<I: Interface + ?Sized>(self, component: Box<I>) -> Self
where
M: HasComponent<I>,
{
self.set_module_builder(self.take_module_builder().with_component_override::<I>(component));
self
}
pub fn with_component_override_fn<I: Interface + ?Sized>(self, component_fn: ComponentFn<M, I>) -> Self
where
M: HasComponent<I>,
{
self.set_module_builder(self.take_module_builder().with_component_override_fn::<I>(component_fn));
self
}
fn take_module_builder(&self) -> ModuleBuilder<M> {
self.module_builder.take().unwrap()
}
fn set_module_builder(&self, module_builder: ModuleBuilder<M>) {
self.module_builder.set(Some(module_builder))
}
pub const fn with_routable_preset(mut self, routable_preset: bool) -> Self {
self.routable_preset = routable_preset;
self
}
pub fn with_unroutable_scribe(mut self, unroutable_scribe: Arc<dyn UnroutableScribe>) -> Self {
self.unroutable_scribe = unroutable_scribe;
self
}
#[inline]
pub fn path_prefix(mut self, path: impl Into<String>) -> Self {
let path: String = path.into();
let s = path.trim_ascii().trim_matches('/');
self.router_path_prefix = if s.is_empty() { s.to_owned() } else { "/".to_owned() + s };
self
}
pub fn router_push(mut self, router: Router) -> Self {
self.router = self.router.push(router);
self
}
#[inline]
pub fn dm_router_hoop<H: Handler>(mut self, hoop: H) -> Self {
self.router = self.router.hoop(hoop);
self
}
#[inline]
pub fn dm_router_hoop_when<H, F>(mut self, hoop: H, filter: F) -> Self
where
H: Handler,
F: Fn(&Request, &Depot) -> bool + Send + Sync + 'static,
{
self.router = self.router.hoop_when(hoop, filter);
self
}
#[inline]
pub fn router_then<F>(mut self, func: F) -> Self
where
F: FnOnce(Router) -> Router,
{
self.router = self.router.then(func);
self
}
#[inline]
pub fn router_scheme(mut self, scheme: Scheme) -> Self {
self.router = self.router.scheme(scheme);
self
}
#[inline]
pub fn router_host(mut self, host: impl Into<String>) -> Self {
self.router = self.router.host(host);
self
}
#[inline]
pub fn service_catcher(mut self, catcher: impl Into<Arc<Catcher>>) -> Self {
self.service = self.service.catcher(catcher);
self
}
#[inline]
pub fn service_hoop<H: Handler>(mut self, hoop: H) -> Self {
self.service = self.service.hoop(hoop);
self
}
#[inline]
pub fn service_hoop_when<H, F>(mut self, hoop: H, filter: F) -> Self
where
H: Handler,
F: Fn(&Request, &Depot) -> bool + Send + Sync + 'static,
{
self.service = self.service.hoop_when(hoop, filter);
self
}
#[inline]
pub fn allowed_media_types<T>(mut self, allowed_media_types: T) -> Self
where
T: Into<Arc<Vec<Mime>>>,
{
self.service = self.service.allowed_media_types(allowed_media_types);
self
}
#[allow(static_mut_refs)]
pub async fn run(mut self, local_addr: impl ToSocketAddrs + Send) {
let dm_router = DmRouter::new(std::mem::take(&mut self.router.hoops), self.unroutable_scribe);
let _ = self
.module_builder
.take()
.unwrap()
.with_router(dm_router.clone())
.with_routable_preset(self.routable_preset)
.build();
unsafe {
debug!("{}", MANIFEST_LIST.to_xml_string());
};
let mut root_router = Router::new();
if !self.router_path_prefix.is_empty() {
root_router = root_router.path(&self.router_path_prefix);
}
root_router = root_router.push(dm_router.into_inner()).push(self.router);
match current_mode() {
Mode::Prod => {}
_ => {
root_router = root_router.push(Router::with_path("/api-err.json").get(api_err_list));
let doc =
OpenApi::new(format!("{} API", self.name.to_uppercase()), self.version).merge_router(&root_router);
let doc_router_path = "/api-doc/openapi.json";
root_router = root_router
.push(doc.into_router(doc_router_path))
.push(SwaggerUi::new(self.router_path_prefix.clone() + doc_router_path).into_router("/api-doc"))
}
}
self.service.router = root_router.into();
let worker_id: u16 = env::var(ENV_DIPPER_WORKER_ID).map_or(0u16, |v| {
v.parse::<u16>()
.expect("The env 'DIPPER_WORKER_ID' must be 'u16' integer")
});
let mut hoops: Vec<Arc<dyn Handler>> = vec![
Arc::new(TraceHoop::new()),
Arc::new(Metrics::new(self.name.to_owned())),
Arc::new(RequestIdHoop::new(worker_id)),
Arc::new(CatchPanic::new()),
Arc::new(affix_state::inject(unsafe { &MANIFEST_LIST })),
];
hoops.append(&mut self.service.hoops);
self.service.hoops = hoops;
let acceptor = TcpListener::new(local_addr).bind().await;
let server = Server::new(acceptor);
let shutdown_secs = env::var(ENV_DIPPER_SHUTDOWN_SECS).map_or(0i64, |v| {
v.parse().expect("The env 'DIPPER_SHUTDOWN_SECS' must be integer.")
});
if shutdown_secs != 0 {
let handle = server.handle();
let shutdown_secs = if shutdown_secs > 0 {
#[allow(clippy::cast_sign_loss)]
Some(Duration::from_secs(shutdown_secs as u64))
} else {
None
};
tokio::spawn(async move {
if let Some(shutdown_secs) = shutdown_secs {
info!(
"Enable graceful shutdown with a timeout of {}.",
format_duration(shutdown_secs)
);
} else {
info!("Enable graceful shutdown without timeout.");
}
#[cfg(not(windows))]
{
let mut sigint_stream = signal::unix::signal(signal::unix::SignalKind::interrupt()).unwrap();
let mut sigterm_stream = signal::unix::signal(signal::unix::SignalKind::terminate()).unwrap();
tokio::select! {
_ = sigint_stream.recv() => {
println!("Received SIGINT");
},
_ = sigterm_stream.recv() => {
println!("Received SIGTERM");
},
}
}
#[cfg(windows)]
{
signal::ctrl_c().await.expect("failed to listen for ctrl-c event");
println!("Received ctrl-c event");
}
handle.stop_graceful(shutdown_secs);
otel::shutdown_all_providers();
});
} else {
info!("Disable graceful shutdown.");
}
server.serve(self.service).await;
}
}