• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2007 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.net.http;
18 
19 import android.content.Context;
20 import android.os.SystemClock;
21 
22 import java.io.IOException;
23 import java.net.UnknownHostException;
24 import java.util.ListIterator;
25 import java.util.LinkedList;
26 
27 import javax.net.ssl.SSLHandshakeException;
28 
29 import org.apache.http.ConnectionReuseStrategy;
30 import org.apache.http.HttpEntity;
31 import org.apache.http.HttpException;
32 import org.apache.http.HttpHost;
33 import org.apache.http.HttpVersion;
34 import org.apache.http.ParseException;
35 import org.apache.http.ProtocolVersion;
36 import org.apache.http.protocol.ExecutionContext;
37 import org.apache.http.protocol.HttpContext;
38 import org.apache.http.protocol.BasicHttpContext;
39 
40 /**
41  * {@hide}
42  */
43 abstract class Connection {
44 
45     /**
46      * Allow a TCP connection 60 idle seconds before erroring out
47      */
48     static final int SOCKET_TIMEOUT = 60000;
49 
50     private static final int SEND = 0;
51     private static final int READ = 1;
52     private static final int DRAIN = 2;
53     private static final int DONE = 3;
54     private static final String[] states = {"SEND",  "READ", "DRAIN", "DONE"};
55 
56     Context mContext;
57 
58     /** The low level connection */
59     protected AndroidHttpClientConnection mHttpClientConnection = null;
60 
61     /**
62      * The server SSL certificate associated with this connection
63      * (null if the connection is not secure)
64      * It would be nice to store the whole certificate chain, but
65      * we want to keep things as light-weight as possible
66      */
67     protected SslCertificate mCertificate = null;
68 
69     /**
70      * The host this connection is connected to.  If using proxy,
71      * this is set to the proxy address
72      */
73     HttpHost mHost;
74 
75     /** true if the connection can be reused for sending more requests */
76     private boolean mCanPersist;
77 
78     /** context required by ConnectionReuseStrategy. */
79     private HttpContext mHttpContext;
80 
81     /** set when cancelled */
82     private static int STATE_NORMAL = 0;
83     private static int STATE_CANCEL_REQUESTED = 1;
84     private int mActive = STATE_NORMAL;
85 
86     /** The number of times to try to re-connect (if connect fails). */
87     private final static int RETRY_REQUEST_LIMIT = 2;
88 
89     private static final int MIN_PIPE = 2;
90     private static final int MAX_PIPE = 3;
91 
92     /**
93      * Doesn't seem to exist anymore in the new HTTP client, so copied here.
94      */
95     private static final String HTTP_CONNECTION = "http.connection";
96 
97     RequestQueue.ConnectionManager mConnectionManager;
98     RequestFeeder mRequestFeeder;
99 
100     /**
101      * Buffer for feeding response blocks to webkit.  One block per
102      * connection reduces memory churn.
103      */
104     private byte[] mBuf;
105 
Connection(Context context, HttpHost host, RequestQueue.ConnectionManager connectionManager, RequestFeeder requestFeeder)106     protected Connection(Context context, HttpHost host,
107                          RequestQueue.ConnectionManager connectionManager,
108                          RequestFeeder requestFeeder) {
109         mContext = context;
110         mHost = host;
111         mConnectionManager = connectionManager;
112         mRequestFeeder = requestFeeder;
113 
114         mCanPersist = false;
115         mHttpContext = new BasicHttpContext(null);
116     }
117 
getHost()118     HttpHost getHost() {
119         return mHost;
120     }
121 
122     /**
123      * connection factory: returns an HTTP or HTTPS connection as
124      * necessary
125      */
getConnection( Context context, HttpHost host, RequestQueue.ConnectionManager connectionManager, RequestFeeder requestFeeder)126     static Connection getConnection(
127             Context context, HttpHost host,
128             RequestQueue.ConnectionManager connectionManager,
129             RequestFeeder requestFeeder) {
130 
131         if (host.getSchemeName().equals("http")) {
132             return new HttpConnection(context, host, connectionManager,
133                                       requestFeeder);
134         }
135 
136         // Otherwise, default to https
137         return new HttpsConnection(context, host, connectionManager,
138                                    requestFeeder);
139     }
140 
141     /**
142      * @return The server SSL certificate associated with this
143      * connection (null if the connection is not secure)
144      */
getCertificate()145     /* package */ SslCertificate getCertificate() {
146         return mCertificate;
147     }
148 
149     /**
150      * Close current network connection
151      * Note: this runs in non-network thread
152      */
cancel()153     void cancel() {
154         mActive = STATE_CANCEL_REQUESTED;
155         closeConnection();
156         if (HttpLog.LOGV) HttpLog.v(
157             "Connection.cancel(): connection closed " + mHost);
158     }
159 
160     /**
161      * Process requests in queue
162      * pipelines requests
163      */
processRequests(Request firstRequest)164     void processRequests(Request firstRequest) {
165         Request req = null;
166         boolean empty;
167         int error = EventHandler.OK;
168         Exception exception = null;
169 
170         LinkedList<Request> pipe = new LinkedList<Request>();
171 
172         int minPipe = MIN_PIPE, maxPipe = MAX_PIPE;
173         int state = SEND;
174 
175         while (state != DONE) {
176             if (HttpLog.LOGV) HttpLog.v(
177                     states[state] + " pipe " + pipe.size());
178 
179             /* If a request was cancelled, give other cancel requests
180                some time to go through so we don't uselessly restart
181                connections */
182             if (mActive == STATE_CANCEL_REQUESTED) {
183                 try {
184                     Thread.sleep(100);
185                 } catch (InterruptedException x) { /* ignore */ }
186                 mActive = STATE_NORMAL;
187             }
188 
189             switch (state) {
190                 case SEND: {
191                     if (pipe.size() == maxPipe) {
192                         state = READ;
193                         break;
194                     }
195                     /* get a request */
196                     if (firstRequest == null) {
197                         req = mRequestFeeder.getRequest(mHost);
198                     } else {
199                         req = firstRequest;
200                         firstRequest = null;
201                     }
202                     if (req == null) {
203                         state = DRAIN;
204                         break;
205                     }
206                     req.setConnection(this);
207 
208                     /* Don't work on cancelled requests. */
209                     if (req.mCancelled) {
210                         if (HttpLog.LOGV) HttpLog.v(
211                                 "processRequests(): skipping cancelled request "
212                                 + req);
213                         req.complete();
214                         break;
215                     }
216 
217                     if (mHttpClientConnection == null ||
218                         !mHttpClientConnection.isOpen()) {
219                         /* If this call fails, the address is bad or
220                            the net is down.  Punt for now.
221 
222                            FIXME: blow out entire queue here on
223                            connection failure if net up? */
224 
225                         if (!openHttpConnection(req)) {
226                             state = DONE;
227                             break;
228                         }
229                     }
230 
231                     try {
232                         /* FIXME: don't increment failure count if old
233                            connection?  There should not be a penalty for
234                            attempting to reuse an old connection */
235                         req.sendRequest(mHttpClientConnection);
236                     } catch (HttpException e) {
237                         exception = e;
238                         error = EventHandler.ERROR;
239                     } catch (IOException e) {
240                         exception = e;
241                         error = EventHandler.ERROR_IO;
242                     } catch (IllegalStateException e) {
243                         exception = e;
244                         error = EventHandler.ERROR_IO;
245                     }
246                     if (exception != null) {
247                         if (httpFailure(req, error, exception) &&
248                             !req.mCancelled) {
249                             /* retry request if not permanent failure
250                                or cancelled */
251                             pipe.addLast(req);
252                         }
253                         exception = null;
254                         state = clearPipe(pipe) ? DONE : SEND;
255                         minPipe = maxPipe = 1;
256                         break;
257                     }
258 
259                     pipe.addLast(req);
260                     if (!mCanPersist) state = READ;
261                     break;
262 
263                 }
264                 case DRAIN:
265                 case READ: {
266                     empty = !mRequestFeeder.haveRequest(mHost);
267                     int pipeSize = pipe.size();
268                     if (state != DRAIN && pipeSize < minPipe &&
269                         !empty && mCanPersist) {
270                         state = SEND;
271                         break;
272                     } else if (pipeSize == 0) {
273                         /* Done if no other work to do */
274                         state = empty ? DONE : SEND;
275                         break;
276                     }
277 
278                     req = (Request)pipe.removeFirst();
279                     if (HttpLog.LOGV) HttpLog.v(
280                             "processRequests() reading " + req);
281 
282                     try {
283                         req.readResponse(mHttpClientConnection);
284                     } catch (ParseException e) {
285                         exception = e;
286                         error = EventHandler.ERROR_IO;
287                     } catch (IOException e) {
288                         exception = e;
289                         error = EventHandler.ERROR_IO;
290                     } catch (IllegalStateException e) {
291                         exception = e;
292                         error = EventHandler.ERROR_IO;
293                     }
294                     if (exception != null) {
295                         if (httpFailure(req, error, exception) &&
296                             !req.mCancelled) {
297                             /* retry request if not permanent failure
298                                or cancelled */
299                             req.reset();
300                             pipe.addFirst(req);
301                         }
302                         exception = null;
303                         mCanPersist = false;
304                     }
305                     if (!mCanPersist) {
306                         if (HttpLog.LOGV) HttpLog.v(
307                                 "processRequests(): no persist, closing " +
308                                 mHost);
309 
310                         closeConnection();
311 
312                         mHttpContext.removeAttribute(HTTP_CONNECTION);
313                         clearPipe(pipe);
314                         minPipe = maxPipe = 1;
315                         state = SEND;
316                     }
317                     break;
318                 }
319             }
320         }
321     }
322 
323     /**
324      * After a send/receive failure, any pipelined requests must be
325      * cleared back to the mRequest queue
326      * @return true if mRequests is empty after pipe cleared
327      */
clearPipe(LinkedList<Request> pipe)328     private boolean clearPipe(LinkedList<Request> pipe) {
329         boolean empty = true;
330         if (HttpLog.LOGV) HttpLog.v(
331                 "Connection.clearPipe(): clearing pipe " + pipe.size());
332         synchronized (mRequestFeeder) {
333             Request tReq;
334             while (!pipe.isEmpty()) {
335                 tReq = (Request)pipe.removeLast();
336                 if (HttpLog.LOGV) HttpLog.v(
337                         "clearPipe() adding back " + mHost + " " + tReq);
338                 mRequestFeeder.requeueRequest(tReq);
339                 empty = false;
340             }
341             if (empty) empty = mRequestFeeder.haveRequest(mHost);
342         }
343         return empty;
344     }
345 
346     /**
347      * @return true on success
348      */
openHttpConnection(Request req)349     private boolean openHttpConnection(Request req) {
350 
351         long now = SystemClock.uptimeMillis();
352         int error = EventHandler.OK;
353         Exception exception = null;
354 
355         try {
356             // reset the certificate to null before opening a connection
357             mCertificate = null;
358             mHttpClientConnection = openConnection(req);
359             if (mHttpClientConnection != null) {
360                 mHttpClientConnection.setSocketTimeout(SOCKET_TIMEOUT);
361                 mHttpContext.setAttribute(HTTP_CONNECTION,
362                                           mHttpClientConnection);
363             } else {
364                 // we tried to do SSL tunneling, failed,
365                 // and need to drop the request;
366                 // we have already informed the handler
367                 req.mFailCount = RETRY_REQUEST_LIMIT;
368                 return false;
369             }
370         } catch (UnknownHostException e) {
371             if (HttpLog.LOGV) HttpLog.v("Failed to open connection");
372             error = EventHandler.ERROR_LOOKUP;
373             exception = e;
374         } catch (IllegalArgumentException e) {
375             if (HttpLog.LOGV) HttpLog.v("Illegal argument exception");
376             error = EventHandler.ERROR_CONNECT;
377             req.mFailCount = RETRY_REQUEST_LIMIT;
378             exception = e;
379         } catch (SSLConnectionClosedByUserException e) {
380             // hack: if we have an SSL connection failure,
381             // we don't want to reconnect
382             req.mFailCount = RETRY_REQUEST_LIMIT;
383             // no error message
384             return false;
385         } catch (SSLHandshakeException e) {
386             // hack: if we have an SSL connection failure,
387             // we don't want to reconnect
388             req.mFailCount = RETRY_REQUEST_LIMIT;
389             if (HttpLog.LOGV) HttpLog.v(
390                     "SSL exception performing handshake");
391             error = EventHandler.ERROR_FAILED_SSL_HANDSHAKE;
392             exception = e;
393         } catch (IOException e) {
394             error = EventHandler.ERROR_CONNECT;
395             exception = e;
396         }
397 
398         if (HttpLog.LOGV) {
399             long now2 = SystemClock.uptimeMillis();
400             HttpLog.v("Connection.openHttpConnection() " +
401                       (now2 - now) + " " + mHost);
402         }
403 
404         if (error == EventHandler.OK) {
405             return true;
406         } else {
407             if (req.mFailCount < RETRY_REQUEST_LIMIT) {
408                 // requeue
409                 mRequestFeeder.requeueRequest(req);
410                 req.mFailCount++;
411             } else {
412                 httpFailure(req, error, exception);
413             }
414             return error == EventHandler.OK;
415         }
416     }
417 
418     /**
419      * Helper.  Calls the mEventHandler's error() method only if
420      * request failed permanently.  Increments mFailcount on failure.
421      *
422      * Increments failcount only if the network is believed to be
423      * connected
424      *
425      * @return true if request can be retried (less than
426      * RETRY_REQUEST_LIMIT failures have occurred).
427      */
httpFailure(Request req, int errorId, Exception e)428     private boolean httpFailure(Request req, int errorId, Exception e) {
429         boolean ret = true;
430 
431         // e.printStackTrace();
432         if (HttpLog.LOGV) HttpLog.v(
433                 "httpFailure() ******* " + e + " count " + req.mFailCount +
434                 " " + mHost + " " + req.getUri());
435 
436         if (++req.mFailCount >= RETRY_REQUEST_LIMIT) {
437             ret = false;
438             String error;
439             if (errorId < 0) {
440                 error = mContext.getText(
441                         EventHandler.errorStringResources[-errorId]).toString();
442             } else {
443                 Throwable cause = e.getCause();
444                 error = cause != null ? cause.toString() : e.getMessage();
445             }
446             req.mEventHandler.error(errorId, error);
447             req.complete();
448         }
449 
450         closeConnection();
451         mHttpContext.removeAttribute(HTTP_CONNECTION);
452 
453         return ret;
454     }
455 
getHttpContext()456     HttpContext getHttpContext() {
457         return mHttpContext;
458     }
459 
460     /**
461      * Use same logic as ConnectionReuseStrategy
462      * @see ConnectionReuseStrategy
463      */
keepAlive(HttpEntity entity, ProtocolVersion ver, int connType, final HttpContext context)464     private boolean keepAlive(HttpEntity entity,
465             ProtocolVersion ver, int connType, final HttpContext context) {
466         org.apache.http.HttpConnection conn = (org.apache.http.HttpConnection)
467             context.getAttribute(ExecutionContext.HTTP_CONNECTION);
468 
469         if (conn != null && !conn.isOpen())
470             return false;
471         // do NOT check for stale connection, that is an expensive operation
472 
473         if (entity != null) {
474             if (entity.getContentLength() < 0) {
475                 if (!entity.isChunked() || ver.lessEquals(HttpVersion.HTTP_1_0)) {
476                     // if the content length is not known and is not chunk
477                     // encoded, the connection cannot be reused
478                     return false;
479                 }
480             }
481         }
482         // Check for 'Connection' directive
483         if (connType == Headers.CONN_CLOSE) {
484             return false;
485         } else if (connType == Headers.CONN_KEEP_ALIVE) {
486             return true;
487         }
488         // Resorting to protocol version default close connection policy
489         return !ver.lessEquals(HttpVersion.HTTP_1_0);
490     }
491 
setCanPersist(HttpEntity entity, ProtocolVersion ver, int connType)492     void setCanPersist(HttpEntity entity, ProtocolVersion ver, int connType) {
493         mCanPersist = keepAlive(entity, ver, connType, mHttpContext);
494     }
495 
setCanPersist(boolean canPersist)496     void setCanPersist(boolean canPersist) {
497         mCanPersist = canPersist;
498     }
499 
getCanPersist()500     boolean getCanPersist() {
501         return mCanPersist;
502     }
503 
504     /** typically http or https... set by subclass */
getScheme()505     abstract String getScheme();
closeConnection()506     abstract void closeConnection();
openConnection(Request req)507     abstract AndroidHttpClientConnection openConnection(Request req) throws IOException;
508 
509     /**
510      * Prints request queue to log, for debugging.
511      * returns request count
512      */
toString()513     public synchronized String toString() {
514         return mHost.toString();
515     }
516 
getBuf()517     byte[] getBuf() {
518         if (mBuf == null) mBuf = new byte[8192];
519         return mBuf;
520     }
521 
522 }
523