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