Java tutorial
/** * 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 org.mycard.net.network; import java.io.IOException; import java.net.UnknownHostException; 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.BasicHttpContext; import org.apache.http.protocol.ExecutionContext; import org.apache.http.protocol.HttpContext; import android.content.Context; /*** * {@hide} */ abstract class Connection { /*** * Allow a TCP connection 60 idle seconds before erroring out */ static final int SOCKET_TIMEOUT = 20000; private static final int SEND = 0; private static final int READ = 1; private static final int DRAIN = 2; private static final int DONE = 3; 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 null; } /*** * Close current network connection * Note: this runs in non-network thread */ void cancel() { mActive = STATE_CANCEL_REQUESTED; closeConnection(); } /*** * 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<Request>(); int minPipe = MIN_PIPE, maxPipe = MAX_PIPE; int state = SEND; while (state != DONE) { /** 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) { 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(); 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) { 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; synchronized (mRequestFeeder) { Request tReq; while (!pipe.isEmpty()) { tReq = (Request) pipe.removeLast(); mRequestFeeder.requeueRequest(tReq); empty = false; } if (empty) empty = !mRequestFeeder.haveRequest(mHost); } return empty; } /*** * @return true on success */ private boolean openHttpConnection(Request req) { int error = EventHandler.OK; Exception exception = null; try { 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 (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 = null; 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; } }