diff --git a/pom.xml b/pom.xml
index caea56440..b9984bd2f 100644
--- a/pom.xml
+++ b/pom.xml
@@ -62,6 +62,7 @@
simpleclient_jetty
simpleclient_jetty_jdk8
simpleclient_vertx
+ simpleclient_undertow
benchmark
diff --git a/simpleclient_undertow/pom.xml b/simpleclient_undertow/pom.xml
new file mode 100644
index 000000000..90c8f2a65
--- /dev/null
+++ b/simpleclient_undertow/pom.xml
@@ -0,0 +1,97 @@
+
+
+ 4.0.0
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 3.6.1
+
+ 1.7
+ 1.7
+
+
+
+
+
+
+ io.prometheus
+ parent
+ 0.0.27-SNAPSHOT
+
+
+ io.prometheus
+ simpleclient_undertow
+ bundle
+
+ Prometheus Java Simpleclient Undertow
+
+ Undertow Web Handler exporter for the simpleclient
+
+
+
+
+ The Apache Software License, Version 2.0
+ http://www.apache.org/licenses/LICENSE-2.0.txt
+ repo
+
+
+
+
+
+ kislitsyn
+ Anton Kislitsyn
+ anton.v.kislitsyn@gmail.com
+
+
+
+
+ UTF-8
+
+
+
+
+ io.prometheus
+ simpleclient
+ 0.0.27-SNAPSHOT
+
+
+ io.prometheus
+ simpleclient_common
+ 0.0.27-SNAPSHOT
+
+
+ io.undertow
+ undertow-core
+ 1.4.20.Final
+ provided
+
+
+
+ io.undertow
+ undertow-servlet
+ 1.4.20.Final
+ test
+
+
+ org.apache.commons
+ commons-lang3
+ 3.4
+ test
+
+
+ junit
+ junit
+ 4.11
+ test
+
+
+ org.assertj
+ assertj-core
+ 2.6.0
+ test
+
+
+
diff --git a/simpleclient_undertow/src/main/java/io/prometheus/client/undertow/MetricsHandler.java b/simpleclient_undertow/src/main/java/io/prometheus/client/undertow/MetricsHandler.java
new file mode 100644
index 000000000..0899d3978
--- /dev/null
+++ b/simpleclient_undertow/src/main/java/io/prometheus/client/undertow/MetricsHandler.java
@@ -0,0 +1,85 @@
+package io.prometheus.client.undertow;
+
+import io.prometheus.client.CollectorRegistry;
+import io.prometheus.client.exporter.common.TextFormat;
+import io.undertow.server.HttpHandler;
+import io.undertow.server.HttpServerExchange;
+import io.undertow.server.handlers.BlockingHandler;
+import io.undertow.util.Headers;
+
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+import java.util.Collections;
+import java.util.Deque;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Metrics Handler for Undertow.
+ *
+ * Example usage:
+ *
+ * {@code
+ * Undertow.builder()
+ * .addHttpListener(port, host)
+ * .setHandler(path().addExactPath("/metrics", new MetricsHandler()))
+ * .build();
+ * }
+ *
+ */
+public class MetricsHandler implements HttpHandler {
+
+ private final HttpHandler handler;
+
+ /**
+ * Construct a MetricsHandler for the default registry
+ */
+ public MetricsHandler() {
+ this(CollectorRegistry.defaultRegistry);
+ }
+
+ /**
+ * Construct a MetricsHandler for the given registry
+ *
+ * @param registry collector registry
+ */
+ public MetricsHandler(CollectorRegistry registry) {
+ handler = new BlockingHandler(new Handler(registry));
+ }
+
+
+ @Override
+ public void handleRequest(HttpServerExchange exchange) throws Exception {
+ handler.handleRequest(exchange);
+ }
+
+ private class Handler implements HttpHandler {
+
+ private static final String NAME_PARAMETER = "name[]";
+
+ private final CollectorRegistry registry;
+
+ public Handler(CollectorRegistry registry) {
+ this.registry = registry;
+ }
+
+ @Override
+ public void handleRequest(HttpServerExchange exchange) throws Exception {
+ exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, TextFormat.CONTENT_TYPE_004);
+ try (Writer writer = new OutputStreamWriter(exchange.getOutputStream())) {
+ TextFormat.write004(writer, registry.filteredMetricFamilySamples(parse(exchange.getQueryParameters())));
+ writer.flush();
+ }
+ }
+
+ private Set parse(Map> params) {
+ Deque includedParam = params.get(NAME_PARAMETER);
+ if (includedParam == null) {
+ return Collections.emptySet();
+ } else {
+ return new HashSet<>(includedParam);
+ }
+ }
+ }
+}
diff --git a/simpleclient_undertow/src/test/java/io/prometheus/client/undertow/MetricsHandlerTest.java b/simpleclient_undertow/src/test/java/io/prometheus/client/undertow/MetricsHandlerTest.java
new file mode 100644
index 000000000..9d7209379
--- /dev/null
+++ b/simpleclient_undertow/src/test/java/io/prometheus/client/undertow/MetricsHandlerTest.java
@@ -0,0 +1,103 @@
+package io.prometheus.client.undertow;
+
+import io.prometheus.client.CollectorRegistry;
+import io.prometheus.client.Gauge;
+import io.undertow.Undertow;
+import io.undertow.server.HttpHandler;
+import org.apache.commons.lang3.StringUtils;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.ServerSocket;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.Scanner;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class MetricsHandlerTest {
+
+ private static Undertow server;
+ private static URI testURI;
+
+ @BeforeClass
+ public static void setUp() throws IOException, URISyntaxException {
+
+ String host = InetAddress.getLocalHost().getHostAddress();
+
+ ServerSocket socket = new ServerSocket(0);
+ Integer port = socket.getLocalPort();
+ socket.close();
+
+ CollectorRegistry registry = new CollectorRegistry();
+ HttpHandler handler = new MetricsHandler(registry);
+
+ server = Undertow.builder()
+ .addHttpListener(port, host)
+ .setHandler(handler)
+ .build();
+
+ testURI = new URI("http://" + host + ":" + port);
+
+ server.start();
+
+ Gauge.build("a", "a help").register(registry);
+ Gauge.build("b", "b help").register(registry);
+ Gauge.build("c", "c help").register(registry);
+ }
+
+ @AfterClass
+ public static void tearDown() {
+ server.stop();
+ }
+
+ @Test
+ public void metricsRequest_shouldReturnMetrics() {
+ String out = makeRequest();
+
+ assertThat(out).contains("a 0.0");
+ assertThat(out).contains("b 0.0");
+ assertThat(out).contains("c 0.0");
+ }
+
+ @Test
+ public void metricsRequest_shouldAllowFilteringMetrics() {
+ String out = makeRequest("name[]=b", "name[]=c");
+
+ assertThat(out).doesNotContain("a 0.0");
+ assertThat(out).contains("b 0.0");
+ assertThat(out).contains("c 0.0");
+ }
+
+
+ private String makeRequest(String... values) {
+ URI uri = testURI;
+ if (values.length > 0) {
+ uri = createURI(testURI, values);
+ }
+
+ try (Scanner scanner = new Scanner(uri.toURL().openStream(), "UTF-8").useDelimiter("\\A")) {
+ return scanner.next();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private URI createURI(URI uri, String[] values) {
+ String query = StringUtils.join(values, "&");
+ try {
+ return new URI(uri.getScheme(),
+ uri.getUserInfo(),
+ uri.getHost(),
+ uri.getPort(),
+ uri.getPath(),
+ query,
+ uri.getFragment());
+ } catch (URISyntaxException e) {
+ throw new IllegalArgumentException(e);
+ }
+ }
+}