spacegate_shell/
lib.rs

1//! **A library-first, lightweight, high-performance, cloud-native supported API gateway🪐**
2//!
3//! ## 🚀 Installation
4//!
5//! see [installation.md](https://github.com/ideal-world/spacegate/blob/master/docs/k8s/installation.md)
6//!
7//! ## Special instructions for configuration
8//! ### Setting HTTP Route Priority
9//! You can specify the priority of an httproute by adding a priority field in the annotations section of the route.
10//! A higher value for the priority field indicates a higher priority. The httproute library stores the priority
11//! value using the i64 data type, so the maximum and minimum values for the priority are [i64::MAX]
12//! (https://doc.rust-lang.org/std/primitive.i64.html#associatedconstant.MAX) and
13//! [i64::MIN](https://doc.rust-lang.org/std/primitive.i64.html#associatedconstant.MIN) respectively.
14//!
15//! If the priority field is not present in an httproute, its priority will be default to 0, and the default priority
16//! will be determined based on the creation order (earlier routes will have higher priority).
17//!
18//! Note: Trace-level logs will print the contents of both the request and response bodies,
19//! potentially causing significant performance overhead. It is recommended to use debug level
20//! logs at most.
21//!
22//!
23//! ## startup
24//! ### static config
25//! see [`startup_static`]
26//! ### by config file
27//! see [`startup_file`]
28//! ### by k8s resource
29//! see [`startup_k8s`]
30//! ### by redis
31//! see [`startup_redis`]
32//!
33#![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;
41/// re-export spacegate_kernel
42pub use spacegate_kernel as kernel;
43pub use spacegate_kernel::{SgBody, SgRequest, SgRequestExt, SgResponse, SgResponseExt};
44/// re-export spacegate_plugin
45pub use spacegate_plugin as plugin;
46use tokio::signal;
47use tracing::{info, instrument};
48/// Configuration retrieval and event listener
49pub mod config;
50/// http extensions
51pub mod extension;
52/// Spacegate service creation
53pub mod server;
54
55/// Extended features
56pub 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")]
63/// # Startup the gateway by config file
64/// The `conf_dir` is the path of the configuration dir.
65pub 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")]
71/// # Startup the gateway by k8s resource
72/// The `namespace` is the k8s namespace.
73/// If the `namespace` is None, it will read from the file `/var/run/secrets/kubernetes.io/serviceaccount/namespace`.
74pub 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")]
93/// # Startup the gateway by redis
94/// The `url` is the redis url, and the json format will be used.
95pub 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
101/// # Startup the gateway by static config
102/// The `config` is the static config.
103pub 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/// # Startup the gateway
110/// The `config` could be any type that implements [`spacegate_config::service::CreateListener`] and [`spacegate_config::service::Retrieve`] trait.
111///
112/// ## Errors
113/// If the config is invalid, it will return a BoxError.
114#[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}