• 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     RequestFeeder mRequestFeeder;
98 
99     /**
100      * Buffer for feeding response blocks to webkit.  One block per
101      * connection reduces memory churn.
102      */
103     private byte[] mBuf;
104 
Connection(Context context, HttpHost host, RequestFeeder requestFeeder)105     protected Connection(Context context, HttpHost host,
106                          RequestFeeder requestFeeder) {
107         mContext = context;
108         mHost = host;
109         mRequestFeeder = requestFeeder;
110 
111         mCanPersist = false;
112         mHttpContext = new BasicHttpContext(null);
113     }
114 
getHost()115     HttpHost getHost() {
116         return mHost;
117     }
118 
119     /**
120      * connection factory: returns an HTTP or HTTPS connection as
121      * necessary
122      */
getConnection( Context context, HttpHost host, HttpHost proxy, RequestFeeder requestFeeder)123     static Connection getConnection(
124             Context context, HttpHost host, HttpHost proxy,
125             RequestFeeder requestFeeder) {
126 
127         if (host.getSchemeName().equals("http")) {
128             return new HttpConnection(context, host, requestFeeder);
129         }
130 
131         // Otherwise, default to https
132         return new HttpsConnection(context, host, proxy, requestFeeder);
133     }
134 
135     /**
136      * @return The server SSL certificate associated with this
137      * connection (null if the connection is not secure)
138      */
getCertificate()139     /* package */ SslCertificate getCertificate() {
140         return mCertificate;
141     }
142 
143     /**
144      * Close current network connection
145      * Note: this runs in non-network thread
146      */
cancel()147     void cancel() {
148         mActive = STATE_CANCEL_REQUESTED;
149         closeConnection();
150         if (HttpLog.LOGV) HttpLog.v(
151             "Connection.cancel(): connection closed " + mHost);
152     }
153 
154     /**
155      * Process requests in queue
156      * pipelines requests
157      */
processRequests(Request firstRequest)158     void processRequests(Request firstRequest) {
159         Request req = null;
160         boolean empty;
161         int error = EventHandler.OK;
162         Exception exception = null;
163 
164         LinkedList<Request> pipe = new LinkedList<Request>();
165 
166         int minPipe = MIN_PIPE, maxPipe = MAX_PIPE;
167         int state = SEND;
168 
169         while (state != DONE) {
170             if (HttpLog.LOGV) HttpLog.v(
171                     states[state] + " pipe " + pipe.size());
172 
173             /* If a request was cancelled, give other cancel requests
174                some time to go through so we don't uselessly restart
175                connections */
176             if (mActive == STATE_CANCEL_REQUESTED) {
177                 try {
178                     Thread.sleep(100);
179                 } catch (InterruptedException x) { /* ignore */ }
180                 mActive = STATE_NORMAL;
181             }
182 
183             switch (state) {
184                 case SEND: {
185                     if (pipe.size() == maxPipe) {
186                         state = READ;
187                         break;
188                     }
189                     /* get a request */
190                     if (firstRequest == null) {
191                         req = mRequestFeeder.getRequest(mHost);
192                     } else {
193                         req = firstRequest;
194                         firstRequest = null;
195                     }
196                     if (req == null) {
197                         state = DRAIN;
198                         break;
199                     }
200                     req.setConnection(this);
201 
202                     /* Don't work on cancelled requests. */
203                     if (req.mCancelled) {
204                         if (HttpLog.LOGV) HttpLog.v(
205                                 "processRequests(): skipping cancelled request "
206                                 + req);
207                         req.complete();
208                         break;
209                     }
210 
211                     if (mHttpClientConnection == null ||
212                         !mHttpClientConnection.isOpen()) {
213                         /* If this call fails, the address is bad or
214                            the net is down.  Punt for now.
215 
216                            FIXME: blow out entire queue here on
217                            connection failure if net up? */
218 
219                         if (!openHttpConnection(req)) {
220                             state = DONE;
221                             break;
222                         }
223                     }
224 
225                     /* we have a connection, let the event handler
226                      * know of any associated certificate,
227                      * potentially none.
228                      */
229                     req.mEventHandler.certificate(mCertificate);
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 = ErrorStrings.getString(errorId, mContext);
441             } else {
442                 Throwable cause = e.getCause();
443                 error = cause != null ? cause.toString() : e.getMessage();
444             }
445             req.mEventHandler.error(errorId, error);
446             req.complete();
447         }
448 
449         closeConnection();
450         mHttpContext.removeAttribute(HTTP_CONNECTION);
451 
452         return ret;
453     }
454 
getHttpContext()455     HttpContext getHttpContext() {
456         return mHttpContext;
457     }
458 
459     /**
460      * Use same logic as ConnectionReuseStrategy
461      * @see ConnectionReuseStrategy
462      */
keepAlive(HttpEntity entity, ProtocolVersion ver, int connType, final HttpContext context)463     private boolean keepAlive(HttpEntity entity,
464             ProtocolVersion ver, int connType, final HttpContext context) {
465         org.apache.http.HttpConnection conn = (org.apache.http.HttpConnection)
466             context.getAttribute(ExecutionContext.HTTP_CONNECTION);
467 
468         if (conn != null && !conn.isOpen())
469             return false;
470         // do NOT check for stale connection, that is an expensive operation
471 
472         if (entity != null) {
473             if (entity.getContentLength() < 0) {
474                 if (!entity.isChunked() || ver.lessEquals(HttpVersion.HTTP_1_0)) {
475                     // if the content length is not known and is not chunk
476                     // encoded, the connection cannot be reused
477                     return false;
478                 }
479             }
480         }
481         // Check for 'Connection' directive
482         if (connType == Headers.CONN_CLOSE) {
483             return false;
484         } else if (connType == Headers.CONN_KEEP_ALIVE) {
485             return true;
486         }
487         // Resorting to protocol version default close connection policy
488         return !ver.lessEquals(HttpVersion.HTTP_1_0);
489     }
490 
setCanPersist(HttpEntity entity, ProtocolVersion ver, int connType)491     void setCanPersist(HttpEntity entity, ProtocolVersion ver, int connType) {
492         mCanPersist = keepAlive(entity, ver, connType, mHttpContext);
493     }
494 
setCanPersist(boolean canPersist)495     void setCanPersist(boolean canPersist) {
496         mCanPersist = canPersist;
497     }
498 
getCanPersist()499     boolean getCanPersist() {
500         return mCanPersist;
501     }
502 
503     /** typically http or https... set by subclass */
getScheme()504     abstract String getScheme();
closeConnection()505     abstract void closeConnection();
openConnection(Request req)506     abstract AndroidHttpClientConnection openConnection(Request req) throws IOException;
507 
508     /**
509      * Prints request queue to log, for debugging.
510      * returns request count
511      */
toString()512     public synchronized String toString() {
513         return mHost.toString();
514     }
515 
getBuf()516     byte[] getBuf() {
517         if (mBuf == null) mBuf = new byte[8192];
518         return mBuf;
519     }
520 
521 }
522