io.prometheus.client.exporter.HTTPServer Maven / Gradle / Ivy
The newest version!
package io.prometheus.client.exporter;
import io.prometheus.client.CollectorRegistry;
import io.prometheus.client.exporter.common.TextFormat;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.net.HttpURLConnection;
import java.net.InetSocketAddress;
import java.net.URLDecoder;
import java.util.List;
import java.util.Set;
import java.util.HashSet;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;
import java.util.concurrent.FutureTask;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.zip.GZIPOutputStream;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;
import com.sun.net.httpserver.HttpExchange;
/**
* Expose Prometheus metrics using a plain Java HttpServer.
*
* Example Usage:
*
* {@code
* HTTPServer server = new HTTPServer(1234);
* }
*
* */
public class HTTPServer {
private static class LocalByteArray extends ThreadLocal {
protected ByteArrayOutputStream initialValue()
{
return new ByteArrayOutputStream(1 << 20);
}
}
static class HTTPMetricHandler implements HttpHandler {
private CollectorRegistry registry;
private final LocalByteArray response = new LocalByteArray();
HTTPMetricHandler(CollectorRegistry registry) {
this.registry = registry;
}
public void handle(HttpExchange t) throws IOException {
String query = t.getRequestURI().getRawQuery();
ByteArrayOutputStream response = this.response.get();
response.reset();
OutputStreamWriter osw = new OutputStreamWriter(response);
TextFormat.write004(osw,
registry.filteredMetricFamilySamples(parseQuery(query)));
osw.flush();
osw.close();
response.flush();
response.close();
t.getResponseHeaders().set("Content-Type",
TextFormat.CONTENT_TYPE_004);
if (shouldUseCompression(t)) {
t.getResponseHeaders().set("Content-Encoding", "gzip");
t.sendResponseHeaders(HttpURLConnection.HTTP_OK, 0);
final GZIPOutputStream os = new GZIPOutputStream(t.getResponseBody());
response.writeTo(os);
os.close();
} else {
t.getResponseHeaders().set("Content-Length",
String.valueOf(response.size()));
t.sendResponseHeaders(HttpURLConnection.HTTP_OK, response.size());
response.writeTo(t.getResponseBody());
}
t.close();
}
}
protected static boolean shouldUseCompression(HttpExchange exchange) {
List encodingHeaders = exchange.getRequestHeaders().get("Accept-Encoding");
if (encodingHeaders == null) return false;
for (String encodingHeader : encodingHeaders) {
String[] encodings = encodingHeader.split(",");
for (String encoding : encodings) {
if (encoding.trim().toLowerCase().equals("gzip")) {
return true;
}
}
}
return false;
}
protected static Set parseQuery(String query) throws IOException {
Set names = new HashSet();
if (query != null) {
String[] pairs = query.split("&");
for (String pair : pairs) {
int idx = pair.indexOf("=");
if (idx != -1 && URLDecoder.decode(pair.substring(0, idx), "UTF-8").equals("name[]")) {
names.add(URLDecoder.decode(pair.substring(idx + 1), "UTF-8"));
}
}
}
return names;
}
static class NamedDaemonThreadFactory implements ThreadFactory {
private static final AtomicInteger POOL_NUMBER = new AtomicInteger(1);
private final int poolNumber = POOL_NUMBER.getAndIncrement();
private final AtomicInteger threadNumber = new AtomicInteger(1);
private final ThreadFactory delegate;
private final boolean daemon;
NamedDaemonThreadFactory(ThreadFactory delegate, boolean daemon) {
this.delegate = delegate;
this.daemon = daemon;
}
@Override
public Thread newThread(Runnable r) {
Thread t = delegate.newThread(r);
t.setName(String.format("prometheus-http-%d-%d", poolNumber, threadNumber.getAndIncrement()));
t.setDaemon(daemon);
return t;
}
static ThreadFactory defaultThreadFactory(boolean daemon) {
return new NamedDaemonThreadFactory(Executors.defaultThreadFactory(), daemon);
}
}
protected final HttpServer server;
protected final ExecutorService executorService;
/**
* Start a HTTP server serving Prometheus metrics from the given registry using the given {@link HttpServer}.
* The {@code httpServer} is expected to already be bound to an address
*/
public HTTPServer(HttpServer httpServer, CollectorRegistry registry, boolean daemon) throws IOException {
if (httpServer.getAddress() == null)
throw new IllegalArgumentException("HttpServer hasn't been bound to an address");
server = httpServer;
HttpHandler mHandler = new HTTPMetricHandler(registry);
server.createContext("/", mHandler);
server.createContext("/metrics", mHandler);
executorService = Executors.newFixedThreadPool(5, NamedDaemonThreadFactory.defaultThreadFactory(daemon));
server.setExecutor(executorService);
start(daemon);
}
/**
* Start a HTTP server serving Prometheus metrics from the given registry.
*/
public HTTPServer(InetSocketAddress addr, CollectorRegistry registry, boolean daemon) throws IOException {
this(HttpServer.create(addr, 3), registry, daemon);
}
/**
* Start a HTTP server serving Prometheus metrics from the given registry using non-daemon threads.
*/
public HTTPServer(InetSocketAddress addr, CollectorRegistry registry) throws IOException {
this(addr, registry, false);
}
/**
* Start a HTTP server serving the default Prometheus registry.
*/
public HTTPServer(int port, boolean daemon) throws IOException {
this(new InetSocketAddress(port), CollectorRegistry.defaultRegistry, daemon);
}
/**
* Start a HTTP server serving the default Prometheus registry using non-daemon threads.
*/
public HTTPServer(int port) throws IOException {
this(port, false);
}
/**
* Start a HTTP server serving the default Prometheus registry.
*/
public HTTPServer(String host, int port, boolean daemon) throws IOException {
this(new InetSocketAddress(host, port), CollectorRegistry.defaultRegistry, daemon);
}
/**
* Start a HTTP server serving the default Prometheus registry using non-daemon threads.
*/
public HTTPServer(String host, int port) throws IOException {
this(new InetSocketAddress(host, port), CollectorRegistry.defaultRegistry, false);
}
/**
* Start a HTTP server by making sure that its background thread inherit proper daemon flag.
*/
private void start(boolean daemon) {
if (daemon == Thread.currentThread().isDaemon()) {
server.start();
} else {
FutureTask startTask = new FutureTask(new Runnable() {
@Override
public void run() {
server.start();
}
}, null);
NamedDaemonThreadFactory.defaultThreadFactory(daemon).newThread(startTask).start();
try {
startTask.get();
} catch (ExecutionException e) {
throw new RuntimeException("Unexpected exception on starting HTTPSever", e);
} catch (InterruptedException e) {
// This is possible only if the current tread has been interrupted,
// but in real use cases this should not happen.
// In any case, there is nothing to do, except to propagate interrupted flag.
Thread.currentThread().interrupt();
}
}
}
/**
* Stop the HTTP server.
*/
public void stop() {
server.stop(0);
executorService.shutdown(); // Free any (parked/idle) threads in pool
}
/**
* Gets the port number.
*/
public int getPort() {
return server.getAddress().getPort();
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy