All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jolokia.jvmagent.handler.JolokiaHttpHandler Maven / Gradle / Ivy

The newest version!
package org.jolokia.jvmagent.handler;

/*
 * Copyright 2009-2013 Roland Huss
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.net.InetSocketAddress;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.Locale;
import java.util.Map;
import java.util.TimeZone;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.management.RuntimeMBeanException;
import javax.security.auth.Subject;

import com.sun.net.httpserver.Headers;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpsExchange;
import org.jolokia.jvmagent.ParsedUri;
import org.jolokia.server.core.config.ConfigKey;
import org.jolokia.server.core.http.BackChannelHolder;
import org.jolokia.server.core.http.HttpRequestHandler;
import org.jolokia.server.core.request.EmptyResponseException;
import org.jolokia.server.core.service.api.JolokiaContext;
import org.jolokia.server.core.util.IoUtil;
import org.jolokia.server.core.util.MimeTypeUtil;
import org.jolokia.json.JSONStructure;

/**
 * HttpHandler for handling a Jolokia request
 *
 * @author roland
 * @since Mar 3, 2010
 */
public class JolokiaHttpHandler implements HttpHandler {

    // The HttpRequestHandler
    private final HttpRequestHandler requestHandler;

    // Context of this request
    private String contextPath;

    // Content type matching
    private final Pattern contentTypePattern = Pattern.compile(".*;\\s*charset=([^;,]+)\\s*.*");

    // Global context
    private final JolokiaContext jolokiaContext;

    // Backchannel Thread Pool (TODO: Optimize that for backchannel handling)
    private final Executor backChannelThreadPool = Executors.newCachedThreadPool();
    /**
     * Create a new HttpHandler for processing HTTP request
     *
     * @param pJolokiaContext jolokia context
     */
    public JolokiaHttpHandler(JolokiaContext pJolokiaContext) {
        jolokiaContext = pJolokiaContext;

        contextPath = jolokiaContext.getConfig(ConfigKey.AGENT_CONTEXT);
        if (!contextPath.endsWith("/")) {
            contextPath += "/";
        }

        requestHandler = new HttpRequestHandler(jolokiaContext);
    }

    /**
     * Handler a request. If the handler is not yet started, an exception is thrown
     *
     * @param pHttpExchange the request/response object
     * @throws IOException if something fails during handling
     * @throws IllegalStateException if the handler has not yet been started
     */
    public void handle(final HttpExchange pHttpExchange) throws IOException {
        try {
            checkAuthentication(pHttpExchange);

            Subject subject = (Subject) pHttpExchange.getAttribute(ConfigKey.JAAS_SUBJECT_REQUEST_ATTRIBUTE);
            if (subject != null)  {
                doHandleAs(subject, pHttpExchange);
            }  else {
                doHandle(pHttpExchange);
            }
        } catch (SecurityException exp) {
            sendForbidden(pHttpExchange,exp);
        }
    }

    // run as priviledged action
    private void doHandleAs(Subject subject, final HttpExchange pHttpExchange) {
        try {
            Subject.doAs(subject, (PrivilegedExceptionAction) () -> {
                doHandle(pHttpExchange);
                return null;
            });
        } catch (PrivilegedActionException e) {
            throw new SecurityException("Security exception: " + e.getCause(),e.getCause());
        }
    }

    /**
     * Protocol based authentication checks called very early and before handling a request.
     * If the check fails a security exception must be thrown
     * 

* The default implementation does nothing and should be overridden for a valid check. * * @param pHttpExchange exchange to check * @throws SecurityException if check fails. */ protected void checkAuthentication(HttpExchange pHttpExchange) throws SecurityException { } /** * Handler a request. If the handler is not yet started, an exception is thrown * * @param pExchange the request/response object * @throws IOException if something fails during handling * @throws IllegalStateException if the handler has not yet been started */ @SuppressWarnings({"PMD.AvoidCatchingThrowable", "PMD.AvoidInstanceofChecksInCatchClause"}) public void doHandle(HttpExchange pExchange) throws IOException { JSONStructure json = null; URI uri = pExchange.getRequestURI(); ParsedUri parsedUri = new ParsedUri(uri, contextPath); try { // Set back channel prepareBackChannel(pExchange); // Check access policy InetSocketAddress address = pExchange.getRemoteAddress(); String scheme = pExchange instanceof HttpsExchange ? "https" : "http"; requestHandler.checkAccess(scheme, getHostName(address), address.getAddress().getHostAddress(), extractOriginOrReferer(pExchange)); String method = pExchange.getRequestMethod(); // If a callback is given, check this is a valid javascript function name validateCallbackIfGiven(parsedUri); // Dispatch for the proper HTTP request method if ("GET".equalsIgnoreCase(method)) { setHeaders(pExchange); json = executeGetRequest(parsedUri); } else if ("POST".equalsIgnoreCase(method)) { setHeaders(pExchange); json = executePostRequest(pExchange, parsedUri); } else if ("OPTIONS".equalsIgnoreCase(method)) { performCorsPreflightCheck(pExchange); } else { throw new IllegalArgumentException("HTTP Method " + method + " is not supported."); } if (jolokiaContext.isDebug()) { jolokiaContext.debug("Response: " + json); } } catch (EmptyResponseException exp) { // No response needed, will answer later ... return; } catch (Throwable exp) { json = requestHandler.handleThrowable( exp instanceof RuntimeMBeanException ? ((RuntimeMBeanException) exp).getTargetException() : exp); } finally { releaseBackChannel(); } sendResponse(pExchange, parsedUri, json); } private void prepareBackChannel(HttpExchange pExchange) { BackChannelHolder.set(new HttpExchangeBackChannel(pExchange,backChannelThreadPool)); } private void releaseBackChannel() { BackChannelHolder.remove(); } private void validateCallbackIfGiven(ParsedUri pUri) { String callback = pUri.getParameter(ConfigKey.CALLBACK.getKeyValue()); if (callback != null && !MimeTypeUtil.isValidCallback(callback)) { throw new IllegalArgumentException("Invalid callback name given, which must be a valid javascript function name"); } } // ======================================================================== // Used for checking origin or referer is an origin policy is enabled private String extractOriginOrReferer(HttpExchange pExchange) { Headers headers = pExchange.getRequestHeaders(); String origin = headers.getFirst("Origin"); if (origin == null) { origin = headers.getFirst("Referer"); } return origin != null ? origin.replaceAll("[\\n\\r]*","") : null; } // Return hostname of given address, but only when reverse DNS lookups are allowed private String getHostName(InetSocketAddress address) { return Boolean.parseBoolean(jolokiaContext.getConfig(ConfigKey.ALLOW_DNS_REVERSE_LOOKUP)) ? address.getHostName() : null; } private JSONStructure executeGetRequest(ParsedUri parsedUri) throws EmptyResponseException { return requestHandler.handleGetRequest(parsedUri.getUri().toString(),parsedUri.getPathInfo(), parsedUri.getParameterMap()); } private JSONStructure executePostRequest(HttpExchange pExchange, ParsedUri pUri) throws IOException, EmptyResponseException { String encoding = null; Headers headers = pExchange.getRequestHeaders(); String cType = headers.getFirst("Content-Type"); if (cType != null) { Matcher matcher = contentTypePattern.matcher(cType); if (matcher.matches()) { encoding = matcher.group(1); } } InputStream is = pExchange.getRequestBody(); return requestHandler.handlePostRequest(pUri.toString(),is, encoding, pUri.getParameterMap()); } private void performCorsPreflightCheck(HttpExchange pExchange) { Headers requestHeaders = pExchange.getRequestHeaders(); Map respHeaders = requestHandler.handleCorsPreflightRequest(extractOriginOrReferer(pExchange), requestHeaders.getFirst("Access-Control-Request-Headers")); Headers responseHeaders = pExchange.getResponseHeaders(); for (Map.Entry entry : respHeaders.entrySet()) { responseHeaders.set(entry.getKey(), entry.getValue()); } } private void setHeaders(HttpExchange pExchange) { String origin = requestHandler.extractCorsOrigin(pExchange.getRequestHeaders().getFirst("Origin")); Headers headers = pExchange.getResponseHeaders(); if (origin != null) { headers.set("Access-Control-Allow-Origin",origin); headers.set("Access-Control-Allow-Credentials","true"); } // Avoid caching at all costs headers.set("Cache-Control", "no-cache"); headers.set("Pragma","no-cache"); // Check for a date header and set it accordingly to the recommendations of // RFC-2616. See also {@link AgentServlet#setNoCacheHeaders()} // Issue: #71 Calendar cal = Calendar.getInstance(); headers.set("Date",formatHeaderDate(cal.getTime())); // 1h in the past since it seems, that some servlet set the date header on their // own so that it cannot be guaranteed that these headers are really equals. // It happened on Tomcat that "Date:" was finally set *before* "Expires:" in the final // answers sometimes which seems to be an implementation peculiarity from Tomcat cal.add(Calendar.HOUR, -1); headers.set("Expires",formatHeaderDate(cal.getTime())); } private void sendForbidden(HttpExchange pExchange, SecurityException securityException) throws IOException { String response = "403 (Forbidden)\n"; if (securityException != null && securityException.getMessage() != null) { response += "\n" + securityException.getMessage() + "\n"; } pExchange.sendResponseHeaders(403, response.length()); OutputStream os = pExchange.getResponseBody(); os.write(response.getBytes()); os.close(); } private void sendResponse(HttpExchange pExchange, ParsedUri pParsedUri, JSONStructure pJson) throws IOException { boolean streaming = Boolean.parseBoolean(jolokiaContext.getConfig(ConfigKey.STREAMING)); if (streaming) { sendStreamingResponse(pExchange, pParsedUri, pJson); } else { // Fallback, send as one object // TODO: Remove for 2.0 sendAllJSON(pExchange, pParsedUri, pJson); } } private void sendStreamingResponse(HttpExchange pExchange, ParsedUri pParsedUri, JSONStructure pJson) throws IOException { Headers headers = pExchange.getResponseHeaders(); if (pJson != null) { headers.set("Content-Type", getMimeType(pParsedUri) + "; charset=utf-8"); pExchange.sendResponseHeaders(200, 0); Writer writer = new OutputStreamWriter(pExchange.getResponseBody(), StandardCharsets.UTF_8); String callback = pParsedUri.getParameter(ConfigKey.CALLBACK.getKeyValue()); IoUtil.streamResponseAndClose(writer, pJson, callback != null && MimeTypeUtil.isValidCallback(callback) ? callback : null); } else { headers.set("Content-Type", "text/plain"); pExchange.sendResponseHeaders(200,-1); } } private void sendAllJSON(HttpExchange pExchange, ParsedUri pParsedUri, JSONStructure pJson) throws IOException { OutputStream out = null; try { Headers headers = pExchange.getResponseHeaders(); if (pJson != null) { headers.set("Content-Type", getMimeType(pParsedUri) + "; charset=utf-8"); String json = pJson.toJSONString(); String callback = pParsedUri.getParameter(ConfigKey.CALLBACK.getKeyValue()); String content = callback != null && MimeTypeUtil.isValidCallback(callback) ? callback + "(" + json + ");" : json; byte[] response = content.getBytes(StandardCharsets.UTF_8); pExchange.sendResponseHeaders(200,response.length); out = pExchange.getResponseBody(); out.write(response); } else { headers.set("Content-Type", "text/plain"); pExchange.sendResponseHeaders(200,-1); } } finally { if (out != null) { // Always close in order to finish the request. // Otherwise, the thread blocks. out.close(); } } } // Get the proper mime type according to configuration private String getMimeType(ParsedUri pParsedUri) { return MimeTypeUtil.getResponseMimeType( pParsedUri.getParameter(ConfigKey.MIME_TYPE.getKeyValue()), jolokiaContext.getConfig(ConfigKey.MIME_TYPE), pParsedUri.getParameter(ConfigKey.CALLBACK.getKeyValue())); } private String formatHeaderDate(Date date) { DateFormat rfc1123Format = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US); rfc1123Format.setTimeZone(TimeZone.getTimeZone("GMT")); return rfc1123Format.format(date); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy