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