From abe8f47b7dd4f29f7ae4a356d53633dcb902c2d5 Mon Sep 17 00:00:00 2001 From: Chris Arnott Date: Mon, 8 Jul 2024 11:35:43 +0100 Subject: [PATCH 01/17] feat(http_server): Add custom response header configuration Add a "custom_response_headers" option to `SimpleHttpConfig` to add to all Http responses. Closes: #20395 --- src/sources/http_server.rs | 28 ++++++++++++++++++++++++++++ src/sources/util/http/prelude.rs | 7 ++++++- 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/src/sources/http_server.rs b/src/sources/http_server.rs index b13160c315ee4..96c0d25e83947 100644 --- a/src/sources/http_server.rs +++ b/src/sources/http_server.rs @@ -98,6 +98,11 @@ pub struct SimpleHttpConfig { #[configurable(metadata(docs::examples = "X-*"))] #[configurable(metadata(docs::examples = "*"))] headers: Vec, + + /// Custom response headers to be added to the HTTP response + #[serde(default)] + #[configurable(metadata(docs::examples = "example_custom_response_headers()"))] + custom_response_headers: HashMap, /// A list of URL query parameters to include in the log event. /// @@ -170,6 +175,12 @@ pub struct SimpleHttpConfig { keepalive: KeepaliveConfig, } +fn example_custom_response_headers() -> HashMap { + HashMap::<_, _>::from_iter([ + ("Access-Control-Allow-Origin", "*"), + ]) +} + impl SimpleHttpConfig { /// Builds the `schema::Definition` for this source using the provided `LogNamespace`. fn schema_definition(&self, log_namespace: LogNamespace) -> Definition { @@ -265,6 +276,7 @@ impl Default for SimpleHttpConfig { address: "0.0.0.0:8080".parse().unwrap(), encoding: None, headers: Vec::new(), + custom_response_headers: HashMap::new(), query_parameters: Vec::new(), tls: None, auth: None, @@ -355,6 +367,7 @@ impl SourceConfig for SimpleHttpConfig { let source = SimpleHttpSource { headers: build_param_matcher(&remove_duplicates(self.headers.clone(), "headers"))?, + custom_response_headers: self.custom_response_headers.clone(), query_parameters: remove_duplicates(self.query_parameters.clone(), "query_parameters"), path_key: self.path_key.clone(), host_key: self.host_key.clone(), @@ -403,6 +416,7 @@ impl SourceConfig for SimpleHttpConfig { #[derive(Clone)] struct SimpleHttpSource { headers: Vec, + custom_response_headers: HashMap, query_parameters: Vec, path_key: OptionalValuePath, host_key: OptionalValuePath, @@ -544,6 +558,20 @@ impl HttpSource for SimpleHttpSource { fn enable_source_ip(&self) -> bool { self.host_key.path.is_some() } + + /// Enriches the warp::reply::Reply with custom headers + /// + /// This method adds the custom headers specified in the configuration + /// to the HTTP response. + fn enrich_reply( + &self, + mut reply: T + ) -> warp::http::response::Builder { + for (key, value) in &self.custom_response_headers { + reply = warp::reply::with_headers(reply, key, value); + } + reply + } } #[cfg(test)] diff --git a/src/sources/util/http/prelude.rs b/src/sources/util/http/prelude.rs index 5346f14f519db..b1df63903aa30 100644 --- a/src/sources/util/http/prelude.rs +++ b/src/sources/util/http/prelude.rs @@ -70,6 +70,11 @@ pub trait HttpSource: Clone + Send + Sync + 'static { decode(encoding_header, body) } + // This function can be defined to enrich warp::Replies. + fn enrich_reply(&self, reply: T) -> T { + reply + } + #[allow(clippy::too_many_arguments)] fn run( self, @@ -186,7 +191,7 @@ pub trait HttpSource: Clone + Send + Sync + 'static { }); Err(r) } - }); + }).map(|reply| &self.enrich_reply(reply)); let span = Span::current(); let make_svc = make_service_fn(move |conn: &MaybeTlsIncomingStream| { From 87d86794eaed5909653be0f5492379f7e2167497 Mon Sep 17 00:00:00 2001 From: Chris Arnott Date: Mon, 8 Jul 2024 11:49:20 +0100 Subject: [PATCH 02/17] Update http_server.rs --- src/sources/http_server.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sources/http_server.rs b/src/sources/http_server.rs index 96c0d25e83947..955a5b027959d 100644 --- a/src/sources/http_server.rs +++ b/src/sources/http_server.rs @@ -177,7 +177,7 @@ pub struct SimpleHttpConfig { fn example_custom_response_headers() -> HashMap { HashMap::<_, _>::from_iter([ - ("Access-Control-Allow-Origin", "*"), + ("Access-Control-Allow-Origin", "my-cool-server"), ]) } From d3c2c3cd460a8779bf3a6ba824440e23ed463093 Mon Sep 17 00:00:00 2001 From: Chris Arnott Date: Fri, 19 Jul 2024 23:06:22 +0100 Subject: [PATCH 03/17] Fix the build --- src/sources/http_server.rs | 23 ++++++++--------- src/sources/util/http/prelude.rs | 42 ++++++++++++++++++++------------ 2 files changed, 39 insertions(+), 26 deletions(-) diff --git a/src/sources/http_server.rs b/src/sources/http_server.rs index 955a5b027959d..b601e0fa40efd 100644 --- a/src/sources/http_server.rs +++ b/src/sources/http_server.rs @@ -98,9 +98,9 @@ pub struct SimpleHttpConfig { #[configurable(metadata(docs::examples = "X-*"))] #[configurable(metadata(docs::examples = "*"))] headers: Vec, - + /// Custom response headers to be added to the HTTP response - #[serde(default)] + #[serde(default)] #[configurable(metadata(docs::examples = "example_custom_response_headers()"))] custom_response_headers: HashMap, @@ -176,9 +176,10 @@ pub struct SimpleHttpConfig { } fn example_custom_response_headers() -> HashMap { - HashMap::<_, _>::from_iter([ - ("Access-Control-Allow-Origin", "my-cool-server"), - ]) + HashMap::::from_iter([( + "Access-Control-Allow-Origin".to_string(), + "my-cool-server".to_string(), + )]) } impl SimpleHttpConfig { @@ -563,19 +564,18 @@ impl HttpSource for SimpleHttpSource { /// /// This method adds the custom headers specified in the configuration /// to the HTTP response. - fn enrich_reply( - &self, - mut reply: T - ) -> warp::http::response::Builder { + fn enrich_reply(&self, reply: T) -> Box { + let mut boxed_reply: Box = Box::new(reply); for (key, value) in &self.custom_response_headers { - reply = warp::reply::with_headers(reply, key, value); + boxed_reply = Box::new(warp::reply::with_header(boxed_reply, key, value)); } - reply + boxed_reply } } #[cfg(test)] mod tests { + use std::collections::HashMap; use std::str::FromStr; use std::{io::Write, net::SocketAddr}; @@ -647,6 +647,7 @@ mod tests { SimpleHttpConfig { address, headers, + custom_response_headers: HashMap::new(), encoding: None, query_parameters, response_code, diff --git a/src/sources/util/http/prelude.rs b/src/sources/util/http/prelude.rs index b1df63903aa30..52175a60ff6a9 100644 --- a/src/sources/util/http/prelude.rs +++ b/src/sources/util/http/prelude.rs @@ -70,9 +70,9 @@ pub trait HttpSource: Clone + Send + Sync + 'static { decode(encoding_header, body) } - // This function can be defined to enrich warp::Replies. - fn enrich_reply(&self, reply: T) -> T { - reply + // This function can be defined to enrich `warp::Reply`s. + fn enrich_reply(&self, reply: T) -> Box { + Box::new(reply) } #[allow(clippy::too_many_arguments)] @@ -95,6 +95,7 @@ pub trait HttpSource: Clone + Send + Sync + 'static { let path = path.to_owned(); let acknowledgements = cx.do_acknowledgements(acknowledgements); let enable_source_ip = self.enable_source_ip(); + let self_clone = self.clone(); Ok(Box::pin(async move { let mut filter: BoxedFilter<()> = match method { @@ -175,23 +176,34 @@ pub trait HttpSource: Clone + Send + Sync + 'static { events }); - handle_request(events, acknowledgements, response_code, cx.out.clone()) + handle_request(events, acknowledgements, response_code, cx.out.clone()).map( + { + let self_clone = self.clone(); + move |result| { + result.map(move |reply| self_clone.enrich_reply(reply)) + } + }, + ) }, ); let ping = warp::get().and(warp::path("ping")).map(|| "pong"); - let routes = svc.or(ping).recover(|r: Rejection| async move { - if let Some(e_msg) = r.find::() { - let json = warp::reply::json(e_msg); - Ok(warp::reply::with_status(json, e_msg.status_code())) - } else { - //other internal error - will return 500 internal server error - emit!(HttpInternalError { - message: &format!("Internal error: {:?}", r) - }); - Err(r) + let routes = svc.or(ping).recover(move |r: Rejection| { + let self_clone = self_clone.clone(); + async move { + if let Some(e_msg) = r.find::() { + let json = warp::reply::json(e_msg); + Ok(self_clone + .enrich_reply(warp::reply::with_status(json, e_msg.status_code()))) + } else { + //other internal error - will return 500 internal server error + emit!(HttpInternalError { + message: &format!("Internal error: {:?}", r) + }); + Err(r) + } } - }).map(|reply| &self.enrich_reply(reply)); + }); let span = Span::current(); let make_svc = make_service_fn(move |conn: &MaybeTlsIncomingStream| { From 38f210d33695dfdaa2c8020cf81e64eb15a997df Mon Sep 17 00:00:00 2001 From: Chris Arnott Date: Tue, 9 Jul 2024 23:59:46 +0100 Subject: [PATCH 04/17] Add changelog --- .../20395_custom_response_headers_from_http_server.feature.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 changelog.d/20395_custom_response_headers_from_http_server.feature.md diff --git a/changelog.d/20395_custom_response_headers_from_http_server.feature.md b/changelog.d/20395_custom_response_headers_from_http_server.feature.md new file mode 100644 index 0000000000000..0238a08eae512 --- /dev/null +++ b/changelog.d/20395_custom_response_headers_from_http_server.feature.md @@ -0,0 +1,3 @@ +Allows configuring custom headers to be added to responses from the http_server when using the HttpSource. + +authors: chriscancompute From a76b5fab8af70ffd05ef69958fa7e1758945d52a Mon Sep 17 00:00:00 2001 From: Chris Arnott Date: Sun, 28 Jul 2024 21:39:12 +0100 Subject: [PATCH 05/17] Add a test --- src/sources/http_server.rs | 66 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/src/sources/http_server.rs b/src/sources/http_server.rs index b601e0fa40efd..e5ba63eac9848 100644 --- a/src/sources/http_server.rs +++ b/src/sources/http_server.rs @@ -619,6 +619,7 @@ mod tests { #[allow(clippy::too_many_arguments)] async fn source<'a>( headers: Vec, + custom_response_headers: HashMap, query_parameters: Vec, path_key: &'a str, host_key: &'a str, @@ -759,6 +760,7 @@ mod tests { let (rx, addr) = source( vec![], + HashMap::new(), vec![], "http_path", "remote_ip", @@ -805,6 +807,7 @@ mod tests { let mut events = assert_source_compliance(&HTTP_PUSH_SOURCE_TAGS, async move { let (rx, addr) = source( vec![], + HashMap::new(), vec![], "http_path", "remote_ip", @@ -844,6 +847,7 @@ mod tests { let mut events = assert_source_compliance(&HTTP_PUSH_SOURCE_TAGS, async move { let (rx, addr) = source( vec![], + HashMap::new(), vec![], "http_path", "remote_ip", @@ -877,6 +881,7 @@ mod tests { let mut events = assert_source_compliance(&HTTP_PUSH_SOURCE_TAGS, async { let (rx, addr) = source( vec![], + HashMap::new(), vec![], "http_path", "remote_ip", @@ -915,6 +920,7 @@ mod tests { let mut events = assert_source_compliance(&HTTP_PUSH_SOURCE_TAGS, async { let (rx, addr) = source( vec![], + HashMap::new(), vec![], "http_path", "remote_ip", @@ -960,6 +966,7 @@ mod tests { let mut events = assert_source_compliance(&HTTP_PUSH_SOURCE_TAGS, async { let (rx, addr) = source( vec![], + HashMap::new(), vec![], "http_path", "remote_ip", @@ -1011,6 +1018,7 @@ mod tests { let mut events = assert_source_compliance(&HTTP_PUSH_SOURCE_TAGS, async { let (rx, addr) = source( vec![], + HashMap::new(), vec![], "http_path", "remote_ip", @@ -1097,6 +1105,7 @@ mod tests { "X-*".to_string(), "AbsentHeader".to_string(), ], + HashMap::new(), vec![], "http_path", "remote_ip", @@ -1141,6 +1150,7 @@ mod tests { let (rx, addr) = source( vec!["*".to_string()], + HashMap::new(), vec![], "http_path", "remote_ip", @@ -1174,11 +1184,59 @@ mod tests { } } + #[tokio::test] + async fn http_custom_response_headers() { + async fn send(address: SocketAddr, body: &str) -> reqwest::header::HeaderMap { + reqwest::Client::new() + .post(&format!("http://{}/", address)) + .body(body.to_owned()) + .send() + .await + .unwrap() + .headers() + } + + let mut events = assert_source_compliance(&HTTP_PUSH_SOURCE_TAGS, async { + let mut custom_headers: HashMap = HashMap::new(); + custom_headers.insert("Access-Control-Allow-Origin", "example.com"); + + let (rx, addr) = source( + vec!["*".to_string()], + custom_headers, + vec![], + "http_path", + "remote_ip", + "/", + "POST", + StatusCode::OK, + true, + EventStatus::Delivered, + true, + None, + Some(JsonDeserializerConfig::default().into()), + ) + .await; + + spawn_collect_n( + async move { + let response_headers = send(addr, "{\"key1\":\"value1\"}").await; + assert!(response_headers.contains_key("Access-Control-Allow-Origin")); + assert_eq!(response_headers["Access-Control-Allow-Origin"], "example.com"); + }, + rx, + 1, + ) + .await + }) + .await; + } + #[tokio::test] async fn http_query() { let mut events = assert_source_compliance(&HTTP_PUSH_SOURCE_TAGS, async { let (rx, addr) = source( vec![], + HashMap::new(), vec![ "source".to_string(), "region".to_string(), @@ -1235,6 +1293,7 @@ mod tests { let (rx, addr) = source( vec![], + HashMap::new(), vec![], "http_path", "remote_ip", @@ -1266,6 +1325,7 @@ mod tests { let mut events = assert_source_compliance(&HTTP_PUSH_SOURCE_TAGS, async { let (rx, addr) = source( vec![], + HashMap::new(), vec![], "vector_http_path", "vector_remote_ip", @@ -1307,6 +1367,7 @@ mod tests { let mut events = assert_source_compliance(&HTTP_PUSH_SOURCE_TAGS, async { let (rx, addr) = source( vec![], + HashMap::new(), vec![], "vector_http_path", "vector_remote_ip", @@ -1368,6 +1429,7 @@ mod tests { components::init_test(); let (_rx, addr) = source( vec![], + HashMap::new(), vec![], "vector_http_path", "vector_remote_ip", @@ -1393,6 +1455,7 @@ mod tests { assert_source_compliance(&HTTP_PUSH_SOURCE_TAGS, async move { let (rx, addr) = source( vec![], + HashMap::new(), vec![], "http_path", "remote_ip", @@ -1427,6 +1490,7 @@ mod tests { assert_source_compliance(&HTTP_PUSH_SOURCE_TAGS, async { let (rx, addr) = source( vec![], + HashMap::new(), vec![], "http_path", "remote_ip", @@ -1458,6 +1522,7 @@ mod tests { let events = assert_source_compliance(&HTTP_PUSH_SOURCE_TAGS, async { let (rx, addr) = source( vec![], + HashMap::new(), vec![], "http_path", "remote_ip", @@ -1491,6 +1556,7 @@ mod tests { components::init_test(); let (_rx, addr) = source( vec![], + HashMap::new(), vec![], "http_path", "remote_ip", From 27352cc64317aab9c760ce2193e984fe6dbe9ddb Mon Sep 17 00:00:00 2001 From: Chris Arnott Date: Mon, 29 Jul 2024 10:49:11 +0100 Subject: [PATCH 06/17] Fix test compile --- src/sources/http_server.rs | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/src/sources/http_server.rs b/src/sources/http_server.rs index e5ba63eac9848..1d647fd069eca 100644 --- a/src/sources/http_server.rs +++ b/src/sources/http_server.rs @@ -648,7 +648,7 @@ mod tests { SimpleHttpConfig { address, headers, - custom_response_headers: HashMap::new(), + custom_response_headers, encoding: None, query_parameters, response_code, @@ -1186,19 +1186,21 @@ mod tests { #[tokio::test] async fn http_custom_response_headers() { - async fn send(address: SocketAddr, body: &str) -> reqwest::header::HeaderMap { + async fn send(address: SocketAddr, body: &str) -> reqwest::Response { reqwest::Client::new() .post(&format!("http://{}/", address)) .body(body.to_owned()) .send() .await .unwrap() - .headers() - } + } - let mut events = assert_source_compliance(&HTTP_PUSH_SOURCE_TAGS, async { - let mut custom_headers: HashMap = HashMap::new(); - custom_headers.insert("Access-Control-Allow-Origin", "example.com"); + assert_source_compliance(&HTTP_PUSH_SOURCE_TAGS, async { + let mut custom_headers: HashMap = HashMap::new(); + custom_headers.insert( + "Access-Control-Allow-Origin".to_string(), + "example.com".to_string(), + ); let (rx, addr) = source( vec!["*".to_string()], @@ -1219,9 +1221,13 @@ mod tests { spawn_collect_n( async move { - let response_headers = send(addr, "{\"key1\":\"value1\"}").await; + let response = send(addr, "{\"key1\":\"value1\"}").await; + let response_headers = response.headers(); assert!(response_headers.contains_key("Access-Control-Allow-Origin")); - assert_eq!(response_headers["Access-Control-Allow-Origin"], "example.com"); + assert_eq!( + response_headers["Access-Control-Allow-Origin"], + "example.com" + ); }, rx, 1, From dc670fddba1938a0603ad72b96c5472de7a1bb76 Mon Sep 17 00:00:00 2001 From: Chris Arnott Date: Mon, 5 Aug 2024 11:51:37 +0100 Subject: [PATCH 07/17] Update docs --- src/sources/http_server.rs | 3 +++ .../cue/reference/components/sources/base/http.cue | 14 ++++++++++++++ .../components/sources/base/http_server.cue | 14 ++++++++++++++ 3 files changed, 31 insertions(+) diff --git a/src/sources/http_server.rs b/src/sources/http_server.rs index 1d647fd069eca..9972c056f8669 100644 --- a/src/sources/http_server.rs +++ b/src/sources/http_server.rs @@ -102,6 +102,9 @@ pub struct SimpleHttpConfig { /// Custom response headers to be added to the HTTP response #[serde(default)] #[configurable(metadata(docs::examples = "example_custom_response_headers()"))] + #[configurable(metadata( + docs::additional_props_description = "A custom response header key-value pair" + ))] custom_response_headers: HashMap, /// A list of URL query parameters to include in the log event. diff --git a/website/cue/reference/components/sources/base/http.cue b/website/cue/reference/components/sources/base/http.cue index 73ae0efbe4ac7..2b7e6c9fb118d 100644 --- a/website/cue/reference/components/sources/base/http.cue +++ b/website/cue/reference/components/sources/base/http.cue @@ -47,6 +47,20 @@ base: components: sources: http: configuration: { } } } + custom_response_headers: { + description: "Custom response headers to be added to the HTTP response" + required: false + type: object: { + examples: [{ + "Access-Control-Allow-Origin": "my-cool-server" + }] + options: "*": { + description: "A custom response header key-value pair" + required: true + type: string: {} + } + } + } decoding: { description: "Configures how events are decoded from raw bytes." required: false diff --git a/website/cue/reference/components/sources/base/http_server.cue b/website/cue/reference/components/sources/base/http_server.cue index 3837266a94199..69a58674003ca 100644 --- a/website/cue/reference/components/sources/base/http_server.cue +++ b/website/cue/reference/components/sources/base/http_server.cue @@ -47,6 +47,20 @@ base: components: sources: http_server: configuration: { } } } + custom_response_headers: { + description: "Custom response headers to be added to the HTTP response" + required: false + type: object: { + examples: [{ + "Access-Control-Allow-Origin": "my-cool-server" + }] + options: "*": { + description: "A custom response header key-value pair" + required: true + type: string: {} + } + } + } decoding: { description: "Configures how events are decoded from raw bytes." required: false From 8859463237256c2898d99fbbe772078ac337f446 Mon Sep 17 00:00:00 2001 From: Chris Arnott Date: Tue, 6 Aug 2024 17:00:28 +0100 Subject: [PATCH 08/17] Update changelog.d/20395_custom_response_headers_from_http_server.feature.md Co-authored-by: Jesse Szwedko --- .../20395_custom_response_headers_from_http_server.feature.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelog.d/20395_custom_response_headers_from_http_server.feature.md b/changelog.d/20395_custom_response_headers_from_http_server.feature.md index 0238a08eae512..46d0ccc47715c 100644 --- a/changelog.d/20395_custom_response_headers_from_http_server.feature.md +++ b/changelog.d/20395_custom_response_headers_from_http_server.feature.md @@ -1,3 +1,3 @@ -Allows configuring custom headers to be added to responses from the http_server when using the HttpSource. +The `http_server` source now allows configuring custom headers to be added to responses via the `custom_response_headers` option. authors: chriscancompute From aa06e83418ecbfa4faa84fce89a4078c02c069e7 Mon Sep 17 00:00:00 2001 From: Chris Arnott Date: Wed, 28 Aug 2024 10:33:13 +0100 Subject: [PATCH 09/17] Allow multiple values for each http header --- src/sources/http_server.rs | 33 ++++++++++--------- .../components/sources/base/http.cue | 4 +-- .../components/sources/base/http_server.cue | 4 +-- 3 files changed, 21 insertions(+), 20 deletions(-) diff --git a/src/sources/http_server.rs b/src/sources/http_server.rs index 115e876a69fdb..46a27d8d4004a 100644 --- a/src/sources/http_server.rs +++ b/src/sources/http_server.rs @@ -103,9 +103,9 @@ pub struct SimpleHttpConfig { #[serde(default)] #[configurable(metadata(docs::examples = "example_custom_response_headers()"))] #[configurable(metadata( - docs::additional_props_description = "A custom response header key-value pair" + docs::additional_props_description = "A custom response header key-values pair" ))] - custom_response_headers: HashMap, + custom_response_headers: HashMap>, /// A list of URL query parameters to include in the log event. /// @@ -178,10 +178,10 @@ pub struct SimpleHttpConfig { keepalive: KeepaliveConfig, } -fn example_custom_response_headers() -> HashMap { - HashMap::::from_iter([( +fn example_custom_response_headers() -> HashMap> { + HashMap::>::from_iter([( "Access-Control-Allow-Origin".to_string(), - "my-cool-server".to_string(), + vec!["my-cool-server".to_string(), "my-other-server".to_string()], )]) } @@ -420,7 +420,7 @@ impl SourceConfig for SimpleHttpConfig { #[derive(Clone)] struct SimpleHttpSource { headers: Vec, - custom_response_headers: HashMap, + custom_response_headers: HashMap>, query_parameters: Vec, path_key: OptionalValuePath, host_key: OptionalValuePath, @@ -569,8 +569,12 @@ impl HttpSource for SimpleHttpSource { /// to the HTTP response. fn enrich_reply(&self, reply: T) -> Box { let mut boxed_reply: Box = Box::new(reply); - for (key, value) in &self.custom_response_headers { - boxed_reply = Box::new(warp::reply::with_header(boxed_reply, key, value)); + for (key, values) in &self.custom_response_headers { + boxed_reply = Box::new(warp::reply::with_header( + boxed_reply, + key, + values.join(", "), + )); } boxed_reply } @@ -622,7 +626,7 @@ mod tests { #[allow(clippy::too_many_arguments)] async fn source<'a>( headers: Vec, - custom_response_headers: HashMap, + custom_response_headers: HashMap>, query_parameters: Vec, path_key: &'a str, host_key: &'a str, @@ -1199,10 +1203,10 @@ mod tests { } assert_source_compliance(&HTTP_PUSH_SOURCE_TAGS, async { - let mut custom_headers: HashMap = HashMap::new(); + let mut custom_headers: HashMap> = HashMap::new(); custom_headers.insert( "Access-Control-Allow-Origin".to_string(), - "example.com".to_string(), + vec!["example.com".to_string(), "example2.com".to_string()], ); let (rx, addr) = source( @@ -1226,11 +1230,8 @@ mod tests { async move { let response = send(addr, "{\"key1\":\"value1\"}").await; let response_headers = response.headers(); - assert!(response_headers.contains_key("Access-Control-Allow-Origin")); - assert_eq!( - response_headers["Access-Control-Allow-Origin"], - "example.com" - ); + let view = response_headers.get("Access-Control-Allow-Origin").unwrap(); + assert_eq!(view.to_str().unwrap(), "example.com, example2.com"); }, rx, 1, diff --git a/website/cue/reference/components/sources/base/http.cue b/website/cue/reference/components/sources/base/http.cue index 7113af2da12f1..79aba9bd439ea 100644 --- a/website/cue/reference/components/sources/base/http.cue +++ b/website/cue/reference/components/sources/base/http.cue @@ -52,12 +52,12 @@ base: components: sources: http: configuration: { required: false type: object: { examples: [{ - "Access-Control-Allow-Origin": "my-cool-server" + "Access-Control-Allow-Origin": ["my-cool-server", "my-other-server"] }] options: "*": { description: "A custom response header key-value pair" required: true - type: string: {} + type: array: items: type: string: {} } } } diff --git a/website/cue/reference/components/sources/base/http_server.cue b/website/cue/reference/components/sources/base/http_server.cue index ca4a16fc5b32e..c5b020a7fc1fa 100644 --- a/website/cue/reference/components/sources/base/http_server.cue +++ b/website/cue/reference/components/sources/base/http_server.cue @@ -52,12 +52,12 @@ base: components: sources: http_server: configuration: { required: false type: object: { examples: [{ - "Access-Control-Allow-Origin": "my-cool-server" + "Access-Control-Allow-Origin": ["my-cool-server", "my-other-server"] }] options: "*": { description: "A custom response header key-value pair" required: true - type: string: {} + type: array: items: type: string: {} } } } From 63c97edef56d3fd7b87a444bd2fa5152aeb9879b Mon Sep 17 00:00:00 2001 From: Chris Arnott Date: Wed, 28 Aug 2024 14:31:31 +0100 Subject: [PATCH 10/17] Use header map append and insert --- src/sources/http_server.rs | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/src/sources/http_server.rs b/src/sources/http_server.rs index 46a27d8d4004a..cf3393ebe5cce 100644 --- a/src/sources/http_server.rs +++ b/src/sources/http_server.rs @@ -6,7 +6,7 @@ use http::StatusCode; use http_serde; use tokio_util::codec::Decoder as _; use vrl::value::{kind::Collection, Kind}; -use warp::http::{HeaderMap, HeaderValue}; +use warp::http::{HeaderMap, HeaderName, HeaderValue}; use vector_lib::codecs::{ decoding::{DeserializerConfig, FramingConfig}, @@ -568,15 +568,19 @@ impl HttpSource for SimpleHttpSource { /// This method adds the custom headers specified in the configuration /// to the HTTP response. fn enrich_reply(&self, reply: T) -> Box { - let mut boxed_reply: Box = Box::new(reply); + let mut response = reply.into_response(); + let header_map = response.headers_mut(); + for (key, values) in &self.custom_response_headers { - boxed_reply = Box::new(warp::reply::with_header( - boxed_reply, - key, - values.join(", "), - )); + let header_name: HeaderName = key.parse().unwrap(); + if let Some((first, rest)) = values.split_first() { + header_map.insert(header_name.clone(), first.parse().unwrap()); + for value in rest { + header_map.append(header_name.clone(), value.parse().unwrap()); + } + } } - boxed_reply + Box::new(response) } } @@ -1230,8 +1234,11 @@ mod tests { async move { let response = send(addr, "{\"key1\":\"value1\"}").await; let response_headers = response.headers(); - let view = response_headers.get("Access-Control-Allow-Origin").unwrap(); - assert_eq!(view.to_str().unwrap(), "example.com, example2.com"); + let view = response_headers.get_all("Access-Control-Allow-Origin"); + let mut iter = view.iter(); + assert_eq!(&"example.com", iter.next().unwrap()); + assert_eq!(&"example2.com", iter.next().unwrap()); + assert!(iter.next().is_none()); }, rx, 1, From 46d23f8bc66abd917839bf280ec39cfc1fa75641 Mon Sep 17 00:00:00 2001 From: Chris Arnott Date: Mon, 11 Nov 2024 13:57:44 +0000 Subject: [PATCH 11/17] Parse custom headers earlier --- src/sources/http_server.rs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/sources/http_server.rs b/src/sources/http_server.rs index cf3393ebe5cce..ec93d811cfc65 100644 --- a/src/sources/http_server.rs +++ b/src/sources/http_server.rs @@ -371,7 +371,9 @@ impl SourceConfig for SimpleHttpConfig { let source = SimpleHttpSource { headers: build_param_matcher(&remove_duplicates(self.headers.clone(), "headers"))?, - custom_response_headers: self.custom_response_headers.clone(), + custom_response_headers: self.custom_response_headers.clone().into_iter().map(|(k, v)| { + (k.parse().unwrap(), v.into_iter().map(|v| v.parse().unwrap()).collect()) + }).collect(), query_parameters: remove_duplicates(self.query_parameters.clone(), "query_parameters"), path_key: self.path_key.clone(), host_key: self.host_key.clone(), @@ -420,7 +422,7 @@ impl SourceConfig for SimpleHttpConfig { #[derive(Clone)] struct SimpleHttpSource { headers: Vec, - custom_response_headers: HashMap>, + custom_response_headers: HashMap>, query_parameters: Vec, path_key: OptionalValuePath, host_key: OptionalValuePath, @@ -572,12 +574,8 @@ impl HttpSource for SimpleHttpSource { let header_map = response.headers_mut(); for (key, values) in &self.custom_response_headers { - let header_name: HeaderName = key.parse().unwrap(); - if let Some((first, rest)) = values.split_first() { - header_map.insert(header_name.clone(), first.parse().unwrap()); - for value in rest { - header_map.append(header_name.clone(), value.parse().unwrap()); - } + for value in values { + header_map.append(key.clone(), value.clone()); } } Box::new(response) From 0da494ae6cec8b58c22584011477c71a20c423ea Mon Sep 17 00:00:00 2001 From: Chris Arnott Date: Mon, 11 Nov 2024 13:58:49 +0000 Subject: [PATCH 12/17] Update http.cue --- website/cue/reference/components/sources/base/http.cue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/cue/reference/components/sources/base/http.cue b/website/cue/reference/components/sources/base/http.cue index 79aba9bd439ea..b3c369d455cc8 100644 --- a/website/cue/reference/components/sources/base/http.cue +++ b/website/cue/reference/components/sources/base/http.cue @@ -55,7 +55,7 @@ base: components: sources: http: configuration: { "Access-Control-Allow-Origin": ["my-cool-server", "my-other-server"] }] options: "*": { - description: "A custom response header key-value pair" + description: "A custom response header key-values pair" required: true type: array: items: type: string: {} } From 1ea1d69696d0376a7ad9b1fbf08b8e50436730c4 Mon Sep 17 00:00:00 2001 From: Chris Arnott Date: Mon, 11 Nov 2024 13:59:07 +0000 Subject: [PATCH 13/17] Update http_server.cue --- website/cue/reference/components/sources/base/http_server.cue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/cue/reference/components/sources/base/http_server.cue b/website/cue/reference/components/sources/base/http_server.cue index c5b020a7fc1fa..4d6e03a84c3b1 100644 --- a/website/cue/reference/components/sources/base/http_server.cue +++ b/website/cue/reference/components/sources/base/http_server.cue @@ -55,7 +55,7 @@ base: components: sources: http_server: configuration: { "Access-Control-Allow-Origin": ["my-cool-server", "my-other-server"] }] options: "*": { - description: "A custom response header key-value pair" + description: "A custom response header key-values pair" required: true type: array: items: type: string: {} } From 0e36d4c202a808581bd57cabd32e0ac085609164 Mon Sep 17 00:00:00 2001 From: Chris Arnott Date: Mon, 11 Nov 2024 14:25:44 +0000 Subject: [PATCH 14/17] Update http package (#2) --- Cargo.lock | 96 +++++++++++++++++++++++++++--------------------------- Cargo.toml | 2 +- 2 files changed, 49 insertions(+), 49 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 92130847fd029..5f383e3ca720e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -543,7 +543,7 @@ checksum = "4c3082de64b6d8e3956fa92e3009c27db209aa17388abf7a7d766adc6bb9b8ba" dependencies = [ "async-graphql", "futures-util", - "http 0.2.9", + "http 0.2.12", "serde_json", "warp", ] @@ -617,7 +617,7 @@ dependencies = [ "base64 0.21.7", "bytes 1.8.0", "futures 0.3.31", - "http 0.2.9", + "http 0.2.12", "memchr", "nkeys 0.3.2", "nuid", @@ -784,7 +784,7 @@ dependencies = [ "bytes 1.8.0", "fastrand 2.1.1", "hex", - "http 0.2.9", + "http 0.2.12", "hyper 0.14.28", "ring 0.17.5", "time", @@ -815,7 +815,7 @@ dependencies = [ "aws-smithy-types", "aws-types", "bytes 1.8.0", - "http 0.2.9", + "http 0.2.12", "http-body 0.4.5", "pin-project-lite", "tracing 0.1.40", @@ -837,7 +837,7 @@ dependencies = [ "aws-smithy-types", "aws-types", "fastrand 2.1.1", - "http 0.2.9", + "http 0.2.12", "percent-encoding", "tracing 0.1.40", "uuid", @@ -861,7 +861,7 @@ dependencies = [ "aws-smithy-types", "aws-smithy-xml", "aws-types", - "http 0.2.9", + "http 0.2.12", "regex", "tracing 0.1.40", ] @@ -884,7 +884,7 @@ dependencies = [ "aws-types", "bytes 1.8.0", "fastrand 2.1.1", - "http 0.2.9", + "http 0.2.12", "regex", "tracing 0.1.40", ] @@ -906,7 +906,7 @@ dependencies = [ "aws-smithy-types", "aws-types", "bytes 1.8.0", - "http 0.2.9", + "http 0.2.12", "regex", "tracing 0.1.40", ] @@ -928,7 +928,7 @@ dependencies = [ "aws-smithy-types", "aws-types", "bytes 1.8.0", - "http 0.2.9", + "http 0.2.12", "regex", "tracing 0.1.40", ] @@ -950,7 +950,7 @@ dependencies = [ "aws-smithy-types", "aws-types", "bytes 1.8.0", - "http 0.2.9", + "http 0.2.12", "regex", "tracing 0.1.40", ] @@ -976,7 +976,7 @@ dependencies = [ "aws-smithy-xml", "aws-types", "bytes 1.8.0", - "http 0.2.9", + "http 0.2.12", "http-body 0.4.5", "once_cell", "percent-encoding", @@ -1003,7 +1003,7 @@ dependencies = [ "aws-types", "bytes 1.8.0", "fastrand 2.1.1", - "http 0.2.9", + "http 0.2.12", "regex", "tracing 0.1.40", ] @@ -1026,7 +1026,7 @@ dependencies = [ "aws-smithy-types", "aws-smithy-xml", "aws-types", - "http 0.2.9", + "http 0.2.12", "regex", "tracing 0.1.40", ] @@ -1048,7 +1048,7 @@ dependencies = [ "aws-smithy-types", "aws-types", "bytes 1.8.0", - "http 0.2.9", + "http 0.2.12", "regex", "tracing 0.1.40", ] @@ -1070,7 +1070,7 @@ dependencies = [ "aws-smithy-types", "aws-types", "bytes 1.8.0", - "http 0.2.9", + "http 0.2.12", "regex", "tracing 0.1.40", ] @@ -1092,7 +1092,7 @@ dependencies = [ "aws-smithy-types", "aws-types", "bytes 1.8.0", - "http 0.2.9", + "http 0.2.12", "regex", "tracing 0.1.40", ] @@ -1115,7 +1115,7 @@ dependencies = [ "aws-smithy-types", "aws-smithy-xml", "aws-types", - "http 0.2.9", + "http 0.2.12", "regex", "tracing 0.1.40", ] @@ -1135,7 +1135,7 @@ dependencies = [ "form_urlencoded", "hex", "hmac", - "http 0.2.9", + "http 0.2.12", "http 1.1.0", "once_cell", "percent-encoding", @@ -1167,7 +1167,7 @@ dependencies = [ "crc32c", "crc32fast", "hex", - "http 0.2.9", + "http 0.2.12", "http-body 0.4.5", "md-5", "pin-project-lite", @@ -1199,7 +1199,7 @@ dependencies = [ "bytes 1.8.0", "bytes-utils", "futures-core", - "http 0.2.9", + "http 0.2.12", "http-body 0.4.5", "once_cell", "percent-encoding", @@ -1240,7 +1240,7 @@ dependencies = [ "bytes 1.8.0", "fastrand 2.1.1", "h2 0.3.26", - "http 0.2.9", + "http 0.2.12", "http-body 0.4.5", "http-body 1.0.0", "httparse", @@ -1263,7 +1263,7 @@ dependencies = [ "aws-smithy-async", "aws-smithy-types", "bytes 1.8.0", - "http 0.2.9", + "http 0.2.12", "http 1.1.0", "pin-project-lite", "tokio", @@ -1281,7 +1281,7 @@ dependencies = [ "bytes 1.8.0", "bytes-utils", "futures-core", - "http 0.2.9", + "http 0.2.12", "http 1.1.0", "http-body 0.4.5", "http-body 1.0.0", @@ -1331,7 +1331,7 @@ dependencies = [ "bitflags 1.3.2", "bytes 1.8.0", "futures-util", - "http 0.2.9", + "http 0.2.12", "http-body 0.4.5", "hyper 0.14.28", "itoa", @@ -1385,7 +1385,7 @@ dependencies = [ "async-trait", "bytes 1.8.0", "futures-util", - "http 0.2.9", + "http 0.2.12", "http-body 0.4.5", "mime", "rustversion", @@ -4025,7 +4025,7 @@ dependencies = [ "futures-core", "futures-sink", "futures-util", - "http 0.2.9", + "http 0.2.12", "indexmap 2.6.0", "slab", "tokio", @@ -4125,7 +4125,7 @@ dependencies = [ "base64 0.21.7", "bytes 1.8.0", "headers-core", - "http 0.2.9", + "http 0.2.12", "httpdate", "mime", "sha1", @@ -4137,7 +4137,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7f66481bfee273957b1f20485a4ff3362987f85b2c236580d81b4eb7a326429" dependencies = [ - "http 0.2.9", + "http 0.2.12", ] [[package]] @@ -4390,7 +4390,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" dependencies = [ "bytes 1.8.0", - "http 0.2.9", + "http 0.2.12", "pin-project-lite", ] @@ -4429,7 +4429,7 @@ version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f560b665ad9f1572cfcaf034f7fb84338a7ce945216d64a90fd81f046a3caee" dependencies = [ - "http 0.2.9", + "http 0.2.12", "serde", ] @@ -4482,7 +4482,7 @@ dependencies = [ "futures-core", "futures-util", "h2 0.3.26", - "http 0.2.9", + "http 0.2.12", "http-body 0.4.5", "httparse", "httpdate", @@ -4537,7 +4537,7 @@ version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6ee5d7a8f718585d1c3c61dfde28ef5b0bb14734b4db13f5ada856cdc6c612b" dependencies = [ - "http 0.2.9", + "http 0.2.12", "hyper 0.14.28", "linked_hash_set", "once_cell", @@ -4558,7 +4558,7 @@ dependencies = [ "bytes 1.8.0", "futures 0.3.31", "headers", - "http 0.2.9", + "http 0.2.12", "hyper 0.14.28", "openssl", "tokio", @@ -4573,7 +4573,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" dependencies = [ "futures-util", - "http 0.2.9", + "http 0.2.12", "hyper 0.14.28", "log", "rustls 0.21.11", @@ -5210,7 +5210,7 @@ dependencies = [ "base64 0.21.7", "bytes 1.8.0", "chrono", - "http 0.2.9", + "http 0.2.12", "percent-encoding", "serde", "serde-value", @@ -5291,7 +5291,7 @@ dependencies = [ "dirs-next", "either", "futures 0.3.31", - "http 0.2.9", + "http 0.2.12", "http-body 0.4.5", "hyper 0.14.28", "hyper-openssl", @@ -5322,7 +5322,7 @@ checksum = "25983d07f414dfffba08c5951fe110f649113416b1d8e22f7c89c750eb2555a7" dependencies = [ "chrono", "form_urlencoded", - "http 0.2.9", + "http 0.2.12", "json-patch", "k8s-openapi 0.18.0", "once_cell", @@ -6407,7 +6407,7 @@ dependencies = [ "base64 0.13.1", "chrono", "getrandom 0.2.15", - "http 0.2.9", + "http 0.2.12", "rand 0.8.5", "reqwest 0.11.26", "serde", @@ -6511,7 +6511,7 @@ dependencies = [ "flagset", "futures 0.3.31", "getrandom 0.2.15", - "http 0.2.9", + "http 0.2.12", "log", "md-5", "once_cell", @@ -6535,7 +6535,7 @@ dependencies = [ "dyn-clone", "ed25519-dalek", "hmac", - "http 0.2.9", + "http 0.2.12", "itertools 0.10.5", "log", "oauth2", @@ -8061,7 +8061,7 @@ dependencies = [ "futures-core", "futures-util", "h2 0.3.26", - "http 0.2.9", + "http 0.2.12", "http-body 0.4.5", "hyper 0.14.28", "hyper-rustls 0.24.2", @@ -10028,7 +10028,7 @@ dependencies = [ "bytes 1.8.0", "flate2", "h2 0.3.26", - "http 0.2.9", + "http 0.2.12", "http-body 0.4.5", "hyper 0.14.28", "hyper-timeout 0.4.1", @@ -10136,7 +10136,7 @@ dependencies = [ "bytes 1.8.0", "futures-core", "futures-util", - "http 0.2.9", + "http 0.2.12", "http-body 0.4.5", "http-range-header", "mime", @@ -10418,7 +10418,7 @@ dependencies = [ "byteorder", "bytes 1.8.0", "data-encoding", - "http 0.2.9", + "http 0.2.12", "httparse", "log", "rand 0.8.5", @@ -10848,7 +10848,7 @@ dependencies = [ "hex", "hickory-proto", "hostname 0.4.0", - "http 0.2.9", + "http 0.2.12", "http-body 0.4.5", "http-serde", "hyper 0.14.28", @@ -11069,7 +11069,7 @@ dependencies = [ "chrono", "chrono-tz", "encoding_rs", - "http 0.2.9", + "http 0.2.12", "indexmap 2.6.0", "inventory", "no-proxy", @@ -11139,7 +11139,7 @@ dependencies = [ "futures 0.3.31", "futures-util", "headers", - "http 0.2.9", + "http 0.2.12", "hyper-proxy", "indexmap 2.6.0", "ipnet", @@ -11509,7 +11509,7 @@ dependencies = [ "futures-channel", "futures-util", "headers", - "http 0.2.9", + "http 0.2.12", "hyper 0.14.28", "log", "mime", diff --git a/Cargo.toml b/Cargo.toml index 429fc661eef3f..317e6f91d15ff 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -309,7 +309,7 @@ hash_hasher = { version = "2.0.0", default-features = false } hashbrown = { version = "0.14.5", default-features = false, optional = true, features = ["ahash"] } headers = { version = "0.3.9", default-features = false } hostname = { version = "0.4.0", default-features = false } -http = { version = "0.2.9", default-features = false } +http = { version = "0.2.12", default-features = false } http-serde = "1.1.3" http-body = { version = "0.4.5", default-features = false } hyper = { version = "0.14.28", default-features = false, features = ["client", "runtime", "http1", "http2", "server", "stream"] } From 6e802fc180e855ab0b9ccefa8cfc1280d31e40e8 Mon Sep 17 00:00:00 2001 From: Chris Arnott Date: Mon, 11 Nov 2024 14:27:22 +0000 Subject: [PATCH 15/17] use try_append for headers --- src/sources/http_server.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/sources/http_server.rs b/src/sources/http_server.rs index ceb9a4fef9aa3..fd3c9d479c1fd 100644 --- a/src/sources/http_server.rs +++ b/src/sources/http_server.rs @@ -551,7 +551,9 @@ impl HttpSource for SimpleHttpSource { for (key, values) in &self.custom_response_headers { for value in values { - header_map.append(key.clone(), value.clone()); + header_map + .try_append(key.clone(), value.clone()) + .expect("Failed to append header. Too many custom http headers specified in custom_response_headers config."); } } Box::new(response) From 21c5c4ccdd19dce21150b4e36f3dd13cb2fff30f Mon Sep 17 00:00:00 2001 From: Chris Arnott Date: Mon, 11 Nov 2024 16:00:49 +0000 Subject: [PATCH 16/17] Pass the error up --- src/sources/http_server.rs | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/sources/http_server.rs b/src/sources/http_server.rs index fd3c9d479c1fd..5acfe2c4b25e9 100644 --- a/src/sources/http_server.rs +++ b/src/sources/http_server.rs @@ -378,11 +378,22 @@ impl SourceConfig for SimpleHttpConfig { .build()? .with_log_namespace(log_namespace); + let custom_response_headers = + self.custom_response_headers.clone().into_iter() + .map(|(k, v)| { + let parsed_key = + k.parse::().map_err(|e| format!("Failed to parse header key {}: {}", k, e))?; + let parsed_values = + v.into_iter() + .map(|v| v.parse::().map_err(|e| format!("Failed to parse header value {}: {}", v, e))) + .collect::>()?; + Ok::<(HeaderName, Vec), String>((parsed_key, parsed_values)) + }) + .collect::>()?; + let source = SimpleHttpSource { headers: build_param_matcher(&remove_duplicates(self.headers.clone(), "headers"))?, - custom_response_headers: self.custom_response_headers.clone().into_iter().map(|(k, v)| { - (k.parse().unwrap(), v.into_iter().map(|v| v.parse().unwrap()).collect()) - }).collect(), + custom_response_headers, query_parameters: build_param_matcher(&remove_duplicates( self.query_parameters.clone(), "query_parameters", From bbce50d723a58a82e45c2ce9c94dfb81e57ff3fb Mon Sep 17 00:00:00 2001 From: Chris Arnott Date: Fri, 13 Dec 2024 10:22:58 +0000 Subject: [PATCH 17/17] Formatting --- src/sources/http_server.rs | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/src/sources/http_server.rs b/src/sources/http_server.rs index 5acfe2c4b25e9..bb0fe801905da 100644 --- a/src/sources/http_server.rs +++ b/src/sources/http_server.rs @@ -378,18 +378,24 @@ impl SourceConfig for SimpleHttpConfig { .build()? .with_log_namespace(log_namespace); - let custom_response_headers = - self.custom_response_headers.clone().into_iter() - .map(|(k, v)| { - let parsed_key = - k.parse::().map_err(|e| format!("Failed to parse header key {}: {}", k, e))?; - let parsed_values = - v.into_iter() - .map(|v| v.parse::().map_err(|e| format!("Failed to parse header value {}: {}", v, e))) - .collect::>()?; - Ok::<(HeaderName, Vec), String>((parsed_key, parsed_values)) - }) - .collect::>()?; + let custom_response_headers = self + .custom_response_headers + .clone() + .into_iter() + .map(|(k, v)| { + let parsed_key = k + .parse::() + .map_err(|e| format!("Failed to parse header key {}: {}", k, e))?; + let parsed_values = v + .into_iter() + .map(|v| { + v.parse::() + .map_err(|e| format!("Failed to parse header value {}: {}", v, e)) + }) + .collect::>()?; + Ok::<(HeaderName, Vec), String>((parsed_key, parsed_values)) + }) + .collect::>()?; let source = SimpleHttpSource { headers: build_param_matcher(&remove_duplicates(self.headers.clone(), "headers"))?,