1pub(crate) use crate::application::json_input::{
4 json_bool_field, json_f32_field, json_string_field, json_usize_field,
5};
6pub(crate) use crate::application::{
7 AdminUseCases, CreateDocumentInput, CreateEdgeInput, CreateEntityOutput, CreateKvInput,
8 CreateNodeEmbeddingInput, CreateNodeGraphLinkInput, CreateNodeInput, CreateNodeTableLinkInput,
9 CreateRowInput, CreateVectorInput, DeleteEntityInput, EntityUseCases, ExecuteQueryInput,
10 ExplainQueryInput, GraphCentralityInput, GraphClusteringInput, GraphCommunitiesInput,
11 GraphComponentsInput, GraphCyclesInput, GraphHitsInput, GraphNeighborhoodInput,
12 GraphPersonalizedPageRankInput, GraphShortestPathInput, GraphTopologicalSortInput,
13 GraphTraversalInput, GraphUseCases, InspectNativeArtifactInput, NativeUseCases,
14 PatchEntityInput, PatchEntityOperation, PatchEntityOperationType, SearchHybridInput,
15 SearchIvfInput, SearchMultimodalInput, SearchSimilarInput, SearchTextInput, TreeUseCases,
16};
17use std::collections::{BTreeMap, HashMap};
18use std::io::{self, Read, Write};
19use std::net::{TcpListener, TcpStream};
20use std::sync::atomic::{AtomicU64, Ordering};
21use std::thread;
22use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
23
24use std::sync::Arc;
25
26use crate::api::{RedDBError, RedDBOptions, RedDBResult};
27use crate::auth::store::AuthStore;
28use crate::catalog::{CatalogModelSnapshot, CollectionDescriptor, CollectionModel, SchemaMode};
29use crate::health::{HealthProvider, HealthReport, HealthState};
30use crate::json::{parse_json, to_vec as json_to_vec, Map, Value as JsonValue};
31use crate::runtime::{
32 RedDBRuntime, RuntimeFilter, RuntimeFilterValue, RuntimeGraphCentralityAlgorithm,
33 RuntimeGraphCentralityResult, RuntimeGraphClusteringResult, RuntimeGraphCommunityAlgorithm,
34 RuntimeGraphCommunityResult, RuntimeGraphComponentsMode, RuntimeGraphComponentsResult,
35 RuntimeGraphCyclesResult, RuntimeGraphDirection, RuntimeGraphHitsResult,
36 RuntimeGraphNeighborhoodResult, RuntimeGraphPathAlgorithm, RuntimeGraphPathResult,
37 RuntimeGraphPattern, RuntimeGraphProjection, RuntimeGraphTopologicalSortResult,
38 RuntimeGraphTraversalResult, RuntimeGraphTraversalStrategy, RuntimeIvfSearchResult,
39 RuntimeQueryWeights, RuntimeStats, ScanCursor, ScanPage,
40};
41use crate::storage::unified::devx::refs::{NodeRef, TableRef, VectorRef};
42use crate::storage::unified::dsl::{MatchComponents, QueryResult as DslQueryResult};
43use crate::storage::unified::{MetadataValue, RefTarget, SparseVector};
44use crate::storage::{CrossRef, EntityData, EntityId, EntityKind, SimilarResult, UnifiedEntity};
45use crate::transport::execution_admission::ExecutionAdmission;
46use reddb_types::Value;
47
48fn analytics_job_json(job: &crate::PhysicalAnalyticsJob) -> JsonValue {
49 crate::presentation::admin_json::analytics_job_json(job)
50}
51
52#[cfg(test)]
54pub(crate) static RED_ADMIN_TOKEN_ENV_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
55
56#[cfg(test)]
57mod tests {
58 use super::*;
59 use crate::api::RedDBOptions;
60 use crate::health::HealthReport;
61 use crate::transport::{TransportListenerFailure, TransportListenerState, TransportReadiness};
62
63 #[test]
64 fn server_options_default_http_body_limit_is_32_mib() {
65 assert_eq!(ServerOptions::default().max_body_bytes, 32 * 1024 * 1024);
66 }
67
68 #[test]
69 fn health_json_reports_transport_listeners() {
70 let runtime = RedDBRuntime::with_options(RedDBOptions::in_memory()).expect("runtime");
71 let mut options = ServerOptions::default();
72 options.transport_readiness = TransportReadiness {
73 active: vec![TransportListenerState {
74 transport: "grpc".to_string(),
75 bind_addr: "127.0.0.1:5000".to_string(),
76 explicit: true,
77 }],
78 failed: vec![TransportListenerFailure {
79 transport: "http".to_string(),
80 bind_addr: "127.0.0.1:5000".to_string(),
81 explicit: false,
82 reason: "http listener bind 127.0.0.1:5000: address in use".to_string(),
83 }],
84 };
85 let server = RedDBServer::with_options(runtime, options);
86
87 let payload = server.health_json_with_transport(&HealthReport::healthy());
88 let JsonValue::Object(root) = payload else {
89 panic!("health payload should be an object");
90 };
91 let Some(JsonValue::Object(listeners)) = root.get("transport_listeners") else {
92 panic!("health payload should include transport_listeners");
93 };
94 let Some(JsonValue::Array(active)) = listeners.get("active") else {
95 panic!("transport_listeners.active should be an array");
96 };
97 let Some(JsonValue::Array(failed)) = listeners.get("failed") else {
98 panic!("transport_listeners.failed should be an array");
99 };
100
101 assert_eq!(active.len(), 1);
102 assert_eq!(failed.len(), 1);
103 }
104}
105
106fn graph_projection_json(projection: &crate::PhysicalGraphProjection) -> JsonValue {
107 crate::presentation::admin_json::graph_projection_json(projection)
108}
109
110mod axum_edge;
111pub mod handlers_admin;
112mod handlers_admin_metrics;
113mod handlers_admin_status;
114mod handlers_ai;
115mod handlers_ai_model_cache;
116pub(crate) mod handlers_auth;
117mod handlers_backup;
118mod handlers_browser_auth;
119mod handlers_cache;
120mod handlers_capabilities;
121mod handlers_collection_policy;
122mod handlers_ec;
123pub(crate) mod handlers_entity;
124mod handlers_failover;
125mod handlers_geo;
126mod handlers_graph;
127mod handlers_iam_policy;
128mod handlers_keyed;
129mod handlers_log;
130mod handlers_metrics;
131mod handlers_ops;
132mod handlers_ops_policy;
133pub mod ui_bridge;
137mod ws_edge;
138pub mod ui_bundle_resolver;
142pub mod ui_auth;
145mod ui_static;
149pub mod ui_deeplink;
154pub(crate) mod handlers_query;
159mod handlers_replication;
160mod handlers_topology;
161mod handlers_vector;
162pub mod header_escape_guard;
163pub mod http_connection_limiter;
164pub mod http_handler_metrics;
165pub mod http_limits;
166pub mod http_principal_limiter;
167pub mod http_request_metrics;
168pub mod ingest_pipeline;
169pub mod output_stream;
170mod patch_support;
171mod request_body;
172mod request_context;
173mod request_identity;
174pub(crate) mod route_catalog;
175mod routes;
176pub(crate) use routes::discovered_route_catalog;
177mod routing;
178mod serverless_support;
179pub mod tls;
180mod transport;
181mod transport_surface;
182
183#[doc(hidden)]
187pub fn command_coverage_matrix() -> String {
188 route_catalog::render_command_coverage_matrix(routes::discovered_route_catalog())
189}
190
191use self::handlers_ai::*;
192use self::handlers_entity::*;
193use self::handlers_graph::*;
194use self::handlers_keyed::*;
195use self::handlers_metrics::*;
196use self::handlers_ops::*;
197use self::handlers_query::*;
198use self::http_connection_limiter::{
199 HandlerDeadline, HttpConnectionLimiter, MonotonicClock, SystemMonotonicClock,
200};
201use self::http_handler_metrics::{HttpHandlerMetrics, HttpRejectReason, HttpTransport};
202pub use self::http_limits::{
203 HttpLimitsCliInput, HttpLimitsResolved, DEFAULT_HANDLER_TIMEOUT_MS, DEFAULT_RETRY_AFTER_SECS,
204};
205use self::http_principal_limiter::PrincipalConnectionLimiter;
206use self::http_request_metrics::HttpRequestMetrics;
207use self::patch_support::*;
208use self::request_body::*;
209use self::routing::*;
210use self::serverless_support::*;
211use self::transport::*;
212
213pub(crate) fn command_catalog() -> &'static route_catalog::CommandCatalog {
214 routes::discovered_route_catalog()
215}
216
217#[derive(Debug, Clone, Copy, PartialEq, Eq)]
223pub enum ServerSurface {
224 Public,
226 AdminOnly,
230 MetricsOnly,
234}
235
236#[derive(Debug, Clone)]
237pub struct ServerOptions {
238 pub bind_addr: String,
239 pub max_body_bytes: usize,
240 pub read_timeout_ms: u64,
241 pub write_timeout_ms: u64,
242 pub max_scan_limit: usize,
243 pub surface: ServerSurface,
247 pub transport_readiness: crate::transport::TransportReadiness,
248 pub websocket_allowed_origins: Vec<String>,
256 pub ui_dir: Option<std::path::PathBuf>,
265}
266
267pub const DEFAULT_HTTP_MAX_BODY_BYTES: usize = 32 * 1024 * 1024;
268
269impl Default for ServerOptions {
270 fn default() -> Self {
271 Self {
272 bind_addr: "127.0.0.1:5000".to_string(),
273 max_body_bytes: DEFAULT_HTTP_MAX_BODY_BYTES,
274 read_timeout_ms: 5_000,
275 write_timeout_ms: 5_000,
276 max_scan_limit: 1_000,
277 surface: ServerSurface::Public,
278 transport_readiness: crate::transport::TransportReadiness::default(),
279 websocket_allowed_origins: Vec::new(),
280 ui_dir: None,
281 }
282 }
283}
284
285pub struct ServerReplicationState {
287 pub config: crate::replication::ReplicationConfig,
288 pub primary: Option<crate::replication::primary::PrimaryReplication>,
289}
290
291#[derive(Clone)]
292pub struct RedDBServer {
293 runtime: RedDBRuntime,
294 options: ServerOptions,
295 auth_store: Option<Arc<AuthStore>>,
296 replication: Option<Arc<ServerReplicationState>>,
297 http_limiter: HttpConnectionLimiter,
302 execution_admission: ExecutionAdmission,
306 health_limiter: HttpConnectionLimiter,
312 handler_timeout: Duration,
318 handler_clock: Arc<dyn MonotonicClock>,
325 slow_inject_ms: Arc<AtomicU64>,
332 http_metrics: HttpHandlerMetrics,
338 http_request_metrics: HttpRequestMetrics,
344 retry_after_secs: u64,
348 principal_limiter: PrincipalConnectionLimiter,
356 pub(crate) stream_capacity: Arc<output_stream::StreamCapacityRegistry>,
364 pub(crate) lease_registry: Arc<output_stream::LeaseRegistry>,
369 pub(crate) cursor_registry: Arc<output_stream::CursorRegistry>,
374}
375
376const DEFAULT_HANDLER_TIMEOUT: Duration = Duration::from_millis(30_000);
378
379const HEALTH_HANDLER_CAP: usize = 4;
382
383#[derive(Debug, Clone, Copy, PartialEq, Eq)]
384enum ServerlessWarmupScope {
385 Indexes,
386 GraphProjections,
387 AnalyticsJobs,
388 NativeArtifacts,
389}
390
391#[derive(Debug, Clone, Copy, PartialEq, Eq)]
392enum DeploymentProfile {
393 Embedded,
394 Server,
395 Serverless,
396}
397
398fn percent_decode_path_segment(input: &str) -> Result<String, String> {
399 let bytes = input.as_bytes();
400 let mut out = Vec::with_capacity(bytes.len());
401 let mut index = 0;
402 while index < bytes.len() {
403 match bytes[index] {
404 b'%' => {
405 if index + 2 >= bytes.len() {
406 return Err("truncated percent escape".to_string());
407 }
408 let high = hex_value(bytes[index + 1])
409 .ok_or_else(|| "invalid percent escape".to_string())?;
410 let low = hex_value(bytes[index + 2])
411 .ok_or_else(|| "invalid percent escape".to_string())?;
412 out.push((high << 4) | low);
413 index += 3;
414 }
415 byte => {
416 out.push(byte);
417 index += 1;
418 }
419 }
420 }
421 String::from_utf8(out).map_err(|_| "path segment is not valid UTF-8".to_string())
422}
423
424fn hex_value(byte: u8) -> Option<u8> {
425 match byte {
426 b'0'..=b'9' => Some(byte - b'0'),
427 b'a'..=b'f' => Some(byte - b'a' + 10),
428 b'A'..=b'F' => Some(byte - b'A' + 10),
429 _ => None,
430 }
431}
432
433#[derive(Debug, Clone)]
434struct ParsedQueryRequest {
435 query: String,
436 entity_types: Option<Vec<String>>,
437 capabilities: Option<Vec<String>>,
438 commit_policy: Option<crate::replication::CommitPolicy>,
439 params: Option<Vec<crate::runtime::query_request::ParamValue>>,
442}
443
444#[derive(Debug, Clone, Copy)]
445enum PatchOperationType {
446 Set,
447 Replace,
448 Unset,
449}
450
451#[derive(Debug, Clone)]
452struct PatchOperation {
453 op: PatchOperationType,
454 path: Vec<String>,
455 value: Option<JsonValue>,
456}
457
458impl RedDBServer {
459 pub fn new(runtime: RedDBRuntime) -> Self {
460 Self::with_options(runtime, ServerOptions::default())
461 }
462
463 pub fn from_database_options(
464 db_options: RedDBOptions,
465 server_options: ServerOptions,
466 ) -> RedDBResult<Self> {
467 let runtime = RedDBRuntime::with_options(db_options)?;
468 Ok(Self::with_options(runtime, server_options))
469 }
470
471 pub fn with_options(runtime: RedDBRuntime, options: ServerOptions) -> Self {
472 let http_limits = HttpLimitsResolved::builtin_defaults();
473 Self {
474 runtime,
475 options,
476 auth_store: None,
477 replication: None,
478 http_limiter: HttpConnectionLimiter::new(http_limits.max_handlers),
479 execution_admission: ExecutionAdmission::new(
480 http_limits.execution_workers,
481 http_limits
482 .max_handlers
483 .saturating_sub(http_limits.execution_workers),
484 ),
485 health_limiter: HttpConnectionLimiter::new(HEALTH_HANDLER_CAP),
486 handler_timeout: DEFAULT_HANDLER_TIMEOUT,
487 handler_clock: Arc::new(SystemMonotonicClock::new()),
488 slow_inject_ms: Arc::new(AtomicU64::new(0)),
489 http_metrics: HttpHandlerMetrics::new(),
490 http_request_metrics: HttpRequestMetrics::new(),
491 retry_after_secs: DEFAULT_RETRY_AFTER_SECS,
492 principal_limiter: PrincipalConnectionLimiter::new(
493 http_limits::DEFAULT_MAX_INFLIGHT_PER_PRINCIPAL,
494 ),
495 stream_capacity: output_stream::StreamCapacityRegistry::new(),
496 lease_registry: output_stream::LeaseRegistry::new(),
497 cursor_registry: output_stream::CursorRegistry::new(),
498 }
499 }
500
501 #[doc(hidden)]
502 pub fn stream_capacity(&self) -> &Arc<output_stream::StreamCapacityRegistry> {
503 &self.stream_capacity
504 }
505
506 #[doc(hidden)]
507 pub fn lease_registry(&self) -> &Arc<output_stream::LeaseRegistry> {
508 &self.lease_registry
509 }
510
511 #[doc(hidden)]
512 pub fn cursor_registry(&self) -> &Arc<output_stream::CursorRegistry> {
513 &self.cursor_registry
514 }
515
516 #[doc(hidden)]
517 pub fn http_metrics(&self) -> &HttpHandlerMetrics {
518 &self.http_metrics
519 }
520
521 #[doc(hidden)]
524 pub fn http_request_metrics(&self) -> &HttpRequestMetrics {
525 &self.http_request_metrics
526 }
527
528 #[doc(hidden)]
533 pub fn with_http_limiter_cap(mut self, cap: usize) -> Self {
534 self.http_limiter = HttpConnectionLimiter::new(cap);
535 let execution_workers = self.execution_admission.active_max().min(cap);
536 self.execution_admission =
537 ExecutionAdmission::new(execution_workers, cap.saturating_sub(execution_workers));
538 self
539 }
540
541 pub fn with_http_limits(mut self, limits: HttpLimitsResolved) -> Self {
546 self.http_limiter = HttpConnectionLimiter::new(limits.max_handlers);
547 self.execution_admission = ExecutionAdmission::new(
548 limits.execution_workers,
549 limits.max_handlers.saturating_sub(limits.execution_workers),
550 );
551 self.handler_timeout = Duration::from_millis(limits.handler_timeout_ms);
552 self.retry_after_secs = limits.retry_after_secs;
553 self.principal_limiter = PrincipalConnectionLimiter::new(limits.max_inflight_per_principal);
554 self
555 }
556
557 #[doc(hidden)]
562 pub fn with_principal_inflight_cap(mut self, cap: usize) -> Self {
563 self.principal_limiter = PrincipalConnectionLimiter::new(cap);
564 self
565 }
566
567 #[doc(hidden)]
568 pub fn principal_limiter(&self) -> &PrincipalConnectionLimiter {
569 &self.principal_limiter
570 }
571
572 #[doc(hidden)]
573 pub fn retry_after_secs(&self) -> u64 {
574 self.retry_after_secs
575 }
576
577 #[doc(hidden)]
578 pub fn http_limiter(&self) -> &HttpConnectionLimiter {
579 &self.http_limiter
580 }
581
582 pub(crate) fn execution_admission(&self) -> &ExecutionAdmission {
583 &self.execution_admission
584 }
585
586 #[doc(hidden)]
587 pub fn http_execution_active(&self) -> usize {
588 self.execution_admission.active()
589 }
590
591 #[doc(hidden)]
592 pub fn http_execution_queued(&self) -> usize {
593 self.execution_admission.queued()
594 }
595
596 #[doc(hidden)]
597 pub fn http_execution_workers(&self) -> usize {
598 self.execution_admission.active_max()
599 }
600
601 #[doc(hidden)]
603 pub fn with_http_execution_workers(mut self, workers: usize) -> Self {
604 assert!(workers > 0, "HTTP execution workers must be positive");
605 let workers = workers.min(self.http_limiter.cap());
606 self.execution_admission =
607 ExecutionAdmission::new(workers, self.http_limiter.cap().saturating_sub(workers));
608 self
609 }
610
611 #[doc(hidden)]
613 pub fn health_limiter(&self) -> &HttpConnectionLimiter {
614 &self.health_limiter
615 }
616
617 #[doc(hidden)]
619 pub fn with_health_limiter_cap(mut self, cap: usize) -> Self {
620 self.health_limiter = HttpConnectionLimiter::new(cap);
621 self
622 }
623
624 #[doc(hidden)]
627 pub fn with_handler_timeout(mut self, timeout: Duration) -> Self {
628 self.handler_timeout = timeout;
629 self
630 }
631
632 #[doc(hidden)]
633 pub fn handler_timeout(&self) -> Duration {
634 self.handler_timeout
635 }
636
637 #[doc(hidden)]
642 pub fn with_handler_clock(mut self, clock: Arc<dyn MonotonicClock>) -> Self {
643 self.handler_clock = clock;
644 self
645 }
646
647 #[doc(hidden)]
653 pub fn set_test_slow_inject_ms(&self, ms: u64) {
654 self.slow_inject_ms.store(ms, Ordering::Relaxed);
655 }
656
657 pub fn with_auth(mut self, auth_store: Arc<AuthStore>) -> Self {
661 self.runtime.set_auth_store(Arc::clone(&auth_store));
662 self.auth_store = Some(auth_store);
663 self
664 }
665
666 pub fn with_replication(mut self, state: Arc<ServerReplicationState>) -> Self {
668 self.replication = Some(state);
669 self
670 }
671
672 pub fn with_websocket_allowed_origins(mut self, origins: Vec<String>) -> Self {
677 self.options.websocket_allowed_origins = origins;
678 self
679 }
680
681 pub(crate) fn websocket_allowed_origins(&self) -> &[String] {
683 &self.options.websocket_allowed_origins
684 }
685
686 pub fn with_ui_dir(mut self, dir: std::path::PathBuf) -> Self {
692 self.options.ui_dir = Some(dir);
693 self
694 }
695
696 pub(crate) fn ui_dir(&self) -> Option<&std::path::Path> {
699 self.options.ui_dir.as_deref()
700 }
701
702 pub fn with_browser_token_authority(
709 self,
710 authority: Arc<crate::auth::browser_token::BrowserTokenAuthority>,
711 ) -> Self {
712 self.runtime.set_browser_token_authority(Some(authority));
713 self
714 }
715
716 pub fn runtime(&self) -> &RedDBRuntime {
717 &self.runtime
718 }
719
720 pub fn options(&self) -> &ServerOptions {
721 &self.options
722 }
723
724 fn admin_use_cases(&self) -> AdminUseCases<'_, RedDBRuntime> {
725 AdminUseCases::new(&self.runtime)
726 }
727
728 fn entity_use_cases(&self) -> EntityUseCases<'_, RedDBRuntime> {
729 EntityUseCases::new(&self.runtime)
730 }
731
732 fn graph_use_cases(&self) -> GraphUseCases<'_, RedDBRuntime> {
733 GraphUseCases::new(&self.runtime)
734 }
735
736 fn native_use_cases(&self) -> NativeUseCases<'_, RedDBRuntime> {
737 NativeUseCases::new(&self.runtime)
738 }
739
740 fn tree_use_cases(&self) -> TreeUseCases<'_, RedDBRuntime> {
741 TreeUseCases::new(&self.runtime)
742 }
743
744 fn transport_readiness_json(&self) -> JsonValue {
745 let active = self
746 .options
747 .transport_readiness
748 .active
749 .iter()
750 .map(|listener| {
751 let mut object = Map::new();
752 object.insert(
753 "transport".to_string(),
754 JsonValue::String(listener.transport.clone()),
755 );
756 object.insert(
757 "bind_addr".to_string(),
758 JsonValue::String(listener.bind_addr.clone()),
759 );
760 object.insert("explicit".to_string(), JsonValue::Bool(listener.explicit));
761 JsonValue::Object(object)
762 })
763 .collect();
764 let failed = self
765 .options
766 .transport_readiness
767 .failed
768 .iter()
769 .map(|listener| {
770 let mut object = Map::new();
771 object.insert(
772 "transport".to_string(),
773 JsonValue::String(listener.transport.clone()),
774 );
775 object.insert(
776 "bind_addr".to_string(),
777 JsonValue::String(listener.bind_addr.clone()),
778 );
779 object.insert("explicit".to_string(), JsonValue::Bool(listener.explicit));
780 object.insert(
781 "reason".to_string(),
782 JsonValue::String(listener.reason.clone()),
783 );
784 JsonValue::Object(object)
785 })
786 .collect();
787
788 let mut object = Map::new();
789 object.insert("active".to_string(), JsonValue::Array(active));
790 object.insert("failed".to_string(), JsonValue::Array(failed));
791 JsonValue::Object(object)
792 }
793
794 fn handle_grpc_discovery(&self) -> HttpResponse {
795 let mut methods = Map::new();
796 methods.insert(
797 "query".to_string(),
798 JsonValue::String("reddb.v1.RedDB/Query".to_string()),
799 );
800 methods.insert(
801 "batch_query".to_string(),
802 JsonValue::String("reddb.v1.RedDB/BatchQuery".to_string()),
803 );
804 methods.insert(
805 "health".to_string(),
806 JsonValue::String("reddb.v1.RedDB/Health".to_string()),
807 );
808 methods.insert(
809 "prepare".to_string(),
810 JsonValue::String("reddb.v1.RedDB/Prepare".to_string()),
811 );
812 methods.insert(
813 "execute_prepared".to_string(),
814 JsonValue::String("reddb.v1.RedDB/ExecutePrepared".to_string()),
815 );
816
817 let mut examples = Map::new();
818 examples.insert(
819 "query".to_string(),
820 JsonValue::String(
821 "grpcurl -plaintext -d '{\"query\":\"SELECT 1\"}' 127.0.0.1:5000 reddb.v1.RedDB/Query"
822 .to_string(),
823 ),
824 );
825 examples.insert(
826 "query_with_params".to_string(),
827 JsonValue::String(
828 "grpcurl -plaintext -d '{\"query\":\"SELECT $1 AS value\",\"params\":[{\"intValue\":42}]}' 127.0.0.1:5000 reddb.v1.RedDB/Query"
829 .to_string(),
830 ),
831 );
832 examples.insert(
833 "health".to_string(),
834 JsonValue::String(
835 "grpcurl -plaintext -d '{}' 127.0.0.1:5000 reddb.v1.RedDB/Health".to_string(),
836 ),
837 );
838
839 let mut object = Map::new();
840 object.insert("ok".to_string(), JsonValue::Bool(true));
841 object.insert(
842 "service".to_string(),
843 JsonValue::String("reddb.v1.RedDB".to_string()),
844 );
845 object.insert(
846 "package".to_string(),
847 JsonValue::String("reddb.v1".to_string()),
848 );
849 object.insert(
850 "proto".to_string(),
851 JsonValue::String("crates/reddb-grpc-proto/proto/reddb.proto".to_string()),
852 );
853 object.insert("methods".to_string(), JsonValue::Object(methods));
854 object.insert("examples".to_string(), JsonValue::Object(examples));
855 object.insert(
856 "transport_listeners".to_string(),
857 self.transport_readiness_json(),
858 );
859 object.insert(
860 "hint".to_string(),
861 JsonValue::String(
862 "If grpcurl cannot list services, pass the proto file with -import-path crates/reddb-grpc-proto/proto -proto reddb.proto."
863 .to_string(),
864 ),
865 );
866 json_response(200, JsonValue::Object(object))
867 }
868
869 fn handle_query_contract(&self) -> HttpResponse {
870 let mut examples = Map::new();
871 examples.insert(
872 "raw_sql".to_string(),
873 JsonValue::String("curl -sS http://127.0.0.1:5000/query -d 'SELECT 1'".to_string()),
874 );
875 examples.insert(
876 "json_query".to_string(),
877 JsonValue::String(
878 "curl -sS http://127.0.0.1:5000/query -H 'content-type: application/json' -d '{\"query\":\"SELECT 1\"}'"
879 .to_string(),
880 ),
881 );
882 examples.insert(
883 "json_query_with_params".to_string(),
884 JsonValue::String(
885 "curl -sS http://127.0.0.1:5000/query -H 'content-type: application/json' -d '{\"query\":\"SELECT $1 AS value\",\"params\":[42]}'"
886 .to_string(),
887 ),
888 );
889
890 let mut request_body = Map::new();
891 request_body.insert(
892 "query".to_string(),
893 JsonValue::String("required string".to_string()),
894 );
895 request_body.insert(
896 "params".to_string(),
897 JsonValue::String("optional array".to_string()),
898 );
899
900 let mut response_shape = Map::new();
901 response_shape.insert(
902 "columns".to_string(),
903 JsonValue::String("projected column names".to_string()),
904 );
905 response_shape.insert(
906 "records[].values".to_string(),
907 JsonValue::String("only projected values".to_string()),
908 );
909 response_shape.insert(
910 "records[].meta".to_string(),
911 JsonValue::String("internal metadata when present".to_string()),
912 );
913
914 let mut object = Map::new();
915 object.insert("ok".to_string(), JsonValue::Bool(false));
916 object.insert(
917 "code".to_string(),
918 JsonValue::String("method_not_allowed".to_string()),
919 );
920 object.insert(
921 "message".to_string(),
922 JsonValue::String("/query accepts POST requests".to_string()),
923 );
924 object.insert(
925 "hint".to_string(),
926 JsonValue::String(
927 "Send raw SQL in the body, or JSON with a string 'query' field.".to_string(),
928 ),
929 );
930 object.insert("method".to_string(), JsonValue::String("POST".to_string()));
931 object.insert("path".to_string(), JsonValue::String("/query".to_string()));
932 object.insert("request_body".to_string(), JsonValue::Object(request_body));
933 object.insert(
934 "response_shape".to_string(),
935 JsonValue::Object(response_shape),
936 );
937 object.insert("examples".to_string(), JsonValue::Object(examples));
938 object.insert(
939 "docs".to_string(),
940 JsonValue::String("https://reddb.io/docs/query".to_string()),
941 );
942
943 json_response(405, JsonValue::Object(object))
944 .with_header("Allow", http::HeaderValue::from_static("POST"))
945 }
946
947 fn handle_root_discovery(&self) -> HttpResponse {
948 let mut endpoints = Map::new();
949 endpoints.insert(
950 "health".to_string(),
951 JsonValue::String("GET /health".to_string()),
952 );
953 endpoints.insert(
954 "ready".to_string(),
955 JsonValue::String("GET /ready".to_string()),
956 );
957 endpoints.insert(
958 "query".to_string(),
959 JsonValue::String("POST /query".to_string()),
960 );
961 endpoints.insert(
962 "query_readiness".to_string(),
963 JsonValue::String("GET /ready/query".to_string()),
964 );
965 endpoints.insert(
966 "catalog".to_string(),
967 JsonValue::String("GET /catalog".to_string()),
968 );
969 endpoints.insert(
970 "deployment_profiles".to_string(),
971 JsonValue::String("GET /deployment/profiles".to_string()),
972 );
973
974 let mut examples = Map::new();
975 examples.insert(
976 "http_raw_sql".to_string(),
977 JsonValue::String("curl -sS http://127.0.0.1:5000/query -d 'SELECT 1'".to_string()),
978 );
979 examples.insert(
980 "http_json_query".to_string(),
981 JsonValue::String(
982 "curl -sS http://127.0.0.1:5000/query -H 'content-type: application/json' -d '{\"query\":\"SELECT 1\"}'"
983 .to_string(),
984 ),
985 );
986 examples.insert(
987 "http_json_query_with_params".to_string(),
988 JsonValue::String(
989 "curl -sS http://127.0.0.1:5000/query -H 'content-type: application/json' -d '{\"query\":\"SELECT $1 AS value\",\"params\":[42]}'"
990 .to_string(),
991 ),
992 );
993
994 let mut object = Map::new();
995 object.insert("ok".to_string(), JsonValue::Bool(true));
996 object.insert(
997 "service".to_string(),
998 JsonValue::String("reddb".to_string()),
999 );
1000 object.insert(
1001 "version".to_string(),
1002 JsonValue::String(env!("CARGO_PKG_VERSION").to_string()),
1003 );
1004 object.insert("endpoints".to_string(), JsonValue::Object(endpoints));
1005 object.insert("examples".to_string(), JsonValue::Object(examples));
1006 object.insert(
1007 "docs".to_string(),
1008 JsonValue::String("https://reddb.io/docs".to_string()),
1009 );
1010 object.insert(
1011 "transport_listeners".to_string(),
1012 self.transport_readiness_json(),
1013 );
1014 json_response(200, JsonValue::Object(object))
1015 }
1016
1017 fn health_json_with_transport(&self, report: &HealthReport) -> JsonValue {
1018 let mut value = crate::presentation::ops_json::health_json(report);
1019 if let JsonValue::Object(ref mut object) = value {
1020 object.insert(
1021 "transport_listeners".to_string(),
1022 self.transport_readiness_json(),
1023 );
1024 }
1025 value
1026 }
1027
1028 pub fn serve(&self) -> io::Result<()> {
1029 let listener = TcpListener::bind(&self.options.bind_addr)?;
1030 self.serve_on(listener)
1031 }
1032
1033 pub fn serve_on(&self, listener: TcpListener) -> io::Result<()> {
1040 let runtime = axum_edge::build_edge_runtime()?;
1041 runtime.block_on(
1042 self.clone()
1043 .serve_edge_on_std(listener, HttpTransport::Http),
1044 )
1045 }
1046
1047 pub fn serve_one_on(&self, listener: TcpListener) -> io::Result<()> {
1051 let runtime = axum_edge::build_background_edge_runtime()?;
1052 let server = self.clone();
1053 runtime.block_on(async move {
1054 listener.set_nonblocking(true)?;
1055 let listener = tokio::net::TcpListener::from_std(listener)?;
1056 let (stream, _peer) = listener.accept().await?;
1057 server.serve_edge_one(stream).await;
1058 Ok(())
1059 })
1060 }
1061
1062 pub fn serve_in_background(&self) -> thread::JoinHandle<io::Result<()>> {
1063 let server = self.clone();
1064 thread::spawn(move || server.serve())
1065 }
1066
1067 pub fn serve_in_background_on(
1068 &self,
1069 listener: TcpListener,
1070 ) -> thread::JoinHandle<io::Result<()>> {
1071 let server = self.clone();
1072 thread::spawn(move || {
1073 let runtime = axum_edge::build_background_edge_runtime()?;
1074 runtime.block_on(server.serve_edge_on_std(listener, HttpTransport::Http))
1075 })
1076 }
1077
1078 pub fn serve_tls(&self, tls_config: std::sync::Arc<rustls::ServerConfig>) -> io::Result<()> {
1082 let listener = TcpListener::bind(&self.options.bind_addr)?;
1083 self.serve_tls_on(listener, tls_config)
1084 }
1085
1086 pub fn serve_tls_on(
1087 &self,
1088 listener: TcpListener,
1089 tls_config: std::sync::Arc<rustls::ServerConfig>,
1090 ) -> io::Result<()> {
1091 let runtime = axum_edge::build_edge_runtime()?;
1092 let acceptor = axum_edge::tls_acceptor(tls_config);
1093 runtime.block_on(self.clone().serve_edge_tls_on_std(
1094 listener,
1095 acceptor,
1096 HttpTransport::Https,
1097 ))
1098 }
1099
1100 pub fn serve_tls_in_background(
1101 &self,
1102 tls_config: std::sync::Arc<rustls::ServerConfig>,
1103 ) -> thread::JoinHandle<io::Result<()>> {
1104 let server = self.clone();
1105 thread::spawn(move || server.serve_tls(tls_config))
1106 }
1107
1108 pub fn serve_tls_in_background_on(
1109 &self,
1110 listener: TcpListener,
1111 tls_config: std::sync::Arc<rustls::ServerConfig>,
1112 ) -> thread::JoinHandle<io::Result<()>> {
1113 let server = self.clone();
1114 thread::spawn(move || {
1115 let runtime = axum_edge::build_background_edge_runtime()?;
1116 let acceptor = axum_edge::tls_acceptor(tls_config);
1117 runtime.block_on(server.serve_edge_tls_on_std(listener, acceptor, HttpTransport::Https))
1118 })
1119 }
1120
1121 fn handle_connection(&self, stream: TcpStream) -> io::Result<()> {
1122 let started = Instant::now();
1123 let result = self.handle_connection_inner(stream);
1124 let elapsed = started.elapsed().as_secs_f64();
1125 self.http_metrics
1126 .record_duration(HttpTransport::Http, elapsed);
1127 result
1128 }
1129
1130 fn handle_connection_inner(&self, mut stream: TcpStream) -> io::Result<()> {
1131 stream.set_read_timeout(Some(Duration::from_millis(self.options.read_timeout_ms)))?;
1132 stream.set_write_timeout(Some(Duration::from_millis(self.options.write_timeout_ms)))?;
1133
1134 let deadline = HandlerDeadline::arm(Arc::clone(&self.handler_clock), self.handler_timeout);
1142
1143 let request = HttpRequest::read_from(&mut stream, self.options.max_body_bytes)?;
1144
1145 if deadline.expired() {
1147 self.http_metrics
1148 .record_reject(HttpTransport::Http, HttpRejectReason::HandlerTimeout);
1149 Self::write_handler_timeout_503(&mut stream);
1150 return Ok(());
1151 }
1152
1153 if self.try_route_streaming(&request, &mut stream)? {
1154 return Ok(());
1155 }
1156 let response = self.route(request);
1157
1158 let inject_ms = self.slow_inject_ms.load(Ordering::Relaxed);
1162 if inject_ms > 0 {
1163 thread::sleep(Duration::from_millis(inject_ms));
1164 }
1165
1166 if deadline.expired() {
1168 self.http_metrics
1169 .record_reject(HttpTransport::Http, HttpRejectReason::HandlerTimeout);
1170 Self::write_handler_timeout_503(&mut stream);
1171 return Ok(());
1172 }
1173
1174 response.write_to(&mut stream)?;
1175 stream.flush()?;
1176 Ok(())
1177 }
1178
1179 fn write_handler_timeout_503<S: Write>(stream: &mut S) {
1185 const RESPONSE: &[u8] = b"HTTP/1.1 503 Service Unavailable\r\n\
1186 Connection: close\r\n\
1187 Content-Length: 0\r\n\
1188 \r\n";
1189 let _ = stream.write_all(RESPONSE);
1190 let _ = stream.flush();
1191 }
1192}