summaryrefslogtreecommitdiffstats
path: root/android/src/android/net/http/RequestQueue.java
diff options
context:
space:
mode:
Diffstat (limited to 'android/src/android/net/http/RequestQueue.java')
-rw-r--r--android/src/android/net/http/RequestQueue.java542
1 files changed, 542 insertions, 0 deletions
diff --git a/android/src/android/net/http/RequestQueue.java b/android/src/android/net/http/RequestQueue.java
new file mode 100644
index 0000000..7d2da1b
--- /dev/null
+++ b/android/src/android/net/http/RequestQueue.java
@@ -0,0 +1,542 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * High level HTTP Interface
+ * Queues requests as necessary
+ */
+
+package android.net.http;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+import android.net.Proxy;
+import android.net.WebAddress;
+import android.util.Log;
+
+import java.io.InputStream;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
+import java.util.ListIterator;
+import java.util.Map;
+
+import org.apache.http.HttpHost;
+
+/**
+ * {@hide}
+ */
+public class RequestQueue implements RequestFeeder {
+
+
+ /**
+ * Requests, indexed by HttpHost (scheme, host, port)
+ */
+ private final LinkedHashMap<HttpHost, LinkedList<Request>> mPending;
+ private final Context mContext;
+ private final ActivePool mActivePool;
+ private final ConnectivityManager mConnectivityManager;
+
+ private HttpHost mProxyHost = null;
+ private BroadcastReceiver mProxyChangeReceiver;
+
+ /* default simultaneous connection count */
+ private static final int CONNECTION_COUNT = 4;
+
+ /**
+ * This class maintains active connection threads
+ */
+ class ActivePool implements ConnectionManager {
+ /** Threads used to process requests */
+ ConnectionThread[] mThreads;
+
+ IdleCache mIdleCache;
+
+ private int mTotalRequest;
+ private int mTotalConnection;
+ private int mConnectionCount;
+
+ ActivePool(int connectionCount) {
+ mIdleCache = new IdleCache();
+ mConnectionCount = connectionCount;
+ mThreads = new ConnectionThread[mConnectionCount];
+
+ for (int i = 0; i < mConnectionCount; i++) {
+ mThreads[i] = new ConnectionThread(
+ mContext, i, this, RequestQueue.this);
+ }
+ }
+
+ void startup() {
+ for (int i = 0; i < mConnectionCount; i++) {
+ mThreads[i].start();
+ }
+ }
+
+ void shutdown() {
+ for (int i = 0; i < mConnectionCount; i++) {
+ mThreads[i].requestStop();
+ }
+ }
+
+ void startConnectionThread() {
+ synchronized (RequestQueue.this) {
+ RequestQueue.this.notify();
+ }
+ }
+
+ public void startTiming() {
+ for (int i = 0; i < mConnectionCount; i++) {
+ ConnectionThread rt = mThreads[i];
+ rt.mCurrentThreadTime = -1;
+ rt.mTotalThreadTime = 0;
+ }
+ mTotalRequest = 0;
+ mTotalConnection = 0;
+ }
+
+ public void stopTiming() {
+ int totalTime = 0;
+ for (int i = 0; i < mConnectionCount; i++) {
+ ConnectionThread rt = mThreads[i];
+ if (rt.mCurrentThreadTime != -1) {
+ totalTime += rt.mTotalThreadTime;
+ }
+ rt.mCurrentThreadTime = 0;
+ }
+ Log.d("Http", "Http thread used " + totalTime + " ms " + " for "
+ + mTotalRequest + " requests and " + mTotalConnection
+ + " new connections");
+ }
+
+ void logState() {
+ StringBuilder dump = new StringBuilder();
+ for (int i = 0; i < mConnectionCount; i++) {
+ dump.append(mThreads[i] + "\n");
+ }
+ HttpLog.v(dump.toString());
+ }
+
+
+ public HttpHost getProxyHost() {
+ return mProxyHost;
+ }
+
+ /**
+ * Turns off persistence on all live connections
+ */
+ void disablePersistence() {
+ for (int i = 0; i < mConnectionCount; i++) {
+ Connection connection = mThreads[i].mConnection;
+ if (connection != null) connection.setCanPersist(false);
+ }
+ mIdleCache.clear();
+ }
+
+ /* Linear lookup -- okay for small thread counts. Might use
+ private HashMap<HttpHost, LinkedList<ConnectionThread>> mActiveMap;
+ if this turns out to be a hotspot */
+ ConnectionThread getThread(HttpHost host) {
+ synchronized(RequestQueue.this) {
+ for (int i = 0; i < mThreads.length; i++) {
+ ConnectionThread ct = mThreads[i];
+ Connection connection = ct.mConnection;
+ if (connection != null && connection.mHost.equals(host)) {
+ return ct;
+ }
+ }
+ }
+ return null;
+ }
+
+ public Connection getConnection(Context context, HttpHost host) {
+ host = RequestQueue.this.determineHost(host);
+ Connection con = mIdleCache.getConnection(host);
+ if (con == null) {
+ mTotalConnection++;
+ con = Connection.getConnection(mContext, host, mProxyHost,
+ RequestQueue.this);
+ }
+ return con;
+ }
+ public boolean recycleConnection(Connection connection) {
+ return mIdleCache.cacheConnection(connection.getHost(), connection);
+ }
+
+ }
+
+ /**
+ * A RequestQueue class instance maintains a set of queued
+ * requests. It orders them, makes the requests against HTTP
+ * servers, and makes callbacks to supplied eventHandlers as data
+ * is read. It supports request prioritization, connection reuse
+ * and pipelining.
+ *
+ * @param context application context
+ */
+ public RequestQueue(Context context) {
+ this(context, CONNECTION_COUNT);
+ }
+
+ /**
+ * A RequestQueue class instance maintains a set of queued
+ * requests. It orders them, makes the requests against HTTP
+ * servers, and makes callbacks to supplied eventHandlers as data
+ * is read. It supports request prioritization, connection reuse
+ * and pipelining.
+ *
+ * @param context application context
+ * @param connectionCount The number of simultaneous connections
+ */
+ public RequestQueue(Context context, int connectionCount) {
+ mContext = context;
+
+ mPending = new LinkedHashMap<HttpHost, LinkedList<Request>>(32);
+
+ mActivePool = new ActivePool(connectionCount);
+ mActivePool.startup();
+
+ mConnectivityManager = (ConnectivityManager)
+ context.getSystemService(Context.CONNECTIVITY_SERVICE);
+ }
+
+ /**
+ * Enables data state and proxy tracking
+ */
+ public synchronized void enablePlatformNotifications() {
+ if (HttpLog.LOGV) HttpLog.v("RequestQueue.enablePlatformNotifications() network");
+
+ if (mProxyChangeReceiver == null) {
+ mProxyChangeReceiver =
+ new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context ctx, Intent intent) {
+ setProxyConfig();
+ }
+ };
+ mContext.registerReceiver(mProxyChangeReceiver,
+ new IntentFilter(Proxy.PROXY_CHANGE_ACTION));
+ }
+ // we need to resample the current proxy setup
+ setProxyConfig();
+ }
+
+ /**
+ * If platform notifications have been enabled, call this method
+ * to disable before destroying RequestQueue
+ */
+ public synchronized void disablePlatformNotifications() {
+ if (HttpLog.LOGV) HttpLog.v("RequestQueue.disablePlatformNotifications() network");
+
+ if (mProxyChangeReceiver != null) {
+ mContext.unregisterReceiver(mProxyChangeReceiver);
+ mProxyChangeReceiver = null;
+ }
+ }
+
+ /**
+ * Because our IntentReceiver can run within a different thread,
+ * synchronize setting the proxy
+ */
+ private synchronized void setProxyConfig() {
+ NetworkInfo info = mConnectivityManager.getActiveNetworkInfo();
+ if (info != null && info.getType() == ConnectivityManager.TYPE_WIFI) {
+ mProxyHost = null;
+ } else {
+ String host = Proxy.getHost(mContext);
+ if (HttpLog.LOGV) HttpLog.v("RequestQueue.setProxyConfig " + host);
+ if (host == null) {
+ mProxyHost = null;
+ } else {
+ mActivePool.disablePersistence();
+ mProxyHost = new HttpHost(host, Proxy.getPort(mContext), "http");
+ }
+ }
+ }
+
+ /**
+ * used by webkit
+ * @return proxy host if set, null otherwise
+ */
+ public HttpHost getProxyHost() {
+ return mProxyHost;
+ }
+
+ /**
+ * Queues an HTTP request
+ * @param url The url to load.
+ * @param method "GET" or "POST."
+ * @param headers A hashmap of http headers.
+ * @param eventHandler The event handler for handling returned
+ * data. Callbacks will be made on the supplied instance.
+ * @param bodyProvider InputStream providing HTTP body, null if none
+ * @param bodyLength length of body, must be 0 if bodyProvider is null
+ */
+ public RequestHandle queueRequest(
+ String url, String method,
+ Map<String, String> headers, EventHandler eventHandler,
+ InputStream bodyProvider, int bodyLength) {
+ WebAddress uri = new WebAddress(url);
+ return queueRequest(url, uri, method, headers, eventHandler,
+ bodyProvider, bodyLength);
+ }
+
+ /**
+ * Queues an HTTP request
+ * @param url The url to load.
+ * @param uri The uri of the url to load.
+ * @param method "GET" or "POST."
+ * @param headers A hashmap of http headers.
+ * @param eventHandler The event handler for handling returned
+ * data. Callbacks will be made on the supplied instance.
+ * @param bodyProvider InputStream providing HTTP body, null if none
+ * @param bodyLength length of body, must be 0 if bodyProvider is null
+ */
+ public RequestHandle queueRequest(
+ String url, WebAddress uri, String method, Map<String, String> headers,
+ EventHandler eventHandler,
+ InputStream bodyProvider, int bodyLength) {
+
+ if (HttpLog.LOGV) HttpLog.v("RequestQueue.queueRequest " + uri);
+
+ // Ensure there is an eventHandler set
+ if (eventHandler == null) {
+ eventHandler = new LoggingEventHandler();
+ }
+
+ /* Create and queue request */
+ Request req;
+ HttpHost httpHost = new HttpHost(uri.getHost(), uri.getPort(), uri.getScheme());
+
+ // set up request
+ req = new Request(method, httpHost, mProxyHost, uri.getPath(), bodyProvider,
+ bodyLength, eventHandler, headers);
+
+ queueRequest(req, false);
+
+ mActivePool.mTotalRequest++;
+
+ // dump();
+ mActivePool.startConnectionThread();
+
+ return new RequestHandle(
+ this, url, uri, method, headers, bodyProvider, bodyLength,
+ req);
+ }
+
+ private static class SyncFeeder implements RequestFeeder {
+ // This is used in the case where the request fails and needs to be
+ // requeued into the RequestFeeder.
+ private Request mRequest;
+ SyncFeeder() {
+ }
+ public Request getRequest() {
+ Request r = mRequest;
+ mRequest = null;
+ return r;
+ }
+ public Request getRequest(HttpHost host) {
+ return getRequest();
+ }
+ public boolean haveRequest(HttpHost host) {
+ return mRequest != null;
+ }
+ public void requeueRequest(Request r) {
+ mRequest = r;
+ }
+ }
+
+ public RequestHandle queueSynchronousRequest(String url, WebAddress uri,
+ String method, Map<String, String> headers,
+ EventHandler eventHandler, InputStream bodyProvider,
+ int bodyLength) {
+ if (HttpLog.LOGV) {
+ HttpLog.v("RequestQueue.dispatchSynchronousRequest " + uri);
+ }
+
+ HttpHost host = new HttpHost(uri.getHost(), uri.getPort(), uri.getScheme());
+
+ Request req = new Request(method, host, mProxyHost, uri.getPath(),
+ bodyProvider, bodyLength, eventHandler, headers);
+
+ // Open a new connection that uses our special RequestFeeder
+ // implementation.
+ host = determineHost(host);
+ Connection conn = Connection.getConnection(mContext, host, mProxyHost,
+ new SyncFeeder());
+
+ // TODO: I would like to process the request here but LoadListener
+ // needs a RequestHandle to process some messages.
+ return new RequestHandle(this, url, uri, method, headers, bodyProvider,
+ bodyLength, req, conn);
+
+ }
+
+ // Chooses between the proxy and the request's host.
+ private HttpHost determineHost(HttpHost host) {
+ // There used to be a comment in ConnectionThread about t-mob's proxy
+ // being really bad about https. But, HttpsConnection actually looks
+ // for a proxy and connects through it anyway. I think that this check
+ // is still valid because if a site is https, we will use
+ // HttpsConnection rather than HttpConnection if the proxy address is
+ // not secure.
+ return (mProxyHost == null || "https".equals(host.getSchemeName()))
+ ? host : mProxyHost;
+ }
+
+ /**
+ * @return true iff there are any non-active requests pending
+ */
+ synchronized boolean requestsPending() {
+ return !mPending.isEmpty();
+ }
+
+
+ /**
+ * debug tool: prints request queue to log
+ */
+ synchronized void dump() {
+ HttpLog.v("dump()");
+ StringBuilder dump = new StringBuilder();
+ int count = 0;
+ Iterator<Map.Entry<HttpHost, LinkedList<Request>>> iter;
+
+ // mActivePool.log(dump);
+
+ if (!mPending.isEmpty()) {
+ iter = mPending.entrySet().iterator();
+ while (iter.hasNext()) {
+ Map.Entry<HttpHost, LinkedList<Request>> entry = iter.next();
+ String hostName = entry.getKey().getHostName();
+ StringBuilder line = new StringBuilder("p" + count++ + " " + hostName + " ");
+
+ LinkedList<Request> reqList = entry.getValue();
+ ListIterator reqIter = reqList.listIterator(0);
+ while (iter.hasNext()) {
+ Request request = (Request)iter.next();
+ line.append(request + " ");
+ }
+ dump.append(line);
+ dump.append("\n");
+ }
+ }
+ HttpLog.v(dump.toString());
+ }
+
+ /*
+ * RequestFeeder implementation
+ */
+ public synchronized Request getRequest() {
+ Request ret = null;
+
+ if (!mPending.isEmpty()) {
+ ret = removeFirst(mPending);
+ }
+ if (HttpLog.LOGV) HttpLog.v("RequestQueue.getRequest() => " + ret);
+ return ret;
+ }
+
+ /**
+ * @return a request for given host if possible
+ */
+ public synchronized Request getRequest(HttpHost host) {
+ Request ret = null;
+
+ if (mPending.containsKey(host)) {
+ LinkedList<Request> reqList = mPending.get(host);
+ ret = reqList.removeFirst();
+ if (reqList.isEmpty()) {
+ mPending.remove(host);
+ }
+ }
+ if (HttpLog.LOGV) HttpLog.v("RequestQueue.getRequest(" + host + ") => " + ret);
+ return ret;
+ }
+
+ /**
+ * @return true if a request for this host is available
+ */
+ public synchronized boolean haveRequest(HttpHost host) {
+ return mPending.containsKey(host);
+ }
+
+ /**
+ * Put request back on head of queue
+ */
+ public void requeueRequest(Request request) {
+ queueRequest(request, true);
+ }
+
+ /**
+ * This must be called to cleanly shutdown RequestQueue
+ */
+ public void shutdown() {
+ mActivePool.shutdown();
+ }
+
+ protected synchronized void queueRequest(Request request, boolean head) {
+ HttpHost host = request.mProxyHost == null ? request.mHost : request.mProxyHost;
+ LinkedList<Request> reqList;
+ if (mPending.containsKey(host)) {
+ reqList = mPending.get(host);
+ } else {
+ reqList = new LinkedList<Request>();
+ mPending.put(host, reqList);
+ }
+ if (head) {
+ reqList.addFirst(request);
+ } else {
+ reqList.add(request);
+ }
+ }
+
+
+ public void startTiming() {
+ mActivePool.startTiming();
+ }
+
+ public void stopTiming() {
+ mActivePool.stopTiming();
+ }
+
+ /* helper */
+ private Request removeFirst(LinkedHashMap<HttpHost, LinkedList<Request>> requestQueue) {
+ Request ret = null;
+ Iterator<Map.Entry<HttpHost, LinkedList<Request>>> iter = requestQueue.entrySet().iterator();
+ if (iter.hasNext()) {
+ Map.Entry<HttpHost, LinkedList<Request>> entry = iter.next();
+ LinkedList<Request> reqList = entry.getValue();
+ ret = reqList.removeFirst();
+ if (reqList.isEmpty()) {
+ requestQueue.remove(entry.getKey());
+ }
+ }
+ return ret;
+ }
+
+ /**
+ * This interface is exposed to each connection
+ */
+ interface ConnectionManager {
+ HttpHost getProxyHost();
+ Connection getConnection(Context context, HttpHost host);
+ boolean recycleConnection(Connection connection);
+ }
+}