org.pixmob.fm2.util.HttpUtils.java Source code

Java tutorial

Introduction

Here is the source code for org.pixmob.fm2.util.HttpUtils.java

Source

/*
 * Copyright (C) 2012 Pixmob (http://github.com/pixmob)
 *
 * 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.
 */
package org.pixmob.fm2.util;

import static org.pixmob.fm2.Constants.APPLICATION_NAME_USER_AGENT;
import static org.pixmob.fm2.Constants.DEBUG;
import static org.pixmob.fm2.Constants.TAG;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Field;
import java.net.HttpURLConnection;
import java.net.InetAddress;
import java.net.Socket;
import java.net.URL;
import java.net.URLEncoder;
import java.net.UnknownHostException;
import java.security.GeneralSecurityException;
import java.security.KeyStore;
import java.security.SecureRandom;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.zip.GZIPInputStream;

import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;

import org.apache.http.conn.ssl.BrowserCompatHostnameVerifier;
import org.pixmob.fm2.R;

import android.content.Context;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Build;
import android.util.Log;

/**
 * Http utilities.
 * @author Pixmob
 */
public final class HttpUtils {
    private static final SecureRandom SECURE_RANDOM = new SecureRandom();
    private static String applicationVersion;

    static {
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.FROYO) {
            // Disable connection pooling before Froyo:
            // http://stackoverflow.com/a/4261005/422906
            System.setProperty("http.keepAlive", "false");
        }
    }

    private HttpUtils() {
    }

    /**
     * Get Http cookies from a response.
     */
    public static void readCookies(HttpURLConnection conn, Set<String> cookies) {
        final List<String> newCookies = conn.getHeaderFields().get("Set-Cookie");
        if (newCookies != null) {
            for (final String newCookie : newCookies) {
                cookies.add(newCookie.split(";", 2)[0]);
            }
        }
    }

    /**
     * Download a file.
     */
    public static void downloadToFile(Context context, String uri, Set<String> cookies, File outputFile)
            throws IOException {
        final HttpURLConnection conn = newRequest(context, uri, cookies);
        try {
            conn.connect();

            final int sc = conn.getResponseCode();
            if (sc != HttpURLConnection.HTTP_OK) {
                throw new IOException("Cannot download file: " + uri + "; statusCode=" + sc);
            }

            final InputStream input = getInputStream(conn);
            IOUtils.writeToFile(input, outputFile);
        } finally {
            conn.disconnect();
        }
    }

    /**
     * Create a new Http connection for an URI.
     */
    public static HttpURLConnection newRequest(Context context, String uri, Set<String> cookies)
            throws IOException {
        if (DEBUG) {
            Log.d(TAG, "Setup connection to " + uri);
        }

        final HttpURLConnection conn = (HttpURLConnection) new URL(uri).openConnection();
        conn.setUseCaches(false);
        conn.setInstanceFollowRedirects(false);
        conn.setConnectTimeout(30000);
        conn.setReadTimeout(60000);
        conn.setRequestProperty("Accept-Encoding", "gzip");
        conn.setRequestProperty("User-Agent", getUserAgent(context));
        conn.setRequestProperty("Cache-Control", "max-age=0");
        conn.setDoInput(true);

        // Close the connection when the request is done, or the application may
        // freeze due to a bug in some Android versions.
        conn.setRequestProperty("Connection", "close");

        if (conn instanceof HttpsURLConnection) {
            setupSecureConnection(context, (HttpsURLConnection) conn);
        }

        if (cookies != null && !cookies.isEmpty()) {
            final StringBuilder buf = new StringBuilder(256);
            for (final String cookie : cookies) {
                if (buf.length() != 0) {
                    buf.append("; ");
                }
                buf.append(cookie);
            }
            conn.addRequestProperty("Cookie", buf.toString());
        }

        return conn;
    }

    /**
     * Prepare a <code>POST</code> Http request.
     */
    public static HttpURLConnection newPostRequest(Context context, String uri, Set<String> cookies,
            Map<String, String> params, String charset) throws IOException {
        final StringBuilder query = new StringBuilder();
        if (params != null) {
            for (final Map.Entry<String, String> e : params.entrySet()) {
                if (query.length() != 0) {
                    query.append("&");
                }
                query.append(e.getKey()).append("=").append(URLEncoder.encode(e.getValue()));
            }
        }

        final byte[] payload = query.toString().getBytes(charset);

        final HttpURLConnection conn = newRequest(context, uri, cookies);
        conn.setDoOutput(true);
        conn.setFixedLengthStreamingMode(payload.length);
        conn.setRequestProperty("Accept-Charset", charset);
        conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded;charset=" + charset);
        conn.setRequestProperty("Referer", uri);

        final OutputStream queryOutput = conn.getOutputStream();
        try {
            queryOutput.write(payload);
        } finally {
            IOUtils.close(queryOutput);
        }

        return conn;
    }

    /**
     * Open the {@link InputStream} of an Http response. This method supports
     * GZIP responses.
     */
    public static InputStream getInputStream(HttpURLConnection conn) throws IOException {
        final List<String> contentEncodingValues = conn.getHeaderFields().get("Content-Encoding");
        if (contentEncodingValues != null) {
            for (final String contentEncoding : contentEncodingValues) {
                if (contentEncoding != null && contentEncoding.contains("gzip")) {
                    return new GZIPInputStream(conn.getInputStream());
                }
            }
        }
        return conn.getInputStream();
    }

    /**
     * Get Http User Agent for this application.
     */
    public static final String getUserAgent(Context context) {
        if (applicationVersion == null) {
            try {
                applicationVersion = context.getPackageManager().getPackageInfo(context.getPackageName(),
                        0).versionName;
            } catch (NameNotFoundException e) {
                applicationVersion = "0.0.0";
            }
        }
        return APPLICATION_NAME_USER_AGENT + "/" + applicationVersion + " (" + Build.MANUFACTURER + " "
                + Build.MODEL + " with Android " + Build.VERSION.RELEASE + "/" + Build.VERSION.SDK_INT + ")";
    }

    private static KeyStore loadCertificates(Context context) throws IOException {
        try {
            final KeyStore localTrustStore = KeyStore.getInstance("BKS");
            final InputStream in = context.getResources().openRawResource(R.raw.mykeystore);
            try {
                localTrustStore.load(in, "mysecret".toCharArray());
            } finally {
                in.close();
            }

            return localTrustStore;
        } catch (Exception e) {
            final IOException ioe = new IOException("Failed to load SSL certificates");
            ioe.initCause(e);
            throw ioe;
        }
    }

    /**
     * Setup SSL connection.
     */
    private static void setupSecureConnection(Context context, HttpsURLConnection conn) throws IOException {
        if (DEBUG) {
            Log.d(TAG, "Load custom SSL certificates");
        }

        final SSLContext sslContext;
        try {
            // Load SSL certificates:
            // http://nelenkov.blogspot.com/2011/12/using-custom-certificate-trust-store-on.html
            // Earlier Android versions do not have updated root CA
            // certificates, resulting in connection errors.
            final KeyStore keyStore = loadCertificates(context);

            final CustomTrustManager customTrustManager = new CustomTrustManager(keyStore);
            final TrustManager[] tms = new TrustManager[] { customTrustManager };

            // Init SSL connection with custom certificates.
            // The same SecureRandom instance is used for every connection to
            // speed up initialization.
            sslContext = SSLContext.getInstance("TLS");
            sslContext.init(null, tms, SECURE_RANDOM);
        } catch (GeneralSecurityException e) {
            final IOException ioe = new IOException("Failed to initialize SSL engine");
            ioe.initCause(e);
            throw ioe;
        }

        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
            // Fix slow read:
            // http://code.google.com/p/android/issues/detail?id=13117
            // Prior to ICS, the host name is still resolved even if we already
            // know its IP address, for each connection.
            final SSLSocketFactory delegate = sslContext.getSocketFactory();
            final SSLSocketFactory socketFactory = new SSLSocketFactory() {
                @Override
                public Socket createSocket(String host, int port) throws IOException, UnknownHostException {
                    InetAddress addr = InetAddress.getByName(host);
                    injectHostname(addr, host);
                    return delegate.createSocket(addr, port);
                }

                @Override
                public Socket createSocket(InetAddress host, int port) throws IOException {
                    return delegate.createSocket(host, port);
                }

                @Override
                public Socket createSocket(String host, int port, InetAddress localHost, int localPort)
                        throws IOException, UnknownHostException {
                    return delegate.createSocket(host, port, localHost, localPort);
                }

                @Override
                public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort)
                        throws IOException {
                    return delegate.createSocket(address, port, localAddress, localPort);
                }

                private void injectHostname(InetAddress address, String host) {
                    try {
                        Field field = InetAddress.class.getDeclaredField("hostName");
                        field.setAccessible(true);
                        field.set(address, host);
                    } catch (Exception ignored) {
                    }
                }

                @Override
                public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException {
                    injectHostname(s.getInetAddress(), host);
                    return delegate.createSocket(s, host, port, autoClose);
                }

                @Override
                public String[] getDefaultCipherSuites() {
                    return delegate.getDefaultCipherSuites();
                }

                @Override
                public String[] getSupportedCipherSuites() {
                    return delegate.getSupportedCipherSuites();
                }
            };
            conn.setSSLSocketFactory(socketFactory);
        } else {
            conn.setSSLSocketFactory(sslContext.getSocketFactory());
        }

        conn.setHostnameVerifier(new BrowserCompatHostnameVerifier());
    }
}