linkrs_server/
http_server.rs1use std::sync::Arc;
4
5use log::info;
6
7use crate::config::Config;
8use crate::storage::UndoTarget;
9use crate::storage::{
10 StorageClient, StorageOperationContextOps, StorageSchemaContextOps, StorageSnapshotOps,
11 StorageSyncContextOps,
12};
13use crate::HttpServer;
14use linkrs_core::error::DBResult;
15
16use super::shutdown::async_shutdown_signal;
17
18pub async fn start_http_server<
20 S: StorageClient
21 + StorageSchemaContextOps
22 + StorageSnapshotOps
23 + StorageSyncContextOps
24 + StorageOperationContextOps
25 + crate::storage::AutoCommitBatchOps
26 + crate::storage::AutoCommitGroupOps
27 + UndoTarget
28 + Clone
29 + Send
30 + Sync
31 + 'static,
32>(
33 server: Arc<HttpServer<S>>,
34 config: &Config,
35) -> DBResult<()> {
36 use axum::serve;
37 use tokio::net::TcpListener;
38
39 if config.server.http.https_enabled {
40 return Err(linkrs_core::DBError::validation(
41 "http.https_enabled is not served by this build; terminate TLS upstream and leave it disabled",
42 ));
43 }
44 if !config.server.http.enabled {
45 return Err(linkrs_core::DBError::validation(
46 "http.enabled is false; nothing to serve in http-only mode",
47 ));
48 }
49
50 let state = crate::http::AppState::new(server.clone());
51
52 let storage_path = format!("{}/metadata.db", config.storage_path());
54 let web_router = match crate::web::WebState::new(&storage_path, state.clone()).await {
55 Ok(web_state) => Some(crate::web::create_router(web_state)),
56 Err(e) => {
57 log::warn!(
58 "Failed to initialize web management: {}, continuing without it",
59 e
60 );
61 None
62 }
63 };
64
65 let app = crate::http::router::create_router(state, web_router);
66
67 let addr = format!("{}:{}", config.http_bind_address(), config.http_port());
68 let listener = TcpListener::bind(&addr).await?;
69
70 info!("HTTP server listening on {}", addr);
71
72 serve(listener, app)
73 .with_graceful_shutdown(async_shutdown_signal())
74 .await?;
75
76 Ok(())
77}
78
79#[cfg(feature = "grpc")]
81pub async fn start_http_and_grpc_servers<
82 S: StorageClient
83 + StorageSchemaContextOps
84 + StorageSnapshotOps
85 + StorageSyncContextOps
86 + StorageOperationContextOps
87 + crate::storage::AutoCommitBatchOps
88 + crate::storage::AutoCommitGroupOps
89 + Clone
90 + Send
91 + Sync
92 + 'static,
93>(
94 http_server: Arc<HttpServer<S>>,
95 config: &Config,
96) -> DBResult<()> {
97 use axum::serve;
98 use tokio::net::TcpListener;
99
100 if config.server.http.https_enabled {
101 return Err(linkrs_core::DBError::validation(
102 "http.https_enabled is not served by this build; terminate TLS upstream and leave it disabled",
103 ));
104 }
105
106 let http_state = crate::http::AppState::new(http_server.clone());
107
108 let storage_path = format!("{}/metadata.db", config.storage_path());
110 let web_router = match crate::web::WebState::new(&storage_path, http_state.clone()).await {
111 Ok(web_state) => Some(crate::web::create_router(web_state)),
112 Err(e) => {
113 log::warn!(
114 "Failed to initialize web management: {}, continuing without it",
115 e
116 );
117 None
118 }
119 };
120
121 let http_enabled = config.server.http.enabled;
122 let grpc_enabled = config.server.grpc.enabled;
123 if !http_enabled && !grpc_enabled {
124 return Err(linkrs_core::DBError::validation(
125 "at least one of http.enabled and grpc.enabled must be true",
126 ));
127 }
128
129 let http_app = crate::http::router::create_router(http_state.clone(), web_router);
130
131 let grpc_state = http_state.clone();
133 let grpc_config = config.clone();
134 let http_bind = config.http_bind_address().to_string();
135 let http_port = config.http_port();
136
137 let http_future = async move {
139 if !http_enabled {
140 async_shutdown_signal().await;
141 return Ok::<(), linkrs_core::error::DBError>(());
142 }
143 let http_addr = format!("{}:{}", http_bind, http_port);
144 let http_listener = TcpListener::bind(&http_addr).await?;
145 info!("HTTP server listening on {}", http_addr);
146 serve(http_listener, http_app)
147 .with_graceful_shutdown(async_shutdown_signal())
148 .await?;
149 Ok::<(), linkrs_core::error::DBError>(())
150 };
151
152 let grpc_future = async move {
154 if !grpc_enabled {
155 async_shutdown_signal().await;
156 return Ok::<(), linkrs_core::error::DBError>(());
157 }
158 let grpc_addr = format!(
159 "{}:{}",
160 grpc_state.server.get_config().grpc_bind_address(),
161 grpc_state.server.get_config().grpc_port()
162 )
163 .parse::<std::net::SocketAddr>()
164 .map_err(|e| linkrs_core::error::DBError::internal(e.to_string()))?;
165 info!("gRPC server listening on {}", grpc_addr);
166 crate::grpc::run_server(grpc_state, grpc_config, grpc_addr)
167 .await
168 .map_err(|e| linkrs_core::error::DBError::internal(e.to_string()))?;
169 Ok::<(), linkrs_core::error::DBError>(())
170 };
171
172 tokio::select! {
174 result = http_future => result?,
175 result = grpc_future => result?,
176 }
177
178 Ok(())
179}