package com.buddycloud.http; import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; import java.util.concurrent.Executor; import java.util.concurrent.Executors; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpDelete; import org.apache.http.client.methods.HttpEntityEnclosingRequestBase; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpPut; import org.apache.http.client.methods.HttpRequestBase; import org.apache.http.client.params.ClientPNames; import org.apache.http.conn.ClientConnectionManager; import org.apache.http.conn.params.ConnManagerParams; import org.apache.http.conn.params.ConnPerRouteBean; 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.scheme.SocketFactory; import org.apache.http.conn.ssl.SSLSocketFactory; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.impl.client.DefaultHttpRequestRetryHandler; import org.apache.http.impl.conn.SingleClientConnManager; import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager; import org.apache.http.params.BasicHttpParams; import org.apache.http.params.HttpParams; import org.apache.http.util.EntityUtils; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import android.annotation.TargetApi; import android.content.Context; import android.content.pm.PackageInfo; import android.content.pm.PackageManager.NameNotFoundException; import android.net.Uri; import android.os.AsyncTask; import android.os.Build; import android.util.Base64; import com.buddycloud.log.Logger; import com.buddycloud.model.ModelCallback; import com.buddycloud.preferences.Preferences; public class BuddycloudHTTPHelper { private static final String BROWSER_LIKE_USER_AGENT = "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) " + "Chrome/31.0.1650.63 Safari/537.36"; private static final Executor HIGH_PRIORITY_EXECUTOR = Executors .newFixedThreadPool(5); private static final Executor LO_PRIORITY_EXECUTOR = Executors .newFixedThreadPool(5); private static final String TAG = "BuddycloudHTTPHelper"; private static HttpClient hiPriorityClient = null; private static HttpClient loPriorityClient = null; private static HttpClient getHiPriorityClient(Context context) { if (hiPriorityClient == null) { hiPriorityClient = createHttpClient(context); } return hiPriorityClient; } private static HttpClient getLoPriorityClient(Context context) { if (loPriorityClient == null) { loPriorityClient = createHttpClient(context); } return loPriorityClient; } public static void getObject(String url, Context parent, final ModelCallback<JSONObject> callback) { getObject(url, true, true, parent, callback); } public static void getObjectInLoPriority(String url, Context parent, final ModelCallback<JSONObject> callback) { getObjectInLoPriority(url, true, true, parent, callback); } public static void getArray(String url, Context parent, final ModelCallback<JSONArray> callback) { getArray(url, true, true, parent, callback); } public static void post(String url, HttpEntity entity, Context parent, final ModelCallback<JSONObject> callback) { post(url, true, true, entity, parent, callback); } public static void put(String url, HttpEntity entity, Context parent, final ModelCallback<JSONObject> callback) { reqObject("put", url, true, true, entity, parent, callback); } public static void getObject(String url, boolean auth, boolean acceptsJSON, Context parent, final ModelCallback<JSONObject> callback) { reqObject("get", url, auth, acceptsJSON, null, parent, callback); } public static void getObjectInLoPriority(String url, boolean auth, boolean acceptsJSON, Context parent, final ModelCallback<JSONObject> callback) { reqObjectInLoPriority("get", url, auth, acceptsJSON, null, parent, callback); } public static void getArray(String url, boolean auth, boolean acceptsJSON, Context parent, final ModelCallback<JSONArray> callback) { reqArray("get", url, auth, acceptsJSON, null, parent, callback); } public static void post(String url, boolean auth, boolean acceptsJSON, HttpEntity entity, Context parent, final ModelCallback<JSONObject> callback) { reqObject("post", url, auth, acceptsJSON, entity, parent, callback); } public static void postArray(String url, boolean auth, boolean acceptsJSON, HttpEntity entity, Context parent, final ModelCallback<JSONArray> callback) { reqArray("post", url, auth, acceptsJSON, entity, parent, callback); } public static void post(String url, Map<String, String> headers, HttpEntity entity, Context parent, final ModelCallback<JSONObject> callback) { reqObject("post", url, headers, entity, parent, callback); } public static void delete(String url, boolean auth, boolean acceptsJSON, Context parent, final ModelCallback<JSONObject> callback) { reqObject("delete", url, auth, acceptsJSON, null, parent, callback); } public static void delete(String url, boolean auth, boolean acceptsJSON, HttpEntity entity, Context parent, final ModelCallback<JSONObject> callback) { reqObject("delete", url, auth, acceptsJSON, entity, parent, callback); } public static void reqStatus(String url, boolean auth, Context parent, ModelCallback<Integer> callback) { RequestAsyncTask<Integer> task = new RequestAsyncTask<Integer>("get", url, null, auth, false, parent, callback) { @Override protected Integer toJSON(String responseStr) throws JSONException { return Integer.valueOf(responseStr); } }; task.returnCodeOnly = true; executeOnExecutor(task, HIGH_PRIORITY_EXECUTOR); } public static void checkSSL(String url, Context parent, ModelCallback<Integer> callback) { RequestAsyncTask<Integer> task = new RequestAsyncTask<Integer>("get", url, null, false, false, parent, callback) { @Override protected Integer toJSON(String responseStr) throws JSONException { return Integer.valueOf(responseStr); } }; task.client = createSecureHttpClient(); task.returnCodeOnly = true; executeOnExecutor(task, HIGH_PRIORITY_EXECUTOR); } private static void reqObject(String method, String url, boolean auth, boolean acceptsJSON, HttpEntity entity, Context parent, ModelCallback<JSONObject> callback) { RequestAsyncTask<JSONObject> task = new RequestAsyncTask<JSONObject>(method, url, entity, auth, acceptsJSON, parent, callback) { @Override protected JSONObject toJSON(String responseStr) throws JSONException { if (responseStr == null || responseStr.length() == 0 || responseStr.equals("OK")) { return new JSONObject(); } return new JSONObject(responseStr); } }; executeOnExecutor(task, HIGH_PRIORITY_EXECUTOR); } private static void reqObjectInLoPriority(String method, String url, boolean auth, boolean acceptsJSON, HttpEntity entity, Context parent, ModelCallback<JSONObject> callback) { RequestAsyncTask<JSONObject> task = new RequestAsyncTask<JSONObject>( method, url, entity, auth, acceptsJSON, parent, callback) { @Override protected JSONObject toJSON(String responseStr) throws JSONException { if (responseStr == null || responseStr.length() == 0 || responseStr.equals("OK")) { return new JSONObject(); } return new JSONObject(responseStr); } }; task.lo = true; executeOnExecutor(task, LO_PRIORITY_EXECUTOR); } private static void reqObject(String method, String url, Map<String, String> headers, HttpEntity entity, Context parent, ModelCallback<JSONObject> callback) { RequestAsyncTask<JSONObject> task = new RequestAsyncTask<JSONObject>( method, url, entity, false, false, parent, callback) { @Override protected JSONObject toJSON(String responseStr) throws JSONException { if (responseStr == null || responseStr.length() == 0 || responseStr.equals("OK")) { return new JSONObject(); } return new JSONObject(responseStr); } }; task.headers = headers; executeOnExecutor(task, HIGH_PRIORITY_EXECUTOR); } private static void reqArray(String method, String url, boolean auth, boolean acceptsJSON, HttpEntity entity, Context parent, ModelCallback<JSONArray> callback) { RequestAsyncTask<JSONArray> task = new RequestAsyncTask<JSONArray>(method, url, entity, auth, acceptsJSON, parent, callback) { @Override protected JSONArray toJSON(String responseStr) throws JSONException { return new JSONArray(responseStr); } }; executeOnExecutor(task, HIGH_PRIORITY_EXECUTOR); } public static void reqArrayNoSSL(String url, Context parent, ModelCallback<JSONArray> callback) { RequestAsyncTask<JSONArray> task = new RequestAsyncTask<JSONArray>( "get", url, null, false, false, parent, callback) { @Override protected JSONArray toJSON(String responseStr) throws JSONException { return new JSONArray(responseStr); } }; task.client = new DefaultHttpClient(); task.client.getParams().setParameter( ClientPNames.ALLOW_CIRCULAR_REDIRECTS, true); task.headers = new HashMap<String, String>(); task.headers.put("User-Agent", BROWSER_LIKE_USER_AGENT); executeOnExecutor(task, HIGH_PRIORITY_EXECUTOR); } @TargetApi(11) static public <T> void executeOnExecutor(RequestAsyncTask<T> task, Executor executor) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { task.executeOnExecutor(executor); } else { task.execute(); } } protected static void addAcceptJSONHeader(HttpRequestBase method) { method.setHeader("Accept", "application/json"); } protected static void addAuthHeader(HttpRequestBase method, Context parent) { String loginPref = Preferences.getPreference(parent, Preferences.MY_CHANNEL_JID); String passPref = Preferences.getPreference(parent, Preferences.PASSWORD); String auth = loginPref + ":" + passPref; String authToken = Base64.encodeToString(auth.getBytes(), Base64.NO_WRAP); method.setHeader("Authorization", "Basic " + authToken); } public static String getAuthHeader(Context parent, String password) { String loginPref = Preferences.getPreference(parent, Preferences.MY_CHANNEL_JID); String auth = loginPref + ":" + password; String authToken = Base64.encodeToString(auth.getBytes(), Base64.NO_WRAP); return "Basic " + authToken; } protected static void addUserAgentHeader(HttpRequestBase method, Context parent) { try { PackageInfo pInfo = parent.getPackageManager().getPackageInfo( parent.getPackageName(), 0); method.setHeader("User-Agent", "buddycloud for Android v" + pInfo.versionCode); } catch (NameNotFoundException e) { e.printStackTrace(); } } public static HttpClient createSecureHttpClient() { try { SchemeRegistry registry = new SchemeRegistry(); registry.register(new Scheme("https", createSecureSocketFactory(), 443)); ClientConnectionManager ccm = new SingleClientConnManager( new DefaultHttpClient().getParams(), registry); return new DefaultHttpClient(ccm, null); } catch (Exception e) { throw new RuntimeException(e); } } protected static SocketFactory createSecureSocketFactory() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { return new TLSSNISocketFactory(); } return SSLSocketFactory.getSocketFactory(); } public static HttpClient createHttpClient(Context context) { try { SchemeRegistry registry = new SchemeRegistry(); SocketFactory socketFactory = createSecureSocketFactory(); registry.register(new Scheme("http", PlainSocketFactory .getSocketFactory(), 80)); registry.register(new Scheme("https", socketFactory, 443)); HttpParams connManagerParams = new BasicHttpParams(); ConnManagerParams.setMaxTotalConnections(connManagerParams, 20); ConnManagerParams.setMaxConnectionsPerRoute(connManagerParams, new ConnPerRouteBean(20)); ClientConnectionManager ccm = new ThreadSafeClientConnManager( connManagerParams, registry); DefaultHttpClient client = new DefaultHttpClient(ccm, null); client.setHttpRequestRetryHandler(new DefaultHttpRequestRetryHandler( 0, true)); return client; } catch (Exception e) { throw new RuntimeException(e); } } public static boolean isError(int statusCode) { return statusCode >= 400; } private static abstract class RequestAsyncTask<T extends Object> extends AsyncTask<Void, Void, Object> { private static final String ALLOWED = "/:@?=&"; private boolean lo = false; private String methodType; private String url; private HttpEntity entity; private boolean auth; private boolean acceptsJSON; private Context parent; private ModelCallback<T> callback; private boolean returnCodeOnly; private HttpClient client; private Map<String, String> headers; public RequestAsyncTask(String methodType, String url, HttpEntity entity, boolean auth, boolean acceptsJSON, Context parent, ModelCallback<T> callback) { this.methodType = methodType; this.url = Uri.encode(url, ALLOWED); this.entity = entity; this.auth = auth; this.acceptsJSON = acceptsJSON; this.parent = parent; this.callback = callback; this.client = lo ? getLoPriorityClient(parent) : getHiPriorityClient(parent); } @Override protected Object doInBackground(Void... params) { try { Logger.debug(TAG, "HTTP S: {M: " + methodType + ", U: " + url + ", Lo: " + lo + "}"); long t = System.currentTimeMillis(); HttpRequestBase method = null; if (methodType.equals("get")) { method = new HttpGet(url); method.setHeader("Accept", "application/json"); } else if (methodType.equals("post") || methodType.equals("put")) { if (methodType.equals("post")) { method = new HttpPost(url); } else { method = new HttpPut(url); } if (entity != null) { ((HttpEntityEnclosingRequestBase) method) .setEntity(entity); } } else if (methodType.equals("delete")) { method = new HttpDelete(url); } if (acceptsJSON) { addAcceptJSONHeader(method); } if (auth) { addAuthHeader(method, parent); } addUserAgentHeader(method, parent); if (headers != null) { for (Entry<String, String> header : headers.entrySet()) { method.setHeader(header.getKey(), header.getValue()); } } HttpResponse response = client.execute(method); int statusCode = response.getStatusLine().getStatusCode(); Logger.debug(TAG, "HTTP R: {M: " + methodType + ", U: " + url + ", T: " + (System.currentTimeMillis() - t) + ", S: " + statusCode + "}"); if (isError(statusCode)) { // Make sure entity is consumed (released) so connection can // be re-used // this avoids the SingleClientConnManager warning about // invalid status connection not released response.getEntity().consumeContent(); return new Exception(response.getStatusLine().toString()); } if (returnCodeOnly) { return statusCode; } HttpEntity resEntityGet = ((HttpResponse) response).getEntity(); if (resEntityGet == null) { return ""; } String responseStr = EntityUtils.toString(resEntityGet, "utf-8"); return responseStr; } catch (Throwable e) { Logger.error(TAG, e.getLocalizedMessage(), e); return e; } } @Override protected void onPostExecute(Object response) { if (response instanceof Throwable) { callback.error((Throwable) response); } else { try { T jsonResponse = (T) toJSON(response.toString()); callback.success(jsonResponse); } catch (Throwable e) { callback.error(e); } } } protected abstract T toJSON(String responseStr) throws JSONException; } }