Skip to content

Commit 6e0e81c

Browse files
committed
fix: prevent exception when using custom root metrics path
Signed-off-by: David Sondermann <david.sondermann@hivemq.com>
1 parent 701e49d commit 6e0e81c

File tree

2 files changed

+107
-55
lines changed
  • prometheus-metrics-exporter-httpserver/src

2 files changed

+107
-55
lines changed

prometheus-metrics-exporter-httpserver/src/main/java/io/prometheus/metrics/exporter/httpserver/HTTPServer.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,11 +66,21 @@ private HTTPServer(
6666
this.server = httpServer;
6767
this.executorService = executorService;
6868
String metricsPath = getMetricsPath(metricsHandlerPath);
69+
try {
70+
server.removeContext("/");
71+
} catch (IllegalArgumentException e) {
72+
// context "/" not registered yet, ignore
73+
}
6974
registerHandler(
7075
"/",
7176
defaultHandler == null ? new DefaultHandler(metricsPath) : defaultHandler,
7277
authenticator,
7378
authenticatedSubjectAttributeName);
79+
try {
80+
server.removeContext(metricsPath);
81+
} catch (IllegalArgumentException e) {
82+
// context metricsPath not registered yet, ignore
83+
}
7484
registerHandler(
7585
metricsPath,
7686
new MetricsHandler(config, registry),

prometheus-metrics-exporter-httpserver/src/test/java/io/prometheus/metrics/exporter/httpserver/HTTPServerTest.java

Lines changed: 97 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -10,26 +10,42 @@
1010
import com.sun.net.httpserver.HttpsConfigurator;
1111
import io.prometheus.metrics.model.registry.PrometheusRegistry;
1212
import io.prometheus.metrics.model.registry.PrometheusScrapeRequest;
13+
import io.prometheus.metrics.model.snapshots.CounterSnapshot;
14+
import io.prometheus.metrics.model.snapshots.CounterSnapshot.CounterDataPointSnapshot;
15+
import io.prometheus.metrics.model.snapshots.Labels;
16+
import io.prometheus.metrics.model.snapshots.MetricMetadata;
1317
import io.prometheus.metrics.model.snapshots.MetricSnapshots;
1418
import java.io.IOException;
1519
import java.lang.reflect.Method;
1620
import java.net.InetAddress;
17-
import java.net.InetSocketAddress;
18-
import java.net.Socket;
19-
import java.nio.charset.StandardCharsets;
20-
import java.security.NoSuchAlgorithmException;
21+
import java.net.URI;
22+
import java.net.http.HttpClient;
23+
import java.net.http.HttpRequest;
24+
import java.net.http.HttpResponse;
2125
import java.security.Principal;
26+
import java.util.List;
2227
import java.util.concurrent.Executors;
2328
import javax.net.ssl.SSLContext;
2429
import javax.security.auth.Subject;
30+
import org.junit.jupiter.api.BeforeEach;
2531
import org.junit.jupiter.api.Test;
2632

2733
public class HTTPServerTest {
2834

35+
private PrometheusRegistry registry;
36+
37+
@BeforeEach
38+
void setUp() {
39+
final MetricMetadata metadata = new MetricMetadata("my-counter");
40+
final CounterDataPointSnapshot dataPointSnapshot =
41+
new CounterDataPointSnapshot(1.0, Labels.EMPTY, null, System.currentTimeMillis());
42+
43+
registry = new PrometheusRegistry();
44+
registry.register(() -> new CounterSnapshot(metadata, List.of(dataPointSnapshot)));
45+
}
46+
2947
@Test
30-
@SuppressWarnings({"removal"})
3148
public void testSubjectDoAs() throws Exception {
32-
3349
final String user = "joe";
3450
final Subject subject = new Subject();
3551
subject.getPrincipals().add(() -> user);
@@ -66,61 +82,61 @@ public Result authenticate(HttpExchange exchange) {
6682
.authenticatedSubjectAttributeName("aa")
6783
.buildAndStart();
6884

69-
run(server, "204", "/");
85+
run(server, "/", 204, "");
7086
}
7187

72-
private static void run(HTTPServer server, String expected, String path) throws IOException {
73-
try (Socket socket = new Socket()) {
74-
socket.connect(new InetSocketAddress("localhost", server.getPort()));
75-
76-
socket
77-
.getOutputStream()
78-
.write(("GET " + path + " HTTP/1.1 \r\n").getBytes(StandardCharsets.UTF_8));
79-
socket.getOutputStream().write("HOST: localhost \r\n\r\n".getBytes(StandardCharsets.UTF_8));
80-
socket.getOutputStream().flush();
81-
82-
String actualResponse = "";
83-
byte[] resp = new byte[500];
84-
int read = socket.getInputStream().read(resp, 0, resp.length);
85-
if (read > 0) {
86-
actualResponse = new String(resp, 0, read, StandardCharsets.UTF_8);
87-
}
88-
assertThat(actualResponse).contains(expected);
89-
}
88+
@Test
89+
void defaultHandler() throws Exception {
90+
run(
91+
HTTPServer.builder().port(0).buildAndStart(),
92+
"/",
93+
200,
94+
"<title>Prometheus Java Client</title>");
9095
}
9196

9297
@Test
93-
void defaultHandler() throws IOException {
94-
run(HTTPServer.builder().port(0).buildAndStart(), "200", "/");
98+
void metrics() throws Exception {
99+
run(
100+
HTTPServer.builder()
101+
.port(0)
102+
.registry(registry)
103+
.executorService(Executors.newFixedThreadPool(1))
104+
.buildAndStart(),
105+
"/metrics",
106+
200,
107+
"my_counter_total 1.0");
95108
}
96109

97110
@Test
98-
void metrics() throws IOException {
111+
void metricsCustomPath() throws Exception {
99112
run(
100113
HTTPServer.builder()
101114
.port(0)
102-
.registry(new PrometheusRegistry())
115+
.registry(registry)
116+
.metricsHandlerPath("/my-metrics")
103117
.executorService(Executors.newFixedThreadPool(1))
104118
.buildAndStart(),
105-
"200",
106-
"/metrics");
119+
"/my-metrics",
120+
200,
121+
"my_counter_total 1.0");
107122
}
108123

109124
@Test
110-
void metricsCustomPath() throws IOException {
125+
void metricsCustomRootPath() throws Exception {
111126
run(
112127
HTTPServer.builder()
113128
.port(0)
114-
.registry(new PrometheusRegistry())
115-
.metricsHandlerPath("/my-metrics")
129+
.registry(registry)
130+
.metricsHandlerPath("/")
116131
.executorService(Executors.newFixedThreadPool(1))
117132
.buildAndStart(),
118-
"200",
119-
"/my-metrics");
133+
"/",
134+
200,
135+
"my_counter_total 1.0");
120136
}
121137

122138
@Test
123-
void registryThrows() throws IOException {
139+
void registryThrows() throws Exception {
124140
HTTPServer server =
125141
HTTPServer.builder()
126142
.port(0)
@@ -132,11 +148,12 @@ public MetricSnapshots scrape(PrometheusScrapeRequest scrapeRequest) {
132148
}
133149
})
134150
.buildAndStart();
135-
run(server, "500", "/metrics");
151+
run(server, "/metrics", 500, "An Exception occurred while scraping metrics");
136152
}
137153

138154
@Test
139-
void config() throws NoSuchAlgorithmException, IOException {
155+
@SuppressWarnings("resource")
156+
void config() {
140157
assertThatExceptionOfType(IllegalStateException.class)
141158
.isThrownBy(
142159
() ->
@@ -147,45 +164,70 @@ void config() throws NoSuchAlgorithmException, IOException {
147164
.buildAndStart())
148165
.withMessage("cannot configure 'inetAddress' and 'hostname' at the same time");
149166

150-
// ssl doesn't work without in tests
151-
run(
152-
HTTPServer.builder()
153-
.port(0)
154-
.httpsConfigurator(new HttpsConfigurator(SSLContext.getDefault()))
155-
.buildAndStart(),
156-
"",
157-
"/");
167+
// SSL doesn't work in this simple test configuration
168+
assertThatExceptionOfType(IOException.class)
169+
.isThrownBy(
170+
() ->
171+
run(
172+
HTTPServer.builder()
173+
.port(0)
174+
.httpsConfigurator(new HttpsConfigurator(SSLContext.getDefault()))
175+
.buildAndStart(),
176+
"/",
177+
0,
178+
"ignored"));
158179
}
159180

160181
@Test
161-
void health() throws IOException {
162-
run(HTTPServer.builder().port(0).buildAndStart(), "200", "/-/healthy");
182+
void health() throws Exception {
183+
run(HTTPServer.builder().port(0).buildAndStart(), "/-/healthy", 200, "Exporter is healthy.");
163184
}
164185

165186
@Test
166-
void healthEnabled() throws IOException {
187+
void healthEnabled() throws Exception {
167188
HttpHandler handler = exchange -> exchange.sendResponseHeaders(204, -1);
168189
run(
169190
HTTPServer.builder()
170191
.port(0)
171192
.defaultHandler(handler)
172193
.registerHealthHandler(true)
173194
.buildAndStart(),
174-
"200",
175-
"/-/healthy");
195+
"/-/healthy",
196+
200,
197+
"Exporter is healthy.");
176198
}
177199

178200
@Test
179-
void healthDisabled() throws IOException {
201+
void healthDisabled() throws Exception {
180202
HttpHandler handler = exchange -> exchange.sendResponseHeaders(204, -1);
181203
run(
182204
HTTPServer.builder()
183205
.port(0)
184206
.defaultHandler(handler)
185207
.registerHealthHandler(false)
186208
.buildAndStart(),
187-
"204",
188-
"/-/healthy");
209+
"/-/healthy",
210+
204,
211+
"");
212+
}
213+
214+
private static void run(
215+
HTTPServer server, String path, int expectedStatusCode, String expectedBody)
216+
throws Exception {
217+
// we cannot use try-with-resources or even client.close(), or the test will fail with Java 17
218+
@SuppressWarnings("resource")
219+
final HttpClient client = HttpClient.newBuilder().build();
220+
try {
221+
final URI uri = URI.create("http://localhost:%s%s".formatted(server.getPort(), path));
222+
final HttpRequest request = HttpRequest.newBuilder().uri(uri).GET().build();
223+
224+
final HttpResponse<String> response =
225+
client.send(request, HttpResponse.BodyHandlers.ofString());
226+
assertThat(response.statusCode()).isEqualTo(expectedStatusCode);
227+
assertThat(response.body()).contains(expectedBody);
228+
} finally {
229+
server.stop();
230+
}
189231
}
190232

191233
/**

0 commit comments

Comments
 (0)