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}