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

org.rhq.plugins.www.util.WWWUtils Maven / Gradle / Ivy

There is a newer version: 4.13.0
Show newest version
/*
 * RHQ Management Platform
 * Copyright (C) 2005-2013 Red Hat, Inc.
 * All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation version 2 of the License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
 */
package org.rhq.plugins.www.util;

import static org.rhq.core.domain.measurement.AvailabilityType.UP;
import static org.rhq.plugins.apache.AvailabilityResult.availabilityIsDown;
import static org.rhq.plugins.apache.AvailabilityResult.availabilityIsUp;

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.ConnectException;
import java.net.HttpURLConnection;
import java.net.ProtocolException;
import java.net.SocketTimeoutException;
import java.net.URL;
import java.security.cert.X509Certificate;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.rhq.core.util.stream.StreamUtil;
import org.rhq.plugins.apache.AvailabilityResult;
import org.rhq.plugins.apache.AvailabilityResult.ErrorType;

/**
 * Helper class that contains methods that send HTTP requests and evaluate results.
 *
 * @author Ian Springer
 */
public class WWWUtils {

    private static final Log LOG = LogFactory.getLog(WWWUtils.class);

    // prevent instantiation
    private WWWUtils() {
    }

    /**
     * Sends a HEAD request to the passed URL and returns true if the URL was reachable.
     *
     * @param httpURL a http or https URL to check
     * @param timeout timeout, in milliseconds
     *
     * @return true if connecting to the URL succeeds, or false otherwise
     *
     * @deprecated as of RHQ 4.10. Use {@link #checkAvailability(java.net.URL, int)} instead.
     */
    @Deprecated
    public static boolean isAvailable(URL httpURL, int timeout) {
        return checkAvailability(httpURL, timeout).getAvailabilityType() == UP;
    }

    /**
     * Checks availability of the httpURL.
     * 
     * Will first try with HEAD request and fallback to GET.
     *
     * @param httpURL a http or https URL to check
     * @param timeout timeout, in milliseconds
     * @return an {@link AvailabilityResult}
     */
    public static AvailabilityResult checkAvailability(URL httpURL, int timeout) {

        if (timeout < 0) {
            throw new IllegalArgumentException("Timeout cannot be negative.");
        }

        if (timeout == 0) {
            if (LOG.isDebugEnabled()) {
                LOG.debug("Pinging [" + httpURL + "] with no timeout...");
            }
        } else {
            if (LOG.isDebugEnabled()) {
                LOG.debug("Pinging [" + httpURL + "] with timeout of " + timeout + " milliseconds...");
            }
        }

        HttpURLConnection connection;
        try {
            connection = (HttpURLConnection) httpURL.openConnection();
            connection.setInstanceFollowRedirects(false);
        } catch (IOException e) {
            String errorMessage = "Failed to open URLConnection for [" + httpURL + "].";
            LOG.error(errorMessage, e);
            return availabilityIsDown(errorMessage, e);
        }

        try {
            connection.setRequestMethod("HEAD");
        } catch (ProtocolException ignore) {
            try {
                connection.setRequestMethod("GET");
            } catch (ProtocolException e) {
                String errorMessage = "Failed to set request method to HEAD or GET on URLConnection for [" + httpURL
                    + "].";
                LOG.error(errorMessage, e);
                return availabilityIsDown(errorMessage, e);
            }
        }

        connection.setConnectTimeout(timeout);
        // Hold off on setting the read timeout until after we connect.

        if (connection instanceof HttpsURLConnection) {
            disableCertificateVerification((HttpsURLConnection) connection);
        }

        // First just connect to the HTTP server.
        long connectStartTime = System.currentTimeMillis();
        if (LOG.isDebugEnabled()) {
            LOG.debug("Connecting to [" + httpURL + "]...");
        }
        try {
            connection.connect();
        } catch (IOException e) {
            String errorMessage;
            ErrorType errorType = ErrorType.UNKNOWN;
            if (e instanceof ConnectException) {
                // This most likely just means the server is down.
                errorMessage = "Failed to connect to [" + httpURL + "].";
                errorType = ErrorType.CANNOT_CONNECT;
            } else if (e instanceof SocketTimeoutException) {
                // This probably means the server is up but not properly accepting connection requests.
                long connectDuration = System.currentTimeMillis() - connectStartTime;
                errorMessage = "Attempt to connect to [" + httpURL + "] timed out after " + connectDuration
                    + " milliseconds.";
                errorType = ErrorType.CONNECTION_TIMEOUT;
            } else {
                errorMessage = "An error occurred while attempting to connect to [" + httpURL + "].";
            }
            if (LOG.isDebugEnabled()) {
                LOG.warn(errorMessage, e);
            }
            return availabilityIsDown(errorType, errorMessage, e);
        }
        int connectDuration = (int) (System.currentTimeMillis() - connectStartTime);
        if (LOG.isDebugEnabled()) {
            LOG.debug("Connected to [" + httpURL + "] in " + connectDuration + " milliseconds.");
        }

        if ((timeout > 0) && (connectDuration >= timeout)) {
            String errorMessage = "Attempt to ping [" + httpURL + "] timed out after " + connectDuration
                + " milliseconds.";
            if (LOG.isDebugEnabled()) {
                LOG.warn(errorMessage);
            }
            return availabilityIsDown(ErrorType.CONNECTION_TIMEOUT, errorMessage);
        }

        try {
            int readTimeout = (timeout > 0) ? (timeout - connectDuration) : 0;
            connection.setReadTimeout(readTimeout);
            if (connection.getReadTimeout() != readTimeout) {
                if (LOG.isDebugEnabled()) {
                    LOG.warn("Failed to set read timeout on URLConnection for [" + httpURL
                            + "] - this most likely means we're running in a non-standard JRE.");
                }
            }

            // Now actually send the request and read the response.
            long readStartTime = System.currentTimeMillis();
            if (LOG.isDebugEnabled()) {
                LOG.debug("Sending " + connection.getRequestMethod() + " request to [" + httpURL + "]...");
            }
            try {
                // Calling getResponseCode() will cause the request to be sent.
                int responseCode = connection.getResponseCode();
                if (LOG.isDebugEnabled()) {
                    if (responseCode == -1) {
                        LOG.warn("Ping request to [" + httpURL + "] returned an invalid response: "
                            + getResponseBody(connection));
                    } else if (responseCode >= 500) {
                        LOG.warn("Ping request to [" + httpURL + "] returned a response with server error "
                            + responseCode + " (" + connection.getResponseMessage() + "): "
                            + getResponseBody(connection));
                    } else if (responseCode >= 400) {
                        LOG.warn("Ping request to [" + httpURL + "] returned a response with client error "
                            + responseCode + " (" + connection.getResponseMessage() + ").");
                    }
                }
            } catch (IOException e) {
                String errorMessage;
                ErrorType errorType = ErrorType.UNKNOWN;
                if (e instanceof SocketTimeoutException) {
                    long readDuration = System.currentTimeMillis() - readStartTime;
                    errorMessage = "Attempt to read response from " + connection.getRequestMethod() + " request to ["
                        + httpURL + "] timed out after " + readDuration + " milliseconds.";
                    errorType = ErrorType.CONNECTION_TIMEOUT;
                } else {
                    errorMessage = "An error occurred while attempting to read response from "
                        + connection.getRequestMethod() + " to [" + httpURL + "].";
                }
                if (LOG.isDebugEnabled()) {
                    LOG.warn(errorMessage, e);
                }
                return availabilityIsDown(errorType, errorMessage, e);
            }
            long readDuration = System.currentTimeMillis() - readStartTime;
            if (LOG.isDebugEnabled()) {
                LOG.debug("Read response from " + connection.getRequestMethod() + " request to [" + httpURL + "] in "
                    + readDuration + " milliseconds.");
            }
        } finally {
            // We don't care about keeping the connection around. We're only going to be pinging each server once every
            // minute.
            connection.disconnect();
        }

        return availabilityIsUp();
    }

    /**
     * Get the content of the 'Server' header.
     *
     * @param httpURL a http or https URL to get the header from
     *
     * @return the contents of the header or null if anything went wrong or the field was not present.
     */
    public static String getServerHeader(URL httpURL) {
        String ret;

        try {
            HttpURLConnection connection = (HttpURLConnection) httpURL.openConnection();
            connection.setRequestMethod("HEAD");
            connection.setConnectTimeout(3000);
            connection.setReadTimeout(1000);

            connection.connect();
            // Get the response code to actually trigger sending the request.
            connection.getResponseCode();
            ret = connection.getHeaderField("Server");
        } catch (IOException e) {
            ret = null;
        }
        return ret;
    }

    private static TrustManager NO_OP_TRUST_MANAGER = new X509TrustManager() {
        @Override
        public X509Certificate[] getAcceptedIssuers() {
            return new X509Certificate[0];
        }

        @Override
        public void checkClientTrusted(X509Certificate[] certs, String authType) {
            return;
        }

        @Override
        public void checkServerTrusted(X509Certificate[] certs, String authType) {
            return;
        }
    };

    private static HostnameVerifier NO_OP_HOSTNAME_VERIFIER = new HostnameVerifier() {
        @Override
        public boolean verify(String s, SSLSession sslSession) {
        return true;
        }
    };

    // This method has been added in support of https://bugzilla.redhat.com/show_bug.cgi?id=690430.
    private static void disableCertificateVerification(HttpsURLConnection connection) {
        try {
            SSLContext sslContext = SSLContext.getInstance("SSL");
            sslContext.init(null, new TrustManager[]{NO_OP_TRUST_MANAGER}, new java.security.SecureRandom());
            connection.setSSLSocketFactory(sslContext.getSocketFactory());
            connection.setHostnameVerifier(NO_OP_HOSTNAME_VERIFIER);
        } catch (Exception e) {
            logWarnWithStackTraceOnlyIfDebugEnabled("Failed to disable certificate and hostname validation on URLConnection for ["
                    + connection.getURL() + "].", e);
        }
    }

    private static void logWarnWithStackTraceOnlyIfDebugEnabled(String message, Exception e) {
        if (LOG.isDebugEnabled()) {
            LOG.warn(message, e);
        } else {
            LOG.warn(message + " (enable DEBUG logging to see stack trace): " + e);
        }
    }

    private static String getResponseBody(HttpURLConnection connection) {
        String responseBody;
        try {
            InputStream inputStream = (connection.getInputStream() != null) ? connection.getInputStream() :
                    connection.getErrorStream();
            responseBody = (inputStream != null) ? StreamUtil.slurp(new InputStreamReader(inputStream)) : "";
        } catch (IOException e) {
            responseBody = "";
        }
        return responseBody;
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy