Skip to main content

vector/sources/
http_server.rs

1use std::{collections::HashMap, net::SocketAddr};
2
3use bytes::{Bytes, BytesMut};
4use chrono::Utc;
5use http::StatusCode;
6use http_serde;
7use tokio_util::codec::Decoder as _;
8use vector_lib::{
9    codecs::decoding::{DeserializerConfig, FramingConfig},
10    config::{DataType, LegacyKey, LogNamespace},
11    configurable::configurable_component,
12    lookup::{lookup_v2::OptionalValuePath, owned_value_path, path},
13    schema::Definition,
14};
15use vrl::value::{Kind, kind::Collection};
16use warp::http::HeaderMap;
17
18use crate::{
19    codecs::{Decoder, DecodingConfig},
20    common::http::{ErrorMessage, server_auth::HttpServerAuthConfig},
21    config::{
22        GenerateConfig, Resource, SourceAcknowledgementsConfig, SourceConfig, SourceContext,
23        SourceOutput,
24    },
25    event::Event,
26    http::KeepaliveConfig,
27    serde::{bool_or_struct, default_decoding},
28    sources::util::{
29        HttpSource,
30        http::{HttpMethod, add_headers, add_query_parameters},
31    },
32    tls::TlsEnableableConfig,
33};
34
35/// Configuration for the `http` source.
36#[configurable_component(source("http", "Host an HTTP endpoint to receive logs."))]
37#[configurable(metadata(deprecated))]
38#[derive(Clone, Debug)]
39pub struct HttpConfig(SimpleHttpConfig);
40
41impl GenerateConfig for HttpConfig {
42    fn generate_config() -> serde_json::Value {
43        <SimpleHttpConfig as GenerateConfig>::generate_config()
44    }
45}
46
47#[async_trait::async_trait]
48#[typetag::serde(name = "http")]
49impl SourceConfig for HttpConfig {
50    async fn build(&self, cx: SourceContext) -> vector_lib::Result<super::Source> {
51        self.0.build(cx).await
52    }
53
54    fn outputs(&self, global_log_namespace: LogNamespace) -> Vec<SourceOutput> {
55        self.0.outputs(global_log_namespace)
56    }
57
58    fn resources(&self) -> Vec<Resource> {
59        self.0.resources()
60    }
61
62    fn can_acknowledge(&self) -> bool {
63        self.0.can_acknowledge()
64    }
65}
66
67/// Configuration for the `http_server` source.
68#[configurable_component(source("http_server", "Host an HTTP endpoint to receive logs."))]
69#[derive(Clone, Debug)]
70pub struct SimpleHttpConfig {
71    /// The socket address to listen for connections on.
72    ///
73    /// It _must_ include a port.
74    #[configurable(metadata(docs::examples = "0.0.0.0:80"))]
75    #[configurable(metadata(docs::examples = "localhost:80"))]
76    address: SocketAddr,
77
78    /// A list of HTTP headers to include in the log event.
79    ///
80    /// Accepts the wildcard (`*`) character for headers matching a specified pattern.
81    ///
82    /// Specifying "*" results in all headers included in the log event.
83    ///
84    /// These headers are not included in the JSON payload if a field with a conflicting name exists.
85    #[serde(default)]
86    #[configurable(metadata(docs::examples = "User-Agent"))]
87    #[configurable(metadata(docs::examples = "X-My-Custom-Header"))]
88    #[configurable(metadata(docs::examples = "X-*"))]
89    #[configurable(metadata(docs::examples = "*"))]
90    headers: Vec<String>,
91
92    /// A list of URL query parameters to include in the log event.
93    ///
94    /// Accepts the wildcard (`*`) character for query parameters matching a specified pattern.
95    ///
96    /// Specifying "*" results in all query parameters included in the log event.
97    ///
98    /// These override any values included in the body with conflicting names.
99    #[serde(default)]
100    #[configurable(metadata(docs::examples = "application"))]
101    #[configurable(metadata(docs::examples = "source"))]
102    #[configurable(metadata(docs::examples = "param*"))]
103    #[configurable(metadata(docs::examples = "*"))]
104    query_parameters: Vec<String>,
105
106    /// HTTP authentication configuration.
107    ///
108    /// Use HTTP authentication with HTTPS only. The authentication credentials are passed as an
109    /// HTTP header without any additional encryption beyond what is provided by the transport itself.
110    ///
111    /// When using the `custom` strategy, the VRL program may write `%field = value` to enrich
112    /// authenticated events. These metadata fields are injected into the event body (legacy
113    /// namespace) or under `http_server.<field>` in event metadata (Vector namespace).
114    #[configurable(derived)]
115    auth: Option<HttpServerAuthConfig>,
116
117    /// Whether or not to treat the configured `path` as an absolute path.
118    ///
119    /// If set to `true`, only requests using the exact URL path specified in `path` are accepted. Otherwise,
120    /// requests sent to a URL path that starts with the value of `path` are accepted.
121    ///
122    /// With `strict_path` set to `false` and `path` set to `""`, the configured HTTP source accepts requests from
123    /// any URL path.
124    #[serde(default = "crate::serde::default_true")]
125    strict_path: bool,
126
127    /// The URL path on which log event POST requests are sent.
128    #[serde(default = "default_path")]
129    #[configurable(metadata(docs::examples = "/event/path"))]
130    #[configurable(metadata(docs::examples = "/logs"))]
131    path: String,
132
133    /// The event key in which the requested URL path used to send the request is stored.
134    #[serde(default = "default_path_key")]
135    #[configurable(metadata(docs::examples = "vector_http_path"))]
136    path_key: OptionalValuePath,
137
138    /// If set, the name of the log field used to add the remote IP to each event
139    #[serde(default = "default_host_key")]
140    #[configurable(metadata(docs::examples = "hostname"))]
141    host_key: OptionalValuePath,
142
143    /// Specifies the action of the HTTP request.
144    #[serde(default = "default_http_method")]
145    method: HttpMethod,
146
147    /// Specifies the HTTP response status code that will be returned on successful requests.
148    #[configurable(metadata(docs::examples = 202))]
149    #[configurable(metadata(docs::numeric_type = "uint"))]
150    #[serde(with = "http_serde::status_code")]
151    #[serde(default = "default_http_response_code")]
152    response_code: StatusCode,
153
154    #[configurable(derived)]
155    tls: Option<TlsEnableableConfig>,
156
157    #[configurable(derived)]
158    framing: Option<FramingConfig>,
159
160    #[configurable(derived)]
161    decoding: Option<DeserializerConfig>,
162
163    #[configurable(derived)]
164    #[serde(default, deserialize_with = "bool_or_struct")]
165    acknowledgements: SourceAcknowledgementsConfig,
166
167    /// The namespace to use for logs. This overrides the global setting.
168    #[configurable(metadata(docs::hidden))]
169    #[serde(default)]
170    log_namespace: Option<bool>,
171
172    #[configurable(derived)]
173    #[serde(default)]
174    keepalive: KeepaliveConfig,
175}
176
177impl SimpleHttpConfig {
178    /// Builds the `schema::Definition` for this source using the provided `LogNamespace`.
179    fn schema_definition(&self, log_namespace: LogNamespace) -> Definition {
180        let mut schema_definition = self
181            .decoding
182            .as_ref()
183            .unwrap_or(&default_decoding())
184            .schema_definition(log_namespace)
185            .with_source_metadata(
186                SimpleHttpConfig::NAME,
187                self.path_key.path.clone().map(LegacyKey::InsertIfEmpty),
188                &owned_value_path!("path"),
189                Kind::bytes(),
190                None,
191            )
192            // for metadata that is added to the events dynamically from the self.headers
193            .with_source_metadata(
194                SimpleHttpConfig::NAME,
195                None,
196                &owned_value_path!("headers"),
197                Kind::object(Collection::empty().with_unknown(Kind::bytes())).or_undefined(),
198                None,
199            )
200            // for metadata that is added to the events dynamically from the self.query_parameters
201            .with_source_metadata(
202                SimpleHttpConfig::NAME,
203                None,
204                &owned_value_path!("query_parameters"),
205                Kind::object(Collection::empty().with_unknown(Kind::bytes())).or_undefined(),
206                None,
207            )
208            .with_source_metadata(
209                SimpleHttpConfig::NAME,
210                self.host_key.path.clone().map(LegacyKey::Overwrite),
211                &owned_value_path!("host"),
212                Kind::bytes().or_undefined(),
213                None,
214            )
215            .with_standard_vector_source_metadata();
216
217        // For metadata that is added to the events dynamically from config options.
218        if log_namespace == LogNamespace::Legacy {
219            // Custom auth programs can inject any VRL value, not just bytes; widen the unknown
220            // field kind accordingly so schema-aware downstream components don't reject events.
221            let unknown_kind = if matches!(self.auth, Some(HttpServerAuthConfig::Custom { .. })) {
222                Kind::any()
223            } else {
224                Kind::bytes()
225            };
226            schema_definition = schema_definition.unknown_fields(unknown_kind);
227        }
228
229        schema_definition
230    }
231
232    fn get_decoding_config(&self) -> crate::Result<DecodingConfig> {
233        let decoding = self.decoding.clone().unwrap_or_else(default_decoding);
234        let framing = self
235            .framing
236            .clone()
237            .unwrap_or_else(|| decoding.default_stream_framing());
238
239        Ok(DecodingConfig::new(
240            framing,
241            decoding,
242            self.log_namespace.unwrap_or(false).into(),
243        ))
244    }
245}
246
247impl Default for SimpleHttpConfig {
248    fn default() -> Self {
249        Self {
250            address: "0.0.0.0:8080".parse().unwrap(),
251            headers: Vec::new(),
252            query_parameters: Vec::new(),
253            tls: None,
254            auth: None,
255            path: default_path(),
256            path_key: default_path_key(),
257            host_key: default_host_key(),
258            method: default_http_method(),
259            response_code: default_http_response_code(),
260            strict_path: true,
261            framing: None,
262            decoding: Some(default_decoding()),
263            acknowledgements: SourceAcknowledgementsConfig::default(),
264            log_namespace: None,
265            keepalive: KeepaliveConfig::default(),
266        }
267    }
268}
269
270impl_generate_config_from_default!(SimpleHttpConfig);
271
272const fn default_http_method() -> HttpMethod {
273    HttpMethod::Post
274}
275
276fn default_path() -> String {
277    "/".to_string()
278}
279
280fn default_path_key() -> OptionalValuePath {
281    OptionalValuePath::from(owned_value_path!("path"))
282}
283
284fn default_host_key() -> OptionalValuePath {
285    OptionalValuePath::none()
286}
287
288const fn default_http_response_code() -> StatusCode {
289    StatusCode::OK
290}
291
292/// Removes duplicates from the list, and logs a `warn!()` for each duplicate removed.
293pub fn remove_duplicates(mut list: Vec<String>, list_name: &str) -> Vec<String> {
294    list.sort();
295
296    let mut dedup = false;
297    for (idx, name) in list.iter().enumerate() {
298        if idx < list.len() - 1 && list[idx] == list[idx + 1] {
299            warn!(
300                "`{}` configuration contains duplicate entry for `{}`. Removing duplicate.",
301                list_name, name
302            );
303            dedup = true;
304        }
305    }
306
307    if dedup {
308        list.dedup();
309    }
310    list
311}
312
313/// Convert [`SocketAddr`] into a string, returning only the IP address.
314fn socket_addr_to_ip_string(addr: &SocketAddr) -> String {
315    addr.ip().to_string()
316}
317
318#[derive(Clone)]
319pub enum HttpConfigParamKind {
320    Glob(glob::Pattern),
321    Exact(String),
322}
323
324pub fn build_param_matcher(list: &[String]) -> crate::Result<Vec<HttpConfigParamKind>> {
325    list.iter()
326        .map(|s| match s.contains('*') {
327            true => Ok(HttpConfigParamKind::Glob(glob::Pattern::new(s)?)),
328            false => Ok(HttpConfigParamKind::Exact(s.to_string())),
329        })
330        .collect::<crate::Result<Vec<HttpConfigParamKind>>>()
331}
332
333#[async_trait::async_trait]
334#[typetag::serde(name = "http_server")]
335impl SourceConfig for SimpleHttpConfig {
336    async fn build(&self, cx: SourceContext) -> crate::Result<super::Source> {
337        let log_namespace = cx.log_namespace(self.log_namespace);
338        let decoder = self
339            .get_decoding_config()?
340            .build()?
341            .with_log_namespace(log_namespace);
342
343        let source = SimpleHttpSource {
344            headers: build_param_matcher(&remove_duplicates(self.headers.clone(), "headers"))?,
345            query_parameters: build_param_matcher(&remove_duplicates(
346                self.query_parameters.clone(),
347                "query_parameters",
348            ))?,
349            path_key: self.path_key.clone(),
350            host_key: self.host_key.clone(),
351            decoder,
352            log_namespace,
353        };
354        source.run(
355            self.address,
356            self.path.as_str(),
357            self.method,
358            self.response_code,
359            self.strict_path,
360            self.tls.as_ref(),
361            self.auth.as_ref(),
362            cx,
363            self.acknowledgements,
364            self.keepalive.clone(),
365        )
366    }
367
368    fn outputs(&self, global_log_namespace: LogNamespace) -> Vec<SourceOutput> {
369        // There is a global and per-source `log_namespace` config.
370        // The source config overrides the global setting and is merged here.
371        let log_namespace = global_log_namespace.merge(self.log_namespace);
372
373        let schema_definition = self.schema_definition(log_namespace);
374
375        vec![SourceOutput::new_maybe_logs(
376            self.decoding
377                .as_ref()
378                .map(|d| d.output_type())
379                .unwrap_or(DataType::Log),
380            schema_definition,
381        )]
382    }
383
384    fn resources(&self) -> Vec<Resource> {
385        vec![Resource::tcp(self.address)]
386    }
387
388    fn can_acknowledge(&self) -> bool {
389        true
390    }
391}
392
393#[derive(Clone)]
394struct SimpleHttpSource {
395    headers: Vec<HttpConfigParamKind>,
396    query_parameters: Vec<HttpConfigParamKind>,
397    path_key: OptionalValuePath,
398    host_key: OptionalValuePath,
399    decoder: Decoder,
400    log_namespace: LogNamespace,
401}
402
403impl HttpSource for SimpleHttpSource {
404    fn log_namespace(&self) -> LogNamespace {
405        self.log_namespace
406    }
407
408    fn name() -> &'static str {
409        SimpleHttpConfig::NAME
410    }
411
412    /// Enriches the log events with metadata for the `request_path` and for each of the headers.
413    /// Non-log events are skipped.
414    fn enrich_events(
415        &self,
416        events: &mut [Event],
417        request_path: &str,
418        headers: &HeaderMap,
419        query_parameters: &HashMap<String, String>,
420        source_ip: Option<&SocketAddr>,
421    ) {
422        let now = Utc::now();
423        for event in events.iter_mut() {
424            match event {
425                Event::Log(log) => {
426                    // add request_path to each event
427                    self.log_namespace.insert_source_metadata(
428                        SimpleHttpConfig::NAME,
429                        log,
430                        self.path_key.path.as_ref().map(LegacyKey::InsertIfEmpty),
431                        path!("path"),
432                        request_path.to_owned(),
433                    );
434
435                    self.log_namespace.insert_standard_vector_source_metadata(
436                        log,
437                        SimpleHttpConfig::NAME,
438                        now,
439                    );
440
441                    if let Some(addr) = source_ip {
442                        self.log_namespace.insert_source_metadata(
443                            SimpleHttpConfig::NAME,
444                            log,
445                            self.host_key.path.as_ref().map(LegacyKey::Overwrite),
446                            path!("host"),
447                            socket_addr_to_ip_string(addr),
448                        );
449                    }
450                }
451                _ => {
452                    continue;
453                }
454            }
455        }
456
457        add_headers(
458            events,
459            &self.headers,
460            headers,
461            self.log_namespace,
462            SimpleHttpConfig::NAME,
463        );
464
465        add_query_parameters(
466            events,
467            &self.query_parameters,
468            query_parameters,
469            self.log_namespace,
470            SimpleHttpConfig::NAME,
471        );
472    }
473
474    fn build_events(
475        &self,
476        body: Bytes,
477        _header_map: &HeaderMap,
478        _query_parameters: &HashMap<String, String>,
479        _request_path: &str,
480    ) -> Result<Vec<Event>, ErrorMessage> {
481        let mut decoder = self.decoder.clone();
482        let mut events = Vec::new();
483        let mut bytes = BytesMut::new();
484        bytes.extend_from_slice(&body);
485
486        loop {
487            match decoder.decode_eof(&mut bytes) {
488                Ok(Some((next, _))) => {
489                    events.extend(next);
490                }
491                Ok(None) => break,
492                Err(error) => {
493                    // Error is logged / emitted by `vector_lib::codecs::Decoder`, no further
494                    // handling is needed here
495                    return Err(ErrorMessage::new(
496                        StatusCode::BAD_REQUEST,
497                        format!("Failed decoding body: {error}"),
498                    ));
499                }
500            }
501        }
502
503        Ok(events)
504    }
505
506    fn enable_source_ip(&self) -> bool {
507        self.host_key.path.is_some()
508    }
509}
510
511#[cfg(test)]
512mod tests {
513    use std::{io::Write, net::SocketAddr, str::FromStr};
514
515    use flate2::{
516        Compression,
517        write::{GzEncoder, ZlibEncoder},
518    };
519    use futures::Stream;
520    use headers::{Authorization, authorization::Credentials};
521    use http::{HeaderMap, Method, StatusCode, Uri, header::AUTHORIZATION};
522    use similar_asserts::assert_eq;
523    use vector_lib::{
524        codecs::{
525            BytesDecoderConfig, JsonDeserializerConfig,
526            decoding::{DeserializerConfig, FramingConfig},
527        },
528        config::LogNamespace,
529        event::LogEvent,
530        lookup::{
531            OwnedTargetPath, PathPrefix, event_path, lookup_v2::OptionalValuePath,
532            owned_value_path, path,
533        },
534        schema::Definition,
535    };
536    use vrl::{
537        path::ValuePath as _,
538        value::{Kind, ObjectMap, kind::Collection},
539    };
540
541    use super::{SimpleHttpConfig, remove_duplicates};
542    use crate::{
543        SourceSender,
544        common::http::server_auth::HttpServerAuthConfig,
545        components::validation::prelude::*,
546        config::{SourceConfig, SourceContext, log_schema},
547        event::{Event, EventStatus, Value},
548        sources::http_server::HttpMethod,
549        test_util::{
550            addr::next_addr,
551            components::{self, HTTP_PUSH_SOURCE_TAGS, assert_source_compliance},
552            spawn_collect_n, wait_for_tcp,
553        },
554    };
555
556    #[test]
557    fn generate_config() {
558        crate::test_util::test_generate_config::<SimpleHttpConfig>();
559    }
560
561    #[allow(clippy::too_many_arguments)]
562    async fn source<'a>(
563        headers: Vec<String>,
564        query_parameters: Vec<String>,
565        path_key: &'a str,
566        host_key: &'a str,
567        path: &'a str,
568        method: &'a str,
569        response_code: StatusCode,
570        auth: Option<HttpServerAuthConfig>,
571        strict_path: bool,
572        status: EventStatus,
573        acknowledgements: bool,
574        framing: Option<FramingConfig>,
575        decoding: Option<DeserializerConfig>,
576    ) -> (impl Stream<Item = Event> + 'a, SocketAddr) {
577        let (sender, recv) = SourceSender::new_test_finalize(status);
578        let (_guard, address) = next_addr();
579        let path = path.to_owned();
580        let host_key = OptionalValuePath::from(owned_value_path!(host_key));
581        let path_key = OptionalValuePath::from(owned_value_path!(path_key));
582        let context = SourceContext::new_test(sender, None);
583        let method = match Method::from_str(method).unwrap() {
584            Method::GET => HttpMethod::Get,
585            Method::POST => HttpMethod::Post,
586            _ => HttpMethod::Post,
587        };
588
589        tokio::spawn(async move {
590            SimpleHttpConfig {
591                address,
592                headers,
593                query_parameters,
594                response_code,
595                tls: None,
596                auth,
597                strict_path,
598                path_key,
599                host_key,
600                path,
601                method,
602                framing,
603                decoding,
604                acknowledgements: acknowledgements.into(),
605                log_namespace: None,
606                keepalive: Default::default(),
607            }
608            .build(context)
609            .await
610            .unwrap()
611            .await
612            .unwrap();
613        });
614        wait_for_tcp(address).await;
615        (recv, address)
616    }
617
618    async fn send(address: SocketAddr, body: &str) -> u16 {
619        reqwest::Client::new()
620            .post(format!("http://{address}/"))
621            .body(body.to_owned())
622            .send()
623            .await
624            .unwrap()
625            .status()
626            .as_u16()
627    }
628
629    async fn send_with_headers(address: SocketAddr, body: &str, headers: HeaderMap) -> u16 {
630        reqwest::Client::new()
631            .post(format!("http://{address}/"))
632            .headers(headers)
633            .body(body.to_owned())
634            .send()
635            .await
636            .unwrap()
637            .status()
638            .as_u16()
639    }
640
641    async fn send_with_query(address: SocketAddr, body: &str, query: &str) -> u16 {
642        reqwest::Client::new()
643            .post(format!("http://{address}?{query}"))
644            .body(body.to_owned())
645            .send()
646            .await
647            .unwrap()
648            .status()
649            .as_u16()
650    }
651
652    async fn send_with_path(address: SocketAddr, body: &str, path: &str) -> u16 {
653        reqwest::Client::new()
654            .post(format!("http://{address}{path}"))
655            .body(body.to_owned())
656            .send()
657            .await
658            .unwrap()
659            .status()
660            .as_u16()
661    }
662
663    async fn send_request(address: SocketAddr, method: &str, body: &str, path: &str) -> u16 {
664        let method = Method::from_bytes(method.to_owned().as_bytes()).unwrap();
665        reqwest::Client::new()
666            .request(method, format!("http://{address}{path}"))
667            .body(body.to_owned())
668            .send()
669            .await
670            .unwrap()
671            .status()
672            .as_u16()
673    }
674
675    async fn send_bytes(address: SocketAddr, body: Vec<u8>, headers: HeaderMap) -> u16 {
676        reqwest::Client::new()
677            .post(format!("http://{address}/"))
678            .headers(headers)
679            .body(body)
680            .send()
681            .await
682            .unwrap()
683            .status()
684            .as_u16()
685    }
686
687    async fn spawn_ok_collect_n(
688        send: impl std::future::Future<Output = u16> + Send + 'static,
689        rx: impl Stream<Item = Event> + Unpin,
690        n: usize,
691    ) -> Vec<Event> {
692        spawn_collect_n(async move { assert_eq!(200, send.await) }, rx, n).await
693    }
694
695    #[tokio::test]
696    async fn http_multiline_text() {
697        let mut events = assert_source_compliance(&HTTP_PUSH_SOURCE_TAGS, async move {
698            let body = "test body\ntest body 2";
699
700            let (rx, addr) = source(
701                vec![],
702                vec![],
703                "http_path",
704                "remote_ip",
705                "/",
706                "POST",
707                StatusCode::OK,
708                None,
709                true,
710                EventStatus::Delivered,
711                true,
712                None,
713                None,
714            )
715            .await;
716
717            spawn_ok_collect_n(send(addr, body), rx, 2).await
718        })
719        .await;
720
721        {
722            let event = events.remove(0);
723            let log = event.as_log();
724            assert_eq!(*log.get_message().unwrap(), "test body".into());
725            assert!(log.get_timestamp().is_some());
726            assert_eq!(
727                *log.get_source_type().unwrap(),
728                SimpleHttpConfig::NAME.into()
729            );
730            assert_eq!(log["http_path"], "/".into());
731            assert_event_metadata(log).await;
732        }
733        {
734            let event = events.remove(0);
735            let log = event.as_log();
736            assert_eq!(*log.get_message().unwrap(), "test body 2".into());
737            assert_event_metadata(log).await;
738        }
739    }
740
741    #[tokio::test]
742    async fn http_multiline_text2() {
743        //same as above test but with a newline at the end
744        let body = "test body\ntest body 2\n";
745
746        let mut events = assert_source_compliance(&HTTP_PUSH_SOURCE_TAGS, async move {
747            let (rx, addr) = source(
748                vec![],
749                vec![],
750                "http_path",
751                "remote_ip",
752                "/",
753                "POST",
754                StatusCode::OK,
755                None,
756                true,
757                EventStatus::Delivered,
758                true,
759                None,
760                None,
761            )
762            .await;
763
764            spawn_ok_collect_n(send(addr, body), rx, 2).await
765        })
766        .await;
767
768        {
769            let event = events.remove(0);
770            let log = event.as_log();
771            assert_eq!(*log.get_message().unwrap(), "test body".into());
772            assert_event_metadata(log).await;
773        }
774        {
775            let event = events.remove(0);
776            let log = event.as_log();
777            assert_eq!(*log.get_message().unwrap(), "test body 2".into());
778            assert_event_metadata(log).await;
779        }
780    }
781
782    #[tokio::test]
783    async fn http_bytes_codec_preserves_newlines() {
784        let body = "foo\nbar";
785
786        let mut events = assert_source_compliance(&HTTP_PUSH_SOURCE_TAGS, async move {
787            let (rx, addr) = source(
788                vec![],
789                vec![],
790                "http_path",
791                "remote_ip",
792                "/",
793                "POST",
794                StatusCode::OK,
795                None,
796                true,
797                EventStatus::Delivered,
798                true,
799                Some(BytesDecoderConfig::new().into()),
800                None,
801            )
802            .await;
803
804            spawn_ok_collect_n(send(addr, body), rx, 1).await
805        })
806        .await;
807
808        assert_eq!(events.len(), 1);
809
810        {
811            let event = events.remove(0);
812            let log = event.as_log();
813            assert_eq!(*log.get_message().unwrap(), "foo\nbar".into());
814            assert_event_metadata(log).await;
815        }
816    }
817
818    #[tokio::test]
819    async fn http_json_parsing() {
820        let mut events = assert_source_compliance(&HTTP_PUSH_SOURCE_TAGS, async {
821            let (rx, addr) = source(
822                vec![],
823                vec![],
824                "http_path",
825                "remote_ip",
826                "/",
827                "POST",
828                StatusCode::OK,
829                None,
830                true,
831                EventStatus::Delivered,
832                true,
833                None,
834                Some(JsonDeserializerConfig::default().into()),
835            )
836            .await;
837
838            spawn_collect_n(
839                async move {
840                    assert_eq!(400, send(addr, "{").await); //malformed
841                    assert_eq!(400, send(addr, r#"{"key"}"#).await); //key without value
842
843                    assert_eq!(200, send(addr, "{}").await); //can be one object or array of objects
844                    assert_eq!(200, send(addr, "[{},{},{}]").await);
845                },
846                rx,
847                2,
848            )
849            .await
850        })
851        .await;
852
853        assert!(events.remove(1).as_log().get_timestamp().is_some());
854        assert!(events.remove(0).as_log().get_timestamp().is_some());
855    }
856
857    #[tokio::test]
858    async fn http_json_values() {
859        let mut events = assert_source_compliance(&HTTP_PUSH_SOURCE_TAGS, async {
860            let (rx, addr) = source(
861                vec![],
862                vec![],
863                "http_path",
864                "remote_ip",
865                "/",
866                "POST",
867                StatusCode::OK,
868                None,
869                true,
870                EventStatus::Delivered,
871                true,
872                None,
873                Some(JsonDeserializerConfig::default().into()),
874            )
875            .await;
876
877            spawn_collect_n(
878                async move {
879                    assert_eq!(200, send(addr, r#"[{"key":"value"}]"#).await);
880                    assert_eq!(200, send(addr, r#"{"key2":"value2"}"#).await);
881                },
882                rx,
883                2,
884            )
885            .await
886        })
887        .await;
888
889        {
890            let event = events.remove(0);
891            let log = event.as_log();
892            assert_eq!(log["key"], "value".into());
893            assert_event_metadata(log).await;
894        }
895        {
896            let event = events.remove(0);
897            let log = event.as_log();
898            assert_eq!(log["key2"], "value2".into());
899            assert_event_metadata(log).await;
900        }
901    }
902
903    #[tokio::test]
904    async fn http_json_dotted_keys() {
905        let mut events = assert_source_compliance(&HTTP_PUSH_SOURCE_TAGS, async {
906            let (rx, addr) = source(
907                vec![],
908                vec![],
909                "http_path",
910                "remote_ip",
911                "/",
912                "POST",
913                StatusCode::OK,
914                None,
915                true,
916                EventStatus::Delivered,
917                true,
918                None,
919                Some(JsonDeserializerConfig::default().into()),
920            )
921            .await;
922
923            spawn_collect_n(
924                async move {
925                    assert_eq!(200, send(addr, r#"[{"dotted.key":"value"}]"#).await);
926                    assert_eq!(
927                        200,
928                        send(addr, r#"{"nested":{"dotted.key2":"value2"}}"#).await
929                    );
930                },
931                rx,
932                2,
933            )
934            .await
935        })
936        .await;
937
938        {
939            let event = events.remove(0);
940            let log = event.as_log();
941            assert_eq!(
942                log.get(event_path!("dotted.key")).unwrap(),
943                &Value::from("value")
944            );
945        }
946        {
947            let event = events.remove(0);
948            let log = event.as_log();
949            let mut map = ObjectMap::new();
950            map.insert("dotted.key2".into(), Value::from("value2"));
951            assert_eq!(log["nested"], map.into());
952        }
953    }
954
955    #[tokio::test]
956    async fn http_ndjson() {
957        let mut events = assert_source_compliance(&HTTP_PUSH_SOURCE_TAGS, async {
958            let (rx, addr) = source(
959                vec![],
960                vec![],
961                "http_path",
962                "remote_ip",
963                "/",
964                "POST",
965                StatusCode::OK,
966                None,
967                true,
968                EventStatus::Delivered,
969                true,
970                None,
971                Some(JsonDeserializerConfig::default().into()),
972            )
973            .await;
974
975            spawn_collect_n(
976                async move {
977                    assert_eq!(
978                        200,
979                        send(addr, r#"[{"key1":"value1"},{"key2":"value2"}]"#).await
980                    );
981
982                    assert_eq!(
983                        200,
984                        send(addr, "{\"key1\":\"value1\"}\n\n{\"key2\":\"value2\"}").await
985                    );
986                },
987                rx,
988                4,
989            )
990            .await
991        })
992        .await;
993
994        {
995            let event = events.remove(0);
996            let log = event.as_log();
997            assert_eq!(log["key1"], "value1".into());
998            assert_event_metadata(log).await;
999        }
1000        {
1001            let event = events.remove(0);
1002            let log = event.as_log();
1003            assert_eq!(log["key2"], "value2".into());
1004            assert_event_metadata(log).await;
1005        }
1006        {
1007            let event = events.remove(0);
1008            let log = event.as_log();
1009            assert_eq!(log["key1"], "value1".into());
1010            assert_event_metadata(log).await;
1011        }
1012        {
1013            let event = events.remove(0);
1014            let log = event.as_log();
1015            assert_eq!(log["key2"], "value2".into());
1016            assert_event_metadata(log).await;
1017        }
1018    }
1019
1020    async fn assert_event_metadata(log: &LogEvent) {
1021        assert!(log.get_timestamp().is_some());
1022
1023        let source_type_key_value = log
1024            .get((PathPrefix::Event, log_schema().source_type_key().unwrap()))
1025            .unwrap()
1026            .as_str()
1027            .unwrap();
1028        assert_eq!(source_type_key_value, SimpleHttpConfig::NAME);
1029        assert_eq!(log["http_path"], "/".into());
1030    }
1031
1032    #[tokio::test]
1033    async fn http_headers() {
1034        let mut events = assert_source_compliance(&HTTP_PUSH_SOURCE_TAGS, async {
1035            let mut headers = HeaderMap::new();
1036            headers.insert("User-Agent", "test_client".parse().unwrap());
1037            headers.insert("Upgrade-Insecure-Requests", "false".parse().unwrap());
1038            headers.insert("X-Test-Header", "true".parse().unwrap());
1039
1040            let (rx, addr) = source(
1041                vec![
1042                    "User-Agent".to_string(),
1043                    "Upgrade-Insecure-Requests".to_string(),
1044                    "X-*".to_string(),
1045                    "AbsentHeader".to_string(),
1046                ],
1047                vec![],
1048                "http_path",
1049                "remote_ip",
1050                "/",
1051                "POST",
1052                StatusCode::OK,
1053                None,
1054                true,
1055                EventStatus::Delivered,
1056                true,
1057                None,
1058                Some(JsonDeserializerConfig::default().into()),
1059            )
1060            .await;
1061
1062            spawn_ok_collect_n(
1063                send_with_headers(addr, "{\"key1\":\"value1\"}", headers),
1064                rx,
1065                1,
1066            )
1067            .await
1068        })
1069        .await;
1070
1071        {
1072            let event = events.remove(0);
1073            let log = event.as_log();
1074            assert_eq!(log["key1"], "value1".into());
1075            assert_eq!(log["\"User-Agent\""], "test_client".into());
1076            assert_eq!(log["\"Upgrade-Insecure-Requests\""], "false".into());
1077            assert_eq!(log["\"x-test-header\""], "true".into());
1078            assert_eq!(log["AbsentHeader"], Value::Null);
1079            assert_event_metadata(log).await;
1080        }
1081    }
1082
1083    #[tokio::test]
1084    async fn http_headers_wildcard() {
1085        let mut events = assert_source_compliance(&HTTP_PUSH_SOURCE_TAGS, async {
1086            let mut headers = HeaderMap::new();
1087            headers.insert("User-Agent", "test_client".parse().unwrap());
1088            headers.insert("X-Case-Sensitive-Value", "CaseSensitive".parse().unwrap());
1089            // Header that conflicts with an existing field.
1090            headers.insert("key1", "value_from_header".parse().unwrap());
1091
1092            let (rx, addr) = source(
1093                vec!["*".to_string()],
1094                vec![],
1095                "http_path",
1096                "remote_ip",
1097                "/",
1098                "POST",
1099                StatusCode::OK,
1100                None,
1101                true,
1102                EventStatus::Delivered,
1103                true,
1104                None,
1105                Some(JsonDeserializerConfig::default().into()),
1106            )
1107            .await;
1108
1109            spawn_ok_collect_n(
1110                send_with_headers(addr, "{\"key1\":\"value1\"}", headers),
1111                rx,
1112                1,
1113            )
1114            .await
1115        })
1116        .await;
1117
1118        {
1119            let event = events.remove(0);
1120            let log = event.as_log();
1121            assert_eq!(log["key1"], "value1".into());
1122            assert_eq!(log["\"user-agent\""], "test_client".into());
1123            assert_eq!(log["\"x-case-sensitive-value\""], "CaseSensitive".into());
1124            assert_event_metadata(log).await;
1125        }
1126    }
1127
1128    #[tokio::test]
1129    async fn http_query() {
1130        let mut events = assert_source_compliance(&HTTP_PUSH_SOURCE_TAGS, async {
1131            let (rx, addr) = source(
1132                vec![],
1133                vec![
1134                    "source".to_string(),
1135                    "region".to_string(),
1136                    "absent".to_string(),
1137                ],
1138                "http_path",
1139                "remote_ip",
1140                "/",
1141                "POST",
1142                StatusCode::OK,
1143                None,
1144                true,
1145                EventStatus::Delivered,
1146                true,
1147                None,
1148                Some(JsonDeserializerConfig::default().into()),
1149            )
1150            .await;
1151
1152            spawn_ok_collect_n(
1153                send_with_query(addr, "{\"key1\":\"value1\"}", "source=staging&region=gb"),
1154                rx,
1155                1,
1156            )
1157            .await
1158        })
1159        .await;
1160
1161        {
1162            let event = events.remove(0);
1163            let log = event.as_log();
1164            assert_eq!(log["key1"], "value1".into());
1165            assert_eq!(log["source"], "staging".into());
1166            assert_eq!(log["region"], "gb".into());
1167            assert_eq!(log["absent"], Value::Null);
1168            assert_event_metadata(log).await;
1169        }
1170    }
1171
1172    #[tokio::test]
1173    async fn http_query_wildcard() {
1174        let mut events = assert_source_compliance(&HTTP_PUSH_SOURCE_TAGS, async {
1175            let (rx, addr) = source(
1176                vec![],
1177                vec!["*".to_string()],
1178                "http_path",
1179                "remote_ip",
1180                "/",
1181                "POST",
1182                StatusCode::OK,
1183                None,
1184                true,
1185                EventStatus::Delivered,
1186                true,
1187                None,
1188                Some(JsonDeserializerConfig::default().into()),
1189            )
1190            .await;
1191
1192            spawn_ok_collect_n(
1193                send_with_query(
1194                    addr,
1195                    "{\"key1\":\"value1\",\"key2\":\"value2\"}",
1196                    "source=staging&region=gb&key1=value_from_query",
1197                ),
1198                rx,
1199                1,
1200            )
1201            .await
1202        })
1203        .await;
1204
1205        {
1206            let event = events.remove(0);
1207            let log = event.as_log();
1208            assert_eq!(log["key1"], "value_from_query".into());
1209            assert_eq!(log["key2"], "value2".into());
1210            assert_eq!(log["source"], "staging".into());
1211            assert_eq!(log["region"], "gb".into());
1212            assert_event_metadata(log).await;
1213        }
1214    }
1215
1216    #[tokio::test]
1217    async fn http_gzip_deflate() {
1218        let mut events = assert_source_compliance(&HTTP_PUSH_SOURCE_TAGS, async {
1219            let body = "test body";
1220
1221            let mut encoder = GzEncoder::new(Vec::new(), Compression::default());
1222            encoder.write_all(body.as_bytes()).unwrap();
1223            let body = encoder.finish().unwrap();
1224
1225            let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
1226            encoder.write_all(body.as_slice()).unwrap();
1227            let body = encoder.finish().unwrap();
1228
1229            let mut headers = HeaderMap::new();
1230            headers.insert("Content-Encoding", "gzip, deflate".parse().unwrap());
1231
1232            let (rx, addr) = source(
1233                vec![],
1234                vec![],
1235                "http_path",
1236                "remote_ip",
1237                "/",
1238                "POST",
1239                StatusCode::OK,
1240                None,
1241                true,
1242                EventStatus::Delivered,
1243                true,
1244                None,
1245                None,
1246            )
1247            .await;
1248
1249            spawn_ok_collect_n(send_bytes(addr, body, headers), rx, 1).await
1250        })
1251        .await;
1252
1253        {
1254            let event = events.remove(0);
1255            let log = event.as_log();
1256            assert_eq!(*log.get_message().unwrap(), "test body".into());
1257            assert_event_metadata(log).await;
1258        }
1259    }
1260
1261    #[tokio::test]
1262    async fn http_rejects_gzip_bomb_with_413() {
1263        // A modestly-sized gzipped blob of zeros that would expand past the default
1264        // 100 MiB cap if decompression were unbounded.
1265        let mut encoder = GzEncoder::new(Vec::new(), Compression::default());
1266        let chunk = [0u8; 8 * 1024];
1267        for _ in 0..(200 * 1024 * 1024 / chunk.len()) {
1268            encoder.write_all(&chunk).unwrap();
1269        }
1270        let body = encoder.finish().unwrap();
1271
1272        let mut headers = HeaderMap::new();
1273        headers.insert("Content-Encoding", "gzip".parse().unwrap());
1274
1275        components::init_test();
1276        let (_rx, addr) = source(
1277            vec![],
1278            vec![],
1279            "http_path",
1280            "remote_ip",
1281            "/",
1282            "POST",
1283            StatusCode::OK,
1284            None,
1285            true,
1286            EventStatus::Delivered,
1287            true,
1288            None,
1289            None,
1290        )
1291        .await;
1292
1293        assert_eq!(413, send_bytes(addr, body, headers).await);
1294    }
1295
1296    #[tokio::test]
1297    async fn http_path() {
1298        let mut events = assert_source_compliance(&HTTP_PUSH_SOURCE_TAGS, async {
1299            let (rx, addr) = source(
1300                vec![],
1301                vec![],
1302                "vector_http_path",
1303                "vector_remote_ip",
1304                "/event/path",
1305                "POST",
1306                StatusCode::OK,
1307                None,
1308                true,
1309                EventStatus::Delivered,
1310                true,
1311                None,
1312                Some(JsonDeserializerConfig::default().into()),
1313            )
1314            .await;
1315
1316            spawn_ok_collect_n(
1317                send_with_path(addr, "{\"key1\":\"value1\"}", "/event/path"),
1318                rx,
1319                1,
1320            )
1321            .await
1322        })
1323        .await;
1324
1325        {
1326            let event = events.remove(0);
1327            let log = event.as_log();
1328            assert_eq!(log["key1"], "value1".into());
1329            assert_eq!(log["vector_http_path"], "/event/path".into());
1330            assert!(log.get_timestamp().is_some());
1331            assert_eq!(
1332                *log.get_source_type().unwrap(),
1333                SimpleHttpConfig::NAME.into()
1334            );
1335        }
1336    }
1337
1338    #[tokio::test]
1339    async fn http_path_no_restriction() {
1340        let mut events = assert_source_compliance(&HTTP_PUSH_SOURCE_TAGS, async {
1341            let (rx, addr) = source(
1342                vec![],
1343                vec![],
1344                "vector_http_path",
1345                "vector_remote_ip",
1346                "/event",
1347                "POST",
1348                StatusCode::OK,
1349                None,
1350                false,
1351                EventStatus::Delivered,
1352                true,
1353                None,
1354                Some(JsonDeserializerConfig::default().into()),
1355            )
1356            .await;
1357
1358            spawn_collect_n(
1359                async move {
1360                    assert_eq!(
1361                        200,
1362                        send_with_path(addr, "{\"key1\":\"value1\"}", "/event/path1").await
1363                    );
1364                    assert_eq!(
1365                        200,
1366                        send_with_path(addr, "{\"key2\":\"value2\"}", "/event/path2").await
1367                    );
1368                },
1369                rx,
1370                2,
1371            )
1372            .await
1373        })
1374        .await;
1375
1376        {
1377            let event = events.remove(0);
1378            let log = event.as_log();
1379            assert_eq!(log["key1"], "value1".into());
1380            assert_eq!(log["vector_http_path"], "/event/path1".into());
1381            assert!(log.get_timestamp().is_some());
1382            assert_eq!(
1383                *log.get_source_type().unwrap(),
1384                SimpleHttpConfig::NAME.into()
1385            );
1386        }
1387        {
1388            let event = events.remove(0);
1389            let log = event.as_log();
1390            assert_eq!(log["key2"], "value2".into());
1391            assert_eq!(log["vector_http_path"], "/event/path2".into());
1392            assert!(log.get_timestamp().is_some());
1393            assert_eq!(
1394                *log.get_source_type().unwrap(),
1395                SimpleHttpConfig::NAME.into()
1396            );
1397        }
1398    }
1399
1400    #[tokio::test]
1401    async fn http_wrong_path() {
1402        components::init_test();
1403        let (_rx, addr) = source(
1404            vec![],
1405            vec![],
1406            "vector_http_path",
1407            "vector_remote_ip",
1408            "/",
1409            "POST",
1410            StatusCode::OK,
1411            None,
1412            true,
1413            EventStatus::Delivered,
1414            true,
1415            None,
1416            Some(JsonDeserializerConfig::default().into()),
1417        )
1418        .await;
1419
1420        assert_eq!(
1421            404,
1422            send_with_path(addr, "{\"key1\":\"value1\"}", "/event/path").await
1423        );
1424    }
1425
1426    #[tokio::test]
1427    async fn http_status_code() {
1428        assert_source_compliance(&HTTP_PUSH_SOURCE_TAGS, async move {
1429            let (rx, addr) = source(
1430                vec![],
1431                vec![],
1432                "http_path",
1433                "remote_ip",
1434                "/",
1435                "POST",
1436                StatusCode::ACCEPTED,
1437                None,
1438                true,
1439                EventStatus::Delivered,
1440                true,
1441                None,
1442                None,
1443            )
1444            .await;
1445
1446            spawn_collect_n(
1447                async move {
1448                    assert_eq!(
1449                        StatusCode::ACCEPTED,
1450                        send(addr, "{\"key1\":\"value1\"}").await
1451                    );
1452                },
1453                rx,
1454                1,
1455            )
1456            .await;
1457        })
1458        .await;
1459    }
1460
1461    #[tokio::test]
1462    async fn http_delivery_failure() {
1463        assert_source_compliance(&HTTP_PUSH_SOURCE_TAGS, async {
1464            let (rx, addr) = source(
1465                vec![],
1466                vec![],
1467                "http_path",
1468                "remote_ip",
1469                "/",
1470                "POST",
1471                StatusCode::OK,
1472                None,
1473                true,
1474                EventStatus::Rejected,
1475                true,
1476                None,
1477                None,
1478            )
1479            .await;
1480
1481            spawn_collect_n(
1482                async move {
1483                    assert_eq!(400, send(addr, "test body\n").await);
1484                },
1485                rx,
1486                1,
1487            )
1488            .await;
1489        })
1490        .await;
1491    }
1492
1493    #[tokio::test]
1494    async fn ignores_disabled_acknowledgements() {
1495        let events = assert_source_compliance(&HTTP_PUSH_SOURCE_TAGS, async {
1496            let (rx, addr) = source(
1497                vec![],
1498                vec![],
1499                "http_path",
1500                "remote_ip",
1501                "/",
1502                "POST",
1503                StatusCode::OK,
1504                None,
1505                true,
1506                EventStatus::Rejected,
1507                false,
1508                None,
1509                None,
1510            )
1511            .await;
1512
1513            spawn_collect_n(
1514                async move {
1515                    assert_eq!(200, send(addr, "test body\n").await);
1516                },
1517                rx,
1518                1,
1519            )
1520            .await
1521        })
1522        .await;
1523
1524        assert_eq!(events.len(), 1);
1525    }
1526
1527    #[tokio::test]
1528    async fn http_get_method() {
1529        components::init_test();
1530        let (_rx, addr) = source(
1531            vec![],
1532            vec![],
1533            "http_path",
1534            "remote_ip",
1535            "/",
1536            "GET",
1537            StatusCode::OK,
1538            None,
1539            true,
1540            EventStatus::Delivered,
1541            true,
1542            None,
1543            None,
1544        )
1545        .await;
1546
1547        assert_eq!(200, send_request(addr, "GET", "", "/").await);
1548    }
1549
1550    #[tokio::test]
1551    async fn returns_401_when_required_auth_is_missing() {
1552        components::init_test();
1553        let (_rx, addr) = source(
1554            vec![],
1555            vec![],
1556            "http_path",
1557            "remote_ip",
1558            "/",
1559            "GET",
1560            StatusCode::OK,
1561            Some(HttpServerAuthConfig::Basic {
1562                username: "test".to_string(),
1563                password: "test".to_string().into(),
1564            }),
1565            true,
1566            EventStatus::Delivered,
1567            true,
1568            None,
1569            None,
1570        )
1571        .await;
1572
1573        assert_eq!(401, send_request(addr, "GET", "", "/").await);
1574    }
1575
1576    #[tokio::test]
1577    async fn returns_401_when_required_auth_is_wrong() {
1578        components::init_test();
1579        let (_rx, addr) = source(
1580            vec![],
1581            vec![],
1582            "http_path",
1583            "remote_ip",
1584            "/",
1585            "POST",
1586            StatusCode::OK,
1587            Some(HttpServerAuthConfig::Basic {
1588                username: "test".to_string(),
1589                password: "test".to_string().into(),
1590            }),
1591            true,
1592            EventStatus::Delivered,
1593            true,
1594            None,
1595            None,
1596        )
1597        .await;
1598
1599        let mut headers = HeaderMap::new();
1600        headers.insert(
1601            AUTHORIZATION,
1602            Authorization::basic("wrong", "test").0.encode(),
1603        );
1604        assert_eq!(401, send_with_headers(addr, "", headers).await);
1605    }
1606
1607    #[tokio::test]
1608    async fn http_get_with_correct_auth() {
1609        components::init_test();
1610        let (_rx, addr) = source(
1611            vec![],
1612            vec![],
1613            "http_path",
1614            "remote_ip",
1615            "/",
1616            "POST",
1617            StatusCode::OK,
1618            Some(HttpServerAuthConfig::Basic {
1619                username: "test".to_string(),
1620                password: "test".to_string().into(),
1621            }),
1622            true,
1623            EventStatus::Delivered,
1624            true,
1625            None,
1626            None,
1627        )
1628        .await;
1629
1630        let mut headers = HeaderMap::new();
1631        headers.insert(
1632            AUTHORIZATION,
1633            Authorization::basic("test", "test").0.encode(),
1634        );
1635        assert_eq!(200, send_with_headers(addr, "", headers).await);
1636    }
1637
1638    #[test]
1639    fn output_schema_definition_vector_namespace() {
1640        let config = SimpleHttpConfig {
1641            log_namespace: Some(true),
1642            ..Default::default()
1643        };
1644
1645        let definitions = config
1646            .outputs(LogNamespace::Vector)
1647            .remove(0)
1648            .schema_definition(true);
1649
1650        let expected_definition =
1651            Definition::new_with_default_metadata(Kind::bytes(), [LogNamespace::Vector])
1652                .with_meaning(OwnedTargetPath::event_root(), "message")
1653                .with_metadata_field(
1654                    &owned_value_path!("vector", "source_type"),
1655                    Kind::bytes(),
1656                    None,
1657                )
1658                .with_metadata_field(
1659                    &owned_value_path!(SimpleHttpConfig::NAME, "path"),
1660                    Kind::bytes(),
1661                    None,
1662                )
1663                .with_metadata_field(
1664                    &owned_value_path!(SimpleHttpConfig::NAME, "headers"),
1665                    Kind::object(Collection::empty().with_unknown(Kind::bytes())).or_undefined(),
1666                    None,
1667                )
1668                .with_metadata_field(
1669                    &owned_value_path!(SimpleHttpConfig::NAME, "query_parameters"),
1670                    Kind::object(Collection::empty().with_unknown(Kind::bytes())).or_undefined(),
1671                    None,
1672                )
1673                .with_metadata_field(
1674                    &owned_value_path!(SimpleHttpConfig::NAME, "host"),
1675                    Kind::bytes().or_undefined(),
1676                    None,
1677                )
1678                .with_metadata_field(
1679                    &owned_value_path!("vector", "ingest_timestamp"),
1680                    Kind::timestamp(),
1681                    None,
1682                );
1683
1684        assert_eq!(definitions, Some(expected_definition))
1685    }
1686
1687    #[test]
1688    fn output_schema_definition_legacy_namespace() {
1689        let config = SimpleHttpConfig::default();
1690
1691        let definitions = config
1692            .outputs(LogNamespace::Legacy)
1693            .remove(0)
1694            .schema_definition(true);
1695
1696        let expected_definition = Definition::new_with_default_metadata(
1697            Kind::object(Collection::empty()),
1698            [LogNamespace::Legacy],
1699        )
1700        .with_event_field(
1701            &owned_value_path!("message"),
1702            Kind::bytes(),
1703            Some("message"),
1704        )
1705        .with_event_field(&owned_value_path!("source_type"), Kind::bytes(), None)
1706        .with_event_field(&owned_value_path!("timestamp"), Kind::timestamp(), None)
1707        .with_event_field(&owned_value_path!("path"), Kind::bytes(), None)
1708        .with_event_field(
1709            &owned_value_path!("host"),
1710            Kind::bytes().or_undefined(),
1711            None,
1712        )
1713        .unknown_fields(Kind::bytes());
1714
1715        assert_eq!(definitions, Some(expected_definition))
1716    }
1717
1718    #[test]
1719    fn validate_remove_duplicates() {
1720        let mut list = vec![
1721            "a".to_owned(),
1722            "b".to_owned(),
1723            "c".to_owned(),
1724            "d".to_owned(),
1725        ];
1726
1727        // no duplicates should be identical
1728        {
1729            let list_dedup = remove_duplicates(list.clone(), "foo");
1730
1731            assert_eq!(list, list_dedup);
1732        }
1733
1734        list.push("b".to_owned());
1735
1736        // remove duplicate "b"
1737        {
1738            let list_dedup = remove_duplicates(list.clone(), "foo");
1739            assert_eq!(
1740                vec![
1741                    "a".to_owned(),
1742                    "b".to_owned(),
1743                    "c".to_owned(),
1744                    "d".to_owned()
1745                ],
1746                list_dedup
1747            );
1748        }
1749    }
1750
1751    #[test]
1752    fn inject_auth_enrichment_does_not_clobber_vector_namespace_builtin_fields() {
1753        use crate::{codecs::DecodingConfig, sources::util::HttpSource as _};
1754        use vector_lib::codecs::BytesDeserializerConfig;
1755        use vrl::value::KeyString;
1756
1757        let decoder = DecodingConfig::new(
1758            BytesDecoderConfig::new().into(),
1759            BytesDeserializerConfig::new().into(),
1760            LogNamespace::Vector,
1761        )
1762        .build()
1763        .unwrap()
1764        .with_log_namespace(LogNamespace::Vector);
1765
1766        let source = super::SimpleHttpSource {
1767            headers: vec![],
1768            query_parameters: vec![],
1769            path_key: OptionalValuePath::none(),
1770            host_key: OptionalValuePath::none(),
1771            decoder,
1772            log_namespace: LogNamespace::Vector,
1773        };
1774
1775        let mut log = LogEvent::default();
1776        // Pre-populate %http_server.path as enrich_events would.
1777        log.insert(
1778            (
1779                PathPrefix::Metadata,
1780                path!(SimpleHttpConfig::NAME).concat(path!("path")),
1781            ),
1782            "/real/path",
1783        );
1784
1785        let mut events = vec![Event::Log(log)];
1786        let mut enrichment = ObjectMap::new();
1787        // Attempt to clobber the built-in `path` field and inject a new field.
1788        enrichment.insert(KeyString::from("path"), Value::from("/clobbered"));
1789        enrichment.insert(KeyString::from("tenant_id"), Value::from("t-123"));
1790
1791        source.inject_auth_enrichment(&mut events, enrichment);
1792
1793        let Event::Log(log) = &events[0] else {
1794            panic!("expected log event");
1795        };
1796        assert_eq!(
1797            log.get((
1798                PathPrefix::Metadata,
1799                path!(SimpleHttpConfig::NAME).concat(path!("path")),
1800            )),
1801            Some(&Value::from("/real/path")),
1802            "auth enrichment must not overwrite built-in source metadata"
1803        );
1804        assert_eq!(
1805            log.get((
1806                PathPrefix::Metadata,
1807                path!(SimpleHttpConfig::NAME).concat(path!("tenant_id")),
1808            )),
1809            Some(&Value::from("t-123")),
1810            "new auth enrichment field must be injected"
1811        );
1812    }
1813
1814    #[test]
1815    fn inject_auth_enrichment_does_not_overwrite_existing_metadata_in_vector_namespace() {
1816        use crate::{codecs::DecodingConfig, sources::util::HttpSource as _};
1817        use vector_lib::codecs::BytesDeserializerConfig;
1818        use vrl::value::KeyString;
1819
1820        let decoder = DecodingConfig::new(
1821            BytesDecoderConfig::new().into(),
1822            BytesDeserializerConfig::new().into(),
1823            LogNamespace::Vector,
1824        )
1825        .build()
1826        .unwrap()
1827        .with_log_namespace(LogNamespace::Vector);
1828
1829        let source = super::SimpleHttpSource {
1830            headers: vec![],
1831            query_parameters: vec![],
1832            path_key: OptionalValuePath::none(),
1833            host_key: OptionalValuePath::none(),
1834            decoder,
1835            log_namespace: LogNamespace::Vector,
1836        };
1837
1838        let mut log = LogEvent::default();
1839        // Pre-populate a key (e.g. already written by enrich_events or the decoded event).
1840        log.insert(
1841            (
1842                PathPrefix::Metadata,
1843                path!(SimpleHttpConfig::NAME).concat(path!("tenant_id")),
1844            ),
1845            "existing",
1846        );
1847
1848        let mut events = vec![Event::Log(log)];
1849        let mut enrichment = ObjectMap::new();
1850        enrichment.insert(KeyString::from("tenant_id"), Value::from("auth-value"));
1851
1852        source.inject_auth_enrichment(&mut events, enrichment);
1853
1854        let Event::Log(log) = &events[0] else {
1855            panic!("expected log event");
1856        };
1857        assert_eq!(
1858            log.get((
1859                PathPrefix::Metadata,
1860                path!(SimpleHttpConfig::NAME).concat(path!("tenant_id")),
1861            )),
1862            Some(&Value::from("existing")),
1863            "auth enrichment must not overwrite already-present metadata keys"
1864        );
1865    }
1866
1867    #[test]
1868    fn inject_auth_enrichment_applies_to_non_log_events_in_vector_namespace() {
1869        use crate::{codecs::DecodingConfig, sources::util::HttpSource as _};
1870        use vector_lib::{
1871            codecs::BytesDeserializerConfig,
1872            event::{Metric, MetricKind, MetricValue},
1873        };
1874        use vrl::value::KeyString;
1875
1876        let decoder = DecodingConfig::new(
1877            BytesDecoderConfig::new().into(),
1878            BytesDeserializerConfig::new().into(),
1879            LogNamespace::Vector,
1880        )
1881        .build()
1882        .unwrap()
1883        .with_log_namespace(LogNamespace::Vector);
1884
1885        let source = super::SimpleHttpSource {
1886            headers: vec![],
1887            query_parameters: vec![],
1888            path_key: OptionalValuePath::none(),
1889            host_key: OptionalValuePath::none(),
1890            decoder,
1891            log_namespace: LogNamespace::Vector,
1892        };
1893
1894        let metric = Metric::new(
1895            "requests",
1896            MetricKind::Incremental,
1897            MetricValue::Counter { value: 1.0 },
1898        );
1899        let mut events = vec![Event::Metric(metric)];
1900
1901        let mut enrichment = ObjectMap::new();
1902        enrichment.insert(KeyString::from("tenant_id"), Value::from("t-456"));
1903
1904        source.inject_auth_enrichment(&mut events, enrichment);
1905
1906        let Event::Metric(metric) = &events[0] else {
1907            panic!("expected metric event");
1908        };
1909        assert_eq!(
1910            metric
1911                .metadata()
1912                .value()
1913                .get(path!(SimpleHttpConfig::NAME).concat(path!("tenant_id")),),
1914            Some(&Value::from("t-456")),
1915            "auth enrichment must be written to non-log event metadata"
1916        );
1917    }
1918
1919    impl ValidatableComponent for SimpleHttpConfig {
1920        fn validation_configuration() -> ValidationConfiguration {
1921            let config = Self {
1922                decoding: Some(DeserializerConfig::Json(Default::default())),
1923                ..Default::default()
1924            };
1925
1926            let log_namespace: LogNamespace = config.log_namespace.unwrap_or(false).into();
1927
1928            let listen_addr_http = format!("http://{}/", config.address);
1929            let uri = Uri::try_from(&listen_addr_http).expect("should not fail to parse URI");
1930
1931            let external_resource = ExternalResource::new(
1932                ResourceDirection::Push,
1933                HttpResourceConfig::from_parts(uri, Some(config.method.into())),
1934                config
1935                    .get_decoding_config()
1936                    .expect("should not fail to get decoding config"),
1937            );
1938
1939            ValidationConfiguration::from_source(
1940                Self::NAME,
1941                log_namespace,
1942                vec![ComponentTestCaseConfig::from_source(
1943                    config,
1944                    None,
1945                    Some(external_resource),
1946                )],
1947            )
1948        }
1949    }
1950
1951    register_validatable_component!(SimpleHttpConfig);
1952}