blob: ce6b1ad223adc5797822b94cdb9d8194b20328e4 [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001/*
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
22package android.net.http;
23
24import android.content.BroadcastReceiver;
25import android.content.Context;
26import android.content.Intent;
27import android.content.IntentFilter;
28import android.net.ConnectivityManager;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080029import android.net.NetworkInfo;
30import android.net.Proxy;
31import android.net.WebAddress;
32import android.os.Handler;
33import android.os.Message;
34import android.os.SystemProperties;
35import android.text.TextUtils;
36import android.util.Log;
37
38import java.io.InputStream;
39import java.util.Iterator;
40import java.util.LinkedHashMap;
41import java.util.LinkedList;
42import java.util.ListIterator;
43import java.util.Map;
44
45import org.apache.http.HttpHost;
46
47/**
48 * {@hide}
49 */
50public class RequestQueue implements RequestFeeder {
51
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080052
53 /**
54 * Requests, indexed by HttpHost (scheme, host, port)
55 */
Patrick Scottaaebc862009-09-16 09:59:12 -040056 private final LinkedHashMap<HttpHost, LinkedList<Request>> mPending;
57 private final Context mContext;
58 private final ActivePool mActivePool;
59 private final ConnectivityManager mConnectivityManager;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080060
61 private HttpHost mProxyHost = null;
62 private BroadcastReceiver mProxyChangeReceiver;
63
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080064 /* default simultaneous connection count */
65 private static final int CONNECTION_COUNT = 4;
66
67 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080068 * 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
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
91 void startup() {
92 for (int i = 0; i < mConnectionCount; i++) {
93 mThreads[i].start();
94 }
95 }
96
97 void shutdown() {
98 for (int i = 0; i < mConnectionCount; i++) {
99 mThreads[i].requestStop();
100 }
101 }
102
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800103 void startConnectionThread() {
104 synchronized (RequestQueue.this) {
105 RequestQueue.this.notify();
106 }
107 }
108
109 public void startTiming() {
110 for (int i = 0; i < mConnectionCount; i++) {
Grace Kloba340a1b22009-07-02 15:30:34 -0700111 ConnectionThread rt = mThreads[i];
112 rt.mCurrentThreadTime = -1;
113 rt.mTotalThreadTime = 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800114 }
115 mTotalRequest = 0;
116 mTotalConnection = 0;
117 }
118
119 public void stopTiming() {
120 int totalTime = 0;
121 for (int i = 0; i < mConnectionCount; i++) {
122 ConnectionThread rt = mThreads[i];
Grace Kloba340a1b22009-07-02 15:30:34 -0700123 if (rt.mCurrentThreadTime != -1) {
124 totalTime += rt.mTotalThreadTime;
125 }
126 rt.mCurrentThreadTime = 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800127 }
128 Log.d("Http", "Http thread used " + totalTime + " ms " + " for "
129 + mTotalRequest + " requests and " + mTotalConnection
Grace Kloba340a1b22009-07-02 15:30:34 -0700130 + " new connections");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800131 }
132
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
142 public HttpHost getProxyHost() {
143 return mProxyHost;
144 }
145
146 /**
147 * Turns off persistence on all live connections
148 */
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 */
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
173 public Connection getConnection(Context context, HttpHost host) {
Patrick Scott86806ce12009-10-01 15:54:46 -0400174 host = RequestQueue.this.determineHost(host);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800175 Connection con = mIdleCache.getConnection(host);
176 if (con == null) {
177 mTotalConnection++;
Patrick Scott86806ce12009-10-01 15:54:46 -0400178 con = Connection.getConnection(mContext, host, mProxyHost,
179 RequestQueue.this);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800180 }
181 return con;
182 }
Patrick Scott86806ce12009-10-01 15:54:46 -0400183 public boolean recycleConnection(Connection connection) {
184 return mIdleCache.cacheConnection(connection.getHost(), connection);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800185 }
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 */
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 */
212 public RequestQueue(Context context, int connectionCount) {
213 mContext = context;
214
Patrick Scottfe4fec72009-07-14 15:54:30 -0400215 mPending = new LinkedHashMap<HttpHost, LinkedList<Request>>(32);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800216
217 mActivePool = new ActivePool(connectionCount);
218 mActivePool.startup();
Patrick Scottaaebc862009-09-16 09:59:12 -0400219
220 mConnectivityManager = (ConnectivityManager)
221 context.getSystemService(Context.CONNECTIVITY_SERVICE);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800222 }
223
224 /**
225 * Enables data state and proxy tracking
226 */
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 }
Robert Greenwaltf1cac902010-03-30 19:15:32 -0700241 // we need to resample the current proxy setup
242 setProxyConfig();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800243 }
244
245 /**
246 * If platform notifications have been enabled, call this method
247 * to disable before destroying RequestQueue
248 */
249 public synchronized void disablePlatformNotifications() {
250 if (HttpLog.LOGV) HttpLog.v("RequestQueue.disablePlatformNotifications() network");
251
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800252 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 */
262 private synchronized void setProxyConfig() {
Patrick Scottaaebc862009-09-16 09:59:12 -0400263 NetworkInfo info = mConnectivityManager.getActiveNetworkInfo();
264 if (info != null && info.getType() == ConnectivityManager.TYPE_WIFI) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800265 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 */
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
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800295 */
296 public RequestHandle queueRequest(
297 String url, String method,
298 Map<String, String> headers, EventHandler eventHandler,
Patrick Scottfe4fec72009-07-14 15:54:30 -0400299 InputStream bodyProvider, int bodyLength) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800300 WebAddress uri = new WebAddress(url);
301 return queueRequest(url, uri, method, headers, eventHandler,
Patrick Scottfe4fec72009-07-14 15:54:30 -0400302 bodyProvider, bodyLength);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800303 }
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
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800315 */
316 public RequestHandle queueRequest(
317 String url, WebAddress uri, String method, Map<String, String> headers,
318 EventHandler eventHandler,
Patrick Scottfe4fec72009-07-14 15:54:30 -0400319 InputStream bodyProvider, int bodyLength) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800320
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;
Bjorn Bringerteb8be972010-10-12 16:24:55 +0100330 HttpHost httpHost = new HttpHost(uri.getHost(), uri.getPort(), uri.getScheme());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800331
332 // set up request
Bjorn Bringerteb8be972010-10-12 16:24:55 +0100333 req = new Request(method, httpHost, mProxyHost, uri.getPath(), bodyProvider,
Patrick Scottfe4fec72009-07-14 15:54:30 -0400334 bodyLength, eventHandler, headers);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800335
Patrick Scottb5b33c72009-05-15 13:47:17 -0400336 queueRequest(req, false);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800337
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
Patrick Scott86806ce12009-10-01 15:54:46 -0400348 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;
352 SyncFeeder() {
353 }
354 public Request getRequest() {
355 Request r = mRequest;
356 mRequest = null;
357 return r;
358 }
359 public Request getRequest(HttpHost host) {
360 return getRequest();
361 }
362 public boolean haveRequest(HttpHost host) {
363 return mRequest != null;
364 }
365 public void requeueRequest(Request r) {
366 mRequest = r;
367 }
368 }
369
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
Bjorn Bringerteb8be972010-10-12 16:24:55 +0100378 HttpHost host = new HttpHost(uri.getHost(), uri.getPort(), uri.getScheme());
Patrick Scott86806ce12009-10-01 15:54:46 -0400379
Bjorn Bringerteb8be972010-10-12 16:24:55 +0100380 Request req = new Request(method, host, mProxyHost, uri.getPath(),
Patrick Scott86806ce12009-10-01 15:54:46 -0400381 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.
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
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800408 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800409 * @return true iff there are any non-active requests pending
410 */
411 synchronized boolean requestsPending() {
412 return !mPending.isEmpty();
413 }
414
415
416 /**
417 * debug tool: prints request queue to log
418 */
419 synchronized void dump() {
420 HttpLog.v("dump()");
421 StringBuilder dump = new StringBuilder();
422 int count = 0;
Patrick Scottfe4fec72009-07-14 15:54:30 -0400423 Iterator<Map.Entry<HttpHost, LinkedList<Request>>> iter;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800424
425 // mActivePool.log(dump);
426
427 if (!mPending.isEmpty()) {
428 iter = mPending.entrySet().iterator();
429 while (iter.hasNext()) {
Patrick Scottfe4fec72009-07-14 15:54:30 -0400430 Map.Entry<HttpHost, LinkedList<Request>> entry = iter.next();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800431 String hostName = entry.getKey().getHostName();
432 StringBuilder line = new StringBuilder("p" + count++ + " " + hostName + " ");
433
Patrick Scottfe4fec72009-07-14 15:54:30 -0400434 LinkedList<Request> reqList = entry.getValue();
435 ListIterator reqIter = reqList.listIterator(0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800436 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 */
450 public synchronized Request getRequest() {
451 Request ret = null;
452
Patrick Scottaaebc862009-09-16 09:59:12 -0400453 if (!mPending.isEmpty()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800454 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 */
463 public synchronized Request getRequest(HttpHost host) {
464 Request ret = null;
465
Patrick Scottaaebc862009-09-16 09:59:12 -0400466 if (mPending.containsKey(host)) {
Patrick Scottfe4fec72009-07-14 15:54:30 -0400467 LinkedList<Request> reqList = mPending.get(host);
Grace Klobaa16de7b2009-07-24 16:27:11 -0700468 ret = reqList.removeFirst();
469 if (reqList.isEmpty()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800470 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 */
480 public synchronized boolean haveRequest(HttpHost host) {
481 return mPending.containsKey(host);
482 }
483
484 /**
485 * Put request back on head of queue
486 */
487 public void requeueRequest(Request request) {
488 queueRequest(request, true);
489 }
490
491 /**
492 * This must be called to cleanly shutdown RequestQueue
493 */
494 public void shutdown() {
495 mActivePool.shutdown();
496 }
497
498 protected synchronized void queueRequest(Request request, boolean head) {
499 HttpHost host = request.mProxyHost == null ? request.mHost : request.mProxyHost;
Patrick Scottfe4fec72009-07-14 15:54:30 -0400500 LinkedList<Request> reqList;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800501 if (mPending.containsKey(host)) {
502 reqList = mPending.get(host);
503 } else {
Patrick Scottfe4fec72009-07-14 15:54:30 -0400504 reqList = new LinkedList<Request>();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800505 mPending.put(host, reqList);
506 }
Patrick Scottfe4fec72009-07-14 15:54:30 -0400507 if (head) {
508 reqList.addFirst(request);
509 } else {
510 reqList.add(request);
511 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800512 }
513
514
515 public void startTiming() {
516 mActivePool.startTiming();
517 }
518
519 public void stopTiming() {
520 mActivePool.stopTiming();
521 }
522
523 /* helper */
Patrick Scottfe4fec72009-07-14 15:54:30 -0400524 private Request removeFirst(LinkedHashMap<HttpHost, LinkedList<Request>> requestQueue) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800525 Request ret = null;
Patrick Scottfe4fec72009-07-14 15:54:30 -0400526 Iterator<Map.Entry<HttpHost, LinkedList<Request>>> iter = requestQueue.entrySet().iterator();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800527 if (iter.hasNext()) {
Patrick Scottfe4fec72009-07-14 15:54:30 -0400528 Map.Entry<HttpHost, LinkedList<Request>> entry = iter.next();
529 LinkedList<Request> reqList = entry.getValue();
Grace Klobaa16de7b2009-07-24 16:27:11 -0700530 ret = reqList.removeFirst();
531 if (reqList.isEmpty()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800532 requestQueue.remove(entry.getKey());
533 }
534 }
535 return ret;
536 }
537
538 /**
539 * This interface is exposed to each connection
540 */
541 interface ConnectionManager {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800542 HttpHost getProxyHost();
543 Connection getConnection(Context context, HttpHost host);
Patrick Scott86806ce12009-10-01 15:54:46 -0400544 boolean recycleConnection(Connection connection);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800545 }
546}