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_capabilities;
119mod handlers_collection_policy;
120mod handlers_ec;
121pub(crate) mod handlers_entity;
122mod handlers_failover;
123mod handlers_geo;
124mod handlers_graph;
125mod handlers_iam_policy;
126mod handlers_keyed;
127mod handlers_log;
128mod handlers_metrics;
129mod handlers_ops;
130mod handlers_ops_policy;
131pub mod ui_bridge;
135mod ws_edge;
136pub mod ui_bundle_resolver;
140pub mod ui_auth;
143mod ui_static;
147pub mod ui_deeplink;
152pub(crate) mod handlers_query;
157mod handlers_replication;
158mod handlers_topology;
159mod handlers_vector;
160pub mod header_escape_guard;
161pub mod http_connection_limiter;
162pub mod http_handler_metrics;
163pub mod http_limits;
164pub mod http_principal_limiter;
165pub mod http_request_metrics;
166pub mod ingest_pipeline;
167pub mod output_stream;
168mod patch_support;
169mod request_body;
170mod request_context;
171mod request_identity;
172pub(crate) mod route_catalog;
173mod routes;
174pub(crate) use routes::discovered_route_catalog;
175mod routing;
176mod serverless_support;
177pub mod tls;
178mod transport;
179mod transport_surface;
180
181#[doc(hidden)]
185pub fn command_coverage_matrix() -> String {
186 route_catalog::render_command_coverage_matrix(routes::discovered_route_catalog())
187}
188
189use self::handlers_ai::*;
190use self::handlers_entity::*;
191use self::handlers_graph::*;
192use self::handlers_keyed::*;
193use self::handlers_metrics::*;
194use self::handlers_ops::*;
195use self::handlers_query::*;
196use self::http_connection_limiter::{
197 HandlerDeadline, HttpConnectionLimiter, MonotonicClock, SystemMonotonicClock,
198};
199use self::http_handler_metrics::{HttpHandlerMetrics, HttpRejectReason, HttpTransport};
200pub use self::http_limits::{
201 HttpLimitsCliInput, HttpLimitsResolved, DEFAULT_HANDLER_TIMEOUT_MS, DEFAULT_RETRY_AFTER_SECS,
202};
203use self::http_principal_limiter::PrincipalConnectionLimiter;
204use self::http_request_metrics::HttpRequestMetrics;
205use self::patch_support::*;
206use self::request_body::*;
207use self::routing::*;
208use self::serverless_support::*;
209use self::transport::*;
210
211pub(crate) fn command_catalog() -> &'static route_catalog::CommandCatalog {
212 routes::discovered_route_catalog()
213}
214
215#[derive(Debug, Clone, Copy, PartialEq, Eq)]
221pub enum ServerSurface {
222 Public,
224 AdminOnly,
228 MetricsOnly,
232}
233
234#[derive(Debug, Clone)]
235pub struct ServerOptions {
236 pub bind_addr: String,
237 pub max_body_bytes: usize,
238 pub read_timeout_ms: u64,
239 pub write_timeout_ms: u64,
240 pub max_scan_limit: usize,
241 pub surface: ServerSurface,
245 pub transport_readiness: crate::transport::TransportReadiness,
246 pub websocket_allowed_origins: Vec<String>,
254 pub ui_dir: Option<std::path::PathBuf>,
263}
264
265pub const DEFAULT_HTTP_MAX_BODY_BYTES: usize = 32 * 1024 * 1024;
266
267impl Default for ServerOptions {
268 fn default() -> Self {
269 Self {
270 bind_addr: "127.0.0.1:5000".to_string(),
271 max_body_bytes: DEFAULT_HTTP_MAX_BODY_BYTES,
272 read_timeout_ms: 5_000,
273 write_timeout_ms: 5_000,
274 max_scan_limit: 1_000,
275 surface: ServerSurface::Public,
276 transport_readiness: crate::transport::TransportReadiness::default(),
277 websocket_allowed_origins: Vec::new(),
278 ui_dir: None,
279 }
280 }
281}
282
283pub struct ServerReplicationState {
285 pub config: crate::replication::ReplicationConfig,
286 pub primary: Option<crate::replication::primary::PrimaryReplication>,
287}
288
289#[derive(Clone)]
290pub struct RedDBServer {
291 runtime: RedDBRuntime,
292 options: ServerOptions,
293 auth_store: Option<Arc<AuthStore>>,
294 replication: Option<Arc<ServerReplicationState>>,
295 http_limiter: HttpConnectionLimiter,
300 handler_timeout: Duration,
306 handler_clock: Arc<dyn MonotonicClock>,
313 slow_inject_ms: Arc<AtomicU64>,
320 http_metrics: HttpHandlerMetrics,
326 http_request_metrics: HttpRequestMetrics,
332 retry_after_secs: u64,
336 principal_limiter: PrincipalConnectionLimiter,
344 pub(crate) stream_capacity: Arc<output_stream::StreamCapacityRegistry>,
352 pub(crate) lease_registry: Arc<output_stream::LeaseRegistry>,
357 pub(crate) cursor_registry: Arc<output_stream::CursorRegistry>,
362}
363
364const DEFAULT_HANDLER_TIMEOUT: Duration = Duration::from_millis(30_000);
366
367#[derive(Debug, Clone, Copy, PartialEq, Eq)]
368enum ServerlessWarmupScope {
369 Indexes,
370 GraphProjections,
371 AnalyticsJobs,
372 NativeArtifacts,
373}
374
375#[derive(Debug, Clone, Copy, PartialEq, Eq)]
376enum DeploymentProfile {
377 Embedded,
378 Server,
379 Serverless,
380}
381
382fn percent_decode_path_segment(input: &str) -> Result<String, String> {
383 let bytes = input.as_bytes();
384 let mut out = Vec::with_capacity(bytes.len());
385 let mut index = 0;
386 while index < bytes.len() {
387 match bytes[index] {
388 b'%' => {
389 if index + 2 >= bytes.len() {
390 return Err("truncated percent escape".to_string());
391 }
392 let high = hex_value(bytes[index + 1])
393 .ok_or_else(|| "invalid percent escape".to_string())?;
394 let low = hex_value(bytes[index + 2])
395 .ok_or_else(|| "invalid percent escape".to_string())?;
396 out.push((high << 4) | low);
397 index += 3;
398 }
399 byte => {
400 out.push(byte);
401 index += 1;
402 }
403 }
404 }
405 String::from_utf8(out).map_err(|_| "path segment is not valid UTF-8".to_string())
406}
407
408fn hex_value(byte: u8) -> Option<u8> {
409 match byte {
410 b'0'..=b'9' => Some(byte - b'0'),
411 b'a'..=b'f' => Some(byte - b'a' + 10),
412 b'A'..=b'F' => Some(byte - b'A' + 10),
413 _ => None,
414 }
415}
416
417#[derive(Debug, Clone)]
418struct ParsedQueryRequest {
419 query: String,
420 entity_types: Option<Vec<String>>,
421 capabilities: Option<Vec<String>>,
422 commit_policy: Option<crate::replication::CommitPolicy>,
423 params: Option<Vec<crate::runtime::query_request::ParamValue>>,
426}
427
428#[derive(Debug, Clone, Copy)]
429enum PatchOperationType {
430 Set,
431 Replace,
432 Unset,
433}
434
435#[derive(Debug, Clone)]
436struct PatchOperation {
437 op: PatchOperationType,
438 path: Vec<String>,
439 value: Option<JsonValue>,
440}
441
442impl RedDBServer {
443 pub fn new(runtime: RedDBRuntime) -> Self {
444 Self::with_options(runtime, ServerOptions::default())
445 }
446
447 pub fn from_database_options(
448 db_options: RedDBOptions,
449 server_options: ServerOptions,
450 ) -> RedDBResult<Self> {
451 let runtime = RedDBRuntime::with_options(db_options)?;
452 Ok(Self::with_options(runtime, server_options))
453 }
454
455 pub fn with_options(runtime: RedDBRuntime, options: ServerOptions) -> Self {
456 Self {
457 runtime,
458 options,
459 auth_store: None,
460 replication: None,
461 http_limiter: HttpConnectionLimiter::with_default_cap(),
462 handler_timeout: DEFAULT_HANDLER_TIMEOUT,
463 handler_clock: Arc::new(SystemMonotonicClock::new()),
464 slow_inject_ms: Arc::new(AtomicU64::new(0)),
465 http_metrics: HttpHandlerMetrics::new(),
466 http_request_metrics: HttpRequestMetrics::new(),
467 retry_after_secs: DEFAULT_RETRY_AFTER_SECS,
468 principal_limiter: PrincipalConnectionLimiter::new(
469 http_limits::DEFAULT_MAX_INFLIGHT_PER_PRINCIPAL,
470 ),
471 stream_capacity: output_stream::StreamCapacityRegistry::new(),
472 lease_registry: output_stream::LeaseRegistry::new(),
473 cursor_registry: output_stream::CursorRegistry::new(),
474 }
475 }
476
477 #[doc(hidden)]
478 pub fn stream_capacity(&self) -> &Arc<output_stream::StreamCapacityRegistry> {
479 &self.stream_capacity
480 }
481
482 #[doc(hidden)]
483 pub fn lease_registry(&self) -> &Arc<output_stream::LeaseRegistry> {
484 &self.lease_registry
485 }
486
487 #[doc(hidden)]
488 pub fn cursor_registry(&self) -> &Arc<output_stream::CursorRegistry> {
489 &self.cursor_registry
490 }
491
492 #[doc(hidden)]
493 pub fn http_metrics(&self) -> &HttpHandlerMetrics {
494 &self.http_metrics
495 }
496
497 #[doc(hidden)]
500 pub fn http_request_metrics(&self) -> &HttpRequestMetrics {
501 &self.http_request_metrics
502 }
503
504 #[doc(hidden)]
509 pub fn with_http_limiter_cap(mut self, cap: usize) -> Self {
510 self.http_limiter = HttpConnectionLimiter::new(cap);
511 self
512 }
513
514 pub fn with_http_limits(mut self, limits: HttpLimitsResolved) -> Self {
519 self.http_limiter = HttpConnectionLimiter::new(limits.max_handlers);
520 self.handler_timeout = Duration::from_millis(limits.handler_timeout_ms);
521 self.retry_after_secs = limits.retry_after_secs;
522 self.principal_limiter = PrincipalConnectionLimiter::new(limits.max_inflight_per_principal);
523 self
524 }
525
526 #[doc(hidden)]
531 pub fn with_principal_inflight_cap(mut self, cap: usize) -> Self {
532 self.principal_limiter = PrincipalConnectionLimiter::new(cap);
533 self
534 }
535
536 #[doc(hidden)]
537 pub fn principal_limiter(&self) -> &PrincipalConnectionLimiter {
538 &self.principal_limiter
539 }
540
541 #[doc(hidden)]
542 pub fn retry_after_secs(&self) -> u64 {
543 self.retry_after_secs
544 }
545
546 #[doc(hidden)]
547 pub fn http_limiter(&self) -> &HttpConnectionLimiter {
548 &self.http_limiter
549 }
550
551 #[doc(hidden)]
554 pub fn with_handler_timeout(mut self, timeout: Duration) -> Self {
555 self.handler_timeout = timeout;
556 self
557 }
558
559 #[doc(hidden)]
560 pub fn handler_timeout(&self) -> Duration {
561 self.handler_timeout
562 }
563
564 #[doc(hidden)]
569 pub fn with_handler_clock(mut self, clock: Arc<dyn MonotonicClock>) -> Self {
570 self.handler_clock = clock;
571 self
572 }
573
574 #[doc(hidden)]
580 pub fn set_test_slow_inject_ms(&self, ms: u64) {
581 self.slow_inject_ms.store(ms, Ordering::Relaxed);
582 }
583
584 pub fn with_auth(mut self, auth_store: Arc<AuthStore>) -> Self {
588 self.runtime.set_auth_store(Arc::clone(&auth_store));
589 self.auth_store = Some(auth_store);
590 self
591 }
592
593 pub fn with_replication(mut self, state: Arc<ServerReplicationState>) -> Self {
595 self.replication = Some(state);
596 self
597 }
598
599 pub fn with_websocket_allowed_origins(mut self, origins: Vec<String>) -> Self {
604 self.options.websocket_allowed_origins = origins;
605 self
606 }
607
608 pub(crate) fn websocket_allowed_origins(&self) -> &[String] {
610 &self.options.websocket_allowed_origins
611 }
612
613 pub fn with_ui_dir(mut self, dir: std::path::PathBuf) -> Self {
619 self.options.ui_dir = Some(dir);
620 self
621 }
622
623 pub(crate) fn ui_dir(&self) -> Option<&std::path::Path> {
626 self.options.ui_dir.as_deref()
627 }
628
629 pub fn with_browser_token_authority(
636 self,
637 authority: Arc<crate::auth::browser_token::BrowserTokenAuthority>,
638 ) -> Self {
639 self.runtime.set_browser_token_authority(Some(authority));
640 self
641 }
642
643 pub fn runtime(&self) -> &RedDBRuntime {
644 &self.runtime
645 }
646
647 pub fn options(&self) -> &ServerOptions {
648 &self.options
649 }
650
651 fn admin_use_cases(&self) -> AdminUseCases<'_, RedDBRuntime> {
652 AdminUseCases::new(&self.runtime)
653 }
654
655 fn entity_use_cases(&self) -> EntityUseCases<'_, RedDBRuntime> {
656 EntityUseCases::new(&self.runtime)
657 }
658
659 fn graph_use_cases(&self) -> GraphUseCases<'_, RedDBRuntime> {
660 GraphUseCases::new(&self.runtime)
661 }
662
663 fn native_use_cases(&self) -> NativeUseCases<'_, RedDBRuntime> {
664 NativeUseCases::new(&self.runtime)
665 }
666
667 fn tree_use_cases(&self) -> TreeUseCases<'_, RedDBRuntime> {
668 TreeUseCases::new(&self.runtime)
669 }
670
671 fn transport_readiness_json(&self) -> JsonValue {
672 let active = self
673 .options
674 .transport_readiness
675 .active
676 .iter()
677 .map(|listener| {
678 let mut object = Map::new();
679 object.insert(
680 "transport".to_string(),
681 JsonValue::String(listener.transport.clone()),
682 );
683 object.insert(
684 "bind_addr".to_string(),
685 JsonValue::String(listener.bind_addr.clone()),
686 );
687 object.insert("explicit".to_string(), JsonValue::Bool(listener.explicit));
688 JsonValue::Object(object)
689 })
690 .collect();
691 let failed = self
692 .options
693 .transport_readiness
694 .failed
695 .iter()
696 .map(|listener| {
697 let mut object = Map::new();
698 object.insert(
699 "transport".to_string(),
700 JsonValue::String(listener.transport.clone()),
701 );
702 object.insert(
703 "bind_addr".to_string(),
704 JsonValue::String(listener.bind_addr.clone()),
705 );
706 object.insert("explicit".to_string(), JsonValue::Bool(listener.explicit));
707 object.insert(
708 "reason".to_string(),
709 JsonValue::String(listener.reason.clone()),
710 );
711 JsonValue::Object(object)
712 })
713 .collect();
714
715 let mut object = Map::new();
716 object.insert("active".to_string(), JsonValue::Array(active));
717 object.insert("failed".to_string(), JsonValue::Array(failed));
718 JsonValue::Object(object)
719 }
720
721 fn handle_grpc_discovery(&self) -> HttpResponse {
722 let mut methods = Map::new();
723 methods.insert(
724 "query".to_string(),
725 JsonValue::String("reddb.v1.RedDB/Query".to_string()),
726 );
727 methods.insert(
728 "batch_query".to_string(),
729 JsonValue::String("reddb.v1.RedDB/BatchQuery".to_string()),
730 );
731 methods.insert(
732 "health".to_string(),
733 JsonValue::String("reddb.v1.RedDB/Health".to_string()),
734 );
735 methods.insert(
736 "prepare".to_string(),
737 JsonValue::String("reddb.v1.RedDB/Prepare".to_string()),
738 );
739 methods.insert(
740 "execute_prepared".to_string(),
741 JsonValue::String("reddb.v1.RedDB/ExecutePrepared".to_string()),
742 );
743
744 let mut examples = Map::new();
745 examples.insert(
746 "query".to_string(),
747 JsonValue::String(
748 "grpcurl -plaintext -d '{\"query\":\"SELECT 1\"}' 127.0.0.1:5000 reddb.v1.RedDB/Query"
749 .to_string(),
750 ),
751 );
752 examples.insert(
753 "query_with_params".to_string(),
754 JsonValue::String(
755 "grpcurl -plaintext -d '{\"query\":\"SELECT $1 AS value\",\"params\":[{\"intValue\":42}]}' 127.0.0.1:5000 reddb.v1.RedDB/Query"
756 .to_string(),
757 ),
758 );
759 examples.insert(
760 "health".to_string(),
761 JsonValue::String(
762 "grpcurl -plaintext -d '{}' 127.0.0.1:5000 reddb.v1.RedDB/Health".to_string(),
763 ),
764 );
765
766 let mut object = Map::new();
767 object.insert("ok".to_string(), JsonValue::Bool(true));
768 object.insert(
769 "service".to_string(),
770 JsonValue::String("reddb.v1.RedDB".to_string()),
771 );
772 object.insert(
773 "package".to_string(),
774 JsonValue::String("reddb.v1".to_string()),
775 );
776 object.insert(
777 "proto".to_string(),
778 JsonValue::String("crates/reddb-grpc-proto/proto/reddb.proto".to_string()),
779 );
780 object.insert("methods".to_string(), JsonValue::Object(methods));
781 object.insert("examples".to_string(), JsonValue::Object(examples));
782 object.insert(
783 "transport_listeners".to_string(),
784 self.transport_readiness_json(),
785 );
786 object.insert(
787 "hint".to_string(),
788 JsonValue::String(
789 "If grpcurl cannot list services, pass the proto file with -import-path crates/reddb-grpc-proto/proto -proto reddb.proto."
790 .to_string(),
791 ),
792 );
793 json_response(200, JsonValue::Object(object))
794 }
795
796 fn handle_query_contract(&self) -> HttpResponse {
797 let mut examples = Map::new();
798 examples.insert(
799 "raw_sql".to_string(),
800 JsonValue::String("curl -sS http://127.0.0.1:5000/query -d 'SELECT 1'".to_string()),
801 );
802 examples.insert(
803 "json_query".to_string(),
804 JsonValue::String(
805 "curl -sS http://127.0.0.1:5000/query -H 'content-type: application/json' -d '{\"query\":\"SELECT 1\"}'"
806 .to_string(),
807 ),
808 );
809 examples.insert(
810 "json_query_with_params".to_string(),
811 JsonValue::String(
812 "curl -sS http://127.0.0.1:5000/query -H 'content-type: application/json' -d '{\"query\":\"SELECT $1 AS value\",\"params\":[42]}'"
813 .to_string(),
814 ),
815 );
816
817 let mut request_body = Map::new();
818 request_body.insert(
819 "query".to_string(),
820 JsonValue::String("required string".to_string()),
821 );
822 request_body.insert(
823 "params".to_string(),
824 JsonValue::String("optional array".to_string()),
825 );
826
827 let mut response_shape = Map::new();
828 response_shape.insert(
829 "columns".to_string(),
830 JsonValue::String("projected column names".to_string()),
831 );
832 response_shape.insert(
833 "records[].values".to_string(),
834 JsonValue::String("only projected values".to_string()),
835 );
836 response_shape.insert(
837 "records[].meta".to_string(),
838 JsonValue::String("internal metadata when present".to_string()),
839 );
840
841 let mut object = Map::new();
842 object.insert("ok".to_string(), JsonValue::Bool(false));
843 object.insert(
844 "code".to_string(),
845 JsonValue::String("method_not_allowed".to_string()),
846 );
847 object.insert(
848 "message".to_string(),
849 JsonValue::String("/query accepts POST requests".to_string()),
850 );
851 object.insert(
852 "hint".to_string(),
853 JsonValue::String(
854 "Send raw SQL in the body, or JSON with a string 'query' field.".to_string(),
855 ),
856 );
857 object.insert("method".to_string(), JsonValue::String("POST".to_string()));
858 object.insert("path".to_string(), JsonValue::String("/query".to_string()));
859 object.insert("request_body".to_string(), JsonValue::Object(request_body));
860 object.insert(
861 "response_shape".to_string(),
862 JsonValue::Object(response_shape),
863 );
864 object.insert("examples".to_string(), JsonValue::Object(examples));
865 object.insert(
866 "docs".to_string(),
867 JsonValue::String("https://reddb.io/docs/query".to_string()),
868 );
869
870 json_response(405, JsonValue::Object(object))
871 .with_header("Allow", http::HeaderValue::from_static("POST"))
872 }
873
874 fn handle_root_discovery(&self) -> HttpResponse {
875 let mut endpoints = Map::new();
876 endpoints.insert(
877 "health".to_string(),
878 JsonValue::String("GET /health".to_string()),
879 );
880 endpoints.insert(
881 "ready".to_string(),
882 JsonValue::String("GET /ready".to_string()),
883 );
884 endpoints.insert(
885 "query".to_string(),
886 JsonValue::String("POST /query".to_string()),
887 );
888 endpoints.insert(
889 "query_readiness".to_string(),
890 JsonValue::String("GET /ready/query".to_string()),
891 );
892 endpoints.insert(
893 "catalog".to_string(),
894 JsonValue::String("GET /catalog".to_string()),
895 );
896 endpoints.insert(
897 "deployment_profiles".to_string(),
898 JsonValue::String("GET /deployment/profiles".to_string()),
899 );
900
901 let mut examples = Map::new();
902 examples.insert(
903 "http_raw_sql".to_string(),
904 JsonValue::String("curl -sS http://127.0.0.1:5000/query -d 'SELECT 1'".to_string()),
905 );
906 examples.insert(
907 "http_json_query".to_string(),
908 JsonValue::String(
909 "curl -sS http://127.0.0.1:5000/query -H 'content-type: application/json' -d '{\"query\":\"SELECT 1\"}'"
910 .to_string(),
911 ),
912 );
913 examples.insert(
914 "http_json_query_with_params".to_string(),
915 JsonValue::String(
916 "curl -sS http://127.0.0.1:5000/query -H 'content-type: application/json' -d '{\"query\":\"SELECT $1 AS value\",\"params\":[42]}'"
917 .to_string(),
918 ),
919 );
920
921 let mut object = Map::new();
922 object.insert("ok".to_string(), JsonValue::Bool(true));
923 object.insert(
924 "service".to_string(),
925 JsonValue::String("reddb".to_string()),
926 );
927 object.insert(
928 "version".to_string(),
929 JsonValue::String(env!("CARGO_PKG_VERSION").to_string()),
930 );
931 object.insert("endpoints".to_string(), JsonValue::Object(endpoints));
932 object.insert("examples".to_string(), JsonValue::Object(examples));
933 object.insert(
934 "docs".to_string(),
935 JsonValue::String("https://reddb.io/docs".to_string()),
936 );
937 object.insert(
938 "transport_listeners".to_string(),
939 self.transport_readiness_json(),
940 );
941 json_response(200, JsonValue::Object(object))
942 }
943
944 fn health_json_with_transport(&self, report: &HealthReport) -> JsonValue {
945 let mut value = crate::presentation::ops_json::health_json(report);
946 if let JsonValue::Object(ref mut object) = value {
947 object.insert(
948 "transport_listeners".to_string(),
949 self.transport_readiness_json(),
950 );
951 }
952 value
953 }
954
955 pub fn serve(&self) -> io::Result<()> {
956 let listener = TcpListener::bind(&self.options.bind_addr)?;
957 self.serve_on(listener)
958 }
959
960 pub fn serve_on(&self, listener: TcpListener) -> io::Result<()> {
967 let runtime = axum_edge::build_edge_runtime()?;
968 runtime.block_on(
969 self.clone()
970 .serve_edge_on_std(listener, HttpTransport::Http),
971 )
972 }
973
974 pub fn serve_one_on(&self, listener: TcpListener) -> io::Result<()> {
978 let runtime = axum_edge::build_background_edge_runtime()?;
979 let server = self.clone();
980 runtime.block_on(async move {
981 listener.set_nonblocking(true)?;
982 let listener = tokio::net::TcpListener::from_std(listener)?;
983 let (stream, _peer) = listener.accept().await?;
984 server.serve_edge_one(stream).await;
985 Ok(())
986 })
987 }
988
989 pub fn serve_in_background(&self) -> thread::JoinHandle<io::Result<()>> {
990 let server = self.clone();
991 thread::spawn(move || server.serve())
992 }
993
994 pub fn serve_in_background_on(
995 &self,
996 listener: TcpListener,
997 ) -> thread::JoinHandle<io::Result<()>> {
998 let server = self.clone();
999 thread::spawn(move || {
1000 let runtime = axum_edge::build_background_edge_runtime()?;
1001 runtime.block_on(server.serve_edge_on_std(listener, HttpTransport::Http))
1002 })
1003 }
1004
1005 pub fn serve_tls(&self, tls_config: std::sync::Arc<rustls::ServerConfig>) -> io::Result<()> {
1009 let listener = TcpListener::bind(&self.options.bind_addr)?;
1010 self.serve_tls_on(listener, tls_config)
1011 }
1012
1013 pub fn serve_tls_on(
1014 &self,
1015 listener: TcpListener,
1016 tls_config: std::sync::Arc<rustls::ServerConfig>,
1017 ) -> io::Result<()> {
1018 let runtime = axum_edge::build_edge_runtime()?;
1019 let acceptor = axum_edge::tls_acceptor(tls_config);
1020 runtime.block_on(self.clone().serve_edge_tls_on_std(
1021 listener,
1022 acceptor,
1023 HttpTransport::Https,
1024 ))
1025 }
1026
1027 pub fn serve_tls_in_background(
1028 &self,
1029 tls_config: std::sync::Arc<rustls::ServerConfig>,
1030 ) -> thread::JoinHandle<io::Result<()>> {
1031 let server = self.clone();
1032 thread::spawn(move || server.serve_tls(tls_config))
1033 }
1034
1035 pub fn serve_tls_in_background_on(
1036 &self,
1037 listener: TcpListener,
1038 tls_config: std::sync::Arc<rustls::ServerConfig>,
1039 ) -> thread::JoinHandle<io::Result<()>> {
1040 let server = self.clone();
1041 thread::spawn(move || {
1042 let runtime = axum_edge::build_background_edge_runtime()?;
1043 let acceptor = axum_edge::tls_acceptor(tls_config);
1044 runtime.block_on(server.serve_edge_tls_on_std(listener, acceptor, HttpTransport::Https))
1045 })
1046 }
1047
1048 fn handle_connection(&self, stream: TcpStream) -> io::Result<()> {
1049 let started = Instant::now();
1050 let result = self.handle_connection_inner(stream);
1051 let elapsed = started.elapsed().as_secs_f64();
1052 self.http_metrics
1053 .record_duration(HttpTransport::Http, elapsed);
1054 result
1055 }
1056
1057 fn handle_connection_inner(&self, mut stream: TcpStream) -> io::Result<()> {
1058 stream.set_read_timeout(Some(Duration::from_millis(self.options.read_timeout_ms)))?;
1059 stream.set_write_timeout(Some(Duration::from_millis(self.options.write_timeout_ms)))?;
1060
1061 let deadline = HandlerDeadline::arm(Arc::clone(&self.handler_clock), self.handler_timeout);
1069
1070 let request = HttpRequest::read_from(&mut stream, self.options.max_body_bytes)?;
1071
1072 if deadline.expired() {
1074 self.http_metrics
1075 .record_reject(HttpTransport::Http, HttpRejectReason::HandlerTimeout);
1076 Self::write_handler_timeout_503(&mut stream);
1077 return Ok(());
1078 }
1079
1080 if self.try_route_streaming(&request, &mut stream)? {
1081 return Ok(());
1082 }
1083 let response = self.route(request);
1084
1085 let inject_ms = self.slow_inject_ms.load(Ordering::Relaxed);
1089 if inject_ms > 0 {
1090 thread::sleep(Duration::from_millis(inject_ms));
1091 }
1092
1093 if deadline.expired() {
1095 self.http_metrics
1096 .record_reject(HttpTransport::Http, HttpRejectReason::HandlerTimeout);
1097 Self::write_handler_timeout_503(&mut stream);
1098 return Ok(());
1099 }
1100
1101 stream.write_all(&response.to_http_bytes())?;
1102 stream.flush()?;
1103 Ok(())
1104 }
1105
1106 fn write_handler_timeout_503<S: Write>(stream: &mut S) {
1112 const RESPONSE: &[u8] = b"HTTP/1.1 503 Service Unavailable\r\n\
1113 Connection: close\r\n\
1114 Content-Length: 0\r\n\
1115 \r\n";
1116 let _ = stream.write_all(RESPONSE);
1117 let _ = stream.flush();
1118 }
1119}