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