001package io.prometheus.metrics.exporter.httpserver; 002 003import com.sun.net.httpserver.Authenticator; 004import com.sun.net.httpserver.HttpContext; 005import com.sun.net.httpserver.HttpHandler; 006import com.sun.net.httpserver.HttpServer; 007import com.sun.net.httpserver.HttpsConfigurator; 008import com.sun.net.httpserver.HttpsServer; 009import io.prometheus.metrics.config.ExporterHttpServerProperties; 010import io.prometheus.metrics.config.PrometheusProperties; 011import io.prometheus.metrics.model.registry.PrometheusRegistry; 012 013import java.io.Closeable; 014import java.io.IOException; 015import java.net.InetAddress; 016import java.net.InetSocketAddress; 017import java.util.concurrent.ExecutorService; 018import java.util.concurrent.Executors; 019import java.util.concurrent.ThreadPoolExecutor; 020import java.util.concurrent.TimeUnit; 021 022/** 023 * Expose Prometheus metrics using a plain Java HttpServer. 024 * <p> 025 * Example Usage: 026 * <pre> 027 * {@code 028 * HTTPServer server = HTTPServer.newBuilder() 029 * .withPort(9090) 030 * .buildAndStart(); 031 * }</pre> 032 * */ 033public class HTTPServer implements Closeable { 034 035 static { 036 if (!System.getProperties().containsKey("sun.net.httpserver.maxReqTime")) { 037 System.setProperty("sun.net.httpserver.maxReqTime", "60"); 038 } 039 040 if (!System.getProperties().containsKey("sun.net.httpserver.maxRspTime")) { 041 System.setProperty("sun.net.httpserver.maxRspTime", "600"); 042 } 043 } 044 045 protected final HttpServer server; 046 protected final ExecutorService executorService; 047 048 private HTTPServer(PrometheusProperties config, ExecutorService executorService, HttpServer httpServer, PrometheusRegistry registry, Authenticator authenticator, HttpHandler defaultHandler) { 049 if (httpServer.getAddress() == null) { 050 throw new IllegalArgumentException("HttpServer hasn't been bound to an address"); 051 } 052 this.server = httpServer; 053 this.executorService = executorService; 054 registerHandler("/", defaultHandler == null ? new DefaultHandler() : defaultHandler, authenticator); 055 registerHandler("/metrics", new MetricsHandler(config, registry), authenticator); 056 registerHandler("/-/healthy", new HealthyHandler(), authenticator); 057 this.server.start(); 058 } 059 060 private void registerHandler(String path, HttpHandler handler, Authenticator authenticator) { 061 HttpContext context = server.createContext(path, handler); 062 if (authenticator != null) { 063 context.setAuthenticator(authenticator); 064 } 065 } 066 067 /** 068 * Stop the HTTP server. Same as {@link #close()}. 069 */ 070 public void stop() { 071 close(); 072 } 073 074 /** 075 * Stop the HTTPServer. Same as {@link #stop()}. 076 */ 077 @Override 078 public void close() { 079 server.stop(0); 080 executorService.shutdown(); // Free any (parked/idle) threads in pool 081 } 082 083 /** 084 * Gets the port number. 085 * This is useful if you did not specify a port and the server picked a free port automatically. 086 */ 087 public int getPort() { 088 return server.getAddress().getPort(); 089 } 090 091 public static Builder newBuilder() { 092 return new Builder(PrometheusProperties.get()); 093 } 094 095 public static Builder newBuilder(PrometheusProperties config) { 096 return new Builder(config); 097 } 098 099 public static class Builder { 100 101 private final PrometheusProperties config; 102 private Integer port = null; 103 private String hostname = null; 104 private InetAddress inetAddress = null; 105 private InetSocketAddress inetSocketAddress = null; 106 private ExecutorService executorService = null; 107 private PrometheusRegistry registry = null; 108 private ExporterHttpServerProperties properties = null; 109 private Authenticator authenticator = null; 110 private HttpsConfigurator httpsConfigurator = null; 111 private HttpHandler defaultHandler = null; 112 113 private Builder(PrometheusProperties config) { 114 this.config = config; 115 } 116 117 /** 118 * Port to bind to. Default is 0, indicating that a random port will be selected. 119 * You can learn the randomly selected port by calling {@link HTTPServer#getPort()}. 120 */ 121 public Builder withPort(int port) { 122 this.port = port; 123 return this; 124 } 125 126 /** 127 * Use this hostname to resolve the IP address to bind to. 128 * Must not be called together with {@link #withInetAddress(InetAddress)}. 129 * Default is empty, indicating that the HTTPServer binds to the wildcard address. 130 */ 131 public Builder withHostname(String hostname) { 132 this.hostname = hostname; 133 return this; 134 } 135 136 /** 137 * Bind to this IP address. 138 * Must not be called together with {@link #withHostname(String)}. 139 * Default is empty, indicating that the HTTPServer binds to the wildcard address. 140 */ 141 public Builder withInetAddress(InetAddress address) { 142 this.inetAddress = address; 143 return this; 144 } 145 146 /** 147 * Optional: ExecutorService used by the {@code httpServer}. 148 */ 149 public Builder withExecutorService(ExecutorService executorService) { 150 this.executorService = executorService; 151 return this; 152 } 153 154 /** 155 * Optional: Default is {@link PrometheusRegistry#defaultRegistry}. 156 */ 157 public Builder withRegistry(PrometheusRegistry registry) { 158 this.registry = registry; 159 return this; 160 } 161 162 /** 163 * Optional: {@link Authenticator} for authentication. 164 */ 165 public Builder withAuthenticator(Authenticator authenticator) { 166 this.authenticator = authenticator; 167 return this; 168 } 169 170 /** 171 * Optional: {@link HttpsConfigurator} for TLS/SSL 172 */ 173 public Builder withHttpsConfigurator(HttpsConfigurator configurator) { 174 this.httpsConfigurator = configurator; 175 return this; 176 } 177 178 /** 179 * Optional: Override default handler, i.e. the handler that will be registered for the / endpoint. 180 */ 181 public Builder withDefaultHandler(HttpHandler defaultHandler) { 182 this.defaultHandler = defaultHandler; 183 return this; 184 } 185 186 /** 187 * Build and start the HTTPServer. 188 */ 189 public HTTPServer buildAndStart() throws IOException { 190 if (registry == null) { 191 registry = PrometheusRegistry.defaultRegistry; 192 } 193 HttpServer httpServer; 194 if (httpsConfigurator != null) { 195 httpServer = HttpsServer.create(makeInetSocketAddress(), 3); 196 ((HttpsServer)httpServer).setHttpsConfigurator(httpsConfigurator); 197 } else { 198 httpServer = HttpServer.create(makeInetSocketAddress(), 3); 199 } 200 ExecutorService executorService = makeExecutorService(); 201 httpServer.setExecutor(executorService); 202 return new HTTPServer(config, executorService, httpServer, registry, authenticator, defaultHandler); 203 } 204 205 private InetSocketAddress makeInetSocketAddress() { 206 if (inetSocketAddress != null) { 207 assertNull(port, "cannot configure 'inetSocketAddress' and 'port' at the same time"); 208 assertNull(hostname, "cannot configure 'inetSocketAddress' and 'hostname' at the same time"); 209 assertNull(inetAddress, "cannot configure 'inetSocketAddress' and 'inetAddress' at the same time"); 210 return inetSocketAddress; 211 } else if (inetAddress != null) { 212 assertNull(hostname, "cannot configure 'inetAddress' and 'hostname' at the same time"); 213 return new InetSocketAddress(inetAddress, findPort()); 214 } else if (hostname != null) { 215 return new InetSocketAddress(hostname, findPort()); 216 } else { 217 return new InetSocketAddress(findPort()); 218 } 219 } 220 221 private ExecutorService makeExecutorService() { 222 if (executorService != null) { 223 return executorService; 224 } else { 225 ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newCachedThreadPool(NamedDaemonThreadFactory.defaultThreadFactory(true)); 226 executor.setKeepAliveTime(2, TimeUnit.MINUTES); 227 return executor; 228 } 229 } 230 231 private int findPort() { 232 if (properties != null && properties.getPort() != null) { 233 return properties.getPort(); // you can overwrite the hard-coded port with properties. 234 } 235 if (port != null) { 236 return port; 237 } 238 return 0; // random port will be selected 239 } 240 241 private void assertNull(Object o, String msg) { 242 if (o != null) { 243 throw new IllegalStateException(msg); 244 } 245 } 246 } 247}