1#![warn(clippy::unwrap_used)]
34
35use config::SgHttpRoute;
36pub use hyper;
37pub use spacegate_config::model;
38pub use spacegate_config::model::{BoxError, BoxResult};
39use spacegate_config::service::{CreateListener, Retrieve};
40use spacegate_config::Config;
41pub use spacegate_kernel as kernel;
43pub use spacegate_kernel::{SgBody, SgRequest, SgRequestExt, SgResponse, SgResponseExt};
44pub use spacegate_plugin as plugin;
46use tokio::signal;
47use tracing::{info, instrument};
48pub mod config;
50pub mod extension;
52pub mod server;
54
55pub mod ext_features;
57#[cfg(feature = "ext-axum")]
58pub use spacegate_ext_axum as ext_axum;
59#[cfg(feature = "ext-redis")]
60pub use spacegate_ext_redis as ext_redis;
61
62#[cfg(feature = "fs")]
63pub async fn startup_file(conf_dir: impl AsRef<std::path::Path>) -> Result<(), BoxError> {
66 use spacegate_config::service::{config_format::Json, fs::Fs};
67 let config = Fs::new(conf_dir, Json::default());
68 startup(config).await
69}
70#[cfg(feature = "k8s")]
71pub async fn startup_k8s(namespace: Option<&str>) -> Result<(), BoxError> {
75 use spacegate_config::service::k8s::K8s;
76 fn k8s_namespace_from_file() -> &'static str {
77 static NAMESPACE: std::sync::OnceLock<&'static str> = std::sync::OnceLock::new();
78 NAMESPACE.get_or_init(|| {
79 let ns = std::fs::read_to_string("/var/run/secrets/kubernetes.io/serviceaccount/namespace")
80 .expect("failed to read namespace from file, is this program running in a k8s pod?")
81 .trim()
82 .to_string()
83 .leak();
84 ns
85 })
86 }
87
88 let namespace = namespace.unwrap_or_else(|| k8s_namespace_from_file());
89 let config = K8s::with_default_client(namespace).await?;
90 startup(config).await
91}
92#[cfg(feature = "cache")]
93pub async fn startup_redis(url: impl Into<String>) -> Result<(), BoxError> {
96 use spacegate_config::service::{config_format::Json, redis::Redis};
97 let config = Redis::new(url.into(), Json::default())?;
98 startup(config).await
99}
100
101pub async fn startup_static(config: Config) -> Result<(), BoxError> {
104 use spacegate_config::service::memory::Memory;
105 let config = Memory::new(config);
106 startup(config).await
107}
108
109#[instrument(fields(listener = (L::CONFIG_LISTENER_NAME)), skip(config))]
115pub async fn startup<L>(config: L) -> Result<(), BoxError>
116where
117 L: CreateListener + Retrieve + 'static,
118{
119 info!("Spacegate Meta Info: {:?}", Meta::new());
120 info!("Starting gateway...");
121 config::startup_with_shutdown_signal(config, ctrl_c_cancel_token()).await
122}
123
124#[derive(Debug, Clone, Copy)]
125pub struct Meta {
126 pub version: &'static str,
127}
128
129impl Meta {
130 const DEFAULT: Meta = Self {
131 version: env!("CARGO_PKG_VERSION"),
132 };
133 pub const fn new() -> Self {
134 Self::DEFAULT
135 }
136}
137
138impl Default for Meta {
139 fn default() -> Self {
140 Self::DEFAULT
141 }
142}
143
144pub async fn wait_graceful_shutdown() {
145 match signal::ctrl_c().await {
146 Ok(_) => {
147 let instances = server::RunningSgGateway::global_store().lock().expect("fail to lock").drain().collect::<Vec<_>>();
148 for (_, inst) in instances {
149 inst.shutdown().await;
150 }
151 tracing::info!("Received ctrl+c signal, shutting down...");
152 }
153 Err(error) => {
154 tracing::error!("Received the ctrl+c signal, but with an error: {error}");
155 }
156 }
157}
158
159pub fn ctrl_c_cancel_token() -> tokio_util::sync::CancellationToken {
160 let cancel_token = tokio_util::sync::CancellationToken::new();
161 {
162 let cancel_token = cancel_token.clone();
163 tokio::spawn(async move {
164 let _ = tokio::signal::ctrl_c().await;
165 info!("Received ctrl+c signal, shutting down...");
166 cancel_token.cancel();
167 });
168 }
169 cancel_token
170}