From 140c3b53dd67f5298874bbe4fa5df3d9c12fba38 Mon Sep 17 00:00:00 2001 From: Alexandre LEBRUN Date: Tue, 4 Mar 2025 17:27:30 +0100 Subject: [PATCH] expose text Content-Type in embedded http server --- .../embeddedhttp/SunEmbeddedHttpServer.scala | 3 +- .../prometheus/EmbeddedHttpServerSpec.scala | 64 ++++++++++++------- 2 files changed, 43 insertions(+), 24 deletions(-) diff --git a/reporters/kamon-prometheus/src/main/scala/kamon/prometheus/embeddedhttp/SunEmbeddedHttpServer.scala b/reporters/kamon-prometheus/src/main/scala/kamon/prometheus/embeddedhttp/SunEmbeddedHttpServer.scala index 57175fd31..30babd186 100644 --- a/reporters/kamon-prometheus/src/main/scala/kamon/prometheus/embeddedhttp/SunEmbeddedHttpServer.scala +++ b/reporters/kamon-prometheus/src/main/scala/kamon/prometheus/embeddedhttp/SunEmbeddedHttpServer.scala @@ -41,6 +41,7 @@ class SunEmbeddedHttpServer(hostname: String, port: Int, path: String, scrapeSou val bytes = data.getBytes(StandardCharsets.UTF_8) var os: OutputStream = null try { + httpExchange.getResponseHeaders.set("Content-Type", "text/plain; charset=UTF-8") if (shouldUseCompression(httpExchange)) { httpExchange.getResponseHeaders.set("Content-Encoding", "gzip") httpExchange.sendResponseHeaders(200, 0) @@ -51,7 +52,7 @@ class SunEmbeddedHttpServer(hostname: String, port: Int, path: String, scrapeSou httpExchange.sendResponseHeaders(200, bytes.length) os.write(bytes) } - } finally Option(os).map(_.close()) + } finally Option(os).foreach(_.close()) } else httpExchange.sendResponseHeaders(404, -1) } } diff --git a/reporters/kamon-prometheus/src/test/scala/kamon/prometheus/EmbeddedHttpServerSpec.scala b/reporters/kamon-prometheus/src/test/scala/kamon/prometheus/EmbeddedHttpServerSpec.scala index 5338d2753..bc5bf28ff 100644 --- a/reporters/kamon-prometheus/src/test/scala/kamon/prometheus/EmbeddedHttpServerSpec.scala +++ b/reporters/kamon-prometheus/src/test/scala/kamon/prometheus/EmbeddedHttpServerSpec.scala @@ -7,8 +7,9 @@ import org.scalatest.matchers.should.Matchers import org.scalatest.wordspec.AnyWordSpec import java.io.FileNotFoundException -import java.net.URL +import java.net.{URL, URLConnection} import java.util.zip.GZIPInputStream +import scala.jdk.CollectionConverters._ class SunHttpServerSpecSuite extends EmbeddedHttpServerSpecSuite { override def testConfig: Config = ConfigFactory.load() @@ -32,7 +33,7 @@ abstract class EmbeddedHttpServerSpecSuite extends AnyWordSpec "the embedded sun http server" should { "provide no data comment on GET to /metrics when no data loaded yet" in { // act - val metrics = httpGetMetrics("/metrics") + val metrics = httpGetMetrics("/metrics").content // assert metrics shouldBe "# The kamon-prometheus module didn't receive any data just yet.\n" } @@ -41,7 +42,7 @@ abstract class EmbeddedHttpServerSpecSuite extends AnyWordSpec // arrange testee.reportPeriodSnapshot(emptyPeriodSnapshot) // act - val metrics = httpGetMetrics("/metrics") + val metrics = httpGetMetrics("/metrics").content // assert metrics shouldBe "" } @@ -50,7 +51,7 @@ abstract class EmbeddedHttpServerSpecSuite extends AnyWordSpec // arrange testee.reportPeriodSnapshot(counter("jvm.mem")) // act - val metrics = httpGetMetrics("/metrics") + val metrics = httpGetMetrics("/metrics").content // assert metrics shouldBe "# TYPE jvm_mem_total counter\njvm_mem_total 1.0\n" } @@ -60,8 +61,9 @@ abstract class EmbeddedHttpServerSpecSuite extends AnyWordSpec testee.reconfigure(testConfig) testee.reportPeriodSnapshot(counter("jvm.mem")) // act - val metrics = httpGetMetrics("/metrics") + val metrics = httpGetMetrics("/metrics").content // assert + println(metrics) metrics shouldBe "# TYPE jvm_mem_total counter\njvm_mem_total 2.0\n" } @@ -70,42 +72,58 @@ abstract class EmbeddedHttpServerSpecSuite extends AnyWordSpec testee.reportPeriodSnapshot(counter("jvm.mem")) // act val metrics = httpGetMetrics("/metrics") - val gzippedMetrics = httpGetGzippedMetrics("/metrics") + val gzippedMetrics = httpGetMetrics("/metrics", useGzipEncoding = true) + // assert + metrics.contentLength should be > gzippedMetrics.contentLength + } + + "property set Content-Type" in { + // arrange + testee.reportPeriodSnapshot(counter("jvm.mem")) + // act + val metrics = httpGetMetrics("/metrics") + val gzippedMetrics = httpGetMetrics("/metrics", useGzipEncoding = true) // assert - metrics.length should be > gzippedMetrics.length + metrics.headers("Content-type").head shouldBe "text/plain; charset=UTF-8" + gzippedMetrics.headers("Content-type").head shouldBe "text/plain; charset=UTF-8" } "respect the path configuration" in { - httpGetMetrics("/metrics") should not be empty + httpGetMetrics("/metrics").content should not be empty assertThrows[FileNotFoundException] { httpGetMetrics("/new-metrics") } testee.reconfigure(changeEndpoint("/new-metrics")) - httpGetMetrics("/new-metrics") should not be empty + httpGetMetrics("/new-metrics").content should not be empty assertThrows[FileNotFoundException] { - httpGetMetrics("/metrics") + httpGetMetrics("/metrics").content } } } - private def httpGetMetrics(endpoint: String): String = { - val url = new URL(s"http://127.0.0.1:$port$endpoint") - val src = scala.io.Source.fromURL(url) - try src.mkString - finally src.close() - } + private case class Result(content: String, headers: Map[String, List[String]], contentLength: Int) - private def httpGetGzippedMetrics(endpoint: String): String = { + private def httpGetMetrics(endpoint: String, useGzipEncoding: Boolean = false): Result = { val url = new URL(s"http://127.0.0.1:$port$endpoint") val connection = url.openConnection - connection.setRequestProperty("Accept-Encoding", "gzip") - val gzipStream = new GZIPInputStream(connection.getInputStream) - val src = scala.io.Source.fromInputStream(gzipStream) - connection.getRequestProperty("Accept-Encoding") shouldBe "gzip" - try src.getLines.mkString - finally gzipStream.close() + val stream = if (useGzipEncoding) { + connection.setRequestProperty("Accept-Encoding", "gzip") + new GZIPInputStream(connection.getInputStream) + } else connection.getInputStream + val src = scala.io.Source.fromInputStream(stream) + if (useGzipEncoding) { + connection.getRequestProperty("Accept-Encoding") shouldBe "gzip" + } + try { + val content = src.mkString + Result( + content, + connection.getHeaderFields.asScala.toMap.view.mapValues(_.asScala.toList).toMap, + connection.getContentLength + ) + } finally stream.close() } private def changeEndpoint(path: String): Config = {