1use 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};
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::{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 snafu::{ResultExt, ensure};
44use tokio::sync::Mutex;
45use tokio::sync::oneshot::{self, Sender};
46use tonic::codegen::Service;
47use tower::{Layer, ServiceBuilder};
48use tower_http::compression::CompressionLayer;
49use tower_http::cors::{AllowOrigin, Any, CorsLayer};
50use tower_http::decompression::RequestDecompressionLayer;
51use tower_http::trace::TraceLayer;
52
53use self::authorize::AuthState;
54use self::result::table_result::TableResponse;
55use crate::elasticsearch;
56use crate::error::{
57 AddressBindSnafu, AlreadyStartedSnafu, Error, InternalIoSnafu, InvalidHeaderValueSnafu, Result,
58};
59use crate::http::influxdb::{influxdb_health, influxdb_ping, influxdb_write_v1, influxdb_write_v2};
60use crate::http::otlp::OtlpState;
61use crate::http::prom_store::PromStoreState;
62use crate::http::prometheus::{
63 build_info_query, format_query, instant_query, label_values_query, labels_query,
64 metadata_query, parse_query, range_query, series_query,
65};
66use crate::http::result::arrow_result::ArrowResponse;
67use crate::http::result::csv_result::CsvResponse;
68use crate::http::result::error_result::ErrorResponse;
69use crate::http::result::greptime_result_v1::GreptimedbV1Response;
70use crate::http::result::influxdb_result_v1::InfluxdbV1Response;
71use crate::http::result::json_result::JsonResponse;
72use crate::http::result::null_result::NullResponse;
73use crate::interceptor::LogIngestInterceptorRef;
74use crate::metrics::http_metrics_layer;
75use crate::metrics_handler::MetricsHandler;
76use crate::pending_rows_batcher::PendingRowsBatcher;
77use crate::prometheus_handler::PrometheusHandlerRef;
78use crate::query_handler::sql::ServerSqlQueryHandlerRef;
79use crate::query_handler::{
80 DashboardHandlerRef, InfluxdbLineProtocolHandlerRef, JaegerQueryHandlerRef, LogQueryHandlerRef,
81 OpenTelemetryProtocolHandlerRef, OpentsdbProtocolHandlerRef, PipelineHandlerRef,
82 PromStoreProtocolHandlerRef,
83};
84use crate::request_memory_limiter::ServerMemoryLimiter;
85use crate::server::Server;
86
87pub mod authorize;
88#[cfg(feature = "dashboard")]
89mod dashboard;
90pub mod dyn_log;
91pub mod dyn_trace;
92pub mod event;
93pub mod extractor;
94pub mod handler;
95pub mod header;
96pub mod influxdb;
97pub mod jaeger;
98pub mod logs;
99pub mod loki;
100pub mod mem_prof;
101mod memory_limit;
102pub mod opentsdb;
103pub mod otlp;
104pub mod pprof;
105pub mod prom_store;
106pub mod prometheus;
107pub mod result;
108pub mod splunk;
109mod timeout;
110pub mod utils;
111
112use result::HttpOutputWriter;
113pub(crate) use timeout::DynamicTimeoutLayer;
114
115mod client_ip;
116use crate::prom_remote_write::validation::PromValidationMode;
117mod hints;
118mod read_preference;
119#[cfg(any(test, feature = "testing"))]
120pub mod test_helpers;
121
122pub const HTTP_API_VERSION: &str = "v1";
123pub const HTTP_API_PREFIX: &str = "/v1/";
124pub const HTTP_API_PREFIX_WITHOUT_TRAILING_SLASH: &str = "/v1";
125
126pub trait ExtraHttpRouterProvider: Send + Sync {
128 fn router(&self) -> Router;
130}
131
132pub type ExtraHttpRouterProviderRef = Arc<dyn ExtraHttpRouterProvider>;
133
134#[derive(Clone, Default)]
136pub struct ExtraHttpRouterProviders {
137 providers: Vec<ExtraHttpRouterProviderRef>,
138}
139
140impl ExtraHttpRouterProviders {
141 pub fn new() -> Self {
143 Self::default()
144 }
145
146 pub fn add(&mut self, provider: ExtraHttpRouterProviderRef) {
148 self.providers.push(provider);
149 }
150
151 pub fn iter(&self) -> impl Iterator<Item = &dyn ExtraHttpRouterProvider> {
153 self.providers.iter().map(|x| x.as_ref())
154 }
155}
156
157const DEFAULT_BODY_LIMIT: ReadableSize = ReadableSize::mb(64);
159const DEFAULT_HTTP_API_ADDR_PORT: u16 = 4006;
161
162pub const AUTHORIZATION_HEADER: &str = "x-greptime-auth";
164
165pub static PUBLIC_API_PREFIX: [&str; 4] = [
168 "/v1/influxdb/ping",
169 "/v1/influxdb/health",
170 "/v1/health",
171 "/v1/splunk/services/collector/health",
172];
173
174#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
180pub(crate) enum HttpServerKind {
181 #[default]
184 Full,
185 Api,
187}
188
189#[derive(Default)]
190pub struct HttpServer {
191 router: StdMutex<Router>,
192 shutdown_tx: Mutex<Option<Sender<()>>>,
193 user_provider: Option<UserProviderRef>,
194 memory_limiter: ServerMemoryLimiter,
195
196 options: HttpOptions,
198 bind_addr: Option<SocketAddr>,
199 kind: HttpServerKind,
201}
202
203pub(crate) fn is_namespace(path: &str, root: &str) -> bool {
207 path == root
208 || path
209 .strip_prefix(root)
210 .is_some_and(|suffix| suffix.starts_with('/'))
211}
212
213pub fn is_api_listener_path(path: &str) -> bool {
215 is_namespace(path, HTTP_API_PREFIX_WITHOUT_TRAILING_SLASH) || is_namespace(path, "/dashboard")
216}
217
218async fn enforce_api_surface(req: Request, next: Next) -> Response {
222 if !is_api_listener_path(req.uri().path()) {
223 return HttpStatusCode::NOT_FOUND.into_response();
224 }
225 next.run(req).await
226}
227
228impl HttpServer {
229 fn kind(&self) -> &'static str {
231 match self.kind {
232 HttpServerKind::Api => "HTTP API",
233 HttpServerKind::Full => "HTTP",
234 }
235 }
236}
237
238#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
239#[serde(default)]
240pub struct HttpOptions {
241 pub addr: String,
242
243 #[serde(with = "humantime_serde")]
244 pub timeout: Duration,
245
246 #[serde(skip)]
247 pub disable_dashboard: bool,
248
249 pub body_limit: ReadableSize,
250
251 pub cors_allowed_origins: Vec<String>,
252
253 pub enable_cors: bool,
254
255 pub experimental_enable_explain_analyze_stream: bool,
256
257 pub enable_api_server: bool,
262 pub api_server_addr: String,
265}
266
267impl Default for HttpOptions {
268 fn default() -> Self {
269 Self {
270 addr: "127.0.0.1:4000".to_string(),
271 timeout: Duration::from_secs(0),
272 disable_dashboard: false,
273 body_limit: DEFAULT_BODY_LIMIT,
274 cors_allowed_origins: Vec::new(),
275 enable_cors: true,
276 experimental_enable_explain_analyze_stream: true,
277 enable_api_server: false,
278 api_server_addr: format!("127.0.0.1:{}", DEFAULT_HTTP_API_ADDR_PORT),
279 }
280 }
281}
282
283#[derive(Debug, Serialize, Deserialize, Eq, PartialEq)]
284pub struct ColumnSchema {
285 name: String,
286 data_type: String,
287}
288
289impl ColumnSchema {
290 pub fn new(name: String, data_type: String) -> ColumnSchema {
291 ColumnSchema { name, data_type }
292 }
293}
294
295#[derive(Debug, Serialize, Deserialize, Eq, PartialEq)]
296pub struct OutputSchema {
297 column_schemas: Vec<ColumnSchema>,
298}
299
300impl OutputSchema {
301 pub fn new(columns: Vec<ColumnSchema>) -> OutputSchema {
302 OutputSchema {
303 column_schemas: columns,
304 }
305 }
306}
307
308impl From<SchemaRef> for OutputSchema {
309 fn from(schema: SchemaRef) -> OutputSchema {
310 OutputSchema {
311 column_schemas: schema
312 .column_schemas()
313 .iter()
314 .map(|cs| ColumnSchema {
315 name: cs.name.clone(),
316 data_type: cs.data_type.name(),
317 })
318 .collect(),
319 }
320 }
321}
322
323#[derive(Debug, Serialize, Deserialize, Eq, PartialEq)]
324pub struct HttpRecordsOutput {
325 schema: OutputSchema,
326 rows: Vec<Vec<Value>>,
327 #[serde(default)]
330 total_rows: usize,
331
332 #[serde(skip_serializing_if = "HashMap::is_empty")]
334 #[serde(default)]
335 metrics: HashMap<String, Value>,
336}
337
338impl HttpRecordsOutput {
339 pub fn num_rows(&self) -> usize {
340 self.rows.len()
341 }
342
343 pub fn num_cols(&self) -> usize {
344 self.schema.column_schemas.len()
345 }
346
347 pub fn schema(&self) -> &OutputSchema {
348 &self.schema
349 }
350
351 pub fn rows(&self) -> &Vec<Vec<Value>> {
352 &self.rows
353 }
354}
355
356impl HttpRecordsOutput {
357 pub fn try_new(
358 schema: SchemaRef,
359 recordbatches: Vec<RecordBatch>,
360 ) -> std::result::Result<HttpRecordsOutput, Error> {
361 if recordbatches.is_empty() {
362 Ok(HttpRecordsOutput {
363 schema: OutputSchema::from(schema),
364 rows: vec![],
365 total_rows: 0,
366 metrics: Default::default(),
367 })
368 } else {
369 let num_rows = recordbatches.iter().map(|r| r.num_rows()).sum::<usize>();
370 let mut rows = Vec::with_capacity(num_rows);
371
372 for recordbatch in recordbatches {
373 let mut writer = HttpOutputWriter::new(schema.num_columns(), None);
374 writer.write(recordbatch, &mut rows)?;
375 }
376
377 Ok(HttpRecordsOutput {
378 schema: OutputSchema::from(schema),
379 total_rows: rows.len(),
380 rows,
381 metrics: Default::default(),
382 })
383 }
384 }
385}
386
387#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)]
388#[serde(rename_all = "lowercase")]
389pub enum GreptimeQueryOutput {
390 AffectedRows(usize),
391 Records(HttpRecordsOutput),
392}
393
394#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
396pub enum ResponseFormat {
397 Arrow,
398 Csv(bool, bool),
400 Table,
401 #[default]
402 GreptimedbV1,
403 InfluxdbV1,
404 Json,
405 Null,
406}
407
408impl ResponseFormat {
409 pub fn parse(s: &str) -> Option<Self> {
410 match s {
411 "arrow" => Some(ResponseFormat::Arrow),
412 "csv" => Some(ResponseFormat::Csv(false, false)),
413 "csvwithnames" => Some(ResponseFormat::Csv(true, false)),
414 "csvwithnamesandtypes" => Some(ResponseFormat::Csv(true, true)),
415 "table" => Some(ResponseFormat::Table),
416 "greptimedb_v1" => Some(ResponseFormat::GreptimedbV1),
417 "influxdb_v1" => Some(ResponseFormat::InfluxdbV1),
418 "json" => Some(ResponseFormat::Json),
419 "null" => Some(ResponseFormat::Null),
420 _ => None,
421 }
422 }
423
424 pub fn as_str(&self) -> &'static str {
425 match self {
426 ResponseFormat::Arrow => "arrow",
427 ResponseFormat::Csv(_, _) => "csv",
428 ResponseFormat::Table => "table",
429 ResponseFormat::GreptimedbV1 => "greptimedb_v1",
430 ResponseFormat::InfluxdbV1 => "influxdb_v1",
431 ResponseFormat::Json => "json",
432 ResponseFormat::Null => "null",
433 }
434 }
435}
436
437#[derive(Debug, Clone, Copy, PartialEq, Eq)]
438pub enum Epoch {
439 Nanosecond,
440 Microsecond,
441 Millisecond,
442 Second,
443}
444
445impl Epoch {
446 pub fn parse(s: &str) -> Option<Epoch> {
447 match s {
452 "ns" => Some(Epoch::Nanosecond),
453 "u" | "µ" => Some(Epoch::Microsecond),
454 "ms" => Some(Epoch::Millisecond),
455 "s" => Some(Epoch::Second),
456 _ => None, }
458 }
459
460 pub fn convert_timestamp(&self, ts: Timestamp) -> Option<Timestamp> {
461 match self {
462 Epoch::Nanosecond => ts.convert_to(TimeUnit::Nanosecond),
463 Epoch::Microsecond => ts.convert_to(TimeUnit::Microsecond),
464 Epoch::Millisecond => ts.convert_to(TimeUnit::Millisecond),
465 Epoch::Second => ts.convert_to(TimeUnit::Second),
466 }
467 }
468}
469
470impl Display for Epoch {
471 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
472 match self {
473 Epoch::Nanosecond => write!(f, "Epoch::Nanosecond"),
474 Epoch::Microsecond => write!(f, "Epoch::Microsecond"),
475 Epoch::Millisecond => write!(f, "Epoch::Millisecond"),
476 Epoch::Second => write!(f, "Epoch::Second"),
477 }
478 }
479}
480
481#[derive(Serialize, Deserialize, Debug)]
482pub enum HttpResponse {
483 Arrow(ArrowResponse),
484 Csv(CsvResponse),
485 Table(TableResponse),
486 Error(ErrorResponse),
487 GreptimedbV1(GreptimedbV1Response),
488 InfluxdbV1(InfluxdbV1Response),
489 Json(JsonResponse),
490 Null(NullResponse),
491}
492
493impl HttpResponse {
494 pub fn with_execution_time(self, execution_time: u64) -> Self {
495 match self {
496 HttpResponse::Arrow(resp) => resp.with_execution_time(execution_time).into(),
497 HttpResponse::Csv(resp) => resp.with_execution_time(execution_time).into(),
498 HttpResponse::Table(resp) => resp.with_execution_time(execution_time).into(),
499 HttpResponse::GreptimedbV1(resp) => resp.with_execution_time(execution_time).into(),
500 HttpResponse::InfluxdbV1(resp) => resp.with_execution_time(execution_time).into(),
501 HttpResponse::Json(resp) => resp.with_execution_time(execution_time).into(),
502 HttpResponse::Null(resp) => resp.with_execution_time(execution_time).into(),
503 HttpResponse::Error(resp) => resp.with_execution_time(execution_time).into(),
504 }
505 }
506
507 pub fn with_limit(self, limit: usize) -> Self {
508 match self {
509 HttpResponse::Csv(resp) => resp.with_limit(limit).into(),
510 HttpResponse::Table(resp) => resp.with_limit(limit).into(),
511 HttpResponse::GreptimedbV1(resp) => resp.with_limit(limit).into(),
512 HttpResponse::Json(resp) => resp.with_limit(limit).into(),
513 _ => self,
514 }
515 }
516}
517
518pub fn process_with_limit(
519 mut outputs: Vec<GreptimeQueryOutput>,
520 limit: usize,
521) -> Vec<GreptimeQueryOutput> {
522 outputs
523 .drain(..)
524 .map(|data| match data {
525 GreptimeQueryOutput::Records(mut records) => {
526 if records.rows.len() > limit {
527 records.rows.truncate(limit);
528 records.total_rows = limit;
529 }
530 GreptimeQueryOutput::Records(records)
531 }
532 _ => data,
533 })
534 .collect()
535}
536
537impl IntoResponse for HttpResponse {
538 fn into_response(self) -> Response {
539 match self {
540 HttpResponse::Arrow(resp) => resp.into_response(),
541 HttpResponse::Csv(resp) => resp.into_response(),
542 HttpResponse::Table(resp) => resp.into_response(),
543 HttpResponse::GreptimedbV1(resp) => resp.into_response(),
544 HttpResponse::InfluxdbV1(resp) => resp.into_response(),
545 HttpResponse::Json(resp) => resp.into_response(),
546 HttpResponse::Null(resp) => resp.into_response(),
547 HttpResponse::Error(resp) => resp.into_response(),
548 }
549 }
550}
551
552impl From<ArrowResponse> for HttpResponse {
553 fn from(value: ArrowResponse) -> Self {
554 HttpResponse::Arrow(value)
555 }
556}
557
558impl From<CsvResponse> for HttpResponse {
559 fn from(value: CsvResponse) -> Self {
560 HttpResponse::Csv(value)
561 }
562}
563
564impl From<TableResponse> for HttpResponse {
565 fn from(value: TableResponse) -> Self {
566 HttpResponse::Table(value)
567 }
568}
569
570impl From<ErrorResponse> for HttpResponse {
571 fn from(value: ErrorResponse) -> Self {
572 HttpResponse::Error(value)
573 }
574}
575
576impl From<GreptimedbV1Response> for HttpResponse {
577 fn from(value: GreptimedbV1Response) -> Self {
578 HttpResponse::GreptimedbV1(value)
579 }
580}
581
582impl From<InfluxdbV1Response> for HttpResponse {
583 fn from(value: InfluxdbV1Response) -> Self {
584 HttpResponse::InfluxdbV1(value)
585 }
586}
587
588impl From<JsonResponse> for HttpResponse {
589 fn from(value: JsonResponse) -> Self {
590 HttpResponse::Json(value)
591 }
592}
593
594impl From<NullResponse> for HttpResponse {
595 fn from(value: NullResponse) -> Self {
596 HttpResponse::Null(value)
597 }
598}
599
600#[derive(Clone)]
601pub struct ApiState {
602 pub sql_handler: ServerSqlQueryHandlerRef,
603 pub experimental_enable_explain_analyze_stream: bool,
604}
605
606#[derive(Clone)]
607pub struct GreptimeOptionsConfigState {
608 pub greptime_config_options: String,
609}
610
611#[derive(Clone)]
612pub struct DashboardState {
613 pub handler: DashboardHandlerRef,
614}
615
616pub struct HttpServerBuilder {
617 options: HttpOptions,
618 user_provider: Option<UserProviderRef>,
619 router: Router,
620 memory_limiter: ServerMemoryLimiter,
621}
622
623impl HttpServerBuilder {
624 pub fn new(options: HttpOptions) -> Self {
625 Self {
626 options,
627 user_provider: None,
628 router: Router::new(),
629 memory_limiter: ServerMemoryLimiter::default(),
630 }
631 }
632
633 pub fn with_memory_limiter(mut self, limiter: ServerMemoryLimiter) -> Self {
635 self.memory_limiter = limiter;
636 self
637 }
638
639 pub fn with_sql_handler(self, sql_handler: ServerSqlQueryHandlerRef) -> Self {
640 let sql_router = HttpServer::route_sql(ApiState {
641 sql_handler,
642 experimental_enable_explain_analyze_stream: self
643 .options
644 .experimental_enable_explain_analyze_stream,
645 });
646
647 Self {
648 router: self
649 .router
650 .nest(&format!("/{HTTP_API_VERSION}"), sql_router),
651 ..self
652 }
653 }
654
655 pub fn with_logs_handler(self, logs_handler: LogQueryHandlerRef) -> Self {
656 let logs_router = HttpServer::route_logs(logs_handler);
657
658 Self {
659 router: self
660 .router
661 .nest(&format!("/{HTTP_API_VERSION}"), logs_router),
662 ..self
663 }
664 }
665
666 pub fn with_opentsdb_handler(self, handler: OpentsdbProtocolHandlerRef) -> Self {
667 Self {
668 router: self.router.nest(
669 &format!("/{HTTP_API_VERSION}/opentsdb"),
670 HttpServer::route_opentsdb(handler),
671 ),
672 ..self
673 }
674 }
675
676 pub fn with_influxdb_handler(self, handler: InfluxdbLineProtocolHandlerRef) -> Self {
677 Self {
678 router: self.router.nest(
679 &format!("/{HTTP_API_VERSION}/influxdb"),
680 HttpServer::route_influxdb(handler),
681 ),
682 ..self
683 }
684 }
685
686 pub fn with_prom_handler(
687 self,
688 handler: PromStoreProtocolHandlerRef,
689 pipeline_handler: Option<PipelineHandlerRef>,
690 prom_store_with_metric_engine: bool,
691 prom_validation_mode: PromValidationMode,
692 experimental_enable_prometheus_native_histogram: bool,
693 pending_rows_batcher: Option<Arc<PendingRowsBatcher>>,
694 ) -> Self {
695 let state = PromStoreState {
696 prom_store_handler: handler,
697 pipeline_handler,
698 prom_store_with_metric_engine,
699 prom_validation_mode,
700 experimental_enable_prometheus_native_histogram,
701 pending_rows_batcher,
702 };
703
704 Self {
705 router: self.router.nest(
706 &format!("/{HTTP_API_VERSION}/prometheus"),
707 HttpServer::route_prom(state),
708 ),
709 ..self
710 }
711 }
712
713 pub fn with_prometheus_handler(self, handler: PrometheusHandlerRef) -> Self {
714 Self {
715 router: self.router.nest(
716 &format!("/{HTTP_API_VERSION}/prometheus/api/v1"),
717 HttpServer::route_prometheus(handler),
718 ),
719 ..self
720 }
721 }
722
723 pub fn with_otlp_handler(
724 self,
725 handler: OpenTelemetryProtocolHandlerRef,
726 with_metric_engine: bool,
727 ) -> Self {
728 Self {
729 router: self.router.nest(
730 &format!("/{HTTP_API_VERSION}/otlp"),
731 HttpServer::route_otlp(handler, with_metric_engine),
732 ),
733 ..self
734 }
735 }
736
737 pub fn with_user_provider(self, user_provider: UserProviderRef) -> Self {
738 Self {
739 user_provider: Some(user_provider),
740 ..self
741 }
742 }
743
744 pub fn with_metrics_handler(self, handler: MetricsHandler) -> Self {
745 Self {
746 router: self.router.merge(HttpServer::route_metrics(handler)),
747 ..self
748 }
749 }
750
751 pub fn with_log_ingest_handler(
752 self,
753 handler: PipelineHandlerRef,
754 validator: Option<LogValidatorRef>,
755 ingest_interceptor: Option<LogIngestInterceptorRef<Error>>,
756 ) -> Self {
757 let log_state = LogState {
758 log_handler: handler,
759 log_validator: validator,
760 ingest_interceptor,
761 };
762
763 let router = self.router.nest(
764 &format!("/{HTTP_API_VERSION}"),
765 HttpServer::route_pipelines(log_state.clone()),
766 );
767 let router = router.nest(
769 &format!("/{HTTP_API_VERSION}/events"),
770 #[allow(deprecated)]
771 HttpServer::route_log_deprecated(log_state.clone()),
772 );
773
774 let router = router.nest(
775 &format!("/{HTTP_API_VERSION}/loki"),
776 HttpServer::route_loki(log_state.clone()),
777 );
778
779 let router = router.nest(
780 &format!("/{HTTP_API_VERSION}/elasticsearch"),
781 HttpServer::route_elasticsearch(log_state.clone()),
782 );
783
784 let router = router.nest(
785 &format!("/{HTTP_API_VERSION}/elasticsearch/"),
786 Router::new()
787 .route("/", routing::get(elasticsearch::handle_get_version))
788 .with_state(log_state.clone()),
789 );
790
791 let router = router.nest(
792 &format!("/{HTTP_API_VERSION}/splunk"),
793 HttpServer::route_splunk(log_state),
794 );
795
796 Self { router, ..self }
797 }
798
799 pub fn with_greptime_config_options(self, opts: String) -> Self {
800 let config_router = HttpServer::route_config(GreptimeOptionsConfigState {
801 greptime_config_options: opts,
802 });
803
804 Self {
805 router: self.router.merge(config_router),
806 ..self
807 }
808 }
809
810 pub fn with_jaeger_handler(self, handler: JaegerQueryHandlerRef) -> Self {
811 Self {
812 router: self.router.nest(
813 &format!("/{HTTP_API_VERSION}/jaeger"),
814 HttpServer::route_jaeger(handler),
815 ),
816 ..self
817 }
818 }
819
820 pub fn with_dashboard_handler(self, handler: DashboardHandlerRef) -> Self {
821 Self {
822 router: self.router.nest(
823 &format!("/{HTTP_API_VERSION}/dashboards"),
824 HttpServer::route_dashboard(handler),
825 ),
826 ..self
827 }
828 }
829
830 pub fn with_extra_router(self, router: Router) -> Self {
831 Self {
832 router: self.router.merge(router),
833 ..self
834 }
835 }
836
837 pub fn add_layer<L>(self, layer: L) -> Self
838 where
839 L: Layer<Route> + Clone + Send + Sync + 'static,
840 L::Service: Service<Request> + Clone + Send + Sync + 'static,
841 <L::Service as Service<Request>>::Response: IntoResponse + 'static,
842 <L::Service as Service<Request>>::Error: Into<Infallible> + 'static,
843 <L::Service as Service<Request>>::Future: Send + 'static,
844 {
845 Self {
846 router: self.router.layer(layer),
847 ..self
848 }
849 }
850
851 pub fn build(self) -> HttpServer {
852 HttpServer {
853 options: self.options,
854 user_provider: self.user_provider,
855 shutdown_tx: Mutex::new(None),
856 router: StdMutex::new(self.router),
857 bind_addr: None,
858 memory_limiter: self.memory_limiter,
859 kind: HttpServerKind::Full,
860 }
861 }
862
863 pub fn build_servers(self) -> (HttpServer, Option<HttpServer>) {
882 let api_enabled = self.options.enable_api_server;
883 let api_addr = self.options.api_server_addr.clone();
884
885 let internal = HttpServer {
886 options: self.options,
887 user_provider: self.user_provider.clone(),
888 shutdown_tx: Mutex::new(None),
889 router: StdMutex::new(self.router.clone()),
890 bind_addr: None,
891 memory_limiter: self.memory_limiter.clone(),
892 kind: HttpServerKind::Full,
893 };
894
895 let api = if api_enabled {
896 let api_options = HttpOptions {
899 addr: api_addr,
900 ..internal.options.clone()
901 };
902 Some(HttpServer {
903 options: api_options,
904 user_provider: self.user_provider.clone(),
905 shutdown_tx: Mutex::new(None),
906 router: StdMutex::new(self.router),
907 bind_addr: None,
908 memory_limiter: self.memory_limiter,
909 kind: HttpServerKind::Api,
910 })
911 } else {
912 None
913 };
914
915 (internal, api)
916 }
917}
918
919impl HttpServer {
920 pub fn make_app(&self) -> Router {
930 let mut router = self.router.lock().unwrap().clone();
931
932 router = router
933 .route(
934 &format!("/{HTTP_API_VERSION}/health"),
935 routing::get(handler::health).post(handler::health),
936 )
937 .route("/", routing::get(handler::index))
938 .route(
939 "/health",
940 routing::get(handler::health).post(handler::health),
941 )
942 .route(
943 "/ready",
944 routing::get(handler::health).post(handler::health),
945 )
946 .route("/status", routing::get(handler::status));
947
948 #[cfg(feature = "dashboard")]
951 {
952 if !self.options.disable_dashboard {
953 info!("Enable dashboard service at '/dashboard'");
954 router = router.route(
956 "/dashboard",
957 routing::get(|uri: axum::http::uri::Uri| async move {
958 let path = uri.path();
959 let query = uri.query().map(|q| format!("?{}", q)).unwrap_or_default();
960
961 let new_uri = format!("{}/{}", path, query);
962 axum::response::Redirect::permanent(&new_uri)
963 }),
964 );
965
966 router = router
970 .route(
971 "/dashboard/",
972 routing::get(dashboard::static_handler).post(dashboard::static_handler),
973 )
974 .route(
975 "/dashboard/{*x}",
976 routing::get(dashboard::static_handler).post(dashboard::static_handler),
977 );
978 }
979 }
980
981 router = router.route_layer(middleware::from_fn(http_metrics_layer));
983
984 router
985 }
986
987 pub fn build(&self, router: Router) -> Result<Router> {
990 let timeout_layer = if self.options.timeout != Duration::default() {
991 Some(
992 ServiceBuilder::new().layer(
993 DynamicTimeoutLayer::new(self.options.timeout)
994 .with_status_code_fn(Self::request_timeout_status_code),
995 ),
996 )
997 } else {
998 info!("HTTP server timeout is disabled");
999 None
1000 };
1001 let body_limit_layer = if self.options.body_limit != ReadableSize(0) {
1002 Some(
1003 ServiceBuilder::new()
1004 .layer(DefaultBodyLimit::max(self.options.body_limit.0 as usize)),
1005 )
1006 } else {
1007 info!("HTTP server body limit is disabled");
1008 None
1009 };
1010 let cors_layer = if self.options.enable_cors {
1011 Some(
1012 CorsLayer::new()
1013 .allow_methods([
1014 Method::GET,
1015 Method::POST,
1016 Method::PUT,
1017 Method::DELETE,
1018 Method::HEAD,
1019 ])
1020 .allow_origin(if self.options.cors_allowed_origins.is_empty() {
1021 AllowOrigin::from(Any)
1022 } else {
1023 AllowOrigin::from(
1024 self.options
1025 .cors_allowed_origins
1026 .iter()
1027 .map(|s| {
1028 HeaderValue::from_str(s.as_str())
1029 .context(InvalidHeaderValueSnafu)
1030 })
1031 .collect::<Result<Vec<HeaderValue>>>()?,
1032 )
1033 })
1034 .allow_headers(Any),
1035 )
1036 } else {
1037 info!("HTTP server cross-origin is disabled");
1038 None
1039 };
1040
1041 let router = router
1042 .layer(
1044 ServiceBuilder::new()
1045 .layer(TraceLayer::new_for_http().on_failure(()))
1048 .option_layer(cors_layer)
1049 .option_layer(timeout_layer)
1050 .option_layer(body_limit_layer)
1051 .layer(middleware::from_fn_with_state(
1053 self.memory_limiter.clone(),
1054 memory_limit::memory_limit_middleware,
1055 ))
1056 .layer(middleware::from_fn_with_state(
1058 AuthState::new(self.user_provider.clone()),
1059 authorize::check_http_auth,
1060 ))
1061 .layer(middleware::from_fn(hints::extract_hints))
1062 .layer(middleware::from_fn(client_ip::log_error_with_client_ip))
1063 .layer(middleware::from_fn(
1064 read_preference::extract_read_preference,
1065 )),
1066 );
1067
1068 let router = router.nest(
1071 "/debug",
1072 Router::new()
1073 .route("/log_level", routing::post(dyn_log::dyn_log_handler))
1075 .route("/enable_trace", routing::post(dyn_trace::dyn_trace_handler))
1076 .nest(
1077 "/prof",
1078 Router::new()
1079 .route("/cpu", routing::post(pprof::pprof_handler))
1080 .route("/mem", routing::post(mem_prof::mem_prof_handler))
1081 .route("/mem/symbol", routing::post(mem_prof::symbolicate_handler))
1082 .route(
1083 "/mem/activate",
1084 routing::post(mem_prof::activate_heap_prof_handler),
1085 )
1086 .route(
1087 "/mem/deactivate",
1088 routing::post(mem_prof::deactivate_heap_prof_handler),
1089 )
1090 .route(
1091 "/mem/status",
1092 routing::get(mem_prof::heap_prof_status_handler),
1093 ) .route(
1095 "/mem/gdump",
1096 routing::get(mem_prof::gdump_status_handler)
1097 .post(mem_prof::gdump_toggle_handler),
1098 ),
1099 ),
1100 );
1101
1102 if self.kind == HttpServerKind::Api {
1107 Ok(router.layer(middleware::from_fn(enforce_api_surface)))
1108 } else {
1109 Ok(router)
1110 }
1111 }
1112
1113 fn request_timeout_status_code(request: &Request) -> HttpStatusCode {
1114 if request.uri().path() == "/v1/prometheus/write" {
1115 HttpStatusCode::GATEWAY_TIMEOUT
1116 } else {
1117 HttpStatusCode::REQUEST_TIMEOUT
1118 }
1119 }
1120
1121 fn route_metrics<S>(metrics_handler: MetricsHandler) -> Router<S> {
1122 Router::new()
1123 .route("/metrics", routing::get(handler::metrics))
1124 .with_state(metrics_handler)
1125 }
1126
1127 fn route_loki<S>(log_state: LogState) -> Router<S> {
1128 Router::new()
1129 .route("/api/v1/push", routing::post(loki::loki_ingest))
1130 .layer(
1131 ServiceBuilder::new()
1132 .layer(RequestDecompressionLayer::new().pass_through_unaccepted(true)),
1133 )
1134 .with_state(log_state)
1135 }
1136
1137 fn route_splunk<S>(log_state: LogState) -> Router<S> {
1138 Router::new()
1139 .route(
1140 "/services/collector/health",
1141 routing::get(splunk::handle_health),
1142 )
1143 .route(
1144 "/services/collector/health/1.0",
1145 routing::get(splunk::handle_health),
1146 )
1147 .route(
1150 "/services/collector/event",
1151 routing::post(splunk::handle_event),
1152 )
1153 .route("/services/collector", routing::post(splunk::handle_event))
1154 .route(
1155 "/services/collector/event/1.0",
1156 routing::post(splunk::handle_event),
1157 )
1158 .route("/services/collector/raw", routing::post(splunk::handle_raw))
1161 .route(
1162 "/services/collector/raw/1.0",
1163 routing::post(splunk::handle_raw),
1164 )
1165 .layer(
1166 ServiceBuilder::new()
1167 .layer(RequestDecompressionLayer::new().pass_through_unaccepted(true)),
1168 )
1169 .with_state(log_state)
1170 }
1171
1172 fn route_elasticsearch<S>(log_state: LogState) -> Router<S> {
1173 Router::new()
1174 .route(
1176 "/",
1177 routing::head((HttpStatusCode::OK, elasticsearch::elasticsearch_headers())),
1178 )
1179 .route("/", routing::get(elasticsearch::handle_get_version))
1181 .route("/_license", routing::get(elasticsearch::handle_get_license))
1183 .route("/_bulk", routing::post(elasticsearch::handle_bulk_api))
1184 .route(
1185 "/{index}/_bulk",
1186 routing::post(elasticsearch::handle_bulk_api_with_index),
1187 )
1188 .route(
1190 "/_ilm/policy/{*path}",
1191 routing::any((
1192 HttpStatusCode::OK,
1193 elasticsearch::elasticsearch_headers(),
1194 axum::Json(serde_json::json!({})),
1195 )),
1196 )
1197 .route(
1199 "/_index_template/{*path}",
1200 routing::any((
1201 HttpStatusCode::OK,
1202 elasticsearch::elasticsearch_headers(),
1203 axum::Json(serde_json::json!({})),
1204 )),
1205 )
1206 .route(
1209 "/_ingest/{*path}",
1210 routing::any((
1211 HttpStatusCode::OK,
1212 elasticsearch::elasticsearch_headers(),
1213 axum::Json(serde_json::json!({})),
1214 )),
1215 )
1216 .route(
1219 "/_nodes/{*path}",
1220 routing::any((
1221 HttpStatusCode::OK,
1222 elasticsearch::elasticsearch_headers(),
1223 axum::Json(serde_json::json!({})),
1224 )),
1225 )
1226 .route(
1229 "/logstash/{*path}",
1230 routing::any((
1231 HttpStatusCode::OK,
1232 elasticsearch::elasticsearch_headers(),
1233 axum::Json(serde_json::json!({})),
1234 )),
1235 )
1236 .route(
1237 "/_logstash/{*path}",
1238 routing::any((
1239 HttpStatusCode::OK,
1240 elasticsearch::elasticsearch_headers(),
1241 axum::Json(serde_json::json!({})),
1242 )),
1243 )
1244 .layer(ServiceBuilder::new().layer(RequestDecompressionLayer::new()))
1245 .with_state(log_state)
1246 }
1247
1248 #[deprecated(since = "0.11.0", note = "Use `route_pipelines()` instead.")]
1249 fn route_log_deprecated<S>(log_state: LogState) -> Router<S> {
1250 Router::new()
1251 .route("/logs", routing::post(event::log_ingester))
1252 .route(
1253 "/pipelines/{pipeline_name}",
1254 routing::get(event::query_pipeline),
1255 )
1256 .route(
1257 "/pipelines/{pipeline_name}",
1258 routing::post(event::add_pipeline),
1259 )
1260 .route(
1261 "/pipelines/{pipeline_name}",
1262 routing::delete(event::delete_pipeline),
1263 )
1264 .route("/pipelines/dryrun", routing::post(event::pipeline_dryrun))
1265 .layer(
1266 ServiceBuilder::new()
1267 .layer(RequestDecompressionLayer::new().pass_through_unaccepted(true)),
1268 )
1269 .with_state(log_state)
1270 }
1271
1272 fn route_pipelines<S>(log_state: LogState) -> Router<S> {
1273 Router::new()
1274 .route("/ingest", routing::post(event::log_ingester))
1275 .route(
1276 "/pipelines/{pipeline_name}",
1277 routing::get(event::query_pipeline),
1278 )
1279 .route(
1280 "/pipelines/{pipeline_name}/ddl",
1281 routing::get(event::query_pipeline_ddl),
1282 )
1283 .route(
1284 "/pipelines/{pipeline_name}",
1285 routing::post(event::add_pipeline),
1286 )
1287 .route(
1288 "/pipelines/{pipeline_name}",
1289 routing::delete(event::delete_pipeline),
1290 )
1291 .route("/pipelines/_dryrun", routing::post(event::pipeline_dryrun))
1292 .layer(
1293 ServiceBuilder::new()
1294 .layer(RequestDecompressionLayer::new().pass_through_unaccepted(true)),
1295 )
1296 .with_state(log_state)
1297 }
1298
1299 fn route_sql<S>(api_state: ApiState) -> Router<S> {
1300 let mut router = Router::new()
1301 .route("/sql", routing::get(handler::sql).post(handler::sql))
1302 .route(
1303 "/sql/parse",
1304 routing::get(handler::sql_parse).post(handler::sql_parse),
1305 )
1306 .route(
1307 "/sql/format",
1308 routing::get(handler::sql_format).post(handler::sql_format),
1309 )
1310 .route(
1311 "/promql",
1312 routing::get(handler::promql).post(handler::promql),
1313 );
1314
1315 if api_state.experimental_enable_explain_analyze_stream {
1316 router = router.route(
1317 "/sql/analyze/stream",
1318 routing::post(handler::sql_analyze_stream),
1319 );
1320 }
1321
1322 router.with_state(api_state)
1323 }
1324
1325 fn route_logs<S>(log_handler: LogQueryHandlerRef) -> Router<S> {
1326 Router::new()
1327 .route("/logs", routing::get(logs::logs).post(logs::logs))
1328 .with_state(log_handler)
1329 }
1330
1331 pub fn route_prometheus<S>(prometheus_handler: PrometheusHandlerRef) -> Router<S> {
1335 Router::new()
1336 .route(
1337 "/format_query",
1338 routing::post(format_query).get(format_query),
1339 )
1340 .route("/status/buildinfo", routing::get(build_info_query))
1341 .route("/query", routing::post(instant_query).get(instant_query))
1342 .route("/query_range", routing::post(range_query).get(range_query))
1343 .route("/labels", routing::post(labels_query).get(labels_query))
1344 .route("/metadata", routing::get(metadata_query))
1345 .route("/series", routing::post(series_query).get(series_query))
1346 .route("/parse_query", routing::post(parse_query).get(parse_query))
1347 .route(
1348 "/label/{label_name}/values",
1349 routing::get(label_values_query),
1350 )
1351 .layer(ServiceBuilder::new().layer(CompressionLayer::new()))
1352 .with_state(prometheus_handler)
1353 }
1354
1355 fn route_prom<S>(state: PromStoreState) -> Router<S> {
1361 Router::new()
1362 .route("/read", routing::post(prom_store::remote_read))
1363 .route("/write", routing::post(prom_store::remote_write))
1364 .with_state(state)
1365 }
1366
1367 fn route_influxdb<S>(influxdb_handler: InfluxdbLineProtocolHandlerRef) -> Router<S> {
1368 Router::new()
1369 .route("/write", routing::post(influxdb_write_v1))
1370 .route("/api/v2/write", routing::post(influxdb_write_v2))
1371 .layer(
1372 ServiceBuilder::new()
1373 .layer(RequestDecompressionLayer::new().pass_through_unaccepted(true)),
1374 )
1375 .route("/ping", routing::get(influxdb_ping))
1376 .route("/health", routing::get(influxdb_health))
1377 .with_state(influxdb_handler)
1378 }
1379
1380 fn route_opentsdb<S>(opentsdb_handler: OpentsdbProtocolHandlerRef) -> Router<S> {
1381 Router::new()
1382 .route("/api/put", routing::post(opentsdb::put))
1383 .with_state(opentsdb_handler)
1384 }
1385
1386 fn route_otlp<S>(
1387 otlp_handler: OpenTelemetryProtocolHandlerRef,
1388 with_metric_engine: bool,
1389 ) -> Router<S> {
1390 Router::new()
1391 .route("/v1/metrics", routing::post(otlp::metrics))
1392 .route("/v1/traces", routing::post(otlp::traces))
1393 .route("/v1/logs", routing::post(otlp::logs))
1394 .layer(
1395 ServiceBuilder::new()
1396 .layer(RequestDecompressionLayer::new().pass_through_unaccepted(true)),
1397 )
1398 .with_state(OtlpState {
1399 with_metric_engine,
1400 handler: otlp_handler,
1401 })
1402 }
1403
1404 fn route_config<S>(state: GreptimeOptionsConfigState) -> Router<S> {
1405 Router::new()
1406 .route("/config", routing::get(handler::config))
1407 .with_state(state)
1408 }
1409
1410 fn route_jaeger<S>(handler: JaegerQueryHandlerRef) -> Router<S> {
1411 Router::new()
1412 .route("/api/services", routing::get(jaeger::handle_get_services))
1413 .route(
1414 "/api/services/{service_name}/operations",
1415 routing::get(jaeger::handle_get_operations_by_service),
1416 )
1417 .route(
1418 "/api/operations",
1419 routing::get(jaeger::handle_get_operations),
1420 )
1421 .route("/api/traces", routing::get(jaeger::handle_find_traces))
1422 .route(
1423 "/api/traces/{trace_id}",
1424 routing::get(jaeger::handle_get_trace),
1425 )
1426 .with_state(handler)
1427 }
1428
1429 #[cfg(feature = "dashboard")]
1430 fn route_dashboard<S>(handler: DashboardHandlerRef) -> Router<S> {
1431 use crate::http::dashboard::{add_dashboard, delete_dashboard, list_dashboards};
1432
1433 Router::new()
1434 .route("/", routing::get(list_dashboards))
1435 .route("/{dashboard_name}", routing::post(add_dashboard))
1436 .route("/{dashboard_name}", routing::delete(delete_dashboard))
1437 .layer(
1438 ServiceBuilder::new()
1439 .layer(RequestDecompressionLayer::new().pass_through_unaccepted(true)),
1440 )
1441 .with_state(DashboardState { handler })
1442 }
1443
1444 #[cfg(not(feature = "dashboard"))]
1445 fn route_dashboard<S>(handler: DashboardHandlerRef) -> Router<S> {
1446 Router::new().with_state(DashboardState { handler })
1447 }
1448}
1449
1450pub const HTTP_SERVER: &str = "HTTP_SERVER";
1451pub const HTTP_API_SERVER: &str = "HTTP_API_SERVER";
1452
1453#[async_trait]
1454impl Server for HttpServer {
1455 async fn shutdown(&self) -> Result<()> {
1456 let mut shutdown_tx = self.shutdown_tx.lock().await;
1457 if let Some(tx) = shutdown_tx.take()
1458 && tx.send(()).is_err()
1459 {
1460 info!("Receiver dropped, the HTTP server has already exited");
1461 }
1462 info!("Shutdown {}", self.kind());
1463
1464 Ok(())
1465 }
1466
1467 async fn start(&mut self, listening: SocketAddr) -> Result<()> {
1468 let (tx, rx) = oneshot::channel();
1469 let serve = {
1470 let mut shutdown_tx = self.shutdown_tx.lock().await;
1471 ensure!(
1472 shutdown_tx.is_none(),
1473 AlreadyStartedSnafu {
1474 server: self.kind()
1475 }
1476 );
1477
1478 let app = self.build(self.make_app())?;
1479 let listener = tokio::net::TcpListener::bind(listening)
1480 .await
1481 .context(AddressBindSnafu { addr: listening })?
1482 .tap_io(|tcp_stream| {
1483 if let Err(e) = tcp_stream.set_nodelay(true) {
1484 error!(e; "Failed to set TCP_NODELAY on incoming connection");
1485 }
1486 });
1487 let serve = axum::serve(
1488 listener,
1489 app.into_make_service_with_connect_info::<SocketAddr>(),
1490 );
1491
1492 *shutdown_tx = Some(tx);
1509
1510 serve
1511 };
1512 let listening = serve.local_addr().context(InternalIoSnafu)?;
1513 info!("{} server is bound to {}", self.kind(), listening);
1514
1515 common_runtime::spawn_global(async move {
1516 if let Err(e) = serve
1517 .with_graceful_shutdown(rx.map(drop))
1518 .await
1519 .context(InternalIoSnafu)
1520 {
1521 error!(e; "Failed to shutdown http server");
1522 }
1523 });
1524
1525 self.bind_addr = Some(listening);
1526 Ok(())
1527 }
1528
1529 fn name(&self) -> &str {
1530 match self.kind {
1531 HttpServerKind::Api => HTTP_API_SERVER,
1532 HttpServerKind::Full => HTTP_SERVER,
1533 }
1534 }
1535
1536 fn bind_addr(&self) -> Option<SocketAddr> {
1537 self.bind_addr
1538 }
1539
1540 fn as_any(&self) -> &dyn std::any::Any {
1541 self
1542 }
1543}
1544
1545#[cfg(test)]
1546mod test {
1547 use std::future::pending;
1548 use std::io::Cursor;
1549 use std::sync::Arc;
1550
1551 use arrow_ipc::reader::StreamReader;
1552 use arrow_schema::DataType;
1553 use axum::http::StatusCode;
1554 use axum::routing::{get, post};
1555 use common_query::{Output, OutputData};
1556 use common_recordbatch::RecordBatches;
1557 use datafusion_expr::LogicalPlan;
1558 use datatypes::prelude::*;
1559 use datatypes::schema::{ColumnSchema, Schema};
1560 use datatypes::vectors::{StringVector, UInt32Vector};
1561 use header::constants::GREPTIME_DB_HEADER_TIMEOUT;
1562 use query::parser::PromQuery;
1563 use query::query_engine::DescribeResult;
1564 use session::context::QueryContextRef;
1565 use sql::statements::statement::Statement;
1566 use tokio::sync::mpsc;
1567 use tokio::time::Instant;
1568
1569 use super::*;
1570 use crate::http::test_helpers::TestClient;
1571 use crate::prom_remote_write::validation::validate_label_name;
1572 use crate::query_handler::sql::SqlQueryHandler;
1573
1574 struct DummyInstance {
1575 _tx: mpsc::Sender<(String, Vec<u8>)>,
1576 }
1577
1578 #[async_trait]
1579 impl SqlQueryHandler for DummyInstance {
1580 async fn do_query(&self, _: &str, _: QueryContextRef) -> Vec<Result<Output>> {
1581 unimplemented!()
1582 }
1583
1584 async fn do_analyze_stream_query(&self, _: &str, _: QueryContextRef) -> Result<Output> {
1585 let stream = common_recordbatch::RecordBatches::empty().as_stream();
1586 Ok(Output::new(OutputData::Stream(stream), Default::default()))
1587 }
1588
1589 async fn do_promql_query(&self, _: &PromQuery, _: QueryContextRef) -> Vec<Result<Output>> {
1590 unimplemented!()
1591 }
1592
1593 async fn do_exec_plan(
1594 &self,
1595 _plan: LogicalPlan,
1596 _stmt: Option<Statement>,
1597 _query_ctx: QueryContextRef,
1598 ) -> Result<Output> {
1599 unimplemented!()
1600 }
1601
1602 async fn do_describe(
1603 &self,
1604 _stmt: sql::statements::statement::Statement,
1605 _query_ctx: QueryContextRef,
1606 ) -> Result<Option<DescribeResult>> {
1607 unimplemented!()
1608 }
1609
1610 async fn is_valid_schema(&self, _catalog: &str, _schema: &str) -> Result<bool> {
1611 Ok(true)
1612 }
1613 }
1614
1615 async fn forever() {
1616 pending().await
1617 }
1618
1619 fn make_test_app(tx: mpsc::Sender<(String, Vec<u8>)>) -> Router {
1620 make_test_app_custom(tx, HttpOptions::default())
1621 }
1622
1623 fn make_test_app_custom(tx: mpsc::Sender<(String, Vec<u8>)>, options: HttpOptions) -> Router {
1624 let instance = Arc::new(DummyInstance { _tx: tx });
1625 let server = HttpServerBuilder::new(options)
1626 .with_sql_handler(instance.clone())
1627 .build();
1628 let app = server
1629 .make_app()
1630 .route("/test/timeout", get(forever))
1631 .route("/v1/prometheus/write", post(forever));
1632 server.build(app).unwrap()
1633 }
1634
1635 #[tokio::test]
1636 pub async fn test_analyze_stream_route_config_gate() {
1637 let (tx, _rx) = mpsc::channel(100);
1638 let options = HttpOptions {
1639 experimental_enable_explain_analyze_stream: false,
1640 ..Default::default()
1641 };
1642 let app = make_test_app_custom(tx, options);
1643 let client = TestClient::new(app).await;
1644 let res = client
1645 .post("/v1/sql/analyze/stream?sql=EXPLAIN%20ANALYZE%20VERBOSE%20SELECT%201")
1646 .send()
1647 .await;
1648 assert_eq!(res.status(), StatusCode::NOT_FOUND);
1649
1650 let (tx, _rx) = mpsc::channel(100);
1651 let app = make_test_app_custom(tx, HttpOptions::default());
1652 let client = TestClient::new(app).await;
1653 let res = client
1654 .post("/v1/sql/analyze/stream?sql=EXPLAIN%20ANALYZE%20VERBOSE%20SELECT%201")
1655 .send()
1656 .await;
1657 assert_ne!(res.status(), StatusCode::NOT_FOUND);
1658 }
1659
1660 fn make_split_builder() -> HttpServerBuilder {
1661 let (tx, _rx) = mpsc::channel(100);
1662 let instance = Arc::new(DummyInstance { _tx: tx });
1663 let options = HttpOptions {
1665 enable_api_server: true,
1666 ..HttpOptions::default()
1667 };
1668 HttpServerBuilder::new(options)
1669 .with_sql_handler(instance)
1670 .with_metrics_handler(MetricsHandler)
1671 .with_greptime_config_options("dummy = \"value\"".to_string())
1672 }
1673
1674 #[tokio::test]
1675 pub async fn test_http_api_options_defaults() {
1676 let opts = HttpOptions::default();
1677 assert!(!opts.enable_api_server);
1679 assert_eq!(opts.api_server_addr, "127.0.0.1:4006");
1680 }
1681
1682 #[tokio::test]
1683 pub async fn test_build_serves_all_routes() {
1684 let server = make_split_builder().build();
1688 assert_eq!(server.name(), HTTP_SERVER);
1689
1690 let app = server.build(server.make_app()).unwrap();
1691 let client = TestClient::new(app).await;
1692
1693 for path in ["/v1/health", "/health", "/status", "/metrics", "/config"] {
1694 let res = client.get(path).send().await;
1695 assert_eq!(
1696 res.status(),
1697 StatusCode::OK,
1698 "internal/full server should serve {path}"
1699 );
1700 }
1701 }
1702
1703 #[tokio::test]
1704 pub async fn test_build_servers_separates_api_and_internal_routes() {
1705 let (internal, api) = make_split_builder().build_servers();
1708 let api = api.expect("API server is explicitly enabled in make_split_builder");
1709 assert_eq!(internal.name(), HTTP_SERVER);
1710 assert_eq!(api.name(), HTTP_API_SERVER);
1711
1712 let internal_app = internal.build(internal.make_app()).unwrap();
1713 let api_app = api.build(api.make_app()).unwrap();
1714
1715 let internal_client = TestClient::new(internal_app).await;
1716 let api_client = TestClient::new(api_app).await;
1717
1718 assert_eq!(
1720 internal_client.get("/v1/health").send().await.status(),
1721 StatusCode::OK
1722 );
1723 assert_eq!(
1724 api_client.get("/v1/health").send().await.status(),
1725 StatusCode::OK
1726 );
1727
1728 for path in ["/health", "/status", "/metrics", "/config"] {
1730 assert_eq!(
1731 internal_client.get(path).send().await.status(),
1732 StatusCode::OK,
1733 "internal/full server should serve {path}"
1734 );
1735 assert_eq!(
1737 api_client.get(path).send().await.status(),
1738 StatusCode::NOT_FOUND,
1739 "API server should NOT serve {path}"
1740 );
1741 }
1742 }
1743
1744 #[test]
1745 fn test_build_servers_api_inherits_http_options() {
1746 let http_opts = HttpOptions {
1748 timeout: Duration::from_secs(42),
1749 body_limit: ReadableSize::mb(128),
1750 cors_allowed_origins: vec!["https://example.com".to_string()],
1751 enable_api_server: true,
1752 ..HttpOptions::default()
1753 };
1754 let (internal, api) = HttpServerBuilder::new(http_opts.clone()).build_servers();
1755 let api = api.expect("API server is explicitly enabled");
1756
1757 assert_eq!(internal.options.addr, http_opts.addr);
1759 assert_eq!(api.options.addr, "127.0.0.1:4006");
1760 assert_eq!(api.options.timeout, http_opts.timeout);
1762 assert_eq!(api.options.body_limit, http_opts.body_limit);
1763 assert_eq!(
1764 api.options.cors_allowed_origins,
1765 http_opts.cors_allowed_origins
1766 );
1767 }
1768
1769 #[test]
1770 fn test_is_api_listener_path() {
1771 assert!(is_api_listener_path("/v1"));
1773 assert!(is_api_listener_path("/v1/"));
1774 assert!(is_api_listener_path("/v1/sql"));
1775 assert!(!is_api_listener_path("/v10/sql"));
1776 assert!(!is_api_listener_path("/v1-internal"));
1777 assert!(is_api_listener_path("/dashboard"));
1779 assert!(is_api_listener_path("/dashboard/app.js"));
1780 assert!(!is_api_listener_path("/dashboard-admin"));
1781 assert!(!is_api_listener_path("/metrics"));
1783 assert!(!is_api_listener_path("/status/plugin"));
1784 assert!(!is_api_listener_path("/health"));
1785 }
1786
1787 #[tokio::test]
1788 pub async fn test_extra_router_respects_api_surface() {
1789 let (tx, _rx) = mpsc::channel(100);
1795 let instance = Arc::new(DummyInstance { _tx: tx });
1796 let options = HttpOptions {
1797 enable_api_server: true,
1798 ..HttpOptions::default()
1799 };
1800 let builder = HttpServerBuilder::new(options)
1801 .with_sql_handler(instance)
1802 .with_extra_router(
1803 Router::new()
1804 .route("/status/plugin", routing::get(|| async { "ok" }))
1805 .route("/v1/plugin", routing::get(|| async { "ok" })),
1806 );
1807 let (full, api) = builder.build_servers();
1808 let api = api.expect("API server is explicitly enabled");
1809
1810 let full_client = TestClient::new(full.build(full.make_app()).unwrap()).await;
1811 let api_client = TestClient::new(api.build(api.make_app()).unwrap()).await;
1812
1813 assert_eq!(
1815 full_client.get("/status/plugin").send().await.status(),
1816 StatusCode::OK
1817 );
1818 assert_eq!(
1820 api_client.get("/status/plugin").send().await.status(),
1821 StatusCode::NOT_FOUND
1822 );
1823
1824 assert_eq!(
1826 full_client.get("/v1/plugin").send().await.status(),
1827 StatusCode::OK
1828 );
1829 assert_eq!(
1830 api_client.get("/v1/plugin").send().await.status(),
1831 StatusCode::OK
1832 );
1833
1834 assert_eq!(
1836 api_client.get("/v10/plugin").send().await.status(),
1837 StatusCode::NOT_FOUND
1838 );
1839 }
1840
1841 #[test]
1842 fn test_build_servers_api_disabled_by_default() {
1843 let (_internal, api) = HttpServerBuilder::new(HttpOptions::default()).build_servers();
1846 assert!(api.is_none());
1847 }
1848
1849 #[tokio::test]
1850 pub async fn test_cors() {
1851 let (tx, _rx) = mpsc::channel(100);
1853 let app = make_test_app(tx);
1854 let client = TestClient::new(app).await;
1855
1856 let res = client.get("/health").send().await;
1857
1858 assert_eq!(res.status(), StatusCode::OK);
1859 assert_eq!(
1860 res.headers()
1861 .get(http::header::ACCESS_CONTROL_ALLOW_ORIGIN)
1862 .expect("expect cors header origin"),
1863 "*"
1864 );
1865
1866 let res = client.get("/v1/health").send().await;
1867
1868 assert_eq!(res.status(), StatusCode::OK);
1869 assert_eq!(
1870 res.headers()
1871 .get(http::header::ACCESS_CONTROL_ALLOW_ORIGIN)
1872 .expect("expect cors header origin"),
1873 "*"
1874 );
1875
1876 let res = client
1877 .options("/health")
1878 .header("Access-Control-Request-Headers", "x-greptime-auth")
1879 .header("Access-Control-Request-Method", "DELETE")
1880 .header("Origin", "https://example.com")
1881 .send()
1882 .await;
1883 assert_eq!(res.status(), StatusCode::OK);
1884 assert_eq!(
1885 res.headers()
1886 .get(http::header::ACCESS_CONTROL_ALLOW_ORIGIN)
1887 .expect("expect cors header origin"),
1888 "*"
1889 );
1890 assert_eq!(
1891 res.headers()
1892 .get(http::header::ACCESS_CONTROL_ALLOW_HEADERS)
1893 .expect("expect cors header headers"),
1894 "*"
1895 );
1896 assert_eq!(
1897 res.headers()
1898 .get(http::header::ACCESS_CONTROL_ALLOW_METHODS)
1899 .expect("expect cors header methods"),
1900 "GET,POST,PUT,DELETE,HEAD"
1901 );
1902 }
1903
1904 #[tokio::test]
1905 pub async fn test_cors_custom_origins() {
1906 let (tx, _rx) = mpsc::channel(100);
1908 let origin = "https://example.com";
1909
1910 let options = HttpOptions {
1911 cors_allowed_origins: vec![origin.to_string()],
1912 ..Default::default()
1913 };
1914
1915 let app = make_test_app_custom(tx, options);
1916 let client = TestClient::new(app).await;
1917
1918 let res = client.get("/health").header("Origin", origin).send().await;
1919
1920 assert_eq!(res.status(), StatusCode::OK);
1921 assert_eq!(
1922 res.headers()
1923 .get(http::header::ACCESS_CONTROL_ALLOW_ORIGIN)
1924 .expect("expect cors header origin"),
1925 origin
1926 );
1927
1928 let res = client
1929 .get("/health")
1930 .header("Origin", "https://notallowed.com")
1931 .send()
1932 .await;
1933
1934 assert_eq!(res.status(), StatusCode::OK);
1935 assert!(
1936 !res.headers()
1937 .contains_key(http::header::ACCESS_CONTROL_ALLOW_ORIGIN)
1938 );
1939 }
1940
1941 #[tokio::test]
1942 pub async fn test_cors_disabled() {
1943 let (tx, _rx) = mpsc::channel(100);
1945
1946 let options = HttpOptions {
1947 enable_cors: false,
1948 ..Default::default()
1949 };
1950
1951 let app = make_test_app_custom(tx, options);
1952 let client = TestClient::new(app).await;
1953
1954 let res = client.get("/health").send().await;
1955
1956 assert_eq!(res.status(), StatusCode::OK);
1957 assert!(
1958 !res.headers()
1959 .contains_key(http::header::ACCESS_CONTROL_ALLOW_ORIGIN)
1960 );
1961 }
1962
1963 #[test]
1964 fn test_http_options_default() {
1965 let default = HttpOptions::default();
1966 assert_eq!("127.0.0.1:4000".to_string(), default.addr);
1967 assert_eq!(Duration::from_secs(0), default.timeout)
1968 }
1969
1970 #[tokio::test]
1971 async fn test_http_server_request_timeout() {
1972 common_telemetry::init_default_ut_logging();
1973
1974 let (tx, _rx) = mpsc::channel(100);
1975 let options = HttpOptions {
1976 timeout: Duration::from_millis(10),
1977 ..Default::default()
1978 };
1979 let app = make_test_app_custom(tx, options);
1980 let client = TestClient::new(app).await;
1981 let res = client.get("/test/timeout").send().await;
1982 assert_eq!(res.status(), StatusCode::REQUEST_TIMEOUT);
1983
1984 let res = client.post("/v1/prometheus/write").send().await;
1985 assert_eq!(res.status(), StatusCode::GATEWAY_TIMEOUT);
1986
1987 let now = Instant::now();
1988 let res = client
1989 .get("/test/timeout")
1990 .header(GREPTIME_DB_HEADER_TIMEOUT, "20ms")
1991 .send()
1992 .await;
1993 assert_eq!(res.status(), StatusCode::REQUEST_TIMEOUT);
1994 let elapsed = now.elapsed();
1995 assert!(elapsed > Duration::from_millis(15));
1996
1997 tokio::time::timeout(
1998 Duration::from_millis(15),
1999 client
2000 .get("/test/timeout")
2001 .header(GREPTIME_DB_HEADER_TIMEOUT, "0s")
2002 .send(),
2003 )
2004 .await
2005 .unwrap_err();
2006
2007 tokio::time::timeout(
2008 Duration::from_millis(15),
2009 client
2010 .get("/test/timeout")
2011 .header(
2012 GREPTIME_DB_HEADER_TIMEOUT,
2013 humantime::format_duration(Duration::default()).to_string(),
2014 )
2015 .send(),
2016 )
2017 .await
2018 .unwrap_err();
2019 }
2020
2021 #[tokio::test]
2022 async fn test_schema_for_empty_response() {
2023 let column_schemas = vec![
2024 ColumnSchema::new("numbers", ConcreteDataType::uint32_datatype(), false),
2025 ColumnSchema::new("strings", ConcreteDataType::string_datatype(), true),
2026 ];
2027 let schema = Arc::new(Schema::new(column_schemas));
2028
2029 let recordbatches = RecordBatches::try_new(schema.clone(), vec![]).unwrap();
2030 let outputs = vec![Ok(Output::new_with_record_batches(recordbatches))];
2031
2032 let json_resp = GreptimedbV1Response::from_output(outputs).await;
2033 if let HttpResponse::GreptimedbV1(json_resp) = json_resp {
2034 let json_output = &json_resp.output[0];
2035 if let GreptimeQueryOutput::Records(r) = json_output {
2036 assert_eq!(r.num_rows(), 0);
2037 assert_eq!(r.num_cols(), 2);
2038 assert_eq!(r.schema.column_schemas[0].name, "numbers");
2039 assert_eq!(r.schema.column_schemas[0].data_type, "UInt32");
2040 } else {
2041 panic!("invalid output type");
2042 }
2043 } else {
2044 panic!("invalid format")
2045 }
2046 }
2047
2048 #[tokio::test]
2049 async fn test_recordbatches_conversion() {
2050 let column_schemas = vec![
2051 ColumnSchema::new("numbers", ConcreteDataType::uint32_datatype(), false),
2052 ColumnSchema::new("strings", ConcreteDataType::string_datatype(), true),
2053 ];
2054 let schema = Arc::new(Schema::new(column_schemas));
2055 let columns: Vec<VectorRef> = vec![
2056 Arc::new(UInt32Vector::from_slice(vec![1, 2, 3, 4])),
2057 Arc::new(StringVector::from(vec![
2058 None,
2059 Some("hello"),
2060 Some("greptime"),
2061 None,
2062 ])),
2063 ];
2064 let recordbatch = RecordBatch::new(schema.clone(), columns).unwrap();
2065
2066 for format in [
2067 ResponseFormat::GreptimedbV1,
2068 ResponseFormat::InfluxdbV1,
2069 ResponseFormat::Csv(true, true),
2070 ResponseFormat::Table,
2071 ResponseFormat::Arrow,
2072 ResponseFormat::Json,
2073 ResponseFormat::Null,
2074 ] {
2075 let recordbatches =
2076 RecordBatches::try_new(schema.clone(), vec![recordbatch.clone()]).unwrap();
2077 let outputs = vec![Ok(Output::new_with_record_batches(recordbatches))];
2078 let json_resp = match format {
2079 ResponseFormat::Arrow => ArrowResponse::from_output(outputs, None).await,
2080 ResponseFormat::Csv(with_names, with_types) => {
2081 CsvResponse::from_output(outputs, with_names, with_types).await
2082 }
2083 ResponseFormat::Table => TableResponse::from_output(outputs).await,
2084 ResponseFormat::GreptimedbV1 => GreptimedbV1Response::from_output(outputs).await,
2085 ResponseFormat::InfluxdbV1 => InfluxdbV1Response::from_output(outputs, None).await,
2086 ResponseFormat::Json => JsonResponse::from_output(outputs).await,
2087 ResponseFormat::Null => NullResponse::from_output(outputs).await,
2088 };
2089
2090 match json_resp {
2091 HttpResponse::GreptimedbV1(resp) => {
2092 let json_output = &resp.output[0];
2093 if let GreptimeQueryOutput::Records(r) = json_output {
2094 assert_eq!(r.num_rows(), 4);
2095 assert_eq!(r.num_cols(), 2);
2096 assert_eq!(r.schema.column_schemas[0].name, "numbers");
2097 assert_eq!(r.schema.column_schemas[0].data_type, "UInt32");
2098 assert_eq!(r.rows[0][0], serde_json::Value::from(1));
2099 assert_eq!(r.rows[0][1], serde_json::Value::Null);
2100 } else {
2101 panic!("invalid output type");
2102 }
2103 }
2104 HttpResponse::InfluxdbV1(resp) => {
2105 let json_output = &resp.results()[0];
2106 assert_eq!(json_output.num_rows(), 4);
2107 assert_eq!(json_output.num_cols(), 2);
2108 assert_eq!(json_output.series[0].columns.clone()[0], "numbers");
2109 assert_eq!(
2110 json_output.series[0].values[0][0],
2111 serde_json::Value::from(1)
2112 );
2113 assert_eq!(json_output.series[0].values[0][1], serde_json::Value::Null);
2114 }
2115 HttpResponse::Csv(resp) => {
2116 let output = &resp.output()[0];
2117 if let GreptimeQueryOutput::Records(r) = output {
2118 assert_eq!(r.num_rows(), 4);
2119 assert_eq!(r.num_cols(), 2);
2120 assert_eq!(r.schema.column_schemas[0].name, "numbers");
2121 assert_eq!(r.schema.column_schemas[0].data_type, "UInt32");
2122 assert_eq!(r.rows[0][0], serde_json::Value::from(1));
2123 assert_eq!(r.rows[0][1], serde_json::Value::Null);
2124 } else {
2125 panic!("invalid output type");
2126 }
2127 }
2128
2129 HttpResponse::Table(resp) => {
2130 let output = &resp.output()[0];
2131 if let GreptimeQueryOutput::Records(r) = output {
2132 assert_eq!(r.num_rows(), 4);
2133 assert_eq!(r.num_cols(), 2);
2134 assert_eq!(r.schema.column_schemas[0].name, "numbers");
2135 assert_eq!(r.schema.column_schemas[0].data_type, "UInt32");
2136 assert_eq!(r.rows[0][0], serde_json::Value::from(1));
2137 assert_eq!(r.rows[0][1], serde_json::Value::Null);
2138 } else {
2139 panic!("invalid output type");
2140 }
2141 }
2142
2143 HttpResponse::Arrow(resp) => {
2144 let output = resp.data;
2145 let mut reader = StreamReader::try_new(Cursor::new(output), None)
2146 .expect("Arrow reader error");
2147 let schema = reader.schema();
2148 assert_eq!(schema.fields[0].name(), "numbers");
2149 assert_eq!(schema.fields[0].data_type(), &DataType::UInt32);
2150 assert_eq!(schema.fields[1].name(), "strings");
2151 assert_eq!(schema.fields[1].data_type(), &DataType::Utf8);
2152
2153 let rb = reader.next().unwrap().expect("read record batch failed");
2154 assert_eq!(rb.num_columns(), 2);
2155 assert_eq!(rb.num_rows(), 4);
2156 }
2157
2158 HttpResponse::Json(resp) => {
2159 let output = &resp.output()[0];
2160 if let GreptimeQueryOutput::Records(r) = output {
2161 assert_eq!(r.num_rows(), 4);
2162 assert_eq!(r.num_cols(), 2);
2163 assert_eq!(r.schema.column_schemas[0].name, "numbers");
2164 assert_eq!(r.schema.column_schemas[0].data_type, "UInt32");
2165 assert_eq!(r.rows[0][0], serde_json::Value::from(1));
2166 assert_eq!(r.rows[0][1], serde_json::Value::Null);
2167 } else {
2168 panic!("invalid output type");
2169 }
2170 }
2171
2172 HttpResponse::Null(resp) => {
2173 assert_eq!(resp.rows(), 4);
2174 }
2175
2176 HttpResponse::Error(err) => unreachable!("{err:?}"),
2177 }
2178 }
2179 }
2180
2181 #[test]
2182 fn test_response_format_misc() {
2183 assert_eq!(ResponseFormat::default(), ResponseFormat::GreptimedbV1);
2184 assert_eq!(ResponseFormat::parse("arrow"), Some(ResponseFormat::Arrow));
2185 assert_eq!(
2186 ResponseFormat::parse("csv"),
2187 Some(ResponseFormat::Csv(false, false))
2188 );
2189 assert_eq!(
2190 ResponseFormat::parse("csvwithnames"),
2191 Some(ResponseFormat::Csv(true, false))
2192 );
2193 assert_eq!(
2194 ResponseFormat::parse("csvwithnamesandtypes"),
2195 Some(ResponseFormat::Csv(true, true))
2196 );
2197 assert_eq!(ResponseFormat::parse("table"), Some(ResponseFormat::Table));
2198 assert_eq!(
2199 ResponseFormat::parse("greptimedb_v1"),
2200 Some(ResponseFormat::GreptimedbV1)
2201 );
2202 assert_eq!(
2203 ResponseFormat::parse("influxdb_v1"),
2204 Some(ResponseFormat::InfluxdbV1)
2205 );
2206 assert_eq!(ResponseFormat::parse("json"), Some(ResponseFormat::Json));
2207 assert_eq!(ResponseFormat::parse("null"), Some(ResponseFormat::Null));
2208
2209 assert_eq!(ResponseFormat::parse("invalid"), None);
2211 assert_eq!(ResponseFormat::parse(""), None);
2212 assert_eq!(ResponseFormat::parse("CSV"), None); assert_eq!(ResponseFormat::Arrow.as_str(), "arrow");
2216 assert_eq!(ResponseFormat::Csv(false, false).as_str(), "csv");
2217 assert_eq!(ResponseFormat::Csv(true, true).as_str(), "csv");
2218 assert_eq!(ResponseFormat::Table.as_str(), "table");
2219 assert_eq!(ResponseFormat::GreptimedbV1.as_str(), "greptimedb_v1");
2220 assert_eq!(ResponseFormat::InfluxdbV1.as_str(), "influxdb_v1");
2221 assert_eq!(ResponseFormat::Json.as_str(), "json");
2222 assert_eq!(ResponseFormat::Null.as_str(), "null");
2223 assert_eq!(ResponseFormat::default().as_str(), "greptimedb_v1");
2224 }
2225
2226 #[test]
2227 fn test_decode_label_name_strict() {
2228 let strict = PromValidationMode::Strict;
2229
2230 assert!(strict.decode_label_name(b"__name__").is_ok());
2232 assert!(strict.decode_label_name(b"job").is_ok());
2233 assert!(strict.decode_label_name(b"instance").is_ok());
2234 assert!(strict.decode_label_name(b"_private").is_ok());
2235 assert!(strict.decode_label_name(b"label_with_underscores").is_ok());
2236 assert!(strict.decode_label_name(b"abc123").is_ok());
2237 assert!(strict.decode_label_name(b"A").is_ok());
2238 assert!(strict.decode_label_name(b"_").is_ok());
2239
2240 assert!(strict.decode_label_name(b"0abc").is_err());
2242 assert!(strict.decode_label_name(b"123").is_err());
2243
2244 assert!(strict.decode_label_name(b"label-name").is_err());
2246 assert!(strict.decode_label_name(b"label.name").is_err());
2247 assert!(strict.decode_label_name(b"label name").is_err());
2248 assert!(strict.decode_label_name(b"label/name").is_err());
2249
2250 assert!(strict.decode_label_name(b"").is_err());
2252
2253 assert!(strict.decode_label_name("ラベル".as_bytes()).is_err());
2255
2256 assert!(strict.decode_label_name(&[0xff, 0xfe]).is_err());
2258 }
2259
2260 #[test]
2261 fn test_decode_label_name_lossy() {
2262 let lossy = PromValidationMode::Lossy;
2263
2264 assert!(lossy.decode_label_name(b"__name__").is_ok());
2266 assert!(lossy.decode_label_name(b"label-name").is_err());
2267 assert!(lossy.decode_label_name(b"0abc").is_err());
2268
2269 assert!(lossy.decode_label_name(&[0xff, 0xfe]).is_err());
2271 }
2272
2273 #[test]
2274 fn test_decode_label_name_unchecked() {
2275 let unchecked = PromValidationMode::Unchecked;
2276
2277 assert!(unchecked.decode_label_name(b"__name__").is_ok());
2279 assert!(unchecked.decode_label_name(b"label-name").is_err());
2280 assert!(unchecked.decode_label_name(b"0abc").is_err());
2281 }
2282
2283 #[test]
2284 fn test_is_valid_prom_label_name_bytes() {
2285 assert!(validate_label_name(b"__name__"));
2286 assert!(validate_label_name(b"job"));
2287 assert!(validate_label_name(b"_"));
2288 assert!(validate_label_name(b"A"));
2289 assert!(validate_label_name(b"abc123"));
2290 assert!(validate_label_name(b"_leading_underscore"));
2291
2292 assert!(!validate_label_name(b""));
2293 assert!(!validate_label_name(b"0starts_with_digit"));
2294 assert!(!validate_label_name(b"has-dash"));
2295 assert!(!validate_label_name(b"has.dot"));
2296 assert!(!validate_label_name(b"has space"));
2297 assert!(!validate_label_name(&[0xff, 0xfe]));
2298 }
2299}