• 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