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}