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.io.PrintWriter; 010import java.io.StringWriter; 011import java.net.URI; 012import java.nio.charset.StandardCharsets; 013import java.util.Collections; 014import java.util.Enumeration; 015import java.util.List; 016import java.util.logging.Level; 017import java.util.logging.Logger; 018 019public class HttpExchangeAdapter implements PrometheusHttpExchange { 020 021 private final HttpExchange httpExchange; 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 = httpExchange; 028 } 029 030 public class HttpRequest implements PrometheusHttpRequest { 031 032 @Override 033 public String getQueryString() { 034 return httpExchange.getRequestURI().getRawQuery(); 035 } 036 037 @Override 038 public Enumeration<String> getHeaders(String name) { 039 List<String> headers = httpExchange.getRequestHeaders().get(name); 040 if (headers == null) { 041 return Collections.emptyEnumeration(); 042 } else { 043 return Collections.enumeration(headers); 044 } 045 } 046 047 @Override 048 public String getMethod() { 049 return httpExchange.getRequestMethod(); 050 } 051 052 @Override 053 public String getRequestPath() { 054 URI requestURI = httpExchange.getRequestURI(); 055 String uri = requestURI.toString(); 056 int qx = uri.indexOf('?'); 057 if (qx != -1) { 058 uri = uri.substring(0, qx); 059 } 060 return uri; 061 } 062 } 063 064 public class HttpResponse implements PrometheusHttpResponse { 065 066 @Override 067 public void setHeader(String name, String value) { 068 httpExchange.getResponseHeaders().set(name, value); 069 } 070 071 @Override 072 public OutputStream sendHeadersAndGetBody(int statusCode, int contentLength) 073 throws IOException { 074 if (responseSent) { 075 throw new IOException("Cannot send multiple HTTP responses for a single HTTP exchange."); 076 } 077 responseSent = true; 078 httpExchange.sendResponseHeaders(statusCode, contentLength); 079 return httpExchange.getResponseBody(); 080 } 081 } 082 083 @Override 084 public HttpRequest getRequest() { 085 return request; 086 } 087 088 @Override 089 public HttpResponse getResponse() { 090 return response; 091 } 092 093 @Override 094 public void handleException(IOException e) throws IOException { 095 sendErrorResponseWithStackTrace(e); 096 } 097 098 @Override 099 public void handleException(RuntimeException e) { 100 sendErrorResponseWithStackTrace(e); 101 } 102 103 private void sendErrorResponseWithStackTrace(Exception requestHandlerException) { 104 if (!responseSent) { 105 responseSent = true; 106 try { 107 StringWriter stringWriter = new StringWriter(); 108 PrintWriter printWriter = new PrintWriter(stringWriter); 109 printWriter.write("An Exception occurred while scraping metrics: "); 110 requestHandlerException.printStackTrace(new PrintWriter(printWriter)); 111 byte[] stackTrace = stringWriter.toString().getBytes(StandardCharsets.UTF_8); 112 httpExchange.getResponseHeaders().set("Content-Type", "text/plain; charset=utf-8"); 113 httpExchange.sendResponseHeaders(500, stackTrace.length); 114 httpExchange.getResponseBody().write(stackTrace); 115 } catch (Exception errorWriterException) { 116 // We want to avoid logging so that we don't mess with application logs when the HTTPServer 117 // is used in a Java agent. 118 // However, if we can't even send an error response to the client there's nothing we can do 119 // but logging a message. 120 Logger.getLogger(this.getClass().getName()) 121 .log( 122 Level.SEVERE, 123 "The Prometheus metrics HTTPServer caught an Exception during scrape and " 124 + "failed to send an error response to the client.", 125 errorWriterException); 126 Logger.getLogger(this.getClass().getName()) 127 .log( 128 Level.SEVERE, 129 "Original Exception that caused the Prometheus scrape error:", 130 requestHandlerException); 131 } 132 } else { 133 // If the exception occurs after response headers have been sent, it's too late to respond 134 // with HTTP 500. 135 Logger.getLogger(this.getClass().getName()) 136 .log( 137 Level.SEVERE, 138 "The Prometheus metrics HTTPServer caught an Exception while trying to send " 139 + "the metrics response.", 140 requestHandlerException); 141 } 142 } 143 144 @Override 145 public void close() { 146 httpExchange.close(); 147 } 148}