RequestQueue.java revision aaebc86386c8bb44c25dd06fe573e52ef6b60fbe
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 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; 29import 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 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 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 103 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++) { 111 ConnectionThread rt = mThreads[i]; 112 rt.mCurrentThreadTime = -1; 113 rt.mTotalThreadTime = 0; 114 } 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]; 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 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) { 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 } 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 348 synchronized boolean requestsPending() { 349 return !mPending.isEmpty(); 350 } 351 352 353 /** 354 * debug tool: prints request queue to log 355 */ 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 */ 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 */ 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 */ 417 public synchronized boolean haveRequest(HttpHost host) { 418 return mPending.containsKey(host); 419 } 420 421 /** 422 * Put request back on head of queue 423 */ 424 public void requeueRequest(Request request) { 425 queueRequest(request, true); 426 } 427 428 /** 429 * This must be called to cleanly shutdown RequestQueue 430 */ 431 public void shutdown() { 432 mActivePool.shutdown(); 433 } 434 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 452 public void startTiming() { 453 mActivePool.startTiming(); 454 } 455 456 public void stopTiming() { 457 mActivePool.stopTiming(); 458 } 459 460 /* helper */ 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 { 479 HttpHost getProxyHost(); 480 Connection getConnection(Context context, HttpHost host); 481 boolean recycleConnection(HttpHost host, Connection connection); 482 } 483} 484