From cb839125e0e7d9273329f60ea4c2ea217149c759 Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Mon, 30 Mar 2026 16:50:45 +0000 Subject: [PATCH] fix: scope version parsing to the openmetrics media type in Accept header parseOpenMetricsVersion was splitting the entire Accept header by ";" which meant version parameters from other media types (e.g. text/plain; version=0.0.4) could be incorrectly attributed to openmetrics-text. Now splits by "," first to isolate individual media types, finds the openmetrics-text entry, then parses only its parameters for version. Signed-off-by: Gregor Zeitlinger --- .../expositionformats/ExpositionFormats.java | 19 +++++++----- .../ExpositionFormatsTest.java | 31 +++++++++++++++++++ 2 files changed, 43 insertions(+), 7 deletions(-) diff --git a/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/ExpositionFormats.java b/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/ExpositionFormats.java index 10556859d..3863b5b74 100644 --- a/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/ExpositionFormats.java +++ b/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/ExpositionFormats.java @@ -108,14 +108,19 @@ private static String parseOpenMetricsVersion(@Nullable String acceptHeader) { if (acceptHeader == null) { return null; } - for (String mediaType : acceptHeader.split(";")) { - String[] tokens = mediaType.split("="); - if (tokens.length == 2) { - String key = tokens[0].trim(); - String value = tokens[1].trim(); - if (key.equals("version")) { - return value; + for (String mediaType : acceptHeader.split(",")) { + if (mediaType.contains("application/openmetrics-text")) { + for (String param : mediaType.split(";")) { + String[] tokens = param.split("="); + if (tokens.length == 2) { + String key = tokens[0].trim(); + String value = tokens[1].trim(); + if (key.equals("version")) { + return value; + } + } } + return null; } } return null; diff --git a/prometheus-metrics-exposition-textformats/src/test/java/io/prometheus/metrics/expositionformats/ExpositionFormatsTest.java b/prometheus-metrics-exposition-textformats/src/test/java/io/prometheus/metrics/expositionformats/ExpositionFormatsTest.java index 66e49b385..e0782251e 100644 --- a/prometheus-metrics-exposition-textformats/src/test/java/io/prometheus/metrics/expositionformats/ExpositionFormatsTest.java +++ b/prometheus-metrics-exposition-textformats/src/test/java/io/prometheus/metrics/expositionformats/ExpositionFormatsTest.java @@ -231,6 +231,37 @@ void testOM2ContentNegotiationDisabled() { assertThat(writer3).isInstanceOf(OpenMetrics2TextFormatWriter.class); } + @Test + void testOM2ContentNegotiationMultiTypeAcceptHeaderWithoutOMVersion() { + // When the Accept header has multiple media types and only text/plain has a version, + // the openmetrics type should be treated as having no version (use OM1). + PrometheusProperties props = + PrometheusProperties.builder() + .openMetrics2Properties( + OpenMetrics2Properties.builder().enabled(true).contentNegotiation(true).build()) + .build(); + ExpositionFormats formats = ExpositionFormats.init(props); + ExpositionFormatWriter writer = + formats.findWriter("application/openmetrics-text, text/plain; version=0.0.4"); + assertThat(writer).isInstanceOf(OpenMetricsTextFormatWriter.class); + } + + @Test + void testOM2ContentNegotiationMultiTypeAcceptHeaderWithOMVersion() { + // When the Accept header has multiple media types and openmetrics has version=2.0.0, + // the OM2 writer should be selected. + PrometheusProperties props = + PrometheusProperties.builder() + .openMetrics2Properties( + OpenMetrics2Properties.builder().enabled(true).contentNegotiation(true).build()) + .build(); + ExpositionFormats formats = ExpositionFormats.init(props); + ExpositionFormatWriter writer = + formats.findWriter( + "application/openmetrics-text; version=2.0.0, text/plain; version=0.0.4"); + assertThat(writer).isInstanceOf(OpenMetrics2TextFormatWriter.class); + } + @Test void testCounterComplete() throws IOException { String openMetricsText =