001package io.prometheus.metrics.exporter.httpserver; 002 003import com.sun.net.httpserver.HttpExchange; 004import io.prometheus.metrics.exporter.common.PrometheusHttpExchange; 005import io.prometheus.metrics.exporter.common.PrometheusHttpRequest; 006import io.prometheus.metrics.exporter.common.PrometheusHttpResponse; 007import java.io.IOException; 008import java.io.OutputStream; 009import java.net.URI; 010import java.util.Collections; 011import java.util.Enumeration; 012import java.util.List; 013import java.util.logging.Level; 014import java.util.logging.Logger; 015 016public class HttpExchangeAdapter implements PrometheusHttpExchange { 017 018 private static final Logger logger = Logger.getLogger(HttpExchangeAdapter.class.getName()); 019 020 private final HttpExchange httpExchange; 021 private final HttpErrorHandlingPolicy errorHandlingPolicy; 022 private final HttpRequest request = new HttpRequest(); 023 private final HttpResponse response = new HttpResponse(); 024 private volatile boolean responseSent = false; 025 026 public HttpExchangeAdapter(HttpExchange httpExchange) { 027 this(httpExchange, HttpErrorHandlingPolicy.builder().build()); 028 } 029 030 public HttpExchangeAdapter( 031 HttpExchange httpExchange, HttpErrorHandlingPolicy errorHandlingPolicy) { 032 this.httpExchange = httpExchange; 033 this.errorHandlingPolicy = errorHandlingPolicy; 034 } 035 036 public class HttpRequest implements PrometheusHttpRequest { 037 038 @Override 039 public String getQueryString() { 040 return httpExchange.getRequestURI().getRawQuery(); 041 } 042 043 @Override 044 public Enumeration<String> getHeaders(String name) { 045 List<String> headers = httpExchange.getRequestHeaders().get(name); 046 if (headers == null) { 047 return Collections.emptyEnumeration(); 048 } else { 049 return Collections.enumeration(headers); 050 } 051 } 052 053 @Override 054 public String getMethod() { 055 return httpExchange.getRequestMethod(); 056 } 057 058 @Override 059 public String getRequestPath() { 060 URI requestURI = httpExchange.getRequestURI(); 061 String uri = requestURI.toString(); 062 int qx = uri.indexOf('?'); 063 if (qx != -1) { 064 uri = uri.substring(0, qx); 065 } 066 return uri; 067 } 068 } 069 070 public class HttpResponse implements PrometheusHttpResponse { 071 072 @Override 073 public void setHeader(String name, String value) { 074 httpExchange.getResponseHeaders().set(name, value); 075 } 076 077 @Override 078 public OutputStream sendHeadersAndGetBody(int statusCode, int contentLength) 079 throws IOException { 080 if (responseSent) { 081 throw new IOException("Cannot send multiple HTTP responses for a single HTTP exchange."); 082 } 083 responseSent = true; 084 httpExchange.sendResponseHeaders(statusCode, contentLength); 085 return httpExchange.getResponseBody(); 086 } 087 } 088 089 @Override 090 public HttpRequest getRequest() { 091 return request; 092 } 093 094 @Override 095 public HttpResponse getResponse() { 096 return response; 097 } 098 099 @Override 100 public void handleException(IOException e) throws IOException { 101 sendErrorResponse(e); 102 } 103 104 @Override 105 public void handleException(RuntimeException e) { 106 sendErrorResponse(e); 107 } 108 109 private void sendErrorResponse(Exception requestHandlerException) { 110 if (!responseSent) { 111 responseSent = true; 112 byte[] errorResponse = errorHandlingPolicy.getErrorResponse(requestHandlerException); 113 try { 114 httpExchange.getResponseHeaders().set("Content-Type", "text/plain; charset=utf-8"); 115 httpExchange.sendResponseHeaders(500, errorResponse.length); 116 httpExchange.getResponseBody().write(errorResponse); 117 } catch (IOException errorWriterException) { 118 // If we can't even send an error response to the client, logging is the only remaining 119 // signal. 120 logger.log( 121 Level.SEVERE, 122 "The Prometheus metrics HTTPServer caught an Exception during scrape and " 123 + "failed to send an error response to the client.", 124 errorWriterException); 125 if (!errorHandlingPolicy.hasErrorReporter()) { 126 logger.log( 127 Level.SEVERE, 128 "Original Exception that caused the Prometheus scrape error:", 129 requestHandlerException); 130 } 131 } 132 reportException(requestHandlerException); 133 } else { 134 // If the exception occurs after response headers have been sent, it's too late to respond 135 // with HTTP 500. 136 if (errorHandlingPolicy.hasErrorReporter()) { 137 reportException(requestHandlerException); 138 } else { 139 logger.log( 140 Level.SEVERE, 141 "The Prometheus metrics HTTPServer caught an Exception while trying to send " 142 + "the metrics response.", 143 requestHandlerException); 144 } 145 } 146 } 147 148 private void reportException(Exception requestHandlerException) { 149 try { 150 errorHandlingPolicy.report(requestHandlerException); 151 } catch (RuntimeException ignored) { 152 // A caller-supplied reporter must not prevent the safe error response from being sent or 153 // implicitly fall back to application logging. 154 } 155 } 156 157 @Override 158 public void close() { 159 httpExchange.close(); 160 } 161}