From 52c6cd2c4bb59e518aa63aa3dedc42453ee586f5 Mon Sep 17 00:00:00 2001 From: ilrudie Date: Fri, 8 Nov 2024 16:06:08 -0500 Subject: [PATCH 1/3] mostly serve metrics with text/plain unless given an openmetrics Accept header Signed-off-by: ilrudie --- src/metrics/server.rs | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/src/metrics/server.rs b/src/metrics/server.rs index bd992360a..1114f5d84 100644 --- a/src/metrics/server.rs +++ b/src/metrics/server.rs @@ -62,7 +62,7 @@ impl Server { async fn handle_metrics( reg: Arc>, - _req: Request, + req: Request, ) -> Response> { let mut buf = String::new(); let reg = reg.lock().expect("mutex"); @@ -73,12 +73,26 @@ async fn handle_metrics( .expect("builder with known status code should not fail"); } + let response_content_type = match req + .headers() + .get(http::header::ACCEPT) + .unwrap_or(&http::HeaderValue::from_str("").expect("static str must parse")) + .to_str() + .unwrap_or_default() + .to_lowercase() + .split(";") + .collect::>() + .first() + { + Some(&"application/openmetrics-text") => { + "application/openmetrics-text;charset=utf-8;version=1.0.0" + } + _ => "text/plain; charset=utf-8", + }; + Response::builder() .status(hyper::StatusCode::OK) - .header( - hyper::header::CONTENT_TYPE, - "application/openmetrics-text;charset=utf-8;version=1.0.0", - ) + .header(hyper::header::CONTENT_TYPE, response_content_type) .body(buf.into()) .expect("builder with known status code should not fail") } From c70f5ee1ef6b154d7f2df7bffa27dab70f9ad97b Mon Sep 17 00:00:00 2001 From: ilrudie Date: Thu, 21 Nov 2024 16:31:13 -0500 Subject: [PATCH 2/3] ability to parse multiple Accept headers Signed-off-by: ilrudie --- src/metrics/server.rs | 47 ++++++++++++++++++++++++++++++------------- 1 file changed, 33 insertions(+), 14 deletions(-) diff --git a/src/metrics/server.rs b/src/metrics/server.rs index 1114f5d84..8df7de438 100644 --- a/src/metrics/server.rs +++ b/src/metrics/server.rs @@ -73,22 +73,25 @@ async fn handle_metrics( .expect("builder with known status code should not fail"); } - let response_content_type = match req + let response_content_type: &str = req .headers() - .get(http::header::ACCEPT) - .unwrap_or(&http::HeaderValue::from_str("").expect("static str must parse")) - .to_str() + .get_all(http::header::ACCEPT) + .iter() + .find_map(|v| { + match v + .to_str() + .unwrap_or_default() + .to_lowercase() + .split(";") + .collect::>() + .first() + { + Some(&"application/openmetrics-text") => Some(ContentType::OpenMetrics), + _ => None, + } + }) .unwrap_or_default() - .to_lowercase() - .split(";") - .collect::>() - .first() - { - Some(&"application/openmetrics-text") => { - "application/openmetrics-text;charset=utf-8;version=1.0.0" - } - _ => "text/plain; charset=utf-8", - }; + .into(); Response::builder() .status(hyper::StatusCode::OK) @@ -96,3 +99,19 @@ async fn handle_metrics( .body(buf.into()) .expect("builder with known status code should not fail") } + +#[derive(Default)] +enum ContentType { + #[default] + PlainText, + OpenMetrics, +} + +impl From for &str { + fn from(c: ContentType) -> Self { + match c { + ContentType::PlainText => "text/plain; charset=utf-8", + ContentType::OpenMetrics => "application/openmetrics-text;charset=utf-8;version=1.0.0", + } + } +} From 18c7c5a24662fc735c377217e3b850f1eaa6e133 Mon Sep 17 00:00:00 2001 From: ilrudie Date: Thu, 21 Nov 2024 19:08:46 -0500 Subject: [PATCH 3/3] refactor to support testing, tests Signed-off-by: ilrudie --- src/metrics/server.rs | 75 ++++++++++++++++++++++++++++++++----------- 1 file changed, 56 insertions(+), 19 deletions(-) diff --git a/src/metrics/server.rs b/src/metrics/server.rs index 8df7de438..0c19641e9 100644 --- a/src/metrics/server.rs +++ b/src/metrics/server.rs @@ -73,25 +73,7 @@ async fn handle_metrics( .expect("builder with known status code should not fail"); } - let response_content_type: &str = req - .headers() - .get_all(http::header::ACCEPT) - .iter() - .find_map(|v| { - match v - .to_str() - .unwrap_or_default() - .to_lowercase() - .split(";") - .collect::>() - .first() - { - Some(&"application/openmetrics-text") => Some(ContentType::OpenMetrics), - _ => None, - } - }) - .unwrap_or_default() - .into(); + let response_content_type = content_type(&req); Response::builder() .status(hyper::StatusCode::OK) @@ -115,3 +97,58 @@ impl From for &str { } } } + +#[inline(always)] +fn content_type(req: &Request) -> &str { + req.headers() + .get_all(http::header::ACCEPT) + .iter() + .find_map(|v| { + match v + .to_str() + .unwrap_or_default() + .to_lowercase() + .split(";") + .collect::>() + .first() + { + Some(&"application/openmetrics-text") => Some(ContentType::OpenMetrics), + _ => None, + } + }) + .unwrap_or_default() + .into() +} + +mod test { + + #[test] + fn test_content_type() { + let plain_text_req = http::Request::new("I want some plain text"); + assert_eq!( + super::content_type(&plain_text_req), + "text/plain; charset=utf-8" + ); + + let openmetrics_req = http::Request::builder() + .header("X-Custom-Beep", "boop") + .header("Accept", "application/json") + .header("Accept", "application/openmetrics-text; other stuff") + .body("I would like openmetrics") + .unwrap(); + assert_eq!( + super::content_type(&openmetrics_req), + "application/openmetrics-text;charset=utf-8;version=1.0.0" + ); + + let unsupported_req_accept = http::Request::builder() + .header("Accept", "application/json") + .body("I would like some json") + .unwrap(); + // asking for something we don't support, fall back to plaintext + assert_eq!( + super::content_type(&unsupported_req_accept), + "text/plain; charset=utf-8" + ) + } +}