com.funambol.platform.HttpConnectionAdapter.java Source code

Java tutorial

Introduction

Here is the source code for com.funambol.platform.HttpConnectionAdapter.java

Source

/*
 * Funambol is a mobile platform developed by Funambol, Inc. 
 * Copyright (C) 2010 Funambol, Inc.
 * 
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the GNU Affero General Public License version 3 as published by
 * the Free Software Foundation with the addition of the following permission 
 * added to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED
 * WORK IN WHICH THE COPYRIGHT IS OWNED BY FUNAMBOL, FUNAMBOL DISCLAIMS THE 
 * WARRANTY OF NON INFRINGEMENT  OF THIRD PARTY RIGHTS.
 * 
 * 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 Affero General Public License 
 * along with this program; if not, see http://www.gnu.org/licenses or write to
 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 * MA 02110-1301 USA.
 * 
 * You can contact Funambol, Inc. headquarters at 643 Bair Island Road, Suite 
 * 305, Redwood City, CA 94063, USA, or at email address info@funambol.com.
 * 
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU Affero General Public License version 3.
 * 
 * In accordance with Section 7(b) of the GNU Affero General Public License
 * version 3, these Appropriate Legal Notices must retain the display of the
 * "Powered by Funambol" logo. If the display of the logo is not reasonably 
 * feasible for technical reasons, the Appropriate Legal Notices must display
 * the words "Powered by Funambol". 
 */

package com.funambol.platform;

import java.net.HttpURLConnection;
import java.io.IOException;
import java.io.InputStream;
import java.io.ByteArrayInputStream;
import java.io.OutputStream;
import java.util.Map;
import java.util.HashMap;

//import android.net.http.AndroidHttpClient;

import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpEntityEnclosingRequest;
import org.apache.http.HttpException;
import org.apache.http.HttpHost;
import org.apache.http.HttpRequest;
import org.apache.http.HttpRequestInterceptor;
import org.apache.http.HttpResponse;
import org.apache.http.entity.AbstractHttpEntity;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.client.HttpClient;
import org.apache.http.client.ResponseHandler;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.protocol.ClientContext;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.client.params.HttpClientParams;
import org.apache.http.conn.ClientConnectionManager;
import org.apache.http.conn.scheme.PlainSocketFactory;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.client.RequestWrapper;
import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;
import org.apache.http.params.HttpProtocolParams;
import org.apache.http.protocol.BasicHttpProcessor;
import org.apache.http.protocol.HttpContext;
import org.apache.http.protocol.BasicHttpContext;
import org.apache.http.entity.InputStreamEntity;
import org.apache.http.protocol.HTTP;

import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.StatusLine;
import org.apache.http.HttpResponse;
import org.apache.http.HttpEntity;
import org.apache.http.Header;
import org.apache.http.HttpHost;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.conn.params.ConnRouteParams;
import org.apache.http.params.CoreProtocolPNames;
import org.apache.http.params.CoreConnectionPNames;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.HttpVersion;

import android.net.SSLCertificateSocketFactory;
//import android.net.SSLSessionCache;

import com.funambol.platform.net.ProxyConfig;
import com.funambol.util.Log;

/**
 * This class is a simple HttpConnection class that wraps
 *
 * A portable code must use this class only to perform http connections, and must take care
 * of closing the connection when not used anymore.
 * <pre>
 * Example:
 * 
 *   void httpConnectionExample(String url) throws IOException {
 *      HttpConnectionAdapter conn = new HttpConnectionAdapter();
 *
 *      // Open the connection
 *      conn.open(url);
 *
 *      conn.setRequestMethod(HttpConnectionAdapter.POST);
 *      conn.setRequestProperty("CUSTOM-HEADER", "CUSTOM-VALUE");
 *
 *      OutputStream os = conn.openOutputStream();
 *      os.write("TEST");
 *      os.close();
 *
 *      // Suppose the answer is bound to 1KB
 *      byte anwser[] = new byte[1024];
 *      InputStream is = conn.openInputStream();
 *      is.read(answer);
 *      is.close();
 *
 *      // Close the connection
 *      conn.close();
 * </pre>
 */
public class HttpConnectionAdapter {

    private static final String TAG_LOG = "HttpConnectionAdapter";

    public static int HTTP_ACCEPTED = HttpURLConnection.HTTP_ACCEPTED;
    public static int HTTP_BAD_GATEWAY = HttpURLConnection.HTTP_BAD_GATEWAY;
    public static int HTTP_BAD_METHOD = HttpURLConnection.HTTP_BAD_METHOD;
    public static int HTTP_BAD_REQUEST = HttpURLConnection.HTTP_BAD_REQUEST;
    public static int HTTP_CLIENT_TIMEOUT = HttpURLConnection.HTTP_CLIENT_TIMEOUT;
    public static int HTTP_CONFLICT = HttpURLConnection.HTTP_CONFLICT;
    public static int HTTP_CREATED = HttpURLConnection.HTTP_CREATED;
    public static int HTTP_ENTITY_TOO_LARGE = HttpURLConnection.HTTP_ENTITY_TOO_LARGE;
    public static int HTTP_FORBIDDEN = HttpURLConnection.HTTP_FORBIDDEN;
    public static int HTTP_GATEWAY_TIMEOUT = HttpURLConnection.HTTP_GATEWAY_TIMEOUT;
    public static int HTTP_GONE = HttpURLConnection.HTTP_GONE;
    public static int HTTP_INTERNAL_ERROR = HttpURLConnection.HTTP_INTERNAL_ERROR;
    public static int HTTP_LENGTH_REQUIRED = HttpURLConnection.HTTP_LENGTH_REQUIRED;
    public static int HTTP_MOVED_PERM = HttpURLConnection.HTTP_MOVED_PERM;
    public static int HTTP_MOVED_TEMP = HttpURLConnection.HTTP_MOVED_TEMP;
    public static int HTTP_MULT_CHOICE = HttpURLConnection.HTTP_MULT_CHOICE;
    public static int HTTP_NO_CONTENT = HttpURLConnection.HTTP_NO_CONTENT;
    public static int HTTP_NOT_ACCEPTABLE = HttpURLConnection.HTTP_NOT_ACCEPTABLE;
    public static int HTTP_NOT_AUTHORITATIVE = HttpURLConnection.HTTP_NOT_AUTHORITATIVE;
    public static int HTTP_NOT_FOUND = HttpURLConnection.HTTP_NOT_FOUND;
    public static int HTTP_NOT_IMPLEMENTED = HttpURLConnection.HTTP_NOT_IMPLEMENTED;
    public static int HTTP_NOT_MODIFIED = HttpURLConnection.HTTP_NOT_MODIFIED;
    public static int HTTP_OK = HttpURLConnection.HTTP_OK;
    public static int HTTP_PARTIAL = HttpURLConnection.HTTP_PARTIAL;
    public static int HTTP_PAYMENT_REQUIRED = HttpURLConnection.HTTP_PAYMENT_REQUIRED;
    public static int HTTP_PRECON_FAILED = HttpURLConnection.HTTP_PRECON_FAILED;
    public static int HTTP_PROXY_AUTH = HttpURLConnection.HTTP_PROXY_AUTH;
    public static int HTTP_REQ_TOO_LONG = HttpURLConnection.HTTP_REQ_TOO_LONG;
    public static int HTTP_RESET = HttpURLConnection.HTTP_RESET;
    public static int HTTP_SEE_OTHER = HttpURLConnection.HTTP_SEE_OTHER;
    public static int HTTP_UNAUTHORIZED = HttpURLConnection.HTTP_UNAUTHORIZED;
    public static int HTTP_UNAVAILABLE = HttpURLConnection.HTTP_UNAVAILABLE;
    public static int HTTP_UNSUPPORTED_TYPE = HttpURLConnection.HTTP_UNSUPPORTED_TYPE;
    public static int HTTP_USE_PROXY = HttpURLConnection.HTTP_USE_PROXY;
    public static int HTTP_VERSION = HttpURLConnection.HTTP_VERSION;

    public static int HTTP_EXPECT_FAILED = 417;
    public static int HTTP_UNSUPPORTED_RANGE = 416;
    public static int HTTP_TEMP_REDIRECT = 307;

    // These are the constants that can be specified in the setRequestMethod
    public static final String GET = "GET";
    public static final String POST = "POST";
    public static final String PUT = "PUT";
    public static final String HEAD = "HEAD";

    private static final String HTTP_DEFAULT_PORT = "80";
    private static final String HTTPS_DEFAULT_PORT = "443";

    private Map<String, String> requestHeaders;
    private Header responseHeaders[];

    private String requestMethod = GET;

    private HttpRequestBase request;
    private DefaultHttpClient httpClient;
    private int responseCode;
    private OutputStream outputStream;
    private String url;
    private HttpResponse httpResponse = null;
    private InputStream respInputStream;
    private ProxyConfig proxyConfig;
    private int chunkLength = -1;

    // Default connection and socket timeout of 3 * 60 seconds.  Tweak to taste.
    private static final int SOCKET_OPERATION_TIMEOUT = 3 * 60 * 1000;

    public HttpConnectionAdapter() {

        // These default values are mostly grabbed from the AndroidDefaultClient
        // implementation that was introduced in Android 2.2
        HttpParams params = new BasicHttpParams();
        params.setParameter(CoreProtocolPNames.PROTOCOL_VERSION, HttpVersion.HTTP_1_1);
        params.setParameter(CoreProtocolPNames.HTTP_CONTENT_CHARSET, HTTP.UTF_8);
        params.setParameter(CoreProtocolPNames.USER_AGENT, "Apache-HttpClient/Android");
        HttpConnectionParams.setConnectionTimeout(params, SOCKET_OPERATION_TIMEOUT);
        HttpConnectionParams.setSoTimeout(params, SOCKET_OPERATION_TIMEOUT);
        // Turn off stale checking.  Our connections break all the time anyway,
        // and it's not worth it to pay the penalty of checking every time.
        params.setParameter(CoreConnectionPNames.STALE_CONNECTION_CHECK, false);
        //HttpConnectionParams.setSocketBufferSize(params, 8192);

        SchemeRegistry schemeRegistry = new SchemeRegistry();
        schemeRegistry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
        schemeRegistry.register(new Scheme("https", SSLSocketFactory.getSocketFactory(), 443));
        ThreadSafeClientConnManager cm = new ThreadSafeClientConnManager(params, schemeRegistry);
        httpClient = new DefaultHttpClient(cm, params);
    }

    /**
     * Open the connection to the given url.
     */
    public void open(String url, ProxyConfig proxyConfig) throws IOException {
        this.url = url;
        this.proxyConfig = proxyConfig;
    }

    /**
     * This method closes this connection. It does not close the corresponding
     * input and output stream which need to be closed separately (if they were
     * previously opened)
     *
     * @throws IOException if the connection cannot be closed
     */
    public void close() throws IOException {
        if (requestHeaders != null) {
            requestHeaders.clear();
        }
    }

    /**
     * Open the input stream. The ownership of the stream is transferred to the
     * caller which is responsible to close and release the resource once it is
     * no longer used. This method shall be called only once per connection.
     *
     * @throws IOException if the input stream cannot be opened or the output
     * stream has not been closed yet.
     */
    public InputStream openInputStream() throws IOException {

        if (request == null) {
            // This is a GET request
            HttpGet req = new HttpGet(url);
            performRequest(req);
        }
        return respInputStream;
    }

    /**
     * Execute the http post operation with the given input stream to be read to
     * fetch body content.
     *
     * @throws IOException if the output stream cannot be opened.
     */
    public void execute(InputStream is, long length) throws IOException {

        Log.debug(TAG_LOG, "Opening url: " + url);
        Log.debug(TAG_LOG, "Opening with method " + requestMethod);

        String contentType = null;
        if (requestHeaders != null) {
            contentType = requestHeaders.get("Content-Type");
        }
        if (contentType == null) {
            contentType = "binary/octet-stream";
        }

        if (POST.equals(requestMethod)) {
            request = new HttpPost(url);
            if (is != null) {
                InputStreamEntity reqEntity = new InputStreamEntity(is, length);
                reqEntity.setContentType(contentType);
                if (chunkLength > 0) {
                    reqEntity.setChunked(true);
                }
                ((HttpPost) request).setEntity(reqEntity);
            }
        } else if (PUT.equals(requestMethod)) {
            request = new HttpPut(url);
            if (is != null) {
                InputStreamEntity reqEntity = new InputStreamEntity(is, length);
                reqEntity.setContentType(contentType);
                if (chunkLength > 0) {
                    reqEntity.setChunked(true);
                }
                ((HttpPost) request).setEntity(reqEntity);
            }
        } else {
            request = new HttpGet(url);
        }

        performRequest(request);
    }

    /**
     * Returns the HTTP response status code. It parses responses like:
     *
     * HTTP/1.0 200 OK
     * HTTP/1.0 401 Unauthorized
     * 
     * and extracts the ints 200 and 401 respectively. from the response (i.e., the response is not valid HTTP).
     *
     * Returns:
     *   the HTTP Status-Code or -1 if no status code can be discerned. 
     * Throws:
     *   IOException - if an error occurred connecting to the server.
     */
    public int getResponseCode() throws IOException {
        return responseCode;
    }

    /**
     * Returns the HTTP response message. It parses responses like:
     *
     * HTTP/1.0 200 OK
     * HTTP/1.0 401 Unauthorized
     * 
     * and extracts the strings OK and Unauthorized respectively. from the response (i.e., the response is not valid HTTP).
     *
     * Returns:
     *   the HTTP Response-Code or null if no status message can be discerned. 
     * Throws:
     *   IOException - if an error occurred connecting to the server.
     */
    public String getResponseMessage() throws IOException {
        return null;
    }

    /**
     * Set the method for the URL request, one of:
     * GET
     * POST
     * HEAD 
     * are legal, subject to protocol restrictions. The default method is GET. 
     */
    public void setRequestMethod(String method) throws IOException {
        requestMethod = method;
    }

    /**
     * Set chunked encoding for the content to be uploaded. This avoid the output
     * stream to buffer all data before transmitting it.
     * This is currently not supported by this implementation and the method has
     * no effect because httpclient performs chunking by default.
     *
     * @param chunkLength the length of the single chunk
     */
    public void setChunkedStreamingMode(int chunkLength) throws IOException {
        this.chunkLength = chunkLength;
    }

    /**
     * Sets the general request property. If a property with the key already exists,
     * overwrite its value with the new value.
     *
     * NOTE: HTTP requires all request properties which can legally have multiple instances
     * with the same key to use a comma-seperated list syntax which enables multiple
     * properties to be appended into a single property.
     *
     * @param key the keyword by which the request is known (e.g., "accept").
     * @param value the value associated with it.
     */
    public void setRequestProperty(String key, String value) throws IOException {
        if (requestHeaders == null) {
            requestHeaders = new HashMap<String, String>();
        }
        requestHeaders.put(key, value);
    }

    /**
     * Returns the value of the named header field.
     *
     * @param key name of a header field.
     * @return the value of the named header field, or null if there is no such field in the header.
     * @throws IOException if an error occurred connecting to the server.
     */
    public String getHeaderField(String key) throws IOException {
        if (request != null) {

            if (Log.isLoggable(Log.TRACE)) {
                Header headers[] = httpResponse.getHeaders(key);
                if (headers != null) {
                    for (int i = 0; i < headers.length; ++i) {
                        Header h = headers[i];
                        Log.trace(TAG_LOG, "Header " + key + " has value " + h.getValue());
                    }
                }
            }

            Header header = httpResponse.getFirstHeader(key);
            if (header != null) {
                return header.getValue();
            }
        }
        return null;
    }

    /**
     * Returns the key for the nth header field. Some implementations may treat the
     * 0th header field as special, i.e. as the status line returned by the HTTP server.
     * In this case, getHeaderField(0)  returns the status line, but getHeaderFieldKey(0) returns null.
     */
    public String getHeaderFieldKey(int num) throws IOException {
        if (num < responseHeaders.length) {
            Header header = responseHeaders[num];
            return header.getName();
        } else {
            return null;
        }
    }

    /**
     * Returns the answer length (excluding headers. This is the content-length
     * field length)
     */
    public int getLength() throws IOException {
        String len = getHeaderField("content-length");
        if (len == null) {
            len = getHeaderField("Content-Length");
        }
        if (len != null) {
            try {
                return Integer.parseInt(len);
            } catch (Exception e) {
                return -1;
            }
        } else {
            return -1;
        }
    }

    /**
     * Sets the timeout value in milliseconds for establishing the connection
     * to the pointed resource. A {@link SocketTimeoutException} is thrown if
     * the connection could not be established in this time. Default is 0 which
     * stands for an infinite timeout.
     * 
     * @param timeout the connecting timeout in milliseconds.
     * @throws IllegalArgumentException if the parameter <code>timeout</code> is less than zero. 
     */
    public void setConnectTimeout(int timeout) throws IllegalArgumentException {
    }

    /**
     * Sets the timeout value in milliseconds for reading from the input stream
     * of an established connection to the resource. A {@link SocketTimeoutException}
     * is thrown if the connection could not be established in this time. Default is
     * 0 which stands for an infinite timeout.
     * 
     * @param timeout the reading timeout in milliseconds.
     * @throws IllegalArgumentException if the parameter <code>timeout</code> is less than zero. 
     */
    public void setReadTimeout(int timeout) throws IllegalArgumentException {
    }

    /**
     * Returns the error input stream pointer
     */
    public InputStream getErrorStream() throws IOException {
        return new ByteArrayInputStream("".getBytes());
    }

    /**
     * Establishes the connection to the earlier configured resource.
     * The connection can only be set up before this method has been called.
     */
    public void connect() throws IOException {
    }

    private void performRequest(HttpRequestBase req) throws IOException {
        // Set all the headers
        if (requestHeaders != null) {
            for (String key : requestHeaders.keySet()) {
                String value = requestHeaders.get(key);
                // The content length is set by httpclient and it is fetched
                // from the request stream
                if (!"Content-Length".equals(key)) {
                    Log.trace(TAG_LOG, "Setting header: " + key + "=" + value);
                    req.addHeader(key, value);
                }
            }
        }

        HttpParams params = httpClient.getParams();

        // Set the proxy if necessary
        if (proxyConfig != null) {
            ConnRouteParams.setDefaultProxy(params, new HttpHost(proxyConfig.getAddress(), proxyConfig.getPort()));
            httpClient.setParams(params);
        } else {
            // TODO FIXME: remove the proxy
        }

        //FIXME
        //        Log.debug(TAG_LOG, "Setting socket buffer size");
        //        HttpConnectionParams.setSocketBufferSize(params, 900);

        try {
            Log.trace(TAG_LOG, "Executing request");
            httpResponse = httpClient.execute(req);
        } catch (Exception e) {
            Log.error(TAG_LOG, "Exception while executing request", e);
            throw new IOException(e.toString());
        }
        // Set the response
        StatusLine statusLine = httpResponse.getStatusLine();
        responseCode = statusLine.getStatusCode();

        HttpEntity respEntity = httpResponse.getEntity();
        if (respEntity != null) {
            respInputStream = respEntity.getContent();
        }
        responseHeaders = httpResponse.getAllHeaders();
    }

}