net.solarnetwork.node.support.HttpClientSupport.java Source code

Java tutorial

Introduction

Here is the source code for net.solarnetwork.node.support.HttpClientSupport.java

Source

/* ==================================================================
 * HttpClientSupport.java - Nov 19, 2013 3:46:57 PM
 * 
 * Copyright 2007-2013 SolarNetwork.net Dev Team
 * 
 * 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; either version 2 of 
 * the License, or (at your option) any later version.
 * 
 * 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., 59 Temple Place, Suite 330, Boston, MA 
 * 02111-1307 USA
 * ==================================================================
 */

package net.solarnetwork.node.support;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.StringReader;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLEncoder;
import java.util.Collection;
import java.util.Map;
import java.util.zip.DeflaterInputStream;
import java.util.zip.GZIPInputStream;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLSocketFactory;
import net.solarnetwork.node.IdentityService;
import net.solarnetwork.node.SSLService;
import net.solarnetwork.util.OptionalService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.FileCopyUtils;

/**
 * Supporting methods for HTTP client operations.
 * 
 * @author matt
 * @version 1.1
 */
public abstract class HttpClientSupport {

    /** A HTTP Accept header value for any text type. */
    public static final String ACCEPT_TEXT = "text/*";

    /** A HTTP Accept header value for a JSON type. */
    public static final String ACCEPT_JSON = "application/json,text/json";

    /** The default value for the {@code connectionTimeout} property. */
    public static final int DEFAULT_CONNECTION_TIMEOUT = 15000;

    /** The HTTP method GET. */
    public static final String HTTP_METHOD_GET = "GET";

    /** The HTTP method POST. */
    public static final String HTTP_METHOD_POST = "POST";

    private int connectionTimeout = DEFAULT_CONNECTION_TIMEOUT;
    private IdentityService identityService = null;
    private OptionalService<SSLService> sslService = null;
    private String uid;
    private String groupUID;

    /** A class-level logger. */
    protected final Logger log = LoggerFactory.getLogger(getClass());

    /**
     * Get an InputStream from a URLConnection response, handling compression.
     * 
     * <p>
     * This method handles decompressing the response if the encoding is set to
     * {@code gzip} or {@code deflate}.
     * </p>
     * 
     * @param conn
     *        the URLConnection
     * @return the InputStream
     * @throws IOException
     *         if any IO error occurs
     */
    protected InputStream getInputStreamFromURLConnection(URLConnection conn) throws IOException {
        String enc = conn.getContentEncoding();
        String type = conn.getContentType();

        if (conn instanceof HttpURLConnection) {
            HttpURLConnection httpConn = (HttpURLConnection) conn;
            if (httpConn.getResponseCode() < 200 || httpConn.getResponseCode() > 299) {
                log.info("Non-200 HTTP response from {}: {}", conn.getURL(), httpConn.getResponseCode());
            }
        }

        log.trace("Got content type [{}] encoded as [{}]", type, enc);

        InputStream is = conn.getInputStream();
        if ("gzip".equalsIgnoreCase(enc)) {
            is = new GZIPInputStream(is);
        } else if ("deflate".equalsIgnoreCase("enc")) {
            is = new DeflaterInputStream(is);
        }
        return is;
    }

    /**
     * Get a Reader for a Unicode encoded URL connection response.
     * 
     * <p>
     * This calls {@link #getInputStreamFromURLConnection(URLConnection)} so
     * compressed responses are handled appropriately.
     * </p>
     * 
     * @param conn
     *        the URLConnection
     * @return the Reader
     * @throws IOException
     *         if an IO error occurs
     */
    protected Reader getUnicodeReaderFromURLConnection(URLConnection conn) throws IOException {
        return new BufferedReader(new UnicodeReader(getInputStreamFromURLConnection(conn), null));
    }

    /**
     * Get a URLConnection for a specific URL and HTTP method.
     * 
     * <p>
     * This defaults to the {@link #ACCEPT_TEXT} accept value.
     * </p>
     * 
     * @param url
     *        the URL to connect to
     * @param httpMethod
     *        the HTTP method
     * @return the URLConnection
     * @throws IOException
     *         if any IO error occurs
     * @see #getURLConnection(String, String, String)
     */
    protected URLConnection getURLConnection(String url, String httpMethod) throws IOException {
        return getURLConnection(url, httpMethod, "text/*");
    }

    /**
     * Get a URLConnection for a specific URL and HTTP method.
     * 
     * <p>
     * If the httpMethod equals {@code POST} then the connection's
     * {@code doOutput} property will be set to <em>true</em>, otherwise it will
     * be set to <em>false</em>. The {@code doInput} property is always set to
     * <em>true</em>.
     * </p>
     * 
     * <p>
     * This method also sets up the request property
     * {@code Accept-Encoding: gzip,deflate} so the response can be compressed.
     * The {@link #getInputSourceFromURLConnection(URLConnection)} automatically
     * handles compressed responses.
     * </p>
     * 
     * <p>
     * If the {@link #getSslService()} property is configured and the URL
     * represents an HTTPS connection, then that factory will be used to for the
     * connection.
     * </p>
     * 
     * @param url
     *        the URL to connect to
     * @param httpMethod
     *        the HTTP method
     * @param accept
     *        the HTTP Accept header value
     * @return the URLConnection
     * @throws IOException
     *         if any IO error occurs
     */
    protected URLConnection getURLConnection(String url, String httpMethod, String accept) throws IOException {
        URL connUrl = new URL(url);
        URLConnection conn = connUrl.openConnection();
        if (conn instanceof HttpURLConnection) {
            HttpURLConnection hConn = (HttpURLConnection) conn;
            hConn.setRequestMethod(httpMethod);
        }
        if (sslService != null && conn instanceof HttpsURLConnection) {
            SSLService service = sslService.service();
            if (service != null) {
                SSLSocketFactory factory = service.getSolarInSocketFactory();
                if (factory != null) {
                    HttpsURLConnection hConn = (HttpsURLConnection) conn;
                    hConn.setSSLSocketFactory(factory);
                }
            }
        }
        conn.setRequestProperty("Accept", accept);
        conn.setRequestProperty("Accept-Encoding", "gzip,deflate");
        conn.setDoInput(true);
        conn.setDoOutput(HTTP_METHOD_POST.equalsIgnoreCase(httpMethod));
        conn.setConnectTimeout(this.connectionTimeout);
        conn.setReadTimeout(connectionTimeout);
        return conn;
    }

    /**
     * Append a URL-escaped key/value pair to a string buffer.
     * 
     * @param buf
     * @param key
     * @param value
     */
    protected void appendXWWWFormURLEncodedValue(StringBuilder buf, String key, Object value) {
        if (value == null) {
            return;
        }
        if (buf.length() > 0) {
            buf.append('&');
        }
        try {
            buf.append(URLEncoder.encode(key, "UTF-8")).append('=')
                    .append(URLEncoder.encode(value.toString(), "UTF-8"));
        } catch (UnsupportedEncodingException e) {
            // should not get here ever
            throw new RuntimeException(e);
        }
    }

    /**
     * Encode a map of data into a string suitable for posting to a web server
     * as the content type {@code application/x-www-form-urlencoded}. Arrays and
     * Collections of values are supported as well.
     * 
     * @param data
     *        the map of data to encode
     * @return the encoded data, or an empty string if nothing to encode
     */
    protected String xWWWFormURLEncoded(Map<String, ?> data) {
        if (data == null || data.size() < 0) {
            return "";
        }
        StringBuilder buf = new StringBuilder();
        for (Map.Entry<String, ?> me : data.entrySet()) {
            String key;
            try {
                key = URLEncoder.encode(me.getKey(), "UTF-8");
            } catch (UnsupportedEncodingException e) {
                // should not get here ever
                throw new RuntimeException(e);
            }
            Object val = me.getValue();
            if (val instanceof Collection<?>) {
                for (Object colVal : (Collection<?>) val) {
                    appendXWWWFormURLEncodedValue(buf, key, colVal);
                }
            } else if (val.getClass().isArray()) {
                for (Object arrayVal : (Object[]) val) {
                    appendXWWWFormURLEncodedValue(buf, key, arrayVal);
                }
            } else {
                appendXWWWFormURLEncodedValue(buf, key, val);
            }
        }
        return buf.toString();
    }

    /**
     * HTTP POST data as {@code application/x-www-form-urlencoded} (e.g. a web
     * form) to a URL.
     * 
     * @param url
     *        the URL to post to
     * @param accept
     *        the value to use for the Accept HTTP header
     * @param data
     *        the data to encode and send as the body of the HTTP POST
     * @return the URLConnection after the post data has been sent
     * @throws IOException
     *         if any IO error occurs
     * @throws RuntimeException
     *         if the HTTP response code is not within the 200 - 299 range
     */
    protected URLConnection postXWWWFormURLEncodedData(String url, String accept, Map<String, ?> data)
            throws IOException {
        URLConnection conn = getURLConnection(url, HTTP_METHOD_POST, accept);
        conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
        String body = xWWWFormURLEncoded(data);
        log.trace("Encoded HTTP POST data {} as {}", data, body);
        OutputStreamWriter out = new OutputStreamWriter(conn.getOutputStream(), "UTF-8");
        FileCopyUtils.copy(new StringReader(body), out);
        if (conn instanceof HttpURLConnection) {
            HttpURLConnection http = (HttpURLConnection) conn;
            int status = http.getResponseCode();
            if (status < 200 || status > 299) {
                throw new RuntimeException("HTTP result status not in the 200-299 range: " + http.getResponseCode()
                        + " " + http.getResponseMessage());
            }
        }
        return conn;
    }

    /**
     * HTTP POST data as {@code application/x-www-form-urlencoded} (e.g. a web
     * form) to a URL and return the response body as a string.
     * 
     * @param url
     *        the URL to post to
     * @param data
     *        the data to encode and send as the body of the HTTP POST
     * @return the response body as a String
     * @throws IOException
     *         if any IO error occurs
     * @throws RuntimeException
     *         if the HTTP response code is not within the 200 - 299 range
     * @see #postXWWWFormURLEncodedData(String, String, Map)
     */
    protected String postXWWWFormURLEncodedDataForString(String url, Map<String, ?> data) throws IOException {
        URLConnection conn = postXWWWFormURLEncodedData(url, "text/*, application/json", data);
        return FileCopyUtils.copyToString(getUnicodeReaderFromURLConnection(conn));
    }

    public void setConnectionTimeout(int connectionTimeout) {
        this.connectionTimeout = connectionTimeout;
    }

    public int getConnectionTimeout() {
        return connectionTimeout;
    }

    public IdentityService getIdentityService() {
        return identityService;
    }

    public void setIdentityService(IdentityService identityService) {
        this.identityService = identityService;
    }

    public OptionalService<SSLService> getSslService() {
        return sslService;
    }

    public void setSslService(OptionalService<SSLService> sslService) {
        this.sslService = sslService;
    }

    public String getUID() {
        return getUid();
    }

    public String getUid() {
        return uid;
    }

    public void setUid(String uid) {
        this.uid = uid;
    }

    public String getGroupUID() {
        return groupUID;
    }

    public void setGroupUID(String groupUID) {
        this.groupUID = groupUID;
    }

}