• 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             Connection con = mIdleCache.getConnection(host);
175             if (con == null) {
176                 mTotalConnection++;
177                 con = Connection.getConnection(
178                         mContext, host, this, RequestQueue.this);
179             }
180             return con;
181         }
recycleConnection(HttpHost host, Connection connection)182         public boolean recycleConnection(HttpHost host, Connection connection) {
183             return mIdleCache.cacheConnection(host, connection);
184         }
185 
186     }
187 
188     /**
189      * A RequestQueue class instance maintains a set of queued
190      * requests.  It orders them, makes the requests against HTTP
191      * servers, and makes callbacks to supplied eventHandlers as data
192      * is read.  It supports request prioritization, connection reuse
193      * and pipelining.
194      *
195      * @param context application context
196      */
RequestQueue(Context context)197     public RequestQueue(Context context) {
198         this(context, CONNECTION_COUNT);
199     }
200 
201     /**
202      * A RequestQueue class instance maintains a set of queued
203      * requests.  It orders them, makes the requests against HTTP
204      * servers, and makes callbacks to supplied eventHandlers as data
205      * is read.  It supports request prioritization, connection reuse
206      * and pipelining.
207      *
208      * @param context application context
209      * @param connectionCount The number of simultaneous connections
210      */
RequestQueue(Context context, int connectionCount)211     public RequestQueue(Context context, int connectionCount) {
212         mContext = context;
213 
214         mPending = new LinkedHashMap<HttpHost, LinkedList<Request>>(32);
215 
216         mActivePool = new ActivePool(connectionCount);
217         mActivePool.startup();
218 
219         mConnectivityManager = (ConnectivityManager)
220                 context.getSystemService(Context.CONNECTIVITY_SERVICE);
221     }
222 
223     /**
224      * Enables data state and proxy tracking
225      */
enablePlatformNotifications()226     public synchronized void enablePlatformNotifications() {
227         if (HttpLog.LOGV) HttpLog.v("RequestQueue.enablePlatformNotifications() network");
228 
229         if (mProxyChangeReceiver == null) {
230             mProxyChangeReceiver =
231                     new BroadcastReceiver() {
232                         @Override
233                         public void onReceive(Context ctx, Intent intent) {
234                             setProxyConfig();
235                         }
236                     };
237             mContext.registerReceiver(mProxyChangeReceiver,
238                                       new IntentFilter(Proxy.PROXY_CHANGE_ACTION));
239         }
240     }
241 
242     /**
243      * If platform notifications have been enabled, call this method
244      * to disable before destroying RequestQueue
245      */
disablePlatformNotifications()246     public synchronized void disablePlatformNotifications() {
247         if (HttpLog.LOGV) HttpLog.v("RequestQueue.disablePlatformNotifications() network");
248 
249         if (mProxyChangeReceiver != null) {
250             mContext.unregisterReceiver(mProxyChangeReceiver);
251             mProxyChangeReceiver = null;
252         }
253     }
254 
255     /**
256      * Because our IntentReceiver can run within a different thread,
257      * synchronize setting the proxy
258      */
setProxyConfig()259     private synchronized void setProxyConfig() {
260         NetworkInfo info = mConnectivityManager.getActiveNetworkInfo();
261         if (info != null && info.getType() == ConnectivityManager.TYPE_WIFI) {
262             mProxyHost = null;
263         } else {
264             String host = Proxy.getHost(mContext);
265             if (HttpLog.LOGV) HttpLog.v("RequestQueue.setProxyConfig " + host);
266             if (host == null) {
267                 mProxyHost = null;
268             } else {
269                 mActivePool.disablePersistence();
270                 mProxyHost = new HttpHost(host, Proxy.getPort(mContext), "http");
271             }
272         }
273     }
274 
275     /**
276      * used by webkit
277      * @return proxy host if set, null otherwise
278      */
getProxyHost()279     public HttpHost getProxyHost() {
280         return mProxyHost;
281     }
282 
283     /**
284      * Queues an HTTP request
285      * @param url The url to load.
286      * @param method "GET" or "POST."
287      * @param headers A hashmap of http headers.
288      * @param eventHandler The event handler for handling returned
289      * data.  Callbacks will be made on the supplied instance.
290      * @param bodyProvider InputStream providing HTTP body, null if none
291      * @param bodyLength length of body, must be 0 if bodyProvider is null
292      */
queueRequest( String url, String method, Map<String, String> headers, EventHandler eventHandler, InputStream bodyProvider, int bodyLength)293     public RequestHandle queueRequest(
294             String url, String method,
295             Map<String, String> headers, EventHandler eventHandler,
296             InputStream bodyProvider, int bodyLength) {
297         WebAddress uri = new WebAddress(url);
298         return queueRequest(url, uri, method, headers, eventHandler,
299                             bodyProvider, bodyLength);
300     }
301 
302     /**
303      * Queues an HTTP request
304      * @param url The url to load.
305      * @param uri The uri of the url to load.
306      * @param method "GET" or "POST."
307      * @param headers A hashmap of http headers.
308      * @param eventHandler The event handler for handling returned
309      * data.  Callbacks will be made on the supplied instance.
310      * @param bodyProvider InputStream providing HTTP body, null if none
311      * @param bodyLength length of body, must be 0 if bodyProvider is null
312      */
queueRequest( String url, WebAddress uri, String method, Map<String, String> headers, EventHandler eventHandler, InputStream bodyProvider, int bodyLength)313     public RequestHandle queueRequest(
314             String url, WebAddress uri, String method, Map<String, String> headers,
315             EventHandler eventHandler,
316             InputStream bodyProvider, int bodyLength) {
317 
318         if (HttpLog.LOGV) HttpLog.v("RequestQueue.queueRequest " + uri);
319 
320         // Ensure there is an eventHandler set
321         if (eventHandler == null) {
322             eventHandler = new LoggingEventHandler();
323         }
324 
325         /* Create and queue request */
326         Request req;
327         HttpHost httpHost = new HttpHost(uri.mHost, uri.mPort, uri.mScheme);
328 
329         // set up request
330         req = new Request(method, httpHost, mProxyHost, uri.mPath, bodyProvider,
331                           bodyLength, eventHandler, headers);
332 
333         queueRequest(req, false);
334 
335         mActivePool.mTotalRequest++;
336 
337         // dump();
338         mActivePool.startConnectionThread();
339 
340         return new RequestHandle(
341                 this, url, uri, method, headers, bodyProvider, bodyLength,
342                 req);
343     }
344 
345     /**
346      * @return true iff there are any non-active requests pending
347      */
requestsPending()348     synchronized boolean requestsPending() {
349         return !mPending.isEmpty();
350     }
351 
352 
353     /**
354      * debug tool: prints request queue to log
355      */
dump()356     synchronized void dump() {
357         HttpLog.v("dump()");
358         StringBuilder dump = new StringBuilder();
359         int count = 0;
360         Iterator<Map.Entry<HttpHost, LinkedList<Request>>> iter;
361 
362         // mActivePool.log(dump);
363 
364         if (!mPending.isEmpty()) {
365             iter = mPending.entrySet().iterator();
366             while (iter.hasNext()) {
367                 Map.Entry<HttpHost, LinkedList<Request>> entry = iter.next();
368                 String hostName = entry.getKey().getHostName();
369                 StringBuilder line = new StringBuilder("p" + count++ + " " + hostName + " ");
370 
371                 LinkedList<Request> reqList = entry.getValue();
372                 ListIterator reqIter = reqList.listIterator(0);
373                 while (iter.hasNext()) {
374                     Request request = (Request)iter.next();
375                     line.append(request + " ");
376                 }
377                 dump.append(line);
378                 dump.append("\n");
379             }
380         }
381         HttpLog.v(dump.toString());
382     }
383 
384     /*
385      * RequestFeeder implementation
386      */
getRequest()387     public synchronized Request getRequest() {
388         Request ret = null;
389 
390         if (!mPending.isEmpty()) {
391             ret = removeFirst(mPending);
392         }
393         if (HttpLog.LOGV) HttpLog.v("RequestQueue.getRequest() => " + ret);
394         return ret;
395     }
396 
397     /**
398      * @return a request for given host if possible
399      */
getRequest(HttpHost host)400     public synchronized Request getRequest(HttpHost host) {
401         Request ret = null;
402 
403         if (mPending.containsKey(host)) {
404             LinkedList<Request> reqList = mPending.get(host);
405             ret = reqList.removeFirst();
406             if (reqList.isEmpty()) {
407                 mPending.remove(host);
408             }
409         }
410         if (HttpLog.LOGV) HttpLog.v("RequestQueue.getRequest(" + host + ") => " + ret);
411         return ret;
412     }
413 
414     /**
415      * @return true if a request for this host is available
416      */
haveRequest(HttpHost host)417     public synchronized boolean haveRequest(HttpHost host) {
418         return mPending.containsKey(host);
419     }
420 
421     /**
422      * Put request back on head of queue
423      */
requeueRequest(Request request)424     public void requeueRequest(Request request) {
425         queueRequest(request, true);
426     }
427 
428     /**
429      * This must be called to cleanly shutdown RequestQueue
430      */
shutdown()431     public void shutdown() {
432         mActivePool.shutdown();
433     }
434 
queueRequest(Request request, boolean head)435     protected synchronized void queueRequest(Request request, boolean head) {
436         HttpHost host = request.mProxyHost == null ? request.mHost : request.mProxyHost;
437         LinkedList<Request> reqList;
438         if (mPending.containsKey(host)) {
439             reqList = mPending.get(host);
440         } else {
441             reqList = new LinkedList<Request>();
442             mPending.put(host, reqList);
443         }
444         if (head) {
445             reqList.addFirst(request);
446         } else {
447             reqList.add(request);
448         }
449     }
450 
451 
startTiming()452     public void startTiming() {
453         mActivePool.startTiming();
454     }
455 
stopTiming()456     public void stopTiming() {
457         mActivePool.stopTiming();
458     }
459 
460     /* helper */
removeFirst(LinkedHashMap<HttpHost, LinkedList<Request>> requestQueue)461     private Request removeFirst(LinkedHashMap<HttpHost, LinkedList<Request>> requestQueue) {
462         Request ret = null;
463         Iterator<Map.Entry<HttpHost, LinkedList<Request>>> iter = requestQueue.entrySet().iterator();
464         if (iter.hasNext()) {
465             Map.Entry<HttpHost, LinkedList<Request>> entry = iter.next();
466             LinkedList<Request> reqList = entry.getValue();
467             ret = reqList.removeFirst();
468             if (reqList.isEmpty()) {
469                 requestQueue.remove(entry.getKey());
470             }
471         }
472         return ret;
473     }
474 
475     /**
476      * This interface is exposed to each connection
477      */
478     interface ConnectionManager {
getProxyHost()479         HttpHost getProxyHost();
getConnection(Context context, HttpHost host)480         Connection getConnection(Context context, HttpHost host);
recycleConnection(HttpHost host, Connection connection)481         boolean recycleConnection(HttpHost host, Connection connection);
482     }
483 }
484