package com.github.droidfu.http;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.zip.GZIPInputStream;
import org.apache.http.Header;
import org.apache.http.HeaderElement;
import org.apache.http.HttpEntity;
import org.apache.http.HttpHost;
import org.apache.http.HttpRequest;
import org.apache.http.HttpRequestInterceptor;
import org.apache.http.HttpResponse;
import org.apache.http.HttpResponseInterceptor;
import org.apache.http.HttpVersion;
import org.apache.http.conn.params.ConnManagerParams;
import org.apache.http.conn.params.ConnPerRouteBean;
import org.apache.http.conn.params.ConnRoutePNames;
import org.apache.http.conn.scheme.PlainSocketFactory;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.entity.HttpEntityWrapper;
import org.apache.http.impl.client.AbstractHttpClient;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;
import org.apache.http.params.HttpProtocolParams;
import org.apache.http.protocol.HttpContext;
import android.content.Context;
import android.content.IntentFilter;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.Proxy;
import android.util.Log;
import com.github.droidfu.cachefu.AbstractCache;
import com.github.droidfu.cachefu.HttpResponseCache;
import com.github.droidfu.http.ssl.EasySSLSocketFactory;
import com.github.droidfu.support.DiagnosticSupport;
public class BetterHttp {
static final String LOG_TAG = "BetterHttp";
public static final int DEFAULT_MAX_CONNECTIONS = 4;
public static final int DEFAULT_SOCKET_TIMEOUT = 30 * 1000;
public static final String DEFAULT_HTTP_USER_AGENT = "Android/DroidFu";
private static final String HEADER_ACCEPT_ENCODING = "Accept-Encoding";
private static final String ENCODING_GZIP = "gzip";
private static int maxConnections = DEFAULT_MAX_CONNECTIONS;
private static int socketTimeout = DEFAULT_SOCKET_TIMEOUT;
private static String httpUserAgent = DEFAULT_HTTP_USER_AGENT;
private static HashMap<String, String> defaultHeaders = new HashMap<String, String>();
private static AbstractHttpClient httpClient;
private static Context appContext;
private static HttpResponseCache responseCache;
public static void setupHttpClient() {
BasicHttpParams httpParams = new BasicHttpParams();
ConnManagerParams.setTimeout(httpParams, socketTimeout);
ConnManagerParams.setMaxConnectionsPerRoute(httpParams,
new ConnPerRouteBean(maxConnections));
ConnManagerParams.setMaxTotalConnections(httpParams, DEFAULT_MAX_CONNECTIONS);
HttpConnectionParams.setSoTimeout(httpParams, socketTimeout);
HttpConnectionParams.setTcpNoDelay(httpParams, true);
HttpProtocolParams.setVersion(httpParams, HttpVersion.HTTP_1_1);
HttpProtocolParams.setUserAgent(httpParams, httpUserAgent);
SchemeRegistry schemeRegistry = new SchemeRegistry();
schemeRegistry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
if (DiagnosticSupport.ANDROID_API_LEVEL >= 7) {
schemeRegistry.register(new Scheme("https", SSLSocketFactory.getSocketFactory(), 443));
} else {
// used to work around a bug in Android 1.6:
// http://code.google.com/p/android/issues/detail?id=1946
// TODO: is there a less rigorous workaround for this?
schemeRegistry.register(new Scheme("https", new EasySSLSocketFactory(), 443));
}
ThreadSafeClientConnManager cm = new ThreadSafeClientConnManager(httpParams, schemeRegistry);
httpClient = new DefaultHttpClient(cm, httpParams);
}
/**
* Enables caching of HTTP responses. This will only enable the in-memory cache. If you also
* want to enable the disk cache, see {@link #enableResponseCache(Context, int, long, int, int)}
* .
*
* @param initialCapacity
* the initial element size of the cache
* @param expirationInMinutes
* time in minutes after which elements will be purged from the cache
* @param maxConcurrentThreads
* how many threads you think may at once access the cache; this need not be an exact
* number, but it helps in fragmenting the cache properly
* @see HttpResponseCache
*/
public static void enableResponseCache(int initialCapacity, long expirationInMinutes,
int maxConcurrentThreads) {
responseCache = new HttpResponseCache(initialCapacity, expirationInMinutes,
maxConcurrentThreads);
}
/**
* Intercept requests to have them ask for GZip encoding and intercept responses to
* automatically wrap the response entity for reinflation. This code is based on code from
* SyncService in the Google I/O 2010 {@linkplain http://code.google.com/p/iosched/ scheduling
* app}.
*/
public static void enableGZIPEncoding() {
httpClient.addRequestInterceptor(new GZIPHttpRequestInterceptor());
httpClient.addResponseInterceptor(new GZIPHttpResponseInterceptor());
}
/**
* Enables caching of HTTP responses. This will also enable the disk cache.
*
* @param context
* the current context
* @param initialCapacity
* the initial element size of the cache
* @param expirationInMinutes
* time in minutes after which elements will be purged from the cache (NOTE: this
* only affects the memory cache, the disk cache does currently NOT handle element
* TTLs!)
* @param maxConcurrentThreads
* how many threads you think may at once access the cache; this need not be an exact
* number, but it helps in fragmenting the cache properly
* @param diskCacheStorageDevice
* where files should be cached persistently (
* {@link AbstractCache#DISK_CACHE_INTERNAL}, {@link AbstractCache#DISK_CACHE_SDCARD}
* )
* @see HttpResponseCache
*/
public static void enableResponseCache(Context context, int initialCapacity,
long expirationInMinutes, int maxConcurrentThreads, int diskCacheStorageDevice) {
enableResponseCache(initialCapacity, expirationInMinutes, maxConcurrentThreads);
responseCache.enableDiskCache(context, diskCacheStorageDevice);
}
/**
* @return the response cache, if enabled, otherwise null
*/
public static HttpResponseCache getResponseCache() {
return responseCache;
}
public static void setHttpClient(AbstractHttpClient httpClient) {
BetterHttp.httpClient = httpClient;
}
public static AbstractHttpClient getHttpClient() {
return httpClient;
}
public static void updateProxySettings() {
if (appContext == null) {
return;
}
HttpParams httpParams = httpClient.getParams();
ConnectivityManager connectivity = (ConnectivityManager) appContext
.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo nwInfo = connectivity.getActiveNetworkInfo();
if (nwInfo == null) {
return;
}
Log.i(LOG_TAG, nwInfo.toString());
if (nwInfo.getType() == ConnectivityManager.TYPE_MOBILE) {
String proxyHost = Proxy.getHost(appContext);
if (proxyHost == null) {
proxyHost = Proxy.getDefaultHost();
}
int proxyPort = Proxy.getPort(appContext);
if (proxyPort == -1) {
proxyPort = Proxy.getDefaultPort();
}
if (proxyHost != null && proxyPort > -1) {
HttpHost proxy = new HttpHost(proxyHost, proxyPort);
httpParams.setParameter(ConnRoutePNames.DEFAULT_PROXY, proxy);
} else {
httpParams.setParameter(ConnRoutePNames.DEFAULT_PROXY, null);
}
} else {
httpParams.setParameter(ConnRoutePNames.DEFAULT_PROXY, null);
}
}
public static BetterHttpRequest get(String url) {
return get(url, false);
}
public static BetterHttpRequest get(String url, boolean cached) {
if (cached && responseCache != null && responseCache.containsKey(url)) {
return new CachedHttpRequest(url);
}
return new HttpGet(httpClient, url, defaultHeaders);
}
public static BetterHttpRequest post(String url) {
return new HttpPost(httpClient, url, defaultHeaders);
}
public static BetterHttpRequest post(String url, HttpEntity payload) {
return new HttpPost(httpClient, url, payload, defaultHeaders);
}
public static BetterHttpRequest put(String url) {
return new HttpPut(httpClient, url, defaultHeaders);
}
public static BetterHttpRequest put(String url, HttpEntity payload) {
return new HttpPut(httpClient, url, payload, defaultHeaders);
}
public static BetterHttpRequest delete(String url) {
return new HttpDelete(httpClient, url, defaultHeaders);
}
public static void setMaximumConnections(int maxConnections) {
BetterHttp.maxConnections = maxConnections;
}
/**
* Adjust the socket timeout, i.e. the amount of time that may pass when waiting for a server
* response. Time unit is milliseconds.
*
* @param socketTimeout
* the timeout in milliseconds
*/
public static void setSocketTimeout(int socketTimeout) {
BetterHttp.socketTimeout = socketTimeout;
HttpConnectionParams.setSoTimeout(httpClient.getParams(), socketTimeout);
}
public static int getSocketTimeout() {
return socketTimeout;
}
public static void setDefaultHeader(String header, String value) {
defaultHeaders.put(header, value);
}
public static HashMap<String, String> getDefaultHeaders() {
return defaultHeaders;
}
public static void setContext(Context context) {
if (appContext != null) {
return;
}
appContext = context.getApplicationContext();
appContext.registerReceiver(new ConnectionChangedBroadcastReceiver(), new IntentFilter(
ConnectivityManager.CONNECTIVITY_ACTION));
}
public static void setPortForScheme(String scheme, int port) {
Scheme _scheme = new Scheme(scheme, PlainSocketFactory.getSocketFactory(), port);
httpClient.getConnectionManager().getSchemeRegistry().register(_scheme);
}
public static void setUserAgent(String userAgent) {
BetterHttp.httpUserAgent = userAgent;
HttpProtocolParams.setUserAgent(httpClient.getParams(), userAgent);
}
/**
* Simple {@link HttpRequestInterceptor} that adds GZIP accept encoding header.
*/
static class GZIPHttpRequestInterceptor implements HttpRequestInterceptor {
public void process(final HttpRequest request, final HttpContext context) {
// Add header to accept gzip content
if (!request.containsHeader(HEADER_ACCEPT_ENCODING)) {
request.addHeader(HEADER_ACCEPT_ENCODING, ENCODING_GZIP);
}
}
}
/**
* Simple {@link HttpResponseInterceptor} that inflates response if GZIP encoding header.
*/
static class GZIPHttpResponseInterceptor implements HttpResponseInterceptor {
public void process(final HttpResponse response, final HttpContext context) {
// Inflate any responses compressed with gzip
final HttpEntity entity = response.getEntity();
final Header encoding = entity.getContentEncoding();
if (encoding != null) {
for (HeaderElement element : encoding.getElements()) {
if (element.getName().equalsIgnoreCase(ENCODING_GZIP)) {
response.setEntity(new GZIPInflatingEntity(response.getEntity()));
break;
}
}
}
}
}
/**
* Simple {@link HttpEntityWrapper} that inflates the wrapped {@link HttpEntity} by passing it
* through {@link GZIPInputStream}.
*/
static class GZIPInflatingEntity extends HttpEntityWrapper {
public GZIPInflatingEntity(final HttpEntity wrapped) {
super(wrapped);
}
@Override
public InputStream getContent() throws IOException {
return new GZIPInputStream(wrappedEntity.getContent());
}
@Override
public long getContentLength() {
return -1;
}
}
}