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, CatalogUseCases, CreateDocumentInput, CreateEdgeInput, CreateEntityOutput,
8 CreateKvInput, CreateNodeEmbeddingInput, CreateNodeGraphLinkInput, CreateNodeInput,
9 CreateNodeTableLinkInput, CreateRowInput, CreateVectorInput, DeleteEntityInput, EntityUseCases,
10 ExecuteQueryInput, ExplainQueryInput, GraphCentralityInput, GraphClusteringInput,
11 GraphCommunitiesInput, GraphComponentsInput, GraphCyclesInput, GraphHitsInput,
12 GraphNeighborhoodInput, GraphPersonalizedPageRankInput, GraphShortestPathInput,
13 GraphTopologicalSortInput, GraphTraversalInput, GraphUseCases, InspectNativeArtifactInput,
14 NativeUseCases, PatchEntityInput, PatchEntityOperation, PatchEntityOperationType,
15 QueryUseCases, SearchHybridInput, SearchIvfInput, SearchMultimodalInput, SearchSimilarInput,
16 SearchTextInput, TreeUseCases,
17};
18use std::collections::{BTreeMap, HashMap};
19use std::io::{self, Read, Write};
20use std::net::{TcpListener, TcpStream};
21use std::sync::atomic::{AtomicU64, Ordering};
22use std::thread;
23use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
24
25use std::sync::Arc;
26
27use crate::api::{RedDBError, RedDBOptions, RedDBResult};
28use crate::auth::store::AuthStore;
29use crate::catalog::{CatalogModelSnapshot, CollectionDescriptor, CollectionModel, SchemaMode};
30use crate::health::{HealthProvider, HealthReport, HealthState};
31use crate::json::{parse_json, to_vec as json_to_vec, Map, Value as JsonValue};
32use crate::runtime::{
33 RedDBRuntime, RuntimeFilter, RuntimeFilterValue, RuntimeGraphCentralityAlgorithm,
34 RuntimeGraphCentralityResult, RuntimeGraphClusteringResult, RuntimeGraphCommunityAlgorithm,
35 RuntimeGraphCommunityResult, RuntimeGraphComponentsMode, RuntimeGraphComponentsResult,
36 RuntimeGraphCyclesResult, RuntimeGraphDirection, RuntimeGraphHitsResult,
37 RuntimeGraphNeighborhoodResult, RuntimeGraphPathAlgorithm, RuntimeGraphPathResult,
38 RuntimeGraphPattern, RuntimeGraphProjection, RuntimeGraphTopologicalSortResult,
39 RuntimeGraphTraversalResult, RuntimeGraphTraversalStrategy, RuntimeIvfSearchResult,
40 RuntimeQueryWeights, RuntimeStats, ScanCursor, ScanPage,
41};
42use crate::storage::schema::Value;
43use crate::storage::unified::devx::refs::{NodeRef, TableRef, VectorRef};
44use crate::storage::unified::dsl::{MatchComponents, QueryResult as DslQueryResult};
45use crate::storage::unified::{MetadataValue, RefTarget, SparseVector};
46use crate::storage::{CrossRef, EntityData, EntityId, EntityKind, SimilarResult, UnifiedEntity};
47
48fn analytics_job_json(job: &crate::PhysicalAnalyticsJob) -> JsonValue {
49 crate::presentation::admin_json::analytics_job_json(job)
50}
51
52#[cfg(test)]
53mod tests {
54 use super::*;
55 use crate::api::RedDBOptions;
56 use crate::health::HealthReport;
57 use crate::service_cli::{
58 TransportListenerFailure, TransportListenerState, TransportReadiness,
59 };
60
61 #[test]
62 fn server_options_default_http_body_limit_is_32_mib() {
63 assert_eq!(ServerOptions::default().max_body_bytes, 32 * 1024 * 1024);
64 }
65
66 #[test]
67 fn health_json_reports_transport_listeners() {
68 let runtime = RedDBRuntime::with_options(RedDBOptions::in_memory()).expect("runtime");
69 let mut options = ServerOptions::default();
70 options.transport_readiness = TransportReadiness {
71 active: vec![TransportListenerState {
72 transport: "grpc".to_string(),
73 bind_addr: "127.0.0.1:5000".to_string(),
74 explicit: true,
75 }],
76 failed: vec![TransportListenerFailure {
77 transport: "http".to_string(),
78 bind_addr: "127.0.0.1:5000".to_string(),
79 explicit: false,
80 reason: "http listener bind 127.0.0.1:5000: address in use".to_string(),
81 }],
82 };
83 let server = RedDBServer::with_options(runtime, options);
84
85 let payload = server.health_json_with_transport(&HealthReport::healthy());
86 let JsonValue::Object(root) = payload else {
87 panic!("health payload should be an object");
88 };
89 let Some(JsonValue::Object(listeners)) = root.get("transport_listeners") else {
90 panic!("health payload should include transport_listeners");
91 };
92 let Some(JsonValue::Array(active)) = listeners.get("active") else {
93 panic!("transport_listeners.active should be an array");
94 };
95 let Some(JsonValue::Array(failed)) = listeners.get("failed") else {
96 panic!("transport_listeners.failed should be an array");
97 };
98
99 assert_eq!(active.len(), 1);
100 assert_eq!(failed.len(), 1);
101 }
102}
103
104fn graph_projection_json(projection: &crate::PhysicalGraphProjection) -> JsonValue {
105 crate::presentation::admin_json::graph_projection_json(projection)
106}
107
108mod axum_edge;
109pub mod handlers_admin;
110mod handlers_admin_metrics;
111mod handlers_admin_status;
112mod handlers_ai;
113mod handlers_ai_model_cache;
114mod handlers_auth;
115mod handlers_backup;
116mod handlers_browser_auth;
117mod handlers_capabilities;
118mod handlers_collection_policy;
119mod handlers_ec;
120pub(crate) mod handlers_entity;
121mod handlers_failover;
122mod handlers_geo;
123mod handlers_graph;
124mod handlers_iam_policy;
125mod handlers_keyed;
126mod handlers_log;
127mod handlers_metrics;
128mod handlers_ops;
129mod handlers_ops_policy;
130pub mod ui_bridge;
134mod ws_edge;
135pub mod ui_bundle_resolver;
139pub mod ui_auth;
142mod ui_static;
146pub mod ui_deeplink;
151pub(crate) mod handlers_query;
156mod handlers_replication;
157mod handlers_topology;
158mod handlers_vector;
159pub mod header_escape_guard;
160pub mod http_connection_limiter;
161pub mod http_handler_metrics;
162pub mod http_limits;
163pub mod http_principal_limiter;
164pub mod http_request_metrics;
165pub mod ingest_pipeline;
166pub mod output_stream;
167mod patch_support;
168mod request_body;
169mod request_context;
170mod route_catalog;
171mod routes;
172mod routing;
173mod serverless_support;
174pub mod tls;
175mod transport;
176
177use self::handlers_ai::*;
178use self::handlers_entity::*;
179use self::handlers_graph::*;
180use self::handlers_keyed::*;
181use self::handlers_metrics::*;
182use self::handlers_ops::*;
183use self::handlers_query::*;
184use self::http_connection_limiter::{
185 HandlerDeadline, HttpConnectionLimiter, MonotonicClock, SystemMonotonicClock,
186};
187use self::http_handler_metrics::{HttpHandlerMetrics, HttpRejectReason, HttpTransport};
188pub use self::http_limits::{
189 HttpLimitsCliInput, HttpLimitsResolved, DEFAULT_HANDLER_TIMEOUT_MS, DEFAULT_RETRY_AFTER_SECS,
190};
191use self::http_principal_limiter::PrincipalConnectionLimiter;
192use self::http_request_metrics::HttpRequestMetrics;
193use self::patch_support::*;
194use self::request_body::*;
195use self::routing::*;
196use self::serverless_support::*;
197use self::transport::*;
198
199#[derive(Debug, Clone, Copy, PartialEq, Eq)]
205pub enum ServerSurface {
206 Public,
208 AdminOnly,
212 MetricsOnly,
216}
217
218#[derive(Debug, Clone)]
219pub struct ServerOptions {
220 pub bind_addr: String,
221 pub max_body_bytes: usize,
222 pub read_timeout_ms: u64,
223 pub write_timeout_ms: u64,
224 pub max_scan_limit: usize,
225 pub surface: ServerSurface,
229 pub transport_readiness: crate::service_cli::TransportReadiness,
230 pub websocket_allowed_origins: Vec<String>,
238 pub ui_dir: Option<std::path::PathBuf>,
247}
248
249pub const DEFAULT_HTTP_MAX_BODY_BYTES: usize = 32 * 1024 * 1024;
250
251impl Default for ServerOptions {
252 fn default() -> Self {
253 Self {
254 bind_addr: "127.0.0.1:5000".to_string(),
255 max_body_bytes: DEFAULT_HTTP_MAX_BODY_BYTES,
256 read_timeout_ms: 5_000,
257 write_timeout_ms: 5_000,
258 max_scan_limit: 1_000,
259 surface: ServerSurface::Public,
260 transport_readiness: crate::service_cli::TransportReadiness::default(),
261 websocket_allowed_origins: Vec::new(),
262 ui_dir: None,
263 }
264 }
265}
266
267pub struct ServerReplicationState {
269 pub config: crate::replication::ReplicationConfig,
270 pub primary: Option<crate::replication::primary::PrimaryReplication>,
271}
272
273#[derive(Clone)]
274pub struct RedDBServer {
275 runtime: RedDBRuntime,
276 options: ServerOptions,
277 auth_store: Option<Arc<AuthStore>>,
278 replication: Option<Arc<ServerReplicationState>>,
279 http_limiter: HttpConnectionLimiter,
284 handler_timeout: Duration,
290 handler_clock: Arc<dyn MonotonicClock>,
297 slow_inject_ms: Arc<AtomicU64>,
304 http_metrics: HttpHandlerMetrics,
310 http_request_metrics: HttpRequestMetrics,
316 retry_after_secs: u64,
320 principal_limiter: PrincipalConnectionLimiter,
328 pub(crate) stream_capacity: Arc<output_stream::StreamCapacityRegistry>,
336 pub(crate) lease_registry: Arc<output_stream::LeaseRegistry>,
341 pub(crate) cursor_registry: Arc<output_stream::CursorRegistry>,
346}
347
348const DEFAULT_HANDLER_TIMEOUT: Duration = Duration::from_millis(30_000);
350
351#[derive(Debug, Clone, Copy, PartialEq, Eq)]
352enum ServerlessWarmupScope {
353 Indexes,
354 GraphProjections,
355 AnalyticsJobs,
356 NativeArtifacts,
357}
358
359#[derive(Debug, Clone, Copy, PartialEq, Eq)]
360enum DeploymentProfile {
361 Embedded,
362 Server,
363 Serverless,
364}
365
366fn percent_decode_path_segment(input: &str) -> Result<String, String> {
367 let bytes = input.as_bytes();
368 let mut out = Vec::with_capacity(bytes.len());
369 let mut index = 0;
370 while index < bytes.len() {
371 match bytes[index] {
372 b'%' => {
373 if index + 2 >= bytes.len() {
374 return Err("truncated percent escape".to_string());
375 }
376 let high = hex_value(bytes[index + 1])
377 .ok_or_else(|| "invalid percent escape".to_string())?;
378 let low = hex_value(bytes[index + 2])
379 .ok_or_else(|| "invalid percent escape".to_string())?;
380 out.push((high << 4) | low);
381 index += 3;
382 }
383 byte => {
384 out.push(byte);
385 index += 1;
386 }
387 }
388 }
389 String::from_utf8(out).map_err(|_| "path segment is not valid UTF-8".to_string())
390}
391
392fn hex_value(byte: u8) -> Option<u8> {
393 match byte {
394 b'0'..=b'9' => Some(byte - b'0'),
395 b'a'..=b'f' => Some(byte - b'a' + 10),
396 b'A'..=b'F' => Some(byte - b'A' + 10),
397 _ => None,
398 }
399}
400
401#[derive(Debug, Clone)]
402struct ParsedQueryRequest {
403 query: String,
404 entity_types: Option<Vec<String>>,
405 capabilities: Option<Vec<String>>,
406 params: Option<Vec<Value>>,
410}
411
412#[derive(Debug, Clone, Copy)]
413enum PatchOperationType {
414 Set,
415 Replace,
416 Unset,
417}
418
419#[derive(Debug, Clone)]
420struct PatchOperation {
421 op: PatchOperationType,
422 path: Vec<String>,
423 value: Option<JsonValue>,
424}
425
426impl RedDBServer {
427 pub fn new(runtime: RedDBRuntime) -> Self {
428 Self::with_options(runtime, ServerOptions::default())
429 }
430
431 pub fn from_database_options(
432 db_options: RedDBOptions,
433 server_options: ServerOptions,
434 ) -> RedDBResult<Self> {
435 let runtime = RedDBRuntime::with_options(db_options)?;
436 Ok(Self::with_options(runtime, server_options))
437 }
438
439 pub fn with_options(runtime: RedDBRuntime, options: ServerOptions) -> Self {
440 Self {
441 runtime,
442 options,
443 auth_store: None,
444 replication: None,
445 http_limiter: HttpConnectionLimiter::with_default_cap(),
446 handler_timeout: DEFAULT_HANDLER_TIMEOUT,
447 handler_clock: Arc::new(SystemMonotonicClock::new()),
448 slow_inject_ms: Arc::new(AtomicU64::new(0)),
449 http_metrics: HttpHandlerMetrics::new(),
450 http_request_metrics: HttpRequestMetrics::new(),
451 retry_after_secs: DEFAULT_RETRY_AFTER_SECS,
452 principal_limiter: PrincipalConnectionLimiter::new(
453 http_limits::DEFAULT_MAX_INFLIGHT_PER_PRINCIPAL,
454 ),
455 stream_capacity: output_stream::StreamCapacityRegistry::new(),
456 lease_registry: output_stream::LeaseRegistry::new(),
457 cursor_registry: output_stream::CursorRegistry::new(),
458 }
459 }
460
461 #[doc(hidden)]
462 pub fn stream_capacity(&self) -> &Arc<output_stream::StreamCapacityRegistry> {
463 &self.stream_capacity
464 }
465
466 #[doc(hidden)]
467 pub fn lease_registry(&self) -> &Arc<output_stream::LeaseRegistry> {
468 &self.lease_registry
469 }
470
471 #[doc(hidden)]
472 pub fn cursor_registry(&self) -> &Arc<output_stream::CursorRegistry> {
473 &self.cursor_registry
474 }
475
476 #[doc(hidden)]
477 pub fn http_metrics(&self) -> &HttpHandlerMetrics {
478 &self.http_metrics
479 }
480
481 #[doc(hidden)]
484 pub fn http_request_metrics(&self) -> &HttpRequestMetrics {
485 &self.http_request_metrics
486 }
487
488 #[doc(hidden)]
493 pub fn with_http_limiter_cap(mut self, cap: usize) -> Self {
494 self.http_limiter = HttpConnectionLimiter::new(cap);
495 self
496 }
497
498 pub fn with_http_limits(mut self, limits: HttpLimitsResolved) -> Self {
503 self.http_limiter = HttpConnectionLimiter::new(limits.max_handlers);
504 self.handler_timeout = Duration::from_millis(limits.handler_timeout_ms);
505 self.retry_after_secs = limits.retry_after_secs;
506 self.principal_limiter = PrincipalConnectionLimiter::new(limits.max_inflight_per_principal);
507 self
508 }
509
510 #[doc(hidden)]
515 pub fn with_principal_inflight_cap(mut self, cap: usize) -> Self {
516 self.principal_limiter = PrincipalConnectionLimiter::new(cap);
517 self
518 }
519
520 #[doc(hidden)]
521 pub fn principal_limiter(&self) -> &PrincipalConnectionLimiter {
522 &self.principal_limiter
523 }
524
525 #[doc(hidden)]
526 pub fn retry_after_secs(&self) -> u64 {
527 self.retry_after_secs
528 }
529
530 #[doc(hidden)]
531 pub fn http_limiter(&self) -> &HttpConnectionLimiter {
532 &self.http_limiter
533 }
534
535 #[doc(hidden)]
538 pub fn with_handler_timeout(mut self, timeout: Duration) -> Self {
539 self.handler_timeout = timeout;
540 self
541 }
542
543 #[doc(hidden)]
544 pub fn handler_timeout(&self) -> Duration {
545 self.handler_timeout
546 }
547
548 #[doc(hidden)]
553 pub fn with_handler_clock(mut self, clock: Arc<dyn MonotonicClock>) -> Self {
554 self.handler_clock = clock;
555 self
556 }
557
558 #[doc(hidden)]
564 pub fn set_test_slow_inject_ms(&self, ms: u64) {
565 self.slow_inject_ms.store(ms, Ordering::Relaxed);
566 }
567
568 pub fn with_auth(mut self, auth_store: Arc<AuthStore>) -> Self {
572 self.runtime.set_auth_store(Arc::clone(&auth_store));
573 self.auth_store = Some(auth_store);
574 self
575 }
576
577 pub fn with_replication(mut self, state: Arc<ServerReplicationState>) -> Self {
579 self.replication = Some(state);
580 self
581 }
582
583 pub fn with_websocket_allowed_origins(mut self, origins: Vec<String>) -> Self {
588 self.options.websocket_allowed_origins = origins;
589 self
590 }
591
592 pub(crate) fn websocket_allowed_origins(&self) -> &[String] {
594 &self.options.websocket_allowed_origins
595 }
596
597 pub fn with_ui_dir(mut self, dir: std::path::PathBuf) -> Self {
603 self.options.ui_dir = Some(dir);
604 self
605 }
606
607 pub(crate) fn ui_dir(&self) -> Option<&std::path::Path> {
610 self.options.ui_dir.as_deref()
611 }
612
613 pub fn with_browser_token_authority(
620 self,
621 authority: Arc<crate::auth::browser_token::BrowserTokenAuthority>,
622 ) -> Self {
623 self.runtime.set_browser_token_authority(Some(authority));
624 self
625 }
626
627 pub fn runtime(&self) -> &RedDBRuntime {
628 &self.runtime
629 }
630
631 pub fn options(&self) -> &ServerOptions {
632 &self.options
633 }
634
635 fn query_use_cases(&self) -> QueryUseCases<'_, RedDBRuntime> {
636 QueryUseCases::new(&self.runtime)
637 }
638
639 fn admin_use_cases(&self) -> AdminUseCases<'_, RedDBRuntime> {
640 AdminUseCases::new(&self.runtime)
641 }
642
643 fn entity_use_cases(&self) -> EntityUseCases<'_, RedDBRuntime> {
644 EntityUseCases::new(&self.runtime)
645 }
646
647 fn catalog_use_cases(&self) -> CatalogUseCases<'_, RedDBRuntime> {
648 CatalogUseCases::new(&self.runtime)
649 }
650
651 fn graph_use_cases(&self) -> GraphUseCases<'_, RedDBRuntime> {
652 GraphUseCases::new(&self.runtime)
653 }
654
655 fn native_use_cases(&self) -> NativeUseCases<'_, RedDBRuntime> {
656 NativeUseCases::new(&self.runtime)
657 }
658
659 fn tree_use_cases(&self) -> TreeUseCases<'_, RedDBRuntime> {
660 TreeUseCases::new(&self.runtime)
661 }
662
663 fn transport_readiness_json(&self) -> JsonValue {
664 let active = self
665 .options
666 .transport_readiness
667 .active
668 .iter()
669 .map(|listener| {
670 let mut object = Map::new();
671 object.insert(
672 "transport".to_string(),
673 JsonValue::String(listener.transport.clone()),
674 );
675 object.insert(
676 "bind_addr".to_string(),
677 JsonValue::String(listener.bind_addr.clone()),
678 );
679 object.insert("explicit".to_string(), JsonValue::Bool(listener.explicit));
680 JsonValue::Object(object)
681 })
682 .collect();
683 let failed = self
684 .options
685 .transport_readiness
686 .failed
687 .iter()
688 .map(|listener| {
689 let mut object = Map::new();
690 object.insert(
691 "transport".to_string(),
692 JsonValue::String(listener.transport.clone()),
693 );
694 object.insert(
695 "bind_addr".to_string(),
696 JsonValue::String(listener.bind_addr.clone()),
697 );
698 object.insert("explicit".to_string(), JsonValue::Bool(listener.explicit));
699 object.insert(
700 "reason".to_string(),
701 JsonValue::String(listener.reason.clone()),
702 );
703 JsonValue::Object(object)
704 })
705 .collect();
706
707 let mut object = Map::new();
708 object.insert("active".to_string(), JsonValue::Array(active));
709 object.insert("failed".to_string(), JsonValue::Array(failed));
710 JsonValue::Object(object)
711 }
712
713 fn handle_grpc_discovery(&self) -> HttpResponse {
714 let mut methods = Map::new();
715 methods.insert(
716 "query".to_string(),
717 JsonValue::String("reddb.v1.RedDB/Query".to_string()),
718 );
719 methods.insert(
720 "batch_query".to_string(),
721 JsonValue::String("reddb.v1.RedDB/BatchQuery".to_string()),
722 );
723 methods.insert(
724 "health".to_string(),
725 JsonValue::String("reddb.v1.RedDB/Health".to_string()),
726 );
727 methods.insert(
728 "prepare".to_string(),
729 JsonValue::String("reddb.v1.RedDB/Prepare".to_string()),
730 );
731 methods.insert(
732 "execute_prepared".to_string(),
733 JsonValue::String("reddb.v1.RedDB/ExecutePrepared".to_string()),
734 );
735
736 let mut examples = Map::new();
737 examples.insert(
738 "query".to_string(),
739 JsonValue::String(
740 "grpcurl -plaintext -d '{\"query\":\"SELECT 1\"}' 127.0.0.1:5000 reddb.v1.RedDB/Query"
741 .to_string(),
742 ),
743 );
744 examples.insert(
745 "query_with_params".to_string(),
746 JsonValue::String(
747 "grpcurl -plaintext -d '{\"query\":\"SELECT $1 AS value\",\"params\":[{\"intValue\":42}]}' 127.0.0.1:5000 reddb.v1.RedDB/Query"
748 .to_string(),
749 ),
750 );
751 examples.insert(
752 "health".to_string(),
753 JsonValue::String(
754 "grpcurl -plaintext -d '{}' 127.0.0.1:5000 reddb.v1.RedDB/Health".to_string(),
755 ),
756 );
757
758 let mut object = Map::new();
759 object.insert("ok".to_string(), JsonValue::Bool(true));
760 object.insert(
761 "service".to_string(),
762 JsonValue::String("reddb.v1.RedDB".to_string()),
763 );
764 object.insert(
765 "package".to_string(),
766 JsonValue::String("reddb.v1".to_string()),
767 );
768 object.insert(
769 "proto".to_string(),
770 JsonValue::String("crates/reddb-grpc-proto/proto/reddb.proto".to_string()),
771 );
772 object.insert("methods".to_string(), JsonValue::Object(methods));
773 object.insert("examples".to_string(), JsonValue::Object(examples));
774 object.insert(
775 "transport_listeners".to_string(),
776 self.transport_readiness_json(),
777 );
778 object.insert(
779 "hint".to_string(),
780 JsonValue::String(
781 "If grpcurl cannot list services, pass the proto file with -import-path crates/reddb-grpc-proto/proto -proto reddb.proto."
782 .to_string(),
783 ),
784 );
785 json_response(200, JsonValue::Object(object))
786 }
787
788 fn handle_query_contract(&self) -> HttpResponse {
789 let mut examples = Map::new();
790 examples.insert(
791 "raw_sql".to_string(),
792 JsonValue::String("curl -sS http://127.0.0.1:5000/query -d 'SELECT 1'".to_string()),
793 );
794 examples.insert(
795 "json_query".to_string(),
796 JsonValue::String(
797 "curl -sS http://127.0.0.1:5000/query -H 'content-type: application/json' -d '{\"query\":\"SELECT 1\"}'"
798 .to_string(),
799 ),
800 );
801 examples.insert(
802 "json_query_with_params".to_string(),
803 JsonValue::String(
804 "curl -sS http://127.0.0.1:5000/query -H 'content-type: application/json' -d '{\"query\":\"SELECT $1 AS value\",\"params\":[42]}'"
805 .to_string(),
806 ),
807 );
808
809 let mut request_body = Map::new();
810 request_body.insert(
811 "query".to_string(),
812 JsonValue::String("required string".to_string()),
813 );
814 request_body.insert(
815 "params".to_string(),
816 JsonValue::String("optional array".to_string()),
817 );
818
819 let mut response_shape = Map::new();
820 response_shape.insert(
821 "columns".to_string(),
822 JsonValue::String("projected column names".to_string()),
823 );
824 response_shape.insert(
825 "records[].values".to_string(),
826 JsonValue::String("only projected values".to_string()),
827 );
828 response_shape.insert(
829 "records[].meta".to_string(),
830 JsonValue::String("internal metadata when present".to_string()),
831 );
832
833 let mut object = Map::new();
834 object.insert("ok".to_string(), JsonValue::Bool(false));
835 object.insert(
836 "code".to_string(),
837 JsonValue::String("method_not_allowed".to_string()),
838 );
839 object.insert(
840 "message".to_string(),
841 JsonValue::String("/query accepts POST requests".to_string()),
842 );
843 object.insert(
844 "hint".to_string(),
845 JsonValue::String(
846 "Send raw SQL in the body, or JSON with a string 'query' field.".to_string(),
847 ),
848 );
849 object.insert("method".to_string(), JsonValue::String("POST".to_string()));
850 object.insert("path".to_string(), JsonValue::String("/query".to_string()));
851 object.insert("request_body".to_string(), JsonValue::Object(request_body));
852 object.insert(
853 "response_shape".to_string(),
854 JsonValue::Object(response_shape),
855 );
856 object.insert("examples".to_string(), JsonValue::Object(examples));
857 object.insert(
858 "docs".to_string(),
859 JsonValue::String("https://reddb.io/docs/query".to_string()),
860 );
861
862 json_response(405, JsonValue::Object(object))
863 .with_header("Allow", http::HeaderValue::from_static("POST"))
864 }
865
866 fn handle_root_discovery(&self) -> HttpResponse {
867 let mut endpoints = Map::new();
868 endpoints.insert(
869 "health".to_string(),
870 JsonValue::String("GET /health".to_string()),
871 );
872 endpoints.insert(
873 "ready".to_string(),
874 JsonValue::String("GET /ready".to_string()),
875 );
876 endpoints.insert(
877 "query".to_string(),
878 JsonValue::String("POST /query".to_string()),
879 );
880 endpoints.insert(
881 "query_readiness".to_string(),
882 JsonValue::String("GET /ready/query".to_string()),
883 );
884 endpoints.insert(
885 "catalog".to_string(),
886 JsonValue::String("GET /catalog".to_string()),
887 );
888 endpoints.insert(
889 "deployment_profiles".to_string(),
890 JsonValue::String("GET /deployment/profiles".to_string()),
891 );
892
893 let mut examples = Map::new();
894 examples.insert(
895 "http_raw_sql".to_string(),
896 JsonValue::String("curl -sS http://127.0.0.1:5000/query -d 'SELECT 1'".to_string()),
897 );
898 examples.insert(
899 "http_json_query".to_string(),
900 JsonValue::String(
901 "curl -sS http://127.0.0.1:5000/query -H 'content-type: application/json' -d '{\"query\":\"SELECT 1\"}'"
902 .to_string(),
903 ),
904 );
905 examples.insert(
906 "http_json_query_with_params".to_string(),
907 JsonValue::String(
908 "curl -sS http://127.0.0.1:5000/query -H 'content-type: application/json' -d '{\"query\":\"SELECT $1 AS value\",\"params\":[42]}'"
909 .to_string(),
910 ),
911 );
912
913 let mut object = Map::new();
914 object.insert("ok".to_string(), JsonValue::Bool(true));
915 object.insert(
916 "service".to_string(),
917 JsonValue::String("reddb".to_string()),
918 );
919 object.insert(
920 "version".to_string(),
921 JsonValue::String(env!("CARGO_PKG_VERSION").to_string()),
922 );
923 object.insert("endpoints".to_string(), JsonValue::Object(endpoints));
924 object.insert("examples".to_string(), JsonValue::Object(examples));
925 object.insert(
926 "docs".to_string(),
927 JsonValue::String("https://reddb.io/docs".to_string()),
928 );
929 object.insert(
930 "transport_listeners".to_string(),
931 self.transport_readiness_json(),
932 );
933 json_response(200, JsonValue::Object(object))
934 }
935
936 fn health_json_with_transport(&self, report: &HealthReport) -> JsonValue {
937 let mut value = crate::presentation::ops_json::health_json(report);
938 if let JsonValue::Object(ref mut object) = value {
939 object.insert(
940 "transport_listeners".to_string(),
941 self.transport_readiness_json(),
942 );
943 }
944 value
945 }
946
947 pub fn serve(&self) -> io::Result<()> {
948 let listener = TcpListener::bind(&self.options.bind_addr)?;
949 self.serve_on(listener)
950 }
951
952 pub fn serve_on(&self, listener: TcpListener) -> io::Result<()> {
959 let runtime = axum_edge::build_edge_runtime()?;
960 runtime.block_on(
961 self.clone()
962 .serve_edge_on_std(listener, HttpTransport::Http),
963 )
964 }
965
966 pub fn serve_one_on(&self, listener: TcpListener) -> io::Result<()> {
970 let runtime = axum_edge::build_background_edge_runtime()?;
971 let server = self.clone();
972 runtime.block_on(async move {
973 listener.set_nonblocking(true)?;
974 let listener = tokio::net::TcpListener::from_std(listener)?;
975 let (stream, _peer) = listener.accept().await?;
976 server.serve_edge_one(stream).await;
977 Ok(())
978 })
979 }
980
981 pub fn serve_in_background(&self) -> thread::JoinHandle<io::Result<()>> {
982 let server = self.clone();
983 thread::spawn(move || server.serve())
984 }
985
986 pub fn serve_in_background_on(
987 &self,
988 listener: TcpListener,
989 ) -> thread::JoinHandle<io::Result<()>> {
990 let server = self.clone();
991 thread::spawn(move || {
992 let runtime = axum_edge::build_background_edge_runtime()?;
993 runtime.block_on(server.serve_edge_on_std(listener, HttpTransport::Http))
994 })
995 }
996
997 pub fn serve_tls(&self, tls_config: std::sync::Arc<rustls::ServerConfig>) -> io::Result<()> {
1001 let listener = TcpListener::bind(&self.options.bind_addr)?;
1002 self.serve_tls_on(listener, tls_config)
1003 }
1004
1005 pub fn serve_tls_on(
1006 &self,
1007 listener: TcpListener,
1008 tls_config: std::sync::Arc<rustls::ServerConfig>,
1009 ) -> io::Result<()> {
1010 let runtime = axum_edge::build_edge_runtime()?;
1011 let acceptor = axum_edge::tls_acceptor(tls_config);
1012 runtime.block_on(self.clone().serve_edge_tls_on_std(
1013 listener,
1014 acceptor,
1015 HttpTransport::Https,
1016 ))
1017 }
1018
1019 pub fn serve_tls_in_background(
1020 &self,
1021 tls_config: std::sync::Arc<rustls::ServerConfig>,
1022 ) -> thread::JoinHandle<io::Result<()>> {
1023 let server = self.clone();
1024 thread::spawn(move || server.serve_tls(tls_config))
1025 }
1026
1027 pub fn serve_tls_in_background_on(
1028 &self,
1029 listener: TcpListener,
1030 tls_config: std::sync::Arc<rustls::ServerConfig>,
1031 ) -> thread::JoinHandle<io::Result<()>> {
1032 let server = self.clone();
1033 thread::spawn(move || {
1034 let runtime = axum_edge::build_background_edge_runtime()?;
1035 let acceptor = axum_edge::tls_acceptor(tls_config);
1036 runtime.block_on(server.serve_edge_tls_on_std(listener, acceptor, HttpTransport::Https))
1037 })
1038 }
1039
1040 fn handle_connection(&self, stream: TcpStream) -> io::Result<()> {
1041 let started = Instant::now();
1042 let result = self.handle_connection_inner(stream);
1043 let elapsed = started.elapsed().as_secs_f64();
1044 self.http_metrics
1045 .record_duration(HttpTransport::Http, elapsed);
1046 result
1047 }
1048
1049 fn handle_connection_inner(&self, mut stream: TcpStream) -> io::Result<()> {
1050 stream.set_read_timeout(Some(Duration::from_millis(self.options.read_timeout_ms)))?;
1051 stream.set_write_timeout(Some(Duration::from_millis(self.options.write_timeout_ms)))?;
1052
1053 let deadline = HandlerDeadline::arm(Arc::clone(&self.handler_clock), self.handler_timeout);
1061
1062 let request = HttpRequest::read_from(&mut stream, self.options.max_body_bytes)?;
1063
1064 if deadline.expired() {
1066 self.http_metrics
1067 .record_reject(HttpTransport::Http, HttpRejectReason::HandlerTimeout);
1068 Self::write_handler_timeout_503(&mut stream);
1069 return Ok(());
1070 }
1071
1072 if self.try_route_streaming(&request, &mut stream)? {
1073 return Ok(());
1074 }
1075 let response = self.route(request);
1076
1077 let inject_ms = self.slow_inject_ms.load(Ordering::Relaxed);
1081 if inject_ms > 0 {
1082 thread::sleep(Duration::from_millis(inject_ms));
1083 }
1084
1085 if deadline.expired() {
1087 self.http_metrics
1088 .record_reject(HttpTransport::Http, HttpRejectReason::HandlerTimeout);
1089 Self::write_handler_timeout_503(&mut stream);
1090 return Ok(());
1091 }
1092
1093 stream.write_all(&response.to_http_bytes())?;
1094 stream.flush()?;
1095 Ok(())
1096 }
1097
1098 fn write_handler_timeout_503<S: Write>(stream: &mut S) {
1104 const RESPONSE: &[u8] = b"HTTP/1.1 503 Service Unavailable\r\n\
1105 Connection: close\r\n\
1106 Content-Length: 0\r\n\
1107 \r\n";
1108 let _ = stream.write_all(RESPONSE);
1109 let _ = stream.flush();
1110 }
1111}