• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2006 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 /**
18  * High level HTTP Interface
19  * Queues requests as necessary
20  */
21 
22 package android.net.http;
23 
24 import android.content.BroadcastReceiver;
25 import android.content.Context;
26 import android.content.Intent;
27 import android.content.IntentFilter;
28 import android.net.ConnectivityManager;
29 import android.net.NetworkInfo;
30 import android.net.Proxy;
31 import android.net.WebAddress;
32 import android.os.Handler;
33 import android.os.Message;
34 import android.os.SystemProperties;
35 import android.text.TextUtils;
36 import android.util.Log;
37 
38 import java.io.InputStream;
39 import java.util.Iterator;
40 import java.util.LinkedHashMap;
41 import java.util.LinkedList;
42 import java.util.ListIterator;
43 import java.util.Map;
44 
45 import org.apache.http.HttpHost;
46 
47 /**
48  * {@hide}
49  */
50 public class RequestQueue implements RequestFeeder {
51 
52 
53     /**
54      * Requests, indexed by HttpHost (scheme, host, port)
55      */
56     private final LinkedHashMap<HttpHost, LinkedList<Request>> mPending;
57     private final Context mContext;
58     private final ActivePool mActivePool;
59     private final ConnectivityManager mConnectivityManager;
60 
61     private HttpHost mProxyHost = null;
62     private BroadcastReceiver mProxyChangeReceiver;
63 
64     /* default simultaneous connection count */
65     private static final int CONNECTION_COUNT = 4;
66 
67     /**
68      * This class maintains active connection threads
69      */
70     class ActivePool implements ConnectionManager {
71         /** Threads used to process requests */
72         ConnectionThread[] mThreads;
73 
74         IdleCache mIdleCache;
75 
76         private int mTotalRequest;
77         private int mTotalConnection;
78         private int mConnectionCount;
79 
ActivePool(int connectionCount)80         ActivePool(int connectionCount) {
81             mIdleCache = new IdleCache();
82             mConnectionCount = connectionCount;
83             mThreads = new ConnectionThread[mConnectionCount];
84 
85             for (int i = 0; i < mConnectionCount; i++) {
86                 mThreads[i] = new ConnectionThread(
87                         mContext, i, this, RequestQueue.this);
88             }
89         }
90 
startup()91         void startup() {
92             for (int i = 0; i < mConnectionCount; i++) {
93                 mThreads[i].start();
94             }
95         }
96 
shutdown()97         void shutdown() {
98             for (int i = 0; i < mConnectionCount; i++) {
99                 mThreads[i].requestStop();
100             }
101         }
102 
startConnectionThread()103         void startConnectionThread() {
104             synchronized (RequestQueue.this) {
105                 RequestQueue.this.notify();
106             }
107         }
108 
startTiming()109         public void startTiming() {
110             for (int i = 0; i < mConnectionCount; i++) {
111                 ConnectionThread rt = mThreads[i];
112                 rt.mCurrentThreadTime = -1;
113                 rt.mTotalThreadTime = 0;
114             }
115             mTotalRequest = 0;
116             mTotalConnection = 0;
117         }
118 
stopTiming()119         public void stopTiming() {
120             int totalTime = 0;
121             for (int i = 0; i < mConnectionCount; i++) {
122                 ConnectionThread rt = mThreads[i];
123                 if (rt.mCurrentThreadTime != -1) {
124                     totalTime += rt.mTotalThreadTime;
125                 }
126                 rt.mCurrentThreadTime = 0;
127             }
128             Log.d("Http", "Http thread used " + totalTime + " ms " + " for "
129                     + mTotalRequest + " requests and " + mTotalConnection
130                     + " new connections");
131         }
132 
logState()133         void logState() {
134             StringBuilder dump = new StringBuilder();
135             for (int i = 0; i < mConnectionCount; i++) {
136                 dump.append(mThreads[i] + "\n");
137             }
138             HttpLog.v(dump.toString());
139         }
140 
141 
getProxyHost()142         public HttpHost getProxyHost() {
143             return mProxyHost;
144         }
145 
146         /**
147          * Turns off persistence on all live connections
148          */
disablePersistence()149         void disablePersistence() {
150             for (int i = 0; i < mConnectionCount; i++) {
151                 Connection connection = mThreads[i].mConnection;
152                 if (connection != null) connection.setCanPersist(false);
153             }
154             mIdleCache.clear();
155         }
156 
157         /* Linear lookup -- okay for small thread counts.  Might use
158            private HashMap<HttpHost, LinkedList<ConnectionThread>> mActiveMap;
159            if this turns out to be a hotspot */
getThread(HttpHost host)160         ConnectionThread getThread(HttpHost host) {
161             synchronized(RequestQueue.this) {
162                 for (int i = 0; i < mThreads.length; i++) {
163                     ConnectionThread ct = mThreads[i];
164                     Connection connection = ct.mConnection;
165                     if (connection != null && connection.mHost.equals(host)) {
166                         return ct;
167                     }
168                 }
169             }
170             return null;
171         }
172 
getConnection(Context context, HttpHost host)173         public Connection getConnection(Context context, HttpHost host) {
174             host = RequestQueue.this.determineHost(host);
175             Connection con = mIdleCache.getConnection(host);
176             if (con == null) {
177                 mTotalConnection++;
178                 con = Connection.getConnection(mContext, host, mProxyHost,
179                         RequestQueue.this);
180             }
181             return con;
182         }
recycleConnection(Connection connection)183         public boolean recycleConnection(Connection connection) {
184             return mIdleCache.cacheConnection(connection.getHost(), connection);
185         }
186 
187     }
188 
189     /**
190      * A RequestQueue class instance maintains a set of queued
191      * requests.  It orders them, makes the requests against HTTP
192      * servers, and makes callbacks to supplied eventHandlers as data
193      * is read.  It supports request prioritization, connection reuse
194      * and pipelining.
195      *
196      * @param context application context
197      */
RequestQueue(Context context)198     public RequestQueue(Context context) {
199         this(context, CONNECTION_COUNT);
200     }
201 
202     /**
203      * A RequestQueue class instance maintains a set of queued
204      * requests.  It orders them, makes the requests against HTTP
205      * servers, and makes callbacks to supplied eventHandlers as data
206      * is read.  It supports request prioritization, connection reuse
207      * and pipelining.
208      *
209      * @param context application context
210      * @param connectionCount The number of simultaneous connections
211      */
RequestQueue(Context context, int connectionCount)212     public RequestQueue(Context context, int connectionCount) {
213         mContext = context;
214 
215         mPending = new LinkedHashMap<HttpHost, LinkedList<Request>>(32);
216 
217         mActivePool = new ActivePool(connectionCount);
218         mActivePool.startup();
219 
220         mConnectivityManager = (ConnectivityManager)
221                 context.getSystemService(Context.CONNECTIVITY_SERVICE);
222     }
223 
224     /**
225      * Enables data state and proxy tracking
226      */
enablePlatformNotifications()227     public synchronized void enablePlatformNotifications() {
228         if (HttpLog.LOGV) HttpLog.v("RequestQueue.enablePlatformNotifications() network");
229 
230         if (mProxyChangeReceiver == null) {
231             mProxyChangeReceiver =
232                     new BroadcastReceiver() {
233                         @Override
234                         public void onReceive(Context ctx, Intent intent) {
235                             setProxyConfig();
236                         }
237                     };
238             mContext.registerReceiver(mProxyChangeReceiver,
239                                       new IntentFilter(Proxy.PROXY_CHANGE_ACTION));
240         }
241         // we need to resample the current proxy setup
242         setProxyConfig();
243     }
244 
245     /**
246      * If platform notifications have been enabled, call this method
247      * to disable before destroying RequestQueue
248      */
disablePlatformNotifications()249     public synchronized void disablePlatformNotifications() {
250         if (HttpLog.LOGV) HttpLog.v("RequestQueue.disablePlatformNotifications() network");
251 
252         if (mProxyChangeReceiver != null) {
253             mContext.unregisterReceiver(mProxyChangeReceiver);
254             mProxyChangeReceiver = null;
255         }
256     }
257 
258     /**
259      * Because our IntentReceiver can run within a different thread,
260      * synchronize setting the proxy
261      */
setProxyConfig()262     private synchronized void setProxyConfig() {
263         NetworkInfo info = mConnectivityManager.getActiveNetworkInfo();
264         if (info != null && info.getType() == ConnectivityManager.TYPE_WIFI) {
265             mProxyHost = null;
266         } else {
267             String host = Proxy.getHost(mContext);
268             if (HttpLog.LOGV) HttpLog.v("RequestQueue.setProxyConfig " + host);
269             if (host == null) {
270                 mProxyHost = null;
271             } else {
272                 mActivePool.disablePersistence();
273                 mProxyHost = new HttpHost(host, Proxy.getPort(mContext), "http");
274             }
275         }
276     }
277 
278     /**
279      * used by webkit
280      * @return proxy host if set, null otherwise
281      */
getProxyHost()282     public HttpHost getProxyHost() {
283         return mProxyHost;
284     }
285 
286     /**
287      * Queues an HTTP request
288      * @param url The url to load.
289      * @param method "GET" or "POST."
290      * @param headers A hashmap of http headers.
291      * @param eventHandler The event handler for handling returned
292      * data.  Callbacks will be made on the supplied instance.
293      * @param bodyProvider InputStream providing HTTP body, null if none
294      * @param bodyLength length of body, must be 0 if bodyProvider is null
295      */
queueRequest( String url, String method, Map<String, String> headers, EventHandler eventHandler, InputStream bodyProvider, int bodyLength)296     public RequestHandle queueRequest(
297             String url, String method,
298             Map<String, String> headers, EventHandler eventHandler,
299             InputStream bodyProvider, int bodyLength) {
300         WebAddress uri = new WebAddress(url);
301         return queueRequest(url, uri, method, headers, eventHandler,
302                             bodyProvider, bodyLength);
303     }
304 
305     /**
306      * Queues an HTTP request
307      * @param url The url to load.
308      * @param uri The uri of the url to load.
309      * @param method "GET" or "POST."
310      * @param headers A hashmap of http headers.
311      * @param eventHandler The event handler for handling returned
312      * data.  Callbacks will be made on the supplied instance.
313      * @param bodyProvider InputStream providing HTTP body, null if none
314      * @param bodyLength length of body, must be 0 if bodyProvider is null
315      */
queueRequest( String url, WebAddress uri, String method, Map<String, String> headers, EventHandler eventHandler, InputStream bodyProvider, int bodyLength)316     public RequestHandle queueRequest(
317             String url, WebAddress uri, String method, Map<String, String> headers,
318             EventHandler eventHandler,
319             InputStream bodyProvider, int bodyLength) {
320 
321         if (HttpLog.LOGV) HttpLog.v("RequestQueue.queueRequest " + uri);
322 
323         // Ensure there is an eventHandler set
324         if (eventHandler == null) {
325             eventHandler = new LoggingEventHandler();
326         }
327 
328         /* Create and queue request */
329         Request req;
330         HttpHost httpHost = new HttpHost(uri.getHost(), uri.getPort(), uri.getScheme());
331 
332         // set up request
333         req = new Request(method, httpHost, mProxyHost, uri.getPath(), bodyProvider,
334                           bodyLength, eventHandler, headers);
335 
336         queueRequest(req, false);
337 
338         mActivePool.mTotalRequest++;
339 
340         // dump();
341         mActivePool.startConnectionThread();
342 
343         return new RequestHandle(
344                 this, url, uri, method, headers, bodyProvider, bodyLength,
345                 req);
346     }
347 
348     private static class SyncFeeder implements RequestFeeder {
349         // This is used in the case where the request fails and needs to be
350         // requeued into the RequestFeeder.
351         private Request mRequest;
SyncFeeder()352         SyncFeeder() {
353         }
getRequest()354         public Request getRequest() {
355             Request r = mRequest;
356             mRequest = null;
357             return r;
358         }
getRequest(HttpHost host)359         public Request getRequest(HttpHost host) {
360             return getRequest();
361         }
haveRequest(HttpHost host)362         public boolean haveRequest(HttpHost host) {
363             return mRequest != null;
364         }
requeueRequest(Request r)365         public void requeueRequest(Request r) {
366             mRequest = r;
367         }
368     }
369 
queueSynchronousRequest(String url, WebAddress uri, String method, Map<String, String> headers, EventHandler eventHandler, InputStream bodyProvider, int bodyLength)370     public RequestHandle queueSynchronousRequest(String url, WebAddress uri,
371             String method, Map<String, String> headers,
372             EventHandler eventHandler, InputStream bodyProvider,
373             int bodyLength) {
374         if (HttpLog.LOGV) {
375             HttpLog.v("RequestQueue.dispatchSynchronousRequest " + uri);
376         }
377 
378         HttpHost host = new HttpHost(uri.getHost(), uri.getPort(), uri.getScheme());
379 
380         Request req = new Request(method, host, mProxyHost, uri.getPath(),
381                 bodyProvider, bodyLength, eventHandler, headers);
382 
383         // Open a new connection that uses our special RequestFeeder
384         // implementation.
385         host = determineHost(host);
386         Connection conn = Connection.getConnection(mContext, host, mProxyHost,
387                 new SyncFeeder());
388 
389         // TODO: I would like to process the request here but LoadListener
390         // needs a RequestHandle to process some messages.
391         return new RequestHandle(this, url, uri, method, headers, bodyProvider,
392                 bodyLength, req, conn);
393 
394     }
395 
396     // Chooses between the proxy and the request's host.
determineHost(HttpHost host)397     private HttpHost determineHost(HttpHost host) {
398         // There used to be a comment in ConnectionThread about t-mob's proxy
399         // being really bad about https. But, HttpsConnection actually looks
400         // for a proxy and connects through it anyway. I think that this check
401         // is still valid because if a site is https, we will use
402         // HttpsConnection rather than HttpConnection if the proxy address is
403         // not secure.
404         return (mProxyHost == null || "https".equals(host.getSchemeName()))
405                 ? host : mProxyHost;
406     }
407 
408     /**
409      * @return true iff there are any non-active requests pending
410      */
requestsPending()411     synchronized boolean requestsPending() {
412         return !mPending.isEmpty();
413     }
414 
415 
416     /**
417      * debug tool: prints request queue to log
418      */
dump()419     synchronized void dump() {
420         HttpLog.v("dump()");
421         StringBuilder dump = new StringBuilder();
422         int count = 0;
423         Iterator<Map.Entry<HttpHost, LinkedList<Request>>> iter;
424 
425         // mActivePool.log(dump);
426 
427         if (!mPending.isEmpty()) {
428             iter = mPending.entrySet().iterator();
429             while (iter.hasNext()) {
430                 Map.Entry<HttpHost, LinkedList<Request>> entry = iter.next();
431                 String hostName = entry.getKey().getHostName();
432                 StringBuilder line = new StringBuilder("p" + count++ + " " + hostName + " ");
433 
434                 LinkedList<Request> reqList = entry.getValue();
435                 ListIterator reqIter = reqList.listIterator(0);
436                 while (iter.hasNext()) {
437                     Request request = (Request)iter.next();
438                     line.append(request + " ");
439                 }
440                 dump.append(line);
441                 dump.append("\n");
442             }
443         }
444         HttpLog.v(dump.toString());
445     }
446 
447     /*
448      * RequestFeeder implementation
449      */
getRequest()450     public synchronized Request getRequest() {
451         Request ret = null;
452 
453         if (!mPending.isEmpty()) {
454             ret = removeFirst(mPending);
455         }
456         if (HttpLog.LOGV) HttpLog.v("RequestQueue.getRequest() => " + ret);
457         return ret;
458     }
459 
460     /**
461      * @return a request for given host if possible
462      */
getRequest(HttpHost host)463     public synchronized Request getRequest(HttpHost host) {
464         Request ret = null;
465 
466         if (mPending.containsKey(host)) {
467             LinkedList<Request> reqList = mPending.get(host);
468             ret = reqList.removeFirst();
469             if (reqList.isEmpty()) {
470                 mPending.remove(host);
471             }
472         }
473         if (HttpLog.LOGV) HttpLog.v("RequestQueue.getRequest(" + host + ") => " + ret);
474         return ret;
475     }
476 
477     /**
478      * @return true if a request for this host is available
479      */
haveRequest(HttpHost host)480     public synchronized boolean haveRequest(HttpHost host) {
481         return mPending.containsKey(host);
482     }
483 
484     /**
485      * Put request back on head of queue
486      */
requeueRequest(Request request)487     public void requeueRequest(Request request) {
488         queueRequest(request, true);
489     }
490 
491     /**
492      * This must be called to cleanly shutdown RequestQueue
493      */
shutdown()494     public void shutdown() {
495         mActivePool.shutdown();
496     }
497 
queueRequest(Request request, boolean head)498     protected synchronized void queueRequest(Request request, boolean head) {
499         HttpHost host = request.mProxyHost == null ? request.mHost : request.mProxyHost;
500         LinkedList<Request> reqList;
501         if (mPending.containsKey(host)) {
502             reqList = mPending.get(host);
503         } else {
504             reqList = new LinkedList<Request>();
505             mPending.put(host, reqList);
506         }
507         if (head) {
508             reqList.addFirst(request);
509         } else {
510             reqList.add(request);
511         }
512     }
513 
514 
startTiming()515     public void startTiming() {
516         mActivePool.startTiming();
517     }
518 
stopTiming()519     public void stopTiming() {
520         mActivePool.stopTiming();
521     }
522 
523     /* helper */
removeFirst(LinkedHashMap<HttpHost, LinkedList<Request>> requestQueue)524     private Request removeFirst(LinkedHashMap<HttpHost, LinkedList<Request>> requestQueue) {
525         Request ret = null;
526         Iterator<Map.Entry<HttpHost, LinkedList<Request>>> iter = requestQueue.entrySet().iterator();
527         if (iter.hasNext()) {
528             Map.Entry<HttpHost, LinkedList<Request>> entry = iter.next();
529             LinkedList<Request> reqList = entry.getValue();
530             ret = reqList.removeFirst();
531             if (reqList.isEmpty()) {
532                 requestQueue.remove(entry.getKey());
533             }
534         }
535         return ret;
536     }
537 
538     /**
539      * This interface is exposed to each connection
540      */
541     interface ConnectionManager {
getProxyHost()542         HttpHost getProxyHost();
getConnection(Context context, HttpHost host)543         Connection getConnection(Context context, HttpHost host);
recycleConnection(Connection connection)544         boolean recycleConnection(Connection connection);
545     }
546 }
547