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}