alvinalexander.com | career | drupal | java | mac | mysql | perl | scala | uml | unix  

Android example source code file (Connection.java)

This example Android source code file (Connection.java) is included in the DevDaily.com "Java Source Code Warehouse" project. The intent of this project is to help you "Learn Android by Example" TM.

Java - Android tags/keywords

android, connection, content, done, drain, httpcontext, httphost, ioexception, net, network, os, protocolversion, read, requestfeeder, retry_request_limit, send, state_cancel_requested, state_normal, string, util

The Connection.java Android example source code

/*
 * Copyright (C) 2007 The Android Open Source Project
 *
 * 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 android.net.http;

import android.content.Context;
import android.os.SystemClock;

import java.io.IOException;
import java.net.UnknownHostException;
import java.util.ListIterator;
import java.util.LinkedList;

import javax.net.ssl.SSLHandshakeException;

import org.apache.http.ConnectionReuseStrategy;
import org.apache.http.HttpEntity;
import org.apache.http.HttpException;
import org.apache.http.HttpHost;
import org.apache.http.HttpVersion;
import org.apache.http.ParseException;
import org.apache.http.ProtocolVersion;
import org.apache.http.protocol.ExecutionContext;
import org.apache.http.protocol.HttpContext;
import org.apache.http.protocol.BasicHttpContext;

/**
 * {@hide}
 */
abstract class Connection {

    /**
     * Allow a TCP connection 60 idle seconds before erroring out
     */
    static final int SOCKET_TIMEOUT = 60000;

    private static final int SEND = 0;
    private static final int READ = 1;
    private static final int DRAIN = 2;
    private static final int DONE = 3;
    private static final String[] states = {"SEND",  "READ", "DRAIN", "DONE"};

    Context mContext;

    /** The low level connection */
    protected AndroidHttpClientConnection mHttpClientConnection = null;

    /**
     * The server SSL certificate associated with this connection
     * (null if the connection is not secure)
     * It would be nice to store the whole certificate chain, but
     * we want to keep things as light-weight as possible
     */
    protected SslCertificate mCertificate = null;

    /**
     * The host this connection is connected to.  If using proxy,
     * this is set to the proxy address
     */
    HttpHost mHost;

    /** true if the connection can be reused for sending more requests */
    private boolean mCanPersist;

    /** context required by ConnectionReuseStrategy. */
    private HttpContext mHttpContext;

    /** set when cancelled */
    private static int STATE_NORMAL = 0;
    private static int STATE_CANCEL_REQUESTED = 1;
    private int mActive = STATE_NORMAL;

    /** The number of times to try to re-connect (if connect fails). */
    private final static int RETRY_REQUEST_LIMIT = 2;

    private static final int MIN_PIPE = 2;
    private static final int MAX_PIPE = 3;

    /**
     * Doesn't seem to exist anymore in the new HTTP client, so copied here.
     */
    private static final String HTTP_CONNECTION = "http.connection";

    RequestFeeder mRequestFeeder;

    /**
     * Buffer for feeding response blocks to webkit.  One block per
     * connection reduces memory churn.
     */
    private byte[] mBuf;

    protected Connection(Context context, HttpHost host,
                         RequestFeeder requestFeeder) {
        mContext = context;
        mHost = host;
        mRequestFeeder = requestFeeder;

        mCanPersist = false;
        mHttpContext = new BasicHttpContext(null);
    }

    HttpHost getHost() {
        return mHost;
    }

    /**
     * connection factory: returns an HTTP or HTTPS connection as
     * necessary
     */
    static Connection getConnection(
            Context context, HttpHost host, HttpHost proxy,
            RequestFeeder requestFeeder) {

        if (host.getSchemeName().equals("http")) {
            return new HttpConnection(context, host, requestFeeder);
        }

        // Otherwise, default to https
        return new HttpsConnection(context, host, proxy, requestFeeder);
    }

    /**
     * @return The server SSL certificate associated with this
     * connection (null if the connection is not secure)
     */
    /* package */ SslCertificate getCertificate() {
        return mCertificate;
    }

    /**
     * Close current network connection
     * Note: this runs in non-network thread
     */
    void cancel() {
        mActive = STATE_CANCEL_REQUESTED;
        closeConnection();
        if (HttpLog.LOGV) HttpLog.v(
            "Connection.cancel(): connection closed " + mHost);
    }

    /**
     * Process requests in queue
     * pipelines requests
     */
    void processRequests(Request firstRequest) {
        Request req = null;
        boolean empty;
        int error = EventHandler.OK;
        Exception exception = null;

        LinkedList<Request> pipe = new LinkedList();

        int minPipe = MIN_PIPE, maxPipe = MAX_PIPE;
        int state = SEND;

        while (state != DONE) {
            if (HttpLog.LOGV) HttpLog.v(
                    states[state] + " pipe " + pipe.size());

            /* If a request was cancelled, give other cancel requests
               some time to go through so we don't uselessly restart
               connections */
            if (mActive == STATE_CANCEL_REQUESTED) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException x) { /* ignore */ }
                mActive = STATE_NORMAL;
            }

            switch (state) {
                case SEND: {
                    if (pipe.size() == maxPipe) {
                        state = READ;
                        break;
                    }
                    /* get a request */
                    if (firstRequest == null) {
                        req = mRequestFeeder.getRequest(mHost);
                    } else {
                        req = firstRequest;
                        firstRequest = null;
                    }
                    if (req == null) {
                        state = DRAIN;
                        break;
                    }
                    req.setConnection(this);

                    /* Don't work on cancelled requests. */
                    if (req.mCancelled) {
                        if (HttpLog.LOGV) HttpLog.v(
                                "processRequests(): skipping cancelled request "
                                + req);
                        req.complete();
                        break;
                    }

                    if (mHttpClientConnection == null ||
                        !mHttpClientConnection.isOpen()) {
                        /* If this call fails, the address is bad or
                           the net is down.  Punt for now.

                           FIXME: blow out entire queue here on
                           connection failure if net up? */

                        if (!openHttpConnection(req)) {
                            state = DONE;
                            break;
                        }
                    }

                    /* we have a connection, let the event handler
                     * know of any associated certificate,
                     * potentially none.
                     */
                    req.mEventHandler.certificate(mCertificate);

                    try {
                        /* FIXME: don't increment failure count if old
                           connection?  There should not be a penalty for
                           attempting to reuse an old connection */
                        req.sendRequest(mHttpClientConnection);
                    } catch (HttpException e) {
                        exception = e;
                        error = EventHandler.ERROR;
                    } catch (IOException e) {
                        exception = e;
                        error = EventHandler.ERROR_IO;
                    } catch (IllegalStateException e) {
                        exception = e;
                        error = EventHandler.ERROR_IO;
                    }
                    if (exception != null) {
                        if (httpFailure(req, error, exception) &&
                            !req.mCancelled) {
                            /* retry request if not permanent failure
                               or cancelled */
                            pipe.addLast(req);
                        }
                        exception = null;
                        state = clearPipe(pipe) ? DONE : SEND;
                        minPipe = maxPipe = 1;
                        break;
                    }

                    pipe.addLast(req);
                    if (!mCanPersist) state = READ;
                    break;

                }
                case DRAIN:
                case READ: {
                    empty = !mRequestFeeder.haveRequest(mHost);
                    int pipeSize = pipe.size();
                    if (state != DRAIN && pipeSize < minPipe &&
                        !empty && mCanPersist) {
                        state = SEND;
                        break;
                    } else if (pipeSize == 0) {
                        /* Done if no other work to do */
                        state = empty ? DONE : SEND;
                        break;
                    }

                    req = (Request)pipe.removeFirst();
                    if (HttpLog.LOGV) HttpLog.v(
                            "processRequests() reading " + req);

                    try {
                        req.readResponse(mHttpClientConnection);
                    } catch (ParseException e) {
                        exception = e;
                        error = EventHandler.ERROR_IO;
                    } catch (IOException e) {
                        exception = e;
                        error = EventHandler.ERROR_IO;
                    } catch (IllegalStateException e) {
                        exception = e;
                        error = EventHandler.ERROR_IO;
                    }
                    if (exception != null) {
                        if (httpFailure(req, error, exception) &&
                            !req.mCancelled) {
                            /* retry request if not permanent failure
                               or cancelled */
                            req.reset();
                            pipe.addFirst(req);
                        }
                        exception = null;
                        mCanPersist = false;
                    }
                    if (!mCanPersist) {
                        if (HttpLog.LOGV) HttpLog.v(
                                "processRequests(): no persist, closing " +
                                mHost);

                        closeConnection();

                        mHttpContext.removeAttribute(HTTP_CONNECTION);
                        clearPipe(pipe);
                        minPipe = maxPipe = 1;
                        state = SEND;
                    }
                    break;
                }
            }
        }
    }

    /**
     * After a send/receive failure, any pipelined requests must be
     * cleared back to the mRequest queue
     * @return true if mRequests is empty after pipe cleared
     */
    private boolean clearPipe(LinkedList<Request> pipe) {
        boolean empty = true;
        if (HttpLog.LOGV) HttpLog.v(
                "Connection.clearPipe(): clearing pipe " + pipe.size());
        synchronized (mRequestFeeder) {
            Request tReq;
            while (!pipe.isEmpty()) {
                tReq = (Request)pipe.removeLast();
                if (HttpLog.LOGV) HttpLog.v(
                        "clearPipe() adding back " + mHost + " " + tReq);
                mRequestFeeder.requeueRequest(tReq);
                empty = false;
            }
            if (empty) empty = !mRequestFeeder.haveRequest(mHost);
        }
        return empty;
    }

    /**
     * @return true on success
     */
    private boolean openHttpConnection(Request req) {

        long now = SystemClock.uptimeMillis();
        int error = EventHandler.OK;
        Exception exception = null;

        try {
            // reset the certificate to null before opening a connection
            mCertificate = null;
            mHttpClientConnection = openConnection(req);
            if (mHttpClientConnection != null) {
                mHttpClientConnection.setSocketTimeout(SOCKET_TIMEOUT);
                mHttpContext.setAttribute(HTTP_CONNECTION,
                                          mHttpClientConnection);
            } else {
                // we tried to do SSL tunneling, failed,
                // and need to drop the request;
                // we have already informed the handler
                req.mFailCount = RETRY_REQUEST_LIMIT;
                return false;
            }
        } catch (UnknownHostException e) {
            if (HttpLog.LOGV) HttpLog.v("Failed to open connection");
            error = EventHandler.ERROR_LOOKUP;
            exception = e;
        } catch (IllegalArgumentException e) {
            if (HttpLog.LOGV) HttpLog.v("Illegal argument exception");
            error = EventHandler.ERROR_CONNECT;
            req.mFailCount = RETRY_REQUEST_LIMIT;
            exception = e;
        } catch (SSLConnectionClosedByUserException e) {
            // hack: if we have an SSL connection failure,
            // we don't want to reconnect
            req.mFailCount = RETRY_REQUEST_LIMIT;
            // no error message
            return false;
        } catch (SSLHandshakeException e) {
            // hack: if we have an SSL connection failure,
            // we don't want to reconnect
            req.mFailCount = RETRY_REQUEST_LIMIT;
            if (HttpLog.LOGV) HttpLog.v(
                    "SSL exception performing handshake");
            error = EventHandler.ERROR_FAILED_SSL_HANDSHAKE;
            exception = e;
        } catch (IOException e) {
            error = EventHandler.ERROR_CONNECT;
            exception = e;
        }

        if (HttpLog.LOGV) {
            long now2 = SystemClock.uptimeMillis();
            HttpLog.v("Connection.openHttpConnection() " +
                      (now2 - now) + " " + mHost);
        }

        if (error == EventHandler.OK) {
            return true;
        } else {
            if (req.mFailCount < RETRY_REQUEST_LIMIT) {
                // requeue
                mRequestFeeder.requeueRequest(req);
                req.mFailCount++;
            } else {
                httpFailure(req, error, exception);
            }
            return error == EventHandler.OK;
        }
    }

    /**
     * Helper.  Calls the mEventHandler's error() method only if
     * request failed permanently.  Increments mFailcount on failure.
     *
     * Increments failcount only if the network is believed to be
     * connected
     *
     * @return true if request can be retried (less than
     * RETRY_REQUEST_LIMIT failures have occurred).
     */
    private boolean httpFailure(Request req, int errorId, Exception e) {
        boolean ret = true;

        // e.printStackTrace();
        if (HttpLog.LOGV) HttpLog.v(
                "httpFailure() ******* " + e + " count " + req.mFailCount +
                " " + mHost + " " + req.getUri());

        if (++req.mFailCount >= RETRY_REQUEST_LIMIT) {
            ret = false;
            String error;
            if (errorId < 0) {
                error = mContext.getText(
                        EventHandler.errorStringResources[-errorId]).toString();
            } else {
                Throwable cause = e.getCause();
                error = cause != null ? cause.toString() : e.getMessage();
            }
            req.mEventHandler.error(errorId, error);
            req.complete();
        }

        closeConnection();
        mHttpContext.removeAttribute(HTTP_CONNECTION);

        return ret;
    }

    HttpContext getHttpContext() {
        return mHttpContext;
    }

    /**
     * Use same logic as ConnectionReuseStrategy
     * @see ConnectionReuseStrategy
     */
    private boolean keepAlive(HttpEntity entity,
            ProtocolVersion ver, int connType, final HttpContext context) {
        org.apache.http.HttpConnection conn = (org.apache.http.HttpConnection)
            context.getAttribute(ExecutionContext.HTTP_CONNECTION);

        if (conn != null && !conn.isOpen())
            return false;
        // do NOT check for stale connection, that is an expensive operation

        if (entity != null) {
            if (entity.getContentLength() < 0) {
                if (!entity.isChunked() || ver.lessEquals(HttpVersion.HTTP_1_0)) {
                    // if the content length is not known and is not chunk
                    // encoded, the connection cannot be reused
                    return false;
                }
            }
        }
        // Check for 'Connection' directive
        if (connType == Headers.CONN_CLOSE) {
            return false;
        } else if (connType == Headers.CONN_KEEP_ALIVE) {
            return true;
        }
        // Resorting to protocol version default close connection policy
        return !ver.lessEquals(HttpVersion.HTTP_1_0);
    }

    void setCanPersist(HttpEntity entity, ProtocolVersion ver, int connType) {
        mCanPersist = keepAlive(entity, ver, connType, mHttpContext);
    }

    void setCanPersist(boolean canPersist) {
        mCanPersist = canPersist;
    }

    boolean getCanPersist() {
        return mCanPersist;
    }

    /** typically http or https... set by subclass */
    abstract String getScheme();
    abstract void closeConnection();
    abstract AndroidHttpClientConnection openConnection(Request req) throws IOException;

    /**
     * Prints request queue to log, for debugging.
     * returns request count
     */
    public synchronized String toString() {
        return mHost.toString();
    }

    byte[] getBuf() {
        if (mBuf == null) mBuf = new byte[8192];
        return mBuf;
    }

}

Other Android examples (source code examples)

Here is a short list of links related to this Android Connection.java source code file:

... this post is sponsored by my books ...

#1 New Release!

FP Best Seller

 

new blog posts

 

Copyright 1998-2021 Alvin Alexander, alvinalexander.com
All Rights Reserved.

A percentage of advertising revenue from
pages under the /java/jwarehouse URI on this website is
paid back to open source projects.