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}