#![warn(clippy::unwrap_used)]
use config::SgHttpRoute;
pub use hyper;
pub use spacegate_config::model;
pub use spacegate_config::model::{BoxError, BoxResult};
use spacegate_config::service::{CreateListener, Retrieve};
use spacegate_config::Config;
pub use spacegate_kernel as kernel;
pub use spacegate_kernel::{SgBody, SgRequest, SgRequestExt, SgResponse, SgResponseExt};
pub use spacegate_plugin as plugin;
use tokio::signal;
use tracing::{info, instrument};
pub mod config;
pub mod extension;
pub mod server;
pub mod ext_features;
#[cfg(feature = "ext-axum")]
pub use spacegate_ext_axum as ext_axum;
#[cfg(feature = "ext-redis")]
pub use spacegate_ext_redis as ext_redis;
#[cfg(feature = "fs")]
pub async fn startup_file(conf_dir: impl AsRef<std::path::Path>) -> Result<(), BoxError> {
use spacegate_config::service::{config_format::Json, fs::Fs};
let config = Fs::new(conf_dir, Json::default());
startup(config).await
}
#[cfg(feature = "k8s")]
pub async fn startup_k8s(namespace: Option<&str>) -> Result<(), BoxError> {
use spacegate_config::service::k8s::K8s;
fn k8s_namespace_from_file() -> &'static str {
static NAMESPACE: std::sync::OnceLock<&'static str> = std::sync::OnceLock::new();
NAMESPACE.get_or_init(|| {
let ns = std::fs::read_to_string("/var/run/secrets/kubernetes.io/serviceaccount/namespace")
.expect("failed to read namespace from file, is this program running in a k8s pod?")
.trim()
.to_string()
.leak();
ns
})
}
let namespace = namespace.unwrap_or_else(|| k8s_namespace_from_file());
let config = K8s::with_default_client(namespace).await?;
startup(config).await
}
#[cfg(feature = "cache")]
pub async fn startup_redis(url: impl Into<String>) -> Result<(), BoxError> {
use spacegate_config::service::{config_format::Json, redis::Redis};
let config = Redis::new(url.into(), Json::default())?;
startup(config).await
}
pub async fn startup_static(config: Config) -> Result<(), BoxError> {
use spacegate_config::service::memory::Memory;
let config = Memory::new(config);
startup(config).await
}
#[instrument(fields(listener = (L::CONFIG_LISTENER_NAME)), skip(config))]
pub async fn startup<L>(config: L) -> Result<(), BoxError>
where
L: CreateListener + Retrieve + 'static,
{
info!("Spacegate Meta Info: {:?}", Meta::new());
info!("Starting gateway...");
config::startup_with_shutdown_signal(config, ctrl_c_cancel_token()).await
}
#[derive(Debug, Clone, Copy)]
pub struct Meta {
pub version: &'static str,
}
impl Meta {
const DEFAULT: Meta = Self {
version: env!("CARGO_PKG_VERSION"),
};
pub const fn new() -> Self {
Self::DEFAULT
}
}
impl Default for Meta {
fn default() -> Self {
Self::DEFAULT
}
}
pub async fn wait_graceful_shutdown() {
match signal::ctrl_c().await {
Ok(_) => {
let instances = server::RunningSgGateway::global_store().lock().expect("fail to lock").drain().collect::<Vec<_>>();
for (_, inst) in instances {
inst.shutdown().await;
}
tracing::info!("Received ctrl+c signal, shutting down...");
}
Err(error) => {
tracing::error!("Received the ctrl+c signal, but with an error: {error}");
}
}
}
pub fn ctrl_c_cancel_token() -> tokio_util::sync::CancellationToken {
let cancel_token = tokio_util::sync::CancellationToken::new();
{
let cancel_token = cancel_token.clone();
tokio::spawn(async move {
let _ = tokio::signal::ctrl_c().await;
info!("Received ctrl+c signal, shutting down...");
cancel_token.cancel();
});
}
cancel_token
}