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.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 053 public class HttpResponse implements PrometheusHttpResponse { 054 055 @Override 056 public void setHeader(String name, String value) { 057 httpExchange.getResponseHeaders().set(name, value); 058 } 059 060 @Override 061 public OutputStream sendHeadersAndGetBody(int statusCode, int contentLength) throws IOException { 062 if (responseSent) { 063 throw new IOException("Cannot send multiple HTTP responses for a single HTTP exchange."); 064 } 065 responseSent = true; 066 httpExchange.sendResponseHeaders(statusCode, contentLength); 067 return httpExchange.getResponseBody(); 068 } 069 } 070 071 @Override 072 public HttpRequest getRequest() { 073 return request; 074 } 075 076 @Override 077 public HttpResponse getResponse() { 078 return response; 079 } 080 081 @Override 082 public void handleException(IOException e) throws IOException { 083 sendErrorResponseWithStackTrace(e); 084 } 085 086 @Override 087 public void handleException(RuntimeException e) { 088 sendErrorResponseWithStackTrace(e); 089 } 090 091 private void sendErrorResponseWithStackTrace(Exception requestHandlerException) { 092 if (!responseSent) { 093 responseSent = true; 094 try { 095 StringWriter stringWriter = new StringWriter(); 096 PrintWriter printWriter = new PrintWriter(stringWriter); 097 printWriter.write("An Exception occurred while scraping metrics: "); 098 requestHandlerException.printStackTrace(new PrintWriter(printWriter)); 099 byte[] stackTrace = stringWriter.toString().getBytes(StandardCharsets.UTF_8); 100 httpExchange.getResponseHeaders().set("Content-Type", "text/plain; charset=utf-8"); 101 httpExchange.sendResponseHeaders(500, stackTrace.length); 102 httpExchange.getResponseBody().write(stackTrace); 103 } catch (Exception errorWriterException) { 104 // We want to avoid logging so that we don't mess with application logs when the HTTPServer is used in a Java agent. 105 // However, if we can't even send an error response to the client there's nothing we can do but logging a message. 106 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); 107 Logger.getLogger(this.getClass().getName()).log(Level.SEVERE, "Original Exception that caused the Prometheus scrape error:", requestHandlerException); 108 } 109 } else { 110 // If the exception occurs after response headers have been sent, it's too late to respond with HTTP 500. 111 Logger.getLogger(this.getClass().getName()).log(Level.SEVERE, "The Prometheus metrics HTTPServer caught an Exception while trying to send the metrics response.", requestHandlerException); 112 } 113 } 114 115 @Override 116 public void close() { 117 httpExchange.close(); 118 } 119}