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