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; 007 008import java.io.IOException; 009import java.io.OutputStream; 010import java.io.PrintWriter; 011import java.io.StringWriter; 012import java.net.URI; 013import java.nio.charset.StandardCharsets; 014import java.util.Collections; 015import java.util.Enumeration; 016import java.util.List; 017import java.util.logging.Level; 018import java.util.logging.Logger; 019 020public class HttpExchangeAdapter implements PrometheusHttpExchange { 021 022 private final HttpExchange httpExchange; 023 private final HttpRequest request = new HttpRequest(); 024 private final HttpResponse response = new HttpResponse(); 025 private volatile boolean responseSent = false; 026 027 public HttpExchangeAdapter(HttpExchange httpExchange) { 028 this.httpExchange = httpExchange; 029 } 030 031 public class HttpRequest implements PrometheusHttpRequest { 032 033 @Override 034 public String getQueryString() { 035 return httpExchange.getRequestURI().getRawQuery(); 036 } 037 038 @Override 039 public Enumeration<String> getHeaders(String name) { 040 List<String> headers = httpExchange.getRequestHeaders().get(name); 041 if (headers == null) { 042 return Collections.emptyEnumeration(); 043 } else { 044 return Collections.enumeration(headers); 045 } 046 } 047 048 @Override 049 public String getMethod() { 050 return httpExchange.getRequestMethod(); 051 } 052 053 @Override 054 public String getRequestPath() { 055 URI requestURI = httpExchange.getRequestURI(); 056 String uri = requestURI.toString(); 057 int qx = uri.indexOf('?'); 058 if (qx != -1) { 059 uri = uri.substring(0, qx); 060 } 061 return uri; 062 } 063 } 064 065 public class HttpResponse implements PrometheusHttpResponse { 066 067 @Override 068 public void setHeader(String name, String value) { 069 httpExchange.getResponseHeaders().set(name, value); 070 } 071 072 @Override 073 public OutputStream sendHeadersAndGetBody(int statusCode, int contentLength) 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 is used in a Java agent. 117 // However, if we can't even send an error response to the client there's nothing we can do but logging a message. 118 Logger.getLogger(this.getClass().getName()).log(Level.SEVERE, "The Prometheus metrics HTTPServer caught an Exception during scrape and failed to send an error response to the client.", errorWriterException); 119 Logger.getLogger(this.getClass().getName()).log(Level.SEVERE, "Original Exception that caused the Prometheus scrape error:", requestHandlerException); 120 } 121 } else { 122 // If the exception occurs after response headers have been sent, it's too late to respond with HTTP 500. 123 Logger.getLogger(this.getClass().getName()).log(Level.SEVERE, "The Prometheus metrics HTTPServer caught an Exception while trying to send the metrics response.", requestHandlerException); 124 } 125 } 126 127 @Override 128 public void close() { 129 httpExchange.close(); 130 } 131}