Skip to main content

servers/
http.rs

1// Copyright 2023 Greptime Team
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::collections::HashMap;
16use std::convert::Infallible;
17use std::fmt::Display;
18use std::net::SocketAddr;
19use std::sync::{Arc, Mutex as StdMutex};
20use std::time::Duration;
21
22use async_trait::async_trait;
23use auth::UserProviderRef;
24use axum::extract::{DefaultBodyLimit, Request, State};
25use axum::http::StatusCode as HttpStatusCode;
26use axum::middleware::Next;
27use axum::response::{IntoResponse, Response};
28use axum::routing::Route;
29use axum::serve::ListenerExt;
30use axum::{Extension, Router, middleware, routing};
31use common_base::readable_size::ReadableSize;
32use common_recordbatch::RecordBatch;
33use common_telemetry::{error, info};
34use common_time::Timestamp;
35use common_time::timestamp::TimeUnit;
36use datatypes::data_type::DataType;
37use datatypes::schema::SchemaRef;
38use event::{LogState, LogValidatorRef};
39use futures::FutureExt;
40use http::{HeaderValue, Method};
41use serde::{Deserialize, Serialize};
42use serde_json::Value;
43use session::context::QueryContext;
44use snafu::{ResultExt, ensure};
45use tokio::sync::Mutex;
46use tokio::sync::oneshot::{self, Sender};
47use tonic::codegen::Service;
48use tower::{Layer, ServiceBuilder};
49use tower_http::compression::CompressionLayer;
50use tower_http::cors::{AllowOrigin, Any, CorsLayer};
51use tower_http::decompression::RequestDecompressionLayer;
52use tower_http::trace::TraceLayer;
53
54use self::authorize::AuthState;
55use self::result::table_result::TableResponse;
56use crate::batcher::BatchingProtocol;
57use crate::batcher::logical_table::LogicalTablePendingRowsBatcher;
58use crate::elasticsearch;
59use crate::error::{
60    AddressBindSnafu, AlreadyStartedSnafu, Error, InternalIoSnafu, InvalidHeaderValueSnafu, Result,
61};
62use crate::http::influxdb::{influxdb_health, influxdb_ping, influxdb_write_v1, influxdb_write_v2};
63use crate::http::otlp::OtlpState;
64use crate::http::prom_store::PromStoreState;
65use crate::http::prometheus::{
66    build_info_query, format_query, instant_query, label_values_query, labels_query,
67    metadata_query, parse_query, range_query, series_query,
68};
69use crate::http::result::arrow_result::ArrowResponse;
70use crate::http::result::csv_result::CsvResponse;
71use crate::http::result::error_result::ErrorResponse;
72use crate::http::result::greptime_result_v1::GreptimedbV1Response;
73use crate::http::result::influxdb_result_v1::InfluxdbV1Response;
74use crate::http::result::json_result::JsonResponse;
75use crate::http::result::null_result::NullResponse;
76use crate::interceptor::LogIngestInterceptorRef;
77use crate::metrics::http_metrics_layer;
78use crate::metrics_handler::MetricsHandler;
79use crate::prometheus_handler::PrometheusHandlerRef;
80use crate::query_handler::sql::ServerSqlQueryHandlerRef;
81use crate::query_handler::{
82    DashboardHandlerRef, InfluxdbLineProtocolHandlerRef, JaegerQueryHandlerRef, LogQueryHandlerRef,
83    OpenTelemetryProtocolHandlerRef, OpentsdbProtocolHandlerRef, PipelineHandlerRef,
84    PromStoreProtocolHandlerRef,
85};
86use crate::request_memory_limiter::ServerMemoryLimiter;
87use crate::server::Server;
88
89pub mod authorize;
90#[cfg(feature = "dashboard")]
91mod dashboard;
92pub mod dyn_log;
93pub mod dyn_trace;
94pub mod event;
95pub mod extractor;
96pub mod handler;
97pub mod header;
98pub mod influxdb;
99pub mod jaeger;
100pub mod logs;
101pub mod loki;
102pub mod mem_prof;
103mod memory_limit;
104pub mod opentsdb;
105pub mod otlp;
106pub mod pprof;
107pub mod prom_store;
108pub mod prometheus;
109pub mod result;
110pub mod splunk;
111mod timeout;
112pub mod utils;
113mod workload_scheduler;
114
115use result::HttpOutputWriter;
116pub(crate) use timeout::DynamicTimeoutLayer;
117
118mod client_ip;
119use crate::prom_remote_write::validation::PromValidationMode;
120mod hints;
121mod read_preference;
122mod skip_wal;
123#[cfg(any(test, feature = "testing"))]
124pub mod test_helpers;
125
126pub const HTTP_API_VERSION: &str = "v1";
127pub const HTTP_API_PREFIX: &str = "/v1/";
128pub const HTTP_API_PREFIX_WITHOUT_TRAILING_SLASH: &str = "/v1";
129
130/// Provides an extra router to merge into an HTTP server.
131pub trait ExtraHttpRouterProvider: Send + Sync {
132    /// Returns the extra HTTP router.
133    fn router(&self) -> Router;
134}
135
136pub type ExtraHttpRouterProviderRef = Arc<dyn ExtraHttpRouterProvider>;
137
138/// Collection of extra HTTP router providers.
139#[derive(Clone, Default)]
140pub struct ExtraHttpRouterProviders {
141    providers: Vec<ExtraHttpRouterProviderRef>,
142}
143
144impl ExtraHttpRouterProviders {
145    /// Creates an empty provider collection.
146    pub fn new() -> Self {
147        Self::default()
148    }
149
150    /// Adds an extra HTTP router provider.
151    pub fn add(&mut self, provider: ExtraHttpRouterProviderRef) {
152        self.providers.push(provider);
153    }
154
155    /// Returns all registered providers.
156    pub fn iter(&self) -> impl Iterator<Item = &dyn ExtraHttpRouterProvider> {
157        self.providers.iter().map(|x| x.as_ref())
158    }
159}
160
161/// Default http body limit (64M).
162const DEFAULT_BODY_LIMIT: ReadableSize = ReadableSize::mb(64);
163/// Default address port for the public HTTP API server.
164const DEFAULT_HTTP_API_ADDR_PORT: u16 = 4006;
165
166/// Authorization header
167pub const AUTHORIZATION_HEADER: &str = "x-greptime-auth";
168
169// TODO(fys): This is a temporary workaround, it will be improved later
170// these APIs will bypass authentication.
171pub static PUBLIC_API_PREFIX: [&str; 4] = [
172    "/v1/influxdb/ping",
173    "/v1/influxdb/health",
174    "/v1/health",
175    "/v1/splunk/services/collector/health",
176];
177
178/// Listener mode for an [`HttpServer`].
179///
180/// Both modes share the *same* complete route registry; the [`HttpServerKind::Api`]
181/// listener additionally installs an outer guard that only exposes the `/v1` APIs
182/// and the dashboard, returning `404` for every other path.
183#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
184pub(crate) enum HttpServerKind {
185    /// Serves the complete historical HTTP surface (both `v1` APIs and internal
186    /// interfaces such as health, status, metrics, config and debug).
187    #[default]
188    Full,
189    /// Serves only the `v1` interfaces plus the dashboard.
190    Api,
191}
192
193#[derive(Default)]
194pub struct HttpServer {
195    router: StdMutex<Router>,
196    shutdown_tx: Mutex<Option<Sender<()>>>,
197    user_provider: Option<UserProviderRef>,
198    memory_limiter: ServerMemoryLimiter,
199
200    // server configs
201    options: HttpOptions,
202    batching_protocols: Vec<BatchingProtocol>,
203    logical_batching_protocols: Vec<BatchingProtocol>,
204    bind_addr: Option<SocketAddr>,
205    /// What this server instance exposes. See [`HttpServerKind`].
206    kind: HttpServerKind,
207}
208
209/// Returns `true` when `path` equals `root` or is nested directly under it
210/// (`root/...`). Uses an exact slash boundary so `/v1` does **not** match `/v10`
211/// or `/v1-internal`.
212pub(crate) fn is_namespace(path: &str, root: &str) -> bool {
213    path == root
214        || path
215            .strip_prefix(root)
216            .is_some_and(|suffix| suffix.starts_with('/'))
217}
218
219/// Paths visible on the dedicated API listener: the `/v1` APIs and the dashboard.
220pub fn is_api_listener_path(path: &str) -> bool {
221    is_namespace(path, HTTP_API_PREFIX_WITHOUT_TRAILING_SLASH) || is_namespace(path, "/dashboard")
222}
223
224#[derive(Clone)]
225struct LogicalBatchingProtocols(Vec<BatchingProtocol>);
226
227/// Sets a local-only write selector after authentication creates the context.
228pub async fn set_http_write_batching(
229    State(protocol): State<BatchingProtocol>,
230    mut req: Request,
231    next: Next,
232) -> Response {
233    let enabled = req
234        .extensions()
235        .get::<Arc<Vec<BatchingProtocol>>>()
236        .is_some_and(|protocols| protocols.contains(&protocol));
237    let logical_enabled = req
238        .extensions()
239        .get::<LogicalBatchingProtocols>()
240        .is_some_and(|protocols| protocols.0.contains(&protocol));
241    if let Some(ctx) = req.extensions_mut().get_mut::<QueryContext>() {
242        ctx.set_batching_enabled(enabled);
243        ctx.set_logical_batching_enabled(logical_enabled);
244    }
245    next.run(req).await
246}
247
248/// Outer guard for the API listener. Rejects any path outside the API surface
249/// with `404 Not Found` before it can reach a handler (and before auth side
250/// effects). Reachability is kept separate from authentication.
251async fn enforce_api_surface(req: Request, next: Next) -> Response {
252    if !is_api_listener_path(req.uri().path()) {
253        return HttpStatusCode::NOT_FOUND.into_response();
254    }
255    next.run(req).await
256}
257
258impl HttpServer {
259    /// A short human-readable label for this server instance, used in logs.
260    fn kind(&self) -> &'static str {
261        match self.kind {
262            HttpServerKind::Api => "HTTP API",
263            HttpServerKind::Full => "HTTP",
264        }
265    }
266}
267
268#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
269#[serde(default)]
270pub struct HttpOptions {
271    pub addr: String,
272
273    /// Request timeout; zero disables it. Frontend raises a nonzero timeout to at least
274    /// the largest active synchronous Prom or shared table batch flush interval plus one second.
275    #[serde(with = "humantime_serde")]
276    pub timeout: Duration,
277
278    #[serde(skip)]
279    pub disable_dashboard: bool,
280
281    pub body_limit: ReadableSize,
282
283    pub cors_allowed_origins: Vec<String>,
284
285    pub enable_cors: bool,
286
287    /// Whether to start the dedicated public HTTP **API** server, which serves
288    /// only the `v1` interfaces plus the dashboard. It shares every other
289    /// `[http]` option with the main server and only differs by its bound
290    /// address (see `api_server_addr`).
291    pub enable_api_server: bool,
292    /// The address to bind the dedicated HTTP API server, in the same form as
293    /// `addr` (e.g. `127.0.0.1:4006`).
294    pub api_server_addr: String,
295}
296
297impl Default for HttpOptions {
298    fn default() -> Self {
299        Self {
300            addr: "127.0.0.1:4000".to_string(),
301            timeout: Duration::from_secs(0),
302            disable_dashboard: false,
303            body_limit: DEFAULT_BODY_LIMIT,
304            cors_allowed_origins: Vec::new(),
305            enable_cors: true,
306            enable_api_server: false,
307            api_server_addr: format!("127.0.0.1:{}", DEFAULT_HTTP_API_ADDR_PORT),
308        }
309    }
310}
311
312#[derive(Debug, Serialize, Deserialize, Eq, PartialEq)]
313pub struct ColumnSchema {
314    name: String,
315    data_type: String,
316}
317
318impl ColumnSchema {
319    pub fn new(name: String, data_type: String) -> ColumnSchema {
320        ColumnSchema { name, data_type }
321    }
322}
323
324#[derive(Debug, Serialize, Deserialize, Eq, PartialEq)]
325pub struct OutputSchema {
326    column_schemas: Vec<ColumnSchema>,
327}
328
329impl OutputSchema {
330    pub fn new(columns: Vec<ColumnSchema>) -> OutputSchema {
331        OutputSchema {
332            column_schemas: columns,
333        }
334    }
335}
336
337impl From<SchemaRef> for OutputSchema {
338    fn from(schema: SchemaRef) -> OutputSchema {
339        OutputSchema {
340            column_schemas: schema
341                .column_schemas()
342                .iter()
343                .map(|cs| ColumnSchema {
344                    name: cs.name.clone(),
345                    data_type: cs.data_type.name(),
346                })
347                .collect(),
348        }
349    }
350}
351
352#[derive(Debug, Serialize, Deserialize, Eq, PartialEq)]
353pub struct HttpRecordsOutput {
354    schema: OutputSchema,
355    rows: Vec<Vec<Value>>,
356    // total_rows is equal to rows.len() in most cases,
357    // the Dashboard query result may be truncated, so we need to return the total_rows.
358    #[serde(default)]
359    total_rows: usize,
360
361    // plan level execution metrics
362    #[serde(skip_serializing_if = "HashMap::is_empty")]
363    #[serde(default)]
364    metrics: HashMap<String, Value>,
365}
366
367impl HttpRecordsOutput {
368    pub fn num_rows(&self) -> usize {
369        self.rows.len()
370    }
371
372    pub fn num_cols(&self) -> usize {
373        self.schema.column_schemas.len()
374    }
375
376    pub fn schema(&self) -> &OutputSchema {
377        &self.schema
378    }
379
380    pub fn rows(&self) -> &Vec<Vec<Value>> {
381        &self.rows
382    }
383}
384
385impl HttpRecordsOutput {
386    pub fn try_new(
387        schema: SchemaRef,
388        recordbatches: Vec<RecordBatch>,
389    ) -> std::result::Result<HttpRecordsOutput, Error> {
390        if recordbatches.is_empty() {
391            Ok(HttpRecordsOutput {
392                schema: OutputSchema::from(schema),
393                rows: vec![],
394                total_rows: 0,
395                metrics: Default::default(),
396            })
397        } else {
398            let num_rows = recordbatches.iter().map(|r| r.num_rows()).sum::<usize>();
399            let mut rows = Vec::with_capacity(num_rows);
400
401            for recordbatch in recordbatches {
402                let mut writer = HttpOutputWriter::new(schema.num_columns(), None);
403                writer.write(recordbatch, &mut rows)?;
404            }
405
406            Ok(HttpRecordsOutput {
407                schema: OutputSchema::from(schema),
408                total_rows: rows.len(),
409                rows,
410                metrics: Default::default(),
411            })
412        }
413    }
414}
415
416#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)]
417#[serde(rename_all = "lowercase")]
418pub enum GreptimeQueryOutput {
419    AffectedRows(usize),
420    Records(HttpRecordsOutput),
421}
422
423/// It allows the results of SQL queries to be presented in different formats.
424#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
425pub enum ResponseFormat {
426    Arrow,
427    // (with_names, with_types)
428    Csv(bool, bool),
429    Table,
430    #[default]
431    GreptimedbV1,
432    InfluxdbV1,
433    Json,
434    Null,
435}
436
437impl ResponseFormat {
438    pub fn parse(s: &str) -> Option<Self> {
439        match s {
440            "arrow" => Some(ResponseFormat::Arrow),
441            "csv" => Some(ResponseFormat::Csv(false, false)),
442            "csvwithnames" => Some(ResponseFormat::Csv(true, false)),
443            "csvwithnamesandtypes" => Some(ResponseFormat::Csv(true, true)),
444            "table" => Some(ResponseFormat::Table),
445            "greptimedb_v1" => Some(ResponseFormat::GreptimedbV1),
446            "influxdb_v1" => Some(ResponseFormat::InfluxdbV1),
447            "json" => Some(ResponseFormat::Json),
448            "null" => Some(ResponseFormat::Null),
449            _ => None,
450        }
451    }
452
453    pub fn as_str(&self) -> &'static str {
454        match self {
455            ResponseFormat::Arrow => "arrow",
456            ResponseFormat::Csv(_, _) => "csv",
457            ResponseFormat::Table => "table",
458            ResponseFormat::GreptimedbV1 => "greptimedb_v1",
459            ResponseFormat::InfluxdbV1 => "influxdb_v1",
460            ResponseFormat::Json => "json",
461            ResponseFormat::Null => "null",
462        }
463    }
464}
465
466#[derive(Debug, Clone, Copy, PartialEq, Eq)]
467pub enum Epoch {
468    Nanosecond,
469    Microsecond,
470    Millisecond,
471    Second,
472}
473
474impl Epoch {
475    pub fn parse(s: &str) -> Option<Epoch> {
476        // Both u and µ indicate microseconds.
477        // epoch = [ns,u,µ,ms,s],
478        // For details, see the Influxdb documents.
479        // https://docs.influxdata.com/influxdb/v1/tools/api/#query-string-parameters-1
480        match s {
481            "ns" => Some(Epoch::Nanosecond),
482            "u" | "µ" => Some(Epoch::Microsecond),
483            "ms" => Some(Epoch::Millisecond),
484            "s" => Some(Epoch::Second),
485            _ => None, // just returns None for other cases
486        }
487    }
488
489    pub fn convert_timestamp(&self, ts: Timestamp) -> Option<Timestamp> {
490        match self {
491            Epoch::Nanosecond => ts.convert_to(TimeUnit::Nanosecond),
492            Epoch::Microsecond => ts.convert_to(TimeUnit::Microsecond),
493            Epoch::Millisecond => ts.convert_to(TimeUnit::Millisecond),
494            Epoch::Second => ts.convert_to(TimeUnit::Second),
495        }
496    }
497}
498
499impl Display for Epoch {
500    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
501        match self {
502            Epoch::Nanosecond => write!(f, "Epoch::Nanosecond"),
503            Epoch::Microsecond => write!(f, "Epoch::Microsecond"),
504            Epoch::Millisecond => write!(f, "Epoch::Millisecond"),
505            Epoch::Second => write!(f, "Epoch::Second"),
506        }
507    }
508}
509
510#[derive(Serialize, Deserialize, Debug)]
511pub enum HttpResponse {
512    Arrow(ArrowResponse),
513    Csv(CsvResponse),
514    Table(TableResponse),
515    Error(ErrorResponse),
516    GreptimedbV1(GreptimedbV1Response),
517    InfluxdbV1(InfluxdbV1Response),
518    Json(JsonResponse),
519    Null(NullResponse),
520}
521
522impl HttpResponse {
523    pub fn with_execution_time(self, execution_time: u64) -> Self {
524        match self {
525            HttpResponse::Arrow(resp) => resp.with_execution_time(execution_time).into(),
526            HttpResponse::Csv(resp) => resp.with_execution_time(execution_time).into(),
527            HttpResponse::Table(resp) => resp.with_execution_time(execution_time).into(),
528            HttpResponse::GreptimedbV1(resp) => resp.with_execution_time(execution_time).into(),
529            HttpResponse::InfluxdbV1(resp) => resp.with_execution_time(execution_time).into(),
530            HttpResponse::Json(resp) => resp.with_execution_time(execution_time).into(),
531            HttpResponse::Null(resp) => resp.with_execution_time(execution_time).into(),
532            HttpResponse::Error(resp) => resp.with_execution_time(execution_time).into(),
533        }
534    }
535
536    pub fn with_limit(self, limit: usize) -> Self {
537        match self {
538            HttpResponse::Csv(resp) => resp.with_limit(limit).into(),
539            HttpResponse::Table(resp) => resp.with_limit(limit).into(),
540            HttpResponse::GreptimedbV1(resp) => resp.with_limit(limit).into(),
541            HttpResponse::Json(resp) => resp.with_limit(limit).into(),
542            _ => self,
543        }
544    }
545}
546
547pub fn process_with_limit(
548    mut outputs: Vec<GreptimeQueryOutput>,
549    limit: usize,
550) -> Vec<GreptimeQueryOutput> {
551    outputs
552        .drain(..)
553        .map(|data| match data {
554            GreptimeQueryOutput::Records(mut records) => {
555                if records.rows.len() > limit {
556                    records.rows.truncate(limit);
557                    records.total_rows = limit;
558                }
559                GreptimeQueryOutput::Records(records)
560            }
561            _ => data,
562        })
563        .collect()
564}
565
566impl IntoResponse for HttpResponse {
567    fn into_response(self) -> Response {
568        match self {
569            HttpResponse::Arrow(resp) => resp.into_response(),
570            HttpResponse::Csv(resp) => resp.into_response(),
571            HttpResponse::Table(resp) => resp.into_response(),
572            HttpResponse::GreptimedbV1(resp) => resp.into_response(),
573            HttpResponse::InfluxdbV1(resp) => resp.into_response(),
574            HttpResponse::Json(resp) => resp.into_response(),
575            HttpResponse::Null(resp) => resp.into_response(),
576            HttpResponse::Error(resp) => resp.into_response(),
577        }
578    }
579}
580
581impl From<ArrowResponse> for HttpResponse {
582    fn from(value: ArrowResponse) -> Self {
583        HttpResponse::Arrow(value)
584    }
585}
586
587impl From<CsvResponse> for HttpResponse {
588    fn from(value: CsvResponse) -> Self {
589        HttpResponse::Csv(value)
590    }
591}
592
593impl From<TableResponse> for HttpResponse {
594    fn from(value: TableResponse) -> Self {
595        HttpResponse::Table(value)
596    }
597}
598
599impl From<ErrorResponse> for HttpResponse {
600    fn from(value: ErrorResponse) -> Self {
601        HttpResponse::Error(value)
602    }
603}
604
605impl From<GreptimedbV1Response> for HttpResponse {
606    fn from(value: GreptimedbV1Response) -> Self {
607        HttpResponse::GreptimedbV1(value)
608    }
609}
610
611impl From<InfluxdbV1Response> for HttpResponse {
612    fn from(value: InfluxdbV1Response) -> Self {
613        HttpResponse::InfluxdbV1(value)
614    }
615}
616
617impl From<JsonResponse> for HttpResponse {
618    fn from(value: JsonResponse) -> Self {
619        HttpResponse::Json(value)
620    }
621}
622
623impl From<NullResponse> for HttpResponse {
624    fn from(value: NullResponse) -> Self {
625        HttpResponse::Null(value)
626    }
627}
628
629#[derive(Clone)]
630pub struct ApiState {
631    pub sql_handler: ServerSqlQueryHandlerRef,
632}
633
634#[derive(Clone)]
635pub struct GreptimeOptionsConfigState {
636    pub greptime_config_options: String,
637}
638
639#[derive(Clone)]
640pub struct DashboardState {
641    pub handler: DashboardHandlerRef,
642}
643
644pub struct HttpServerBuilder {
645    options: HttpOptions,
646    batching_protocols: Vec<BatchingProtocol>,
647    logical_batching_protocols: Vec<BatchingProtocol>,
648    user_provider: Option<UserProviderRef>,
649    router: Router,
650    memory_limiter: ServerMemoryLimiter,
651}
652
653impl HttpServerBuilder {
654    pub fn new(options: HttpOptions) -> Self {
655        Self {
656            options,
657            batching_protocols: Vec::new(),
658            logical_batching_protocols: Vec::new(),
659            user_provider: None,
660            router: Router::new(),
661            memory_limiter: ServerMemoryLimiter::default(),
662        }
663    }
664
665    /// Selects HTTP protocols allowed to use logical-table batching.
666    pub fn with_logical_batching_protocols(mut self, protocols: Vec<BatchingProtocol>) -> Self {
667        self.logical_batching_protocols = protocols;
668        self
669    }
670
671    /// Selects HTTP protocols allowed to use ordinary-table batching.
672    pub fn with_batching_protocols(mut self, protocols: Vec<BatchingProtocol>) -> Self {
673        self.batching_protocols = protocols;
674        self
675    }
676
677    /// Set a global memory limiter for all server protocols.
678    pub fn with_memory_limiter(mut self, limiter: ServerMemoryLimiter) -> Self {
679        self.memory_limiter = limiter;
680        self
681    }
682
683    pub fn with_sql_handler(self, sql_handler: ServerSqlQueryHandlerRef) -> Self {
684        let batch_body_limit = if self.options.body_limit.0 == 0 {
685            4 * 1024 * 1024
686        } else {
687            (self.options.body_limit.0 as usize).min(4 * 1024 * 1024)
688        };
689        let sql_router = HttpServer::route_sql(ApiState { sql_handler }, batch_body_limit);
690
691        Self {
692            router: self
693                .router
694                .nest(&format!("/{HTTP_API_VERSION}"), sql_router),
695            ..self
696        }
697    }
698
699    pub fn with_logs_handler(self, logs_handler: LogQueryHandlerRef) -> Self {
700        let logs_router = HttpServer::route_logs(logs_handler);
701
702        Self {
703            router: self
704                .router
705                .nest(&format!("/{HTTP_API_VERSION}"), logs_router),
706            ..self
707        }
708    }
709
710    pub fn with_opentsdb_handler(self, handler: OpentsdbProtocolHandlerRef) -> Self {
711        Self {
712            router: self.router.nest(
713                &format!("/{HTTP_API_VERSION}/opentsdb"),
714                HttpServer::route_opentsdb(handler),
715            ),
716            ..self
717        }
718    }
719
720    pub fn with_influxdb_handler(self, handler: InfluxdbLineProtocolHandlerRef) -> Self {
721        Self {
722            router: self.router.nest(
723                &format!("/{HTTP_API_VERSION}/influxdb"),
724                HttpServer::route_influxdb(handler, self.memory_limiter.clone()),
725            ),
726            ..self
727        }
728    }
729
730    pub fn with_prom_handler(
731        self,
732        handler: PromStoreProtocolHandlerRef,
733        pipeline_handler: Option<PipelineHandlerRef>,
734        prom_store_with_metric_engine: bool,
735        prom_validation_mode: PromValidationMode,
736        pending_rows_batcher: Option<Arc<LogicalTablePendingRowsBatcher>>,
737    ) -> Self {
738        let state = PromStoreState {
739            prom_store_handler: handler,
740            pipeline_handler,
741            prom_store_with_metric_engine,
742            prom_validation_mode,
743            pending_rows_batcher,
744            memory_limiter: self.memory_limiter.clone(),
745        };
746
747        Self {
748            router: self.router.nest(
749                &format!("/{HTTP_API_VERSION}/prometheus"),
750                HttpServer::route_prom(state),
751            ),
752            ..self
753        }
754    }
755
756    pub fn with_prometheus_handler(self, handler: PrometheusHandlerRef) -> Self {
757        Self {
758            router: self.router.nest(
759                &format!("/{HTTP_API_VERSION}/prometheus/api/v1"),
760                HttpServer::route_prometheus(handler),
761            ),
762            ..self
763        }
764    }
765
766    pub fn with_otlp_handler(
767        self,
768        handler: OpenTelemetryProtocolHandlerRef,
769        with_metric_engine: bool,
770    ) -> Self {
771        Self {
772            router: self.router.nest(
773                &format!("/{HTTP_API_VERSION}/otlp"),
774                HttpServer::route_otlp(handler, with_metric_engine, self.memory_limiter.clone()),
775            ),
776            ..self
777        }
778    }
779
780    pub fn with_user_provider(self, user_provider: UserProviderRef) -> Self {
781        Self {
782            user_provider: Some(user_provider),
783            ..self
784        }
785    }
786
787    pub fn with_metrics_handler(self, handler: MetricsHandler) -> Self {
788        Self {
789            router: self.router.merge(HttpServer::route_metrics(handler)),
790            ..self
791        }
792    }
793
794    pub fn with_log_ingest_handler(
795        self,
796        handler: PipelineHandlerRef,
797        validator: Option<LogValidatorRef>,
798        ingest_interceptor: Option<LogIngestInterceptorRef<Error>>,
799    ) -> Self {
800        let log_state = LogState {
801            log_handler: handler,
802            log_validator: validator,
803            ingest_interceptor,
804        };
805
806        let router = self.router.nest(
807            &format!("/{HTTP_API_VERSION}"),
808            HttpServer::route_pipelines(log_state.clone(), self.memory_limiter.clone()),
809        );
810        // deprecated since v0.11.0. Use `/logs` and `/pipelines` instead.
811        let router = router.nest(
812            &format!("/{HTTP_API_VERSION}/events"),
813            #[allow(deprecated)]
814            HttpServer::route_log_deprecated(log_state.clone(), self.memory_limiter.clone()),
815        );
816
817        let router = router.nest(
818            &format!("/{HTTP_API_VERSION}/loki"),
819            HttpServer::route_loki(log_state.clone(), self.memory_limiter.clone()),
820        );
821
822        let router = router.nest(
823            &format!("/{HTTP_API_VERSION}/elasticsearch"),
824            HttpServer::route_elasticsearch(log_state.clone(), self.memory_limiter.clone()),
825        );
826
827        let router = router.nest(
828            &format!("/{HTTP_API_VERSION}/elasticsearch/"),
829            Router::new()
830                .route("/", routing::get(elasticsearch::handle_get_version))
831                .with_state(log_state.clone()),
832        );
833
834        let router = router.nest(
835            &format!("/{HTTP_API_VERSION}/splunk"),
836            HttpServer::route_splunk(log_state, self.memory_limiter.clone()),
837        );
838
839        Self { router, ..self }
840    }
841
842    pub fn with_greptime_config_options(self, opts: String) -> Self {
843        let config_router = HttpServer::route_config(GreptimeOptionsConfigState {
844            greptime_config_options: opts,
845        });
846
847        Self {
848            router: self.router.merge(config_router),
849            ..self
850        }
851    }
852
853    pub fn with_jaeger_handler(self, handler: JaegerQueryHandlerRef) -> Self {
854        Self {
855            router: self.router.nest(
856                &format!("/{HTTP_API_VERSION}/jaeger"),
857                HttpServer::route_jaeger(handler),
858            ),
859            ..self
860        }
861    }
862
863    pub fn with_dashboard_handler(self, handler: DashboardHandlerRef) -> Self {
864        Self {
865            router: self.router.nest(
866                &format!("/{HTTP_API_VERSION}/dashboards"),
867                HttpServer::route_dashboard(handler, self.memory_limiter.clone()),
868            ),
869            ..self
870        }
871    }
872
873    pub fn with_extra_router(self, router: Router) -> Self {
874        Self {
875            router: self.router.merge(router),
876            ..self
877        }
878    }
879
880    pub fn add_layer<L>(self, layer: L) -> Self
881    where
882        L: Layer<Route> + Clone + Send + Sync + 'static,
883        L::Service: Service<Request> + Clone + Send + Sync + 'static,
884        <L::Service as Service<Request>>::Response: IntoResponse + 'static,
885        <L::Service as Service<Request>>::Error: Into<Infallible> + 'static,
886        <L::Service as Service<Request>>::Future: Send + 'static,
887    {
888        Self {
889            router: self.router.layer(layer),
890            ..self
891        }
892    }
893
894    pub fn build(self) -> HttpServer {
895        HttpServer {
896            options: self.options,
897            batching_protocols: self.batching_protocols.clone(),
898            logical_batching_protocols: self.logical_batching_protocols.clone(),
899            user_provider: self.user_provider,
900            shutdown_tx: Mutex::new(None),
901            router: StdMutex::new(self.router),
902            bind_addr: None,
903            memory_limiter: self.memory_limiter,
904            kind: HttpServerKind::Full,
905        }
906    }
907
908    /// Builds the full HTTP server (serves the `v1` interfaces **and** the
909    /// internal interfaces such as health, status, metrics, config and debug).
910    /// When [`HttpOptions::enable_api_server`] is `true`, it also builds a
911    /// dedicated HTTP **API** server that serves only the `v1` interfaces plus
912    /// the dashboard.
913    ///
914    /// Both servers are built from the **same** complete route registry (so every
915    /// built-in, plugin and downstream route registered via `with_extra_router()`
916    /// is present on both). The API server additionally installs an outer guard
917    /// that exposes only the `/v1` and `/dashboard` namespaces, returning `404`
918    /// for everything else. This means downstream projects (e.g. enterprise) do
919    /// not need to classify their routes as public/internal: non-`/v1`
920    /// operational routes are hidden from the API listener automatically.
921    ///
922    /// The first returned server is bound to `self.options.addr` (e.g. port
923    /// `4000`) and keeps the historical single-server behavior. The optional
924    /// second server is bound to `api_server_addr` (e.g. `127.0.0.1:4006`). The
925    /// API server inherits **every** option from `[http]` except the bound address.
926    pub fn build_servers(self) -> (HttpServer, Option<HttpServer>) {
927        let api_enabled = self.options.enable_api_server;
928        let api_addr = self.options.api_server_addr.clone();
929
930        let internal = HttpServer {
931            options: self.options,
932            batching_protocols: self.batching_protocols.clone(),
933            logical_batching_protocols: self.logical_batching_protocols.clone(),
934            user_provider: self.user_provider.clone(),
935            shutdown_tx: Mutex::new(None),
936            router: StdMutex::new(self.router.clone()),
937            bind_addr: None,
938            memory_limiter: self.memory_limiter.clone(),
939            kind: HttpServerKind::Full,
940        };
941
942        let api = if api_enabled {
943            // The API server shares every option with the full server except the
944            // bound address.
945            let api_options = HttpOptions {
946                addr: api_addr,
947                ..internal.options.clone()
948            };
949            Some(HttpServer {
950                options: api_options,
951                batching_protocols: self.batching_protocols,
952                logical_batching_protocols: self.logical_batching_protocols,
953                user_provider: self.user_provider.clone(),
954                shutdown_tx: Mutex::new(None),
955                router: StdMutex::new(self.router),
956                bind_addr: None,
957                memory_limiter: self.memory_limiter,
958                kind: HttpServerKind::Api,
959            })
960        } else {
961            None
962        };
963
964        (internal, api)
965    }
966}
967
968impl HttpServer {
969    /// Builds the complete route registry shared by every HTTP server: all the
970    /// `v1` protocol routes, the root/health/status/metrics/config interfaces,
971    /// the dashboard and (via `build()`) the debug routes.
972    ///
973    /// Both [`HttpServerKind::Full`] and [`HttpServerKind::Api`] listeners are
974    /// built from this same router. The API listener hides the non-`v1` paths
975    /// later, in [`HttpServer::build`], via an outer guard. Keeping one router
976    /// means downstream routes added through `with_extra_router()` are present
977    /// on both listeners automatically, and the API guard decides exposure.
978    pub fn make_app(&self) -> Router {
979        let mut router = self.router.lock().unwrap().clone();
980
981        router = router
982            .route(
983                &format!("/{HTTP_API_VERSION}/health"),
984                routing::get(handler::health).post(handler::health),
985            )
986            .route("/", routing::get(handler::index))
987            .route(
988                "/health",
989                routing::get(handler::health).post(handler::health),
990            )
991            .route(
992                "/ready",
993                routing::get(handler::health).post(handler::health),
994            )
995            .route("/status", routing::get(handler::status));
996
997        // The dashboard runs on top of the `v1` APIs, so it is exposed on every
998        // HTTP server (the API listener's namespace guard allows `/dashboard`).
999        #[cfg(feature = "dashboard")]
1000        {
1001            if !self.options.disable_dashboard {
1002                info!("Enable dashboard service at '/dashboard'");
1003                // redirect /dashboard to /dashboard/
1004                router = router.route(
1005                    "/dashboard",
1006                    routing::get(|uri: axum::http::uri::Uri| async move {
1007                        let path = uri.path();
1008                        let query = uri.query().map(|q| format!("?{}", q)).unwrap_or_default();
1009
1010                        let new_uri = format!("{}/{}", path, query);
1011                        axum::response::Redirect::permanent(&new_uri)
1012                    }),
1013                );
1014
1015                // "/dashboard" and "/dashboard/" are two different paths in Axum.
1016                // We cannot nest "/dashboard/", because we already mapping "/dashboard/{*x}" while nesting "/dashboard".
1017                // So we explicitly route "/dashboard/" here.
1018                router = router
1019                    .route(
1020                        "/dashboard/",
1021                        routing::get(dashboard::static_handler).post(dashboard::static_handler),
1022                    )
1023                    .route(
1024                        "/dashboard/{*x}",
1025                        routing::get(dashboard::static_handler).post(dashboard::static_handler),
1026                    );
1027            }
1028        }
1029
1030        // Add a layer to collect HTTP metrics for axum.
1031        router = router.route_layer(middleware::from_fn(http_metrics_layer));
1032
1033        router
1034    }
1035
1036    /// Attaches middlewares and debug routes to the router.
1037    /// Callers should call this method after [HttpServer::make_app()].
1038    pub fn build(&self, router: Router) -> Result<Router> {
1039        let timeout_layer = if self.options.timeout != Duration::default() {
1040            Some(
1041                ServiceBuilder::new().layer(
1042                    DynamicTimeoutLayer::new(self.options.timeout)
1043                        .with_status_code_fn(Self::request_timeout_status_code),
1044                ),
1045            )
1046        } else {
1047            info!("HTTP server timeout is disabled");
1048            None
1049        };
1050        let body_limit_layer = if self.options.body_limit != ReadableSize(0) {
1051            Some(
1052                ServiceBuilder::new()
1053                    .layer(DefaultBodyLimit::max(self.options.body_limit.0 as usize)),
1054            )
1055        } else {
1056            info!("HTTP server body limit is disabled");
1057            None
1058        };
1059        let cors_layer = if self.options.enable_cors {
1060            Some(
1061                CorsLayer::new()
1062                    .allow_methods([
1063                        Method::GET,
1064                        Method::POST,
1065                        Method::PUT,
1066                        Method::DELETE,
1067                        Method::HEAD,
1068                    ])
1069                    .allow_origin(cors_allow_origin(&self.options.cors_allowed_origins)?)
1070                    .allow_headers(Any),
1071            )
1072        } else {
1073            info!("HTTP server cross-origin is disabled");
1074            None
1075        };
1076
1077        let router = router
1078            // middlewares
1079            .layer(
1080                ServiceBuilder::new()
1081                    // disable on failure tracing. because printing out isn't very helpful,
1082                    // and we have impl IntoResponse for Error. It will print out more detailed error messages
1083                    .layer(TraceLayer::new_for_http().on_failure(()))
1084                    .option_layer(cors_layer)
1085                    .option_layer(timeout_layer)
1086                    .option_layer(body_limit_layer)
1087                    // memory limit layer - must be before body is consumed
1088                    .layer(middleware::from_fn_with_state(
1089                        self.memory_limiter.clone(),
1090                        memory_limit::memory_limit_middleware,
1091                    ))
1092                    // auth layer
1093                    .layer(middleware::from_fn_with_state(
1094                        AuthState::new(self.user_provider.clone()),
1095                        authorize::check_http_auth,
1096                    ))
1097                    .layer(Extension(Arc::new(self.batching_protocols.clone())))
1098                    .layer(Extension(LogicalBatchingProtocols(
1099                        self.logical_batching_protocols.clone(),
1100                    )))
1101                    .layer(middleware::from_fn(hints::extract_hints))
1102                    .layer(middleware::from_fn(client_ip::log_error_with_client_ip))
1103                    .layer(middleware::from_fn(
1104                        read_preference::extract_read_preference,
1105                    ))
1106                    .layer(middleware::from_fn(skip_wal::extract_skip_wal)),
1107            );
1108
1109        // Debug handlers are part of the complete router; the API listener hides
1110        // them through the outer namespace guard applied below.
1111        let router = router.nest(
1112            "/debug",
1113            Router::new()
1114                // handler for changing log level dynamically
1115                .route("/log_level", routing::post(dyn_log::dyn_log_handler))
1116                .route(
1117                    "/workload_scheduler",
1118                    routing::get(workload_scheduler::get_status_handler),
1119                )
1120                .route(
1121                    "/workload_scheduler/enabled",
1122                    routing::post(workload_scheduler::set_enabled_handler),
1123                )
1124                .route(
1125                    "/workload_scheduler/weights",
1126                    routing::post(workload_scheduler::set_weights_handler),
1127                )
1128                .route("/enable_trace", routing::post(dyn_trace::dyn_trace_handler))
1129                .nest(
1130                    "/prof",
1131                    Router::new()
1132                        .route("/cpu", routing::post(pprof::pprof_handler))
1133                        .route("/mem", routing::post(mem_prof::mem_prof_handler))
1134                        .route("/mem/symbol", routing::post(mem_prof::symbolicate_handler))
1135                        .route(
1136                            "/mem/activate",
1137                            routing::post(mem_prof::activate_heap_prof_handler),
1138                        )
1139                        .route(
1140                            "/mem/deactivate",
1141                            routing::post(mem_prof::deactivate_heap_prof_handler),
1142                        )
1143                        .route(
1144                            "/mem/status",
1145                            routing::get(mem_prof::heap_prof_status_handler),
1146                        ) // jemalloc gdump flag status and toggle
1147                        .route(
1148                            "/mem/gdump",
1149                            routing::get(mem_prof::gdump_status_handler)
1150                                .post(mem_prof::gdump_toggle_handler),
1151                        ),
1152                ),
1153        );
1154
1155        // Seal the API listener last: this is the outermost layer, applied after
1156        // all middleware, debug routes and any caller/plugin routes, so that only
1157        // `/v1` and `/dashboard` paths can reach a handler. Excluded paths return
1158        // 404 before auth or any other side effect.
1159        if self.kind == HttpServerKind::Api {
1160            Ok(router.layer(middleware::from_fn(enforce_api_surface)))
1161        } else {
1162            Ok(router)
1163        }
1164    }
1165
1166    fn request_timeout_status_code(request: &Request) -> HttpStatusCode {
1167        if request.uri().path() == "/v1/prometheus/write" {
1168            HttpStatusCode::GATEWAY_TIMEOUT
1169        } else {
1170            HttpStatusCode::REQUEST_TIMEOUT
1171        }
1172    }
1173
1174    fn route_metrics<S>(metrics_handler: MetricsHandler) -> Router<S> {
1175        Router::new()
1176            .route("/metrics", routing::get(handler::metrics))
1177            .with_state(metrics_handler)
1178    }
1179
1180    fn route_loki<S>(log_state: LogState, memory_limiter: ServerMemoryLimiter) -> Router<S> {
1181        Router::new()
1182            .route("/api/v1/push", routing::post(loki::loki_ingest))
1183            .layer(middleware::from_fn_with_state(
1184                memory_limiter,
1185                memory_limit::decoded_body_accounting_middleware,
1186            ))
1187            .layer(
1188                ServiceBuilder::new()
1189                    .layer(RequestDecompressionLayer::new().pass_through_unaccepted(true)),
1190            )
1191            .layer(middleware::from_fn_with_state(
1192                BatchingProtocol::Loki,
1193                set_http_write_batching,
1194            ))
1195            .with_state(log_state)
1196    }
1197
1198    fn route_splunk<S>(log_state: LogState, memory_limiter: ServerMemoryLimiter) -> Router<S> {
1199        Router::new()
1200            .route(
1201                "/services/collector/health",
1202                routing::get(splunk::handle_health),
1203            )
1204            .route(
1205                "/services/collector/health/1.0",
1206                routing::get(splunk::handle_health),
1207            )
1208            // The event endpoint plus its base and versioned aliases all serve
1209            // the same handler (Splunk JSON event protocol).
1210            .route(
1211                "/services/collector/event",
1212                routing::post(splunk::handle_event),
1213            )
1214            .route("/services/collector", routing::post(splunk::handle_event))
1215            .route(
1216                "/services/collector/event/1.0",
1217                routing::post(splunk::handle_event),
1218            )
1219            // The raw endpoint (plain-text body, one event per line) plus its
1220            // versioned alias.
1221            .route("/services/collector/raw", routing::post(splunk::handle_raw))
1222            .route(
1223                "/services/collector/raw/1.0",
1224                routing::post(splunk::handle_raw),
1225            )
1226            .layer(middleware::from_fn_with_state(
1227                memory_limiter,
1228                memory_limit::decoded_body_accounting_middleware,
1229            ))
1230            .layer(
1231                ServiceBuilder::new()
1232                    .layer(RequestDecompressionLayer::new().pass_through_unaccepted(true)),
1233            )
1234            .layer(middleware::from_fn_with_state(
1235                BatchingProtocol::Splunk,
1236                set_http_write_batching,
1237            ))
1238            .with_state(log_state)
1239    }
1240
1241    fn route_elasticsearch<S>(
1242        log_state: LogState,
1243        memory_limiter: ServerMemoryLimiter,
1244    ) -> Router<S> {
1245        Router::new()
1246            // Return fake responsefor HEAD '/' request.
1247            .route(
1248                "/",
1249                routing::head((HttpStatusCode::OK, elasticsearch::elasticsearch_headers())),
1250            )
1251            // Return fake response for Elasticsearch version request.
1252            .route("/", routing::get(elasticsearch::handle_get_version))
1253            // Return fake response for Elasticsearch license request.
1254            .route("/_license", routing::get(elasticsearch::handle_get_license))
1255            .route("/_bulk", routing::post(elasticsearch::handle_bulk_api))
1256            .route(
1257                "/{index}/_bulk",
1258                routing::post(elasticsearch::handle_bulk_api_with_index),
1259            )
1260            // Return fake response for Elasticsearch ilm request.
1261            .route(
1262                "/_ilm/policy/{*path}",
1263                routing::any((
1264                    HttpStatusCode::OK,
1265                    elasticsearch::elasticsearch_headers(),
1266                    axum::Json(serde_json::json!({})),
1267                )),
1268            )
1269            // Return fake response for Elasticsearch index template request.
1270            .route(
1271                "/_index_template/{*path}",
1272                routing::any((
1273                    HttpStatusCode::OK,
1274                    elasticsearch::elasticsearch_headers(),
1275                    axum::Json(serde_json::json!({})),
1276                )),
1277            )
1278            // Return fake response for Elasticsearch ingest pipeline request.
1279            // See: https://www.elastic.co/guide/en/elasticsearch/reference/8.8/put-pipeline-api.html.
1280            .route(
1281                "/_ingest/{*path}",
1282                routing::any((
1283                    HttpStatusCode::OK,
1284                    elasticsearch::elasticsearch_headers(),
1285                    axum::Json(serde_json::json!({})),
1286                )),
1287            )
1288            // Return fake response for Elasticsearch nodes discovery request.
1289            // See: https://www.elastic.co/guide/en/elasticsearch/reference/8.8/cluster.html.
1290            .route(
1291                "/_nodes/{*path}",
1292                routing::any((
1293                    HttpStatusCode::OK,
1294                    elasticsearch::elasticsearch_headers(),
1295                    axum::Json(serde_json::json!({})),
1296                )),
1297            )
1298            // Return fake response for Logstash APIs requests.
1299            // See: https://www.elastic.co/guide/en/elasticsearch/reference/8.8/logstash-apis.html
1300            .route(
1301                "/logstash/{*path}",
1302                routing::any((
1303                    HttpStatusCode::OK,
1304                    elasticsearch::elasticsearch_headers(),
1305                    axum::Json(serde_json::json!({})),
1306                )),
1307            )
1308            .route(
1309                "/_logstash/{*path}",
1310                routing::any((
1311                    HttpStatusCode::OK,
1312                    elasticsearch::elasticsearch_headers(),
1313                    axum::Json(serde_json::json!({})),
1314                )),
1315            )
1316            .layer(middleware::from_fn_with_state(
1317                memory_limiter,
1318                memory_limit::decoded_body_accounting_middleware,
1319            ))
1320            .layer(ServiceBuilder::new().layer(RequestDecompressionLayer::new()))
1321            .layer(middleware::from_fn_with_state(
1322                BatchingProtocol::Elasticsearch,
1323                set_http_write_batching,
1324            ))
1325            .with_state(log_state)
1326    }
1327
1328    #[deprecated(since = "0.11.0", note = "Use `route_pipelines()` instead.")]
1329    fn route_log_deprecated<S>(
1330        log_state: LogState,
1331        memory_limiter: ServerMemoryLimiter,
1332    ) -> Router<S> {
1333        Router::new()
1334            .route("/logs", routing::post(event::log_ingester))
1335            .route(
1336                "/pipelines/{pipeline_name}",
1337                routing::get(event::query_pipeline),
1338            )
1339            .route(
1340                "/pipelines/{pipeline_name}",
1341                routing::post(event::add_pipeline),
1342            )
1343            .route(
1344                "/pipelines/{pipeline_name}",
1345                routing::delete(event::delete_pipeline),
1346            )
1347            .route("/pipelines/dryrun", routing::post(event::pipeline_dryrun))
1348            .layer(middleware::from_fn_with_state(
1349                memory_limiter,
1350                memory_limit::decoded_body_accounting_middleware,
1351            ))
1352            .layer(
1353                ServiceBuilder::new()
1354                    .layer(RequestDecompressionLayer::new().pass_through_unaccepted(true)),
1355            )
1356            .layer(middleware::from_fn_with_state(
1357                BatchingProtocol::Logs,
1358                set_http_write_batching,
1359            ))
1360            .with_state(log_state)
1361    }
1362
1363    fn route_pipelines<S>(log_state: LogState, memory_limiter: ServerMemoryLimiter) -> Router<S> {
1364        Router::new()
1365            .route("/ingest", routing::post(event::log_ingester))
1366            .route(
1367                "/pipelines/{pipeline_name}",
1368                routing::get(event::query_pipeline),
1369            )
1370            .route(
1371                "/pipelines/{pipeline_name}/ddl",
1372                routing::get(event::query_pipeline_ddl),
1373            )
1374            .route(
1375                "/pipelines/{pipeline_name}",
1376                routing::post(event::add_pipeline),
1377            )
1378            .route(
1379                "/pipelines/{pipeline_name}",
1380                routing::delete(event::delete_pipeline),
1381            )
1382            .route("/pipelines/_dryrun", routing::post(event::pipeline_dryrun))
1383            .layer(middleware::from_fn_with_state(
1384                memory_limiter,
1385                memory_limit::decoded_body_accounting_middleware,
1386            ))
1387            .layer(
1388                ServiceBuilder::new()
1389                    .layer(RequestDecompressionLayer::new().pass_through_unaccepted(true)),
1390            )
1391            .layer(middleware::from_fn_with_state(
1392                BatchingProtocol::Logs,
1393                set_http_write_batching,
1394            ))
1395            .with_state(log_state)
1396    }
1397
1398    fn route_sql<S>(api_state: ApiState, batch_body_limit: usize) -> Router<S> {
1399        Router::new()
1400            .route(
1401                "/capabilities",
1402                routing::get(|State(state): State<ApiState>| async move {
1403                    let mut capabilities =
1404                        serde_json::json!({"metric_packed_import": 1, "metric_packed_export": 1});
1405                    if state.sql_handler.supports_metric_batch_ddl() {
1406                        capabilities["metric_batch_ddl"] = serde_json::json!(1);
1407                    }
1408                    axum::Json(capabilities)
1409                }),
1410            )
1411            .route(
1412                "/ddl/logical-tables",
1413                routing::post(handler::create_logical_tables)
1414                    .layer(DefaultBodyLimit::max(batch_body_limit)),
1415            )
1416            .route(
1417                "/sql",
1418                routing::get(handler::sql).post(handler::sql).layer(
1419                    middleware::from_fn_with_state(
1420                        BatchingProtocol::HttpSql,
1421                        set_http_write_batching,
1422                    ),
1423                ),
1424            )
1425            .route(
1426                "/sql/parse",
1427                routing::get(handler::sql_parse).post(handler::sql_parse),
1428            )
1429            .route(
1430                "/sql/format",
1431                routing::get(handler::sql_format).post(handler::sql_format),
1432            )
1433            .route(
1434                "/promql",
1435                routing::get(handler::promql).post(handler::promql),
1436            )
1437            .route(
1438                "/sql/analyze/stream",
1439                routing::post(handler::sql_analyze_stream),
1440            )
1441            .with_state(api_state)
1442    }
1443
1444    fn route_logs<S>(log_handler: LogQueryHandlerRef) -> Router<S> {
1445        Router::new()
1446            .route("/logs", routing::get(logs::logs).post(logs::logs))
1447            .with_state(log_handler)
1448    }
1449
1450    /// Route Prometheus [HTTP API].
1451    ///
1452    /// [HTTP API]: https://prometheus.io/docs/prometheus/latest/querying/api/
1453    pub fn route_prometheus<S>(prometheus_handler: PrometheusHandlerRef) -> Router<S> {
1454        Router::new()
1455            .route(
1456                "/format_query",
1457                routing::post(format_query).get(format_query),
1458            )
1459            .route("/status/buildinfo", routing::get(build_info_query))
1460            .route("/query", routing::post(instant_query).get(instant_query))
1461            .route("/query_range", routing::post(range_query).get(range_query))
1462            .route("/labels", routing::post(labels_query).get(labels_query))
1463            .route("/metadata", routing::get(metadata_query))
1464            .route("/series", routing::post(series_query).get(series_query))
1465            .route("/parse_query", routing::post(parse_query).get(parse_query))
1466            .route(
1467                "/label/{label_name}/values",
1468                routing::get(label_values_query),
1469            )
1470            .layer(ServiceBuilder::new().layer(CompressionLayer::new()))
1471            .with_state(prometheus_handler)
1472    }
1473
1474    /// Route Prometheus remote [read] and [write] API. In other places the related modules are
1475    /// called `prom_store`.
1476    ///
1477    /// [read]: https://prometheus.io/docs/prometheus/latest/querying/remote_read_api/
1478    /// [write]: https://prometheus.io/docs/concepts/remote_write_spec/
1479    fn route_prom<S>(state: PromStoreState) -> Router<S> {
1480        let write = routing::post(prom_store::remote_write);
1481        let write = if state.prom_store_with_metric_engine {
1482            write
1483        } else {
1484            write.layer(middleware::from_fn_with_state(
1485                BatchingProtocol::Prom,
1486                set_http_write_batching,
1487            ))
1488        };
1489        Router::new()
1490            .route("/read", routing::post(prom_store::remote_read))
1491            .route("/write", write)
1492            .with_state(state)
1493    }
1494
1495    fn route_influxdb<S>(
1496        influxdb_handler: InfluxdbLineProtocolHandlerRef,
1497        memory_limiter: ServerMemoryLimiter,
1498    ) -> Router<S> {
1499        Router::new()
1500            .route("/write", routing::post(influxdb_write_v1))
1501            .route("/api/v2/write", routing::post(influxdb_write_v2))
1502            .layer(middleware::from_fn_with_state(
1503                memory_limiter,
1504                memory_limit::decoded_body_accounting_middleware,
1505            ))
1506            .layer(
1507                ServiceBuilder::new()
1508                    .layer(RequestDecompressionLayer::new().pass_through_unaccepted(true)),
1509            )
1510            .route("/ping", routing::get(influxdb_ping))
1511            .route("/health", routing::get(influxdb_health))
1512            .layer(middleware::from_fn_with_state(
1513                BatchingProtocol::Influxdb,
1514                set_http_write_batching,
1515            ))
1516            .with_state(influxdb_handler)
1517    }
1518
1519    fn route_opentsdb<S>(opentsdb_handler: OpentsdbProtocolHandlerRef) -> Router<S> {
1520        Router::new()
1521            .route("/api/put", routing::post(opentsdb::put))
1522            .layer(middleware::from_fn_with_state(
1523                BatchingProtocol::Opentsdb,
1524                set_http_write_batching,
1525            ))
1526            .with_state(opentsdb_handler)
1527    }
1528
1529    fn route_otlp<S>(
1530        otlp_handler: OpenTelemetryProtocolHandlerRef,
1531        with_metric_engine: bool,
1532        memory_limiter: ServerMemoryLimiter,
1533    ) -> Router<S> {
1534        Router::new()
1535            .route("/v1/metrics", routing::post(otlp::metrics))
1536            .route("/v1/traces", routing::post(otlp::traces))
1537            .route("/v1/logs", routing::post(otlp::logs))
1538            .layer(middleware::from_fn_with_state(
1539                memory_limiter,
1540                memory_limit::decoded_body_accounting_middleware,
1541            ))
1542            .layer(
1543                ServiceBuilder::new()
1544                    .layer(RequestDecompressionLayer::new().pass_through_unaccepted(true)),
1545            )
1546            .layer(middleware::from_fn_with_state(
1547                BatchingProtocol::Otlp,
1548                set_http_write_batching,
1549            ))
1550            .with_state(OtlpState {
1551                with_metric_engine,
1552                handler: otlp_handler,
1553            })
1554    }
1555
1556    fn route_config<S>(state: GreptimeOptionsConfigState) -> Router<S> {
1557        Router::new()
1558            .route("/config", routing::get(handler::config))
1559            .with_state(state)
1560    }
1561
1562    fn route_jaeger<S>(handler: JaegerQueryHandlerRef) -> Router<S> {
1563        Router::new()
1564            .route("/api/services", routing::get(jaeger::handle_get_services))
1565            .route(
1566                "/api/services/{service_name}/operations",
1567                routing::get(jaeger::handle_get_operations_by_service),
1568            )
1569            .route(
1570                "/api/operations",
1571                routing::get(jaeger::handle_get_operations),
1572            )
1573            .route("/api/traces", routing::get(jaeger::handle_find_traces))
1574            .route(
1575                "/api/traces/{trace_id}",
1576                routing::get(jaeger::handle_get_trace),
1577            )
1578            .with_state(handler)
1579    }
1580
1581    #[cfg(feature = "dashboard")]
1582    fn route_dashboard<S>(
1583        handler: DashboardHandlerRef,
1584        memory_limiter: ServerMemoryLimiter,
1585    ) -> Router<S> {
1586        use crate::http::dashboard::{add_dashboard, delete_dashboard, list_dashboards};
1587
1588        Router::new()
1589            .route("/", routing::get(list_dashboards))
1590            .route("/{dashboard_name}", routing::post(add_dashboard))
1591            .route("/{dashboard_name}", routing::delete(delete_dashboard))
1592            .layer(middleware::from_fn_with_state(
1593                memory_limiter,
1594                memory_limit::decoded_body_accounting_middleware,
1595            ))
1596            .layer(
1597                ServiceBuilder::new()
1598                    .layer(RequestDecompressionLayer::new().pass_through_unaccepted(true)),
1599            )
1600            .with_state(DashboardState { handler })
1601    }
1602
1603    #[cfg(not(feature = "dashboard"))]
1604    fn route_dashboard<S>(
1605        handler: DashboardHandlerRef,
1606        _memory_limiter: ServerMemoryLimiter,
1607    ) -> Router<S> {
1608        Router::new().with_state(DashboardState { handler })
1609    }
1610}
1611
1612pub const HTTP_SERVER: &str = "HTTP_SERVER";
1613pub const HTTP_API_SERVER: &str = "HTTP_API_SERVER";
1614
1615/// An empty list allows any origin.
1616pub(crate) fn cors_allow_origin(origins: &[String]) -> Result<AllowOrigin> {
1617    if origins.is_empty() {
1618        return Ok(AllowOrigin::any());
1619    }
1620    let origins = origins
1621        .iter()
1622        .map(|origin| HeaderValue::from_str(origin).context(InvalidHeaderValueSnafu))
1623        .collect::<Result<Vec<_>>>()?;
1624    Ok(AllowOrigin::list(origins))
1625}
1626
1627#[async_trait]
1628impl Server for HttpServer {
1629    async fn shutdown(&self) -> Result<()> {
1630        let mut shutdown_tx = self.shutdown_tx.lock().await;
1631        if let Some(tx) = shutdown_tx.take()
1632            && tx.send(()).is_err()
1633        {
1634            info!("Receiver dropped, the HTTP server has already exited");
1635        }
1636        info!("Shutdown {}", self.kind());
1637
1638        Ok(())
1639    }
1640
1641    async fn start(&mut self, listening: SocketAddr) -> Result<()> {
1642        let (tx, rx) = oneshot::channel();
1643        let serve = {
1644            let mut shutdown_tx = self.shutdown_tx.lock().await;
1645            ensure!(
1646                shutdown_tx.is_none(),
1647                AlreadyStartedSnafu {
1648                    server: self.kind()
1649                }
1650            );
1651
1652            let app = self.build(self.make_app())?;
1653            let listener = tokio::net::TcpListener::bind(listening)
1654                .await
1655                .context(AddressBindSnafu { addr: listening })?
1656                .tap_io(|tcp_stream| {
1657                    if let Err(e) = tcp_stream.set_nodelay(true) {
1658                        error!(e; "Failed to set TCP_NODELAY on incoming connection");
1659                    }
1660                });
1661            let serve = axum::serve(
1662                listener,
1663                app.into_make_service_with_connect_info::<SocketAddr>(),
1664            );
1665
1666            // FIXME(yingwen): Support keepalive.
1667            // See:
1668            // - https://github.com/tokio-rs/axum/discussions/2939
1669            // - https://stackoverflow.com/questions/73069718/how-do-i-keep-alive-tokiotcpstream-in-rust
1670            // let server = axum::Server::try_bind(&listening)
1671            //     .with_context(|_| AddressBindSnafu { addr: listening })?
1672            //     .tcp_nodelay(true)
1673            //     // Enable TCP keepalive to close the dangling established connections.
1674            //     // It's configured to let the keepalive probes first send after the connection sits
1675            //     // idle for 59 minutes, and then send every 10 seconds for 6 times.
1676            //     // So the connection will be closed after roughly 1 hour.
1677            //     .tcp_keepalive(Some(Duration::from_secs(59 * 60)))
1678            //     .tcp_keepalive_interval(Some(Duration::from_secs(10)))
1679            //     .tcp_keepalive_retries(Some(6))
1680            //     .serve(app.into_make_service());
1681
1682            *shutdown_tx = Some(tx);
1683
1684            serve
1685        };
1686        let listening = serve.local_addr().context(InternalIoSnafu)?;
1687        info!("{} server is bound to {}", self.kind(), listening);
1688
1689        common_runtime::spawn_global(async move {
1690            if let Err(e) = serve
1691                .with_graceful_shutdown(rx.map(drop))
1692                .await
1693                .context(InternalIoSnafu)
1694            {
1695                error!(e; "Failed to shutdown http server");
1696            }
1697        });
1698
1699        self.bind_addr = Some(listening);
1700        Ok(())
1701    }
1702
1703    fn name(&self) -> &str {
1704        match self.kind {
1705            HttpServerKind::Api => HTTP_API_SERVER,
1706            HttpServerKind::Full => HTTP_SERVER,
1707        }
1708    }
1709
1710    fn bind_addr(&self) -> Option<SocketAddr> {
1711        self.bind_addr
1712    }
1713
1714    fn as_any(&self) -> &dyn std::any::Any {
1715        self
1716    }
1717}
1718
1719#[cfg(test)]
1720mod test {
1721    use std::future::pending;
1722    use std::io::Cursor;
1723    use std::sync::Arc;
1724
1725    use arrow_ipc::reader::StreamReader;
1726    use arrow_schema::DataType;
1727    use axum::http::StatusCode;
1728    use axum::routing::{get, post};
1729    use common_query::{Output, OutputData};
1730    use common_recordbatch::RecordBatches;
1731    use datafusion_expr::LogicalPlan;
1732    use datatypes::prelude::*;
1733    use datatypes::schema::{ColumnSchema, Schema};
1734    use datatypes::vectors::{StringVector, UInt32Vector};
1735    use header::constants::GREPTIME_DB_HEADER_TIMEOUT;
1736    use query::parser::PromQuery;
1737    use query::query_engine::DescribeResult;
1738    use session::context::QueryContextRef;
1739    use sql::statements::statement::Statement;
1740    use tokio::sync::mpsc;
1741    use tokio::time::Instant;
1742
1743    use crate::http::test_helpers::TestClient;
1744    use crate::http::*;
1745    use crate::prom_remote_write::validation::validate_label_name;
1746    use crate::query_handler::sql::SqlQueryHandler;
1747
1748    struct DummyInstance {
1749        _tx: mpsc::Sender<(String, Vec<u8>)>,
1750    }
1751
1752    #[async_trait]
1753    impl SqlQueryHandler for DummyInstance {
1754        async fn do_query(&self, _: &str, _: QueryContextRef) -> Vec<Result<Output>> {
1755            unimplemented!()
1756        }
1757
1758        async fn do_analyze_stream_query(&self, _: &str, _: QueryContextRef) -> Result<Output> {
1759            let stream = common_recordbatch::RecordBatches::empty().as_stream();
1760            Ok(Output::new(OutputData::Stream(stream), Default::default()))
1761        }
1762
1763        async fn do_promql_query(&self, _: &PromQuery, _: QueryContextRef) -> Vec<Result<Output>> {
1764            unimplemented!()
1765        }
1766
1767        async fn do_exec_plan(
1768            &self,
1769            _plan: LogicalPlan,
1770            _stmt: Option<Statement>,
1771            _query_ctx: QueryContextRef,
1772        ) -> Result<Output> {
1773            unimplemented!()
1774        }
1775
1776        async fn do_describe(
1777            &self,
1778            _stmt: sql::statements::statement::Statement,
1779            _query_ctx: QueryContextRef,
1780        ) -> Result<Option<DescribeResult>> {
1781            unimplemented!()
1782        }
1783
1784        async fn is_valid_schema(&self, _catalog: &str, _schema: &str) -> Result<bool> {
1785            Ok(true)
1786        }
1787    }
1788
1789    async fn forever() {
1790        pending().await
1791    }
1792
1793    fn make_test_app(tx: mpsc::Sender<(String, Vec<u8>)>) -> Router {
1794        make_test_app_custom(tx, HttpOptions::default())
1795    }
1796
1797    fn make_test_app_custom(tx: mpsc::Sender<(String, Vec<u8>)>, options: HttpOptions) -> Router {
1798        let instance = Arc::new(DummyInstance { _tx: tx });
1799        let server = HttpServerBuilder::new(options)
1800            .with_sql_handler(instance.clone())
1801            .build();
1802        let app = server
1803            .make_app()
1804            .route("/test/timeout", get(forever))
1805            .route("/v1/prometheus/write", post(forever));
1806        server.build(app).unwrap()
1807    }
1808
1809    fn make_split_builder() -> HttpServerBuilder {
1810        let (tx, _rx) = mpsc::channel(100);
1811        let instance = Arc::new(DummyInstance { _tx: tx });
1812        // Enable the dedicated API server so the split behavior can be exercised.
1813        let options = HttpOptions {
1814            enable_api_server: true,
1815            ..HttpOptions::default()
1816        };
1817        HttpServerBuilder::new(options)
1818            .with_sql_handler(instance)
1819            .with_metrics_handler(MetricsHandler)
1820            .with_greptime_config_options("dummy = \"value\"".to_string())
1821    }
1822
1823    #[tokio::test]
1824    pub async fn test_http_api_options_defaults() {
1825        let opts = HttpOptions::default();
1826        // The API server is disabled by default.
1827        assert!(!opts.enable_api_server);
1828        assert_eq!(opts.api_server_addr, "127.0.0.1:4006");
1829    }
1830
1831    #[tokio::test]
1832    pub async fn test_build_serves_all_routes() {
1833        // The single (internal/full) server built via `build()` keeps serving both
1834        // the public v1 interfaces and the internal ones. This is the historical
1835        // single-server behavior and must not change.
1836        let server = make_split_builder().build();
1837        assert_eq!(server.name(), HTTP_SERVER);
1838
1839        let app = server.build(server.make_app()).unwrap();
1840        let client = TestClient::new(app).await;
1841
1842        for path in ["/v1/health", "/health", "/status", "/metrics", "/config"] {
1843            let res = client.get(path).send().await;
1844            assert_eq!(
1845                res.status(),
1846                StatusCode::OK,
1847                "internal/full server should serve {path}"
1848            );
1849        }
1850    }
1851
1852    #[tokio::test]
1853    pub async fn test_build_servers_separates_api_and_internal_routes() {
1854        // `build_servers` produces the internal/full server (everything) plus a
1855        // dedicated API server that only serves the v1 interfaces (and dashboard).
1856        let (internal, api) = make_split_builder().build_servers();
1857        let api = api.expect("API server is explicitly enabled in make_split_builder");
1858        assert_eq!(internal.name(), HTTP_SERVER);
1859        assert_eq!(api.name(), HTTP_API_SERVER);
1860
1861        let internal_app = internal.build(internal.make_app()).unwrap();
1862        let api_app = api.build(api.make_app()).unwrap();
1863
1864        let internal_client = TestClient::new(internal_app).await;
1865        let api_client = TestClient::new(api_app).await;
1866
1867        // Both servers serve the v1 interfaces (e.g. the v1 health check).
1868        assert_eq!(
1869            internal_client.get("/v1/health").send().await.status(),
1870            StatusCode::OK
1871        );
1872        assert_eq!(
1873            api_client.get("/v1/health").send().await.status(),
1874            StatusCode::OK
1875        );
1876
1877        // The internal-only interfaces are served by the internal/full server...
1878        for path in ["/health", "/status", "/metrics", "/config"] {
1879            assert_eq!(
1880                internal_client.get(path).send().await.status(),
1881                StatusCode::OK,
1882                "internal/full server should serve {path}"
1883            );
1884            // ...and NOT by the API-only server.
1885            assert_eq!(
1886                api_client.get(path).send().await.status(),
1887                StatusCode::NOT_FOUND,
1888                "API server should NOT serve {path}"
1889            );
1890        }
1891    }
1892
1893    #[test]
1894    fn test_build_servers_api_inherits_http_options() {
1895        // The API server must inherit every `[http]` option except the bound addr.
1896        let http_opts = HttpOptions {
1897            timeout: Duration::from_secs(42),
1898            body_limit: ReadableSize::mb(128),
1899            cors_allowed_origins: vec!["https://example.com".to_string()],
1900            enable_api_server: true,
1901            ..HttpOptions::default()
1902        };
1903        let (internal, api) = HttpServerBuilder::new(http_opts.clone()).build_servers();
1904        let api = api.expect("API server is explicitly enabled");
1905
1906        // Only the bound address differs.
1907        assert_eq!(internal.options.addr, http_opts.addr);
1908        assert_eq!(api.options.addr, "127.0.0.1:4006");
1909        // Everything else is inherited from `[http]`.
1910        assert_eq!(api.options.timeout, http_opts.timeout);
1911        assert_eq!(api.options.body_limit, http_opts.body_limit);
1912        assert_eq!(
1913            api.options.cors_allowed_origins,
1914            http_opts.cors_allowed_origins
1915        );
1916    }
1917
1918    #[test]
1919    fn test_is_api_listener_path() {
1920        // `/v1` namespace (exact slash boundary).
1921        assert!(is_api_listener_path("/v1"));
1922        assert!(is_api_listener_path("/v1/"));
1923        assert!(is_api_listener_path("/v1/sql"));
1924        assert!(!is_api_listener_path("/v10/sql"));
1925        assert!(!is_api_listener_path("/v1-internal"));
1926        // `/dashboard` namespace.
1927        assert!(is_api_listener_path("/dashboard"));
1928        assert!(is_api_listener_path("/dashboard/app.js"));
1929        assert!(!is_api_listener_path("/dashboard-admin"));
1930        // Operational paths are never API-visible.
1931        assert!(!is_api_listener_path("/metrics"));
1932        assert!(!is_api_listener_path("/status/plugin"));
1933        assert!(!is_api_listener_path("/health"));
1934    }
1935
1936    #[tokio::test]
1937    async fn packed_capability_requires_auth_on_full_and_api_listeners() {
1938        let (tx, _rx) = mpsc::channel(1);
1939        let provider =
1940            auth::static_user_provider_from_option("static_user_provider:cmd:user=password")
1941                .unwrap();
1942        let (full, api) = HttpServerBuilder::new(HttpOptions {
1943            enable_api_server: true,
1944            ..Default::default()
1945        })
1946        .with_sql_handler(Arc::new(DummyInstance { _tx: tx }))
1947        .with_user_provider(Arc::new(provider))
1948        .build_servers();
1949        for server in [full, api.unwrap()] {
1950            let client = TestClient::new(server.build(server.make_app()).unwrap()).await;
1951            assert_eq!(
1952                client.get("/v1/capabilities").send().await.status(),
1953                StatusCode::UNAUTHORIZED
1954            );
1955            assert_eq!(
1956                client
1957                    .post("/v1/ddl/logical-tables")
1958                    .form(&[("sql", "SELECT 1")])
1959                    .send()
1960                    .await
1961                    .status(),
1962                StatusCode::UNAUTHORIZED
1963            );
1964            let response = client
1965                .get("/v1/capabilities")
1966                .header("Authorization", "Basic dXNlcjpwYXNzd29yZA==")
1967                .send()
1968                .await;
1969            assert_eq!(response.status(), StatusCode::OK);
1970            assert_eq!(
1971                response.json::<serde_json::Value>().await,
1972                serde_json::json!({"metric_packed_import": 1, "metric_packed_export": 1})
1973            );
1974        }
1975    }
1976
1977    #[tokio::test]
1978    async fn logical_ddl_form_limits_and_unsupported_handler() {
1979        use axum::body::{Body, to_bytes};
1980        use tower::ServiceExt;
1981
1982        async fn post_form(app: Router, body: String) -> (StatusCode, serde_json::Value) {
1983            let response = app
1984                .oneshot(
1985                    Request::builder()
1986                        .method(Method::POST)
1987                        .uri("/v1/ddl/logical-tables")
1988                        .header("Content-Type", "application/x-www-form-urlencoded")
1989                        .body(Body::from(body))
1990                        .unwrap(),
1991                )
1992                .await
1993                .unwrap();
1994            let status = response.status();
1995            let body = to_bytes(response.into_body(), 4096).await.unwrap();
1996            (status, serde_json::from_slice(&body).unwrap())
1997        }
1998
1999        for limit in [0, 64] {
2000            let (tx, _rx) = mpsc::channel(1);
2001            let server = HttpServerBuilder::new(HttpOptions {
2002                body_limit: ReadableSize(limit),
2003                ..Default::default()
2004            })
2005            .with_sql_handler(Arc::new(DummyInstance { _tx: tx }))
2006            .build();
2007            let app = server.build(server.make_app()).unwrap();
2008            let client = TestClient::new(app.clone()).await;
2009            assert_eq!(
2010                client.get("/v1/ddl/logical-tables").send().await.status(),
2011                StatusCode::METHOD_NOT_ALLOWED
2012            );
2013            let small = client
2014                .post("/v1/ddl/logical-tables")
2015                .form(&[("sql", "CREATE TABLE t")])
2016                .send()
2017                .await;
2018            assert_eq!(
2019                small.json::<serde_json::Value>().await["code"],
2020                common_error::status_code::StatusCode::Unsupported as u32
2021            );
2022            let (_, body) = post_form(
2023                app.clone(),
2024                if limit == 0 {
2025                    format!("sql={}", "%20".repeat(1024 * 1024))
2026                } else {
2027                    "sql=".to_string() + &" ".repeat(65)
2028                },
2029            )
2030            .await;
2031            let code = body["code"].clone();
2032            assert_eq!(
2033                code,
2034                if limit == 0 {
2035                    common_error::status_code::StatusCode::Unsupported as u32
2036                } else {
2037                    common_error::status_code::StatusCode::InvalidArguments as u32
2038                }
2039            );
2040            for body in [
2041                format!("sql={}", "x".repeat(1024 * 1024 + 1)),
2042                format!("sql={}", "%C3%A9".repeat(512 * 1024 + 1)),
2043                format!("sql=CREATE+TABLE+t&padding={}", "x".repeat(4 * 1024 * 1024)),
2044            ] {
2045                let (status, body) = post_form(app.clone(), body).await;
2046                assert_eq!(status, StatusCode::BAD_REQUEST);
2047                assert_eq!(
2048                    body["code"],
2049                    common_error::status_code::StatusCode::InvalidArguments as u32
2050                );
2051            }
2052        }
2053    }
2054
2055    #[tokio::test]
2056    pub async fn test_extra_router_respects_api_surface() {
2057        // Downstream routes added through `with_extra_router()` (e.g. enterprise
2058        // plugins or the `RouterConfigurator`) are present on the full listener
2059        // and on the API listener's router, but the API listener's outer guard
2060        // hides every non-`/v1` path automatically. This is what keeps the
2061        // feature correct for greptimedb-enterprise with no downstream changes.
2062        let (tx, _rx) = mpsc::channel(100);
2063        let instance = Arc::new(DummyInstance { _tx: tx });
2064        let options = HttpOptions {
2065            enable_api_server: true,
2066            ..HttpOptions::default()
2067        };
2068        let builder = HttpServerBuilder::new(options)
2069            .with_sql_handler(instance)
2070            .with_extra_router(
2071                Router::new()
2072                    .route("/status/plugin", routing::get(|| async { "ok" }))
2073                    .route("/v1/plugin", routing::get(|| async { "ok" })),
2074            );
2075        let (full, api) = builder.build_servers();
2076        let api = api.expect("API server is explicitly enabled");
2077
2078        let full_client = TestClient::new(full.build(full.make_app()).unwrap()).await;
2079        let api_client = TestClient::new(api.build(api.make_app()).unwrap()).await;
2080
2081        // A non-`/v1` route is reachable on the full listener...
2082        assert_eq!(
2083            full_client.get("/status/plugin").send().await.status(),
2084            StatusCode::OK
2085        );
2086        // ...and hidden (404) on the API listener, without the caller classifying it.
2087        assert_eq!(
2088            api_client.get("/status/plugin").send().await.status(),
2089            StatusCode::NOT_FOUND
2090        );
2091
2092        // A `/v1` route is reachable on both.
2093        assert_eq!(
2094            full_client.get("/v1/plugin").send().await.status(),
2095            StatusCode::OK
2096        );
2097        assert_eq!(
2098            api_client.get("/v1/plugin").send().await.status(),
2099            StatusCode::OK
2100        );
2101
2102        // Slash-boundary correctness on the API listener.
2103        assert_eq!(
2104            api_client.get("/v10/plugin").send().await.status(),
2105            StatusCode::NOT_FOUND
2106        );
2107    }
2108
2109    #[test]
2110    fn test_build_servers_api_disabled_by_default() {
2111        // The API server is disabled by default, so `build_servers` returns no
2112        // second server unless `enable_api_server` is set.
2113        let (_internal, api) = HttpServerBuilder::new(HttpOptions::default()).build_servers();
2114        assert!(api.is_none());
2115    }
2116
2117    #[tokio::test]
2118    pub async fn test_cors() {
2119        // cors is on by default
2120        let (tx, _rx) = mpsc::channel(100);
2121        let app = make_test_app(tx);
2122        let client = TestClient::new(app).await;
2123
2124        let res = client.get("/health").send().await;
2125
2126        assert_eq!(res.status(), StatusCode::OK);
2127        assert_eq!(
2128            res.headers()
2129                .get(http::header::ACCESS_CONTROL_ALLOW_ORIGIN)
2130                .expect("expect cors header origin"),
2131            "*"
2132        );
2133
2134        let res = client.get("/v1/health").send().await;
2135
2136        assert_eq!(res.status(), StatusCode::OK);
2137        assert_eq!(
2138            res.headers()
2139                .get(http::header::ACCESS_CONTROL_ALLOW_ORIGIN)
2140                .expect("expect cors header origin"),
2141            "*"
2142        );
2143
2144        let res = client
2145            .options("/health")
2146            .header("Access-Control-Request-Headers", "x-greptime-auth")
2147            .header("Access-Control-Request-Method", "DELETE")
2148            .header("Origin", "https://example.com")
2149            .send()
2150            .await;
2151        assert_eq!(res.status(), StatusCode::OK);
2152        assert_eq!(
2153            res.headers()
2154                .get(http::header::ACCESS_CONTROL_ALLOW_ORIGIN)
2155                .expect("expect cors header origin"),
2156            "*"
2157        );
2158        assert_eq!(
2159            res.headers()
2160                .get(http::header::ACCESS_CONTROL_ALLOW_HEADERS)
2161                .expect("expect cors header headers"),
2162            "*"
2163        );
2164        assert_eq!(
2165            res.headers()
2166                .get(http::header::ACCESS_CONTROL_ALLOW_METHODS)
2167                .expect("expect cors header methods"),
2168            "GET,POST,PUT,DELETE,HEAD"
2169        );
2170    }
2171
2172    #[tokio::test]
2173    pub async fn test_cors_custom_origins() {
2174        // cors is on by default
2175        let (tx, _rx) = mpsc::channel(100);
2176        let origin = "https://example.com";
2177
2178        let options = HttpOptions {
2179            cors_allowed_origins: vec![origin.to_string()],
2180            ..Default::default()
2181        };
2182
2183        let app = make_test_app_custom(tx, options);
2184        let client = TestClient::new(app).await;
2185
2186        let res = client.get("/health").header("Origin", origin).send().await;
2187
2188        assert_eq!(res.status(), StatusCode::OK);
2189        assert_eq!(
2190            res.headers()
2191                .get(http::header::ACCESS_CONTROL_ALLOW_ORIGIN)
2192                .expect("expect cors header origin"),
2193            origin
2194        );
2195
2196        let res = client
2197            .get("/health")
2198            .header("Origin", "https://notallowed.com")
2199            .send()
2200            .await;
2201
2202        assert_eq!(res.status(), StatusCode::OK);
2203        assert!(
2204            !res.headers()
2205                .contains_key(http::header::ACCESS_CONTROL_ALLOW_ORIGIN)
2206        );
2207    }
2208
2209    #[tokio::test]
2210    pub async fn test_cors_disabled() {
2211        // cors is on by default
2212        let (tx, _rx) = mpsc::channel(100);
2213
2214        let options = HttpOptions {
2215            enable_cors: false,
2216            ..Default::default()
2217        };
2218
2219        let app = make_test_app_custom(tx, options);
2220        let client = TestClient::new(app).await;
2221
2222        let res = client.get("/health").send().await;
2223
2224        assert_eq!(res.status(), StatusCode::OK);
2225        assert!(
2226            !res.headers()
2227                .contains_key(http::header::ACCESS_CONTROL_ALLOW_ORIGIN)
2228        );
2229    }
2230
2231    #[test]
2232    fn test_http_options_default() {
2233        let default = HttpOptions::default();
2234        assert_eq!("127.0.0.1:4000".to_string(), default.addr);
2235        assert_eq!(Duration::from_secs(0), default.timeout)
2236    }
2237
2238    #[tokio::test]
2239    async fn test_http_options_legacy_analyze_stream_config_is_ignored() {
2240        let options: HttpOptions = serde_json::from_value(serde_json::json!({
2241            "addr": "127.0.0.1:4000",
2242            "timeout": "0s",
2243            "body_limit": "64MiB",
2244            "cors_allowed_origins": [],
2245            "enable_cors": true,
2246            "experimental_enable_explain_analyze_stream": false,
2247            "enable_api_server": false,
2248            "api_server_addr": "127.0.0.1:4006"
2249        }))
2250        .unwrap();
2251        let serialized = serde_json::to_string(&options).unwrap();
2252        assert!(!serialized.contains("experimental_enable_explain_analyze_stream"));
2253
2254        let (tx, _rx) = mpsc::channel(100);
2255        let app = make_test_app_custom(tx, options);
2256        let client = TestClient::new(app).await;
2257        let response = client
2258            .post("/v1/sql/analyze/stream")
2259            .form(&handler::SqlQuery {
2260                sql: Some("EXPLAIN ANALYZE VERBOSE SELECT 1".to_string()),
2261                ..Default::default()
2262            })
2263            .send()
2264            .await;
2265        assert_ne!(response.status(), StatusCode::NOT_FOUND);
2266    }
2267
2268    #[tokio::test]
2269    async fn test_http_server_request_timeout() {
2270        common_telemetry::init_default_ut_logging();
2271
2272        let (tx, _rx) = mpsc::channel(100);
2273        let options = HttpOptions {
2274            timeout: Duration::from_millis(10),
2275            ..Default::default()
2276        };
2277        let app = make_test_app_custom(tx, options);
2278        let client = TestClient::new(app).await;
2279        let res = client.get("/test/timeout").send().await;
2280        assert_eq!(res.status(), StatusCode::REQUEST_TIMEOUT);
2281
2282        let res = client.post("/v1/prometheus/write").send().await;
2283        assert_eq!(res.status(), StatusCode::GATEWAY_TIMEOUT);
2284
2285        let now = Instant::now();
2286        let res = client
2287            .get("/test/timeout")
2288            .header(GREPTIME_DB_HEADER_TIMEOUT, "20ms")
2289            .send()
2290            .await;
2291        assert_eq!(res.status(), StatusCode::REQUEST_TIMEOUT);
2292        let elapsed = now.elapsed();
2293        assert!(elapsed > Duration::from_millis(15));
2294
2295        tokio::time::timeout(
2296            Duration::from_millis(15),
2297            client
2298                .get("/test/timeout")
2299                .header(GREPTIME_DB_HEADER_TIMEOUT, "0s")
2300                .send(),
2301        )
2302        .await
2303        .unwrap_err();
2304
2305        tokio::time::timeout(
2306            Duration::from_millis(15),
2307            client
2308                .get("/test/timeout")
2309                .header(
2310                    GREPTIME_DB_HEADER_TIMEOUT,
2311                    humantime::format_duration(Duration::default()).to_string(),
2312                )
2313                .send(),
2314        )
2315        .await
2316        .unwrap_err();
2317    }
2318
2319    #[tokio::test]
2320    async fn test_schema_for_empty_response() {
2321        let column_schemas = vec![
2322            ColumnSchema::new("numbers", ConcreteDataType::uint32_datatype(), false),
2323            ColumnSchema::new("strings", ConcreteDataType::string_datatype(), true),
2324        ];
2325        let schema = Arc::new(Schema::new(column_schemas));
2326
2327        let recordbatches = RecordBatches::try_new(schema.clone(), vec![]).unwrap();
2328        let outputs = vec![Ok(Output::new_with_record_batches(recordbatches))];
2329
2330        let json_resp = GreptimedbV1Response::from_output(outputs).await;
2331        if let HttpResponse::GreptimedbV1(json_resp) = json_resp {
2332            let json_output = &json_resp.output[0];
2333            if let GreptimeQueryOutput::Records(r) = json_output {
2334                assert_eq!(r.num_rows(), 0);
2335                assert_eq!(r.num_cols(), 2);
2336                assert_eq!(r.schema.column_schemas[0].name, "numbers");
2337                assert_eq!(r.schema.column_schemas[0].data_type, "UInt32");
2338            } else {
2339                panic!("invalid output type");
2340            }
2341        } else {
2342            panic!("invalid format")
2343        }
2344    }
2345
2346    #[tokio::test]
2347    async fn test_recordbatches_conversion() {
2348        let column_schemas = vec![
2349            ColumnSchema::new("numbers", ConcreteDataType::uint32_datatype(), false),
2350            ColumnSchema::new("strings", ConcreteDataType::string_datatype(), true),
2351        ];
2352        let schema = Arc::new(Schema::new(column_schemas));
2353        let columns: Vec<VectorRef> = vec![
2354            Arc::new(UInt32Vector::from_slice(vec![1, 2, 3, 4])),
2355            Arc::new(StringVector::from(vec![
2356                None,
2357                Some("hello"),
2358                Some("greptime"),
2359                None,
2360            ])),
2361        ];
2362        let recordbatch = RecordBatch::new(schema.clone(), columns).unwrap();
2363
2364        for format in [
2365            ResponseFormat::GreptimedbV1,
2366            ResponseFormat::InfluxdbV1,
2367            ResponseFormat::Csv(true, true),
2368            ResponseFormat::Table,
2369            ResponseFormat::Arrow,
2370            ResponseFormat::Json,
2371            ResponseFormat::Null,
2372        ] {
2373            let recordbatches =
2374                RecordBatches::try_new(schema.clone(), vec![recordbatch.clone()]).unwrap();
2375            let outputs = vec![Ok(Output::new_with_record_batches(recordbatches))];
2376            let json_resp = match format {
2377                ResponseFormat::Arrow => ArrowResponse::from_output(outputs, None).await,
2378                ResponseFormat::Csv(with_names, with_types) => {
2379                    CsvResponse::from_output(outputs, with_names, with_types).await
2380                }
2381                ResponseFormat::Table => TableResponse::from_output(outputs).await,
2382                ResponseFormat::GreptimedbV1 => GreptimedbV1Response::from_output(outputs).await,
2383                ResponseFormat::InfluxdbV1 => InfluxdbV1Response::from_output(outputs, None).await,
2384                ResponseFormat::Json => JsonResponse::from_output(outputs).await,
2385                ResponseFormat::Null => NullResponse::from_output(outputs).await,
2386            };
2387
2388            match json_resp {
2389                HttpResponse::GreptimedbV1(resp) => {
2390                    let json_output = &resp.output[0];
2391                    if let GreptimeQueryOutput::Records(r) = json_output {
2392                        assert_eq!(r.num_rows(), 4);
2393                        assert_eq!(r.num_cols(), 2);
2394                        assert_eq!(r.schema.column_schemas[0].name, "numbers");
2395                        assert_eq!(r.schema.column_schemas[0].data_type, "UInt32");
2396                        assert_eq!(r.rows[0][0], serde_json::Value::from(1));
2397                        assert_eq!(r.rows[0][1], serde_json::Value::Null);
2398                    } else {
2399                        panic!("invalid output type");
2400                    }
2401                }
2402                HttpResponse::InfluxdbV1(resp) => {
2403                    let json_output = &resp.results()[0];
2404                    assert_eq!(json_output.num_rows(), 4);
2405                    assert_eq!(json_output.num_cols(), 2);
2406                    assert_eq!(json_output.series[0].columns.clone()[0], "numbers");
2407                    assert_eq!(
2408                        json_output.series[0].values[0][0],
2409                        serde_json::Value::from(1)
2410                    );
2411                    assert_eq!(json_output.series[0].values[0][1], serde_json::Value::Null);
2412                }
2413                HttpResponse::Csv(resp) => {
2414                    let output = &resp.output()[0];
2415                    if let GreptimeQueryOutput::Records(r) = output {
2416                        assert_eq!(r.num_rows(), 4);
2417                        assert_eq!(r.num_cols(), 2);
2418                        assert_eq!(r.schema.column_schemas[0].name, "numbers");
2419                        assert_eq!(r.schema.column_schemas[0].data_type, "UInt32");
2420                        assert_eq!(r.rows[0][0], serde_json::Value::from(1));
2421                        assert_eq!(r.rows[0][1], serde_json::Value::Null);
2422                    } else {
2423                        panic!("invalid output type");
2424                    }
2425                }
2426
2427                HttpResponse::Table(resp) => {
2428                    let output = &resp.output()[0];
2429                    if let GreptimeQueryOutput::Records(r) = output {
2430                        assert_eq!(r.num_rows(), 4);
2431                        assert_eq!(r.num_cols(), 2);
2432                        assert_eq!(r.schema.column_schemas[0].name, "numbers");
2433                        assert_eq!(r.schema.column_schemas[0].data_type, "UInt32");
2434                        assert_eq!(r.rows[0][0], serde_json::Value::from(1));
2435                        assert_eq!(r.rows[0][1], serde_json::Value::Null);
2436                    } else {
2437                        panic!("invalid output type");
2438                    }
2439                }
2440
2441                HttpResponse::Arrow(resp) => {
2442                    let output = resp.data;
2443                    let mut reader = StreamReader::try_new(Cursor::new(output), None)
2444                        .expect("Arrow reader error");
2445                    let schema = reader.schema();
2446                    assert_eq!(schema.fields[0].name(), "numbers");
2447                    assert_eq!(schema.fields[0].data_type(), &DataType::UInt32);
2448                    assert_eq!(schema.fields[1].name(), "strings");
2449                    assert_eq!(schema.fields[1].data_type(), &DataType::Utf8);
2450
2451                    let rb = reader.next().unwrap().expect("read record batch failed");
2452                    assert_eq!(rb.num_columns(), 2);
2453                    assert_eq!(rb.num_rows(), 4);
2454                }
2455
2456                HttpResponse::Json(resp) => {
2457                    let output = &resp.output()[0];
2458                    if let GreptimeQueryOutput::Records(r) = output {
2459                        assert_eq!(r.num_rows(), 4);
2460                        assert_eq!(r.num_cols(), 2);
2461                        assert_eq!(r.schema.column_schemas[0].name, "numbers");
2462                        assert_eq!(r.schema.column_schemas[0].data_type, "UInt32");
2463                        assert_eq!(r.rows[0][0], serde_json::Value::from(1));
2464                        assert_eq!(r.rows[0][1], serde_json::Value::Null);
2465                    } else {
2466                        panic!("invalid output type");
2467                    }
2468                }
2469
2470                HttpResponse::Null(resp) => {
2471                    assert_eq!(resp.rows(), 4);
2472                }
2473
2474                HttpResponse::Error(err) => unreachable!("{err:?}"),
2475            }
2476        }
2477    }
2478
2479    #[test]
2480    fn test_response_format_misc() {
2481        assert_eq!(ResponseFormat::default(), ResponseFormat::GreptimedbV1);
2482        assert_eq!(ResponseFormat::parse("arrow"), Some(ResponseFormat::Arrow));
2483        assert_eq!(
2484            ResponseFormat::parse("csv"),
2485            Some(ResponseFormat::Csv(false, false))
2486        );
2487        assert_eq!(
2488            ResponseFormat::parse("csvwithnames"),
2489            Some(ResponseFormat::Csv(true, false))
2490        );
2491        assert_eq!(
2492            ResponseFormat::parse("csvwithnamesandtypes"),
2493            Some(ResponseFormat::Csv(true, true))
2494        );
2495        assert_eq!(ResponseFormat::parse("table"), Some(ResponseFormat::Table));
2496        assert_eq!(
2497            ResponseFormat::parse("greptimedb_v1"),
2498            Some(ResponseFormat::GreptimedbV1)
2499        );
2500        assert_eq!(
2501            ResponseFormat::parse("influxdb_v1"),
2502            Some(ResponseFormat::InfluxdbV1)
2503        );
2504        assert_eq!(ResponseFormat::parse("json"), Some(ResponseFormat::Json));
2505        assert_eq!(ResponseFormat::parse("null"), Some(ResponseFormat::Null));
2506
2507        // invalid formats
2508        assert_eq!(ResponseFormat::parse("invalid"), None);
2509        assert_eq!(ResponseFormat::parse(""), None);
2510        assert_eq!(ResponseFormat::parse("CSV"), None); // Case sensitive
2511
2512        // as str
2513        assert_eq!(ResponseFormat::Arrow.as_str(), "arrow");
2514        assert_eq!(ResponseFormat::Csv(false, false).as_str(), "csv");
2515        assert_eq!(ResponseFormat::Csv(true, true).as_str(), "csv");
2516        assert_eq!(ResponseFormat::Table.as_str(), "table");
2517        assert_eq!(ResponseFormat::GreptimedbV1.as_str(), "greptimedb_v1");
2518        assert_eq!(ResponseFormat::InfluxdbV1.as_str(), "influxdb_v1");
2519        assert_eq!(ResponseFormat::Json.as_str(), "json");
2520        assert_eq!(ResponseFormat::Null.as_str(), "null");
2521        assert_eq!(ResponseFormat::default().as_str(), "greptimedb_v1");
2522    }
2523
2524    #[test]
2525    fn test_decode_label_name_strict() {
2526        let strict = PromValidationMode::Strict;
2527
2528        // Valid Prometheus label names
2529        assert!(strict.decode_label_name(b"__name__").is_ok());
2530        assert!(strict.decode_label_name(b"job").is_ok());
2531        assert!(strict.decode_label_name(b"instance").is_ok());
2532        assert!(strict.decode_label_name(b"_private").is_ok());
2533        assert!(strict.decode_label_name(b"label_with_underscores").is_ok());
2534        assert!(strict.decode_label_name(b"abc123").is_ok());
2535        assert!(strict.decode_label_name(b"A").is_ok());
2536        assert!(strict.decode_label_name(b"_").is_ok());
2537
2538        // Invalid: starts with digit
2539        assert!(strict.decode_label_name(b"0abc").is_err());
2540        assert!(strict.decode_label_name(b"123").is_err());
2541
2542        // Invalid: contains special characters
2543        assert!(strict.decode_label_name(b"label-name").is_err());
2544        assert!(strict.decode_label_name(b"label.name").is_err());
2545        assert!(strict.decode_label_name(b"label name").is_err());
2546        assert!(strict.decode_label_name(b"label/name").is_err());
2547
2548        // Invalid: empty
2549        assert!(strict.decode_label_name(b"").is_err());
2550
2551        // Invalid: non-ASCII UTF-8
2552        assert!(strict.decode_label_name("ラベル".as_bytes()).is_err());
2553
2554        // Invalid UTF-8 bytes should fail
2555        assert!(strict.decode_label_name(&[0xff, 0xfe]).is_err());
2556    }
2557
2558    #[test]
2559    fn test_decode_label_name_lossy() {
2560        let lossy = PromValidationMode::Lossy;
2561
2562        // Label name validation is always enforced.
2563        assert!(lossy.decode_label_name(b"__name__").is_ok());
2564        assert!(lossy.decode_label_name(b"label-name").is_err());
2565        assert!(lossy.decode_label_name(b"0abc").is_err());
2566
2567        // Invalid UTF-8 bytes fail the label-name byte check.
2568        assert!(lossy.decode_label_name(&[0xff, 0xfe]).is_err());
2569    }
2570
2571    #[test]
2572    fn test_decode_label_name_unchecked() {
2573        let unchecked = PromValidationMode::Unchecked;
2574
2575        // Label name validation is always enforced.
2576        assert!(unchecked.decode_label_name(b"__name__").is_ok());
2577        assert!(unchecked.decode_label_name(b"label-name").is_err());
2578        assert!(unchecked.decode_label_name(b"0abc").is_err());
2579    }
2580
2581    #[test]
2582    fn test_is_valid_prom_label_name_bytes() {
2583        assert!(validate_label_name(b"__name__"));
2584        assert!(validate_label_name(b"job"));
2585        assert!(validate_label_name(b"_"));
2586        assert!(validate_label_name(b"A"));
2587        assert!(validate_label_name(b"abc123"));
2588        assert!(validate_label_name(b"_leading_underscore"));
2589
2590        assert!(!validate_label_name(b""));
2591        assert!(!validate_label_name(b"0starts_with_digit"));
2592        assert!(!validate_label_name(b"has-dash"));
2593        assert!(!validate_label_name(b"has.dot"));
2594        assert!(!validate_label_name(b"has space"));
2595        assert!(!validate_label_name(&[0xff, 0xfe]));
2596    }
2597}
2598
2599#[cfg(test)]
2600mod batching_tests {
2601    use std::sync::{Arc, Mutex};
2602
2603    use axum::http::StatusCode;
2604    use common_query::Output;
2605    use session::context::QueryContextRef;
2606
2607    use crate::batcher::BatchingProtocol;
2608    use crate::error::Result as ServerResult;
2609    use crate::http::test_helpers::TestClient;
2610    use crate::http::{HttpOptions, HttpServerBuilder};
2611    use crate::influxdb::InfluxdbRequest;
2612    use crate::opentsdb::codec::DataPoint;
2613    use crate::query_handler::{InfluxdbLineProtocolHandler, OpentsdbProtocolHandler};
2614
2615    #[tokio::test]
2616    async fn test_batching_selectors_are_independent() {
2617        use axum::routing::post;
2618        use axum::{Extension, Json, Router, middleware};
2619        use session::context::{QueryContext, QueryContextBuilder};
2620
2621        use crate::http::{LogicalBatchingProtocols, set_http_write_batching};
2622        for table in [false, true] {
2623            for logical in [false, true] {
2624                let app = Router::new()
2625                    .route(
2626                        "/",
2627                        post(|Extension(ctx): Extension<QueryContext>| async move {
2628                            Json([ctx.batching_enabled(), ctx.logical_batching_enabled()])
2629                        }),
2630                    )
2631                    .route_layer(middleware::from_fn_with_state(
2632                        BatchingProtocol::Otlp,
2633                        set_http_write_batching,
2634                    ))
2635                    .layer(Extension(QueryContextBuilder::default().build()))
2636                    .layer(Extension(Arc::new(if table {
2637                        vec![BatchingProtocol::Otlp]
2638                    } else {
2639                        vec![]
2640                    })))
2641                    .layer(Extension(LogicalBatchingProtocols(if logical {
2642                        vec![BatchingProtocol::Otlp]
2643                    } else {
2644                        vec![]
2645                    })));
2646                let client = TestClient::new(app).await;
2647                let actual: [bool; 2] = client.post("/").send().await.json().await;
2648                assert_eq!(actual, [table, logical]);
2649            }
2650        }
2651    }
2652
2653    #[derive(Default)]
2654    struct RecordingWriteHandler {
2655        selections: Mutex<Vec<bool>>,
2656    }
2657
2658    #[async_trait::async_trait]
2659    impl OpentsdbProtocolHandler for RecordingWriteHandler {
2660        async fn preflight(&self, _: &[DataPoint], _: QueryContextRef) -> ServerResult<()> {
2661            Ok(())
2662        }
2663
2664        async fn exec(&self, points: Vec<DataPoint>, ctx: QueryContextRef) -> ServerResult<usize> {
2665            self.selections.lock().unwrap().push(ctx.batching_enabled());
2666            Ok(points.len())
2667        }
2668    }
2669
2670    #[async_trait::async_trait]
2671    impl InfluxdbLineProtocolHandler for RecordingWriteHandler {
2672        async fn exec(&self, _: InfluxdbRequest, ctx: QueryContextRef) -> ServerResult<Output> {
2673            self.selections.lock().unwrap().push(ctx.batching_enabled());
2674            Ok(Output::new_with_affected_rows(1))
2675        }
2676    }
2677
2678    #[tokio::test]
2679    async fn test_real_influx_and_opentsdb_routes_keep_selection() {
2680        for protocols in [
2681            vec![],
2682            vec![BatchingProtocol::Influxdb],
2683            vec![BatchingProtocol::Opentsdb],
2684            vec![BatchingProtocol::Influxdb, BatchingProtocol::Opentsdb],
2685        ] {
2686            let handler = Arc::new(RecordingWriteHandler::default());
2687            let influx_enabled = protocols.contains(&BatchingProtocol::Influxdb);
2688            let opentsdb_enabled = protocols.contains(&BatchingProtocol::Opentsdb);
2689            let server = HttpServerBuilder::new(HttpOptions::default())
2690                .with_batching_protocols(protocols)
2691                .with_influxdb_handler(handler.clone())
2692                .with_opentsdb_handler(handler.clone())
2693                .build();
2694            let client = TestClient::new(server.build(server.make_app()).unwrap()).await;
2695            for path in [
2696                "/v1/influxdb/write",
2697                "/v1/influxdb/api/v2/write?bucket=public",
2698            ] {
2699                assert_eq!(
2700                    client
2701                        .post(path)
2702                        .body("cpu value=1 42")
2703                        .send()
2704                        .await
2705                        .status(),
2706                    StatusCode::NO_CONTENT
2707                );
2708            }
2709            let point =
2710                serde_json::json!({"metric":"cpu", "timestamp":42, "value":1, "tags":{"host":"a"}});
2711            for query in ["", "?summary", "?details"] {
2712                let response = client
2713                    .post(&format!("/v1/opentsdb/api/put{query}"))
2714                    .json(&point)
2715                    .send()
2716                    .await;
2717                assert_eq!(
2718                    response.status(),
2719                    if query.is_empty() {
2720                        StatusCode::NO_CONTENT
2721                    } else {
2722                        StatusCode::OK
2723                    }
2724                );
2725            }
2726            assert_eq!(
2727                *handler.selections.lock().unwrap(),
2728                vec![
2729                    influx_enabled,
2730                    influx_enabled,
2731                    opentsdb_enabled,
2732                    opentsdb_enabled,
2733                    opentsdb_enabled
2734                ]
2735            );
2736        }
2737    }
2738}