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 reddb_types::Value;
46
47fn analytics_job_json(job: &crate::PhysicalAnalyticsJob) -> JsonValue {
48 crate::presentation::admin_json::analytics_job_json(job)
49}
50
51#[cfg(test)]
53pub(crate) static RED_ADMIN_TOKEN_ENV_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
54
55#[cfg(test)]
56mod tests {
57 use super::*;
58 use crate::api::RedDBOptions;
59 use crate::health::HealthReport;
60 use crate::transport::{TransportListenerFailure, TransportListenerState, TransportReadiness};
61
62 #[test]
63 fn server_options_default_http_body_limit_is_32_mib() {
64 assert_eq!(ServerOptions::default().max_body_bytes, 32 * 1024 * 1024);
65 }
66
67 #[test]
68 fn health_json_reports_transport_listeners() {
69 let runtime = RedDBRuntime::with_options(RedDBOptions::in_memory()).expect("runtime");
70 let mut options = ServerOptions::default();
71 options.transport_readiness = TransportReadiness {
72 active: vec![TransportListenerState {
73 transport: "grpc".to_string(),
74 bind_addr: "127.0.0.1:5000".to_string(),
75 explicit: true,
76 }],
77 failed: vec![TransportListenerFailure {
78 transport: "http".to_string(),
79 bind_addr: "127.0.0.1:5000".to_string(),
80 explicit: false,
81 reason: "http listener bind 127.0.0.1:5000: address in use".to_string(),
82 }],
83 };
84 let server = RedDBServer::with_options(runtime, options);
85
86 let payload = server.health_json_with_transport(&HealthReport::healthy());
87 let JsonValue::Object(root) = payload else {
88 panic!("health payload should be an object");
89 };
90 let Some(JsonValue::Object(listeners)) = root.get("transport_listeners") else {
91 panic!("health payload should include transport_listeners");
92 };
93 let Some(JsonValue::Array(active)) = listeners.get("active") else {
94 panic!("transport_listeners.active should be an array");
95 };
96 let Some(JsonValue::Array(failed)) = listeners.get("failed") else {
97 panic!("transport_listeners.failed should be an array");
98 };
99
100 assert_eq!(active.len(), 1);
101 assert_eq!(failed.len(), 1);
102 }
103}
104
105fn graph_projection_json(projection: &crate::PhysicalGraphProjection) -> JsonValue {
106 crate::presentation::admin_json::graph_projection_json(projection)
107}
108
109mod axum_edge;
110pub mod handlers_admin;
111mod handlers_admin_metrics;
112mod handlers_admin_status;
113mod handlers_ai;
114mod handlers_ai_model_cache;
115pub(crate) mod handlers_auth;
116mod handlers_backup;
117mod handlers_browser_auth;
118mod handlers_cache;
119mod handlers_capabilities;
120mod handlers_collection_policy;
121mod handlers_ec;
122pub(crate) mod handlers_entity;
123mod handlers_failover;
124mod handlers_geo;
125mod handlers_graph;
126mod handlers_iam_policy;
127mod handlers_keyed;
128mod handlers_log;
129mod handlers_metrics;
130mod handlers_ops;
131mod handlers_ops_policy;
132pub mod ui_bridge;
136mod ws_edge;
137pub mod ui_bundle_resolver;
141pub mod ui_auth;
144mod ui_static;
148pub mod ui_deeplink;
153pub(crate) mod handlers_query;
158mod handlers_replication;
159mod handlers_topology;
160mod handlers_vector;
161pub mod header_escape_guard;
162pub mod http_connection_limiter;
163pub mod http_handler_metrics;
164pub mod http_limits;
165pub mod http_principal_limiter;
166pub mod http_request_metrics;
167pub mod ingest_pipeline;
168pub mod output_stream;
169mod patch_support;
170mod request_body;
171mod request_context;
172mod request_identity;
173pub(crate) mod route_catalog;
174mod routes;
175pub(crate) use routes::discovered_route_catalog;
176mod routing;
177mod serverless_support;
178pub mod tls;
179mod transport;
180mod transport_surface;
181
182#[doc(hidden)]
186pub fn command_coverage_matrix() -> String {
187 route_catalog::render_command_coverage_matrix(routes::discovered_route_catalog())
188}
189
190use self::handlers_ai::*;
191use self::handlers_entity::*;
192use self::handlers_graph::*;
193use self::handlers_keyed::*;
194use self::handlers_metrics::*;
195use self::handlers_ops::*;
196use self::handlers_query::*;
197use self::http_connection_limiter::{
198 HandlerDeadline, HttpConnectionLimiter, MonotonicClock, SystemMonotonicClock,
199};
200use self::http_handler_metrics::{HttpHandlerMetrics, HttpRejectReason, HttpTransport};
201pub use self::http_limits::{
202 HttpLimitsCliInput, HttpLimitsResolved, DEFAULT_HANDLER_TIMEOUT_MS, DEFAULT_RETRY_AFTER_SECS,
203};
204use self::http_principal_limiter::PrincipalConnectionLimiter;
205use self::http_request_metrics::HttpRequestMetrics;
206use self::patch_support::*;
207use self::request_body::*;
208use self::routing::*;
209use self::serverless_support::*;
210use self::transport::*;
211
212pub(crate) fn command_catalog() -> &'static route_catalog::CommandCatalog {
213 routes::discovered_route_catalog()
214}
215
216#[derive(Debug, Clone, Copy, PartialEq, Eq)]
222pub enum ServerSurface {
223 Public,
225 AdminOnly,
229 MetricsOnly,
233}
234
235#[derive(Debug, Clone)]
236pub struct ServerOptions {
237 pub bind_addr: String,
238 pub max_body_bytes: usize,
239 pub read_timeout_ms: u64,
240 pub write_timeout_ms: u64,
241 pub max_scan_limit: usize,
242 pub surface: ServerSurface,
246 pub transport_readiness: crate::transport::TransportReadiness,
247 pub websocket_allowed_origins: Vec<String>,
255 pub ui_dir: Option<std::path::PathBuf>,
264}
265
266pub const DEFAULT_HTTP_MAX_BODY_BYTES: usize = 32 * 1024 * 1024;
267
268impl Default for ServerOptions {
269 fn default() -> Self {
270 Self {
271 bind_addr: "127.0.0.1:5000".to_string(),
272 max_body_bytes: DEFAULT_HTTP_MAX_BODY_BYTES,
273 read_timeout_ms: 5_000,
274 write_timeout_ms: 5_000,
275 max_scan_limit: 1_000,
276 surface: ServerSurface::Public,
277 transport_readiness: crate::transport::TransportReadiness::default(),
278 websocket_allowed_origins: Vec::new(),
279 ui_dir: None,
280 }
281 }
282}
283
284pub struct ServerReplicationState {
286 pub config: crate::replication::ReplicationConfig,
287 pub primary: Option<crate::replication::primary::PrimaryReplication>,
288}
289
290#[derive(Clone)]
291pub struct RedDBServer {
292 runtime: RedDBRuntime,
293 options: ServerOptions,
294 auth_store: Option<Arc<AuthStore>>,
295 replication: Option<Arc<ServerReplicationState>>,
296 http_limiter: HttpConnectionLimiter,
301 health_limiter: HttpConnectionLimiter,
307 handler_timeout: Duration,
313 handler_clock: Arc<dyn MonotonicClock>,
320 slow_inject_ms: Arc<AtomicU64>,
327 http_metrics: HttpHandlerMetrics,
333 http_request_metrics: HttpRequestMetrics,
339 retry_after_secs: u64,
343 principal_limiter: PrincipalConnectionLimiter,
351 pub(crate) stream_capacity: Arc<output_stream::StreamCapacityRegistry>,
359 pub(crate) lease_registry: Arc<output_stream::LeaseRegistry>,
364 pub(crate) cursor_registry: Arc<output_stream::CursorRegistry>,
369}
370
371const DEFAULT_HANDLER_TIMEOUT: Duration = Duration::from_millis(30_000);
373
374const HEALTH_HANDLER_CAP: usize = 4;
377
378#[derive(Debug, Clone, Copy, PartialEq, Eq)]
379enum ServerlessWarmupScope {
380 Indexes,
381 GraphProjections,
382 AnalyticsJobs,
383 NativeArtifacts,
384}
385
386#[derive(Debug, Clone, Copy, PartialEq, Eq)]
387enum DeploymentProfile {
388 Embedded,
389 Server,
390 Serverless,
391}
392
393fn percent_decode_path_segment(input: &str) -> Result<String, String> {
394 let bytes = input.as_bytes();
395 let mut out = Vec::with_capacity(bytes.len());
396 let mut index = 0;
397 while index < bytes.len() {
398 match bytes[index] {
399 b'%' => {
400 if index + 2 >= bytes.len() {
401 return Err("truncated percent escape".to_string());
402 }
403 let high = hex_value(bytes[index + 1])
404 .ok_or_else(|| "invalid percent escape".to_string())?;
405 let low = hex_value(bytes[index + 2])
406 .ok_or_else(|| "invalid percent escape".to_string())?;
407 out.push((high << 4) | low);
408 index += 3;
409 }
410 byte => {
411 out.push(byte);
412 index += 1;
413 }
414 }
415 }
416 String::from_utf8(out).map_err(|_| "path segment is not valid UTF-8".to_string())
417}
418
419fn hex_value(byte: u8) -> Option<u8> {
420 match byte {
421 b'0'..=b'9' => Some(byte - b'0'),
422 b'a'..=b'f' => Some(byte - b'a' + 10),
423 b'A'..=b'F' => Some(byte - b'A' + 10),
424 _ => None,
425 }
426}
427
428#[derive(Debug, Clone)]
429struct ParsedQueryRequest {
430 query: String,
431 entity_types: Option<Vec<String>>,
432 capabilities: Option<Vec<String>>,
433 commit_policy: Option<crate::replication::CommitPolicy>,
434 params: Option<Vec<crate::runtime::query_request::ParamValue>>,
437}
438
439#[derive(Debug, Clone, Copy)]
440enum PatchOperationType {
441 Set,
442 Replace,
443 Unset,
444}
445
446#[derive(Debug, Clone)]
447struct PatchOperation {
448 op: PatchOperationType,
449 path: Vec<String>,
450 value: Option<JsonValue>,
451}
452
453impl RedDBServer {
454 pub fn new(runtime: RedDBRuntime) -> Self {
455 Self::with_options(runtime, ServerOptions::default())
456 }
457
458 pub fn from_database_options(
459 db_options: RedDBOptions,
460 server_options: ServerOptions,
461 ) -> RedDBResult<Self> {
462 let runtime = RedDBRuntime::with_options(db_options)?;
463 Ok(Self::with_options(runtime, server_options))
464 }
465
466 pub fn with_options(runtime: RedDBRuntime, options: ServerOptions) -> Self {
467 Self {
468 runtime,
469 options,
470 auth_store: None,
471 replication: None,
472 http_limiter: HttpConnectionLimiter::with_default_cap(),
473 health_limiter: HttpConnectionLimiter::new(HEALTH_HANDLER_CAP),
474 handler_timeout: DEFAULT_HANDLER_TIMEOUT,
475 handler_clock: Arc::new(SystemMonotonicClock::new()),
476 slow_inject_ms: Arc::new(AtomicU64::new(0)),
477 http_metrics: HttpHandlerMetrics::new(),
478 http_request_metrics: HttpRequestMetrics::new(),
479 retry_after_secs: DEFAULT_RETRY_AFTER_SECS,
480 principal_limiter: PrincipalConnectionLimiter::new(
481 http_limits::DEFAULT_MAX_INFLIGHT_PER_PRINCIPAL,
482 ),
483 stream_capacity: output_stream::StreamCapacityRegistry::new(),
484 lease_registry: output_stream::LeaseRegistry::new(),
485 cursor_registry: output_stream::CursorRegistry::new(),
486 }
487 }
488
489 #[doc(hidden)]
490 pub fn stream_capacity(&self) -> &Arc<output_stream::StreamCapacityRegistry> {
491 &self.stream_capacity
492 }
493
494 #[doc(hidden)]
495 pub fn lease_registry(&self) -> &Arc<output_stream::LeaseRegistry> {
496 &self.lease_registry
497 }
498
499 #[doc(hidden)]
500 pub fn cursor_registry(&self) -> &Arc<output_stream::CursorRegistry> {
501 &self.cursor_registry
502 }
503
504 #[doc(hidden)]
505 pub fn http_metrics(&self) -> &HttpHandlerMetrics {
506 &self.http_metrics
507 }
508
509 #[doc(hidden)]
512 pub fn http_request_metrics(&self) -> &HttpRequestMetrics {
513 &self.http_request_metrics
514 }
515
516 #[doc(hidden)]
521 pub fn with_http_limiter_cap(mut self, cap: usize) -> Self {
522 self.http_limiter = HttpConnectionLimiter::new(cap);
523 self
524 }
525
526 pub fn with_http_limits(mut self, limits: HttpLimitsResolved) -> Self {
531 self.http_limiter = HttpConnectionLimiter::new(limits.max_handlers);
532 self.handler_timeout = Duration::from_millis(limits.handler_timeout_ms);
533 self.retry_after_secs = limits.retry_after_secs;
534 self.principal_limiter = PrincipalConnectionLimiter::new(limits.max_inflight_per_principal);
535 self
536 }
537
538 #[doc(hidden)]
543 pub fn with_principal_inflight_cap(mut self, cap: usize) -> Self {
544 self.principal_limiter = PrincipalConnectionLimiter::new(cap);
545 self
546 }
547
548 #[doc(hidden)]
549 pub fn principal_limiter(&self) -> &PrincipalConnectionLimiter {
550 &self.principal_limiter
551 }
552
553 #[doc(hidden)]
554 pub fn retry_after_secs(&self) -> u64 {
555 self.retry_after_secs
556 }
557
558 #[doc(hidden)]
559 pub fn http_limiter(&self) -> &HttpConnectionLimiter {
560 &self.http_limiter
561 }
562
563 #[doc(hidden)]
565 pub fn health_limiter(&self) -> &HttpConnectionLimiter {
566 &self.health_limiter
567 }
568
569 #[doc(hidden)]
571 pub fn with_health_limiter_cap(mut self, cap: usize) -> Self {
572 self.health_limiter = HttpConnectionLimiter::new(cap);
573 self
574 }
575
576 #[doc(hidden)]
579 pub fn with_handler_timeout(mut self, timeout: Duration) -> Self {
580 self.handler_timeout = timeout;
581 self
582 }
583
584 #[doc(hidden)]
585 pub fn handler_timeout(&self) -> Duration {
586 self.handler_timeout
587 }
588
589 #[doc(hidden)]
594 pub fn with_handler_clock(mut self, clock: Arc<dyn MonotonicClock>) -> Self {
595 self.handler_clock = clock;
596 self
597 }
598
599 #[doc(hidden)]
605 pub fn set_test_slow_inject_ms(&self, ms: u64) {
606 self.slow_inject_ms.store(ms, Ordering::Relaxed);
607 }
608
609 pub fn with_auth(mut self, auth_store: Arc<AuthStore>) -> Self {
613 self.runtime.set_auth_store(Arc::clone(&auth_store));
614 self.auth_store = Some(auth_store);
615 self
616 }
617
618 pub fn with_replication(mut self, state: Arc<ServerReplicationState>) -> Self {
620 self.replication = Some(state);
621 self
622 }
623
624 pub fn with_websocket_allowed_origins(mut self, origins: Vec<String>) -> Self {
629 self.options.websocket_allowed_origins = origins;
630 self
631 }
632
633 pub(crate) fn websocket_allowed_origins(&self) -> &[String] {
635 &self.options.websocket_allowed_origins
636 }
637
638 pub fn with_ui_dir(mut self, dir: std::path::PathBuf) -> Self {
644 self.options.ui_dir = Some(dir);
645 self
646 }
647
648 pub(crate) fn ui_dir(&self) -> Option<&std::path::Path> {
651 self.options.ui_dir.as_deref()
652 }
653
654 pub fn with_browser_token_authority(
661 self,
662 authority: Arc<crate::auth::browser_token::BrowserTokenAuthority>,
663 ) -> Self {
664 self.runtime.set_browser_token_authority(Some(authority));
665 self
666 }
667
668 pub fn runtime(&self) -> &RedDBRuntime {
669 &self.runtime
670 }
671
672 pub fn options(&self) -> &ServerOptions {
673 &self.options
674 }
675
676 fn admin_use_cases(&self) -> AdminUseCases<'_, RedDBRuntime> {
677 AdminUseCases::new(&self.runtime)
678 }
679
680 fn entity_use_cases(&self) -> EntityUseCases<'_, RedDBRuntime> {
681 EntityUseCases::new(&self.runtime)
682 }
683
684 fn graph_use_cases(&self) -> GraphUseCases<'_, RedDBRuntime> {
685 GraphUseCases::new(&self.runtime)
686 }
687
688 fn native_use_cases(&self) -> NativeUseCases<'_, RedDBRuntime> {
689 NativeUseCases::new(&self.runtime)
690 }
691
692 fn tree_use_cases(&self) -> TreeUseCases<'_, RedDBRuntime> {
693 TreeUseCases::new(&self.runtime)
694 }
695
696 fn transport_readiness_json(&self) -> JsonValue {
697 let active = self
698 .options
699 .transport_readiness
700 .active
701 .iter()
702 .map(|listener| {
703 let mut object = Map::new();
704 object.insert(
705 "transport".to_string(),
706 JsonValue::String(listener.transport.clone()),
707 );
708 object.insert(
709 "bind_addr".to_string(),
710 JsonValue::String(listener.bind_addr.clone()),
711 );
712 object.insert("explicit".to_string(), JsonValue::Bool(listener.explicit));
713 JsonValue::Object(object)
714 })
715 .collect();
716 let failed = self
717 .options
718 .transport_readiness
719 .failed
720 .iter()
721 .map(|listener| {
722 let mut object = Map::new();
723 object.insert(
724 "transport".to_string(),
725 JsonValue::String(listener.transport.clone()),
726 );
727 object.insert(
728 "bind_addr".to_string(),
729 JsonValue::String(listener.bind_addr.clone()),
730 );
731 object.insert("explicit".to_string(), JsonValue::Bool(listener.explicit));
732 object.insert(
733 "reason".to_string(),
734 JsonValue::String(listener.reason.clone()),
735 );
736 JsonValue::Object(object)
737 })
738 .collect();
739
740 let mut object = Map::new();
741 object.insert("active".to_string(), JsonValue::Array(active));
742 object.insert("failed".to_string(), JsonValue::Array(failed));
743 JsonValue::Object(object)
744 }
745
746 fn handle_grpc_discovery(&self) -> HttpResponse {
747 let mut methods = Map::new();
748 methods.insert(
749 "query".to_string(),
750 JsonValue::String("reddb.v1.RedDB/Query".to_string()),
751 );
752 methods.insert(
753 "batch_query".to_string(),
754 JsonValue::String("reddb.v1.RedDB/BatchQuery".to_string()),
755 );
756 methods.insert(
757 "health".to_string(),
758 JsonValue::String("reddb.v1.RedDB/Health".to_string()),
759 );
760 methods.insert(
761 "prepare".to_string(),
762 JsonValue::String("reddb.v1.RedDB/Prepare".to_string()),
763 );
764 methods.insert(
765 "execute_prepared".to_string(),
766 JsonValue::String("reddb.v1.RedDB/ExecutePrepared".to_string()),
767 );
768
769 let mut examples = Map::new();
770 examples.insert(
771 "query".to_string(),
772 JsonValue::String(
773 "grpcurl -plaintext -d '{\"query\":\"SELECT 1\"}' 127.0.0.1:5000 reddb.v1.RedDB/Query"
774 .to_string(),
775 ),
776 );
777 examples.insert(
778 "query_with_params".to_string(),
779 JsonValue::String(
780 "grpcurl -plaintext -d '{\"query\":\"SELECT $1 AS value\",\"params\":[{\"intValue\":42}]}' 127.0.0.1:5000 reddb.v1.RedDB/Query"
781 .to_string(),
782 ),
783 );
784 examples.insert(
785 "health".to_string(),
786 JsonValue::String(
787 "grpcurl -plaintext -d '{}' 127.0.0.1:5000 reddb.v1.RedDB/Health".to_string(),
788 ),
789 );
790
791 let mut object = Map::new();
792 object.insert("ok".to_string(), JsonValue::Bool(true));
793 object.insert(
794 "service".to_string(),
795 JsonValue::String("reddb.v1.RedDB".to_string()),
796 );
797 object.insert(
798 "package".to_string(),
799 JsonValue::String("reddb.v1".to_string()),
800 );
801 object.insert(
802 "proto".to_string(),
803 JsonValue::String("crates/reddb-grpc-proto/proto/reddb.proto".to_string()),
804 );
805 object.insert("methods".to_string(), JsonValue::Object(methods));
806 object.insert("examples".to_string(), JsonValue::Object(examples));
807 object.insert(
808 "transport_listeners".to_string(),
809 self.transport_readiness_json(),
810 );
811 object.insert(
812 "hint".to_string(),
813 JsonValue::String(
814 "If grpcurl cannot list services, pass the proto file with -import-path crates/reddb-grpc-proto/proto -proto reddb.proto."
815 .to_string(),
816 ),
817 );
818 json_response(200, JsonValue::Object(object))
819 }
820
821 fn handle_query_contract(&self) -> HttpResponse {
822 let mut examples = Map::new();
823 examples.insert(
824 "raw_sql".to_string(),
825 JsonValue::String("curl -sS http://127.0.0.1:5000/query -d 'SELECT 1'".to_string()),
826 );
827 examples.insert(
828 "json_query".to_string(),
829 JsonValue::String(
830 "curl -sS http://127.0.0.1:5000/query -H 'content-type: application/json' -d '{\"query\":\"SELECT 1\"}'"
831 .to_string(),
832 ),
833 );
834 examples.insert(
835 "json_query_with_params".to_string(),
836 JsonValue::String(
837 "curl -sS http://127.0.0.1:5000/query -H 'content-type: application/json' -d '{\"query\":\"SELECT $1 AS value\",\"params\":[42]}'"
838 .to_string(),
839 ),
840 );
841
842 let mut request_body = Map::new();
843 request_body.insert(
844 "query".to_string(),
845 JsonValue::String("required string".to_string()),
846 );
847 request_body.insert(
848 "params".to_string(),
849 JsonValue::String("optional array".to_string()),
850 );
851
852 let mut response_shape = Map::new();
853 response_shape.insert(
854 "columns".to_string(),
855 JsonValue::String("projected column names".to_string()),
856 );
857 response_shape.insert(
858 "records[].values".to_string(),
859 JsonValue::String("only projected values".to_string()),
860 );
861 response_shape.insert(
862 "records[].meta".to_string(),
863 JsonValue::String("internal metadata when present".to_string()),
864 );
865
866 let mut object = Map::new();
867 object.insert("ok".to_string(), JsonValue::Bool(false));
868 object.insert(
869 "code".to_string(),
870 JsonValue::String("method_not_allowed".to_string()),
871 );
872 object.insert(
873 "message".to_string(),
874 JsonValue::String("/query accepts POST requests".to_string()),
875 );
876 object.insert(
877 "hint".to_string(),
878 JsonValue::String(
879 "Send raw SQL in the body, or JSON with a string 'query' field.".to_string(),
880 ),
881 );
882 object.insert("method".to_string(), JsonValue::String("POST".to_string()));
883 object.insert("path".to_string(), JsonValue::String("/query".to_string()));
884 object.insert("request_body".to_string(), JsonValue::Object(request_body));
885 object.insert(
886 "response_shape".to_string(),
887 JsonValue::Object(response_shape),
888 );
889 object.insert("examples".to_string(), JsonValue::Object(examples));
890 object.insert(
891 "docs".to_string(),
892 JsonValue::String("https://reddb.io/docs/query".to_string()),
893 );
894
895 json_response(405, JsonValue::Object(object))
896 .with_header("Allow", http::HeaderValue::from_static("POST"))
897 }
898
899 fn handle_root_discovery(&self) -> HttpResponse {
900 let mut endpoints = Map::new();
901 endpoints.insert(
902 "health".to_string(),
903 JsonValue::String("GET /health".to_string()),
904 );
905 endpoints.insert(
906 "ready".to_string(),
907 JsonValue::String("GET /ready".to_string()),
908 );
909 endpoints.insert(
910 "query".to_string(),
911 JsonValue::String("POST /query".to_string()),
912 );
913 endpoints.insert(
914 "query_readiness".to_string(),
915 JsonValue::String("GET /ready/query".to_string()),
916 );
917 endpoints.insert(
918 "catalog".to_string(),
919 JsonValue::String("GET /catalog".to_string()),
920 );
921 endpoints.insert(
922 "deployment_profiles".to_string(),
923 JsonValue::String("GET /deployment/profiles".to_string()),
924 );
925
926 let mut examples = Map::new();
927 examples.insert(
928 "http_raw_sql".to_string(),
929 JsonValue::String("curl -sS http://127.0.0.1:5000/query -d 'SELECT 1'".to_string()),
930 );
931 examples.insert(
932 "http_json_query".to_string(),
933 JsonValue::String(
934 "curl -sS http://127.0.0.1:5000/query -H 'content-type: application/json' -d '{\"query\":\"SELECT 1\"}'"
935 .to_string(),
936 ),
937 );
938 examples.insert(
939 "http_json_query_with_params".to_string(),
940 JsonValue::String(
941 "curl -sS http://127.0.0.1:5000/query -H 'content-type: application/json' -d '{\"query\":\"SELECT $1 AS value\",\"params\":[42]}'"
942 .to_string(),
943 ),
944 );
945
946 let mut object = Map::new();
947 object.insert("ok".to_string(), JsonValue::Bool(true));
948 object.insert(
949 "service".to_string(),
950 JsonValue::String("reddb".to_string()),
951 );
952 object.insert(
953 "version".to_string(),
954 JsonValue::String(env!("CARGO_PKG_VERSION").to_string()),
955 );
956 object.insert("endpoints".to_string(), JsonValue::Object(endpoints));
957 object.insert("examples".to_string(), JsonValue::Object(examples));
958 object.insert(
959 "docs".to_string(),
960 JsonValue::String("https://reddb.io/docs".to_string()),
961 );
962 object.insert(
963 "transport_listeners".to_string(),
964 self.transport_readiness_json(),
965 );
966 json_response(200, JsonValue::Object(object))
967 }
968
969 fn health_json_with_transport(&self, report: &HealthReport) -> JsonValue {
970 let mut value = crate::presentation::ops_json::health_json(report);
971 if let JsonValue::Object(ref mut object) = value {
972 object.insert(
973 "transport_listeners".to_string(),
974 self.transport_readiness_json(),
975 );
976 }
977 value
978 }
979
980 pub fn serve(&self) -> io::Result<()> {
981 let listener = TcpListener::bind(&self.options.bind_addr)?;
982 self.serve_on(listener)
983 }
984
985 pub fn serve_on(&self, listener: TcpListener) -> io::Result<()> {
992 let runtime = axum_edge::build_edge_runtime()?;
993 runtime.block_on(
994 self.clone()
995 .serve_edge_on_std(listener, HttpTransport::Http),
996 )
997 }
998
999 pub fn serve_one_on(&self, listener: TcpListener) -> io::Result<()> {
1003 let runtime = axum_edge::build_background_edge_runtime()?;
1004 let server = self.clone();
1005 runtime.block_on(async move {
1006 listener.set_nonblocking(true)?;
1007 let listener = tokio::net::TcpListener::from_std(listener)?;
1008 let (stream, _peer) = listener.accept().await?;
1009 server.serve_edge_one(stream).await;
1010 Ok(())
1011 })
1012 }
1013
1014 pub fn serve_in_background(&self) -> thread::JoinHandle<io::Result<()>> {
1015 let server = self.clone();
1016 thread::spawn(move || server.serve())
1017 }
1018
1019 pub fn serve_in_background_on(
1020 &self,
1021 listener: TcpListener,
1022 ) -> thread::JoinHandle<io::Result<()>> {
1023 let server = self.clone();
1024 thread::spawn(move || {
1025 let runtime = axum_edge::build_background_edge_runtime()?;
1026 runtime.block_on(server.serve_edge_on_std(listener, HttpTransport::Http))
1027 })
1028 }
1029
1030 pub fn serve_tls(&self, tls_config: std::sync::Arc<rustls::ServerConfig>) -> io::Result<()> {
1034 let listener = TcpListener::bind(&self.options.bind_addr)?;
1035 self.serve_tls_on(listener, tls_config)
1036 }
1037
1038 pub fn serve_tls_on(
1039 &self,
1040 listener: TcpListener,
1041 tls_config: std::sync::Arc<rustls::ServerConfig>,
1042 ) -> io::Result<()> {
1043 let runtime = axum_edge::build_edge_runtime()?;
1044 let acceptor = axum_edge::tls_acceptor(tls_config);
1045 runtime.block_on(self.clone().serve_edge_tls_on_std(
1046 listener,
1047 acceptor,
1048 HttpTransport::Https,
1049 ))
1050 }
1051
1052 pub fn serve_tls_in_background(
1053 &self,
1054 tls_config: std::sync::Arc<rustls::ServerConfig>,
1055 ) -> thread::JoinHandle<io::Result<()>> {
1056 let server = self.clone();
1057 thread::spawn(move || server.serve_tls(tls_config))
1058 }
1059
1060 pub fn serve_tls_in_background_on(
1061 &self,
1062 listener: TcpListener,
1063 tls_config: std::sync::Arc<rustls::ServerConfig>,
1064 ) -> thread::JoinHandle<io::Result<()>> {
1065 let server = self.clone();
1066 thread::spawn(move || {
1067 let runtime = axum_edge::build_background_edge_runtime()?;
1068 let acceptor = axum_edge::tls_acceptor(tls_config);
1069 runtime.block_on(server.serve_edge_tls_on_std(listener, acceptor, HttpTransport::Https))
1070 })
1071 }
1072
1073 fn handle_connection(&self, stream: TcpStream) -> io::Result<()> {
1074 let started = Instant::now();
1075 let result = self.handle_connection_inner(stream);
1076 let elapsed = started.elapsed().as_secs_f64();
1077 self.http_metrics
1078 .record_duration(HttpTransport::Http, elapsed);
1079 result
1080 }
1081
1082 fn handle_connection_inner(&self, mut stream: TcpStream) -> io::Result<()> {
1083 stream.set_read_timeout(Some(Duration::from_millis(self.options.read_timeout_ms)))?;
1084 stream.set_write_timeout(Some(Duration::from_millis(self.options.write_timeout_ms)))?;
1085
1086 let deadline = HandlerDeadline::arm(Arc::clone(&self.handler_clock), self.handler_timeout);
1094
1095 let request = HttpRequest::read_from(&mut stream, self.options.max_body_bytes)?;
1096
1097 if deadline.expired() {
1099 self.http_metrics
1100 .record_reject(HttpTransport::Http, HttpRejectReason::HandlerTimeout);
1101 Self::write_handler_timeout_503(&mut stream);
1102 return Ok(());
1103 }
1104
1105 if self.try_route_streaming(&request, &mut stream)? {
1106 return Ok(());
1107 }
1108 let response = self.route(request);
1109
1110 let inject_ms = self.slow_inject_ms.load(Ordering::Relaxed);
1114 if inject_ms > 0 {
1115 thread::sleep(Duration::from_millis(inject_ms));
1116 }
1117
1118 if deadline.expired() {
1120 self.http_metrics
1121 .record_reject(HttpTransport::Http, HttpRejectReason::HandlerTimeout);
1122 Self::write_handler_timeout_503(&mut stream);
1123 return Ok(());
1124 }
1125
1126 response.write_to(&mut stream)?;
1127 stream.flush()?;
1128 Ok(())
1129 }
1130
1131 fn write_handler_timeout_503<S: Write>(stream: &mut S) {
1137 const RESPONSE: &[u8] = b"HTTP/1.1 503 Service Unavailable\r\n\
1138 Connection: close\r\n\
1139 Content-Length: 0\r\n\
1140 \r\n";
1141 let _ = stream.write_all(RESPONSE);
1142 let _ = stream.flush();
1143 }
1144}