From 04a14d0a89638a0a74c07e61e4f6923b024bc9c4 Mon Sep 17 00:00:00 2001 From: Joe Anderson Date: Thu, 21 Jun 2012 12:12:17 -0500 Subject: [PATCH 001/613] Added code to allow more than one param with the same key. --- local.properties.dist | 1 - src/com/loopj/android/http/RequestParams.java | 44 +++++++++++++++++++ 2 files changed, 44 insertions(+), 1 deletion(-) delete mode 100644 local.properties.dist diff --git a/local.properties.dist b/local.properties.dist deleted file mode 100644 index 5ecd58e14..000000000 --- a/local.properties.dist +++ /dev/null @@ -1 +0,0 @@ -sdk.dir=/usr/local/android_sdk/ \ No newline at end of file diff --git a/src/com/loopj/android/http/RequestParams.java b/src/com/loopj/android/http/RequestParams.java index 2c80e35cb..ce6af1db1 100644 --- a/src/com/loopj/android/http/RequestParams.java +++ b/src/com/loopj/android/http/RequestParams.java @@ -23,6 +23,7 @@ import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.UnsupportedEncodingException; +import java.util.ArrayList; import java.util.LinkedList; import java.util.List; import java.util.Map; @@ -57,6 +58,7 @@ public class RequestParams { protected ConcurrentHashMap urlParams; protected ConcurrentHashMap fileParams; + protected ConcurrentHashMap> urlParamsWithArray; /** * Constructs a new empty RequestParams instance. @@ -110,6 +112,17 @@ public void put(String key, File file) throws FileNotFoundException { put(key, new FileInputStream(file), file.getName()); } + /** + * Adds param with more than one value. + * @param key the key name for the new param. + * @param values is the ArrayList with values for the param. + */ + public void put(String key, ArrayList values) { + if(key != null && values != null) { + urlParamsWithArray.put(key, values); + } + } + /** * Adds an input stream to the request. * @param key the key name for the new param. @@ -149,6 +162,7 @@ public void put(String key, InputStream stream, String fileName, String contentT public void remove(String key){ urlParams.remove(key); fileParams.remove(key); + urlParamsWithArray.remove(key); } @Override @@ -172,6 +186,20 @@ public String toString() { result.append("FILE"); } + for(ConcurrentHashMap.Entry> entry : urlParamsWithArray.entrySet()) { + if(result.length() > 0) + result.append("&"); + + ArrayList values = entry.getValue(); + for (String value : values) { + if (values.indexOf(value) != 0) + result.append("&"); + result.append(entry.getKey()); + result.append("="); + result.append(value); + } + } + return result.toString(); } @@ -205,6 +233,14 @@ public HttpEntity getEntity() { currentIndex++; } + // Add dupe params + for(ConcurrentHashMap.Entry> entry : urlParamsWithArray.entrySet()) { + ArrayList values = entry.getValue(); + for (String value : values) { + multipartEntity.addPart(entry.getKey(), value); + } + } + entity = multipartEntity; } else { try { @@ -220,6 +256,7 @@ public HttpEntity getEntity() { private void init(){ urlParams = new ConcurrentHashMap(); fileParams = new ConcurrentHashMap(); + urlParamsWithArray = new ConcurrentHashMap>(); } protected List getParamsList() { @@ -229,6 +266,13 @@ protected List getParamsList() { lparams.add(new BasicNameValuePair(entry.getKey(), entry.getValue())); } + for(ConcurrentHashMap.Entry> entry : urlParamsWithArray.entrySet()) { + ArrayList values = entry.getValue(); + for (String value : values) { + lparams.add(new BasicNameValuePair(entry.getKey(), value)); + } + } + return lparams; } From a6af758f66249bb4ede8f874188dbcecb61b4f2a Mon Sep 17 00:00:00 2001 From: Peter Edwards Date: Thu, 8 Nov 2012 18:01:09 +0200 Subject: [PATCH 002/613] Refactored AsyncHttpResponseHandler.java code base to facilitate a single base class and to use binary as the base response format Refactored dependent AsyncHttpResponseHandler.java classes: BinaryHttpResponseHandler.java, JsonHttpResponseHandler.java Added new ResponseHandler: TextHttpResponseHandler.java Cleaned up exception handling, added missing exception handling and mofified RetryHandler behavior to be more robust Moved handler to separate class to avoid leaks in AsyncHttpRequest.java Added PROGRESS and RETRY messages Signed-off-by: Peter Edwards --- .classpath | 17 +- examples/ExampleUsage.java | 2 +- .../loopj/android/http/AsyncHttpClient.java | 19 +- .../loopj/android/http/AsyncHttpRequest.java | 61 +++-- .../http/AsyncHttpResponseHandler.java | 224 ++++++++++++------ .../http/BinaryHttpResponseHandler.java | 76 +----- .../android/http/JsonHttpResponseHandler.java | 59 +++-- src/com/loopj/android/http/RetryHandler.java | 9 +- .../android/http/TextHttpResponseHandler.java | 125 ++++++++++ 9 files changed, 371 insertions(+), 221 deletions(-) create mode 100644 src/com/loopj/android/http/TextHttpResponseHandler.java diff --git a/.classpath b/.classpath index 14fcf527f..6c6163d3b 100644 --- a/.classpath +++ b/.classpath @@ -1,8 +1,9 @@ - - - - - - - - + + + + + + + + + diff --git a/examples/ExampleUsage.java b/examples/ExampleUsage.java index 2b7a4fa0a..202ff68c7 100644 --- a/examples/ExampleUsage.java +++ b/examples/ExampleUsage.java @@ -4,7 +4,7 @@ public class ExampleUsage { public static void makeRequest() { AsyncHttpClient client = new AsyncHttpClient(); - client.get("http://www.google.com/", new AsyncHttpResponseHandler() { + client.get("http://www.google.com/", new TextHttpResponseHandler() { @Override public void onSuccess(String response) { System.out.println(response); diff --git a/src/com/loopj/android/http/AsyncHttpClient.java b/src/com/loopj/android/http/AsyncHttpClient.java index 19e383927..c924a9bb0 100644 --- a/src/com/loopj/android/http/AsyncHttpClient.java +++ b/src/com/loopj/android/http/AsyncHttpClient.java @@ -81,10 +81,10 @@ *

*

  * AsyncHttpClient client = new AsyncHttpClient();
- * client.get("http://www.google.com/", new AsyncHttpResponseHandler() {
+ * client.get("http://www.google.com/", new TextHttpResponseHandler() {
  *     @Override
- *     public void onSuccess(String response) {
- *         System.out.println(response);
+ *     public void onSuccess(String responseBody) {
+ *         System.out.println(responseBody);
  *     }
  * });
  * 
@@ -95,6 +95,7 @@ public class AsyncHttpClient { private static final int DEFAULT_MAX_CONNECTIONS = 10; private static final int DEFAULT_SOCKET_TIMEOUT = 10 * 1000; private static final int DEFAULT_MAX_RETRIES = 5; + private static final int DEFAULT_RETRY_SLEEP_TIME_MILLIS = 1500; private static final int DEFAULT_SOCKET_BUFFER_SIZE = 8192; private static final String HEADER_ACCEPT_ENCODING = "Accept-Encoding"; private static final String ENCODING_GZIP = "gzip"; @@ -113,6 +114,14 @@ public class AsyncHttpClient { * Creates a new AsyncHttpClient. */ public AsyncHttpClient() { + this(DEFAULT_MAX_RETRIES, DEFAULT_RETRY_SLEEP_TIME_MILLIS); + } + + public AsyncHttpClient(int maxRetries) { + this(maxRetries, DEFAULT_RETRY_SLEEP_TIME_MILLIS); + } + + public AsyncHttpClient(int maxRetries, int retrySleepTimeMS) { BasicHttpParams httpParams = new BasicHttpParams(); ConnManagerParams.setTimeout(httpParams, socketTimeout); @@ -163,7 +172,9 @@ public void process(HttpResponse response, HttpContext context) { } }); - httpClient.setHttpRequestRetryHandler(new RetryHandler(DEFAULT_MAX_RETRIES)); + if(maxRetries < 0) maxRetries = DEFAULT_MAX_RETRIES; + if(retrySleepTimeMS <0) retrySleepTimeMS = DEFAULT_RETRY_SLEEP_TIME_MILLIS; + httpClient.setHttpRequestRetryHandler(new RetryHandler(maxRetries, retrySleepTimeMS)); threadPool = (ThreadPoolExecutor)Executors.newCachedThreadPool(); diff --git a/src/com/loopj/android/http/AsyncHttpRequest.java b/src/com/loopj/android/http/AsyncHttpRequest.java index 8832be0c2..101b58c44 100644 --- a/src/com/loopj/android/http/AsyncHttpRequest.java +++ b/src/com/loopj/android/http/AsyncHttpRequest.java @@ -19,7 +19,6 @@ package com.loopj.android.http; import java.io.IOException; -import java.net.ConnectException; import java.net.UnknownHostException; import org.apache.http.HttpResponse; @@ -33,7 +32,6 @@ class AsyncHttpRequest implements Runnable { private final HttpContext context; private final HttpUriRequest request; private final AsyncHttpResponseHandler responseHandler; - private boolean isBinaryRequest; private int executionCount; public AsyncHttpRequest(AbstractHttpClient client, HttpContext context, HttpUriRequest request, AsyncHttpResponseHandler responseHandler) { @@ -41,32 +39,23 @@ public AsyncHttpRequest(AbstractHttpClient client, HttpContext context, HttpUriR this.context = context; this.request = request; this.responseHandler = responseHandler; - if(responseHandler instanceof BinaryHttpResponseHandler) { - this.isBinaryRequest = true; - } } public void run() { - try { - if(responseHandler != null){ - responseHandler.sendStartMessage(); - } + if (responseHandler != null) { + responseHandler.sendStartMessage(); + } + try { makeRequestWithRetries(); - - if(responseHandler != null) { - responseHandler.sendFinishMessage(); - } } catch (IOException e) { - if(responseHandler != null) { - responseHandler.sendFinishMessage(); - if(this.isBinaryRequest) { - responseHandler.sendFailureMessage(e, (byte[]) null); - } else { - responseHandler.sendFailureMessage(e, (String) null); - } + if (responseHandler != null) { + responseHandler.sendFailureMessage(0, null, e); } } + if (responseHandler != null) { + responseHandler.sendFinishMessage(); + } } private void makeRequest() throws IOException { @@ -76,13 +65,11 @@ private void makeRequest() throws IOException { if(responseHandler != null) { responseHandler.sendResponseMessage(response); } - } else{ - //TODO: should raise InterruptedException? this block is reached whenever the request is cancelled before its response is received } } } - private void makeRequestWithRetries() throws ConnectException { + private void makeRequestWithRetries() throws IOException { // This is an additional layer of retry logic lifted from droid-fu // See: https://github.com/kaeppler/droid-fu/blob/master/src/main/java/com/github/droidfu/http/BetterHttpRequestBase.java boolean retry = true; @@ -92,11 +79,12 @@ private void makeRequestWithRetries() throws ConnectException { try { makeRequest(); return; - } catch (UnknownHostException e) { - if(responseHandler != null) { - responseHandler.sendFailureMessage(e, "can't resolve host"); - } - return; + } catch (UnknownHostException e) { + // switching between WI-FI and mobile data networks can cause a retry which then results in an UnknownHostException + // while the WI-FI is initialising. The retry logic will be invoked here, if this is NOT the first retry + // (to assist in genuine cases of unknown host) which seems better than outright failure + cause = new IOException("UnknownHostException exception: " + e.getMessage()); + retry = (executionCount > 0) && retryHandler.retryRequest(cause, ++executionCount, context); } catch (IOException e) { cause = e; retry = retryHandler.retryRequest(cause, ++executionCount, context); @@ -104,14 +92,19 @@ private void makeRequestWithRetries() throws ConnectException { // there's a bug in HttpClient 4.0.x that on some occasions causes // DefaultRequestExecutor to throw an NPE, see // http://code.google.com/p/android/issues/detail?id=5255 - cause = new IOException("NPE in HttpClient" + e.getMessage()); + cause = new IOException("NPE in HttpClient: " + e.getMessage()); retry = retryHandler.retryRequest(cause, ++executionCount, context); + } catch (Exception e) { + // catch anything else to ensure failure message is propagated + cause = new IOException("Unhandled exception: " + e.getMessage()); + retry = false; + } + if(retry && (responseHandler != null)) { + responseHandler.sendRetryMessage(); } } - - // no retries left, crap out with exception - ConnectException ex = new ConnectException(); - ex.initCause(cause); - throw ex; + + // cleaned up to throw IOException + throw(cause); } } diff --git a/src/com/loopj/android/http/AsyncHttpResponseHandler.java b/src/com/loopj/android/http/AsyncHttpResponseHandler.java index 6c6ba088c..92e5d8a2b 100644 --- a/src/com/loopj/android/http/AsyncHttpResponseHandler.java +++ b/src/com/loopj/android/http/AsyncHttpResponseHandler.java @@ -25,10 +25,11 @@ import org.apache.http.HttpResponse; import org.apache.http.StatusLine; import org.apache.http.client.HttpResponseException; -import org.apache.http.entity.BufferedHttpEntity; -import org.apache.http.util.EntityUtils; +import org.apache.http.util.ByteArrayBuffer; import java.io.IOException; +import java.io.InputStream; +import java.lang.ref.WeakReference; /** * Used to intercept and handle the responses from requests made using @@ -49,12 +50,12 @@ * } * * @Override - * public void onSuccess(String response) { + * public void onSuccess(int statusCode, byte[] responseBody ) { * // Successfully got a response * } * * @Override - * public void onFailure(Throwable e, String response) { + * public void onFailure(int statusCode, byte[] responseBody, Throwable e) { * // Response failed :( * } * @@ -70,20 +71,39 @@ public class AsyncHttpResponseHandler { protected static final int FAILURE_MESSAGE = 1; protected static final int START_MESSAGE = 2; protected static final int FINISH_MESSAGE = 3; + protected static final int PROGRESS_MESSAGE = 4; + protected static final int RETRY_MESSAGE = 5; + + protected static final int BUFFER_SIZE = 4096; private Handler handler; + // avoid leaks by using a non-anonymous handler class + // with a weak reference + static class ResponderHandler extends Handler { + private final WeakReference mResponder; + + ResponderHandler(AsyncHttpResponseHandler service) { + mResponder = new WeakReference(service); + } + @Override + public void handleMessage(Message msg) + { + AsyncHttpResponseHandler service = mResponder.get(); + if (service != null) { + service.handleMessage(msg); + } + } + } + /** * Creates a new AsyncHttpResponseHandler */ public AsyncHttpResponseHandler() { - // Set up a handler to post events back to the correct thread if possible - if(Looper.myLooper() != null) { - handler = new Handler(){ - public void handleMessage(Message msg){ - AsyncHttpResponseHandler.this.handleMessage(msg); - } - }; + // Set up a handler to post events back to the correct thread if + // possible + if (Looper.myLooper() != null) { + handler = new ResponderHandler(this); } } @@ -103,52 +123,56 @@ public void onStart() {} public void onFinish() {} /** - * Fired when a request returns successfully, override to handle in your own code - * @param content the body of the HTTP response from the server + * Fired when a request returns successfully, override to handle in your own + * code + * + * @param statusCode the status code of the response + * @param responseBody the body of the HTTP response from the server */ - public void onSuccess(String content) {} + public void onSuccess(int statusCode, byte[] responseBody) { + } + /** - * Fired when a request returns successfully, override to handle in your own code + * Fired when a request fails to complete, override to handle in your own + * code + * * @param statusCode the status code of the response - * @param content the body of the HTTP response from the server + * @param responseBody the response body, if any + * @param error the underlying cause of the failure */ - public void onSuccess(int statusCode, String content) { - onSuccess(content); + public void onFailure(int statusCode, byte[] responseBody, Throwable error) { } /** - * Fired when a request fails to complete, override to handle in your own code - * @param error the underlying cause of the failure - * @deprecated use {@link #onFailure(Throwable, String)} + * Fired when a bytes are received, override to handle in your own + * code + * + * @param current the current number of bytes loaded from the response + * @param total the total number of bytes in the response */ - public void onFailure(Throwable error) {} + public void onProgress(int current, int total) { + } /** - * Fired when a request fails to complete, override to handle in your own code - * @param error the underlying cause of the failure - * @param content the response body, if any + * Fired when a retry occurs, override to handle in your own + * code + * */ - public void onFailure(Throwable error, String content) { - // By default, call the deprecated onFailure(Throwable) for compatibility - onFailure(error); + public void onRetry() { } - + // // Pre-processing of messages (executes in background threadpool thread) // - protected void sendSuccessMessage(int statusCode, String responseBody) { - sendMessage(obtainMessage(SUCCESS_MESSAGE, new Object[]{new Integer(statusCode), responseBody})); + protected void sendSuccessMessage(int statusCode, byte[] responseBody) { + sendMessage(obtainMessage(SUCCESS_MESSAGE, new Object[] { Integer.valueOf(statusCode), responseBody })); } - protected void sendFailureMessage(Throwable e, String responseBody) { - sendMessage(obtainMessage(FAILURE_MESSAGE, new Object[]{e, responseBody})); - } - - protected void sendFailureMessage(Throwable e, byte[] responseBody) { - sendMessage(obtainMessage(FAILURE_MESSAGE, new Object[]{e, responseBody})); + protected void sendFailureMessage(int statusCode, byte[] responseBody, Throwable error) { + sendMessage(obtainMessage(FAILURE_MESSAGE, new Object[] { Integer.valueOf(statusCode), responseBody, error })); } protected void sendStartMessage() { @@ -159,48 +183,71 @@ protected void sendFinishMessage() { sendMessage(obtainMessage(FINISH_MESSAGE, null)); } + protected void sendProgressMessage(int current, int total) { + sendMessage(obtainMessage(PROGRESS_MESSAGE, new Object[] { current, total })); + } + protected void sendRetryMessage() { + sendMessage(obtainMessage(RETRY_MESSAGE, null)); + } + // // Pre-processing of messages (in original calling thread, typically the UI thread) // - protected void handleSuccessMessage(int statusCode, String responseBody) { + protected void handleSuccessMessage(int statusCode, byte[] responseBody) { onSuccess(statusCode, responseBody); } - protected void handleFailureMessage(Throwable e, String responseBody) { - onFailure(e, responseBody); + protected void handleFailureMessage(int statusCode, byte[] responseBody, Throwable error) { + onFailure(statusCode, responseBody, error); } + protected void handleProgressMessage(int current, int total) { + onProgress(current, total); + } - + protected void handleRetryMessage() { + onRetry(); + } + // Methods which emulate android's Handler and Message methods protected void handleMessage(Message msg) { Object[] response; - switch(msg.what) { - case SUCCESS_MESSAGE: - response = (Object[])msg.obj; - handleSuccessMessage(((Integer) response[0]).intValue(), (String) response[1]); - break; - case FAILURE_MESSAGE: - response = (Object[])msg.obj; - handleFailureMessage((Throwable)response[0], (String)response[1]); - break; - case START_MESSAGE: - onStart(); - break; - case FINISH_MESSAGE: - onFinish(); - break; + switch (msg.what) { + case SUCCESS_MESSAGE: + response = (Object[]) msg.obj; + handleSuccessMessage(((Integer) response[0]).intValue(), (byte[]) response[1]); + break; + case FAILURE_MESSAGE: + response = (Object[]) msg.obj; + handleFailureMessage(((Integer) response[0]).intValue(), (byte[]) response[1], (Throwable) response[2]); + break; + case START_MESSAGE: + onStart(); + break; + case FINISH_MESSAGE: + onFinish(); + break; + case PROGRESS_MESSAGE: + response = (Object[]) msg.obj; + onProgress(((Integer) response[0]).intValue(), ((Integer) response[1]).intValue()); + break; + case RETRY_MESSAGE: + onRetry(); + break; } } protected void sendMessage(Message msg) { - if(handler != null){ - handler.sendMessage(msg); - } else { - handleMessage(msg); + // do not send messages if request has been cancelled + if (!Thread.currentThread().isInterrupted()) { + if (handler != null) { + handler.sendMessage(msg); + } else { + handleMessage(msg); + } } } @@ -216,25 +263,48 @@ protected Message obtainMessage(int responseMessage, Object response) { return msg; } - // Interface to AsyncHttpRequest - void sendResponseMessage(HttpResponse response) { - StatusLine status = response.getStatusLine(); - String responseBody = null; - try { - HttpEntity entity = null; - HttpEntity temp = response.getEntity(); - if(temp != null) { - entity = new BufferedHttpEntity(temp); - responseBody = EntityUtils.toString(entity, "UTF-8"); + byte[] getResponseData(HttpEntity entity) throws IOException { + byte[] responseBody = null; + if (entity != null) { + InputStream instream = entity.getContent(); + if (instream != null) { + long contentLength = entity.getContentLength(); + if (contentLength > Integer.MAX_VALUE) { + throw new IllegalArgumentException("HTTP entity too large to be buffered in memory"); + } + if (contentLength < 0) { + contentLength = BUFFER_SIZE; + } + ByteArrayBuffer buffer = new ByteArrayBuffer((int) contentLength); + try { + byte[] tmp = new byte[BUFFER_SIZE]; + int l; + // do not send messages if request has been cancelled + while ((l = instream.read(tmp)) != -1 && !Thread.currentThread().isInterrupted()) { + buffer.append(tmp, 0, l); + sendProgressMessage(buffer.length(), (int) contentLength); + } + } finally { + instream.close(); + } + responseBody = buffer.toByteArray(); } - } catch(IOException e) { - sendFailureMessage(e, (String) null); } - - if(status.getStatusCode() >= 300) { - sendFailureMessage(new HttpResponseException(status.getStatusCode(), status.getReasonPhrase()), responseBody); - } else { - sendSuccessMessage(status.getStatusCode(), responseBody); + return (responseBody); + } + + // Interface to AsyncHttpRequest + void sendResponseMessage(HttpResponse response) throws IOException { + // do not process if request has been cancelled + if (!Thread.currentThread().isInterrupted()) { + StatusLine status = response.getStatusLine(); + byte[] responseBody = null; + responseBody = getResponseData(response.getEntity()); + if (status.getStatusCode() >= 300) { + sendFailureMessage(status.getStatusCode(), responseBody, new HttpResponseException(status.getStatusCode(), status.getReasonPhrase())); + } else { + sendSuccessMessage(status.getStatusCode(), responseBody); + } } } } \ No newline at end of file diff --git a/src/com/loopj/android/http/BinaryHttpResponseHandler.java b/src/com/loopj/android/http/BinaryHttpResponseHandler.java index aff1c631e..647dffc07 100644 --- a/src/com/loopj/android/http/BinaryHttpResponseHandler.java +++ b/src/com/loopj/android/http/BinaryHttpResponseHandler.java @@ -18,16 +18,12 @@ package com.loopj.android.http; -import android.os.Message; +import java.io.IOException; + import org.apache.http.Header; -import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.StatusLine; import org.apache.http.client.HttpResponseException; -import org.apache.http.entity.BufferedHttpEntity; -import org.apache.http.util.EntityUtils; - -import java.io.IOException; /** * Used to intercept and handle the responses from requests made using @@ -100,64 +96,21 @@ public void onSuccess(int statusCode, byte[] binaryData) { * Fired when a request fails to complete, override to handle in your own code * @param error the underlying cause of the failure * @param binaryData the response body, if any - * @deprecated */ - public void onFailure(Throwable error, byte[] binaryData) { + public void onFailure(int statusCode, byte[] binaryData, Throwable error) { // By default, call the deprecated onFailure(Throwable) for compatibility - onFailure(error); - } - - - // - // Pre-processing of messages (executes in background threadpool thread) - // - - protected void sendSuccessMessage(int statusCode, byte[] responseBody) { - sendMessage(obtainMessage(SUCCESS_MESSAGE, new Object[]{statusCode, responseBody})); - } - - protected void sendFailureMessage(Throwable e, byte[] responseBody) { - sendMessage(obtainMessage(FAILURE_MESSAGE, new Object[]{e, responseBody})); } - // - // Pre-processing of messages (in original calling thread, typically the UI thread) - // - - protected void handleSuccessMessage(int statusCode, byte[] responseBody) { - onSuccess(statusCode, responseBody); - } - - protected void handleFailureMessage(Throwable e, byte[] responseBody) { - onFailure(e, responseBody); - } - // Methods which emulate android's Handler and Message methods - protected void handleMessage(Message msg) { - Object[] response; - switch(msg.what) { - case SUCCESS_MESSAGE: - response = (Object[])msg.obj; - handleSuccessMessage(((Integer) response[0]).intValue() , (byte[]) response[1]); - break; - case FAILURE_MESSAGE: - response = (Object[])msg.obj; - handleFailureMessage((Throwable)response[0], (byte[])response[1]); - break; - default: - super.handleMessage(msg); - break; - } - } // Interface to AsyncHttpRequest - void sendResponseMessage(HttpResponse response) { + void sendResponseMessage(HttpResponse response) throws IOException { StatusLine status = response.getStatusLine(); Header[] contentTypeHeaders = response.getHeaders("Content-Type"); byte[] responseBody = null; if(contentTypeHeaders.length != 1) { //malformed/ambiguous HTTP Header, ABORT! - sendFailureMessage(new HttpResponseException(status.getStatusCode(), "None, or more than one, Content-Type Header found!"), responseBody); + sendFailureMessage(status.getStatusCode(), responseBody, new HttpResponseException(status.getStatusCode(), "None, or more than one, Content-Type Header found!")); return; } Header contentTypeHeader = contentTypeHeaders[0]; @@ -169,24 +122,9 @@ void sendResponseMessage(HttpResponse response) { } if(!foundAllowedContentType) { //Content-Type not in allowed list, ABORT! - sendFailureMessage(new HttpResponseException(status.getStatusCode(), "Content-Type not allowed!"), responseBody); + sendFailureMessage(status.getStatusCode(), responseBody, new HttpResponseException(status.getStatusCode(), "Content-Type not allowed!")); return; } - try { - HttpEntity entity = null; - HttpEntity temp = response.getEntity(); - if(temp != null) { - entity = new BufferedHttpEntity(temp); - } - responseBody = EntityUtils.toByteArray(entity); - } catch(IOException e) { - sendFailureMessage(e, (byte[]) null); - } - - if(status.getStatusCode() >= 300) { - sendFailureMessage(new HttpResponseException(status.getStatusCode(), status.getReasonPhrase()), responseBody); - } else { - sendSuccessMessage(status.getStatusCode(), responseBody); - } + super.sendResponseMessage( response ); } } \ No newline at end of file diff --git a/src/com/loopj/android/http/JsonHttpResponseHandler.java b/src/com/loopj/android/http/JsonHttpResponseHandler.java index 8d3973ac2..13c952c3a 100644 --- a/src/com/loopj/android/http/JsonHttpResponseHandler.java +++ b/src/com/loopj/android/http/JsonHttpResponseHandler.java @@ -18,6 +18,8 @@ package com.loopj.android.http; +import java.io.UnsupportedEncodingException; + import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; @@ -93,12 +95,12 @@ public void onFailure(Throwable e, JSONArray errorResponse) {} // @Override - protected void sendSuccessMessage(int statusCode, String responseBody) { + protected void sendSuccessMessage(int statusCode, byte[] responseBody) { try { Object jsonResponse = parseResponse(responseBody); - sendMessage(obtainMessage(SUCCESS_JSON_MESSAGE, new Object[]{statusCode, jsonResponse})); - } catch(JSONException e) { - sendFailureMessage(e, responseBody); + sendMessage(obtainMessage(SUCCESS_JSON_MESSAGE, new Object[] { statusCode, jsonResponse })); + } catch (JSONException e) { + sendFailureMessage(0, responseBody,e); } } @@ -129,36 +131,45 @@ protected void handleSuccessJsonMessage(int statusCode, Object jsonResponse) { } } - protected Object parseResponse(String responseBody) throws JSONException { + protected Object parseResponse(byte[] responseBody) throws JSONException { Object result = null; - //trim the string to prevent start with blank, and test if the string is valid JSON, because the parser don't do this :(. If Json is not valid this will return null - responseBody = responseBody.trim(); - if(responseBody.startsWith("{") || responseBody.startsWith("[")) { - result = new JSONTokener(responseBody).nextValue(); - } - if (result == null) { - result = responseBody; - } - return result; + String responseBodyText = null; + try { + responseBodyText = new String(responseBody, "UTF-8"); + } catch (UnsupportedEncodingException e) { + throw new JSONException("Unable to convert response to UTF-8 string"); + } + + // trim the string to prevent start with blank, and test if the string + // is valid JSON, because the parser don't do this :(. If Json is not + // valid this will return null + responseBodyText = responseBodyText.trim(); + if (responseBodyText.startsWith("{") || responseBodyText.startsWith("[")) { + result = new JSONTokener(responseBodyText).nextValue(); + } + if (result == null) { + result = responseBodyText; + } + return result; } @Override - protected void handleFailureMessage(Throwable e, String responseBody) { + protected void handleFailureMessage(int statusCode, byte[] responseBody, Throwable e) { try { if (responseBody != null) { Object jsonResponse = parseResponse(responseBody); - if(jsonResponse instanceof JSONObject) { - onFailure(e, (JSONObject)jsonResponse); - } else if(jsonResponse instanceof JSONArray) { - onFailure(e, (JSONArray)jsonResponse); + if (jsonResponse instanceof JSONObject) { + onFailure(e, (JSONObject) jsonResponse); + } else if (jsonResponse instanceof JSONArray) { + onFailure(e, (JSONArray) jsonResponse); } else { - onFailure(e, responseBody); + onFailure(0, responseBody,e); } - }else { - onFailure(e, ""); + } else { + onFailure(0, null, e); } - }catch(JSONException ex) { - onFailure(e, responseBody); + } catch (JSONException ex) { + onFailure(0, responseBody, e); } } } diff --git a/src/com/loopj/android/http/RetryHandler.java b/src/com/loopj/android/http/RetryHandler.java index 96d7c00a9..a8ba8fce7 100644 --- a/src/com/loopj/android/http/RetryHandler.java +++ b/src/com/loopj/android/http/RetryHandler.java @@ -40,7 +40,6 @@ import android.os.SystemClock; class RetryHandler implements HttpRequestRetryHandler { - private static final int RETRY_SLEEP_TIME_MILLIS = 1500; private static HashSet> exceptionWhitelist = new HashSet>(); private static HashSet> exceptionBlacklist = new HashSet>(); @@ -59,9 +58,11 @@ class RetryHandler implements HttpRequestRetryHandler { } private final int maxRetries; + private final int retrySleepTimeMS; - public RetryHandler(int maxRetries) { + public RetryHandler(int maxRetries, int retrySleepTimeMS) { this.maxRetries = maxRetries; + this.retrySleepTimeMS = retrySleepTimeMS; } public boolean retryRequest(IOException exception, int executionCount, HttpContext context) { @@ -91,8 +92,8 @@ public boolean retryRequest(IOException exception, int executionCount, HttpConte retry = !requestType.equals("POST"); } - if(retry) { - SystemClock.sleep(RETRY_SLEEP_TIME_MILLIS); + if (retry) { + SystemClock.sleep(retrySleepTimeMS); } else { exception.printStackTrace(); } diff --git a/src/com/loopj/android/http/TextHttpResponseHandler.java b/src/com/loopj/android/http/TextHttpResponseHandler.java new file mode 100644 index 000000000..f0acf050f --- /dev/null +++ b/src/com/loopj/android/http/TextHttpResponseHandler.java @@ -0,0 +1,125 @@ +package com.loopj.android.http; + +import java.io.UnsupportedEncodingException; + +/** + * Used to intercept and handle the responses from requests made using + * {@link AsyncHttpClient}. The {@link #onSuccess(String)} method is + * designed to be anonymously overridden with your own response handling code. + *

+ * Additionally, you can override the {@link #onFailure(String, Throwable)}, + * {@link #onStart()}, and {@link #onFinish()} methods as required. + *

+ * For example: + *

+ *

+ * AsyncHttpClient client = new AsyncHttpClient();
+ * client.get("http://www.google.com/", new AsyncHttpResponseHandler() {
+ *     @Override
+ *     public void onStart() {
+ *         // Initiated the request
+ *     }
+ *
+ *     @Override
+ *     public void onSuccess(String responseBody ) {
+ *         // Successfully got a response
+ *     }
+ * 
+ *     @Override
+ *     public void onFailure(String responseBody, Throwable e) {
+ *         // Response failed :(
+ *     }
+ *
+ *     @Override
+ *     public void onFinish() {
+ *         // Completed the request (either success or failure)
+ *     }
+ * });
+ * 
+ */ +public class TextHttpResponseHandler extends AsyncHttpResponseHandler { + + private String _encoding; + /** + * Creates a new TextHttpResponseHandler + */ + + public TextHttpResponseHandler() + { + this("UTF-8"); + } + + public TextHttpResponseHandler(String encoding) { + super(); + _encoding = encoding; + } + // + // Callbacks to be overridden, typically anonymously + // + + + /** + * Fired when a request returns successfully, override to handle in your own + * code + * + * @param responseBody the body of the HTTP response from the server + */ + public void onSuccess(String responseBody) { + } + + /** + * Fired when a request returns successfully, override to handle in your own + * code + * + * @param statusCode the status code of the response + * @param responseBody the body of the HTTP response from the server + */ + public void onSuccess(int statusCode, String responseBody) { + onSuccess( responseBody ); + } + + /** + * Fired when a request fails to complete, override to handle in your own + * code + * + * @param responseBody the response body, if any + * @param error the underlying cause of the failure + */ + public void onFailure(String responseBody, Throwable e) { + } + + /** + * Fired when a request fails to complete, override to handle in your own + * code + * + * @param statusCode the status code of the response + * @param responseBody the response body, if any + * @param error the underlying cause of the failure + */ + public void onFailure(int statusCode, String responseBody, Throwable e) { + onFailure( responseBody, e ); + } + + // + // Pre-processing of messages (in original calling thread, typically the UI thread) + // + + @Override + protected void handleSuccessMessage(int statusCode,byte[] responseBody) { + try { + onSuccess(statusCode, new String(responseBody, _encoding)); + } catch (UnsupportedEncodingException e) { + onFailure(0, (String) null, e); + } + } + + @Override + protected void handleFailureMessage(int statusCode, byte[] responseBody, Throwable error) { + try { + onFailure(statusCode, new String(responseBody, _encoding), error); + } catch (UnsupportedEncodingException e) { + onFailure(0, (String) null, e); + } + } + +} From 8022805a08e04997d01e153c38fdf375475ca42a Mon Sep 17 00:00:00 2001 From: sweetlilmre Date: Thu, 8 Nov 2012 21:30:26 +0200 Subject: [PATCH 003/613] Reworked Json handler to parse json on the download thread in the failure case and fixed the messaging to be consistant updated javadocs --- .../android/http/JsonHttpResponseHandler.java | 84 +++++++++++++------ 1 file changed, 59 insertions(+), 25 deletions(-) diff --git a/src/com/loopj/android/http/JsonHttpResponseHandler.java b/src/com/loopj/android/http/JsonHttpResponseHandler.java index 13c952c3a..e9232f58a 100644 --- a/src/com/loopj/android/http/JsonHttpResponseHandler.java +++ b/src/com/loopj/android/http/JsonHttpResponseHandler.java @@ -41,6 +41,7 @@ */ public class JsonHttpResponseHandler extends AsyncHttpResponseHandler { protected static final int SUCCESS_JSON_MESSAGE = 100; + protected static final int FAILURE_JSON_MESSAGE = 101; // // Callbacks to be overridden, typically anonymously @@ -86,8 +87,32 @@ public void onSuccess(int statusCode, JSONArray response) { onSuccess(response); } - public void onFailure(Throwable e, JSONObject errorResponse) {} - public void onFailure(Throwable e, JSONArray errorResponse) {} + + /** + * Fired when a request fails and contains a json object + * at the base of the response string. Override to handle in your + * own code. + *
+ * NOTE: remember to override the superclass {@link AsyncHttpResponseHandler#onFailure(int, byte[], Throwable) onFailure} + * method to cover all failure scenarios + * @param statusCode the status code of the response + * @param errorResponse the parsed json object found in the server response (if any) + * @param error the underlying cause of the failure + */ + public void onFailure(int statusCode, JSONObject errorResponse, Throwable error) {} + + /** + * Fired when a request fails and contains a json array + * at the base of the response string. Override to handle in your + * own code. + *
+ * NOTE: remember to override the superclass {@link AsyncHttpResponseHandler#onFailure(int, byte[], Throwable) onFailure} + * method to cover all failure scenarios + * @param statusCode the status code of the response + * @param errorResponse the parsed json array found in the server response (if any) + * @param error the underlying cause of the failure + */ + public void onFailure(int statusCode, JSONArray errorResponse, Throwable error) {} // @@ -98,12 +123,25 @@ public void onFailure(Throwable e, JSONArray errorResponse) {} protected void sendSuccessMessage(int statusCode, byte[] responseBody) { try { Object jsonResponse = parseResponse(responseBody); - sendMessage(obtainMessage(SUCCESS_JSON_MESSAGE, new Object[] { statusCode, jsonResponse })); + sendMessage(obtainMessage(SUCCESS_JSON_MESSAGE, new Object[] { Integer.valueOf(statusCode), jsonResponse })); } catch (JSONException e) { - sendFailureMessage(0, responseBody,e); + sendMessage(obtainMessage(FAILURE_MESSAGE, new Object[] {Integer.valueOf(0), responseBody, e})); } } + @Override + protected void sendFailureMessage(int statusCode, byte[] responseBody, Throwable error) { + try { + if (responseBody != null) { + Object jsonResponse = parseResponse(responseBody); + sendMessage(obtainMessage(FAILURE_JSON_MESSAGE, new Object[] {Integer.valueOf(statusCode), jsonResponse, error})); + } else { + sendMessage(obtainMessage(FAILURE_JSON_MESSAGE, new Object[] {Integer.valueOf(statusCode), (JSONObject)null, error})); + } + } catch (JSONException ex) { + sendMessage(obtainMessage(FAILURE_MESSAGE, new Object[] {Integer.valueOf(0), responseBody, error})); + } + } // // Pre-processing of messages (in original calling thread, typically the UI thread) @@ -111,11 +149,16 @@ protected void sendSuccessMessage(int statusCode, byte[] responseBody) { @Override protected void handleMessage(Message msg) { + Object[] response; switch(msg.what){ case SUCCESS_JSON_MESSAGE: - Object[] response = (Object[]) msg.obj; + response = (Object[]) msg.obj; handleSuccessJsonMessage(((Integer) response[0]).intValue(), response[1]); break; + case FAILURE_JSON_MESSAGE: + response = (Object[]) msg.obj; + handleFailureJsonMessage(((Integer) response[0]).intValue(), response[1], (Throwable) response[2]); + break; default: super.handleMessage(msg); } @@ -127,10 +170,20 @@ protected void handleSuccessJsonMessage(int statusCode, Object jsonResponse) { } else if(jsonResponse instanceof JSONArray) { onSuccess(statusCode, (JSONArray)jsonResponse); } else { - onFailure(new JSONException("Unexpected type " + jsonResponse.getClass().getName()), (JSONObject)null); + onFailure(statusCode, (JSONObject)null, new JSONException("Unexpected type " + jsonResponse.getClass().getName())); } } + protected void handleFailureJsonMessage(int statusCode, Object jsonResponse, Throwable error) { + if (jsonResponse instanceof JSONObject) { + onFailure(statusCode, (JSONObject) jsonResponse, error); + } else if (jsonResponse instanceof JSONArray) { + onFailure(statusCode, (JSONArray) jsonResponse, error); + } else { + onFailure(0, (JSONObject)null, error); + } + } + protected Object parseResponse(byte[] responseBody) throws JSONException { Object result = null; String responseBodyText = null; @@ -153,23 +206,4 @@ protected Object parseResponse(byte[] responseBody) throws JSONException { return result; } - @Override - protected void handleFailureMessage(int statusCode, byte[] responseBody, Throwable e) { - try { - if (responseBody != null) { - Object jsonResponse = parseResponse(responseBody); - if (jsonResponse instanceof JSONObject) { - onFailure(e, (JSONObject) jsonResponse); - } else if (jsonResponse instanceof JSONArray) { - onFailure(e, (JSONArray) jsonResponse); - } else { - onFailure(0, responseBody,e); - } - } else { - onFailure(0, null, e); - } - } catch (JSONException ex) { - onFailure(0, responseBody, e); - } - } } From 21e182a3098d1b5a8e6b1a45a8798c4429571539 Mon Sep 17 00:00:00 2001 From: Peter Edwards Date: Mon, 19 Nov 2012 09:37:24 +0200 Subject: [PATCH 004/613] Big download fixes: Removed unnecessary buffer copy (should allow for larger downloads and helps to combat VM heap constraints) Wrapped allocation in OutOfMemory handler, current behaviour will signal the GC and retry. This _sometimes_ works if GC has a change to run --- .../http/AsyncHttpResponseHandler.java | 27 +++++++++++-------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/src/com/loopj/android/http/AsyncHttpResponseHandler.java b/src/com/loopj/android/http/AsyncHttpResponseHandler.java index 92e5d8a2b..577248c78 100644 --- a/src/com/loopj/android/http/AsyncHttpResponseHandler.java +++ b/src/com/loopj/android/http/AsyncHttpResponseHandler.java @@ -275,19 +275,24 @@ byte[] getResponseData(HttpEntity entity) throws IOException { if (contentLength < 0) { contentLength = BUFFER_SIZE; } - ByteArrayBuffer buffer = new ByteArrayBuffer((int) contentLength); - try { - byte[] tmp = new byte[BUFFER_SIZE]; - int l; - // do not send messages if request has been cancelled - while ((l = instream.read(tmp)) != -1 && !Thread.currentThread().isInterrupted()) { - buffer.append(tmp, 0, l); - sendProgressMessage(buffer.length(), (int) contentLength); + try{ + ByteArrayBuffer buffer = new ByteArrayBuffer((int) contentLength); + try { + byte[] tmp = new byte[BUFFER_SIZE]; + int l; + // do not send messages if request has been cancelled + while ((l = instream.read(tmp)) != -1 && !Thread.currentThread().isInterrupted()) { + buffer.append(tmp, 0, l); + sendProgressMessage(buffer.length(), (int) contentLength); + } + } finally { + instream.close(); } - } finally { - instream.close(); + responseBody = buffer.buffer(); + } catch( OutOfMemoryError e ) { + System.gc(); + throw new IOException("File too large to fit into available memory"); } - responseBody = buffer.toByteArray(); } } return (responseBody); From bd4cd3a98f696990fbfba20c0d4db6929aac5815 Mon Sep 17 00:00:00 2001 From: David Brodsky Date: Tue, 4 Dec 2012 12:59:17 -0800 Subject: [PATCH 005/613] AsyncHttpRequest handle SocketException. Fix issue where responseHandler not notified if link to host offline --- .../loopj/android/http/AsyncHttpRequest.java | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/com/loopj/android/http/AsyncHttpRequest.java b/src/com/loopj/android/http/AsyncHttpRequest.java index 8832be0c2..83effe310 100644 --- a/src/com/loopj/android/http/AsyncHttpRequest.java +++ b/src/com/loopj/android/http/AsyncHttpRequest.java @@ -20,6 +20,7 @@ import java.io.IOException; import java.net.ConnectException; +import java.net.SocketException; import java.net.UnknownHostException; import org.apache.http.HttpResponse; @@ -92,11 +93,17 @@ private void makeRequestWithRetries() throws ConnectException { try { makeRequest(); return; - } catch (UnknownHostException e) { - if(responseHandler != null) { - responseHandler.sendFailureMessage(e, "can't resolve host"); - } - return; + } catch (UnknownHostException e) { + if(responseHandler != null) { + responseHandler.sendFailureMessage(e, "can't resolve host"); + } + return; + }catch (SocketException e){ + // Added to detect host unreachable + if(responseHandler != null) { + responseHandler.sendFailureMessage(e, "can't resolve host"); + } + return; } catch (IOException e) { cause = e; retry = retryHandler.retryRequest(cause, ++executionCount, context); From 15c0acf8de4240c2b51257cae37d7ea461d782b7 Mon Sep 17 00:00:00 2001 From: Cristiano Severini Date: Fri, 14 Dec 2012 09:01:46 +0100 Subject: [PATCH 006/613] fix getUrlWithQueryString fix in getUrlWithQueryString to append additional parameters in url with query string already present --- src/com/loopj/android/http/AsyncHttpClient.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/com/loopj/android/http/AsyncHttpClient.java b/src/com/loopj/android/http/AsyncHttpClient.java index 188fe7308..6053a5693 100644 --- a/src/com/loopj/android/http/AsyncHttpClient.java +++ b/src/com/loopj/android/http/AsyncHttpClient.java @@ -572,7 +572,11 @@ protected void sendRequest(DefaultHttpClient client, HttpContext httpContext, Ht public static String getUrlWithQueryString(String url, RequestParams params) { if(params != null) { String paramString = params.getParamString(); - url += "?" + paramString; + if (url.indexOf("?") == -1) { + url += "?" + paramString; + } else { + url += "&" + paramString; + } } return url; From c2cbe6b6474678a3ba194838707b4573ed71cb74 Mon Sep 17 00:00:00 2001 From: Peter Edwards Date: Fri, 14 Dec 2012 13:46:10 +0200 Subject: [PATCH 007/613] Added FileAsyncHttpResponseHandler based on: https://github.com/loopj/android-async-http/pull/134 Modified Request params to allow multiple params with the same name based on https://github.com/loopj/android-async-http/pull/136 Fixed unhandled exception AsyncHttpRequest --- .classpath | 1 - .../loopj/android/http/AsyncHttpRequest.java | 54 +- .../http/AsyncHttpResponseHandler.java | 7 +- .../http/FileAsyncHttpResponseHandler.java | 59 ++ src/com/loopj/android/http/RequestParams.java | 554 +++++++++--------- 5 files changed, 369 insertions(+), 306 deletions(-) create mode 100644 src/com/loopj/android/http/FileAsyncHttpResponseHandler.java diff --git a/.classpath b/.classpath index 6c6163d3b..69b7cb089 100644 --- a/.classpath +++ b/.classpath @@ -3,7 +3,6 @@ - diff --git a/src/com/loopj/android/http/AsyncHttpRequest.java b/src/com/loopj/android/http/AsyncHttpRequest.java index 101b58c44..8a86348a0 100644 --- a/src/com/loopj/android/http/AsyncHttpRequest.java +++ b/src/com/loopj/android/http/AsyncHttpRequest.java @@ -75,33 +75,35 @@ private void makeRequestWithRetries() throws IOException { boolean retry = true; IOException cause = null; HttpRequestRetryHandler retryHandler = client.getHttpRequestRetryHandler(); - while (retry) { - try { - makeRequest(); - return; - } catch (UnknownHostException e) { - // switching between WI-FI and mobile data networks can cause a retry which then results in an UnknownHostException - // while the WI-FI is initialising. The retry logic will be invoked here, if this is NOT the first retry - // (to assist in genuine cases of unknown host) which seems better than outright failure - cause = new IOException("UnknownHostException exception: " + e.getMessage()); - retry = (executionCount > 0) && retryHandler.retryRequest(cause, ++executionCount, context); - } catch (IOException e) { - cause = e; - retry = retryHandler.retryRequest(cause, ++executionCount, context); - } catch (NullPointerException e) { - // there's a bug in HttpClient 4.0.x that on some occasions causes - // DefaultRequestExecutor to throw an NPE, see - // http://code.google.com/p/android/issues/detail?id=5255 - cause = new IOException("NPE in HttpClient: " + e.getMessage()); - retry = retryHandler.retryRequest(cause, ++executionCount, context); - } catch (Exception e) { - // catch anything else to ensure failure message is propagated - cause = new IOException("Unhandled exception: " + e.getMessage()); - retry = false; - } - if(retry && (responseHandler != null)) { - responseHandler.sendRetryMessage(); + try + { + while (retry) { + try { + makeRequest(); + return; + } catch (UnknownHostException e) { + // switching between WI-FI and mobile data networks can cause a retry which then results in an UnknownHostException + // while the WI-FI is initialising. The retry logic will be invoked here, if this is NOT the first retry + // (to assist in genuine cases of unknown host) which seems better than outright failure + cause = new IOException("UnknownHostException exception: " + e.getMessage()); + retry = (executionCount > 0) && retryHandler.retryRequest(cause, ++executionCount, context); + } catch (IOException e) { + cause = e; + retry = retryHandler.retryRequest(cause, ++executionCount, context); + } catch (NullPointerException e) { + // there's a bug in HttpClient 4.0.x that on some occasions causes + // DefaultRequestExecutor to throw an NPE, see + // http://code.google.com/p/android/issues/detail?id=5255 + cause = new IOException("NPE in HttpClient: " + e.getMessage()); + retry = retryHandler.retryRequest(cause, ++executionCount, context); + } + if(retry && (responseHandler != null)) { + responseHandler.sendRetryMessage(); + } } + } catch (Exception e) { + // catch anything else to ensure failure message is propagated + cause = new IOException("Unhandled exception: " + e.getMessage()); } // cleaned up to throw IOException diff --git a/src/com/loopj/android/http/AsyncHttpResponseHandler.java b/src/com/loopj/android/http/AsyncHttpResponseHandler.java index 577248c78..d6a400e23 100644 --- a/src/com/loopj/android/http/AsyncHttpResponseHandler.java +++ b/src/com/loopj/android/http/AsyncHttpResponseHandler.java @@ -94,7 +94,7 @@ public void handleMessage(Message msg) service.handleMessage(msg); } } - } + } /** * Creates a new AsyncHttpResponseHandler @@ -279,11 +279,12 @@ byte[] getResponseData(HttpEntity entity) throws IOException { ByteArrayBuffer buffer = new ByteArrayBuffer((int) contentLength); try { byte[] tmp = new byte[BUFFER_SIZE]; - int l; + int l, count = 0; // do not send messages if request has been cancelled while ((l = instream.read(tmp)) != -1 && !Thread.currentThread().isInterrupted()) { + count += l; buffer.append(tmp, 0, l); - sendProgressMessage(buffer.length(), (int) contentLength); + sendProgressMessage(count, (int) contentLength); } } finally { instream.close(); diff --git a/src/com/loopj/android/http/FileAsyncHttpResponseHandler.java b/src/com/loopj/android/http/FileAsyncHttpResponseHandler.java new file mode 100644 index 000000000..c39a15f28 --- /dev/null +++ b/src/com/loopj/android/http/FileAsyncHttpResponseHandler.java @@ -0,0 +1,59 @@ +package com.loopj.android.http; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; + +import org.apache.http.HttpEntity; + +/** + * + * @author sweetlilmre + * + * Implements a response handler that will store the response in the provided + * File object. + * + * Events will be sent as per the AsyncHttpResponseHandler base class, however + * all byte[] values returned will be null. + */ +public class FileAsyncHttpResponseHandler extends AsyncHttpResponseHandler { + + private File mFile; + + public File getFile() { + return (mFile); + } + + public FileAsyncHttpResponseHandler(File file) { + super(); + this.mFile = file; + } + + @Override + byte[] getResponseData(HttpEntity entity) throws IOException { + if (entity != null) { + InputStream instream = entity.getContent(); + long contentLength = entity.getContentLength(); + FileOutputStream buffer = new FileOutputStream(this.mFile); + if (instream != null) { + try { + byte[] tmp = new byte[BUFFER_SIZE]; + int l, count = 0;; + // do not send messages if request has been cancelled + while ((l = instream.read(tmp)) != -1 && !Thread.currentThread().isInterrupted()) { + count += l; + buffer.write(tmp, 0, l); + sendProgressMessage(count, (int) contentLength); + } + } finally { + instream.close(); + buffer.flush(); + buffer.close(); + } + } + } + return (null); + } + +} diff --git a/src/com/loopj/android/http/RequestParams.java b/src/com/loopj/android/http/RequestParams.java index d8110e25f..2792a51e4 100644 --- a/src/com/loopj/android/http/RequestParams.java +++ b/src/com/loopj/android/http/RequestParams.java @@ -1,277 +1,279 @@ -/* - Android Asynchronous Http Client - Copyright (c) 2011 James Smith - http://loopj.com - - 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. -*/ - -package com.loopj.android.http; - -import java.io.InputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.UnsupportedEncodingException; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -import org.apache.http.HttpEntity; -import org.apache.http.client.entity.UrlEncodedFormEntity; -import org.apache.http.client.utils.URLEncodedUtils; -import org.apache.http.message.BasicNameValuePair; - -/** - * A collection of string request parameters or files to send along with - * requests made from an {@link AsyncHttpClient} instance. - *

- * For example: - *

- *

- * RequestParams params = new RequestParams();
- * params.put("username", "james");
- * params.put("password", "123456");
- * params.put("email", "my@email.com");
- * params.put("profile_picture", new File("pic.jpg")); // Upload a File
- * params.put("profile_picture2", someInputStream); // Upload an InputStream
- * params.put("profile_picture3", new ByteArrayInputStream(someBytes)); // Upload some bytes
- *
- * AsyncHttpClient client = new AsyncHttpClient();
- * client.post("http://myendpoint.com/", params, responseHandler);
- * 
- */ -public class RequestParams { - private static String ENCODING = "UTF-8"; - - protected ConcurrentHashMap urlParams; - protected ConcurrentHashMap fileParams; - - /** - * Constructs a new empty RequestParams instance. - */ - public RequestParams() { - init(); - } - - /** - * Constructs a new RequestParams instance containing the key/value - * string params from the specified map. - * @param source the source key/value string map to add. - */ - public RequestParams(Map source) { - init(); - - for(Map.Entry entry : source.entrySet()) { - put(entry.getKey(), entry.getValue()); - } - } - - /** - * Constructs a new RequestParams instance and populate it with a single - * initial key/value string param. - * @param key the key name for the intial param. - * @param value the value string for the initial param. - */ - public RequestParams(String key, String value) { - init(); - - put(key, value); - } - - /** - * Constructs a new RequestParams instance and populate it with multiple - * initial key/value string param. - * @param keysAndValues a sequence of keys and values. Objects are - * automatically converted to Strings (including the value {@code null}). - * @throws IllegalArgumentException if the number of arguments isn't even. - */ - public RequestParams(Object... keysAndValues) { - init(); - int len = keysAndValues.length; - if (len % 2 != 0) - throw new IllegalArgumentException("Supplied arguments must be even"); - for (int i = 0; i < len; i += 2) { - String key = String.valueOf(keysAndValues[i]); - String val = String.valueOf(keysAndValues[i + 1]); - put(key, val); - } - } - - /** - * Adds a key/value string pair to the request. - * @param key the key name for the new param. - * @param value the value string for the new param. - */ - public void put(String key, String value){ - if(key != null && value != null) { - urlParams.put(key, value); - } - } - - /** - * Adds a file to the request. - * @param key the key name for the new param. - * @param file the file to add. - */ - public void put(String key, File file) throws FileNotFoundException { - put(key, new FileInputStream(file), file.getName()); - } - - /** - * Adds an input stream to the request. - * @param key the key name for the new param. - * @param stream the input stream to add. - */ - public void put(String key, InputStream stream) { - put(key, stream, null); - } - - /** - * Adds an input stream to the request. - * @param key the key name for the new param. - * @param stream the input stream to add. - * @param fileName the name of the file. - */ - public void put(String key, InputStream stream, String fileName) { - put(key, stream, fileName, null); - } - - /** - * Adds an input stream to the request. - * @param key the key name for the new param. - * @param stream the input stream to add. - * @param fileName the name of the file. - * @param contentType the content type of the file, eg. application/json - */ - public void put(String key, InputStream stream, String fileName, String contentType) { - if(key != null && stream != null) { - fileParams.put(key, new FileWrapper(stream, fileName, contentType)); - } - } - - /** - * Removes a parameter from the request. - * @param key the key name for the parameter to remove. - */ - public void remove(String key){ - urlParams.remove(key); - fileParams.remove(key); - } - - @Override - public String toString() { - StringBuilder result = new StringBuilder(); - for(ConcurrentHashMap.Entry entry : urlParams.entrySet()) { - if(result.length() > 0) - result.append("&"); - - result.append(entry.getKey()); - result.append("="); - result.append(entry.getValue()); - } - - for(ConcurrentHashMap.Entry entry : fileParams.entrySet()) { - if(result.length() > 0) - result.append("&"); - - result.append(entry.getKey()); - result.append("="); - result.append("FILE"); - } - - return result.toString(); - } - - /** - * Returns an HttpEntity containing all request parameters - */ - public HttpEntity getEntity() { - HttpEntity entity = null; - - if(!fileParams.isEmpty()) { - SimpleMultipartEntity multipartEntity = new SimpleMultipartEntity(); - - // Add string params - for(ConcurrentHashMap.Entry entry : urlParams.entrySet()) { - multipartEntity.addPart(entry.getKey(), entry.getValue()); - } - - // Add file params - int currentIndex = 0; - int lastIndex = fileParams.entrySet().size() - 1; - for(ConcurrentHashMap.Entry entry : fileParams.entrySet()) { - FileWrapper file = entry.getValue(); - if(file.inputStream != null) { - boolean isLast = currentIndex == lastIndex; - if(file.contentType != null) { - multipartEntity.addPart(entry.getKey(), file.getFileName(), file.inputStream, file.contentType, isLast); - } else { - multipartEntity.addPart(entry.getKey(), file.getFileName(), file.inputStream, isLast); - } - } - currentIndex++; - } - - entity = multipartEntity; - } else { - try { - entity = new UrlEncodedFormEntity(getParamsList(), ENCODING); - } catch (UnsupportedEncodingException e) { - e.printStackTrace(); - } - } - - return entity; - } - - private void init(){ - urlParams = new ConcurrentHashMap(); - fileParams = new ConcurrentHashMap(); - } - - protected List getParamsList() { - List lparams = new LinkedList(); - - for(ConcurrentHashMap.Entry entry : urlParams.entrySet()) { - lparams.add(new BasicNameValuePair(entry.getKey(), entry.getValue())); - } - - return lparams; - } - - protected String getParamString() { - return URLEncodedUtils.format(getParamsList(), ENCODING); - } - - private static class FileWrapper { - public InputStream inputStream; - public String fileName; - public String contentType; - - public FileWrapper(InputStream inputStream, String fileName, String contentType) { - this.inputStream = inputStream; - this.fileName = fileName; - this.contentType = contentType; - } - - public String getFileName() { - if(fileName != null) { - return fileName; - } else { - return "nofilename"; - } - } - } +/* + Android Asynchronous Http Client + Copyright (c) 2011 James Smith + http://loopj.com + + 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. +*/ + +package com.loopj.android.http; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.apache.http.HttpEntity; +import org.apache.http.client.entity.UrlEncodedFormEntity; +import org.apache.http.client.utils.URLEncodedUtils; +import org.apache.http.message.BasicNameValuePair; + +/** + * A collection of string request parameters or files to send along with + * requests made from an {@link AsyncHttpClient} instance. + *

+ * For example: + *

+ *

+ * RequestParams params = new RequestParams();
+ * params.add("username", "james");
+ * params.add("password", "123456");
+ * params.add("email", "my@email.com");
+ * params.put("profile_picture", new File("pic.jpg")); // Upload a File
+ * params.put("profile_picture2", someInputStream); // Upload an InputStream
+ * params.put("profile_picture3", new ByteArrayInputStream(someBytes)); // Upload some bytes
+ *
+ * AsyncHttpClient client = new AsyncHttpClient();
+ * client.post("http://myendpoint.com/", params, responseHandler);
+ * 
+ */ +public class RequestParams { + private static String ENCODING = "UTF-8"; + + protected List urlParams; + protected ConcurrentHashMap fileParams; + + /** + * Constructs a new empty RequestParams instance. + */ + public RequestParams() { + init(); + } + + /** + * Constructs a new RequestParams instance containing the key/value + * string params from the specified map. + * @param source the source key/value string map to add. + */ + public RequestParams(Map source) { + init(); + + for(Map.Entry entry : source.entrySet()) { + add(entry.getKey(), entry.getValue()); + } + } + + /** + * Constructs a new RequestParams instance and populate it with a single + * initial key/value string param. + * @param key the key name for the intial param. + * @param value the value string for the initial param. + */ + public RequestParams(String key, String value) { + init(); + + add(key, value); + } + + /** + * Constructs a new RequestParams instance and populate it with multiple + * initial key/value string param. + * @param keysAndValues a sequence of keys and values. Objects are + * automatically converted to Strings (including the value {@code null}). + * @throws IllegalArgumentException if the number of arguments isn't even. + */ + public RequestParams(Object... keysAndValues) { + init(); + int len = keysAndValues.length; + if (len % 2 != 0) + throw new IllegalArgumentException("Supplied arguments must be even"); + for (int i = 0; i < len; i += 2) { + String key = String.valueOf(keysAndValues[i]); + String val = String.valueOf(keysAndValues[i + 1]); + add(key, val); + } + } + + /** + * Adds a key/value string pair to the request. + * @param key the key name for the new param. + * @param value the value string for the new param. + */ + public void add(String key, String value){ + if(key != null && value != null) { + urlParams.add(new BasicNameValuePair(key, value)); + } + } + + /** + * Adds a file to the request. + * @param key the key name for the new param. + * @param file the file to add. + */ + public void put(String key, File file) throws FileNotFoundException { + put(key, new FileInputStream(file), file.getName()); + } + + /** + * Adds an input stream to the request. + * @param key the key name for the new param. + * @param stream the input stream to add. + */ + public void put(String key, InputStream stream) { + put(key, stream, null); + } + + /** + * Adds an input stream to the request. + * @param key the key name for the new param. + * @param stream the input stream to add. + * @param fileName the name of the file. + */ + public void put(String key, InputStream stream, String fileName) { + put(key, stream, fileName, null); + } + + /** + * Adds an input stream to the request. + * @param key the key name for the new param. + * @param stream the input stream to add. + * @param fileName the name of the file. + * @param contentType the content type of the file, eg. application/json + */ + public void put(String key, InputStream stream, String fileName, String contentType) { + if(key != null && stream != null) { + fileParams.put(key, new FileWrapper(stream, fileName, contentType)); + } + } + + /** + * Removes a parameter from the request. + * @param key the key name for the parameter to remove. + */ + public void remove(String key){ + urlParams.remove(key); + fileParams.remove(key); + } + + @Override + public String toString() { + StringBuilder result = new StringBuilder(); + for(BasicNameValuePair pair : urlParams) { + if(result.length() > 0) + result.append("&"); + + result.append(pair.getName()); + result.append("="); + result.append(pair.getValue()); + } + + for(ConcurrentHashMap.Entry entry : fileParams.entrySet()) { + if(result.length() > 0) + result.append("&"); + + result.append(entry.getKey()); + result.append("="); + result.append("FILE"); + } + + return result.toString(); + } + + /** + * Returns an HttpEntity containing all request parameters + */ + public HttpEntity getEntity() { + HttpEntity entity = null; + + if(!fileParams.isEmpty()) { + SimpleMultipartEntity multipartEntity = new SimpleMultipartEntity(); + + // Add string params + for(BasicNameValuePair pair : urlParams) { + multipartEntity.addPart(pair.getName(), pair.getValue()); + } + + // Add file params + int currentIndex = 0; + int lastIndex = fileParams.entrySet().size() - 1; + for(ConcurrentHashMap.Entry entry : fileParams.entrySet()) { + FileWrapper file = entry.getValue(); + if(file.inputStream != null) { + boolean isLast = currentIndex == lastIndex; + if(file.contentType != null) { + multipartEntity.addPart(entry.getKey(), file.getFileName(), file.inputStream, file.contentType, isLast); + } else { + multipartEntity.addPart(entry.getKey(), file.getFileName(), file.inputStream, isLast); + } + } + currentIndex++; + } + + entity = multipartEntity; + } else { + try { + entity = new UrlEncodedFormEntity(getParamsList(), ENCODING); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } + } + + return entity; + } + + private void init(){ + urlParams = Collections.synchronizedList(new ArrayList()); + fileParams = new ConcurrentHashMap(); + } + + protected List getParamsList() { + List lparams = new LinkedList(); + + for(BasicNameValuePair pair : urlParams) { + lparams.add(pair); + } + + return lparams; + } + + protected String getParamString() { + return URLEncodedUtils.format(getParamsList(), ENCODING); + } + + private static class FileWrapper { + public InputStream inputStream; + public String fileName; + public String contentType; + + public FileWrapper(InputStream inputStream, String fileName, String contentType) { + this.inputStream = inputStream; + this.fileName = fileName; + this.contentType = contentType; + } + + public String getFileName() { + if(fileName != null) { + return fileName; + } else { + return "nofilename"; + } + } + } } \ No newline at end of file From 337cbed5a9caae2eb6cdfb71b239ea5359ecfb3f Mon Sep 17 00:00:00 2001 From: Surik Sayadyan Date: Sun, 16 Dec 2012 13:15:39 +0200 Subject: [PATCH 008/613] Fix issue #152: catch SocketTimeoutException --- src/com/loopj/android/http/AsyncHttpRequest.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/com/loopj/android/http/AsyncHttpRequest.java b/src/com/loopj/android/http/AsyncHttpRequest.java index 83effe310..0bfbccf76 100644 --- a/src/com/loopj/android/http/AsyncHttpRequest.java +++ b/src/com/loopj/android/http/AsyncHttpRequest.java @@ -21,6 +21,7 @@ import java.io.IOException; import java.net.ConnectException; import java.net.SocketException; +import java.net.SocketTimeoutException; import java.net.UnknownHostException; import org.apache.http.HttpResponse; @@ -104,6 +105,11 @@ private void makeRequestWithRetries() throws ConnectException { responseHandler.sendFailureMessage(e, "can't resolve host"); } return; + }catch (SocketTimeoutException e){ + if(responseHandler != null) { + responseHandler.sendFailureMessage(e, "socket time out"); + } + return; } catch (IOException e) { cause = e; retry = retryHandler.retryRequest(cause, ++executionCount, context); From d8f606cb1fdd4a97073ab37700e4858daef94e8b Mon Sep 17 00:00:00 2001 From: rusty Date: Thu, 3 Jan 2013 14:29:23 +0000 Subject: [PATCH 009/613] Added headers array to onSuccess --- .../android/http/AsyncHttpResponseHandler.java | 15 ++++++++------- .../android/http/JsonHttpResponseHandler.java | 18 +++++++++--------- 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/src/com/loopj/android/http/AsyncHttpResponseHandler.java b/src/com/loopj/android/http/AsyncHttpResponseHandler.java index 6c6ba088c..2107acc60 100644 --- a/src/com/loopj/android/http/AsyncHttpResponseHandler.java +++ b/src/com/loopj/android/http/AsyncHttpResponseHandler.java @@ -21,6 +21,7 @@ import android.os.Handler; import android.os.Looper; import android.os.Message; +import org.apache.http.Header; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.StatusLine; @@ -113,7 +114,7 @@ public void onSuccess(String content) {} * @param statusCode the status code of the response * @param content the body of the HTTP response from the server */ - public void onSuccess(int statusCode, String content) { + public void onSuccess(int statusCode,Header[] headers,String content) { onSuccess(content); } @@ -139,8 +140,8 @@ public void onFailure(Throwable error, String content) { // Pre-processing of messages (executes in background threadpool thread) // - protected void sendSuccessMessage(int statusCode, String responseBody) { - sendMessage(obtainMessage(SUCCESS_MESSAGE, new Object[]{new Integer(statusCode), responseBody})); + protected void sendSuccessMessage(int statusCode, Header[] headers, String responseBody) { + sendMessage(obtainMessage(SUCCESS_MESSAGE, new Object[]{new Integer(statusCode),headers, responseBody})); } protected void sendFailureMessage(Throwable e, String responseBody) { @@ -164,8 +165,8 @@ protected void sendFinishMessage() { // Pre-processing of messages (in original calling thread, typically the UI thread) // - protected void handleSuccessMessage(int statusCode, String responseBody) { - onSuccess(statusCode, responseBody); + protected void handleSuccessMessage(int statusCode, Header[] headers, String responseBody) { + onSuccess(statusCode, headers, responseBody); } protected void handleFailureMessage(Throwable e, String responseBody) { @@ -181,7 +182,7 @@ protected void handleMessage(Message msg) { switch(msg.what) { case SUCCESS_MESSAGE: response = (Object[])msg.obj; - handleSuccessMessage(((Integer) response[0]).intValue(), (String) response[1]); + handleSuccessMessage(((Integer) response[0]).intValue(),(Header[]) response[1], (String) response[2]); break; case FAILURE_MESSAGE: response = (Object[])msg.obj; @@ -234,7 +235,7 @@ void sendResponseMessage(HttpResponse response) { if(status.getStatusCode() >= 300) { sendFailureMessage(new HttpResponseException(status.getStatusCode(), status.getReasonPhrase()), responseBody); } else { - sendSuccessMessage(status.getStatusCode(), responseBody); + sendSuccessMessage(status.getStatusCode(),response.getAllHeaders(), responseBody); } } } \ No newline at end of file diff --git a/src/com/loopj/android/http/JsonHttpResponseHandler.java b/src/com/loopj/android/http/JsonHttpResponseHandler.java index 8d3973ac2..5cc6a0ea1 100644 --- a/src/com/loopj/android/http/JsonHttpResponseHandler.java +++ b/src/com/loopj/android/http/JsonHttpResponseHandler.java @@ -22,7 +22,7 @@ import org.json.JSONException; import org.json.JSONObject; import org.json.JSONTokener; - +import org.apache.http.Header; import android.os.Message; /** @@ -68,7 +68,7 @@ public void onSuccess(JSONArray response) {} * @param statusCode the status code of the response * @param response the parsed json object found in the server response (if any) */ - public void onSuccess(int statusCode, JSONObject response) { + public void onSuccess(int statusCode, Header[] headers, JSONObject response) { onSuccess(response); } @@ -80,7 +80,7 @@ public void onSuccess(int statusCode, JSONObject response) { * @param statusCode the status code of the response * @param response the parsed json array found in the server response (if any) */ - public void onSuccess(int statusCode, JSONArray response) { + public void onSuccess(int statusCode, Header[] headers, JSONArray response) { onSuccess(response); } @@ -93,10 +93,10 @@ public void onFailure(Throwable e, JSONArray errorResponse) {} // @Override - protected void sendSuccessMessage(int statusCode, String responseBody) { + protected void sendSuccessMessage(int statusCode, Header[] headers, String responseBody) { try { Object jsonResponse = parseResponse(responseBody); - sendMessage(obtainMessage(SUCCESS_JSON_MESSAGE, new Object[]{statusCode, jsonResponse})); + sendMessage(obtainMessage(SUCCESS_JSON_MESSAGE, new Object[]{statusCode, headers, jsonResponse})); } catch(JSONException e) { sendFailureMessage(e, responseBody); } @@ -112,18 +112,18 @@ protected void handleMessage(Message msg) { switch(msg.what){ case SUCCESS_JSON_MESSAGE: Object[] response = (Object[]) msg.obj; - handleSuccessJsonMessage(((Integer) response[0]).intValue(), response[1]); + handleSuccessJsonMessage(((Integer) response[0]).intValue(),(Header[]) response[1] ,response[2]); break; default: super.handleMessage(msg); } } - protected void handleSuccessJsonMessage(int statusCode, Object jsonResponse) { + protected void handleSuccessJsonMessage(int statusCode,Header[] headers, Object jsonResponse) { if(jsonResponse instanceof JSONObject) { - onSuccess(statusCode, (JSONObject)jsonResponse); + onSuccess(statusCode, headers, (JSONObject)jsonResponse); } else if(jsonResponse instanceof JSONArray) { - onSuccess(statusCode, (JSONArray)jsonResponse); + onSuccess(statusCode, headers, (JSONArray)jsonResponse); } else { onFailure(new JSONException("Unexpected type " + jsonResponse.getClass().getName()), (JSONObject)null); } From 197aadebb650055891286891f79732d660050997 Mon Sep 17 00:00:00 2001 From: rusty Date: Thu, 3 Jan 2013 14:33:35 +0000 Subject: [PATCH 010/613] Ignore intellij settings --- .gitignore | 4 +++- project.properties | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 0900c481f..e8b2da591 100644 --- a/.gitignore +++ b/.gitignore @@ -8,4 +8,6 @@ bin/ gen/ _layouts .DS_Store -gh-pages \ No newline at end of file +gh-pages + +.idea/ diff --git a/project.properties b/project.properties index 1880987e2..1e3635f1f 100644 --- a/project.properties +++ b/project.properties @@ -7,6 +7,6 @@ # "ant.properties", and override values to adapt the script to your # project structure. -android.library=true +android.library=false # Project target. -target=android-3 +target=android-10 From 78eee0d369f76ca9480c258292c1a959a4da648c Mon Sep 17 00:00:00 2001 From: rusty Date: Fri, 4 Jan 2013 09:35:20 +0000 Subject: [PATCH 011/613] Enhanced backwards compatability for onSuccess --- .../http/AsyncHttpResponseHandler.java | 17 ++++++++++--- .../android/http/JsonHttpResponseHandler.java | 25 ++++++++++++++++++- 2 files changed, 38 insertions(+), 4 deletions(-) diff --git a/src/com/loopj/android/http/AsyncHttpResponseHandler.java b/src/com/loopj/android/http/AsyncHttpResponseHandler.java index 2107acc60..1d55083b4 100644 --- a/src/com/loopj/android/http/AsyncHttpResponseHandler.java +++ b/src/com/loopj/android/http/AsyncHttpResponseHandler.java @@ -112,9 +112,20 @@ public void onSuccess(String content) {} /** * Fired when a request returns successfully, override to handle in your own code * @param statusCode the status code of the response + * @param headers the headers of the HTTP response * @param content the body of the HTTP response from the server */ - public void onSuccess(int statusCode,Header[] headers,String content) { + public void onSuccess(int statusCode, Header[] headers, String content) { + onSuccess(statusCode, content); + } + + /** + * Fired when a request returns successfully, override to handle in your own code + * @param statusCode the status code of the response + * @param content the body of the HTTP response from the server + */ + public void onSuccess(int statusCode, String content) + { onSuccess(content); } @@ -141,7 +152,7 @@ public void onFailure(Throwable error, String content) { // protected void sendSuccessMessage(int statusCode, Header[] headers, String responseBody) { - sendMessage(obtainMessage(SUCCESS_MESSAGE, new Object[]{new Integer(statusCode),headers, responseBody})); + sendMessage(obtainMessage(SUCCESS_MESSAGE, new Object[]{new Integer(statusCode), headers, responseBody})); } protected void sendFailureMessage(Throwable e, String responseBody) { @@ -182,7 +193,7 @@ protected void handleMessage(Message msg) { switch(msg.what) { case SUCCESS_MESSAGE: response = (Object[])msg.obj; - handleSuccessMessage(((Integer) response[0]).intValue(),(Header[]) response[1], (String) response[2]); + handleSuccessMessage(((Integer) response[0]).intValue(), (Header[]) response[1], (String) response[2]); break; case FAILURE_MESSAGE: response = (Object[])msg.obj; diff --git a/src/com/loopj/android/http/JsonHttpResponseHandler.java b/src/com/loopj/android/http/JsonHttpResponseHandler.java index 5cc6a0ea1..7f4379a6d 100644 --- a/src/com/loopj/android/http/JsonHttpResponseHandler.java +++ b/src/com/loopj/android/http/JsonHttpResponseHandler.java @@ -66,21 +66,44 @@ public void onSuccess(JSONArray response) {} * at the base of the response string. Override to handle in your * own code. * @param statusCode the status code of the response + * @param headers the headers of the HTTP response * @param response the parsed json object found in the server response (if any) */ public void onSuccess(int statusCode, Header[] headers, JSONObject response) { - onSuccess(response); + onSuccess(statusCode, response); } + /** + * Fired when a request returns successfully and contains a json object + * at the base of the response string. Override to handle in your + * own code. + * @param statusCode the status code of the response + * @param response the parsed json object found in the server response (if any) + */ + public void onSuccess(int statusCode, JSONObject response) { + onSuccess(response); + } /** * Fired when a request returns successfully and contains a json array * at the base of the response string. Override to handle in your * own code. * @param statusCode the status code of the response + * @param headers the headers of the HTTP response * @param response the parsed json array found in the server response (if any) */ public void onSuccess(int statusCode, Header[] headers, JSONArray response) { + onSuccess(statusCode, response); + } + + /** + * Fired when a request returns successfully and contains a json array + * at the base of the response string. Override to handle in your + * own code. + * @param statusCode the status code of the response + * @param response the parsed json array found in the server response (if any) + */ + public void onSuccess(int statusCode, JSONArray response) { onSuccess(response); } From bce069da009b30e44174d1b73d00c516794e605e Mon Sep 17 00:00:00 2001 From: Tom Pesman Date: Fri, 18 Jan 2013 15:44:46 +0100 Subject: [PATCH 012/613] if httpstatus is 204 there is no content --- .../android/http/JsonHttpResponseHandler.java | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/com/loopj/android/http/JsonHttpResponseHandler.java b/src/com/loopj/android/http/JsonHttpResponseHandler.java index 8d3973ac2..084bf3392 100644 --- a/src/com/loopj/android/http/JsonHttpResponseHandler.java +++ b/src/com/loopj/android/http/JsonHttpResponseHandler.java @@ -18,6 +18,7 @@ package com.loopj.android.http; +import org.apache.http.HttpStatus; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; @@ -94,12 +95,16 @@ public void onFailure(Throwable e, JSONArray errorResponse) {} @Override protected void sendSuccessMessage(int statusCode, String responseBody) { - try { - Object jsonResponse = parseResponse(responseBody); - sendMessage(obtainMessage(SUCCESS_JSON_MESSAGE, new Object[]{statusCode, jsonResponse})); - } catch(JSONException e) { - sendFailureMessage(e, responseBody); - } + if (statusCode != HttpStatus.SC_NO_CONTENT){ + try { + Object jsonResponse = parseResponse(responseBody); + sendMessage(obtainMessage(SUCCESS_JSON_MESSAGE, new Object[]{statusCode, jsonResponse})); + } catch(JSONException e) { + sendFailureMessage(e, responseBody); + } + }else{ + sendMessage(obtainMessage(SUCCESS_JSON_MESSAGE, new Object[]{statusCode, new JSONObject()})); + } } From 33809bf10d860eee6fa67ef00958da0eae1aef01 Mon Sep 17 00:00:00 2001 From: Mindaugas Kuprionis Date: Sun, 20 Jan 2013 17:13:34 +0200 Subject: [PATCH 013/613] Fix SyncHttpClient to work with @sweetlilmre refactored code --- src/com/loopj/android/http/SyncHttpClient.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/com/loopj/android/http/SyncHttpClient.java b/src/com/loopj/android/http/SyncHttpClient.java index 8f55991ef..d5c203856 100644 --- a/src/com/loopj/android/http/SyncHttpClient.java +++ b/src/com/loopj/android/http/SyncHttpClient.java @@ -1,5 +1,7 @@ package com.loopj.android.http; +import java.io.IOException; + import org.apache.http.client.methods.HttpUriRequest; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.protocol.HttpContext; @@ -15,9 +17,9 @@ public abstract class SyncHttpClient extends AsyncHttpClient { * field to be accessible */ private String result; - AsyncHttpResponseHandler responseHandler = new AsyncHttpResponseHandler() { + AsyncHttpResponseHandler responseHandler = new TextHttpResponseHandler() { - void sendResponseMessage(org.apache.http.HttpResponse response) { + void sendResponseMessage(org.apache.http.HttpResponse response) throws IOException { responseCode = response.getStatusLine().getStatusCode(); super.sendResponseMessage(response); }; @@ -37,7 +39,7 @@ public void onSuccess(String content) { } @Override - public void onFailure(Throwable error, String content) { + public void onFailure(String content, Throwable error) { result = onRequestFailed(error, content); } }; From efa18d88cb747a36000e614f2737dddb4add646b Mon Sep 17 00:00:00 2001 From: rustybox Date: Sun, 27 Jan 2013 00:57:25 +0000 Subject: [PATCH 014/613] Update project.properties reverted to how it was in master --- project.properties | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/project.properties b/project.properties index 1e3635f1f..1880987e2 100644 --- a/project.properties +++ b/project.properties @@ -7,6 +7,6 @@ # "ant.properties", and override values to adapt the script to your # project structure. -android.library=false +android.library=true # Project target. -target=android-10 +target=android-3 From 3e12dea27ca068079ede1e8e6da392166873ce38 Mon Sep 17 00:00:00 2001 From: rustybox Date: Sun, 27 Jan 2013 00:58:18 +0000 Subject: [PATCH 015/613] Update .gitignore reverted to how it was in master --- .gitignore | 2 -- 1 file changed, 2 deletions(-) diff --git a/.gitignore b/.gitignore index e8b2da591..87932a4b4 100644 --- a/.gitignore +++ b/.gitignore @@ -9,5 +9,3 @@ gen/ _layouts .DS_Store gh-pages - -.idea/ From b174344a66665bd5c7e6ad3eff11cd2c9d13a1ec Mon Sep 17 00:00:00 2001 From: Davey Fong Date: Sun, 27 Jan 2013 19:58:38 +0800 Subject: [PATCH 016/613] Fixed: Exception black/white list in RetryHandler not checked against child classes like ConnectTimeoutException, causing connection hang when the destination is unreachable. --- src/com/loopj/android/http/RetryHandler.java | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/com/loopj/android/http/RetryHandler.java b/src/com/loopj/android/http/RetryHandler.java index 96d7c00a9..957520de0 100644 --- a/src/com/loopj/android/http/RetryHandler.java +++ b/src/com/loopj/android/http/RetryHandler.java @@ -28,12 +28,13 @@ import java.net.SocketException; import java.net.UnknownHostException; import java.util.HashSet; +import java.util.Iterator; import javax.net.ssl.SSLHandshakeException; import org.apache.http.NoHttpResponseException; -import org.apache.http.client.methods.HttpUriRequest; import org.apache.http.client.HttpRequestRetryHandler; +import org.apache.http.client.methods.HttpUriRequest; import org.apache.http.protocol.ExecutionContext; import org.apache.http.protocol.HttpContext; @@ -73,10 +74,10 @@ public boolean retryRequest(IOException exception, int executionCount, HttpConte if(executionCount > maxRetries) { // Do not retry if over max retry count retry = false; - } else if (exceptionBlacklist.contains(exception.getClass())) { + } else if (isInList(exceptionBlacklist, exception)) { // immediately cancel retry if the error is blacklisted retry = false; - } else if (exceptionWhitelist.contains(exception.getClass())) { + } else if (isInList(exceptionWhitelist, exception)) { // immediately retry if error is whitelisted retry = true; } else if (!sent) { @@ -99,4 +100,14 @@ public boolean retryRequest(IOException exception, int executionCount, HttpConte return retry; } + + protected boolean isInList(HashSet> list, Throwable error) { + Iterator> itr = list.iterator(); + while (itr.hasNext()) { + if (itr.next().isInstance(error)) { + return true; + } + } + return false; + } } \ No newline at end of file From bad4df13d0a19e29a32f7e0030e0b5e4a3f71b47 Mon Sep 17 00:00:00 2001 From: James Smith Date: Tue, 29 Jan 2013 14:54:04 -0800 Subject: [PATCH 017/613] Add jar files to repo since github downloads is now dead (RIP) --- releases/android-async-http-1.2.0.jar | Bin 0 -> 18435 bytes releases/android-async-http-1.2.1.jar | Bin 0 -> 18539 bytes releases/android-async-http-1.3.0.jar | Bin 0 -> 22320 bytes releases/android-async-http-1.3.1.jar | Bin 0 -> 19917 bytes releases/android-async-http-1.3.2.jar | Bin 0 -> 23098 bytes releases/android-async-http-1.4.0.jar | Bin 0 -> 25606 bytes releases/android-async-http-1.4.1.jar | Bin 0 -> 25875 bytes releases/android-async-http-1.4.2.jar | Bin 0 -> 26068 bytes 8 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 releases/android-async-http-1.2.0.jar create mode 100644 releases/android-async-http-1.2.1.jar create mode 100644 releases/android-async-http-1.3.0.jar create mode 100644 releases/android-async-http-1.3.1.jar create mode 100644 releases/android-async-http-1.3.2.jar create mode 100644 releases/android-async-http-1.4.0.jar create mode 100644 releases/android-async-http-1.4.1.jar create mode 100644 releases/android-async-http-1.4.2.jar diff --git a/releases/android-async-http-1.2.0.jar b/releases/android-async-http-1.2.0.jar new file mode 100644 index 0000000000000000000000000000000000000000..3fd4789b52ba902972ca9a907f8a58d020336a0c GIT binary patch literal 18435 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 literal 0 HcmV?d00001 diff --git a/releases/android-async-http-1.2.1.jar b/releases/android-async-http-1.2.1.jar new file mode 100644 index 0000000000000000000000000000000000000000..4db7cf119372fe7eeafb417da0d684296cf19602 GIT binary patch literal 18539 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 literal 0 HcmV?d00001 diff --git a/releases/android-async-http-1.3.0.jar b/releases/android-async-http-1.3.0.jar new file mode 100644 index 0000000000000000000000000000000000000000..faebdf0fc1a7596ed71aad9890d82b00d89ccb6a GIT binary patch literal 22320 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 literal 0 HcmV?d00001 diff --git a/releases/android-async-http-1.3.2.jar b/releases/android-async-http-1.3.2.jar new file mode 100644 index 0000000000000000000000000000000000000000..0af4063246286213ea233d5264c6bf7917c6ef9e GIT binary patch literal 23098 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 literal 0 HcmV?d00001 diff --git a/releases/android-async-http-1.4.0.jar b/releases/android-async-http-1.4.0.jar new file mode 100644 index 0000000000000000000000000000000000000000..2109150d5f4fb2eb00c46c685f5797b67c447091 GIT binary patch literal 25606 zcmb5Wb95(Nw=EoZY}>YNr(@eTI!-$H#kOtR>e#kz+sW7WzUQ2ApF6(yjB{&@s=wy0 z+PiAcHP>Ei?NX2d1%n0xf&>C`b{-W3`acdRATS_VF%=;?DLHY5uQ4DXh5vv;0y+JK zeh87)`2U4x{XJ3tp8o}v6_S$@7gJWDmlc&@Fm zRV+r;iz>FA%5+kC<09XBTc)~+@tu+U=nbs(Cq8UkzZ$CQ8XHPmA z^}Nn|n8baw1+ft8$@z{SxTfDdva_#ydc46Muv@ri!gd+k8BzP{AjKVc0RWC1$bHIq zq)1}4Ud%Zp6NfpRA=97kwbUdu`;n0iu$OkKZpfO!DvAwis{Qp=yNa zSmu)pN0vz(E{5$~^=V78n_UyJma7&Q_<@NnV*z;;r5Q$b-R-0o9p0maqSdR();bYc172$C5*$F&*in^S3cFW{g?a5|X{51Ay)$a93G+It=&Gmn*eIc=qG5g3W z?5vICo`s*TBpr;>1dbBdE6cEw3o??xch#!uBk!R-9fX89_8R#LEc1_|QO2oJq=v-6 z6H@cnsO2Bi18|GcboxxO)KPT?BO|S$?^@-cC{qVB18`E!Lp^3y0yEH>4%QANg@8Fu zVYSZ9_E14F69X)vdF&mAk>nEQ*JZ|qow4HISsMf=>$|LIGl;I%mvlE3V8EOSel{n= zi7*~Ec{}77z-D1*!7t02>)q57Ui*~oP0=3=;WNA3IC(^{wEr+ipn=9eN-xP3WtUOP zw$V)<{0>UckX)Z7_iOAXUXl)gK9RGfKw0?t#6|R}fB%+oqAg2?(>9hR7GJi_Uarpk z8M8(A)eK7NTD$iuTkLv$DKyNe3#D5aiK#(*B$aFz4%EeWLe?0gM)Gh;wCi$2qVa|T za(>geAaBew`G*A?VP}W@8;-Pb_Ja@wft?Wi23}EXXdT-Ef%p^ng5>~07#7Zjkv5tG z(ab%9m5vy8JNKtl8n9&oCm|amoO`ML*VlB+F98t-&mMwfE@F3~IQ_Yr2k7TpY)}9- z+OZ=Iv^ZL8m^l z&raG8wnc!1to!};i@`vHcIYho3V9Zv^HJut_c3$B>-jx|AFG+Lf&*(Lw~OdZm@$$| z9!~%#GSUMf*1#>J=j?DCGzp;5+Btq7br@(EE7-50bUo`6_SRCCz66#EMuuu}LQ`qF z2pz8^7Q0DZ);7K{G7n}Ri#!Kub0O;!R{@^hf1C`rByTvZ#Dj1=P_y%uJ!anNCv%7a@eA=o@Qu9yTrF<^0$tK8L1oX(wz z6QV0~+6mSX);W?9aC{J66y~^DRC`U(=Oc6GlhhiWmQG~5IWY}su6V>bMP)JI0sd$~ z$O(?&5cdZyat|4pW@LwXI5oiWr^3){h^N1?k`TH&CPX=nn)yk^D=SYE>2T* zq0=||n{=m=WLH(C!C8|TbI4eWwqmS$R~f11oI0=ccFTWg(z=wH8|O(>BtV;fROa<| z;Xbf0&h#M37H-NY{kYVXZKZext~^GuTH1}?;nxCv0LP!l9{jR>zrLCE0zZfsTVS4e z@(r}_YJnu^k7YP|4^K<2ffTs>VEP^s%yspX@L;`xFERe2lt%zJ?j;6Up#DPYJq{)7 zA#`Z~JVNaf6SHZEoFJK7YiFF}Yo|yN5I6Dz4=cxKE2=R_ej_t=_7UnCgV;01w6w~4 z!CgE0Rd(#YLj~>cplcx=ggUZ1;}U~Qz(~*(9Lx#qNYJfy3Wk*FlD75#BAc1B zW`nM+bwL$dQ`1s=*^VDVw+a|kSuq$*Bjp?WX5?t%;`wi6R6u@!z#oF5?A0ZW zJ@6l{PdeSsY1f%G+x$Mj4mcbj3l6ix-f-r%j6pIQusgd>hwr1ebqB^ld7kQ)rq@ig z61oYGZIWWVvEBx@V+3=HOKPkAlSe4QPU8=x{VwX3uNJHuDXZBl@*~I(MYdSBMa{7H ziVa?bdeGId*C#F}Znoru4haUA%XL4in7N-FlMIj4HPp|#@fZ{HkJy3TCMhEhlbuG- zpYKKRI@Gknh)?KJct!oFf{XHJM#=#_id4UEI`3apMiDDokBsRCxPEqop7SqZT@9Fx zpcv1A(z`IQl)HVbg>hTseG((7oqjQaKiq878cJBFH6h0xGn$G%$hx%lYeOnWR)i0s>n3>zB~} zkEhAMrbU+)lsoQX+?Tb>L+N6&>8p`9K|P%%EB5-zP_juN?Rc_ae2{o^oPX%kPZ@kK zR+8ymP{|Q+Qc?g55h*~&02E>moOV8tz7_gZza;#s|`+|&MHv!(k>7RdTk1uJ@|MrC4P9QsR33`q~2?{~po$V*%R zLBx=}3gwIT?7iFG?KAZkumD<2PT27D;68?_JNS3$Ue3+5+=o(cP3B8excV*5?4vgL zcfL_|>aKp)3kTYF^tZxp-|z6LZ>K#sYQ6qzv=5;{{n!>UbLJWy{)N+M7 zC>F4mSvdcu^kg?rTWn*1bm$bg{3#!z&a05WJgItd;+ znvz2mI(!;bt+M|; zRgRfOr$NE8?e^9Z&(8VE`VgwjpZxW8t!77c8Eb28`E5aMxAQ4myiK0W#soIm zW(e1d%M(W>gghe+fw8v`xZk`Z;|A#(1HWfNpTR?-B#EcGTLkb4EaAYT7&$t#;!a}K z31&av;c75%AVMUB^?Goh>{uWkMJczkjSL*d$h=5#Iii`r$8f*mEJ*QhC4{%0Sn(Rl zZa)=85FSK{iG-`dJc;oS%7_hr6Jw*zKNd-@MPq-7@*<-}D()-vg${N+AT`@SkhtAA zq%m-!F71a=uAjD#7EES&aw0~7ff_}(rV^IPLFk$@qT5e71e7wJY#N{Z@QM-&OuECF zd}_Co59P`@Z)Q#V*5p8Gbkz$$N?NO)UZNOYcJcxg&K((WVr~mlmX~eHQrbmTN@y zW~3_H&BZd3KEPefWUU{Jv9OGK0TG~U{G`i7vc^n`!P7yG!7Efe#O&SGS}gJ~dBC4j zGf%#EJuLZXisx@c~wB9>*#AItGkxGX81Y@;%4yIx6qU5sX!(WYI|l@WS4*KrX1aG zlNFqBQE_c``Wqp?I?WpBhJ{<*6XYQ=*K%>oUnnFG&>*l*h7mk1Qy2}29)f#Z@`szX(b{C zh8PJk08c=mz2})Q1tKyQ={VBifltg!dV$^92brZ0c$^fV?qvM204`_f!Kgom!@l=U z;-6Z$i3X=-m5Bhj7p6|u?LUnzc&WCqkZ0`vz5^ifnN5kD@E$wpSgT{JM~?J%OV-zK zsVSE>Qt>L}@mD)@b);W*hYpTgCVZZDccO`tZ;)GSuMmU}Xe`4*DRckc3NI5})p0oS z?LmrAt-#zL`EaNgM;yDBG}AEyN=_7PyTOu|P=!=&^%^3< z8@#0xNIbes`zrC_6Rp0x&RM{v8MsJPb-_8BXOB2RE>0^d!wqWQF8s!59J4GvyyvB- zjli`4oZGHlU*tnB+aDic;3s*<^xJ^}NAWxas|TXDT5*EB7%LZ&&BB>UY@bN}>KQuh z>^ynO2dOB#B6&-9*ccSG_2m0KR+OcXD11J29^^0xtpagBRfMy=)D^40O>s7>XYjB$ zrQ9st!!>%tMLr4(EUYfukF+*3co;t)ibku=$zh$ z28kWD_1LL;w7A8A8Yj$)b6yOj-kTD?oqa?*m%1xAx)o{B17!s)fhvNq@90~(m~3J^ zXXr9Epu(Y7N1eLEBihMLR~`A)1;OoP?4fgXShu#^sus za2ZdW03C{^W_E4F4+|%r-|wk|4q!LWzK9>{UfI%!4Q@`6gHvAQ;3MM0oN9-yz?Igb zT5MXn;&zl~s&tOn^e`SXvW^Ej?U^dAoF8CYFM7Q?VWwg0Am&dQp+`IDxY~D*ce{0M z2?T68$AC4Hl{XqyMJaUbm*!rM2dcF1u(~y({jh~^=Q*V7kvCrY&=CuG-r|Y8&+xWFK{;E{G#f6X-q&qBltv7<@jkL%wj8F7l=XL7;`S)F zRsE=Pl42P@vk?H4Say9ufJ=9&Oxj(aHy)J^#Bgpe!iHTpc(e{xf)Pa!{Aib32chr1 z?eGt+-ESa{!Zm_}rx4~ff}2KJ< ztSbryr+rT8O%#Q<96x7R(|v`_q%+R3!4*!{d$vhk-e~CONHWY}m#cEzVOTd+)!X^h zRecqNlv?w~C`EV_(y0O!P>jUHk^?Sx)rnT&N0r5Sh2lM`MjO zT@HWKPZW)*jYVS(M{JrlbJd<MsbPwe(1MUh|F3h~l zoj(x|Uyzd{p`JdnLR7l!-{~R|XLXN?@w|GwpHz8E|{xY{L&TD#!+A8#(6`#*1=~!d%+b5FKk>F z>sGe%_G|B)*(hLY2w8oUvOQbO&A6=MftFSMG=OF0^iMX5{r4Z_#eGI52T3xu{KzB? zKWME`YAwKGBW6@109ZfVRcp#g-GHMi(Mn!oHY2rU2MS!?Q_mcD;L3Y#&K;aV2S`~> z!mgZP@KBR;KFDE?dz3l7SIMRhGS*pxon;(-lsUP7ubP4<8E;~aUUNu!>L8I_Q-y+- z$aNab^Zopc&-f-PFGgn&*Vol_{B%(GPKwK%mWvxlGMoDTx$3LqUD?k2f5()G)|e2n4jGMpiCItzhpmQ6?`z_NuQ;h7qGwJ4(a}J=KY&i~<0h>$Qt%bgQ&Ad^*3cXaOp;26J!`)*(%17rcuBu4a$gF-dMn zT^yYoK1uGz*AsH_fssBx0ePR9a|^NheUdr*DXo(b!Q`B0C^SkY*_-gVmUozTS|+(n zsGB_FRt(~mY?PC1q+QT*TZQmDRp=`W^`VmN1RLfc7`5lmQ@MDNpoQ%6Ct1f;4d$AD zzer87uKJ&`d)^e`rt~uKayj5x%8%8zPkiW4At%z7Uo7Voh+2&PehSk&wP|xDsk{m5 zRG<_vN_c=*_>jbhPO`G?02@_lr7#_1s2WE`x&~6?b#vO;yt*07)Wgz0(!&oA$mUcp z%Ej;K6dKOyj@iMW6v2QRxqB73FwD*#NLuetumIe5zUp`;T+BhCC-dlml+crNYFY6+ z@sjaZkRw^bfx|I}f_dCuT$8AWPI9>o+eMK?-Vkw8x9!nP0AqA%+yhE!ot_Dn-hS`Nr^Y=KWG=UYv`)kQg-kmKxyMc3^J0oR%eEMUhr2F+k z3)$-O>}D?HE9fhsn*NfXgUBxo4Zr@G>phdt$15l6eYqj$H~37%_2){6GTTaoqSO#6 z7u_gUbm_sq@6Vme9JS+vGrnld;X)lb$DcC3TQM-Ll%tUZDGr)r)gz0lWxr&x+w>xE zW;o5b_I$w{%dw$Gg9vyWl&j-?7l;S*-e<#=<0l*^oC~>_VO4J%>0v{51s59Q4e2Td zm5DQ*GHo=bP3Ct$9A5@1FEz9_u!;hS=*Bbf-Bk4C{Ae=8xKvotjv#WCr~H1 z9ztiR^pR16t7GjJP5>^c*UN%RtXykR(2s+WMSg=u7*Ng=(DP~9|9C98o{nHnwz8iAVq7< zp6=-m5WG@5HUFSFY=;`0Z0qR*mvTvfwzaq2?^)%6#d)aJ`6!g*VC`Q8znaoiyrUUZ zyu)f#yy>JEx(y*c(P4K1Wf`zYZsAL+-VxV-t8;jgu$vWEF@Zl9wAkFm*#LXS7 z>AyUYBa#ap^t7tCqLrq<^^L;Cnhv^GREJ@bGzIYAhS^iOdbNv88xrx^TqM#wlS859 zvx#q&?Zg*<@#&j-c|fU)|UW+t@Wv>9@_ZOp6|5Yg72uFthUe2uU?u+Q?9lCOy$ttpCBpO1Q=w86*UZyvNqZQvK<@chZ19~!=e zA4ZBU!1clXbm?RC>lMcA$&J4Nna>-(`Go2*;ks*!O#g-aErJ_tv_uz3g7T)o<(PEyjKbyzWt336V`Skl zQ#eX$Uu}v4iT7g^Qnc~65y9~7Z{C836k!#!cv_RWcNA8~C5rGo+JRG6OuL% z45~n^(ubf8KN^=~>do%p%T_tWja-+6!Xm{nkondy)=Sw5V}CWbxZ7Nd;5%6A0$~T( zC?(Zu$IJ_wfO+X2LxknH@Bb_6{%fwASn+RiM(=M?0LTBxv^zW4+c}#`{w+$dF?IUS z%77%Lap`_RRK9ujwbV7Oq&GoC%RB@|wGFBO)FR>YKC5R-W>%`xpQD8VIb<(D7`}c4 zWLtnxI~^B;qwcA$Jlpn*sN|k^J0cW zZ!4`nsKVn^JItv`m`BPBe9IP{$s&bL3B#NpC5hfgPz=20oR+j%o48CS?Q^@ZMcouH zlSb@Ey;*EeqslJE3w|`FvLn^(Zvv`C%qVU;!USVt2KO`!((CUrmkw5Z8lo;#Hu}`^ zKOuNsiV6(Z1@Us-NfMJK45P47Dl9hCrRK6NBfUqXWWLOGuCXt>j zoiwqDX;KRKrDSHO3IrRuEq|Cu;CC(h0$X z+!;j`^?Q^2l>Ib$ZVr7+#vl5WolHrr4!n`hM@p;KayC4C-NjO=&rX(I&}SD1)muPN zU^G(B+{L976x`GW(|kd3Sf0CE91m~!yKnj0ZjKdz`h#WF`I^gX>p0u_Iig441yT@2 z@4hzzkAEY49ToOoBvNwxApo4UUtalUIr43#N@zw}QoRksuA~{hJToIh94KKkU z2F#J*sV|X7=E{`{^I6eMg6c|0IG_+@J)}r7$sh;e-BGX?VA9iq`n>B4jxh2lM4+~t zODa&fW9WZ(W8mc_!P!}nlG{`vBKQEA*raVXwF!Pj=W1-!|r-qA0 znkeP25n#_hpZ2!eMMLzv5}QRgOOhO%E*Rgc1bAW`-oc8ZEcupg8{XS84WWpOwnQNT zS25}vf+k7zr?*j)Zu0V=N1i;YP~{ac=G!Aa zXtq&G4A4WEWCuH|tHo7n(rkD=jNc4QzEjqPgJIF2>7v6}0_06zCfH^Hm@n^|A6 zm4oHlv{U`4M}D==opF+JgigXC%(iH{U2vW35e>5tBf&{TP9L?C&D|p@Va@(TYJ3A< z_Jo;D^($bH4Fk67Vqge*D_U<$17EM$ccI~#&7&gE-<|M>J!v=l4+P{M?jQQYAt(Xw z&~u!DkToPrFdS}wgfC$SG*NxRoi1^8L_{}BcGH{yH58SkB(+(!1pENSKq4DLv7ncb zPz8rTXBG}~)n#tZ94d2?Bvc*%q~ZAQ1~DkEDyNlj=(;oju9vrmR5Plb@Y}k7KSO~t zndafKVBHd9)Sf0(PQjiK`6uDN?f6@5aH_X*xh1_c(V{%js}R%H(G+G4|y zpANH@o5^>I3;4AthPbWBUDqR0V4b0AAMri)Qo-)9R^{}{dGELcfXM5v)*E4DZKVHd zqe)26xe+V<nxAsWoFY+m!GXpQKld1 zE6%bX;_a)!>f`Xfrwf^OEA4x}cq7m#CK!E(Y5Tp`_@0Jxq;B@j0lV)OxrabeEA9Rz zEZ9p2{kDz@kWn8G#&?Kk84(^UpvWv3*zWnKbipvtB+>kpuI0aLNA`a&UBZ@jhE5*; ztK?A0^dBC^zqHFo?c7aS4Ds7G!R5X+XzoXBfGsqoN0UE@7)U9dlGY_?#vSG3uZ)dm zs?)CBqhKXe6)<3eP(KM#@7!$V!DT8ZEgA{S&AWZu59OBk4}T(2sssDbfZstQ6y(0I8+`8zzX4TEXrai4^w0y zE|fnOQayEunZ#VQ2rjooc%D%qRoskt(h9sR8|vZ-mJIa({k1hgGQxNspb|cqI2d33 zM+-BmeME)hJ`+U5a!X2YyGR5TY{dG0`CJ>} z6PvjQofB`|5|qYaR^U0-T$5s+gSB`qZBc0*ZD*kGU2eRo*)5mZU~J_OvUImiau$Pb zyy5ZEc<1H7Op`lpVUe(A^Qo`YKy1Meo&%Go%)>tSmUw9^D=T#UbKB*I-q_5<0ugD8 z5ytpDHDVU0!m$1w$qaReRO7_{X-B|SSZ_C7`pD1<*6f_8ie(s#%;Kb@>eBGhnI&7- z(z)0lQZU5Cok0D3Fx&(OR0b(`ENx6_bI`R#fl5(0sPkIAP}?0&@?B27xEpFcW6Cq@ zPk7Ftf6tO+UNwScC{d7FjPVF}HqXJjsr>rZxf&qoW%#0%Izv~mTN6%cc zA8}+;*W$Nd@0)l)qgIDVp`2RfuTu!dUO9=iVXS38v(WlxltLSOj~5zD#mTj*u@?fE zJwxq~3LYIS9G!K7Ywdyx^IFr*>y|AxhefZcwHB;NdDmo0Mm&l8qFJqtIfBCM3QV3)D^Rbf2>jW?Hno-KO(vYo?EEOkQB@w<$z=`f}e+RD~dLP zt6eY;ifB3#L>UbW?TPsl7^Vf$1_W792(=rOgrtW(r3JN}QW!YXqGp)Ahbje5hF1|2 zgDM^T2wx(o3o+0%+RYB zILW2o`#?z*#}bjqt@Or56FV_6wBEEmFL8Lc&#UL)j4E27PnI=oTjz#)7R@RmvT0yN z-}WP$*!*xU*ql2lbx~Op7^dc^abX2y*_EubM^Oh$RlXn&E)DG^z_a^aPmk@<+&5KE zSehEj`SK1RHg{b17|znEJhxvemm;zI22j}G4ev2NcW|);?oL@~^aU&{ut<>2s=~5Z z4O2}InmmHxy`c^{W}T6taHG+L>gDRRhbrj^b8VH-e)~PqE7$4dk$#3c?NA5Z8jrjH z;-9HMhdUwQ^c8A*SevqX)j4Bopgiz$cD46D1RJg`p7`*5KzjBdzWQW8WGiYlX{S0M z9c_-?BSqRnpgFnlkKKuf_brTYCnN6)j`UZ`zeLc6;or6bK|B2zB|oH=wQx-PrgW5V z`mM~guoj2Y^<-B2RR~REM30iZihWy9Y?uM40t!Hhm>5&{IQKPG*RT_&#mXoXWr{euItt1-(4SQf7mLmTSG0Wsx4dn z&Z+t1-;B{%l(rRa!@@JL-B`L6Hiq!v+-}P@rYu8^^^C5|G-hHGGf1z-VhcL)*Ih4msskj?&e{V`0<@8!kv-Ot28XVSZgx-owJX545r`PsoENGdt- z&wC2skkLOUp_-e1w?6vWL2USkBo*e>Y;d$2Q#GMSr;!J9-p)o`0_dK5+vee>^-5Ft z4H_)J^MG?qub3(C%KboVG%s`~8=hEYxqIb4RQ4IAl%rI#IiZAWw279p8-T@^ot>4|BS-lXG=KOV=%v^6MB zzn%McKt-ZrHQ8(CzKDvLr{bd~HoSO|3Z_oZmd-AJx3q}Z+gn?jD!bS_nf^zbLLJ6i`4H{^?3R>EoK`U0PdcZBmMDuqqFQpF4I`o{QV z+w=SB7DEe{O8d9ly@m9CY0ts)=Jl_sXMy97>9%d3+Z&W0K(wg8=raQ^<#Z1F7F&y< ztK4$}taWRRJ8(OCh{9by>KK7y@Y;hccA8H$Y7v!*O9wQ2stoe zsWj+;iD1g#fx-~ROqxtU9#N*x3Lv=t(+ukR}2S*yF+K!;AAmNKn6hM}x;@stsshJLy8{1GY2j44xM1ez9^%^a|+ zKM)DyoxhGGd(Hr6nFSZklC2_&{rlD}z!7sME>a;;{U!!T%$Q3=Rj( zX4fSXV!}eE@L@!7o1AYx&sN&WOlx7rNIP^DHzV$a`!#AkIvD$>wA>M7mTxnvEsv`ied zAbWLwM8T>5&!75Pv5GkzdebJSB6Fk&TeagV#6Z;oJKNCWYYwd-5P~%4Yr# z5W4l?QfR-2^i^zxfA;FDe!%^v=u-U~;(K>Hr5fc_FWiUBP)KWZtA zgxxn~;9Xo&k)8%5Xt7tAt9C{&t9+=JXFdOxVil^A0#3uGhTnyQ`jj2ZI1h8F6^;J| zD*o*!6xqHcL5;e+Y`a!D9C;^Jwo8W=!lcGK)L3oGSp19=<|1uM8Wav}qsjz{ttfC> zxunv@ZisXv$9o+Lr>44M(OwG^LX9dl&_4Rnnh367=NgZdFO5X6ssf`5UtDIyjYVh{ zimkc;-9l_uZj^ij3AROkfQDiy%y`eK2}}V_oHmls{nPGg9o3NA!MMmRt$-{$mvmcF zqUz~BLVI%`$)4>Y(o9avBzoEiD~pt(wpCcx!Zvm8{jjybZqHgcTdPzfDY{$*i>5Wd z+CA|>y*6;tgEj8$a`nq30H4>`s-5Ot^Za^Q+54MSm3F~iIgs*%bxv&E?3H@2IsT6X zW_HO${3r`tLW#!lNe?6pDPP)1vR-0zM?fsT6NM&q%V*A0 zkIy|PmAw~HoJmnSu^q|`9Yr!YPkmf`LZzu_VtBwkWuvo`IB*MCt%P2tJFEhhVjGGz zP(`DnA)b4cO2-~g`!KbbMyCusmsY3Q>+O(1AT!+CxT|=#VtD)*G(SdUdGABQceRSDKQZVS^ z;ZU*Q8OfkSWq2s#Z`5Q8Fd_9k5DR!2r&q-v?v1N)>(N6XlW5W?t z%pmDo%d1nai5qGgr_#|1NUpA_z*74MZs$@sRgVgIBM36on!T#A-p#gtqTr#YyN@&# zMw%b;$9nStFe~)Ku+2n9dVO0;Yn>nzyFYyqYX*cX^K<$-Q?mwn3}I>rDjd#G zo-uNx44T2+^2GxXUdSGZN{;ZF)!!C;2s_F-GsWYhhvbLV@tij!b#`n>!c{=ts9v#dv_h(4OF8 zd#zr(9=(5&FcHW!?&#_Wfi$f|Dr4nCzJ>COEm)$QyJ&3n)j{t;KZrmgQ(s?!hp#~{ zkMcchrbO|u#XORtR2&!rUk25i_dNz(=)FT?|2!cIx!@S<77#6dom`paOmK(Cs(}KI zQ{G=%O@@ow)bBxYrj}r~xBt9~?E zTW)XzwIh+g?v*WOG3#^1bQasfvCn-+q;)=t(0iLU3ceu?j&&J~R0M0kb59R;yu+7+ zLhkMziVUAcq@#!F8Dzs_!sBZB(pl8&$f#MTi z8{T|*;bIuuG!}H7Y;c#X#r2|AGk8eP5?g|IIdZ*W;T4zW@IEyNV`k!5370iaF7S}C z7$)o*F(M&|Hzi-4nb1Ik_J&C`$5Cb1_2Lf)p6WN~KbNpCm-~OJ|E6f0{-$XEn+WQE z6x}EoIvLtJlZso~n5sJ&Iyjg*{mZ|NQXH3C|C^x|5zbtJ&$9$6LV!ez*x}dt6|@iv zhm4F|6iTQepGow~YP$8QQ0Y5dI*R(&tSN>!0xBuu86E~2kw$Hs`PF`=SM|}!-7y1@ z{q06@P#Uy!D%)-xl(Q*s^UXpZNFd6XNnr;!8n2m!9sDcCRmj+OD}9eKB>^CAWv|Yl z^cyFv758NU;n43*F1HX}>DpJ;qHqDqv{W67`;zC;j%K5p_L>QGStaMdDkBe7yP%a~ zPIoJ!bPpDw_kCSDtJgiQ+*ILlbIK=QE$ONrH7AIPuUkjLA0t}a;xV>=(UjLwntzxd z84iOJ4TAw+SIFEVvu0u#w=0`#B%nl%^``TrjP~>yqebxr*G1M~B5){0tmNcH;>=Nq zda5DkC4U3^r2o$u(8Z{7`seTO6t4gN z7l8WTuWtS^1OD~Zi`2awRMfG)vrG~?R=Z-6A(>Du;($i4g2gDQ0Dgj!(xj^j@rWYU zkM&+s^cP#3S6fAXi(1kZOG{RZ{Ix|*R`V%zsFIQ*+S;sD78Q%-`vhl^1uRo9sg9Ya zD!~Cg&d+hOu8v9{#?yQ;*Y7^p*O`1?yG80iw8IR#7NRRbi=nNC#Ao8L934m!!tluf zg*ec5ina2-&oX_Wrnvm)easu>JI?e!hmUnz;q+t8TgLw;bz4Pxi7?D|<)qwG0vOsA z$iV4DRHnKQ22u{6?I^nU2KbyjYj{7(1KxEs2&pi3td%lqQjiXPxx7X8e3bFMW#ADg z-4(*UB*VQ{z!BhSUYcQKZbJhk$TT#1kuahTo|!PJd-v_Uf5^Aq=z33wUDLQL_2&9@ zEJB`lIntre?}J!nSf1S;)h0`xKmnV}$BL5Y`U(r`5WQEfOQW+qM&ZmcM^86Kzu~NK$Gd4{qv|WyH*L=%Bj;_hQXHkXTKJO zf{u2J?w?s)xY?0A*Zu|2(GfYmt*T2{=Mv&&-8q_gPpy<9@Mi~8lvN~8>E8dZlB7_1N(S}8tYp2^zS>1YnMy&6P4PZWxRl`)10q<1y&emsuA8RV zb!Gm}$lzj6BiagY#y{{IMNIGa8yz`hwC&RA&%>l{3aJ*_d;3@nIz^Fk_`NyTrLERn zSgCSJ@4-z&0$&8Es{|d|X%#iZ6e;Tbm)ezOaX-F#HqmGUq1fw`+SoabDTy&2Ei&TH zdS6jt$x1v8tSapa@inCDd21>BF6y~XdJdict1Uq5n>Cs^7H##`2q>MxYC*>^qO`8) z6!cUs@*L9oQR?;As1boEZ&1>br64dh9NPP>mkr8UXw?3hZjoh(hl56elqMhpi#7d@gv)_ zsRyOzbBIp_E51$+KmRV;4Ys2j6gV_TkKx`m{HlGB`mxi_c?ESX=S8FY(R% zZT7XNP3GcW8zV#V5)!xp&p590q5ZCC9AOhRh+bJ7Y>>}i zM_W^`P=!L3h@sOpD`ZPfj8eK$JK55+rc{D*QY(}?$tlUl7q&Kv9!12>=1uO^qE@bM zU23NTD{D{>j+}#+EP6G~PMZmuaIler3dqx=6yd-Ky?r!(mWu0m9W(+ z`Ei4JmQ}EZ5?iT{T!jE7DR{-HEBH7X{Yrvs$D+K*FGF~bydgLfa~?RREY3L19iI}^05wNJ@ZXq98`3i;(u-FkI6#ovecuB&*i_T%e zlT&epZ7;*Ok0P@p72!OQ&APYSb=gPrY}||Fpx@2lji8;SzF+USM0n+VB8$Q(tAP%; z=gHEVn>N>{V;uwzZs(;LEfxKwUcToRRvnYOlk9_K5`{WEf#Rmy*_u0^gi)p#acl6! zi+cQF;)2rYsbwg}k@9yXtGwY#d=Xx7wCl!8xh5>0CvPhKFkB=cJ6};gekaqiK3jBh zuUfud&UYR2$rIz&PAu(T455n?)yoPRtl&T{uEu&%dndsFXp778wOH|30w~gE5%vwRCCo$x#;*ULhy1}(?Tt+A?iV50w@an7O z|D<@^&2Y>LyIj`vnbO|sPri-08JY*Dd@p#TkLo+Q6^t_KCyym zYevMnvJBPkc?&%z$`osfa%{0<@tNx_v=&D>ur4!RQJpipNKL)L`SU(##olQk@=j$b z@#&*Sw12k`a=l9>_&q`KpPOIx1yurd+*_g~$D{)l>sJk@dG&>qxuFL=sWKQyQl#v9 z8K{MCB8E0W3=Az!7X_0iJ=7MyC-uRjm+NwdzSVT~ywJ<_;DF$Os741a(t_zxkeqw_ z&^P<`qwfm-x8I8mAL)gbud;_>afiwgc7-25li-#BbUV^x#KG-4!DWFE<{F`(^(H?A zx}r6ZMnaDq3WWMRJczHK7Su!7=WE2;U5(Ltn|DE znP5FFdNT{^S@YUvJq-&Z?XU*K*^@#@zfYJxAo2NQQ5Dt8oE@YMnYCQqm_ z7HwF6{xU1P(`CIYD;F;l~!{N0iL!2NcBMkw4J1q!9i#I$Qqj? z*p#Q+H55t|c3EXLTN_;vw2r-0?p@Sm2Z#6pYx>x6-Z84-7(U2Mt$?M3ER?MPH3;3a z-gVX&BIR7VreBt1Pg3qQTOSH3@kFO({)R9-X?ak>iV;%`nze;uy9q_=>NcmE$y# z@N0CO#;jm8eQpu!=+Ppm|GHdDn=PRU_wx8&MKf)NdL`$T)Q6K+Yw;)(Rav}IxTg)G zrYnsc`eSf!z5`8F*>hNz7)RWf2E!zD#_FT$TQgg_9nDCtzMY{Os}w$YAzY_nj{ueR z+h*@kh-}ytGEdCix*aBSYob^OQ7G=IOOL!y7$ibTklsSulpiXrm<^*|+;J4MdJA1= z&=Z!Sdo9?MW(MG*tuW?seM5hix6mOOxjt!FWk@lC zi4Y?Bf)jD%7pDlHDd8GoW_3F8mCVVnz$UkG@&dc;XdT`1TPk;$>?yyTMWAw&8!}={ zVjam74n9sP4a7GDWUZmcoQGLa(jviSGqQjIUu zfTh6sNOB>vZ`>%86M!44GJz5x8qsNMET*Fpwk zLzf{GIVOOdj~!dCWBhsaYqBZ*&zj&jURcBRZRqYR*43INU4A9hcrE> z5yyqw=XRxU{!$m#X|cnI=sI3>JFqloqWKzIe?{0z32d~}F`-YWi&P3lecK{&f(Eu! zqJCoC?qWfU5}{IJn$;vCZIFLxSS+-Zsdj<^Nyts+5_Mg^ez2aQe%KmAzgy3K+^uIX ze!ZTl0-ddlZLPeGO>BX`$F2SuvQoGEnb7NRe^q1mp3x6bszH-qB~MEqgoK^IDNPD0 zva}4%>vXTBYFygyU6Q9QVT%(A3|Qq7;^8IVxx2}^UUhIL9Q$mWWJky9&vOCx85?rR z_BlMv3Ap`2@T&1x9gD4rJwnqdy=M)hV$~XJ_L$gaR-2((%RJFM(Kr#92AHw0a%YPq zw5Rnn6c&tRDXXEW(o%KIoJH62?Y9@7Ijm|Joev9&$6i!-R5Y4yh>-JA+Q8t`i)nIhxYygCpT?$;|}i+l>3<;`uBtC^$xYQcGsi3`Ew4_1w0z_|57Q^4*g z#-SEd+s}1uS`G8h&Fh^Ggj9@J8l`yh@}nrgAtIno`g3(#Q_0ZS zw@6Pbf-<6zFTqq~uoeUg+SGEBw8#wCm6@hm*+?XTQM@IPL}pWggobRE$Gj^&f!|C?wjrxrZJe39p{h;&zaway&`?&5ARG2Qj4}O9i||&mv%7tm07$z!Vv)VI)R`2rM7J& zg1c4yLkj}B&=Y42c&C^pS;aM?e6i}NRKH`CezTCemP+m5AHW=h|8hWiB>V-NJ2;ev zNtV7(zQA9EOfhc@9pyMF=a!UV7_pZY8+$UmRw;Mt;)c+w<59OEuIgP1l3}n51dz9*2iC$&`@+<{K<$#U_++M!!m6q-)@vAkx zPAsPUSK^DshPrT?JLsR+kN9Cey!;`*x}qGsSR$~}k+{P09=|+vx)Qg=!`zqzIyT<5 zF7`Awp&^drb9E8E=T67xiGJ_qK+{Ctr4D+ceOd}>18S8t<|trBF#CZ8g5NW)svx%{ zbf@3=PUC^sHz8O2XS5}XZ6(nU3p<_~r@7J~K6*rQC#m!|5ph*3J4aifyqm47m7}q< z>%VJlWocSD{@EQXI@E`XjN3;RCel-}0SS$Hf>UjUfH za2ClZke?zKcw;q5WtCObEElA=20PhO_-%?LYv)wa0;I5zu5jyG)aP1MIM&F^U{>5U zoz(VKAp6KK=V;Ki)cWbf zj7DsPi@9j@cF}E;=dNN-2)$ipnaEK;;m*d87#rZO%tKP7yQ~W;R-5+U za$XiRM(e5YaSPxp+=FvMnS0)9*~U<0sC=UCE8hzTFwn!sH%vPTLL$k=PIf@}hy!db zSj|LgD>NZ8&^SRFjU3Sg`s#l4P_zBm6f>*FzSe?dfV0|04MoLs81$6wY>jI>U+xIp zJY3E%_sMMWq2P|IlTm^nQDZ2e?xj$xBbid%{SiS-aDJf`>1f*|EfSetLs8am8!8_# z1Ol>CV_j)!4n`F-4B~mGI0GSO@y7Lg(lWA7u+)6osKFS>6$cQlxp`@u?DxS~;Bxc+ z=XvLj!`pJ=Ph-U@1#uxVUh#!b<2|;WeC&fsfF=}bIWi%PT@5oyJp{PB>@gTq-UcGC zKQMxE4rCI}l(t`GKacp7oxYXXOm=+G#5mw9^|*THB?`h##jRmL5iaAPr*1DFE0vkv znr5ed&i1R#^AK_gi9I;Pui>cgKE-HD3|n)Vbj?I$1^Z!bDma|R_27NSSJZDw$35S3 z65uwulw_bkT(Q|EB~Ki%-;<(qUqtwPHC*{j!JWWKw2?JEv&(gX`Z+fI(x_zRK)2VI zCVSEtgyeZneD*bsxfm8jz?A2_i7=blLOQ{^0iCrOR|zX4r7;WU*fY^2Zy)9)Ab8%i z2uoNLHq>GtQ4)%oPY$T=bV|x&G!HGW=ID$F^P8*81+-8ye9tkP9S;-0KkP9;h1q{y zRl<$KSei>p{&97IfR>){#AB~ILHd(%|dsp=I z$dVV`gq+Q$-qUl+^tp8ndW@Q0g{PkUSZKQ<;|}nQU0*0``;NMdi>NG=s){E@T67oI ziA_>m!ep#N(_6lpAPS_KsrfeVbH0Dt#j>mhiJP8rL?wyLzj@h(E>7hia>MFh2BDrJ z9ZYI@K1*gJ1c*Qbn929MX3P*PBO!@Q(U<)|QN_}D_Lbk7BKfKc;xLg6F$z01^MoK$ zGQ@9cL0=%~R(Ky`NO^AfR~X}_7zkud394`tbN!Zzxf?}*$d~7(g~hpX6uVHhvyGN6 zF-gS6*eE|kqq#)y@Bty47jlS$l{#uG0LRBQ|kY^lf0o}_i;Hf z#EVWfbREQE4JT&j=>6CYM$oh0guFMezI#fCE8pDwu(SjxHQl9goJ}18)MLgflU#O*rCCiA=kM93QW1{VPA+g$b^ zdxfe@M+Eh9F^1ECF#^hB-N(LIUcj^%zVw>WY&ZZHLe`&hZ)b(9zh>%!WQj=MQh?Kx zcNv>e(k$0$Y4qrAa)Ji^6(k2nfnU?wV=}H}$MG*)G97La{=d;F&TQLxa zPb#E*G9XsVenW|e+O7J!rv>a{#J7;8=dd}-)Y%li?f#o~W~_WzSIPvmz}bO#_!|R= z%G*uWll&h!diZ0c21=J>e#ZYU-jBX7I=fvbQ(fLKS4qu+k`v*3g z{|;~iKuu|^naIM5Rz3S3~%oN1PgDO+^0yY**C+xk*F3;>6O`Z&Ay zfo0QGIPc<;xT4mxE%;msoN?yRR>BseZ6*8BCC@i`_g$>cK9d($mtJnRb!L92^MZ@e zHSAeAx!(EeU$FM|Loa8iN#@B^_n>)k#Ke*>%!caRq+zui)PlULetc;h4#C-?A|z! zTzTx$nd;buM~XksbTE&xpWG!M{zCBl?qGMk|K{12Fdz}R1d{6gLQek%W3 z7Wy7L?`~%AC+3d<{-c8ru~~mVsz1bjsEK=z4RiM^`X90XQX%&t(?eO+dnTH@r~SV% z{q-0gs)F1jcM<$|nVCz z{B}=qd*{FY#pM5&Qn-i1cv#_Z&*9JfOOC&-cX-I~FuD4k0eDwZ@rzOXO{(=n{D%oH z_xQp>zr_DjvcrF-yFBE0m_l*SAu9a8as2fYei)Lt$CZ`&8{CHhiih2O=o!3+B~bh& r?63TU55XV02JXQ6F(b literal 0 HcmV?d00001 diff --git a/releases/android-async-http-1.4.1.jar b/releases/android-async-http-1.4.1.jar new file mode 100644 index 0000000000000000000000000000000000000000..551f5fbf61fdc2ea9925193d23e714dd149fd5ce GIT binary patch literal 25875 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 literal 0 HcmV?d00001 From 5fdd159c5011b6ef4be3117555e793fb10afcba9 Mon Sep 17 00:00:00 2001 From: James Smith Date: Tue, 29 Jan 2013 14:54:12 -0800 Subject: [PATCH 018/613] Fix jar building --- build.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.xml b/build.xml index 316d6b940..897d99897 100644 --- a/build.xml +++ b/build.xml @@ -66,7 +66,7 @@ Date: Tue, 29 Jan 2013 16:09:46 -0800 Subject: [PATCH 019/613] Don't delete jars --- build.xml | 1 - 1 file changed, 1 deletion(-) diff --git a/build.xml b/build.xml index 897d99897..aab356af1 100644 --- a/build.xml +++ b/build.xml @@ -90,7 +90,6 @@ - From d00cd59757757e82d3eec1e374681011f1c1266d Mon Sep 17 00:00:00 2001 From: James Smith Date: Tue, 29 Jan 2013 16:15:08 -0800 Subject: [PATCH 020/613] Update build file --- build.xml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/build.xml b/build.xml index aab356af1..32138d3fc 100644 --- a/build.xml +++ b/build.xml @@ -75,8 +75,6 @@ - - @@ -90,6 +88,7 @@ + From 06c1e3fbb5b8dded49d2fa402cb91a8f59f5a0f4 Mon Sep 17 00:00:00 2001 From: James Smith Date: Tue, 29 Jan 2013 16:16:34 -0800 Subject: [PATCH 021/613] Update package name when building --- build.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.xml b/build.xml index 32138d3fc..1cd94b37b 100644 --- a/build.xml +++ b/build.xml @@ -80,7 +80,7 @@ - + From 6e91a826c5afff12eaf0513f52b14a4b50a24942 Mon Sep 17 00:00:00 2001 From: James Smith Date: Tue, 29 Jan 2013 16:18:26 -0800 Subject: [PATCH 022/613] New release --- .gitignore | 2 +- releases/android-async-http-1.4.3.jar | Bin 0 -> 29173 bytes 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 releases/android-async-http-1.4.3.jar diff --git a/.gitignore b/.gitignore index 0900c481f..ff8a6e53d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ _site MANIFEST.MF -*.jar +./*.jar build.num build local.properties diff --git a/releases/android-async-http-1.4.3.jar b/releases/android-async-http-1.4.3.jar new file mode 100644 index 0000000000000000000000000000000000000000..3a749a59b8ae7cca9c02be16ef50a8e031a9076b GIT binary patch literal 29173 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 literal 0 HcmV?d00001 From 5e04e3b5deb81f7332565d683c2d467141a65fde Mon Sep 17 00:00:00 2001 From: Davey Fong Date: Sat, 2 Feb 2013 16:50:36 +0800 Subject: [PATCH 023/613] Fixed: Exception black list in RetryHandler should contains SSLException to skip retry for SSLPeerUnverifiedException in addition to SSLHandshakeException, or else would caus connection hang. --- src/com/loopj/android/http/RetryHandler.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/com/loopj/android/http/RetryHandler.java b/src/com/loopj/android/http/RetryHandler.java index 957520de0..715456fb2 100644 --- a/src/com/loopj/android/http/RetryHandler.java +++ b/src/com/loopj/android/http/RetryHandler.java @@ -30,7 +30,7 @@ import java.util.HashSet; import java.util.Iterator; -import javax.net.ssl.SSLHandshakeException; +import javax.net.ssl.SSLException; import org.apache.http.NoHttpResponseException; import org.apache.http.client.HttpRequestRetryHandler; @@ -56,7 +56,7 @@ class RetryHandler implements HttpRequestRetryHandler { // never retry timeouts exceptionBlacklist.add(InterruptedIOException.class); // never retry SSL handshake failures - exceptionBlacklist.add(SSLHandshakeException.class); + exceptionBlacklist.add(SSLException.class); } private final int maxRetries; From 31894ccbf46172924eff74fdc7dc5572f87e3d82 Mon Sep 17 00:00:00 2001 From: Rishabh Mahajan Date: Mon, 4 Feb 2013 01:11:33 +0900 Subject: [PATCH 024/613] Support same cookie-name for multiple domain --- src/com/loopj/android/http/PersistentCookieStore.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/com/loopj/android/http/PersistentCookieStore.java b/src/com/loopj/android/http/PersistentCookieStore.java index 94076682f..3f98a00bc 100644 --- a/src/com/loopj/android/http/PersistentCookieStore.java +++ b/src/com/loopj/android/http/PersistentCookieStore.java @@ -80,7 +80,7 @@ public PersistentCookieStore(Context context) { @Override public void addCookie(Cookie cookie) { - String name = cookie.getName(); + String name = cookie.getName() + cookie.getDomain(); // Save cookie into local store, or remove if expired if(!cookie.isExpired(new Date())) { From 6d10dd3465180fd848ac305be9fa6f06bf2e0ba9 Mon Sep 17 00:00:00 2001 From: Chang Cheng Date: Sat, 16 Feb 2013 23:34:26 -0800 Subject: [PATCH 025/613] Use obtain instead of new on Message creation to improve memory efficiency. --- src/com/loopj/android/http/AsyncHttpResponseHandler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/com/loopj/android/http/AsyncHttpResponseHandler.java b/src/com/loopj/android/http/AsyncHttpResponseHandler.java index 6c6ba088c..86fbdec98 100644 --- a/src/com/loopj/android/http/AsyncHttpResponseHandler.java +++ b/src/com/loopj/android/http/AsyncHttpResponseHandler.java @@ -209,7 +209,7 @@ protected Message obtainMessage(int responseMessage, Object response) { if(handler != null){ msg = this.handler.obtainMessage(responseMessage, response); }else{ - msg = new Message(); + msg = Message.obtain(); msg.what = responseMessage; msg.obj = response; } From ded7e8001d1e77ce70979adfa0a987bcecd8daea Mon Sep 17 00:00:00 2001 From: Kevin Schultz Date: Fri, 22 Feb 2013 11:50:11 -0500 Subject: [PATCH 026/613] update pull request #170 add response headers from rustybox/response-headers to fix merge conflicts --- .../http/AsyncHttpResponseHandler.java | 28 +++++++--- .../android/http/JsonHttpResponseHandler.java | 55 +++++++++++++------ 2 files changed, 59 insertions(+), 24 deletions(-) diff --git a/src/com/loopj/android/http/AsyncHttpResponseHandler.java b/src/com/loopj/android/http/AsyncHttpResponseHandler.java index 86fbdec98..60177918f 100644 --- a/src/com/loopj/android/http/AsyncHttpResponseHandler.java +++ b/src/com/loopj/android/http/AsyncHttpResponseHandler.java @@ -21,6 +21,7 @@ import android.os.Handler; import android.os.Looper; import android.os.Message; +import org.apache.http.Header; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.StatusLine; @@ -111,9 +112,20 @@ public void onSuccess(String content) {} /** * Fired when a request returns successfully, override to handle in your own code * @param statusCode the status code of the response + * @param headers the headers of the HTTP response * @param content the body of the HTTP response from the server */ - public void onSuccess(int statusCode, String content) { + public void onSuccess(int statusCode, Header[] headers, String content) { + onSuccess(statusCode, content); + } + + /** + * Fired when a request returns successfully, override to handle in your own code + * @param statusCode the status code of the response + * @param content the body of the HTTP response from the server + */ + public void onSuccess(int statusCode, String content) + { onSuccess(content); } @@ -139,8 +151,8 @@ public void onFailure(Throwable error, String content) { // Pre-processing of messages (executes in background threadpool thread) // - protected void sendSuccessMessage(int statusCode, String responseBody) { - sendMessage(obtainMessage(SUCCESS_MESSAGE, new Object[]{new Integer(statusCode), responseBody})); + protected void sendSuccessMessage(int statusCode, Header[] headers, String responseBody) { + sendMessage(obtainMessage(SUCCESS_MESSAGE, new Object[]{new Integer(statusCode), headers, responseBody})); } protected void sendFailureMessage(Throwable e, String responseBody) { @@ -164,8 +176,8 @@ protected void sendFinishMessage() { // Pre-processing of messages (in original calling thread, typically the UI thread) // - protected void handleSuccessMessage(int statusCode, String responseBody) { - onSuccess(statusCode, responseBody); + protected void handleSuccessMessage(int statusCode, Header[] headers, String responseBody) { + onSuccess(statusCode, headers, responseBody); } protected void handleFailureMessage(Throwable e, String responseBody) { @@ -181,7 +193,7 @@ protected void handleMessage(Message msg) { switch(msg.what) { case SUCCESS_MESSAGE: response = (Object[])msg.obj; - handleSuccessMessage(((Integer) response[0]).intValue(), (String) response[1]); + handleSuccessMessage(((Integer) response[0]).intValue(), (Header[]) response[1], (String) response[2]); break; case FAILURE_MESSAGE: response = (Object[])msg.obj; @@ -234,7 +246,7 @@ void sendResponseMessage(HttpResponse response) { if(status.getStatusCode() >= 300) { sendFailureMessage(new HttpResponseException(status.getStatusCode(), status.getReasonPhrase()), responseBody); } else { - sendSuccessMessage(status.getStatusCode(), responseBody); + sendSuccessMessage(status.getStatusCode(), response.getAllHeaders(), responseBody); } } -} \ No newline at end of file +} diff --git a/src/com/loopj/android/http/JsonHttpResponseHandler.java b/src/com/loopj/android/http/JsonHttpResponseHandler.java index 084bf3392..433690af7 100644 --- a/src/com/loopj/android/http/JsonHttpResponseHandler.java +++ b/src/com/loopj/android/http/JsonHttpResponseHandler.java @@ -23,7 +23,7 @@ import org.json.JSONException; import org.json.JSONObject; import org.json.JSONTokener; - +import org.apache.http.Header; import android.os.Message; /** @@ -62,6 +62,18 @@ public void onSuccess(JSONObject response) {} */ public void onSuccess(JSONArray response) {} + /** + * Fired when a request returns successfully and contains a json object + * at the base of the response string. Override to handle in your + * own code. + * @param statusCode the status code of the response + * @param headers the headers of the HTTP response + * @param response the parsed json object found in the server response (if any) + */ + public void onSuccess(int statusCode, Header[] headers, JSONObject response) { + onSuccess(statusCode, response); + } + /** * Fired when a request returns successfully and contains a json object * at the base of the response string. Override to handle in your @@ -73,6 +85,17 @@ public void onSuccess(int statusCode, JSONObject response) { onSuccess(response); } + /** + * Fired when a request returns successfully and contains a json array + * at the base of the response string. Override to handle in your + * own code. + * @param statusCode the status code of the response + * @param headers the headers of the HTTP response + * @param response the parsed json array found in the server response (if any) + */ + public void onSuccess(int statusCode, Header[] headers, JSONArray response) { + onSuccess(statusCode, response); + } /** * Fired when a request returns successfully and contains a json array @@ -81,7 +104,7 @@ public void onSuccess(int statusCode, JSONObject response) { * @param statusCode the status code of the response * @param response the parsed json array found in the server response (if any) */ - public void onSuccess(int statusCode, JSONArray response) { + public void onSuccess(int statusCode, JSONArray response) { onSuccess(response); } @@ -94,16 +117,16 @@ public void onFailure(Throwable e, JSONArray errorResponse) {} // @Override - protected void sendSuccessMessage(int statusCode, String responseBody) { - if (statusCode != HttpStatus.SC_NO_CONTENT){ - try { - Object jsonResponse = parseResponse(responseBody); - sendMessage(obtainMessage(SUCCESS_JSON_MESSAGE, new Object[]{statusCode, jsonResponse})); - } catch(JSONException e) { - sendFailureMessage(e, responseBody); - } - }else{ - sendMessage(obtainMessage(SUCCESS_JSON_MESSAGE, new Object[]{statusCode, new JSONObject()})); + protected void sendSuccessMessage(int statusCode, Header[] headers, String responseBody) { + if (statusCode != HttpStatus.SC_NO_CONTENT){ + try { + Object jsonResponse = parseResponse(responseBody); + sendMessage(obtainMessage(SUCCESS_JSON_MESSAGE, new Object[]{statusCode, headers, jsonResponse})); + } catch(JSONException e) { + sendFailureMessage(e, responseBody); + } + } else { + sendMessage(obtainMessage(SUCCESS_JSON_MESSAGE, new Object[]{statusCode, new JSONObject()})); } } @@ -117,18 +140,18 @@ protected void handleMessage(Message msg) { switch(msg.what){ case SUCCESS_JSON_MESSAGE: Object[] response = (Object[]) msg.obj; - handleSuccessJsonMessage(((Integer) response[0]).intValue(), response[1]); + handleSuccessJsonMessage(((Integer) response[0]).intValue(),(Header[]) response[1] ,response[2]); break; default: super.handleMessage(msg); } } - protected void handleSuccessJsonMessage(int statusCode, Object jsonResponse) { + protected void handleSuccessJsonMessage(int statusCode,Header[] headers, Object jsonResponse) { if(jsonResponse instanceof JSONObject) { - onSuccess(statusCode, (JSONObject)jsonResponse); + onSuccess(statusCode, headers, (JSONObject)jsonResponse); } else if(jsonResponse instanceof JSONArray) { - onSuccess(statusCode, (JSONArray)jsonResponse); + onSuccess(statusCode, headers, (JSONArray)jsonResponse); } else { onFailure(new JSONException("Unexpected type " + jsonResponse.getClass().getName()), (JSONObject)null); } From 0399a072509bf420a0276e4727ab5e8ee58095fe Mon Sep 17 00:00:00 2001 From: David Wen Date: Mon, 4 Mar 2013 16:31:51 -0500 Subject: [PATCH 027/613] Fix creating entity with dupe and file params A boundary will not be written after the last file params. If there are additional dupe params, they do not get added to the entity properly. Switching the order and adding the file params last fixes this issue. --- src/com/loopj/android/http/RequestParams.java | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/com/loopj/android/http/RequestParams.java b/src/com/loopj/android/http/RequestParams.java index cca963a1f..cf5d1dc44 100644 --- a/src/com/loopj/android/http/RequestParams.java +++ b/src/com/loopj/android/http/RequestParams.java @@ -236,6 +236,14 @@ public HttpEntity getEntity() { multipartEntity.addPart(entry.getKey(), entry.getValue()); } + // Add dupe params + for(ConcurrentHashMap.Entry> entry : urlParamsWithArray.entrySet()) { + ArrayList values = entry.getValue(); + for (String value : values) { + multipartEntity.addPart(entry.getKey(), value); + } + } + // Add file params int currentIndex = 0; int lastIndex = fileParams.entrySet().size() - 1; @@ -252,14 +260,6 @@ public HttpEntity getEntity() { currentIndex++; } - // Add dupe params - for(ConcurrentHashMap.Entry> entry : urlParamsWithArray.entrySet()) { - ArrayList values = entry.getValue(); - for (String value : values) { - multipartEntity.addPart(entry.getKey(), value); - } - } - entity = multipartEntity; } else { try { @@ -318,4 +318,4 @@ public String getFileName() { } } } -} \ No newline at end of file +} From d5a99cb69bd6d30bcbea5b15a74926d7be8ec513 Mon Sep 17 00:00:00 2001 From: Surik Sayadyan Date: Mon, 11 Mar 2013 16:33:51 +0200 Subject: [PATCH 028/613] catch exceptions if thread is interrupted --- .../loopj/android/http/AsyncHttpRequest.java | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/com/loopj/android/http/AsyncHttpRequest.java b/src/com/loopj/android/http/AsyncHttpRequest.java index 0bfbccf76..9f4b19dc2 100644 --- a/src/com/loopj/android/http/AsyncHttpRequest.java +++ b/src/com/loopj/android/http/AsyncHttpRequest.java @@ -73,14 +73,20 @@ public void run() { private void makeRequest() throws IOException { if(!Thread.currentThread().isInterrupted()) { - HttpResponse response = client.execute(request, context); - if(!Thread.currentThread().isInterrupted()) { - if(responseHandler != null) { - responseHandler.sendResponseMessage(response); - } - } else{ - //TODO: should raise InterruptedException? this block is reached whenever the request is cancelled before its response is received - } + try { + HttpResponse response = client.execute(request, context); + if(!Thread.currentThread().isInterrupted()) { + if(responseHandler != null) { + responseHandler.sendResponseMessage(response); + } + } else{ + //TODO: should raise InterruptedException? this block is reached whenever the request is cancelled before its response is received + } + } catch (IOException e) { + if(!Thread.currentThread().isInterrupted()) { + throw e; + } + } } } From 457df9c467db22a48800c9202aa6d2c13b8a3526 Mon Sep 17 00:00:00 2001 From: Baron Hall Date: Wed, 13 Mar 2013 13:40:05 -0500 Subject: [PATCH 029/613] FIX #190 Change the access modifiers to protected for the result field and responseHandler field --- src/com/loopj/android/http/SyncHttpClient.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/com/loopj/android/http/SyncHttpClient.java b/src/com/loopj/android/http/SyncHttpClient.java index 8f55991ef..84aeb6e5d 100644 --- a/src/com/loopj/android/http/SyncHttpClient.java +++ b/src/com/loopj/android/http/SyncHttpClient.java @@ -14,8 +14,8 @@ public abstract class SyncHttpClient extends AsyncHttpClient { * the result back to this method. Therefore the result object has to be a * field to be accessible */ - private String result; - AsyncHttpResponseHandler responseHandler = new AsyncHttpResponseHandler() { + protected String result; + protected AsyncHttpResponseHandler responseHandler = new AsyncHttpResponseHandler() { void sendResponseMessage(org.apache.http.HttpResponse response) { responseCode = response.getStatusLine().getStatusCode(); From fb6245d6cca5fbfc2f6536ca265e825957fff15a Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Fri, 22 Mar 2013 16:34:11 +0100 Subject: [PATCH 030/613] Updated version number to 1.4.3 --- AndroidManifest.xml | 2 +- src/com/loopj/android/http/AsyncHttpClient.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/AndroidManifest.xml b/AndroidManifest.xml index fbeab8b4d..5684532ce 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -1,7 +1,7 @@ diff --git a/src/com/loopj/android/http/AsyncHttpClient.java b/src/com/loopj/android/http/AsyncHttpClient.java index 6053a5693..ae17c41f5 100644 --- a/src/com/loopj/android/http/AsyncHttpClient.java +++ b/src/com/loopj/android/http/AsyncHttpClient.java @@ -90,7 +90,7 @@ * */ public class AsyncHttpClient { - private static final String VERSION = "1.4.1"; + private static final String VERSION = "1.4.3"; private static final int DEFAULT_MAX_CONNECTIONS = 10; private static final int DEFAULT_SOCKET_TIMEOUT = 10 * 1000; From 8829b08e92d27e6ac7d845cc36dc8a114ad6b70d Mon Sep 17 00:00:00 2001 From: lucamac Date: Wed, 27 Mar 2013 19:39:29 +0100 Subject: [PATCH 031/613] FAILURE_MESSAGE handling in handleMessage(), correctly handling response[1] as String --- .classpath | 17 +++++++++-------- .../android/http/BinaryHttpResponseHandler.java | 2 +- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/.classpath b/.classpath index 14fcf527f..6c6163d3b 100644 --- a/.classpath +++ b/.classpath @@ -1,8 +1,9 @@ - - - - - - - - + + + + + + + + + diff --git a/src/com/loopj/android/http/BinaryHttpResponseHandler.java b/src/com/loopj/android/http/BinaryHttpResponseHandler.java index aff1c631e..669bf615b 100644 --- a/src/com/loopj/android/http/BinaryHttpResponseHandler.java +++ b/src/com/loopj/android/http/BinaryHttpResponseHandler.java @@ -142,7 +142,7 @@ protected void handleMessage(Message msg) { break; case FAILURE_MESSAGE: response = (Object[])msg.obj; - handleFailureMessage((Throwable)response[0], (byte[])response[1]); + handleFailureMessage((Throwable)response[0], response[1].toString()); break; default: super.handleMessage(msg); From 569259ff0c93d904a8b0be1cd0f8cd97d1db47c3 Mon Sep 17 00:00:00 2001 From: Jeff Brateman Date: Wed, 27 Mar 2013 16:11:14 -0500 Subject: [PATCH 032/613] Add @Override tags to missing locations --- src/com/loopj/android/http/AsyncHttpClient.java | 4 +++- src/com/loopj/android/http/AsyncHttpRequest.java | 1 + .../loopj/android/http/AsyncHttpResponseHandler.java | 11 +++++++---- .../loopj/android/http/BinaryHttpResponseHandler.java | 9 +++++++-- src/com/loopj/android/http/RetryHandler.java | 1 + src/com/loopj/android/http/SyncHttpClient.java | 6 ++++-- 6 files changed, 23 insertions(+), 9 deletions(-) diff --git a/src/com/loopj/android/http/AsyncHttpClient.java b/src/com/loopj/android/http/AsyncHttpClient.java index ae17c41f5..5f0750d7f 100644 --- a/src/com/loopj/android/http/AsyncHttpClient.java +++ b/src/com/loopj/android/http/AsyncHttpClient.java @@ -60,8 +60,8 @@ 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.HttpParams; import org.apache.http.params.HttpConnectionParams; +import org.apache.http.params.HttpParams; import org.apache.http.params.HttpProtocolParams; import org.apache.http.protocol.BasicHttpContext; import org.apache.http.protocol.HttpContext; @@ -135,6 +135,7 @@ public AsyncHttpClient() { httpContext = new SyncBasicHttpContext(new BasicHttpContext()); httpClient = new DefaultHttpClient(cm, httpParams); httpClient.addRequestInterceptor(new HttpRequestInterceptor() { + @Override public void process(HttpRequest request, HttpContext context) { if (!request.containsHeader(HEADER_ACCEPT_ENCODING)) { request.addHeader(HEADER_ACCEPT_ENCODING, ENCODING_GZIP); @@ -146,6 +147,7 @@ public void process(HttpRequest request, HttpContext context) { }); httpClient.addResponseInterceptor(new HttpResponseInterceptor() { + @Override public void process(HttpResponse response, HttpContext context) { final HttpEntity entity = response.getEntity(); if (entity == null) { diff --git a/src/com/loopj/android/http/AsyncHttpRequest.java b/src/com/loopj/android/http/AsyncHttpRequest.java index 9f4b19dc2..48e6807e7 100644 --- a/src/com/loopj/android/http/AsyncHttpRequest.java +++ b/src/com/loopj/android/http/AsyncHttpRequest.java @@ -48,6 +48,7 @@ public AsyncHttpRequest(AbstractHttpClient client, HttpContext context, HttpUriR } } + @Override public void run() { try { if(responseHandler != null){ diff --git a/src/com/loopj/android/http/AsyncHttpResponseHandler.java b/src/com/loopj/android/http/AsyncHttpResponseHandler.java index 86fbdec98..d2db39500 100644 --- a/src/com/loopj/android/http/AsyncHttpResponseHandler.java +++ b/src/com/loopj/android/http/AsyncHttpResponseHandler.java @@ -18,9 +18,8 @@ package com.loopj.android.http; -import android.os.Handler; -import android.os.Looper; -import android.os.Message; +import java.io.IOException; + import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.StatusLine; @@ -28,7 +27,9 @@ import org.apache.http.entity.BufferedHttpEntity; import org.apache.http.util.EntityUtils; -import java.io.IOException; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; /** * Used to intercept and handle the responses from requests made using @@ -80,6 +81,7 @@ public AsyncHttpResponseHandler() { // Set up a handler to post events back to the correct thread if possible if(Looper.myLooper() != null) { handler = new Handler(){ + @Override public void handleMessage(Message msg){ AsyncHttpResponseHandler.this.handleMessage(msg); } @@ -122,6 +124,7 @@ public void onSuccess(int statusCode, String content) { * @param error the underlying cause of the failure * @deprecated use {@link #onFailure(Throwable, String)} */ + @Deprecated public void onFailure(Throwable error) {} /** diff --git a/src/com/loopj/android/http/BinaryHttpResponseHandler.java b/src/com/loopj/android/http/BinaryHttpResponseHandler.java index aff1c631e..30e855937 100644 --- a/src/com/loopj/android/http/BinaryHttpResponseHandler.java +++ b/src/com/loopj/android/http/BinaryHttpResponseHandler.java @@ -18,7 +18,8 @@ package com.loopj.android.http; -import android.os.Message; +import java.io.IOException; + import org.apache.http.Header; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; @@ -27,7 +28,7 @@ import org.apache.http.entity.BufferedHttpEntity; import org.apache.http.util.EntityUtils; -import java.io.IOException; +import android.os.Message; /** * Used to intercept and handle the responses from requests made using @@ -102,6 +103,7 @@ public void onSuccess(int statusCode, byte[] binaryData) { * @param binaryData the response body, if any * @deprecated */ + @Deprecated public void onFailure(Throwable error, byte[] binaryData) { // By default, call the deprecated onFailure(Throwable) for compatibility onFailure(error); @@ -116,6 +118,7 @@ protected void sendSuccessMessage(int statusCode, byte[] responseBody) { sendMessage(obtainMessage(SUCCESS_MESSAGE, new Object[]{statusCode, responseBody})); } + @Override protected void sendFailureMessage(Throwable e, byte[] responseBody) { sendMessage(obtainMessage(FAILURE_MESSAGE, new Object[]{e, responseBody})); } @@ -133,6 +136,7 @@ protected void handleFailureMessage(Throwable e, byte[] responseBody) { } // Methods which emulate android's Handler and Message methods + @Override protected void handleMessage(Message msg) { Object[] response; switch(msg.what) { @@ -151,6 +155,7 @@ protected void handleMessage(Message msg) { } // Interface to AsyncHttpRequest + @Override void sendResponseMessage(HttpResponse response) { StatusLine status = response.getStatusLine(); Header[] contentTypeHeaders = response.getHeaders("Content-Type"); diff --git a/src/com/loopj/android/http/RetryHandler.java b/src/com/loopj/android/http/RetryHandler.java index 715456fb2..5256aad21 100644 --- a/src/com/loopj/android/http/RetryHandler.java +++ b/src/com/loopj/android/http/RetryHandler.java @@ -65,6 +65,7 @@ public RetryHandler(int maxRetries) { this.maxRetries = maxRetries; } + @Override public boolean retryRequest(IOException exception, int executionCount, HttpContext context) { boolean retry = true; diff --git a/src/com/loopj/android/http/SyncHttpClient.java b/src/com/loopj/android/http/SyncHttpClient.java index 84aeb6e5d..1fd4441f3 100644 --- a/src/com/loopj/android/http/SyncHttpClient.java +++ b/src/com/loopj/android/http/SyncHttpClient.java @@ -17,7 +17,8 @@ public abstract class SyncHttpClient extends AsyncHttpClient { protected String result; protected AsyncHttpResponseHandler responseHandler = new AsyncHttpResponseHandler() { - void sendResponseMessage(org.apache.http.HttpResponse response) { + @Override + void sendResponseMessage(org.apache.http.HttpResponse response) { responseCode = response.getStatusLine().getStatusCode(); super.sendResponseMessage(response); }; @@ -51,7 +52,8 @@ public int getResponseCode() { } // Private stuff - protected void sendRequest(DefaultHttpClient client, + @Override + protected void sendRequest(DefaultHttpClient client, HttpContext httpContext, HttpUriRequest uriRequest, String contentType, AsyncHttpResponseHandler responseHandler, Context context) { From 948e25cfc7a1531801fb473d912e7914c62e2ae8 Mon Sep 17 00:00:00 2001 From: lucamac Date: Wed, 27 Mar 2013 23:46:33 +0100 Subject: [PATCH 033/613] Revert to original classpath commited by mistake --- .classpath | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/.classpath b/.classpath index 6c6163d3b..14fcf527f 100644 --- a/.classpath +++ b/.classpath @@ -1,9 +1,8 @@ - - - - - - - - - + + + + + + + + From 33f64e61373492aee54503b3ef91c374ec4d7597 Mon Sep 17 00:00:00 2001 From: Peter Edwards Date: Thu, 28 Mar 2013 15:02:10 +0200 Subject: [PATCH 034/613] change retry handler to call appropriate method --- src/com/loopj/android/http/AsyncHttpResponseHandler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/com/loopj/android/http/AsyncHttpResponseHandler.java b/src/com/loopj/android/http/AsyncHttpResponseHandler.java index d6a400e23..15d5826a7 100644 --- a/src/com/loopj/android/http/AsyncHttpResponseHandler.java +++ b/src/com/loopj/android/http/AsyncHttpResponseHandler.java @@ -235,7 +235,7 @@ protected void handleMessage(Message msg) { onProgress(((Integer) response[0]).intValue(), ((Integer) response[1]).intValue()); break; case RETRY_MESSAGE: - onRetry(); + handleRetryMessage(); break; } } From 7d42f31924266672c1d661ce6cd07334e6eb85e1 Mon Sep 17 00:00:00 2001 From: Kevin Schultz Date: Thu, 28 Feb 2013 11:50:22 -0500 Subject: [PATCH 035/613] switch binary response handler content type filter to a regex for more flexibility --- src/com/loopj/android/http/BinaryHttpResponseHandler.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/com/loopj/android/http/BinaryHttpResponseHandler.java b/src/com/loopj/android/http/BinaryHttpResponseHandler.java index c6f3dfe15..7d675a593 100644 --- a/src/com/loopj/android/http/BinaryHttpResponseHandler.java +++ b/src/com/loopj/android/http/BinaryHttpResponseHandler.java @@ -19,6 +19,7 @@ package com.loopj.android.http; import java.io.IOException; +import java.util.regex.Pattern; import org.apache.http.Header; import org.apache.http.HttpEntity; @@ -168,7 +169,7 @@ void sendResponseMessage(HttpResponse response) { Header contentTypeHeader = contentTypeHeaders[0]; boolean foundAllowedContentType = false; for(String anAllowedContentType : mAllowedContentTypes) { - if(anAllowedContentType.equals(contentTypeHeader.getValue())) { + if(Pattern.matches(anAllowedContentType, contentTypeHeader.getValue())) { foundAllowedContentType = true; } } From 2ef2b28ad3b061dda7980ffc3a9e030712aefccc Mon Sep 17 00:00:00 2001 From: James Smith Date: Mon, 22 Apr 2013 08:05:11 -0700 Subject: [PATCH 036/613] Ensure cookies are deleted from prefs fixes #214 --- src/com/loopj/android/http/PersistentCookieStore.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/com/loopj/android/http/PersistentCookieStore.java b/src/com/loopj/android/http/PersistentCookieStore.java index 3f98a00bc..11b2a2bf1 100644 --- a/src/com/loopj/android/http/PersistentCookieStore.java +++ b/src/com/loopj/android/http/PersistentCookieStore.java @@ -98,9 +98,6 @@ public void addCookie(Cookie cookie) { @Override public void clear() { - // Clear cookies from local store - cookies.clear(); - // Clear cookies from persistent store SharedPreferences.Editor prefsWriter = cookiePrefs.edit(); for(String name : cookies.keySet()) { @@ -108,6 +105,9 @@ public void clear() { } prefsWriter.remove(COOKIE_NAME_STORE); prefsWriter.commit(); + + // Clear cookies from local store + cookies.clear(); } @Override From a2d142d7e648fb5d0f8bb391525135ab8e246aee Mon Sep 17 00:00:00 2001 From: kimiyash Date: Tue, 7 May 2013 21:14:46 +0900 Subject: [PATCH 037/613] fixed bug for same name parameter --- src/com/loopj/android/http/RequestParams.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/com/loopj/android/http/RequestParams.java b/src/com/loopj/android/http/RequestParams.java index cf5d1dc44..cbb0c2886 100644 --- a/src/com/loopj/android/http/RequestParams.java +++ b/src/com/loopj/android/http/RequestParams.java @@ -210,12 +210,12 @@ public String toString() { result.append("&"); ArrayList values = entry.getValue(); - for (String value : values) { - if (values.indexOf(value) != 0) + for (int i = 0; i < values.size(); i++) { + if (i != 0) result.append("&"); result.append(entry.getKey()); result.append("="); - result.append(value); + result.append(values.get(i)); } } From 078321ab5c16807ffadbd00072333f76ff8a1d56 Mon Sep 17 00:00:00 2001 From: coffeesherk Date: Fri, 10 May 2013 10:02:50 +0800 Subject: [PATCH 038/613] fix bug of post 'multipart/form-data' request without file data --- .../android/http/SimpleMultipartEntity.java | 31 +++++++++---------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/src/com/loopj/android/http/SimpleMultipartEntity.java b/src/com/loopj/android/http/SimpleMultipartEntity.java index bff0efcc0..b66557759 100644 --- a/src/com/loopj/android/http/SimpleMultipartEntity.java +++ b/src/com/loopj/android/http/SimpleMultipartEntity.java @@ -58,36 +58,36 @@ public SimpleMultipartEntity() { public void writeFirstBoundaryIfNeeds(){ if(!isSetFirst){ - try { - out.write(("--" + boundary + "\r\n").getBytes()); - } catch (final IOException e) { - e.printStackTrace(); - } + writeBoundary(); } isSetFirst = true; } - public void writeLastBoundaryIfNeeds() { - if(isSetLast){ - return; - } - + public void writeBoundary() { try { - out.write(("\r\n--" + boundary + "--\r\n").getBytes()); + out.write(("--" + boundary + "\r\n").getBytes()); } catch (final IOException e) { e.printStackTrace(); } + } + + public void writeLastBoundaryIfNeeds() { + if(isSetLast){ + return; + } + writeBoundary(); + isSetLast = true; } public void addPart(final String key, final String value) { - writeFirstBoundaryIfNeeds(); + writeBoundary(); try { out.write(("Content-Disposition: form-data; name=\"" +key+"\"\r\n\r\n").getBytes()); out.write(value.getBytes()); - out.write(("\r\n--" + boundary + "\r\n").getBytes()); + out.write(("\r\n").getBytes()); } catch (final IOException e) { e.printStackTrace(); } @@ -98,7 +98,7 @@ public void addPart(final String key, final String fileName, final InputStream f } public void addPart(final String key, final String fileName, final InputStream fin, String type, final boolean isLast){ - writeFirstBoundaryIfNeeds(); + writeBoundary(); try { type = "Content-Type: "+type+"\r\n"; out.write(("Content-Disposition: form-data; name=\""+ key+"\"; filename=\"" + fileName + "\"\r\n").getBytes()); @@ -110,8 +110,7 @@ public void addPart(final String key, final String fileName, final InputStream f while ((l = fin.read(tmp)) != -1) { out.write(tmp, 0, l); } - if(!isLast) - out.write(("\r\n--" + boundary + "\r\n").getBytes()); + out.write(("\r\n").getBytes()); out.flush(); } catch (final IOException e) { e.printStackTrace(); From ced6d5293aeacecdccf3bae06ad2136ebd1798a2 Mon Sep 17 00:00:00 2001 From: coffeesherk Date: Tue, 14 May 2013 09:51:54 +0800 Subject: [PATCH 039/613] add content-type parameter for char-stream data in multipart/form-data add content-type parameter for char-stream data in multipart/form-data & fix bug of writing wrong last boundary --- .../android/http/SimpleMultipartEntity.java | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/src/com/loopj/android/http/SimpleMultipartEntity.java b/src/com/loopj/android/http/SimpleMultipartEntity.java index b66557759..3221ec1eb 100644 --- a/src/com/loopj/android/http/SimpleMultipartEntity.java +++ b/src/com/loopj/android/http/SimpleMultipartEntity.java @@ -77,15 +77,21 @@ public void writeLastBoundaryIfNeeds() { return; } - writeBoundary(); + try { + out.write(("--" + boundary + "--\r\n").getBytes()); + out.flush(); + } catch (final IOException e) { + e.printStackTrace(); + } isSetLast = true; } - public void addPart(final String key, final String value) { + public void addPart(final String key, final String value, final String contentType) { writeBoundary(); try { - out.write(("Content-Disposition: form-data; name=\"" +key+"\"\r\n\r\n").getBytes()); + out.write(("Content-Disposition: form-data; name=\"" +key+"\"\r\n").getBytes()); + out.write(("Content-Type: " + contentType + "\r\n\r\n").getBytes()); out.write(value.getBytes()); out.write(("\r\n").getBytes()); } catch (final IOException e) { @@ -93,6 +99,10 @@ public void addPart(final String key, final String value) { } } + public void addPart(final String key, final String value) { + addPart(key,value,"text/plain; charset=UTF-8"); + } + public void addPart(final String key, final String fileName, final InputStream fin, final boolean isLast){ addPart(key, fileName, fin, "application/octet-stream", isLast); } @@ -111,7 +121,7 @@ public void addPart(final String key, final String fileName, final InputStream f out.write(tmp, 0, l); } out.write(("\r\n").getBytes()); - out.flush(); + } catch (final IOException e) { e.printStackTrace(); } finally { @@ -159,6 +169,7 @@ public boolean isStreaming() { @Override public void writeTo(final OutputStream outstream) throws IOException { + writeLastBoundaryIfNeeds(); outstream.write(out.toByteArray()); } @@ -179,6 +190,7 @@ public void consumeContent() throws IOException, @Override public InputStream getContent() throws IOException, UnsupportedOperationException { + writeLastBoundaryIfNeeds(); return new ByteArrayInputStream(out.toByteArray()); } } \ No newline at end of file From 96ceca32d185af134de8d4278f3bc1813b0cd4c8 Mon Sep 17 00:00:00 2001 From: Clay Allsopp Date: Tue, 30 Jul 2013 18:16:39 -0700 Subject: [PATCH 040/613] Add RequestParams#add This is a shortcut for adding a parameter which can have many values; useful for communicating for something like Rails, where array parameters are non-unique. --- src/com/loopj/android/http/RequestParams.java | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/com/loopj/android/http/RequestParams.java b/src/com/loopj/android/http/RequestParams.java index cca963a1f..d8c5849b3 100644 --- a/src/com/loopj/android/http/RequestParams.java +++ b/src/com/loopj/android/http/RequestParams.java @@ -142,6 +142,22 @@ public void put(String key, ArrayList values) { } } + /** + * Adds value to param which can have more than one value. + * @param key the key name for the param, either existing or new. + * @param value + */ + public void add(String key, String value) { + if(key != null && value != null) { + ArrayList paramArray = urlParamsWithArray.get(key); + if (paramArray == null) { + paramArray = new ArrayList(); + urlParamsWithArray.put(key, paramArray); + } + paramArray.add(value); + } + } + /** * Adds an input stream to the request. * @param key the key name for the new param. From 5f0063a0ddc602a8008a7cdca7edd9fe83730429 Mon Sep 17 00:00:00 2001 From: Clay Allsopp Date: Tue, 30 Jul 2013 18:21:39 -0700 Subject: [PATCH 041/613] DRY --- src/com/loopj/android/http/RequestParams.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/com/loopj/android/http/RequestParams.java b/src/com/loopj/android/http/RequestParams.java index d8c5849b3..08adf3b71 100644 --- a/src/com/loopj/android/http/RequestParams.java +++ b/src/com/loopj/android/http/RequestParams.java @@ -152,7 +152,7 @@ public void add(String key, String value) { ArrayList paramArray = urlParamsWithArray.get(key); if (paramArray == null) { paramArray = new ArrayList(); - urlParamsWithArray.put(key, paramArray); + this.put(key, paramArray); } paramArray.add(value); } From d952527ccb5cbbf8245d28e4658794f8864a28f7 Mon Sep 17 00:00:00 2001 From: Clay Allsopp Date: Tue, 30 Jul 2013 18:24:25 -0700 Subject: [PATCH 042/613] doc --- src/com/loopj/android/http/RequestParams.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/com/loopj/android/http/RequestParams.java b/src/com/loopj/android/http/RequestParams.java index 08adf3b71..eb67b1275 100644 --- a/src/com/loopj/android/http/RequestParams.java +++ b/src/com/loopj/android/http/RequestParams.java @@ -145,7 +145,7 @@ public void put(String key, ArrayList values) { /** * Adds value to param which can have more than one value. * @param key the key name for the param, either existing or new. - * @param value + * @param value the value string for the new param. */ public void add(String key, String value) { if(key != null && value != null) { From 7d10a6341f87db65d636436aedb19f686caa7de8 Mon Sep 17 00:00:00 2001 From: Jeremy Logan Date: Thu, 8 Aug 2013 23:46:06 -0500 Subject: [PATCH 043/613] The failure message should have the response in a byte array. handleSuccessMessage() was being (properly) handed a byte array, but handleFailureMessage() was being passed a string. This adjusts the call to handleFailureMessage() to pass a byte array as well. --- src/com/loopj/android/http/BinaryHttpResponseHandler.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/com/loopj/android/http/BinaryHttpResponseHandler.java b/src/com/loopj/android/http/BinaryHttpResponseHandler.java index 7d675a593..6ccaab0ce 100644 --- a/src/com/loopj/android/http/BinaryHttpResponseHandler.java +++ b/src/com/loopj/android/http/BinaryHttpResponseHandler.java @@ -147,7 +147,7 @@ protected void handleMessage(Message msg) { break; case FAILURE_MESSAGE: response = (Object[])msg.obj; - handleFailureMessage((Throwable)response[0], response[1].toString()); + handleFailureMessage((Throwable)response[0], (byte[]) response[1]); break; default: super.handleMessage(msg); @@ -195,4 +195,4 @@ void sendResponseMessage(HttpResponse response) { sendSuccessMessage(status.getStatusCode(), responseBody); } } -} \ No newline at end of file +} From 506644a6abfd9b4f94d250e043d4fbb700f2eec3 Mon Sep 17 00:00:00 2001 From: Che-Bin Liu Date: Wed, 2 Oct 2013 14:26:31 -0700 Subject: [PATCH 044/613] Allow request params of nested map, list, and set --- src/com/loopj/android/http/RequestParams.java | 135 +++++++++++++----- 1 file changed, 98 insertions(+), 37 deletions(-) diff --git a/src/com/loopj/android/http/RequestParams.java b/src/com/loopj/android/http/RequestParams.java index 758fa9842..cf69f1ea9 100644 --- a/src/com/loopj/android/http/RequestParams.java +++ b/src/com/loopj/android/http/RequestParams.java @@ -24,9 +24,12 @@ import java.io.FileNotFoundException; import java.io.UnsupportedEncodingException; import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import org.apache.http.HttpEntity; @@ -48,6 +51,35 @@ * params.put("profile_picture", new File("pic.jpg")); // Upload a File * params.put("profile_picture2", someInputStream); // Upload an InputStream * params.put("profile_picture3", new ByteArrayInputStream(someBytes)); // Upload some bytes + * + * Map map = new HashMap(); + * map.put("first_name", "James"); + * map.put("last_name", "Smith"); + * params.put("user", map); // url params: "user[first_name]=James&user[last_name]=Smith" + * + * Set set = new HashSet(); // unordered collection + * set.add("music"); + * set.add("art"); + * params.put("like", set); // url params: "like=music&like=art" + * + * List list = new ArrayList(); // Ordered collection + * list.add("Java"); + * list.add("C"); + * params.put("languages", list); // url params: "languages[]=Java&languages[]=C" + * + * String[] colors = { "blue", "yellow" }; // Ordered collection + * params.put("colors", colors); // url params: "colors[]=blue&colors[]=yellow" + * + * List> listOfMaps = new ArrayList>(); + * Map user1 = new HashMap(); + * user1.put("age", "30"); + * user1.put("gender", "male"); + * Map user2 = new HashMap(); + * user2.put("age", "25"); + * user2.put("gender", "female"); + * listOfMaps.add(user1); + * listOfMaps.add(user2); + * params.put("users", listOfMaps); // url params: "users[][age]=30&users[][gender]=male&users[][age]=25&users[][gender]=female" * * AsyncHttpClient client = new AsyncHttpClient(); * client.post("http://myendpoint.com/", params, responseHandler); @@ -58,7 +90,7 @@ public class RequestParams { protected ConcurrentHashMap urlParams; protected ConcurrentHashMap fileParams; - protected ConcurrentHashMap> urlParamsWithArray; + protected ConcurrentHashMap urlParamsWithObjects; /** * Constructs a new empty RequestParams instance. @@ -132,29 +164,34 @@ public void put(String key, File file) throws FileNotFoundException { } /** - * Adds param with more than one value. + * Adds param with non-string value (e.g. Map, List, Set). * @param key the key name for the new param. - * @param values is the ArrayList with values for the param. + * @param value the value object for the new param. */ - public void put(String key, ArrayList values) { - if(key != null && values != null) { - urlParamsWithArray.put(key, values); + public void put(String key, Object value) { + if(key != null && value != null) { + urlParamsWithObjects.put(key, value); } } /** - * Adds value to param which can have more than one value. + * Adds string value to param which can have more than one value. * @param key the key name for the param, either existing or new. * @param value the value string for the new param. */ public void add(String key, String value) { if(key != null && value != null) { - ArrayList paramArray = urlParamsWithArray.get(key); - if (paramArray == null) { - paramArray = new ArrayList(); - this.put(key, paramArray); + Object params = urlParamsWithObjects.get(key); + if (params == null) { + // Backward compatible, which will result in "k=v1&k=v2&k=v3" + params = new HashSet(); + this.put(key, params); + } + if (params instanceof List) { + ((List) params).add(value); + } else if (params instanceof Set) { + ((Set) params).add(value); } - paramArray.add(value); } } @@ -197,7 +234,7 @@ public void put(String key, InputStream stream, String fileName, String contentT public void remove(String key){ urlParams.remove(key); fileParams.remove(key); - urlParamsWithArray.remove(key); + urlParamsWithObjects.remove(key); } @Override @@ -221,18 +258,14 @@ public String toString() { result.append("FILE"); } - for(ConcurrentHashMap.Entry> entry : urlParamsWithArray.entrySet()) { - if(result.length() > 0) + List params = getParamsList(null, urlParamsWithObjects); + for (BasicNameValuePair kv : params) { + if (result.length() > 0) result.append("&"); - - ArrayList values = entry.getValue(); - for (int i = 0; i < values.size(); i++) { - if (i != 0) - result.append("&"); - result.append(entry.getKey()); - result.append("="); - result.append(values.get(i)); - } + + result.append(kv.getName()); + result.append("="); + result.append(kv.getValue()); } return result.toString(); @@ -252,12 +285,10 @@ public HttpEntity getEntity() { multipartEntity.addPart(entry.getKey(), entry.getValue()); } - // Add dupe params - for(ConcurrentHashMap.Entry> entry : urlParamsWithArray.entrySet()) { - ArrayList values = entry.getValue(); - for (String value : values) { - multipartEntity.addPart(entry.getKey(), value); - } + // Add non-string params + List params = getParamsList(null, urlParamsWithObjects); + for (BasicNameValuePair kv : params) { + multipartEntity.addPart(kv.getName(), kv.getValue()); } // Add file params @@ -291,7 +322,7 @@ public HttpEntity getEntity() { private void init(){ urlParams = new ConcurrentHashMap(); fileParams = new ConcurrentHashMap(); - urlParamsWithArray = new ConcurrentHashMap>(); + urlParamsWithObjects = new ConcurrentHashMap(); } protected List getParamsList() { @@ -301,16 +332,46 @@ protected List getParamsList() { lparams.add(new BasicNameValuePair(entry.getKey(), entry.getValue())); } - for(ConcurrentHashMap.Entry> entry : urlParamsWithArray.entrySet()) { - ArrayList values = entry.getValue(); - for (String value : values) { - lparams.add(new BasicNameValuePair(entry.getKey(), value)); - } - } + lparams.addAll(getParamsList(null, urlParamsWithObjects)); return lparams; } + private List getParamsList(String key, Object value) { + List params = new LinkedList(); + if (value instanceof Map) { + Map map = (Map) value; + List list = new ArrayList(map.keySet()); + // Ensure consistent ordering in query string + Collections.sort(list); + for (String nestedKey : list) { + Object nestedValue = map.get(nestedKey); + if (nestedValue != null) { + params.addAll(getParamsList(key == null ? nestedKey : String.format("%s[%s]", key, nestedKey), + nestedValue)); + } + } + } else if (value instanceof List) { + List list = (List) value; + for (Object nestedValue : list) { + params.addAll(getParamsList(String.format("%s[]", key), nestedValue)); + } + } else if (value instanceof Object[]) { + Object[] array = (Object[]) value; + for (Object nestedValue : array) { + params.addAll(getParamsList(String.format("%s[]", key), nestedValue)); + } + } else if (value instanceof Set) { + Set set = (Set) value; + for (Object nestedValue : set) { + params.addAll(getParamsList(key, nestedValue)); + } + } else if (value instanceof String) { + params.add(new BasicNameValuePair(key, (String) value)); + } + return params; + } + protected String getParamString() { return URLEncodedUtils.format(getParamsList(), ENCODING); } From d0e74d14637a6b13d4491ae32f149925e43cf4f5 Mon Sep 17 00:00:00 2001 From: mareksebera Date: Wed, 9 Oct 2013 02:00:51 +0200 Subject: [PATCH 045/613] Removed ADT/Ant configuration files --- .classpath | 8 ----- .gitignore | 33 ++++++++++++++++-- .project | 33 ------------------ build.xml | 98 ------------------------------------------------------ 4 files changed, 31 insertions(+), 141 deletions(-) delete mode 100644 .classpath delete mode 100644 .project delete mode 100644 build.xml diff --git a/.classpath b/.classpath deleted file mode 100644 index 14fcf527f..000000000 --- a/.classpath +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/.gitignore b/.gitignore index 8842bb26b..d3b225642 100644 --- a/.gitignore +++ b/.gitignore @@ -1,11 +1,40 @@ +# Custom _site + +# Ant MANIFEST.MF ./*.jar build.num build + +# ADT +.classpath +.project +.settings local.properties -bin/ -gen/ +bin +gen _layouts +proguard.cfg + +# OSX .DS_Store + +# Github gh-pages + +# Gradle +.gradle +build + +# IDEA +*.iml +*.ipr +*.iws +out +.idea + +# Maven +target +release.properties +pom.xml.* diff --git a/.project b/.project deleted file mode 100644 index a97931397..000000000 --- a/.project +++ /dev/null @@ -1,33 +0,0 @@ - - - android-async-http - - - - - - com.android.ide.eclipse.adt.ResourceManagerBuilder - - - - - com.android.ide.eclipse.adt.PreCompilerBuilder - - - - - org.eclipse.jdt.core.javabuilder - - - - - com.android.ide.eclipse.adt.ApkBuilder - - - - - - com.android.ide.eclipse.adt.AndroidNature - org.eclipse.jdt.core.javanature - - diff --git a/build.xml b/build.xml deleted file mode 100644 index 1cd94b37b..000000000 --- a/build.xml +++ /dev/null @@ -1,98 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - From 160dcb9356a4d157c6b9973103147e9e57d90a79 Mon Sep 17 00:00:00 2001 From: mareksebera Date: Wed, 9 Oct 2013 02:01:38 +0200 Subject: [PATCH 046/613] Added Gradle build configuration files --- build.gradle | 22 ++++++++++++++++++++++ library/build.gradle | 18 ++++++++++++++++++ settings.gradle | 1 + 3 files changed, 41 insertions(+) create mode 100644 build.gradle create mode 100644 library/build.gradle create mode 100644 settings.gradle diff --git a/build.gradle b/build.gradle new file mode 100644 index 000000000..e14bd8b1d --- /dev/null +++ b/build.gradle @@ -0,0 +1,22 @@ +buildscript { + repositories { + mavenCentral() + } + + dependencies { + classpath 'com.android.tools.build:gradle:0.6.+' + } +} + +allprojects { + group = 'com.loopj.android' + version = '1.4.4-SNAPSHOT' + + repositories { + mavenCentral() + } + + tasks.withType(JavaCompile) { + options.encoding = "UTF-8" + } +} diff --git a/library/build.gradle b/library/build.gradle new file mode 100644 index 000000000..4f6054689 --- /dev/null +++ b/library/build.gradle @@ -0,0 +1,18 @@ +apply plugin: 'android-library' + +dependencies { + compile 'com.android.support:support-v4:18.0.+' +} + +android { + compileSdkVersion 18 + buildToolsVersion '18.0.1' + + sourceSets { + main { + manifest.srcFile 'AndroidManifest.xml' + java.srcDirs = ['src'] + res.srcDirs = ['res'] + } + } +} diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 000000000..e469038a7 --- /dev/null +++ b/settings.gradle @@ -0,0 +1 @@ +include 'library' From 6faa3026afe46100843828bb27f7855735ca6a0a Mon Sep 17 00:00:00 2001 From: mareksebera Date: Wed, 9 Oct 2013 02:02:32 +0200 Subject: [PATCH 047/613] Moved library to sub-folder, to ease adding related projects --- AndroidManifest.xml => library/AndroidManifest.xml | 10 ++++++---- .../src}/com/loopj/android/http/AsyncHttpClient.java | 0 .../com/loopj/android/http/AsyncHttpRequest.java | 0 .../loopj/android/http/AsyncHttpResponseHandler.java | 0 .../android/http/BinaryHttpResponseHandler.java | 0 .../loopj/android/http/JsonHttpResponseHandler.java | 0 .../loopj/android/http/PersistentCookieStore.java | 0 .../src}/com/loopj/android/http/RequestParams.java | 0 .../src}/com/loopj/android/http/RetryHandler.java | 0 .../com/loopj/android/http/SerializableCookie.java | 0 .../loopj/android/http/SimpleMultipartEntity.java | 0 .../src}/com/loopj/android/http/SyncHttpClient.java | 0 project.properties | 12 ------------ 13 files changed, 6 insertions(+), 16 deletions(-) rename AndroidManifest.xml => library/AndroidManifest.xml (69%) rename {src => library/src}/com/loopj/android/http/AsyncHttpClient.java (100%) rename {src => library/src}/com/loopj/android/http/AsyncHttpRequest.java (100%) rename {src => library/src}/com/loopj/android/http/AsyncHttpResponseHandler.java (100%) rename {src => library/src}/com/loopj/android/http/BinaryHttpResponseHandler.java (100%) rename {src => library/src}/com/loopj/android/http/JsonHttpResponseHandler.java (100%) rename {src => library/src}/com/loopj/android/http/PersistentCookieStore.java (100%) rename {src => library/src}/com/loopj/android/http/RequestParams.java (100%) rename {src => library/src}/com/loopj/android/http/RetryHandler.java (100%) rename {src => library/src}/com/loopj/android/http/SerializableCookie.java (100%) rename {src => library/src}/com/loopj/android/http/SimpleMultipartEntity.java (100%) rename {src => library/src}/com/loopj/android/http/SyncHttpClient.java (100%) delete mode 100644 project.properties diff --git a/AndroidManifest.xml b/library/AndroidManifest.xml similarity index 69% rename from AndroidManifest.xml rename to library/AndroidManifest.xml index 5684532ce..61a249f4f 100644 --- a/AndroidManifest.xml +++ b/library/AndroidManifest.xml @@ -1,11 +1,13 @@ + android:versionName="1.4.4" + android:versionCode="2"> + - + android:name="android_async_http" /> + + diff --git a/src/com/loopj/android/http/AsyncHttpClient.java b/library/src/com/loopj/android/http/AsyncHttpClient.java similarity index 100% rename from src/com/loopj/android/http/AsyncHttpClient.java rename to library/src/com/loopj/android/http/AsyncHttpClient.java diff --git a/src/com/loopj/android/http/AsyncHttpRequest.java b/library/src/com/loopj/android/http/AsyncHttpRequest.java similarity index 100% rename from src/com/loopj/android/http/AsyncHttpRequest.java rename to library/src/com/loopj/android/http/AsyncHttpRequest.java diff --git a/src/com/loopj/android/http/AsyncHttpResponseHandler.java b/library/src/com/loopj/android/http/AsyncHttpResponseHandler.java similarity index 100% rename from src/com/loopj/android/http/AsyncHttpResponseHandler.java rename to library/src/com/loopj/android/http/AsyncHttpResponseHandler.java diff --git a/src/com/loopj/android/http/BinaryHttpResponseHandler.java b/library/src/com/loopj/android/http/BinaryHttpResponseHandler.java similarity index 100% rename from src/com/loopj/android/http/BinaryHttpResponseHandler.java rename to library/src/com/loopj/android/http/BinaryHttpResponseHandler.java diff --git a/src/com/loopj/android/http/JsonHttpResponseHandler.java b/library/src/com/loopj/android/http/JsonHttpResponseHandler.java similarity index 100% rename from src/com/loopj/android/http/JsonHttpResponseHandler.java rename to library/src/com/loopj/android/http/JsonHttpResponseHandler.java diff --git a/src/com/loopj/android/http/PersistentCookieStore.java b/library/src/com/loopj/android/http/PersistentCookieStore.java similarity index 100% rename from src/com/loopj/android/http/PersistentCookieStore.java rename to library/src/com/loopj/android/http/PersistentCookieStore.java diff --git a/src/com/loopj/android/http/RequestParams.java b/library/src/com/loopj/android/http/RequestParams.java similarity index 100% rename from src/com/loopj/android/http/RequestParams.java rename to library/src/com/loopj/android/http/RequestParams.java diff --git a/src/com/loopj/android/http/RetryHandler.java b/library/src/com/loopj/android/http/RetryHandler.java similarity index 100% rename from src/com/loopj/android/http/RetryHandler.java rename to library/src/com/loopj/android/http/RetryHandler.java diff --git a/src/com/loopj/android/http/SerializableCookie.java b/library/src/com/loopj/android/http/SerializableCookie.java similarity index 100% rename from src/com/loopj/android/http/SerializableCookie.java rename to library/src/com/loopj/android/http/SerializableCookie.java diff --git a/src/com/loopj/android/http/SimpleMultipartEntity.java b/library/src/com/loopj/android/http/SimpleMultipartEntity.java similarity index 100% rename from src/com/loopj/android/http/SimpleMultipartEntity.java rename to library/src/com/loopj/android/http/SimpleMultipartEntity.java diff --git a/src/com/loopj/android/http/SyncHttpClient.java b/library/src/com/loopj/android/http/SyncHttpClient.java similarity index 100% rename from src/com/loopj/android/http/SyncHttpClient.java rename to library/src/com/loopj/android/http/SyncHttpClient.java diff --git a/project.properties b/project.properties deleted file mode 100644 index 1880987e2..000000000 --- a/project.properties +++ /dev/null @@ -1,12 +0,0 @@ -# This file is automatically generated by Android Tools. -# Do not modify this file -- YOUR CHANGES WILL BE ERASED! -# -# This file must be checked in Version Control Systems. -# -# To customize properties used by the Ant build system use, -# "ant.properties", and override values to adapt the script to your -# project structure. - -android.library=true -# Project target. -target=android-3 From a75b90e11b135146f635614723bc534773692fac Mon Sep 17 00:00:00 2001 From: mareksebera Date: Wed, 9 Oct 2013 02:04:26 +0200 Subject: [PATCH 048/613] Added Travis CI configuration file --- .travis.yml | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 000000000..20458a51b --- /dev/null +++ b/.travis.yml @@ -0,0 +1,41 @@ +branches: + only: + - master +language: + - java +jdk: + - openjdk6 +before_install: + # environment info + - mvn -version + - gradle -v + - uname -a + # required libs for android build tools + - if [ `uname -m` = x86_64 ]; then sudo apt-get update; fi + - if [ `uname -m` = x86_64 ]; then sudo apt-get install -qq --force-yes libgd2-xpm ia32-libs ia32-libs-multiarch; fi + # for gradle output style + - export TERM=dumb + # newer version of gradle + - wget http://services.gradle.org/distributions/gradle-1.8-bin.zip + - unzip -qq gradle-1.8-bin.zip + - export GRADLE_HOME=$PWD/gradle-1.8 + - export PATH=$GRADLE_HOME/bin:$PATH + # just to test gradle version, against our provided one + - gradle -v + # newest android SDK 22.0.5 + - wget http://dl.google.com/android/android-sdk_r22.0.5-linux.tgz + - tar -zxf android-sdk_r22.0.5-linux.tgz + - export ANDROID_HOME=`pwd`/android-sdk-linux + - export PATH=${PATH}:${ANDROID_HOME}/tools:${ANDROID_HOME}/platform-tools + # manually set sdk.dir variable, according to local paths + - echo "sdk.dir=$ANDROID_HOME" > local.properties + - echo yes | android update sdk -t tools,platform-tools,extra-android-support,extra-android-m2repository,android-18 --force --no-ui + # build tools cannot be installed through "android update sdk" as of now + - wget http://dl.google.com/android/repository/build-tools_r18.0.1-linux.zip + - mkdir -p $ANDROID_HOME/build-tools + - unzip -qq build-tools_r18.0.1-linux.zip -d $ANDROID_HOME/build-tools/ + - mv $ANDROID_HOME/build-tools/android-4.3 $ANDROID_HOME/build-tools/18.0.1 + # verify files exist in right paths + - find $ANDROID_HOME/build-tools + - file $ANDROID_HOME/build-tools/18.0.1/aapt + # - mvn clean test install From 6d5ac1c9cd4e0b06d97429493741b2fc87a5654c Mon Sep 17 00:00:00 2001 From: mareksebera Date: Wed, 9 Oct 2013 02:09:02 +0200 Subject: [PATCH 049/613] Lint issues fixed --- library/AndroidManifest.xml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/library/AndroidManifest.xml b/library/AndroidManifest.xml index 61a249f4f..dbe4807a3 100644 --- a/library/AndroidManifest.xml +++ b/library/AndroidManifest.xml @@ -4,10 +4,10 @@ android:versionName="1.4.4" android:versionCode="2"> - - - + + + + From a77e7e6eb9ec3a23eb05c894ed69f7567ff90279 Mon Sep 17 00:00:00 2001 From: mareksebera Date: Wed, 9 Oct 2013 02:16:59 +0200 Subject: [PATCH 050/613] Formatting --- build.gradle | 28 +- examples/ExampleUsage.java | 2 - examples/TwitterRestClient.java | 2 - examples/TwitterRestClientUsage.java | 7 +- library/AndroidManifest.xml | 4 +- library/build.gradle | 18 +- .../loopj/android/http/AsyncHttpClient.java | 251 ++++++++++-------- .../loopj/android/http/AsyncHttpRequest.java | 68 ++--- .../http/AsyncHttpResponseHandler.java | 75 +++--- .../http/BinaryHttpResponseHandler.java | 52 ++-- .../android/http/JsonHttpResponseHandler.java | 98 ++++--- .../android/http/PersistentCookieStore.java | 48 ++-- .../com/loopj/android/http/RequestParams.java | 119 +++++---- .../com/loopj/android/http/RetryHandler.java | 40 +-- .../android/http/SerializableCookie.java | 24 +- .../android/http/SimpleMultipartEntity.java | 40 +-- .../loopj/android/http/SyncHttpClient.java | 190 ++++++------- 17 files changed, 559 insertions(+), 507 deletions(-) diff --git a/build.gradle b/build.gradle index e14bd8b1d..6dbebcf59 100644 --- a/build.gradle +++ b/build.gradle @@ -1,22 +1,22 @@ buildscript { - repositories { - mavenCentral() - } + repositories { + mavenCentral() + } - dependencies { - classpath 'com.android.tools.build:gradle:0.6.+' - } + dependencies { + classpath 'com.android.tools.build:gradle:0.6.+' + } } allprojects { - group = 'com.loopj.android' - version = '1.4.4-SNAPSHOT' + group = 'com.loopj.android' + version = '1.4.4-SNAPSHOT' - repositories { - mavenCentral() - } + repositories { + mavenCentral() + } - tasks.withType(JavaCompile) { - options.encoding = "UTF-8" - } + tasks.withType(JavaCompile) { + options.encoding = "UTF-8" + } } diff --git a/examples/ExampleUsage.java b/examples/ExampleUsage.java index 2b7a4fa0a..b5c4fc6f7 100644 --- a/examples/ExampleUsage.java +++ b/examples/ExampleUsage.java @@ -1,5 +1,3 @@ -import com.loopj.android.http.*; - public class ExampleUsage { public static void makeRequest() { AsyncHttpClient client = new AsyncHttpClient(); diff --git a/examples/TwitterRestClient.java b/examples/TwitterRestClient.java index 387a87114..395273df0 100644 --- a/examples/TwitterRestClient.java +++ b/examples/TwitterRestClient.java @@ -1,7 +1,5 @@ // Static wrapper library around AsyncHttpClient -import com.loopj.android.http.*; - public class TwitterRestClient { private static final String BASE_URL = "http://api.twitter.com/1/"; diff --git a/examples/TwitterRestClientUsage.java b/examples/TwitterRestClientUsage.java index a4c89c8ce..297bfecc4 100644 --- a/examples/TwitterRestClientUsage.java +++ b/examples/TwitterRestClientUsage.java @@ -1,18 +1,15 @@ -import org.json.*; -import com.loopj.android.http.*; - class TwitterRestClientUsage { public void getPublicTimeline() { TwitterRestClient.get("statuses/public_timeline.json", null, new JsonHttpResponseHandler() { @Override public void onSuccess(JSONArray timeline) { try { - JSONObject firstEvent = (JSONObject)timeline.get(0); + JSONObject firstEvent = (JSONObject) timeline.get(0); String tweetText = firstEvent.getString("text"); // Do something with the response System.out.println(tweetText); - } catch(JSONException e) { + } catch (JSONException e) { e.printStackTrace(); } } diff --git a/library/AndroidManifest.xml b/library/AndroidManifest.xml index dbe4807a3..fc7a6a3cb 100644 --- a/library/AndroidManifest.xml +++ b/library/AndroidManifest.xml @@ -4,7 +4,9 @@ android:versionName="1.4.4" android:versionCode="2"> - + diff --git a/library/build.gradle b/library/build.gradle index 4f6054689..81185aea5 100644 --- a/library/build.gradle +++ b/library/build.gradle @@ -1,18 +1,18 @@ apply plugin: 'android-library' dependencies { - compile 'com.android.support:support-v4:18.0.+' + compile 'com.android.support:support-v4:18.0.+' } android { - compileSdkVersion 18 - buildToolsVersion '18.0.1' + compileSdkVersion 18 + buildToolsVersion '18.0.1' - sourceSets { - main { - manifest.srcFile 'AndroidManifest.xml' - java.srcDirs = ['src'] - res.srcDirs = ['res'] + sourceSets { + main { + manifest.srcFile 'AndroidManifest.xml' + java.srcDirs = ['src'] + res.srcDirs = ['res'] + } } - } } diff --git a/library/src/com/loopj/android/http/AsyncHttpClient.java b/library/src/com/loopj/android/http/AsyncHttpClient.java index 5f0750d7f..daa958e78 100644 --- a/library/src/com/loopj/android/http/AsyncHttpClient.java +++ b/library/src/com/loopj/android/http/AsyncHttpClient.java @@ -18,18 +18,7 @@ package com.loopj.android.http; -import java.io.IOException; -import java.io.InputStream; -import java.lang.ref.WeakReference; -import java.util.HashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.WeakHashMap; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; -import java.util.concurrent.ThreadPoolExecutor; -import java.util.zip.GZIPInputStream; +import android.content.Context; import org.apache.http.Header; import org.apache.http.HeaderElement; @@ -67,18 +56,29 @@ import org.apache.http.protocol.HttpContext; import org.apache.http.protocol.SyncBasicHttpContext; -import android.content.Context; +import java.io.IOException; +import java.io.InputStream; +import java.lang.ref.WeakReference; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.WeakHashMap; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.zip.GZIPInputStream; /** - * The AsyncHttpClient can be used to make asynchronous GET, POST, PUT and + * The AsyncHttpClient can be used to make asynchronous GET, POST, PUT and * DELETE HTTP requests in your Android applications. Requests can be made * with additional parameters by passing a {@link RequestParams} instance, - * and responses can be handled by passing an anonymously overridden + * and responses can be handled by passing an anonymously overridden * {@link AsyncHttpResponseHandler} instance. - *

    + *

    * For example: - *

    + *

    *

      * AsyncHttpClient client = new AsyncHttpClient();
      * client.get("http://www.google.com/", new AsyncHttpResponseHandler() {
    @@ -167,7 +167,7 @@ public void process(HttpResponse response, HttpContext context) {
     
             httpClient.setHttpRequestRetryHandler(new RetryHandler(DEFAULT_MAX_RETRIES));
     
    -        threadPool = (ThreadPoolExecutor)Executors.newCachedThreadPool();
    +        threadPool = (ThreadPoolExecutor) Executors.newCachedThreadPool();
     
             requestMap = new WeakHashMap>>>();
             clientHeaderMap = new HashMap();
    @@ -183,7 +183,7 @@ public HttpClient getHttpClient() {
         }
     
         /**
    -     * Get the underlying HttpContext instance. This is useful for getting 
    +     * Get the underlying HttpContext instance. This is useful for getting
          * and setting fine-grained settings for requests by accessing the
          * context's attributes such as the CookieStore.
          */
    @@ -193,6 +193,7 @@ public HttpContext getHttpContext() {
     
         /**
          * Sets an optional CookieStore to use when making requests
    +     *
          * @param cookieStore The CookieStore implementation to use, usually an instance of {@link PersistentCookieStore}
          */
         public void setCookieStore(CookieStore cookieStore) {
    @@ -202,6 +203,7 @@ public void setCookieStore(CookieStore cookieStore) {
         /**
          * Overrides the threadpool implementation used when queuing/pooling
          * requests. By default, Executors.newCachedThreadPool() is used.
    +     *
          * @param threadPool an instance of {@link ThreadPoolExecutor} to use for queuing/pooling requests.
          */
         public void setThreadPool(ThreadPoolExecutor threadPool) {
    @@ -211,6 +213,7 @@ public void setThreadPool(ThreadPoolExecutor threadPool) {
         /**
          * Sets the User-Agent header to be sent with each request. By default,
          * "Android Asynchronous Http Client/VERSION (http://loopj.com/android-async-http/)" is used.
    +     *
          * @param userAgent the string to use in the User-Agent header.
          */
         public void setUserAgent(String userAgent) {
    @@ -219,9 +222,10 @@ public void setUserAgent(String userAgent) {
     
         /**
          * Sets the connection time oout. By default, 10 seconds
    +     *
          * @param timeout the connect/socket timeout in milliseconds
          */
    -    public void setTimeout(int timeout){
    +    public void setTimeout(int timeout) {
             final HttpParams httpParams = this.httpClient.getParams();
             ConnManagerParams.setTimeout(httpParams, timeout);
             HttpConnectionParams.setSoTimeout(httpParams, timeout);
    @@ -231,16 +235,18 @@ public void setTimeout(int timeout){
         /**
          * Sets the SSLSocketFactory to user when making requests. By default,
          * a new, default SSLSocketFactory is used.
    +     *
          * @param sslSocketFactory the socket factory to use for https requests.
          */
         public void setSSLSocketFactory(SSLSocketFactory sslSocketFactory) {
             this.httpClient.getConnectionManager().getSchemeRegistry().register(new Scheme("https", sslSocketFactory, 443));
         }
    -    
    +
         /**
          * Sets headers that will be added to all requests this client makes (before sending).
    +     *
          * @param header the name of the header
    -     * @param value the contents of the header
    +     * @param value  the contents of the header
          */
         public void addHeader(String header, String value) {
             clientHeaderMap.put(header, value);
    @@ -248,46 +254,47 @@ public void addHeader(String header, String value) {
     
         /**
          * Sets basic authentication for the request. Uses AuthScope.ANY. This is the same as
    -     * setBasicAuth('username','password',AuthScope.ANY) 
    +     * setBasicAuth('username','password',AuthScope.ANY)
    +     *
          * @param username
          * @param password
          */
    -    public void setBasicAuth(String user, String pass){
    +    public void setBasicAuth(String user, String pass) {
             AuthScope scope = AuthScope.ANY;
             setBasicAuth(user, pass, scope);
         }
    -    
    -   /**
    +
    +    /**
          * Sets basic authentication for the request. You should pass in your AuthScope for security. It should be like this
          * setBasicAuth("username","password", new AuthScope("host",port,AuthScope.ANY_REALM))
    +     *
          * @param username
          * @param password
    -     * @param scope - an AuthScope object
    -     *
    +     * @param scope    - an AuthScope object
          */
    -    public void setBasicAuth( String user, String pass, AuthScope scope){
    -        UsernamePasswordCredentials credentials = new UsernamePasswordCredentials(user,pass);
    +    public void setBasicAuth(String user, String pass, AuthScope scope) {
    +        UsernamePasswordCredentials credentials = new UsernamePasswordCredentials(user, pass);
             this.httpClient.getCredentialsProvider().setCredentials(scope, credentials);
         }
     
         /**
          * Cancels any pending (or potentially active) requests associated with the
          * passed Context.
    -     * 

    + *

    * Note: This will only affect requests which were created with a non-null * android Context. This method is intended to be used in the onDestroy * method of your android activities to destroy all requests which are no * longer required. * - * @param context the android Context instance associated to the request. + * @param context the android Context instance associated to the request. * @param mayInterruptIfRunning specifies if active requests should be cancelled along with pending requests. */ public void cancelRequests(Context context, boolean mayInterruptIfRunning) { List>> requestList = requestMap.get(context); - if(requestList != null) { - for(WeakReference> requestRef : requestList) { + if (requestList != null) { + for (WeakReference> requestRef : requestList) { Future request = requestRef.get(); - if(request != null) { + if (request != null) { request.cancel(mayInterruptIfRunning); } } @@ -302,7 +309,8 @@ public void cancelRequests(Context context, boolean mayInterruptIfRunning) { /** * Perform a HTTP GET request, without any parameters. - * @param url the URL to send the request to. + * + * @param url the URL to send the request to. * @param responseHandler the response handler instance that should handle the response. */ public void get(String url, AsyncHttpResponseHandler responseHandler) { @@ -311,8 +319,9 @@ public void get(String url, AsyncHttpResponseHandler responseHandler) { /** * Perform a HTTP GET request with parameters. - * @param url the URL to send the request to. - * @param params additional GET parameters to send with the request. + * + * @param url the URL to send the request to. + * @param params additional GET parameters to send with the request. * @param responseHandler the response handler instance that should handle the response. */ public void get(String url, RequestParams params, AsyncHttpResponseHandler responseHandler) { @@ -321,8 +330,9 @@ public void get(String url, RequestParams params, AsyncHttpResponseHandler respo /** * Perform a HTTP GET request without any parameters and track the Android Context which initiated the request. - * @param context the Android Context which initiated the request. - * @param url the URL to send the request to. + * + * @param context the Android Context which initiated the request. + * @param url the URL to send the request to. * @param responseHandler the response handler instance that should handle the response. */ public void get(Context context, String url, AsyncHttpResponseHandler responseHandler) { @@ -331,28 +341,29 @@ public void get(Context context, String url, AsyncHttpResponseHandler responseHa /** * Perform a HTTP GET request and track the Android Context which initiated the request. - * @param context the Android Context which initiated the request. - * @param url the URL to send the request to. - * @param params additional GET parameters to send with the request. + * + * @param context the Android Context which initiated the request. + * @param url the URL to send the request to. + * @param params additional GET parameters to send with the request. * @param responseHandler the response handler instance that should handle the response. */ public void get(Context context, String url, RequestParams params, AsyncHttpResponseHandler responseHandler) { sendRequest(httpClient, httpContext, new HttpGet(getUrlWithQueryString(url, params)), null, responseHandler, context); } - + /** * Perform a HTTP GET request and track the Android Context which initiated * the request with customized headers - * - * @param url the URL to send the request to. - * @param headers set headers only for this request - * @param params additional GET parameters to send with the request. + * + * @param url the URL to send the request to. + * @param headers set headers only for this request + * @param params additional GET parameters to send with the request. * @param responseHandler the response handler instance that should handle - * the response. + * the response. */ public void get(Context context, String url, Header[] headers, RequestParams params, AsyncHttpResponseHandler responseHandler) { HttpUriRequest request = new HttpGet(getUrlWithQueryString(url, params)); - if(headers != null) request.setHeaders(headers); + if (headers != null) request.setHeaders(headers); sendRequest(httpClient, httpContext, request, null, responseHandler, context); } @@ -364,7 +375,8 @@ public void get(Context context, String url, Header[] headers, RequestParams par /** * Perform a HTTP POST request, without any parameters. - * @param url the URL to send the request to. + * + * @param url the URL to send the request to. * @param responseHandler the response handler instance that should handle the response. */ public void post(String url, AsyncHttpResponseHandler responseHandler) { @@ -373,8 +385,9 @@ public void post(String url, AsyncHttpResponseHandler responseHandler) { /** * Perform a HTTP POST request with parameters. - * @param url the URL to send the request to. - * @param params additional POST parameters or files to send with the request. + * + * @param url the URL to send the request to. + * @param params additional POST parameters or files to send with the request. * @param responseHandler the response handler instance that should handle the response. */ public void post(String url, RequestParams params, AsyncHttpResponseHandler responseHandler) { @@ -383,9 +396,10 @@ public void post(String url, RequestParams params, AsyncHttpResponseHandler resp /** * Perform a HTTP POST request and track the Android Context which initiated the request. - * @param context the Android Context which initiated the request. - * @param url the URL to send the request to. - * @param params additional POST parameters or files to send with the request. + * + * @param context the Android Context which initiated the request. + * @param url the URL to send the request to. + * @param params additional POST parameters or files to send with the request. * @param responseHandler the response handler instance that should handle the response. */ public void post(Context context, String url, RequestParams params, AsyncHttpResponseHandler responseHandler) { @@ -394,10 +408,11 @@ public void post(Context context, String url, RequestParams params, AsyncHttpRes /** * Perform a HTTP POST request and track the Android Context which initiated the request. - * @param context the Android Context which initiated the request. - * @param url the URL to send the request to. - * @param entity a raw {@link HttpEntity} to send with the request, for example, use this to send string/json/xml payloads to a server by passing a {@link org.apache.http.entity.StringEntity}. - * @param contentType the content type of the payload you are sending, for example application/json if sending a json payload. + * + * @param context the Android Context which initiated the request. + * @param url the URL to send the request to. + * @param entity a raw {@link HttpEntity} to send with the request, for example, use this to send string/json/xml payloads to a server by passing a {@link org.apache.http.entity.StringEntity}. + * @param contentType the content type of the payload you are sending, for example application/json if sending a json payload. * @param responseHandler the response handler instance that should handle the response. */ public void post(Context context, String url, HttpEntity entity, String contentType, AsyncHttpResponseHandler responseHandler) { @@ -407,21 +422,21 @@ public void post(Context context, String url, HttpEntity entity, String contentT /** * Perform a HTTP POST request and track the Android Context which initiated * the request. Set headers only for this request - * - * @param context the Android Context which initiated the request. - * @param url the URL to send the request to. - * @param headers set headers only for this request - * @param params additional POST parameters to send with the request. - * @param contentType the content type of the payload you are sending, for - * example application/json if sending a json payload. + * + * @param context the Android Context which initiated the request. + * @param url the URL to send the request to. + * @param headers set headers only for this request + * @param params additional POST parameters to send with the request. + * @param contentType the content type of the payload you are sending, for + * example application/json if sending a json payload. * @param responseHandler the response handler instance that should handle - * the response. + * the response. */ public void post(Context context, String url, Header[] headers, RequestParams params, String contentType, - AsyncHttpResponseHandler responseHandler) { + AsyncHttpResponseHandler responseHandler) { HttpEntityEnclosingRequestBase request = new HttpPost(url); - if(params != null) request.setEntity(paramsToEntity(params)); - if(headers != null) request.setHeaders(headers); + if (params != null) request.setEntity(paramsToEntity(params)); + if (headers != null) request.setHeaders(headers); sendRequest(httpClient, httpContext, request, contentType, responseHandler, context); } @@ -430,21 +445,21 @@ public void post(Context context, String url, Header[] headers, RequestParams pa * Perform a HTTP POST request and track the Android Context which initiated * the request. Set headers only for this request * - * @param context the Android Context which initiated the request. - * @param url the URL to send the request to. - * @param headers set headers only for this request - * @param entity a raw {@link HttpEntity} to send with the request, for - * example, use this to send string/json/xml payloads to a server by - * passing a {@link org.apache.http.entity.StringEntity}. - * @param contentType the content type of the payload you are sending, for - * example application/json if sending a json payload. + * @param context the Android Context which initiated the request. + * @param url the URL to send the request to. + * @param headers set headers only for this request + * @param entity a raw {@link HttpEntity} to send with the request, for + * example, use this to send string/json/xml payloads to a server by + * passing a {@link org.apache.http.entity.StringEntity}. + * @param contentType the content type of the payload you are sending, for + * example application/json if sending a json payload. * @param responseHandler the response handler instance that should handle - * the response. + * the response. */ public void post(Context context, String url, Header[] headers, HttpEntity entity, String contentType, - AsyncHttpResponseHandler responseHandler) { + AsyncHttpResponseHandler responseHandler) { HttpEntityEnclosingRequestBase request = addEntityToRequestBase(new HttpPost(url), entity); - if(headers != null) request.setHeaders(headers); + if (headers != null) request.setHeaders(headers); sendRequest(httpClient, httpContext, request, contentType, responseHandler, context); } @@ -454,7 +469,8 @@ public void post(Context context, String url, Header[] headers, HttpEntity entit /** * Perform a HTTP PUT request, without any parameters. - * @param url the URL to send the request to. + * + * @param url the URL to send the request to. * @param responseHandler the response handler instance that should handle the response. */ public void put(String url, AsyncHttpResponseHandler responseHandler) { @@ -463,8 +479,9 @@ public void put(String url, AsyncHttpResponseHandler responseHandler) { /** * Perform a HTTP PUT request with parameters. - * @param url the URL to send the request to. - * @param params additional PUT parameters or files to send with the request. + * + * @param url the URL to send the request to. + * @param params additional PUT parameters or files to send with the request. * @param responseHandler the response handler instance that should handle the response. */ public void put(String url, RequestParams params, AsyncHttpResponseHandler responseHandler) { @@ -473,9 +490,10 @@ public void put(String url, RequestParams params, AsyncHttpResponseHandler respo /** * Perform a HTTP PUT request and track the Android Context which initiated the request. - * @param context the Android Context which initiated the request. - * @param url the URL to send the request to. - * @param params additional PUT parameters or files to send with the request. + * + * @param context the Android Context which initiated the request. + * @param url the URL to send the request to. + * @param params additional PUT parameters or files to send with the request. * @param responseHandler the response handler instance that should handle the response. */ public void put(Context context, String url, RequestParams params, AsyncHttpResponseHandler responseHandler) { @@ -485,29 +503,31 @@ public void put(Context context, String url, RequestParams params, AsyncHttpResp /** * Perform a HTTP PUT request and track the Android Context which initiated the request. * And set one-time headers for the request - * @param context the Android Context which initiated the request. - * @param url the URL to send the request to. - * @param entity a raw {@link HttpEntity} to send with the request, for example, use this to send string/json/xml payloads to a server by passing a {@link org.apache.http.entity.StringEntity}. - * @param contentType the content type of the payload you are sending, for example application/json if sending a json payload. + * + * @param context the Android Context which initiated the request. + * @param url the URL to send the request to. + * @param entity a raw {@link HttpEntity} to send with the request, for example, use this to send string/json/xml payloads to a server by passing a {@link org.apache.http.entity.StringEntity}. + * @param contentType the content type of the payload you are sending, for example application/json if sending a json payload. * @param responseHandler the response handler instance that should handle the response. */ public void put(Context context, String url, HttpEntity entity, String contentType, AsyncHttpResponseHandler responseHandler) { sendRequest(httpClient, httpContext, addEntityToRequestBase(new HttpPut(url), entity), contentType, responseHandler, context); } - + /** * Perform a HTTP PUT request and track the Android Context which initiated the request. * And set one-time headers for the request - * @param context the Android Context which initiated the request. - * @param url the URL to send the request to. - * @param headers set one-time headers for this request - * @param entity a raw {@link HttpEntity} to send with the request, for example, use this to send string/json/xml payloads to a server by passing a {@link org.apache.http.entity.StringEntity}. - * @param contentType the content type of the payload you are sending, for example application/json if sending a json payload. + * + * @param context the Android Context which initiated the request. + * @param url the URL to send the request to. + * @param headers set one-time headers for this request + * @param entity a raw {@link HttpEntity} to send with the request, for example, use this to send string/json/xml payloads to a server by passing a {@link org.apache.http.entity.StringEntity}. + * @param contentType the content type of the payload you are sending, for example application/json if sending a json payload. * @param responseHandler the response handler instance that should handle the response. */ - public void put(Context context, String url,Header[] headers, HttpEntity entity, String contentType, AsyncHttpResponseHandler responseHandler) { + public void put(Context context, String url, Header[] headers, HttpEntity entity, String contentType, AsyncHttpResponseHandler responseHandler) { HttpEntityEnclosingRequestBase request = addEntityToRequestBase(new HttpPut(url), entity); - if(headers != null) request.setHeaders(headers); + if (headers != null) request.setHeaders(headers); sendRequest(httpClient, httpContext, request, contentType, responseHandler, context); } @@ -517,7 +537,8 @@ public void put(Context context, String url,Header[] headers, HttpEntity entity, /** * Perform a HTTP DELETE request. - * @param url the URL to send the request to. + * + * @param url the URL to send the request to. * @param responseHandler the response handler instance that should handle the response. */ public void delete(String url, AsyncHttpResponseHandler responseHandler) { @@ -526,41 +547,43 @@ public void delete(String url, AsyncHttpResponseHandler responseHandler) { /** * Perform a HTTP DELETE request. - * @param context the Android Context which initiated the request. - * @param url the URL to send the request to. + * + * @param context the Android Context which initiated the request. + * @param url the URL to send the request to. * @param responseHandler the response handler instance that should handle the response. */ public void delete(Context context, String url, AsyncHttpResponseHandler responseHandler) { final HttpDelete delete = new HttpDelete(url); sendRequest(httpClient, httpContext, delete, null, responseHandler, context); } - + /** * Perform a HTTP DELETE request. - * @param context the Android Context which initiated the request. - * @param url the URL to send the request to. - * @param headers set one-time headers for this request + * + * @param context the Android Context which initiated the request. + * @param url the URL to send the request to. + * @param headers set one-time headers for this request * @param responseHandler the response handler instance that should handle the response. */ public void delete(Context context, String url, Header[] headers, AsyncHttpResponseHandler responseHandler) { final HttpDelete delete = new HttpDelete(url); - if(headers != null) delete.setHeaders(headers); + if (headers != null) delete.setHeaders(headers); sendRequest(httpClient, httpContext, delete, null, responseHandler, context); } // Private stuff protected void sendRequest(DefaultHttpClient client, HttpContext httpContext, HttpUriRequest uriRequest, String contentType, AsyncHttpResponseHandler responseHandler, Context context) { - if(contentType != null) { + if (contentType != null) { uriRequest.addHeader("Content-Type", contentType); } Future request = threadPool.submit(new AsyncHttpRequest(client, httpContext, uriRequest, responseHandler)); - if(context != null) { + if (context != null) { // Add request to request map List>> requestList = requestMap.get(context); - if(requestList == null) { + if (requestList == null) { requestList = new LinkedList>>(); requestMap.put(context, requestList); } @@ -572,7 +595,7 @@ protected void sendRequest(DefaultHttpClient client, HttpContext httpContext, Ht } public static String getUrlWithQueryString(String url, RequestParams params) { - if(params != null) { + if (params != null) { String paramString = params.getParamString(); if (url.indexOf("?") == -1) { url += "?" + paramString; @@ -587,7 +610,7 @@ public static String getUrlWithQueryString(String url, RequestParams params) { private HttpEntity paramsToEntity(RequestParams params) { HttpEntity entity = null; - if(params != null) { + if (params != null) { entity = params.getEntity(); } @@ -595,7 +618,7 @@ private HttpEntity paramsToEntity(RequestParams params) { } private HttpEntityEnclosingRequestBase addEntityToRequestBase(HttpEntityEnclosingRequestBase requestBase, HttpEntity entity) { - if(entity != null){ + if (entity != null) { requestBase.setEntity(entity); } diff --git a/library/src/com/loopj/android/http/AsyncHttpRequest.java b/library/src/com/loopj/android/http/AsyncHttpRequest.java index 48e6807e7..48145eb66 100644 --- a/library/src/com/loopj/android/http/AsyncHttpRequest.java +++ b/library/src/com/loopj/android/http/AsyncHttpRequest.java @@ -18,18 +18,18 @@ package com.loopj.android.http; -import java.io.IOException; -import java.net.ConnectException; -import java.net.SocketException; -import java.net.SocketTimeoutException; -import java.net.UnknownHostException; - import org.apache.http.HttpResponse; import org.apache.http.client.HttpRequestRetryHandler; import org.apache.http.client.methods.HttpUriRequest; import org.apache.http.impl.client.AbstractHttpClient; import org.apache.http.protocol.HttpContext; +import java.io.IOException; +import java.net.ConnectException; +import java.net.SocketException; +import java.net.SocketTimeoutException; +import java.net.UnknownHostException; + class AsyncHttpRequest implements Runnable { private final AbstractHttpClient client; private final HttpContext context; @@ -43,7 +43,7 @@ public AsyncHttpRequest(AbstractHttpClient client, HttpContext context, HttpUriR this.context = context; this.request = request; this.responseHandler = responseHandler; - if(responseHandler instanceof BinaryHttpResponseHandler) { + if (responseHandler instanceof BinaryHttpResponseHandler) { this.isBinaryRequest = true; } } @@ -51,19 +51,19 @@ public AsyncHttpRequest(AbstractHttpClient client, HttpContext context, HttpUriR @Override public void run() { try { - if(responseHandler != null){ + if (responseHandler != null) { responseHandler.sendStartMessage(); } makeRequestWithRetries(); - if(responseHandler != null) { + if (responseHandler != null) { responseHandler.sendFinishMessage(); } } catch (IOException e) { - if(responseHandler != null) { + if (responseHandler != null) { responseHandler.sendFinishMessage(); - if(this.isBinaryRequest) { + if (this.isBinaryRequest) { responseHandler.sendFailureMessage(e, (byte[]) null); } else { responseHandler.sendFailureMessage(e, (String) null); @@ -73,21 +73,21 @@ public void run() { } private void makeRequest() throws IOException { - if(!Thread.currentThread().isInterrupted()) { - try { - HttpResponse response = client.execute(request, context); - if(!Thread.currentThread().isInterrupted()) { - if(responseHandler != null) { - responseHandler.sendResponseMessage(response); - } - } else{ - //TODO: should raise InterruptedException? this block is reached whenever the request is cancelled before its response is received - } - } catch (IOException e) { - if(!Thread.currentThread().isInterrupted()) { - throw e; - } - } + if (!Thread.currentThread().isInterrupted()) { + try { + HttpResponse response = client.execute(request, context); + if (!Thread.currentThread().isInterrupted()) { + if (responseHandler != null) { + responseHandler.sendResponseMessage(response); + } + } else { + //TODO: should raise InterruptedException? this block is reached whenever the request is cancelled before its response is received + } + } catch (IOException e) { + if (!Thread.currentThread().isInterrupted()) { + throw e; + } + } } } @@ -102,18 +102,18 @@ private void makeRequestWithRetries() throws ConnectException { makeRequest(); return; } catch (UnknownHostException e) { - if(responseHandler != null) { - responseHandler.sendFailureMessage(e, "can't resolve host"); - } - return; - }catch (SocketException e){ + if (responseHandler != null) { + responseHandler.sendFailureMessage(e, "can't resolve host"); + } + return; + } catch (SocketException e) { // Added to detect host unreachable - if(responseHandler != null) { + if (responseHandler != null) { responseHandler.sendFailureMessage(e, "can't resolve host"); } return; - }catch (SocketTimeoutException e){ - if(responseHandler != null) { + } catch (SocketTimeoutException e) { + if (responseHandler != null) { responseHandler.sendFailureMessage(e, "socket time out"); } return; diff --git a/library/src/com/loopj/android/http/AsyncHttpResponseHandler.java b/library/src/com/loopj/android/http/AsyncHttpResponseHandler.java index 2030265f5..9bffb1c09 100644 --- a/library/src/com/loopj/android/http/AsyncHttpResponseHandler.java +++ b/library/src/com/loopj/android/http/AsyncHttpResponseHandler.java @@ -21,8 +21,8 @@ import android.os.Handler; import android.os.Looper; import android.os.Message; + import org.apache.http.Header; -import java.io.IOException; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.StatusLine; @@ -30,20 +30,18 @@ import org.apache.http.entity.BufferedHttpEntity; import org.apache.http.util.EntityUtils; -import android.os.Handler; -import android.os.Looper; -import android.os.Message; +import java.io.IOException; /** - * Used to intercept and handle the responses from requests made using - * {@link AsyncHttpClient}. The {@link #onSuccess(String)} method is + * Used to intercept and handle the responses from requests made using + * {@link AsyncHttpClient}. The {@link #onSuccess(String)} method is * designed to be anonymously overridden with your own response handling code. - *

    + *

    * Additionally, you can override the {@link #onFailure(Throwable, String)}, * {@link #onStart()}, and {@link #onFinish()} methods as required. - *

    + *

    * For example: - *

    + *

    *

      * AsyncHttpClient client = new AsyncHttpClient();
      * client.get("http://www.google.com/", new AsyncHttpResponseHandler() {
    @@ -56,7 +54,7 @@
      *     public void onSuccess(String response) {
      *         // Successfully got a response
      *     }
    - * 
    + *
      *     @Override
      *     public void onFailure(Throwable e, String response) {
      *         // Response failed :(
    @@ -82,10 +80,10 @@ public class AsyncHttpResponseHandler {
          */
         public AsyncHttpResponseHandler() {
             // Set up a handler to post events back to the correct thread if possible
    -        if(Looper.myLooper() != null) {
    -            handler = new Handler(){
    +        if (Looper.myLooper() != null) {
    +            handler = new Handler() {
                     @Override
    -                public void handleMessage(Message msg){
    +                public void handleMessage(Message msg) {
                         AsyncHttpResponseHandler.this.handleMessage(msg);
                     }
                 };
    @@ -100,24 +98,29 @@ public void handleMessage(Message msg){
         /**
          * Fired when the request is started, override to handle in your own code
          */
    -    public void onStart() {}
    +    public void onStart() {
    +    }
     
         /**
          * Fired in all cases when the request is finished, after both success and failure, override to handle in your own code
          */
    -    public void onFinish() {}
    +    public void onFinish() {
    +    }
     
         /**
          * Fired when a request returns successfully, override to handle in your own code
    +     *
          * @param content the body of the HTTP response from the server
          */
    -    public void onSuccess(String content) {}
    +    public void onSuccess(String content) {
    +    }
     
         /**
          * Fired when a request returns successfully, override to handle in your own code
    +     *
          * @param statusCode the status code of the response
    -     * @param headers the headers of the HTTP response
    -     * @param content the body of the HTTP response from the server
    +     * @param headers    the headers of the HTTP response
    +     * @param content    the body of the HTTP response from the server
          */
         public void onSuccess(int statusCode, Header[] headers, String content) {
             onSuccess(statusCode, content);
    @@ -125,25 +128,28 @@ public void onSuccess(int statusCode, Header[] headers, String content) {
     
         /**
          * Fired when a request returns successfully, override to handle in your own code
    +     *
          * @param statusCode the status code of the response
    -     * @param content the body of the HTTP response from the server
    +     * @param content    the body of the HTTP response from the server
          */
    -    public void onSuccess(int statusCode, String content)
    -    {
    +    public void onSuccess(int statusCode, String content) {
             onSuccess(content);
         }
     
         /**
          * Fired when a request fails to complete, override to handle in your own code
    +     *
          * @param error the underlying cause of the failure
          * @deprecated use {@link #onFailure(Throwable, String)}
          */
         @Deprecated
    -    public void onFailure(Throwable error) {}
    +    public void onFailure(Throwable error) {
    +    }
     
         /**
          * Fired when a request fails to complete, override to handle in your own code
    -     * @param error the underlying cause of the failure
    +     *
    +     * @param error   the underlying cause of the failure
          * @param content the response body, if any
          */
         public void onFailure(Throwable error, String content) {
    @@ -163,7 +169,7 @@ protected void sendSuccessMessage(int statusCode, Header[] headers, String respo
         protected void sendFailureMessage(Throwable e, String responseBody) {
             sendMessage(obtainMessage(FAILURE_MESSAGE, new Object[]{e, responseBody}));
         }
    -    
    +
         protected void sendFailureMessage(Throwable e, byte[] responseBody) {
             sendMessage(obtainMessage(FAILURE_MESSAGE, new Object[]{e, responseBody}));
         }
    @@ -190,19 +196,18 @@ protected void handleFailureMessage(Throwable e, String responseBody) {
         }
     
     
    -
         // Methods which emulate android's Handler and Message methods
         protected void handleMessage(Message msg) {
             Object[] response;
     
    -        switch(msg.what) {
    +        switch (msg.what) {
                 case SUCCESS_MESSAGE:
    -                response = (Object[])msg.obj;
    +                response = (Object[]) msg.obj;
                     handleSuccessMessage(((Integer) response[0]).intValue(), (Header[]) response[1], (String) response[2]);
                     break;
                 case FAILURE_MESSAGE:
    -                response = (Object[])msg.obj;
    -                handleFailureMessage((Throwable)response[0], (String)response[1]);
    +                response = (Object[]) msg.obj;
    +                handleFailureMessage((Throwable) response[0], (String) response[1]);
                     break;
                 case START_MESSAGE:
                     onStart();
    @@ -214,7 +219,7 @@ protected void handleMessage(Message msg) {
         }
     
         protected void sendMessage(Message msg) {
    -        if(handler != null){
    +        if (handler != null) {
                 handler.sendMessage(msg);
             } else {
                 handleMessage(msg);
    @@ -223,9 +228,9 @@ protected void sendMessage(Message msg) {
     
         protected Message obtainMessage(int responseMessage, Object response) {
             Message msg = null;
    -        if(handler != null){
    +        if (handler != null) {
                 msg = this.handler.obtainMessage(responseMessage, response);
    -        }else{
    +        } else {
                 msg = Message.obtain();
                 msg.what = responseMessage;
                 msg.obj = response;
    @@ -240,15 +245,15 @@ void sendResponseMessage(HttpResponse response) {
             try {
                 HttpEntity entity = null;
                 HttpEntity temp = response.getEntity();
    -            if(temp != null) {
    +            if (temp != null) {
                     entity = new BufferedHttpEntity(temp);
                     responseBody = EntityUtils.toString(entity, "UTF-8");
                 }
    -        } catch(IOException e) {
    +        } catch (IOException e) {
                 sendFailureMessage(e, (String) null);
             }
     
    -        if(status.getStatusCode() >= 300) {
    +        if (status.getStatusCode() >= 300) {
                 sendFailureMessage(new HttpResponseException(status.getStatusCode(), status.getReasonPhrase()), responseBody);
             } else {
                 sendSuccessMessage(status.getStatusCode(), response.getAllHeaders(), responseBody);
    diff --git a/library/src/com/loopj/android/http/BinaryHttpResponseHandler.java b/library/src/com/loopj/android/http/BinaryHttpResponseHandler.java
    index 6ccaab0ce..fb8e980db 100644
    --- a/library/src/com/loopj/android/http/BinaryHttpResponseHandler.java
    +++ b/library/src/com/loopj/android/http/BinaryHttpResponseHandler.java
    @@ -18,8 +18,7 @@
     
     package com.loopj.android.http;
     
    -import java.io.IOException;
    -import java.util.regex.Pattern;
    +import android.os.Message;
     
     import org.apache.http.Header;
     import org.apache.http.HttpEntity;
    @@ -29,16 +28,17 @@
     import org.apache.http.entity.BufferedHttpEntity;
     import org.apache.http.util.EntityUtils;
     
    -import android.os.Message;
    +import java.io.IOException;
    +import java.util.regex.Pattern;
     
     /**
      * Used to intercept and handle the responses from requests made using
    - * {@link AsyncHttpClient}. Receives response body as byte array with a 
    - * content-type whitelist. (e.g. checks Content-Type against allowed list, 
    + * {@link AsyncHttpClient}. Receives response body as byte array with a
    + * content-type whitelist. (e.g. checks Content-Type against allowed list,
      * Content-length).
    - * 

    + *

    * For example: - *

    + *

    *

      * AsyncHttpClient client = new AsyncHttpClient();
      * String[] allowedTypes = new String[] { "image/png" };
    @@ -57,9 +57,9 @@
      */
     public class BinaryHttpResponseHandler extends AsyncHttpResponseHandler {
         // Allow images by default
    -    private static String[] mAllowedContentTypes = new String[] {
    -        "image/jpeg",
    -        "image/png"
    +    private static String[] mAllowedContentTypes = new String[]{
    +            "image/jpeg",
    +            "image/png"
         };
     
         /**
    @@ -85,12 +85,15 @@ public BinaryHttpResponseHandler(String[] allowedContentTypes) {
     
         /**
          * Fired when a request returns successfully, override to handle in your own code
    +     *
          * @param binaryData the body of the HTTP response from the server
          */
    -    public void onSuccess(byte[] binaryData) {}
    +    public void onSuccess(byte[] binaryData) {
    +    }
     
         /**
          * Fired when a request returns successfully, override to handle in your own code
    +     *
          * @param statusCode the status code of the response
          * @param binaryData the body of the HTTP response from the server
          */
    @@ -100,7 +103,8 @@ public void onSuccess(int statusCode, byte[] binaryData) {
     
         /**
          * Fired when a request fails to complete, override to handle in your own code
    -     * @param error the underlying cause of the failure
    +     *
    +     * @param error      the underlying cause of the failure
          * @param binaryData the response body, if any
          * @deprecated
          */
    @@ -140,14 +144,14 @@ protected void handleFailureMessage(Throwable e, byte[] responseBody) {
         @Override
         protected void handleMessage(Message msg) {
             Object[] response;
    -        switch(msg.what) {
    +        switch (msg.what) {
                 case SUCCESS_MESSAGE:
    -                response = (Object[])msg.obj;
    -                handleSuccessMessage(((Integer) response[0]).intValue() , (byte[]) response[1]);
    +                response = (Object[]) msg.obj;
    +                handleSuccessMessage(((Integer) response[0]).intValue(), (byte[]) response[1]);
                     break;
                 case FAILURE_MESSAGE:
    -                response = (Object[])msg.obj;
    -                handleFailureMessage((Throwable)response[0], (byte[]) response[1]);
    +                response = (Object[]) msg.obj;
    +                handleFailureMessage((Throwable) response[0], (byte[]) response[1]);
                     break;
                 default:
                     super.handleMessage(msg);
    @@ -161,19 +165,19 @@ void sendResponseMessage(HttpResponse response) {
             StatusLine status = response.getStatusLine();
             Header[] contentTypeHeaders = response.getHeaders("Content-Type");
             byte[] responseBody = null;
    -        if(contentTypeHeaders.length != 1) {
    +        if (contentTypeHeaders.length != 1) {
                 //malformed/ambiguous HTTP Header, ABORT!
                 sendFailureMessage(new HttpResponseException(status.getStatusCode(), "None, or more than one, Content-Type Header found!"), responseBody);
                 return;
             }
             Header contentTypeHeader = contentTypeHeaders[0];
             boolean foundAllowedContentType = false;
    -        for(String anAllowedContentType : mAllowedContentTypes) {
    -            if(Pattern.matches(anAllowedContentType, contentTypeHeader.getValue())) {
    +        for (String anAllowedContentType : mAllowedContentTypes) {
    +            if (Pattern.matches(anAllowedContentType, contentTypeHeader.getValue())) {
                     foundAllowedContentType = true;
                 }
             }
    -        if(!foundAllowedContentType) {
    +        if (!foundAllowedContentType) {
                 //Content-Type not in allowed list, ABORT!
                 sendFailureMessage(new HttpResponseException(status.getStatusCode(), "Content-Type not allowed!"), responseBody);
                 return;
    @@ -181,15 +185,15 @@ void sendResponseMessage(HttpResponse response) {
             try {
                 HttpEntity entity = null;
                 HttpEntity temp = response.getEntity();
    -            if(temp != null) {
    +            if (temp != null) {
                     entity = new BufferedHttpEntity(temp);
                 }
                 responseBody = EntityUtils.toByteArray(entity);
    -        } catch(IOException e) {
    +        } catch (IOException e) {
                 sendFailureMessage(e, (byte[]) null);
             }
     
    -        if(status.getStatusCode() >= 300) {
    +        if (status.getStatusCode() >= 300) {
                 sendFailureMessage(new HttpResponseException(status.getStatusCode(), status.getReasonPhrase()), responseBody);
             } else {
                 sendSuccessMessage(status.getStatusCode(), responseBody);
    diff --git a/library/src/com/loopj/android/http/JsonHttpResponseHandler.java b/library/src/com/loopj/android/http/JsonHttpResponseHandler.java
    index 433690af7..12516f81d 100644
    --- a/library/src/com/loopj/android/http/JsonHttpResponseHandler.java
    +++ b/library/src/com/loopj/android/http/JsonHttpResponseHandler.java
    @@ -18,23 +18,24 @@
     
     package com.loopj.android.http;
     
    +import android.os.Message;
    +
    +import org.apache.http.Header;
     import org.apache.http.HttpStatus;
     import org.json.JSONArray;
     import org.json.JSONException;
     import org.json.JSONObject;
     import org.json.JSONTokener;
    -import org.apache.http.Header;
    -import android.os.Message;
     
     /**
      * Used to intercept and handle the responses from requests made using
      * {@link AsyncHttpClient}, with automatic parsing into a {@link JSONObject}
      * or {@link JSONArray}.
    - * 

    + *

    * This class is designed to be passed to get, post, put and delete requests * with the {@link #onSuccess(JSONObject)} or {@link #onSuccess(JSONArray)} * methods anonymously overridden. - *

    + *

    * Additionally, you can override the other event methods from the * parent class. */ @@ -49,26 +50,31 @@ public class JsonHttpResponseHandler extends AsyncHttpResponseHandler { * Fired when a request returns successfully and contains a json object * at the base of the response string. Override to handle in your * own code. + * * @param response the parsed json object found in the server response (if any) */ - public void onSuccess(JSONObject response) {} + public void onSuccess(JSONObject response) { + } /** * Fired when a request returns successfully and contains a json array * at the base of the response string. Override to handle in your * own code. + * * @param response the parsed json array found in the server response (if any) */ - public void onSuccess(JSONArray response) {} + public void onSuccess(JSONArray response) { + } /** * Fired when a request returns successfully and contains a json object * at the base of the response string. Override to handle in your * own code. + * * @param statusCode the status code of the response - * @param headers the headers of the HTTP response - * @param response the parsed json object found in the server response (if any) + * @param headers the headers of the HTTP response + * @param response the parsed json object found in the server response (if any) */ public void onSuccess(int statusCode, Header[] headers, JSONObject response) { onSuccess(statusCode, response); @@ -78,8 +84,9 @@ public void onSuccess(int statusCode, Header[] headers, JSONObject response) { * Fired when a request returns successfully and contains a json object * at the base of the response string. Override to handle in your * own code. + * * @param statusCode the status code of the response - * @param response the parsed json object found in the server response (if any) + * @param response the parsed json object found in the server response (if any) */ public void onSuccess(int statusCode, JSONObject response) { onSuccess(response); @@ -89,9 +96,10 @@ public void onSuccess(int statusCode, JSONObject response) { * Fired when a request returns successfully and contains a json array * at the base of the response string. Override to handle in your * own code. + * * @param statusCode the status code of the response - * @param headers the headers of the HTTP response - * @param response the parsed json array found in the server response (if any) + * @param headers the headers of the HTTP response + * @param response the parsed json array found in the server response (if any) */ public void onSuccess(int statusCode, Header[] headers, JSONArray response) { onSuccess(statusCode, response); @@ -101,15 +109,19 @@ public void onSuccess(int statusCode, Header[] headers, JSONArray response) { * Fired when a request returns successfully and contains a json array * at the base of the response string. Override to handle in your * own code. + * * @param statusCode the status code of the response - * @param response the parsed json array found in the server response (if any) + * @param response the parsed json array found in the server response (if any) */ - public void onSuccess(int statusCode, JSONArray response) { + public void onSuccess(int statusCode, JSONArray response) { onSuccess(response); } - public void onFailure(Throwable e, JSONObject errorResponse) {} - public void onFailure(Throwable e, JSONArray errorResponse) {} + public void onFailure(Throwable e, JSONObject errorResponse) { + } + + public void onFailure(Throwable e, JSONArray errorResponse) { + } // @@ -118,16 +130,16 @@ public void onFailure(Throwable e, JSONArray errorResponse) {} @Override protected void sendSuccessMessage(int statusCode, Header[] headers, String responseBody) { - if (statusCode != HttpStatus.SC_NO_CONTENT){ + if (statusCode != HttpStatus.SC_NO_CONTENT) { try { Object jsonResponse = parseResponse(responseBody); - sendMessage(obtainMessage(SUCCESS_JSON_MESSAGE, new Object[]{statusCode, headers, jsonResponse})); - } catch(JSONException e) { - sendFailureMessage(e, responseBody); - } + sendMessage(obtainMessage(SUCCESS_JSON_MESSAGE, new Object[]{statusCode, headers, jsonResponse})); + } catch (JSONException e) { + sendFailureMessage(e, responseBody); + } } else { sendMessage(obtainMessage(SUCCESS_JSON_MESSAGE, new Object[]{statusCode, new JSONObject()})); - } + } } @@ -137,37 +149,37 @@ protected void sendSuccessMessage(int statusCode, Header[] headers, String respo @Override protected void handleMessage(Message msg) { - switch(msg.what){ + switch (msg.what) { case SUCCESS_JSON_MESSAGE: Object[] response = (Object[]) msg.obj; - handleSuccessJsonMessage(((Integer) response[0]).intValue(),(Header[]) response[1] ,response[2]); + handleSuccessJsonMessage(((Integer) response[0]).intValue(), (Header[]) response[1], response[2]); break; default: super.handleMessage(msg); } } - protected void handleSuccessJsonMessage(int statusCode,Header[] headers, Object jsonResponse) { - if(jsonResponse instanceof JSONObject) { - onSuccess(statusCode, headers, (JSONObject)jsonResponse); - } else if(jsonResponse instanceof JSONArray) { - onSuccess(statusCode, headers, (JSONArray)jsonResponse); + protected void handleSuccessJsonMessage(int statusCode, Header[] headers, Object jsonResponse) { + if (jsonResponse instanceof JSONObject) { + onSuccess(statusCode, headers, (JSONObject) jsonResponse); + } else if (jsonResponse instanceof JSONArray) { + onSuccess(statusCode, headers, (JSONArray) jsonResponse); } else { - onFailure(new JSONException("Unexpected type " + jsonResponse.getClass().getName()), (JSONObject)null); + onFailure(new JSONException("Unexpected type " + jsonResponse.getClass().getName()), (JSONObject) null); } } protected Object parseResponse(String responseBody) throws JSONException { Object result = null; //trim the string to prevent start with blank, and test if the string is valid JSON, because the parser don't do this :(. If Json is not valid this will return null - responseBody = responseBody.trim(); - if(responseBody.startsWith("{") || responseBody.startsWith("[")) { - result = new JSONTokener(responseBody).nextValue(); - } - if (result == null) { - result = responseBody; - } - return result; + responseBody = responseBody.trim(); + if (responseBody.startsWith("{") || responseBody.startsWith("[")) { + result = new JSONTokener(responseBody).nextValue(); + } + if (result == null) { + result = responseBody; + } + return result; } @Override @@ -175,17 +187,17 @@ protected void handleFailureMessage(Throwable e, String responseBody) { try { if (responseBody != null) { Object jsonResponse = parseResponse(responseBody); - if(jsonResponse instanceof JSONObject) { - onFailure(e, (JSONObject)jsonResponse); - } else if(jsonResponse instanceof JSONArray) { - onFailure(e, (JSONArray)jsonResponse); + if (jsonResponse instanceof JSONObject) { + onFailure(e, (JSONObject) jsonResponse); + } else if (jsonResponse instanceof JSONArray) { + onFailure(e, (JSONArray) jsonResponse); } else { onFailure(e, responseBody); } - }else { + } else { onFailure(e, ""); } - }catch(JSONException ex) { + } catch (JSONException ex) { onFailure(e, responseBody); } } diff --git a/library/src/com/loopj/android/http/PersistentCookieStore.java b/library/src/com/loopj/android/http/PersistentCookieStore.java index 11b2a2bf1..8f255ae5e 100644 --- a/library/src/com/loopj/android/http/PersistentCookieStore.java +++ b/library/src/com/loopj/android/http/PersistentCookieStore.java @@ -18,6 +18,13 @@ package com.loopj.android.http; +import android.content.Context; +import android.content.SharedPreferences; +import android.text.TextUtils; + +import org.apache.http.client.CookieStore; +import org.apache.http.cookie.Cookie; + import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.ObjectInputStream; @@ -27,21 +34,14 @@ import java.util.List; import java.util.concurrent.ConcurrentHashMap; -import org.apache.http.client.CookieStore; -import org.apache.http.cookie.Cookie; - -import android.content.Context; -import android.content.SharedPreferences; -import android.text.TextUtils; - /** * A persistent cookie store which implements the Apache HttpClient * {@link CookieStore} interface. Cookies are stored and will persist on the * user's device between application sessions since they are serialized and * stored in {@link SharedPreferences}. - *

    + *

    * Instances of this class are designed to be used with - * {@link AsyncHttpClient#setCookieStore}, but can also be used with a + * {@link AsyncHttpClient#setCookieStore}, but can also be used with a * regular old apache HttpClient/HttpContext if you prefer. */ public class PersistentCookieStore implements CookieStore { @@ -61,13 +61,13 @@ public PersistentCookieStore(Context context) { // Load any previously stored cookies into the store String storedCookieNames = cookiePrefs.getString(COOKIE_NAME_STORE, null); - if(storedCookieNames != null) { + if (storedCookieNames != null) { String[] cookieNames = TextUtils.split(storedCookieNames, ","); - for(String name : cookieNames) { + for (String name : cookieNames) { String encodedCookie = cookiePrefs.getString(COOKIE_NAME_PREFIX + name, null); - if(encodedCookie != null) { + if (encodedCookie != null) { Cookie decodedCookie = decodeCookie(encodedCookie); - if(decodedCookie != null) { + if (decodedCookie != null) { cookies.put(name, decodedCookie); } } @@ -83,7 +83,7 @@ public void addCookie(Cookie cookie) { String name = cookie.getName() + cookie.getDomain(); // Save cookie into local store, or remove if expired - if(!cookie.isExpired(new Date())) { + if (!cookie.isExpired(new Date())) { cookies.put(name, cookie); } else { cookies.remove(name); @@ -100,7 +100,7 @@ public void addCookie(Cookie cookie) { public void clear() { // Clear cookies from persistent store SharedPreferences.Editor prefsWriter = cookiePrefs.edit(); - for(String name : cookies.keySet()) { + for (String name : cookies.keySet()) { prefsWriter.remove(COOKIE_NAME_PREFIX + name); } prefsWriter.remove(COOKIE_NAME_STORE); @@ -115,10 +115,10 @@ public boolean clearExpired(Date date) { boolean clearedAny = false; SharedPreferences.Editor prefsWriter = cookiePrefs.edit(); - for(ConcurrentHashMap.Entry entry : cookies.entrySet()) { + for (ConcurrentHashMap.Entry entry : cookies.entrySet()) { String name = entry.getKey(); Cookie cookie = entry.getValue(); - if(cookie.isExpired(date)) { + if (cookie.isExpired(date)) { // Clear cookies from local store cookies.remove(name); @@ -131,7 +131,7 @@ public boolean clearExpired(Date date) { } // Update names in persistent store - if(clearedAny) { + if (clearedAny) { prefsWriter.putString(COOKIE_NAME_STORE, TextUtils.join(",", cookies.keySet())); } prefsWriter.commit(); @@ -166,10 +166,10 @@ protected Cookie decodeCookie(String cookieStr) { ByteArrayInputStream is = new ByteArrayInputStream(bytes); Cookie cookie = null; try { - ObjectInputStream ois = new ObjectInputStream(is); - cookie = ((SerializableCookie)ois.readObject()).getCookie(); + ObjectInputStream ois = new ObjectInputStream(is); + cookie = ((SerializableCookie) ois.readObject()).getCookie(); } catch (Exception e) { - e.printStackTrace(); + e.printStackTrace(); } return cookie; @@ -181,7 +181,7 @@ protected String byteArrayToHexString(byte[] b) { StringBuffer sb = new StringBuffer(b.length * 2); for (byte element : b) { int v = element & 0xff; - if(v < 16) { + if (v < 16) { sb.append('0'); } sb.append(Integer.toHexString(v)); @@ -192,8 +192,8 @@ protected String byteArrayToHexString(byte[] b) { protected byte[] hexStringToByteArray(String s) { int len = s.length(); byte[] data = new byte[len / 2]; - for(int i=0; i + *

    * For example: - *

    + *

    *

      * RequestParams params = new RequestParams();
      * params.put("username", "james");
    @@ -70,12 +70,13 @@ public RequestParams() {
         /**
          * Constructs a new RequestParams instance containing the key/value
          * string params from the specified map.
    +     *
          * @param source the source key/value string map to add.
          */
         public RequestParams(Map source) {
             init();
     
    -        for(Map.Entry entry : source.entrySet()) {
    +        for (Map.Entry entry : source.entrySet()) {
                 put(entry.getKey(), entry.getValue());
             }
         }
    @@ -83,7 +84,8 @@ public RequestParams(Map source) {
         /**
          * Constructs a new RequestParams instance and populate it with a single
          * initial key/value string param.
    -     * @param key the key name for the intial param.
    +     *
    +     * @param key   the key name for the intial param.
          * @param value the value string for the initial param.
          */
         public RequestParams(String key, String value) {
    @@ -95,36 +97,39 @@ public RequestParams(String key, String value) {
         /**
          * Constructs a new RequestParams instance and populate it with multiple
          * initial key/value string param.
    +     *
          * @param keysAndValues a sequence of keys and values. Objects are
    -     * automatically converted to Strings (including the value {@code null}).
    +     *                      automatically converted to Strings (including the value {@code null}).
          * @throws IllegalArgumentException if the number of arguments isn't even.
          */
         public RequestParams(Object... keysAndValues) {
    -      init();
    -      int len = keysAndValues.length;
    -      if (len % 2 != 0)
    -        throw new IllegalArgumentException("Supplied arguments must be even");
    -      for (int i = 0; i < len; i += 2) {
    -        String key = String.valueOf(keysAndValues[i]);
    -        String val = String.valueOf(keysAndValues[i + 1]);
    -        put(key, val);
    -      }
    +        init();
    +        int len = keysAndValues.length;
    +        if (len % 2 != 0)
    +            throw new IllegalArgumentException("Supplied arguments must be even");
    +        for (int i = 0; i < len; i += 2) {
    +            String key = String.valueOf(keysAndValues[i]);
    +            String val = String.valueOf(keysAndValues[i + 1]);
    +            put(key, val);
    +        }
         }
     
         /**
          * Adds a key/value string pair to the request.
    -     * @param key the key name for the new param.
    +     *
    +     * @param key   the key name for the new param.
          * @param value the value string for the new param.
          */
    -    public void put(String key, String value){
    -        if(key != null && value != null) {
    +    public void put(String key, String value) {
    +        if (key != null && value != null) {
                 urlParams.put(key, value);
             }
         }
     
         /**
          * Adds a file to the request.
    -     * @param key the key name for the new param.
    +     *
    +     * @param key  the key name for the new param.
          * @param file the file to add.
          */
         public void put(String key, File file) throws FileNotFoundException {
    @@ -133,22 +138,24 @@ public void put(String key, File file) throws FileNotFoundException {
     
         /**
          * Adds param with more than one value.
    -     * @param key the key name for the new param.
    +     *
    +     * @param key    the key name for the new param.
          * @param values is the ArrayList with values for the param.
          */
    -    public void put(String key, ArrayList values)  {
    -        if(key != null && values != null) {
    +    public void put(String key, ArrayList values) {
    +        if (key != null && values != null) {
                 urlParamsWithArray.put(key, values);
             }
         }
     
         /**
          * Adds value to param which can have more than one value.
    -     * @param key the key name for the param, either existing or new.
    +     *
    +     * @param key   the key name for the param, either existing or new.
          * @param value the value string for the new param.
          */
         public void add(String key, String value) {
    -        if(key != null && value != null) {
    +        if (key != null && value != null) {
                 ArrayList paramArray = urlParamsWithArray.get(key);
                 if (paramArray == null) {
                     paramArray = new ArrayList();
    @@ -160,7 +167,8 @@ public void add(String key, String value) {
     
         /**
          * Adds an input stream to the request.
    -     * @param key the key name for the new param.
    +     *
    +     * @param key    the key name for the new param.
          * @param stream the input stream to add.
          */
         public void put(String key, InputStream stream) {
    @@ -169,8 +177,9 @@ public void put(String key, InputStream stream) {
     
         /**
          * Adds an input stream to the request.
    -     * @param key the key name for the new param.
    -     * @param stream the input stream to add.
    +     *
    +     * @param key      the key name for the new param.
    +     * @param stream   the input stream to add.
          * @param fileName the name of the file.
          */
         public void put(String key, InputStream stream, String fileName) {
    @@ -179,22 +188,24 @@ public void put(String key, InputStream stream, String fileName) {
     
         /**
          * Adds an input stream to the request.
    -     * @param key the key name for the new param.
    -     * @param stream the input stream to add.
    -     * @param fileName the name of the file.
    +     *
    +     * @param key         the key name for the new param.
    +     * @param stream      the input stream to add.
    +     * @param fileName    the name of the file.
          * @param contentType the content type of the file, eg. application/json
          */
         public void put(String key, InputStream stream, String fileName, String contentType) {
    -        if(key != null && stream != null) {
    +        if (key != null && stream != null) {
                 fileParams.put(key, new FileWrapper(stream, fileName, contentType));
             }
         }
     
         /**
          * Removes a parameter from the request.
    +     *
          * @param key the key name for the parameter to remove.
          */
    -    public void remove(String key){
    +    public void remove(String key) {
             urlParams.remove(key);
             fileParams.remove(key);
             urlParamsWithArray.remove(key);
    @@ -203,8 +214,8 @@ public void remove(String key){
         @Override
         public String toString() {
             StringBuilder result = new StringBuilder();
    -        for(ConcurrentHashMap.Entry entry : urlParams.entrySet()) {
    -            if(result.length() > 0)
    +        for (ConcurrentHashMap.Entry entry : urlParams.entrySet()) {
    +            if (result.length() > 0)
                     result.append("&");
     
                 result.append(entry.getKey());
    @@ -212,8 +223,8 @@ public String toString() {
                 result.append(entry.getValue());
             }
     
    -        for(ConcurrentHashMap.Entry entry : fileParams.entrySet()) {
    -            if(result.length() > 0)
    +        for (ConcurrentHashMap.Entry entry : fileParams.entrySet()) {
    +            if (result.length() > 0)
                     result.append("&");
     
                 result.append(entry.getKey());
    @@ -221,8 +232,8 @@ public String toString() {
                 result.append("FILE");
             }
     
    -        for(ConcurrentHashMap.Entry> entry : urlParamsWithArray.entrySet()) {
    -            if(result.length() > 0)
    +        for (ConcurrentHashMap.Entry> entry : urlParamsWithArray.entrySet()) {
    +            if (result.length() > 0)
                     result.append("&");
     
                 ArrayList values = entry.getValue();
    @@ -238,22 +249,22 @@ public String toString() {
             return result.toString();
         }
     
    -   /**
    +    /**
          * Returns an HttpEntity containing all request parameters
          */
         public HttpEntity getEntity() {
             HttpEntity entity = null;
     
    -        if(!fileParams.isEmpty()) {
    +        if (!fileParams.isEmpty()) {
                 SimpleMultipartEntity multipartEntity = new SimpleMultipartEntity();
     
                 // Add string params
    -            for(ConcurrentHashMap.Entry entry : urlParams.entrySet()) {
    +            for (ConcurrentHashMap.Entry entry : urlParams.entrySet()) {
                     multipartEntity.addPart(entry.getKey(), entry.getValue());
                 }
     
                 // Add dupe params
    -            for(ConcurrentHashMap.Entry> entry : urlParamsWithArray.entrySet()) {
    +            for (ConcurrentHashMap.Entry> entry : urlParamsWithArray.entrySet()) {
                     ArrayList values = entry.getValue();
                     for (String value : values) {
                         multipartEntity.addPart(entry.getKey(), value);
    @@ -263,11 +274,11 @@ public HttpEntity getEntity() {
                 // Add file params
                 int currentIndex = 0;
                 int lastIndex = fileParams.entrySet().size() - 1;
    -            for(ConcurrentHashMap.Entry entry : fileParams.entrySet()) {
    +            for (ConcurrentHashMap.Entry entry : fileParams.entrySet()) {
                     FileWrapper file = entry.getValue();
    -                if(file.inputStream != null) {
    +                if (file.inputStream != null) {
                         boolean isLast = currentIndex == lastIndex;
    -                    if(file.contentType != null) {
    +                    if (file.contentType != null) {
                             multipartEntity.addPart(entry.getKey(), file.getFileName(), file.inputStream, file.contentType, isLast);
                         } else {
                             multipartEntity.addPart(entry.getKey(), file.getFileName(), file.inputStream, isLast);
    @@ -288,7 +299,7 @@ public HttpEntity getEntity() {
             return entity;
         }
     
    -    private void init(){
    +    private void init() {
             urlParams = new ConcurrentHashMap();
             fileParams = new ConcurrentHashMap();
             urlParamsWithArray = new ConcurrentHashMap>();
    @@ -297,11 +308,11 @@ private void init(){
         protected List getParamsList() {
             List lparams = new LinkedList();
     
    -        for(ConcurrentHashMap.Entry entry : urlParams.entrySet()) {
    +        for (ConcurrentHashMap.Entry entry : urlParams.entrySet()) {
                 lparams.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));
             }
     
    -        for(ConcurrentHashMap.Entry> entry : urlParamsWithArray.entrySet()) {
    +        for (ConcurrentHashMap.Entry> entry : urlParamsWithArray.entrySet()) {
                 ArrayList values = entry.getValue();
                 for (String value : values) {
                     lparams.add(new BasicNameValuePair(entry.getKey(), value));
    @@ -327,7 +338,7 @@ public FileWrapper(InputStream inputStream, String fileName, String contentType)
             }
     
             public String getFileName() {
    -            if(fileName != null) {
    +            if (fileName != null) {
                     return fileName;
                 } else {
                     return "nofilename";
    diff --git a/library/src/com/loopj/android/http/RetryHandler.java b/library/src/com/loopj/android/http/RetryHandler.java
    index 5256aad21..310756d4f 100644
    --- a/library/src/com/loopj/android/http/RetryHandler.java
    +++ b/library/src/com/loopj/android/http/RetryHandler.java
    @@ -23,6 +23,14 @@
     
     package com.loopj.android.http;
     
    +import android.os.SystemClock;
    +
    +import org.apache.http.NoHttpResponseException;
    +import org.apache.http.client.HttpRequestRetryHandler;
    +import org.apache.http.client.methods.HttpUriRequest;
    +import org.apache.http.protocol.ExecutionContext;
    +import org.apache.http.protocol.HttpContext;
    +
     import java.io.IOException;
     import java.io.InterruptedIOException;
     import java.net.SocketException;
    @@ -32,14 +40,6 @@
     
     import javax.net.ssl.SSLException;
     
    -import org.apache.http.NoHttpResponseException;
    -import org.apache.http.client.HttpRequestRetryHandler;
    -import org.apache.http.client.methods.HttpUriRequest;
    -import org.apache.http.protocol.ExecutionContext;
    -import org.apache.http.protocol.HttpContext;
    -
    -import android.os.SystemClock;
    -
     class RetryHandler implements HttpRequestRetryHandler {
         private static final int RETRY_SLEEP_TIME_MILLIS = 1500;
         private static HashSet> exceptionWhitelist = new HashSet>();
    @@ -72,7 +72,7 @@ public boolean retryRequest(IOException exception, int executionCount, HttpConte
             Boolean b = (Boolean) context.getAttribute(ExecutionContext.HTTP_REQ_SENT);
             boolean sent = (b != null && b.booleanValue());
     
    -        if(executionCount > maxRetries) {
    +        if (executionCount > maxRetries) {
                 // Do not retry if over max retry count
                 retry = false;
             } else if (isInList(exceptionBlacklist, exception)) {
    @@ -86,14 +86,14 @@ public boolean retryRequest(IOException exception, int executionCount, HttpConte
                 retry = true;
             }
     
    -        if(retry) {
    +        if (retry) {
                 // resend all idempotent requests
    -            HttpUriRequest currentReq = (HttpUriRequest) context.getAttribute( ExecutionContext.HTTP_REQUEST );
    +            HttpUriRequest currentReq = (HttpUriRequest) context.getAttribute(ExecutionContext.HTTP_REQUEST);
                 String requestType = currentReq.getMethod();
                 retry = !requestType.equals("POST");
             }
     
    -        if(retry) {
    +        if (retry) {
                 SystemClock.sleep(RETRY_SLEEP_TIME_MILLIS);
             } else {
                 exception.printStackTrace();
    @@ -101,14 +101,14 @@ public boolean retryRequest(IOException exception, int executionCount, HttpConte
     
             return retry;
         }
    -    
    +
         protected boolean isInList(HashSet> list, Throwable error) {
    -    	Iterator> itr = list.iterator();
    -    	while (itr.hasNext()) {
    -    		if (itr.next().isInstance(error)) {
    -    			return true;
    -    		}
    -    	}
    -    	return false;
    +        Iterator> itr = list.iterator();
    +        while (itr.hasNext()) {
    +            if (itr.next().isInstance(error)) {
    +                return true;
    +            }
    +        }
    +        return false;
         }
     }
    \ No newline at end of file
    diff --git a/library/src/com/loopj/android/http/SerializableCookie.java b/library/src/com/loopj/android/http/SerializableCookie.java
    index cc12993c2..d9730d212 100644
    --- a/library/src/com/loopj/android/http/SerializableCookie.java
    +++ b/library/src/com/loopj/android/http/SerializableCookie.java
    @@ -18,15 +18,15 @@
     
     package com.loopj.android.http;
     
    -import java.io.Serializable;
    +import org.apache.http.cookie.Cookie;
    +import org.apache.http.impl.cookie.BasicClientCookie;
    +
    +import java.io.IOException;
     import java.io.ObjectInputStream;
     import java.io.ObjectOutputStream;
    -import java.io.IOException;
    +import java.io.Serializable;
     import java.util.Date;
     
    -import org.apache.http.cookie.Cookie;
    -import org.apache.http.impl.cookie.BasicClientCookie;
    -
     /**
      * A wrapper class around {@link Cookie} and/or {@link BasicClientCookie}
      * designed for use in {@link PersistentCookieStore}.
    @@ -43,7 +43,7 @@ public SerializableCookie(Cookie cookie) {
     
         public Cookie getCookie() {
             Cookie bestCookie = cookie;
    -        if(clientCookie != null) {
    +        if (clientCookie != null) {
                 bestCookie = clientCookie;
             }
             return bestCookie;
    @@ -61,13 +61,13 @@ private void writeObject(ObjectOutputStream out) throws IOException {
         }
     
         private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
    -        String name = (String)in.readObject();
    -        String value = (String)in.readObject();
    +        String name = (String) in.readObject();
    +        String value = (String) in.readObject();
             clientCookie = new BasicClientCookie(name, value);
    -        clientCookie.setComment((String)in.readObject());
    -        clientCookie.setDomain((String)in.readObject());
    -        clientCookie.setExpiryDate((Date)in.readObject());
    -        clientCookie.setPath((String)in.readObject());
    +        clientCookie.setComment((String) in.readObject());
    +        clientCookie.setDomain((String) in.readObject());
    +        clientCookie.setExpiryDate((Date) in.readObject());
    +        clientCookie.setPath((String) in.readObject());
             clientCookie.setVersion(in.readInt());
             clientCookie.setSecure(in.readBoolean());
         }
    diff --git a/library/src/com/loopj/android/http/SimpleMultipartEntity.java b/library/src/com/loopj/android/http/SimpleMultipartEntity.java
    index 3221ec1eb..718226751 100644
    --- a/library/src/com/loopj/android/http/SimpleMultipartEntity.java
    +++ b/library/src/com/loopj/android/http/SimpleMultipartEntity.java
    @@ -23,20 +23,20 @@
     
     package com.loopj.android.http;
     
    +import org.apache.http.Header;
    +import org.apache.http.HttpEntity;
    +import org.apache.http.message.BasicHeader;
    +
     import java.io.ByteArrayInputStream;
     import java.io.ByteArrayOutputStream;
     import java.io.File;
     import java.io.FileInputStream;
     import java.io.FileNotFoundException;
    -import java.io.InputStream;
     import java.io.IOException;
    +import java.io.InputStream;
     import java.io.OutputStream;
     import java.util.Random;
     
    -import org.apache.http.Header;
    -import org.apache.http.HttpEntity;
    -import org.apache.http.message.BasicHeader;
    -
     class SimpleMultipartEntity implements HttpEntity {
         private final static char[] MULTIPART_CHARS = "-_1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray();
     
    @@ -56,8 +56,8 @@ public SimpleMultipartEntity() {
     
         }
     
    -    public void writeFirstBoundaryIfNeeds(){
    -        if(!isSetFirst){
    +    public void writeFirstBoundaryIfNeeds() {
    +        if (!isSetFirst) {
                 writeBoundary();
             }
     
    @@ -73,7 +73,7 @@ public void writeBoundary() {
         }
     
         public void writeLastBoundaryIfNeeds() {
    -        if(isSetLast){
    +        if (isSetLast) {
                 return;
             }
     
    @@ -83,14 +83,14 @@ public void writeLastBoundaryIfNeeds() {
             } catch (final IOException e) {
                 e.printStackTrace();
             }
    -        
    +
             isSetLast = true;
         }
     
         public void addPart(final String key, final String value, final String contentType) {
             writeBoundary();
             try {
    -            out.write(("Content-Disposition: form-data; name=\"" +key+"\"\r\n").getBytes());
    +            out.write(("Content-Disposition: form-data; name=\"" + key + "\"\r\n").getBytes());
                 out.write(("Content-Type: " + contentType + "\r\n\r\n").getBytes());
                 out.write(value.getBytes());
                 out.write(("\r\n").getBytes());
    @@ -100,18 +100,18 @@ public void addPart(final String key, final String value, final String contentTy
         }
     
         public void addPart(final String key, final String value) {
    -        addPart(key,value,"text/plain; charset=UTF-8");
    +        addPart(key, value, "text/plain; charset=UTF-8");
         }
     
    -    public void addPart(final String key, final String fileName, final InputStream fin, final boolean isLast){
    +    public void addPart(final String key, final String fileName, final InputStream fin, final boolean isLast) {
             addPart(key, fileName, fin, "application/octet-stream", isLast);
         }
     
    -    public void addPart(final String key, final String fileName, final InputStream fin, String type, final boolean isLast){
    +    public void addPart(final String key, final String fileName, final InputStream fin, String type, final boolean isLast) {
             writeBoundary();
             try {
    -            type = "Content-Type: "+type+"\r\n";
    -            out.write(("Content-Disposition: form-data; name=\""+ key+"\"; filename=\"" + fileName + "\"\r\n").getBytes());
    +            type = "Content-Type: " + type + "\r\n";
    +            out.write(("Content-Disposition: form-data; name=\"" + key + "\"; filename=\"" + fileName + "\"\r\n").getBytes());
                 out.write(type.getBytes());
                 out.write("Content-Transfer-Encoding: binary\r\n\r\n".getBytes());
     
    @@ -121,7 +121,7 @@ public void addPart(final String key, final String fileName, final InputStream f
                     out.write(tmp, 0, l);
                 }
                 out.write(("\r\n").getBytes());
    -            
    +
             } catch (final IOException e) {
                 e.printStackTrace();
             } finally {
    @@ -180,17 +180,17 @@ public Header getContentEncoding() {
     
         @Override
         public void consumeContent() throws IOException,
    -    UnsupportedOperationException {
    +            UnsupportedOperationException {
             if (isStreaming()) {
                 throw new UnsupportedOperationException(
    -            "Streaming entity does not implement #consumeContent()");
    +                    "Streaming entity does not implement #consumeContent()");
             }
         }
     
         @Override
         public InputStream getContent() throws IOException,
    -    UnsupportedOperationException {
    -    	writeLastBoundaryIfNeeds();
    +            UnsupportedOperationException {
    +        writeLastBoundaryIfNeeds();
             return new ByteArrayInputStream(out.toByteArray());
         }
     }
    \ No newline at end of file
    diff --git a/library/src/com/loopj/android/http/SyncHttpClient.java b/library/src/com/loopj/android/http/SyncHttpClient.java
    index 1fd4441f3..428dc2114 100644
    --- a/library/src/com/loopj/android/http/SyncHttpClient.java
    +++ b/library/src/com/loopj/android/http/SyncHttpClient.java
    @@ -1,123 +1,125 @@
     package com.loopj.android.http;
     
    +import android.content.Context;
    +import android.os.Message;
    +
     import org.apache.http.client.methods.HttpUriRequest;
     import org.apache.http.impl.client.DefaultHttpClient;
     import org.apache.http.protocol.HttpContext;
     
    -import android.content.Context;
    -import android.os.Message;
    -
     public abstract class SyncHttpClient extends AsyncHttpClient {
    -	private int responseCode;
    -	/*
    -	 * as this is a synchronous request this is just a helping mechanism to pass
    -	 * the result back to this method. Therefore the result object has to be a
    -	 * field to be accessible
    -	 */
    -	protected String result;
    -	protected AsyncHttpResponseHandler responseHandler = new AsyncHttpResponseHandler() {
    +    private int responseCode;
    +    /*
    +     * as this is a synchronous request this is just a helping mechanism to pass
    +     * the result back to this method. Therefore the result object has to be a
    +     * field to be accessible
    +     */
    +    protected String result;
    +    protected AsyncHttpResponseHandler responseHandler = new AsyncHttpResponseHandler() {
     
             @Override
             void sendResponseMessage(org.apache.http.HttpResponse response) {
    -			responseCode = response.getStatusLine().getStatusCode();
    -			super.sendResponseMessage(response);
    -		};
    +            responseCode = response.getStatusLine().getStatusCode();
    +            super.sendResponseMessage(response);
    +        }
     
    -		@Override
    -		protected void sendMessage(Message msg) {
    -			/*
    +        ;
    +
    +        @Override
    +        protected void sendMessage(Message msg) {
    +            /*
     			 * Dont use the handler and send it directly to the analysis
     			 * (because its all the same thread)
     			 */
    -			handleMessage(msg);
    -		}
    -
    -		@Override
    -		public void onSuccess(String content) {
    -			result = content;
    -		}
    -
    -		@Override
    -		public void onFailure(Throwable error, String content) {
    -			result = onRequestFailed(error, content);
    -		}
    -	};
    -
    -	/**
    -	 * @return the response code for the last request, might be usefull
    -	 *         sometimes
    -	 */
    -	public int getResponseCode() {
    -		return responseCode;
    -	}
    -
    -	// Private stuff
    +            handleMessage(msg);
    +        }
    +
    +        @Override
    +        public void onSuccess(String content) {
    +            result = content;
    +        }
    +
    +        @Override
    +        public void onFailure(Throwable error, String content) {
    +            result = onRequestFailed(error, content);
    +        }
    +    };
    +
    +    /**
    +     * @return the response code for the last request, might be usefull
    +     * sometimes
    +     */
    +    public int getResponseCode() {
    +        return responseCode;
    +    }
    +
    +    // Private stuff
         @Override
         protected void sendRequest(DefaultHttpClient client,
    -			HttpContext httpContext, HttpUriRequest uriRequest,
    -			String contentType, AsyncHttpResponseHandler responseHandler,
    -			Context context) {
    -		if (contentType != null) {
    -			uriRequest.addHeader("Content-Type", contentType);
    -		}
    +                               HttpContext httpContext, HttpUriRequest uriRequest,
    +                               String contentType, AsyncHttpResponseHandler responseHandler,
    +                               Context context) {
    +        if (contentType != null) {
    +            uriRequest.addHeader("Content-Type", contentType);
    +        }
     
     		/*
     		 * will execute the request directly
     		 */
    -		new AsyncHttpRequest(client, httpContext, uriRequest, responseHandler)
    -				.run();
    -	}
    +        new AsyncHttpRequest(client, httpContext, uriRequest, responseHandler)
    +                .run();
    +    }
     
    -	public abstract String onRequestFailed(Throwable error, String content);
    +    public abstract String onRequestFailed(Throwable error, String content);
     
    -	public void delete(String url, RequestParams queryParams,
    -			AsyncHttpResponseHandler responseHandler) {
    -		// TODO what about query params??
    -		delete(url, responseHandler);
    -	}
    +    public void delete(String url, RequestParams queryParams,
    +                       AsyncHttpResponseHandler responseHandler) {
    +        // TODO what about query params??
    +        delete(url, responseHandler);
    +    }
     
    -	public String get(String url, RequestParams params) {
    -		this.get(url, params, responseHandler);
    +    public String get(String url, RequestParams params) {
    +        this.get(url, params, responseHandler);
     		/*
     		 * the response handler will have set the result when this line is
     		 * reached
     		 */
    -		return result;
    -	}
    -
    -	public String get(String url) {
    -		this.get(url, null, responseHandler);
    -		return result;
    -	}
    -
    -	public String put(String url, RequestParams params) {
    -		this.put(url, params, responseHandler);
    -		return result;
    -	}
    -
    -	public String put(String url) {
    -		this.put(url, null, responseHandler);
    -		return result;
    -	}
    -
    -	public String post(String url, RequestParams params) {
    -		this.post(url, params, responseHandler);
    -		return result;
    -	}
    -
    -	public String post(String url) {
    -		this.post(url, null, responseHandler);
    -		return result;
    -	}
    -
    -	public String delete(String url, RequestParams params) {
    -		this.delete(url, params, responseHandler);
    -		return result;
    -	}
    -
    -	public String delete(String url) {
    -		this.delete(url, null, responseHandler);
    -		return result;
    -	}
    +        return result;
    +    }
    +
    +    public String get(String url) {
    +        this.get(url, null, responseHandler);
    +        return result;
    +    }
    +
    +    public String put(String url, RequestParams params) {
    +        this.put(url, params, responseHandler);
    +        return result;
    +    }
    +
    +    public String put(String url) {
    +        this.put(url, null, responseHandler);
    +        return result;
    +    }
    +
    +    public String post(String url, RequestParams params) {
    +        this.post(url, params, responseHandler);
    +        return result;
    +    }
    +
    +    public String post(String url) {
    +        this.post(url, null, responseHandler);
    +        return result;
    +    }
    +
    +    public String delete(String url, RequestParams params) {
    +        this.delete(url, params, responseHandler);
    +        return result;
    +    }
    +
    +    public String delete(String url) {
    +        this.delete(url, null, responseHandler);
    +        return result;
    +    }
     
     }
    
    From fc5862e67bef5c32fe7e6d236320be0551a91eab Mon Sep 17 00:00:00 2001
    From: mareksebera 
    Date: Wed, 9 Oct 2013 02:19:06 +0200
    Subject: [PATCH 051/613] Fixes #87
    
    ---
     .../src/com/loopj/android/http/RequestParams.java    | 12 ++++++++++++
     1 file changed, 12 insertions(+)
    
    diff --git a/library/src/com/loopj/android/http/RequestParams.java b/library/src/com/loopj/android/http/RequestParams.java
    index 980ff0829..035ffba51 100644
    --- a/library/src/com/loopj/android/http/RequestParams.java
    +++ b/library/src/com/loopj/android/http/RequestParams.java
    @@ -126,6 +126,18 @@ public void put(String key, String value) {
             }
         }
     
    +    /**
    +     * Adds a integer param to the request.
    +     *
    +     * @param key   the key name for the new param.
    +     * @param value the integer value for the new param.
    +     */
    +    public void put(String key, int value) {
    +        if (key != null) {
    +            urlParams.put(key, String.valueOf(value));
    +        }
    +    }
    +
         /**
          * Adds a file to the request.
          *
    
    From d8903be08c0c3ddfd5faacda38daf46b683cb156 Mon Sep 17 00:00:00 2001
    From: mareksebera 
    Date: Wed, 9 Oct 2013 02:24:04 +0200
    Subject: [PATCH 052/613] Fixes #83
    
    ---
     library/src/com/loopj/android/http/AsyncHttpClient.java         | 2 +-
     .../src/com/loopj/android/http/AsyncHttpResponseHandler.java    | 2 +-
     .../src/com/loopj/android/http/BinaryHttpResponseHandler.java   | 2 +-
     library/src/com/loopj/android/http/SyncHttpClient.java          | 2 +-
     4 files changed, 4 insertions(+), 4 deletions(-)
    
    diff --git a/library/src/com/loopj/android/http/AsyncHttpClient.java b/library/src/com/loopj/android/http/AsyncHttpClient.java
    index daa958e78..cb63854d4 100644
    --- a/library/src/com/loopj/android/http/AsyncHttpClient.java
    +++ b/library/src/com/loopj/android/http/AsyncHttpClient.java
    @@ -221,7 +221,7 @@ public void setUserAgent(String userAgent) {
         }
     
         /**
    -     * Sets the connection time oout. By default, 10 seconds
    +     * Set the connection timeout. By default, 10 seconds.
          *
          * @param timeout the connect/socket timeout in milliseconds
          */
    diff --git a/library/src/com/loopj/android/http/AsyncHttpResponseHandler.java b/library/src/com/loopj/android/http/AsyncHttpResponseHandler.java
    index 9bffb1c09..d57fba7c6 100644
    --- a/library/src/com/loopj/android/http/AsyncHttpResponseHandler.java
    +++ b/library/src/com/loopj/android/http/AsyncHttpResponseHandler.java
    @@ -239,7 +239,7 @@ protected Message obtainMessage(int responseMessage, Object response) {
         }
     
         // Interface to AsyncHttpRequest
    -    void sendResponseMessage(HttpResponse response) {
    +    protected void sendResponseMessage(HttpResponse response) {
             StatusLine status = response.getStatusLine();
             String responseBody = null;
             try {
    diff --git a/library/src/com/loopj/android/http/BinaryHttpResponseHandler.java b/library/src/com/loopj/android/http/BinaryHttpResponseHandler.java
    index fb8e980db..28ebe9638 100644
    --- a/library/src/com/loopj/android/http/BinaryHttpResponseHandler.java
    +++ b/library/src/com/loopj/android/http/BinaryHttpResponseHandler.java
    @@ -161,7 +161,7 @@ protected void handleMessage(Message msg) {
     
         // Interface to AsyncHttpRequest
         @Override
    -    void sendResponseMessage(HttpResponse response) {
    +    protected void sendResponseMessage(HttpResponse response) {
             StatusLine status = response.getStatusLine();
             Header[] contentTypeHeaders = response.getHeaders("Content-Type");
             byte[] responseBody = null;
    diff --git a/library/src/com/loopj/android/http/SyncHttpClient.java b/library/src/com/loopj/android/http/SyncHttpClient.java
    index 428dc2114..8b7750982 100644
    --- a/library/src/com/loopj/android/http/SyncHttpClient.java
    +++ b/library/src/com/loopj/android/http/SyncHttpClient.java
    @@ -18,7 +18,7 @@ public abstract class SyncHttpClient extends AsyncHttpClient {
         protected AsyncHttpResponseHandler responseHandler = new AsyncHttpResponseHandler() {
     
             @Override
    -        void sendResponseMessage(org.apache.http.HttpResponse response) {
    +        protected void sendResponseMessage(org.apache.http.HttpResponse response) {
                 responseCode = response.getStatusLine().getStatusCode();
                 super.sendResponseMessage(response);
             }
    
    From 55318eb470cd694c82740e70ae6d1818f86dd32d Mon Sep 17 00:00:00 2001
    From: mareksebera 
    Date: Wed, 9 Oct 2013 02:27:40 +0200
    Subject: [PATCH 053/613] Fixes #77
    
    ---
     library/src/com/loopj/android/http/JsonHttpResponseHandler.java | 2 ++
     1 file changed, 2 insertions(+)
    
    diff --git a/library/src/com/loopj/android/http/JsonHttpResponseHandler.java b/library/src/com/loopj/android/http/JsonHttpResponseHandler.java
    index 12516f81d..6586eb47c 100644
    --- a/library/src/com/loopj/android/http/JsonHttpResponseHandler.java
    +++ b/library/src/com/loopj/android/http/JsonHttpResponseHandler.java
    @@ -191,6 +191,8 @@ protected void handleFailureMessage(Throwable e, String responseBody) {
                         onFailure(e, (JSONObject) jsonResponse);
                     } else if (jsonResponse instanceof JSONArray) {
                         onFailure(e, (JSONArray) jsonResponse);
    +                } else if (jsonResponse instanceof String) {
    +                    onFailure(e, (String) jsonResponse);
                     } else {
                         onFailure(e, responseBody);
                     }
    
    From 5ffb0b076c931827c8d6a3c45604b69e9bfa9687 Mon Sep 17 00:00:00 2001
    From: Marek Sebera 
    Date: Wed, 9 Oct 2013 02:33:04 +0200
    Subject: [PATCH 054/613] Added Travis CI to Readme [ci skip]
    
    ---
     README.md | 4 +++-
     1 file changed, 3 insertions(+), 1 deletion(-)
    
    diff --git a/README.md b/README.md
    index 1602a5343..3d7bd95ed 100644
    --- a/README.md
    +++ b/README.md
    @@ -1,6 +1,8 @@
     Asynchronous Http Client for Android
     ====================================
     
    +Travis CI state : [![Build Status](https://travis-ci.org/loopj/android-async-http.png?branch=master)](https://travis-ci.org/loopj/android-async-http)
    +
     An asynchronous, callback-based Http client for Android built on top of Apache's [HttpClient](http://hc.apache.org/httpcomponents-client-ga/) libraries.
     
     
    @@ -22,4 +24,4 @@ Documentation, Features and Examples
     ------------------------------------
     Full details and documentation can be found on the project page here:
     
    -http://loopj.com/android-async-http/
    \ No newline at end of file
    +http://loopj.com/android-async-http/
    
    From 9b79fb6a8d67a3868c2a679c9a2bb9b11cea1ea1 Mon Sep 17 00:00:00 2001
    From: mareksebera 
    Date: Wed, 9 Oct 2013 02:52:10 +0200
    Subject: [PATCH 055/613] Added FileAsyncHttpResponseHandler, Closes #134
    
    ---
     .../http/FileAsyncHttpResponseHandler.java    | 96 +++++++++++++++++++
     1 file changed, 96 insertions(+)
     create mode 100644 library/src/com/loopj/android/http/FileAsyncHttpResponseHandler.java
    
    diff --git a/library/src/com/loopj/android/http/FileAsyncHttpResponseHandler.java b/library/src/com/loopj/android/http/FileAsyncHttpResponseHandler.java
    new file mode 100644
    index 000000000..8b9d549f4
    --- /dev/null
    +++ b/library/src/com/loopj/android/http/FileAsyncHttpResponseHandler.java
    @@ -0,0 +1,96 @@
    +package com.loopj.android.http;
    +
    +import android.os.Message;
    +
    +import org.apache.http.HttpResponse;
    +import org.apache.http.StatusLine;
    +import org.apache.http.client.HttpResponseException;
    +
    +import java.io.File;
    +import java.io.FileOutputStream;
    +import java.io.IOException;
    +import java.io.InputStream;
    +
    +
    +public class FileAsyncHttpResponseHandler extends AsyncHttpResponseHandler {
    +
    +    private File mFile;
    +
    +    public FileAsyncHttpResponseHandler(File file) {
    +        super();
    +        this.mFile = file;
    +    }
    +
    +    public void onSuccess(File file) {
    +    }
    +
    +    public void onSuccess(int statusCode, File file) {
    +        onSuccess(file);
    +    }
    +
    +    public void onFailure(Throwable e, File response) {
    +    }
    +
    +
    +    protected void sendSuccessMessage(int statusCode, File file) {
    +        sendMessage(obtainMessage(SUCCESS_MESSAGE, new Object[]{statusCode, file}));
    +    }
    +
    +    protected void sendFailureMessage(Throwable e, File file) {
    +        sendMessage(obtainMessage(FAILURE_MESSAGE, new Object[]{e, file}));
    +    }
    +
    +    protected void handleSuccessMessage(int statusCode, File responseBody) {
    +        onSuccess(statusCode, responseBody);
    +    }
    +
    +    protected void handleFailureMessage(Throwable e, File responseBody) {
    +        onFailure(e, responseBody);
    +    }
    +
    +    // Methods which emulate android's Handler and Message methods
    +    protected void handleMessage(Message msg) {
    +        Object[] response;
    +        switch (msg.what) {
    +            case SUCCESS_MESSAGE:
    +                response = (Object[]) msg.obj;
    +                handleSuccessMessage(((Integer) response[0]).intValue(), (File) response[1]);
    +                break;
    +            case FAILURE_MESSAGE:
    +                response = (Object[]) msg.obj;
    +                handleFailureMessage((Throwable) response[0], (File) response[1]);
    +                break;
    +            default:
    +                super.handleMessage(msg);
    +                break;
    +        }
    +    }
    +
    +    @Override
    +    protected void sendResponseMessage(HttpResponse response) {
    +        StatusLine status = response.getStatusLine();
    +
    +        try {
    +            FileOutputStream buffer = new FileOutputStream(this.mFile);
    +            InputStream is = response.getEntity().getContent();
    +
    +            int nRead;
    +            byte[] data = new byte[16384];
    +
    +            while ((nRead = is.read(data, 0, data.length)) != -1)
    +                buffer.write(data, 0, nRead);
    +
    +            buffer.flush();
    +            buffer.close();
    +
    +        } catch (IOException e) {
    +            sendFailureMessage(e, this.mFile);
    +        }
    +
    +        if (status.getStatusCode() >= 300) {
    +            sendFailureMessage(new HttpResponseException(status.getStatusCode(), status.getReasonPhrase()), this.mFile);
    +        } else {
    +            sendSuccessMessage(status.getStatusCode(), this.mFile);
    +        }
    +    }
    +}
    \ No newline at end of file
    
    From 460c0c23730ee02f7c17482cfe5f6d93f3b79f13 Mon Sep 17 00:00:00 2001
    From: mareksebera 
    Date: Wed, 9 Oct 2013 03:02:09 +0200
    Subject: [PATCH 056/613] Javadoc fixed references
    
    ---
     library/src/com/loopj/android/http/AsyncHttpClient.java | 8 ++++----
     1 file changed, 4 insertions(+), 4 deletions(-)
    
    diff --git a/library/src/com/loopj/android/http/AsyncHttpClient.java b/library/src/com/loopj/android/http/AsyncHttpClient.java
    index cb63854d4..3d2bd6f3b 100644
    --- a/library/src/com/loopj/android/http/AsyncHttpClient.java
    +++ b/library/src/com/loopj/android/http/AsyncHttpClient.java
    @@ -259,9 +259,9 @@ public void addHeader(String header, String value) {
          * @param username
          * @param password
          */
    -    public void setBasicAuth(String user, String pass) {
    +    public void setBasicAuth(String username, String password) {
             AuthScope scope = AuthScope.ANY;
    -        setBasicAuth(user, pass, scope);
    +        setBasicAuth(username, password, scope);
         }
     
         /**
    @@ -272,8 +272,8 @@ public void setBasicAuth(String user, String pass) {
          * @param password
          * @param scope    - an AuthScope object
          */
    -    public void setBasicAuth(String user, String pass, AuthScope scope) {
    -        UsernamePasswordCredentials credentials = new UsernamePasswordCredentials(user, pass);
    +    public void setBasicAuth(String username, String password, AuthScope scope) {
    +        UsernamePasswordCredentials credentials = new UsernamePasswordCredentials(username, password);
             this.httpClient.getCredentialsProvider().setCredentials(scope, credentials);
         }
     
    
    From 8c06d353e88d6309c0dab21baea3607a3c4aed21 Mon Sep 17 00:00:00 2001
    From: mareksebera 
    Date: Wed, 9 Oct 2013 03:02:56 +0200
    Subject: [PATCH 057/613] Fixes #192
    
    ---
     library/src/com/loopj/android/http/AsyncHttpRequest.java | 5 +++++
     1 file changed, 5 insertions(+)
    
    diff --git a/library/src/com/loopj/android/http/AsyncHttpRequest.java b/library/src/com/loopj/android/http/AsyncHttpRequest.java
    index 48145eb66..f7d9c6fb2 100644
    --- a/library/src/com/loopj/android/http/AsyncHttpRequest.java
    +++ b/library/src/com/loopj/android/http/AsyncHttpRequest.java
    @@ -21,6 +21,7 @@
     import org.apache.http.HttpResponse;
     import org.apache.http.client.HttpRequestRetryHandler;
     import org.apache.http.client.methods.HttpUriRequest;
    +import org.apache.http.conn.ConnectTimeoutException;
     import org.apache.http.impl.client.AbstractHttpClient;
     import org.apache.http.protocol.HttpContext;
     
    @@ -106,6 +107,10 @@ private void makeRequestWithRetries() throws ConnectException {
                         responseHandler.sendFailureMessage(e, "can't resolve host");
                     }
                     return;
    +            } catch (ConnectTimeoutException e) {
    +                if (responseHandler != null) {
    +                    responseHandler.sendFailureMessage(e, "connection timed out");
    +                }
                 } catch (SocketException e) {
                     // Added to detect host unreachable
                     if (responseHandler != null) {
    
    From 47c849bc48ddad8f152534c561ae8dc8612f3bd5 Mon Sep 17 00:00:00 2001
    From: mareksebera 
    Date: Wed, 9 Oct 2013 03:25:44 +0200
    Subject: [PATCH 058/613] Added HEAD request support, Closes #307, Fixes #146
    
    ---
     .../loopj/android/http/AsyncHttpClient.java   | 62 +++++++++++++++++++
     .../http/BinaryHttpResponseHandler.java       |  2 +-
     2 files changed, 63 insertions(+), 1 deletion(-)
    
    diff --git a/library/src/com/loopj/android/http/AsyncHttpClient.java b/library/src/com/loopj/android/http/AsyncHttpClient.java
    index 3d2bd6f3b..0b7d16731 100644
    --- a/library/src/com/loopj/android/http/AsyncHttpClient.java
    +++ b/library/src/com/loopj/android/http/AsyncHttpClient.java
    @@ -35,6 +35,7 @@
     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.HttpHead;
     import org.apache.http.client.methods.HttpPost;
     import org.apache.http.client.methods.HttpPut;
     import org.apache.http.client.methods.HttpUriRequest;
    @@ -302,6 +303,67 @@ public void cancelRequests(Context context, boolean mayInterruptIfRunning) {
             requestMap.remove(context);
         }
     
    +    //
    +    // HTTP HEAD Requests
    +    //
    +
    +    /**
    +     * Perform a HTTP HEAD request, without any parameters.
    +     * @param url the URL to send the request to.
    +     * @param responseHandler the response handler instance that should handle the response.
    +     */
    +    public void head(String url, AsyncHttpResponseHandler responseHandler) {
    +        head(null, url, null, responseHandler);
    +    }
    +
    +    /**
    +     * Perform a HTTP HEAD request with parameters.
    +     * @param url the URL to send the request to.
    +     * @param params additional HEAD parameters to send with the request.
    +     * @param responseHandler the response handler instance that should handle the response.
    +     */
    +    public void head(String url, RequestParams params, AsyncHttpResponseHandler responseHandler) {
    +        head(null, url, params, responseHandler);
    +    }
    +
    +    /**
    +     * Perform a HTTP HEAD request without any parameters and track the Android Context which initiated the request.
    +     * @param context the Android Context which initiated the request.
    +     * @param url the URL to send the request to.
    +     * @param responseHandler the response handler instance that should handle the response.
    +     */
    +    public void head(Context context, String url, AsyncHttpResponseHandler responseHandler) {
    +        head(context, url, null, responseHandler);
    +    }
    +
    +    /**
    +     * Perform a HTTP HEAD request and track the Android Context which initiated the request.
    +     * @param context the Android Context which initiated the request.
    +     * @param url the URL to send the request to.
    +     * @param params additional HEAD parameters to send with the request.
    +     * @param responseHandler the response handler instance that should handle the response.
    +     */
    +    public void head(Context context, String url, RequestParams params, AsyncHttpResponseHandler responseHandler) {
    +        sendRequest(httpClient, httpContext, new HttpHead(getUrlWithQueryString(url, params)), null, responseHandler, context);
    +    }
    +
    +    /**
    +     * Perform a HTTP HEAD request and track the Android Context which initiated
    +     * the request with customized headers
    +     *
    +     * @param url the URL to send the request to.
    +     * @param headers set headers only for this request
    +     * @param params additional HEAD parameters to send with the request.
    +     * @param responseHandler the response handler instance that should handle
    +     *        the response.
    +     */
    +    public void head(Context context, String url, Header[] headers, RequestParams params, AsyncHttpResponseHandler responseHandler) {
    +        HttpUriRequest request = new HttpHead(getUrlWithQueryString(url, params));
    +        if(headers != null) request.setHeaders(headers);
    +        sendRequest(httpClient, httpContext, request, null, responseHandler,
    +                context);
    +    }
    +
     
         //
         // HTTP GET Requests
    diff --git a/library/src/com/loopj/android/http/BinaryHttpResponseHandler.java b/library/src/com/loopj/android/http/BinaryHttpResponseHandler.java
    index 28ebe9638..c67dfb0cf 100644
    --- a/library/src/com/loopj/android/http/BinaryHttpResponseHandler.java
    +++ b/library/src/com/loopj/android/http/BinaryHttpResponseHandler.java
    @@ -147,7 +147,7 @@ protected void handleMessage(Message msg) {
             switch (msg.what) {
                 case SUCCESS_MESSAGE:
                     response = (Object[]) msg.obj;
    -                handleSuccessMessage(((Integer) response[0]).intValue(), (byte[]) response[1]);
    +                handleSuccessMessage((Integer) response[0], (byte[]) response[1]);
                     break;
                 case FAILURE_MESSAGE:
                     response = (Object[]) msg.obj;
    
    From 29a62c1042a440cfdcbaba1f8029f1bafa56e3c3 Mon Sep 17 00:00:00 2001
    From: mareksebera 
    Date: Thu, 10 Oct 2013 17:07:04 +0200
    Subject: [PATCH 059/613] OSS Sonatype release via Gradle
    
    ---
     build.gradle              |  6 +++
     gradle.properties         | 12 ++++++
     library/build.gradle      |  2 +
     library/gradle.properties |  3 ++
     maven_push.gradle         | 86 +++++++++++++++++++++++++++++++++++++++
     5 files changed, 109 insertions(+)
     create mode 100644 gradle.properties
     create mode 100644 library/gradle.properties
     create mode 100644 maven_push.gradle
    
    diff --git a/build.gradle b/build.gradle
    index 6dbebcf59..a8f9415c7 100644
    --- a/build.gradle
    +++ b/build.gradle
    @@ -8,6 +8,10 @@ buildscript {
         }
     }
     
    +def isReleaseBuild() {
    +    return version.contains("SNAPSHOT") == false
    +}
    +
     allprojects {
         group = 'com.loopj.android'
         version = '1.4.4-SNAPSHOT'
    @@ -20,3 +24,5 @@ allprojects {
             options.encoding = "UTF-8"
         }
     }
    +
    +apply plugin: 'android-reporting'
    \ No newline at end of file
    diff --git a/gradle.properties b/gradle.properties
    new file mode 100644
    index 000000000..2ea2d4a6a
    --- /dev/null
    +++ b/gradle.properties
    @@ -0,0 +1,12 @@
    +VERSION_NAME=1.4.4-SNAPSHOT
    +VERSION_CODE=2
    +GROUP=com.loopj.android
    +
    +POM_DESCRIPTION=An Asynchronous HTTP Library for Android
    +POM_URL=loopj.com/android-async-http/
    +POM_SCM_URL=https://github.com/loopj/android-async-http
    +POM_SCM_CONNECTION=scm:git@github.com:loopj/android-async-http.git
    +POM_SCM_DEV_CONNECTION=scm:git@github.com:loopj/android-async-http.git
    +POM_LICENCE_NAME=The Apache Software License, Version 2.0
    +POM_LICENCE_URL=http://www.apache.org/licenses/LICENSE-2.0.txt
    +POM_LICENCE_DIST=repo
    \ No newline at end of file
    diff --git a/library/build.gradle b/library/build.gradle
    index 81185aea5..034c819a3 100644
    --- a/library/build.gradle
    +++ b/library/build.gradle
    @@ -16,3 +16,5 @@ android {
             }
         }
     }
    +
    +apply from: '../maven_push.gradle'
    \ No newline at end of file
    diff --git a/library/gradle.properties b/library/gradle.properties
    new file mode 100644
    index 000000000..c3a471ebb
    --- /dev/null
    +++ b/library/gradle.properties
    @@ -0,0 +1,3 @@
    +POM_NAME=ActionBar-PullToRefresh Library
    +POM_ARTIFACT_ID=android-async-http
    +POM_PACKAGING=jar
    \ No newline at end of file
    diff --git a/maven_push.gradle b/maven_push.gradle
    new file mode 100644
    index 000000000..b45f1153e
    --- /dev/null
    +++ b/maven_push.gradle
    @@ -0,0 +1,86 @@
    +apply plugin: 'maven'
    +apply plugin: 'signing'
    +
    +def sonatypeRepositoryUrl
    +if (isReleaseBuild()) {
    +    println 'RELEASE BUILD'
    +    sonatypeRepositoryUrl = "https://oss.sonatype.org/service/local/staging/deploy/maven2/"
    +} else {
    +    println 'DEBUG BUILD'
    +    sonatypeRepositoryUrl = "https://oss.sonatype.org/content/repositories/snapshots/"
    +}
    +
    +afterEvaluate { project ->
    +    uploadArchives {
    +        repositories {
    +            mavenDeployer {
    +                beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) }
    +
    +                pom.artifactId = POM_ARTIFACT_ID
    +
    +                repository(url: sonatypeRepositoryUrl) {
    +                    authentication(userName: nexusUsername, password: nexusPassword)
    +                }
    +
    +                pom.project {
    +                    name POM_NAME
    +                    packaging POM_PACKAGING
    +                    description POM_DESCRIPTION
    +                    url POM_URL
    +
    +                    scm {
    +                        url POM_SCM_URL
    +                        connection POM_SCM_CONNECTION
    +                        developerConnection POM_SCM_DEV_CONNECTION
    +                    }
    +
    +                    licenses {
    +                        license {
    +                            name POM_LICENCE_NAME
    +                            url POM_LICENCE_URL
    +                            distribution POM_LICENCE_DIST
    +                        }
    +                    }
    +
    +                    developers {
    +                        developer {
    +                            id "loopj"
    +                            name "James Smith"
    +                        }
    +                        developer {
    +                            id "smarek"
    +                            name "Marek Sebera"
    +                        }
    +                    }
    +                }
    +            }
    +        }
    +    }
    +
    +    signing {
    +        required { isReleaseBuild() && gradle.taskGraph.hasTask("uploadArchives") }
    +        sign configurations.archives
    +    }
    +
    +    task androidJavadocs(type: Javadoc) {
    +        source = android.sourceSets.main.allJava
    +    }
    +
    +    task androidJavadocsJar(type: Jar) {
    +        classifier = 'javadoc'
    +        //basename = artifact_id
    +        from androidJavadocs.destinationDir
    +    }
    +
    +    task androidSourcesJar(type: Jar) {
    +        classifier = 'sources'
    +        //basename = artifact_id
    +        from android.sourceSets.main.allSource
    +    }
    +
    +    artifacts {
    +        //archives packageReleaseJar
    +        archives androidSourcesJar
    +        archives androidJavadocsJar
    +    }
    +}
    \ No newline at end of file
    
    From 54909c0b8d1a5a2f29d2e6f83c515a377a4c156e Mon Sep 17 00:00:00 2001
    From: mareksebera 
    Date: Thu, 10 Oct 2013 17:53:15 +0200
    Subject: [PATCH 060/613] Deprecation of version property
    
    ---
     library/src/com/loopj/android/http/AsyncHttpClient.java | 3 ++-
     1 file changed, 2 insertions(+), 1 deletion(-)
    
    diff --git a/library/src/com/loopj/android/http/AsyncHttpClient.java b/library/src/com/loopj/android/http/AsyncHttpClient.java
    index 0b7d16731..a54968635 100644
    --- a/library/src/com/loopj/android/http/AsyncHttpClient.java
    +++ b/library/src/com/loopj/android/http/AsyncHttpClient.java
    @@ -91,7 +91,8 @@
      * 
    */ public class AsyncHttpClient { - private static final String VERSION = "1.4.3"; + // This property won't be available soon, don't use it + @Deprecated private static final String VERSION = "1.4.4"; private static final int DEFAULT_MAX_CONNECTIONS = 10; private static final int DEFAULT_SOCKET_TIMEOUT = 10 * 1000; From 51ede665194d676b10f8b61742a4aa614a73c277 Mon Sep 17 00:00:00 2001 From: mareksebera Date: Thu, 10 Oct 2013 18:25:55 +0200 Subject: [PATCH 061/613] Gradle AAR release, not JAR --- library/AndroidManifest.xml | 4 ++-- library/build.gradle | 2 +- library/gradle.properties | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/library/AndroidManifest.xml b/library/AndroidManifest.xml index fc7a6a3cb..1ad7c0fcf 100644 --- a/library/AndroidManifest.xml +++ b/library/AndroidManifest.xml @@ -1,8 +1,8 @@ + android:versionName="1.4.4-SNAPSHOT" + android:versionCode="144"> Date: Thu, 10 Oct 2013 18:30:21 +0200 Subject: [PATCH 062/613] Travis CI fixed build --- .travis.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 20458a51b..20f331ddc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -38,4 +38,5 @@ before_install: # verify files exist in right paths - find $ANDROID_HOME/build-tools - file $ANDROID_HOME/build-tools/18.0.1/aapt - # - mvn clean test install + - echo "nexusUsername=dummy" >> gradle.properties + - echo "nexusPassword=dummy" >> gradle.properties From 1929771584ce75b183a65ab56fc3e0b9ab88ab38 Mon Sep 17 00:00:00 2001 From: mareksebera Date: Thu, 10 Oct 2013 18:46:03 +0200 Subject: [PATCH 063/613] uploadArchives for Travis CI --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index 20f331ddc..8565b9375 100644 --- a/.travis.yml +++ b/.travis.yml @@ -40,3 +40,5 @@ before_install: - file $ANDROID_HOME/build-tools/18.0.1/aapt - echo "nexusUsername=dummy" >> gradle.properties - echo "nexusPassword=dummy" >> gradle.properties + - echo "nexusUsername=dummy" >> library/gradle.properties + - echo "nexusPassword=dummy" >> library/gradle.properties From 8a6557af8beb84e9b0ac854272ba1a2cd9dec77c Mon Sep 17 00:00:00 2001 From: mareksebera Date: Fri, 11 Oct 2013 11:18:38 +0200 Subject: [PATCH 064/613] New Gradle configuration files --- gradle.properties | 4 ++-- maven_push.gradle | 34 ++++++++++++++++++++++++++++++---- 2 files changed, 32 insertions(+), 6 deletions(-) diff --git a/gradle.properties b/gradle.properties index 2ea2d4a6a..0de12ce95 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ VERSION_NAME=1.4.4-SNAPSHOT -VERSION_CODE=2 +VERSION_CODE=144 GROUP=com.loopj.android POM_DESCRIPTION=An Asynchronous HTTP Library for Android @@ -9,4 +9,4 @@ POM_SCM_CONNECTION=scm:git@github.com:loopj/android-async-http.git POM_SCM_DEV_CONNECTION=scm:git@github.com:loopj/android-async-http.git POM_LICENCE_NAME=The Apache Software License, Version 2.0 POM_LICENCE_URL=http://www.apache.org/licenses/LICENSE-2.0.txt -POM_LICENCE_DIST=repo \ No newline at end of file +POM_LICENCE_DIST=repo diff --git a/maven_push.gradle b/maven_push.gradle index b45f1153e..cf9d0a612 100644 --- a/maven_push.gradle +++ b/maven_push.gradle @@ -1,6 +1,12 @@ apply plugin: 'maven' apply plugin: 'signing' +configurations { + archives { + extendsFrom configurations.default + } +} + def sonatypeRepositoryUrl if (isReleaseBuild()) { println 'RELEASE BUILD' @@ -10,6 +16,24 @@ if (isReleaseBuild()) { sonatypeRepositoryUrl = "https://oss.sonatype.org/content/repositories/snapshots/" } +if (!signing.hasProperty('password')) { + if (System.console()) + allprojects { + ext.set('signing.password', System.console().readPassword("\n\$ Type in GPG key password: ")) + } + else + allprojects { ext.set('signing.password', 'dummy') } +} + +if (!project.ext.hasProperty('nexusPassword')) { + if (System.console()) + allprojects { + project.ext.set('nexusPassword', new String(System.console().readPassword("\n\$ Type in password for Sonatype nexus account ${nexusUsername}: "))) + } + else + allprojects { project.ext.set('nexusPassword', 'dummy') } +} + afterEvaluate { project -> uploadArchives { repositories { @@ -62,25 +86,27 @@ afterEvaluate { project -> sign configurations.archives } + task androidReleaseJar(type: Jar) { + from "$buildDir/classes/release/" + } + task androidJavadocs(type: Javadoc) { source = android.sourceSets.main.allJava } task androidJavadocsJar(type: Jar) { classifier = 'javadoc' - //basename = artifact_id from androidJavadocs.destinationDir } task androidSourcesJar(type: Jar) { classifier = 'sources' - //basename = artifact_id from android.sourceSets.main.allSource } artifacts { - //archives packageReleaseJar + archives androidReleaseJar archives androidSourcesJar archives androidJavadocsJar } -} \ No newline at end of file +} From d276be61ab1e1595654ae1fadd18b03712bbdc18 Mon Sep 17 00:00:00 2001 From: mareksebera Date: Sat, 12 Oct 2013 02:41:10 +0200 Subject: [PATCH 065/613] Not forcing sign, if there is no keyId --- .travis.yml | 3 +-- maven_push.gradle | 16 ++++++---------- 2 files changed, 7 insertions(+), 12 deletions(-) diff --git a/.travis.yml b/.travis.yml index 8565b9375..a7560a7bb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -38,7 +38,6 @@ before_install: # verify files exist in right paths - find $ANDROID_HOME/build-tools - file $ANDROID_HOME/build-tools/18.0.1/aapt - - echo "nexusUsername=dummy" >> gradle.properties - - echo "nexusPassword=dummy" >> gradle.properties - echo "nexusUsername=dummy" >> library/gradle.properties - echo "nexusPassword=dummy" >> library/gradle.properties + diff --git a/maven_push.gradle b/maven_push.gradle index cf9d0a612..8aeb38b25 100644 --- a/maven_push.gradle +++ b/maven_push.gradle @@ -16,22 +16,18 @@ if (isReleaseBuild()) { sonatypeRepositoryUrl = "https://oss.sonatype.org/content/repositories/snapshots/" } -if (!signing.hasProperty('password')) { +if (ext.hasProperty('signing.keyId') && !ext.hasProperty('signing.password')) { if (System.console()) - allprojects { - ext.set('signing.password', System.console().readPassword("\n\$ Type in GPG key password: ")) - } + ext.set('signing.password', System.console().readPassword("\n\$ Type in GPG key password: ")) else - allprojects { ext.set('signing.password', 'dummy') } + ext.set('signing.password', 'dummy') } -if (!project.ext.hasProperty('nexusPassword')) { +if (!ext.hasProperty('nexusPassword')) { if (System.console()) - allprojects { - project.ext.set('nexusPassword', new String(System.console().readPassword("\n\$ Type in password for Sonatype nexus account ${nexusUsername}: "))) - } + project.ext.set('nexusPassword', new String(System.console().readPassword("\n\$ Type in password for Sonatype nexus account ${nexusUsername}: "))) else - allprojects { project.ext.set('nexusPassword', 'dummy') } + project.ext.set('nexusPassword', 'dummy') } afterEvaluate { project -> From 1e1e1dd78b6b8a06728a55dc304549bffa6d7d7a Mon Sep 17 00:00:00 2001 From: mareksebera Date: Sat, 12 Oct 2013 02:55:25 +0200 Subject: [PATCH 066/613] Not releasing to Maven on CI --- build.gradle | 2 +- maven_push.gradle | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index a8f9415c7..d239f5ad6 100644 --- a/build.gradle +++ b/build.gradle @@ -25,4 +25,4 @@ allprojects { } } -apply plugin: 'android-reporting' \ No newline at end of file +apply plugin: 'android-reporting' diff --git a/maven_push.gradle b/maven_push.gradle index 8aeb38b25..7ed71aae5 100644 --- a/maven_push.gradle +++ b/maven_push.gradle @@ -23,7 +23,7 @@ if (ext.hasProperty('signing.keyId') && !ext.hasProperty('signing.password')) { ext.set('signing.password', 'dummy') } -if (!ext.hasProperty('nexusPassword')) { +if (System.env.TERM != 'dumb' && !ext.hasProperty('nexusPassword')) { if (System.console()) project.ext.set('nexusPassword', new String(System.console().readPassword("\n\$ Type in password for Sonatype nexus account ${nexusUsername}: "))) else From 9ac76078c8a34cfd06698938fbec00bbaca4c203 Mon Sep 17 00:00:00 2001 From: mareksebera Date: Sat, 12 Oct 2013 03:36:54 +0200 Subject: [PATCH 067/613] Some more Gradle release fixes --- maven_push.gradle | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/maven_push.gradle b/maven_push.gradle index 7ed71aae5..76575cd19 100644 --- a/maven_push.gradle +++ b/maven_push.gradle @@ -16,18 +16,18 @@ if (isReleaseBuild()) { sonatypeRepositoryUrl = "https://oss.sonatype.org/content/repositories/snapshots/" } -if (ext.hasProperty('signing.keyId') && !ext.hasProperty('signing.password')) { +if (ext.properties.containsKey('signing.keyId') && !ext.properties.containsKey('signing.password')) { if (System.console()) ext.set('signing.password', System.console().readPassword("\n\$ Type in GPG key password: ")) else ext.set('signing.password', 'dummy') } -if (System.env.TERM != 'dumb' && !ext.hasProperty('nexusPassword')) { +if (System.env.TERM != 'dumb' && !ext.properties.containsKey('nexusPassword')) { if (System.console()) - project.ext.set('nexusPassword', new String(System.console().readPassword("\n\$ Type in password for Sonatype nexus account ${nexusUsername}: "))) + ext.set('nexusPassword', new String(System.console().readPassword("\n\$ Type in password for Sonatype nexus account ${nexusUsername}: "))) else - project.ext.set('nexusPassword', 'dummy') + ext.set('nexusPassword', 'dummy') } afterEvaluate { project -> @@ -82,7 +82,7 @@ afterEvaluate { project -> sign configurations.archives } - task androidReleaseJar(type: Jar) { + task androidReleaseJar(type: Jar, dependsOn: assembleRelease) { from "$buildDir/classes/release/" } From 2a45f5d0e4860671abc96c38685c8fc80a3a1bab Mon Sep 17 00:00:00 2001 From: mareksebera Date: Sat, 12 Oct 2013 03:51:47 +0200 Subject: [PATCH 068/613] Refactoring --- .../loopj/android/http/AsyncHttpResponseHandler.java | 10 ++++++---- .../loopj/android/http/BinaryHttpResponseHandler.java | 4 ++-- .../android/http/FileAsyncHttpResponseHandler.java | 2 +- .../loopj/android/http/JsonHttpResponseHandler.java | 2 +- .../com/loopj/android/http/PersistentCookieStore.java | 2 +- library/src/com/loopj/android/http/RetryHandler.java | 8 +++----- .../com/loopj/android/http/SimpleMultipartEntity.java | 4 ++-- library/src/com/loopj/android/http/SyncHttpClient.java | 2 -- 8 files changed, 16 insertions(+), 18 deletions(-) diff --git a/library/src/com/loopj/android/http/AsyncHttpResponseHandler.java b/library/src/com/loopj/android/http/AsyncHttpResponseHandler.java index d57fba7c6..a08b0714e 100644 --- a/library/src/com/loopj/android/http/AsyncHttpResponseHandler.java +++ b/library/src/com/loopj/android/http/AsyncHttpResponseHandler.java @@ -163,7 +163,7 @@ public void onFailure(Throwable error, String content) { // protected void sendSuccessMessage(int statusCode, Header[] headers, String responseBody) { - sendMessage(obtainMessage(SUCCESS_MESSAGE, new Object[]{new Integer(statusCode), headers, responseBody})); + sendMessage(obtainMessage(SUCCESS_MESSAGE, new Object[]{statusCode, headers, responseBody})); } protected void sendFailureMessage(Throwable e, String responseBody) { @@ -203,7 +203,7 @@ protected void handleMessage(Message msg) { switch (msg.what) { case SUCCESS_MESSAGE: response = (Object[]) msg.obj; - handleSuccessMessage(((Integer) response[0]).intValue(), (Header[]) response[1], (String) response[2]); + handleSuccessMessage((Integer) response[0], (Header[]) response[1], (String) response[2]); break; case FAILURE_MESSAGE: response = (Object[]) msg.obj; @@ -232,8 +232,10 @@ protected Message obtainMessage(int responseMessage, Object response) { msg = this.handler.obtainMessage(responseMessage, response); } else { msg = Message.obtain(); - msg.what = responseMessage; - msg.obj = response; + if (msg != null) { + msg.what = responseMessage; + msg.obj = response; + } } return msg; } diff --git a/library/src/com/loopj/android/http/BinaryHttpResponseHandler.java b/library/src/com/loopj/android/http/BinaryHttpResponseHandler.java index c67dfb0cf..7ceea8a88 100644 --- a/library/src/com/loopj/android/http/BinaryHttpResponseHandler.java +++ b/library/src/com/loopj/android/http/BinaryHttpResponseHandler.java @@ -167,7 +167,7 @@ protected void sendResponseMessage(HttpResponse response) { byte[] responseBody = null; if (contentTypeHeaders.length != 1) { //malformed/ambiguous HTTP Header, ABORT! - sendFailureMessage(new HttpResponseException(status.getStatusCode(), "None, or more than one, Content-Type Header found!"), responseBody); + sendFailureMessage(new HttpResponseException(status.getStatusCode(), "None, or more than one, Content-Type Header found!"), (String) null); return; } Header contentTypeHeader = contentTypeHeaders[0]; @@ -179,7 +179,7 @@ protected void sendResponseMessage(HttpResponse response) { } if (!foundAllowedContentType) { //Content-Type not in allowed list, ABORT! - sendFailureMessage(new HttpResponseException(status.getStatusCode(), "Content-Type not allowed!"), responseBody); + sendFailureMessage(new HttpResponseException(status.getStatusCode(), "Content-Type not allowed!"), (String) null); return; } try { diff --git a/library/src/com/loopj/android/http/FileAsyncHttpResponseHandler.java b/library/src/com/loopj/android/http/FileAsyncHttpResponseHandler.java index 8b9d549f4..b3d477cae 100644 --- a/library/src/com/loopj/android/http/FileAsyncHttpResponseHandler.java +++ b/library/src/com/loopj/android/http/FileAsyncHttpResponseHandler.java @@ -54,7 +54,7 @@ protected void handleMessage(Message msg) { switch (msg.what) { case SUCCESS_MESSAGE: response = (Object[]) msg.obj; - handleSuccessMessage(((Integer) response[0]).intValue(), (File) response[1]); + handleSuccessMessage((Integer) response[0], (File) response[1]); break; case FAILURE_MESSAGE: response = (Object[]) msg.obj; diff --git a/library/src/com/loopj/android/http/JsonHttpResponseHandler.java b/library/src/com/loopj/android/http/JsonHttpResponseHandler.java index 6586eb47c..895fb4488 100644 --- a/library/src/com/loopj/android/http/JsonHttpResponseHandler.java +++ b/library/src/com/loopj/android/http/JsonHttpResponseHandler.java @@ -152,7 +152,7 @@ protected void handleMessage(Message msg) { switch (msg.what) { case SUCCESS_JSON_MESSAGE: Object[] response = (Object[]) msg.obj; - handleSuccessJsonMessage(((Integer) response[0]).intValue(), (Header[]) response[1], response[2]); + handleSuccessJsonMessage((Integer) response[0], (Header[]) response[1], response[2]); break; default: super.handleMessage(msg); diff --git a/library/src/com/loopj/android/http/PersistentCookieStore.java b/library/src/com/loopj/android/http/PersistentCookieStore.java index 8f255ae5e..1e3d3b8d1 100644 --- a/library/src/com/loopj/android/http/PersistentCookieStore.java +++ b/library/src/com/loopj/android/http/PersistentCookieStore.java @@ -178,7 +178,7 @@ protected Cookie decodeCookie(String cookieStr) { // Using some super basic byte array <-> hex conversions so we don't have // to rely on any large Base64 libraries. Can be overridden if you like! protected String byteArrayToHexString(byte[] b) { - StringBuffer sb = new StringBuffer(b.length * 2); + StringBuilder sb = new StringBuilder(b.length * 2); for (byte element : b) { int v = element & 0xff; if (v < 16) { diff --git a/library/src/com/loopj/android/http/RetryHandler.java b/library/src/com/loopj/android/http/RetryHandler.java index 310756d4f..ba33a38cf 100644 --- a/library/src/com/loopj/android/http/RetryHandler.java +++ b/library/src/com/loopj/android/http/RetryHandler.java @@ -36,7 +36,6 @@ import java.net.SocketException; import java.net.UnknownHostException; import java.util.HashSet; -import java.util.Iterator; import javax.net.ssl.SSLException; @@ -70,7 +69,7 @@ public boolean retryRequest(IOException exception, int executionCount, HttpConte boolean retry = true; Boolean b = (Boolean) context.getAttribute(ExecutionContext.HTTP_REQ_SENT); - boolean sent = (b != null && b.booleanValue()); + boolean sent = (b != null && b); if (executionCount > maxRetries) { // Do not retry if over max retry count @@ -103,9 +102,8 @@ public boolean retryRequest(IOException exception, int executionCount, HttpConte } protected boolean isInList(HashSet> list, Throwable error) { - Iterator> itr = list.iterator(); - while (itr.hasNext()) { - if (itr.next().isInstance(error)) { + for (Class aList : list) { + if (aList.isInstance(error)) { return true; } } diff --git a/library/src/com/loopj/android/http/SimpleMultipartEntity.java b/library/src/com/loopj/android/http/SimpleMultipartEntity.java index 718226751..b072b8b0d 100644 --- a/library/src/com/loopj/android/http/SimpleMultipartEntity.java +++ b/library/src/com/loopj/android/http/SimpleMultipartEntity.java @@ -47,7 +47,7 @@ class SimpleMultipartEntity implements HttpEntity { boolean isSetFirst = false; public SimpleMultipartEntity() { - final StringBuffer buf = new StringBuffer(); + final StringBuilder buf = new StringBuilder(); final Random rand = new Random(); for (int i = 0; i < 30; i++) { buf.append(MULTIPART_CHARS[rand.nextInt(MULTIPART_CHARS.length)]); @@ -116,7 +116,7 @@ public void addPart(final String key, final String fileName, final InputStream f out.write("Content-Transfer-Encoding: binary\r\n\r\n".getBytes()); final byte[] tmp = new byte[4096]; - int l = 0; + int l; while ((l = fin.read(tmp)) != -1) { out.write(tmp, 0, l); } diff --git a/library/src/com/loopj/android/http/SyncHttpClient.java b/library/src/com/loopj/android/http/SyncHttpClient.java index 8b7750982..fdb3e3c39 100644 --- a/library/src/com/loopj/android/http/SyncHttpClient.java +++ b/library/src/com/loopj/android/http/SyncHttpClient.java @@ -23,8 +23,6 @@ protected void sendResponseMessage(org.apache.http.HttpResponse response) { super.sendResponseMessage(response); } - ; - @Override protected void sendMessage(Message msg) { /* From 98ad3955309b365492f97f42df54dae76575601d Mon Sep 17 00:00:00 2001 From: mareksebera Date: Sat, 12 Oct 2013 04:10:20 +0200 Subject: [PATCH 069/613] Javadoc gradle tasks and Javadoc comments fixup --- library/build.gradle | 11 +++++++++ .../loopj/android/http/AsyncHttpClient.java | 24 ++++++++++++------- .../http/AsyncHttpResponseHandler.java | 6 ++--- .../http/BinaryHttpResponseHandler.java | 6 +++-- .../android/http/JsonHttpResponseHandler.java | 4 ++-- .../android/http/PersistentCookieStore.java | 4 +++- .../com/loopj/android/http/RequestParams.java | 8 +++++-- 7 files changed, 44 insertions(+), 19 deletions(-) diff --git a/library/build.gradle b/library/build.gradle index 38bb62d9f..680db873c 100644 --- a/library/build.gradle +++ b/library/build.gradle @@ -17,4 +17,15 @@ android { } } +android.libraryVariants.all { variant -> + + task("generate${variant.name}Javadoc", type: Javadoc) { + description "Generates Javadoc for $variant.name." + source = variant.javaCompile.source + ext.androidJar = "${android.plugin.sdkDirectory}/platforms/${android.compileSdkVersion}/android.jar" + classpath = files(variant.javaCompile.classpath.files) + files(ext.androidJar) + } + +} + apply from: '../maven_push.gradle' diff --git a/library/src/com/loopj/android/http/AsyncHttpClient.java b/library/src/com/loopj/android/http/AsyncHttpClient.java index a54968635..f0e2a6e92 100644 --- a/library/src/com/loopj/android/http/AsyncHttpClient.java +++ b/library/src/com/loopj/android/http/AsyncHttpClient.java @@ -77,9 +77,9 @@ * with additional parameters by passing a {@link RequestParams} instance, * and responses can be handled by passing an anonymously overridden * {@link AsyncHttpResponseHandler} instance. - *

    + *

     

    * For example: - *

    + *

     

    *
      * AsyncHttpClient client = new AsyncHttpClient();
      * client.get("http://www.google.com/", new AsyncHttpResponseHandler() {
    @@ -179,6 +179,8 @@ public void process(HttpResponse response, HttpContext context) {
          * Get the underlying HttpClient instance. This is useful for setting
          * additional fine-grained settings for requests by accessing the
          * client's ConnectionManager, HttpParams and SchemeRegistry.
    +     *
    +     * @return underlying HttpClient instance
          */
         public HttpClient getHttpClient() {
             return this.httpClient;
    @@ -188,6 +190,8 @@ public HttpClient getHttpClient() {
          * Get the underlying HttpContext instance. This is useful for getting
          * and setting fine-grained settings for requests by accessing the
          * context's attributes such as the CookieStore.
    +     *
    +     * @return underlying HttpContext instance
          */
         public HttpContext getHttpContext() {
             return this.httpContext;
    @@ -258,8 +262,8 @@ public void addHeader(String header, String value) {
          * Sets basic authentication for the request. Uses AuthScope.ANY. This is the same as
          * setBasicAuth('username','password',AuthScope.ANY)
          *
    -     * @param username
    -     * @param password
    +     * @param username Basic Auth username
    +     * @param password Basic Auth password
          */
         public void setBasicAuth(String username, String password) {
             AuthScope scope = AuthScope.ANY;
    @@ -270,8 +274,8 @@ public void setBasicAuth(String username, String password) {
          * Sets basic authentication for the request. You should pass in your AuthScope for security. It should be like this
          * setBasicAuth("username","password", new AuthScope("host",port,AuthScope.ANY_REALM))
          *
    -     * @param username
    -     * @param password
    +     * @param username Basic Auth username
    +     * @param password Basic Auth password
          * @param scope    - an AuthScope object
          */
         public void setBasicAuth(String username, String password, AuthScope scope) {
    @@ -282,7 +286,7 @@ public void setBasicAuth(String username, String password, AuthScope scope) {
         /**
          * Cancels any pending (or potentially active) requests associated with the
          * passed Context.
    -     * 

    + *

     

    * Note: This will only affect requests which were created with a non-null * android Context. This method is intended to be used in the onDestroy * method of your android activities to destroy all requests which are no @@ -352,6 +356,7 @@ public void head(Context context, String url, RequestParams params, AsyncHttpRes * Perform a HTTP HEAD request and track the Android Context which initiated * the request with customized headers * + * @param context Context to execute request against * @param url the URL to send the request to. * @param headers set headers only for this request * @param params additional HEAD parameters to send with the request. @@ -418,6 +423,7 @@ public void get(Context context, String url, RequestParams params, AsyncHttpResp * Perform a HTTP GET request and track the Android Context which initiated * the request with customized headers * + * @param context Context to execute request against * @param url the URL to send the request to. * @param headers set headers only for this request * @param params additional GET parameters to send with the request. @@ -474,9 +480,9 @@ public void post(Context context, String url, RequestParams params, AsyncHttpRes * * @param context the Android Context which initiated the request. * @param url the URL to send the request to. - * @param entity a raw {@link HttpEntity} to send with the request, for example, use this to send string/json/xml payloads to a server by passing a {@link org.apache.http.entity.StringEntity}. + * @param entity a raw {@link org.apache.http.HttpEntity} to send with the request, for example, use this to send string/json/xml payloads to a server by passing a {@link org.apache.http.entity.StringEntity}. * @param contentType the content type of the payload you are sending, for example application/json if sending a json payload. - * @param responseHandler the response handler instance that should handle the response. + * @param responseHandler the response ha ndler instance that should handle the response. */ public void post(Context context, String url, HttpEntity entity, String contentType, AsyncHttpResponseHandler responseHandler) { sendRequest(httpClient, httpContext, addEntityToRequestBase(new HttpPost(url), entity), contentType, responseHandler, context); diff --git a/library/src/com/loopj/android/http/AsyncHttpResponseHandler.java b/library/src/com/loopj/android/http/AsyncHttpResponseHandler.java index a08b0714e..5739a38e4 100644 --- a/library/src/com/loopj/android/http/AsyncHttpResponseHandler.java +++ b/library/src/com/loopj/android/http/AsyncHttpResponseHandler.java @@ -36,12 +36,12 @@ * Used to intercept and handle the responses from requests made using * {@link AsyncHttpClient}. The {@link #onSuccess(String)} method is * designed to be anonymously overridden with your own response handling code. - *

    + *

     

    * Additionally, you can override the {@link #onFailure(Throwable, String)}, * {@link #onStart()}, and {@link #onFinish()} methods as required. - *

    + *

     

    * For example: - *

    + *

     

    *
      * AsyncHttpClient client = new AsyncHttpClient();
      * client.get("http://www.google.com/", new AsyncHttpResponseHandler() {
    diff --git a/library/src/com/loopj/android/http/BinaryHttpResponseHandler.java b/library/src/com/loopj/android/http/BinaryHttpResponseHandler.java
    index 7ceea8a88..814dba6b0 100644
    --- a/library/src/com/loopj/android/http/BinaryHttpResponseHandler.java
    +++ b/library/src/com/loopj/android/http/BinaryHttpResponseHandler.java
    @@ -36,9 +36,9 @@
      * {@link AsyncHttpClient}. Receives response body as byte array with a
      * content-type whitelist. (e.g. checks Content-Type against allowed list,
      * Content-length).
    - * 

    + *

     

    * For example: - *

    + *

     

    *
      * AsyncHttpClient client = new AsyncHttpClient();
      * String[] allowedTypes = new String[] { "image/png" };
    @@ -72,6 +72,8 @@ public BinaryHttpResponseHandler() {
         /**
          * Creates a new BinaryHttpResponseHandler, and overrides the default allowed
          * content types with passed String array (hopefully) of content types.
    +     *
    +     * @param allowedContentTypes content types array, eg. 'image/jpeg'
          */
         public BinaryHttpResponseHandler(String[] allowedContentTypes) {
             this();
    diff --git a/library/src/com/loopj/android/http/JsonHttpResponseHandler.java b/library/src/com/loopj/android/http/JsonHttpResponseHandler.java
    index 895fb4488..54a900f42 100644
    --- a/library/src/com/loopj/android/http/JsonHttpResponseHandler.java
    +++ b/library/src/com/loopj/android/http/JsonHttpResponseHandler.java
    @@ -31,11 +31,11 @@
      * Used to intercept and handle the responses from requests made using
      * {@link AsyncHttpClient}, with automatic parsing into a {@link JSONObject}
      * or {@link JSONArray}.
    - * 

    + *

     

    * This class is designed to be passed to get, post, put and delete requests * with the {@link #onSuccess(JSONObject)} or {@link #onSuccess(JSONArray)} * methods anonymously overridden. - *

    + *

     

    * Additionally, you can override the other event methods from the * parent class. */ diff --git a/library/src/com/loopj/android/http/PersistentCookieStore.java b/library/src/com/loopj/android/http/PersistentCookieStore.java index 1e3d3b8d1..69d790fae 100644 --- a/library/src/com/loopj/android/http/PersistentCookieStore.java +++ b/library/src/com/loopj/android/http/PersistentCookieStore.java @@ -39,7 +39,7 @@ * {@link CookieStore} interface. Cookies are stored and will persist on the * user's device between application sessions since they are serialized and * stored in {@link SharedPreferences}. - *

    + *

     

    * Instances of this class are designed to be used with * {@link AsyncHttpClient#setCookieStore}, but can also be used with a * regular old apache HttpClient/HttpContext if you prefer. @@ -54,6 +54,8 @@ public class PersistentCookieStore implements CookieStore { /** * Construct a persistent cookie store. + * + * @param context Context to attach cookie store to */ public PersistentCookieStore(Context context) { cookiePrefs = context.getSharedPreferences(COOKIE_PREFS, 0); diff --git a/library/src/com/loopj/android/http/RequestParams.java b/library/src/com/loopj/android/http/RequestParams.java index 035ffba51..5121f5b11 100644 --- a/library/src/com/loopj/android/http/RequestParams.java +++ b/library/src/com/loopj/android/http/RequestParams.java @@ -37,9 +37,9 @@ /** * A collection of string request parameters or files to send along with * requests made from an {@link AsyncHttpClient} instance. - *

    + *

     

    * For example: - *

    + *

     

    *
      * RequestParams params = new RequestParams();
      * params.put("username", "james");
    @@ -143,6 +143,8 @@ public void put(String key, int value) {
          *
          * @param key  the key name for the new param.
          * @param file the file to add.
    +     *
    +     * @throws java.io.FileNotFoundException if the file is not found
          */
         public void put(String key, File file) throws FileNotFoundException {
             put(key, new FileInputStream(file), file.getName());
    @@ -263,6 +265,8 @@ public String toString() {
     
         /**
          * Returns an HttpEntity containing all request parameters
    +     *
    +     * @return an HttpEntity containing all request parameters
          */
         public HttpEntity getEntity() {
             HttpEntity entity = null;
    
    From fa31db815324ef3ecc98d5e067ef53d36b15bb34 Mon Sep 17 00:00:00 2001
    From: mareksebera 
    Date: Sat, 12 Oct 2013 04:39:17 +0200
    Subject: [PATCH 070/613] Javadoc generation fixup
    
    ---
     maven_push.gradle | 6 +-----
     1 file changed, 1 insertion(+), 5 deletions(-)
    
    diff --git a/maven_push.gradle b/maven_push.gradle
    index 76575cd19..1c7ec7110 100644
    --- a/maven_push.gradle
    +++ b/maven_push.gradle
    @@ -86,13 +86,9 @@ afterEvaluate { project ->
             from "$buildDir/classes/release/"
         }
     
    -    task androidJavadocs(type: Javadoc) {
    -        source = android.sourceSets.main.allJava
    -    }
    -
         task androidJavadocsJar(type: Jar) {
             classifier = 'javadoc'
    -        from androidJavadocs.destinationDir
    +        from generateReleaseJavadoc.destinationDir
         }
     
         task androidSourcesJar(type: Jar) {
    
    From c7231d8e26cbbf25e11ff0f6a0429a71332a48b0 Mon Sep 17 00:00:00 2001
    From: mareksebera 
    Date: Sat, 12 Oct 2013 05:03:08 +0200
    Subject: [PATCH 071/613] Fixes #121
    
    ---
     .../android/http/JsonHttpResponseHandler.java | 57 +++++++++++--------
     1 file changed, 34 insertions(+), 23 deletions(-)
    
    diff --git a/library/src/com/loopj/android/http/JsonHttpResponseHandler.java b/library/src/com/loopj/android/http/JsonHttpResponseHandler.java
    index 54a900f42..8c52bf757 100644
    --- a/library/src/com/loopj/android/http/JsonHttpResponseHandler.java
    +++ b/library/src/com/loopj/android/http/JsonHttpResponseHandler.java
    @@ -129,14 +129,19 @@ public void onFailure(Throwable e, JSONArray errorResponse) {
         //
     
         @Override
    -    protected void sendSuccessMessage(int statusCode, Header[] headers, String responseBody) {
    +    protected void sendSuccessMessage(final int statusCode, final Header[] headers, final String responseBody) {
             if (statusCode != HttpStatus.SC_NO_CONTENT) {
    -            try {
    -                Object jsonResponse = parseResponse(responseBody);
    -                sendMessage(obtainMessage(SUCCESS_JSON_MESSAGE, new Object[]{statusCode, headers, jsonResponse}));
    -            } catch (JSONException e) {
    -                sendFailureMessage(e, responseBody);
    -            }
    +            new Thread(new Runnable() {
    +                @Override
    +                public void run() {
    +                    try {
    +                        Object jsonResponse = parseResponse(responseBody);
    +                        sendMessage(obtainMessage(SUCCESS_JSON_MESSAGE, new Object[]{statusCode, headers, jsonResponse}));
    +                    } catch (JSONException e) {
    +                        sendFailureMessage(e, responseBody);
    +                    }
    +                }
    +            }).start();
             } else {
                 sendMessage(obtainMessage(SUCCESS_JSON_MESSAGE, new Object[]{statusCode, new JSONObject()}));
             }
    @@ -183,24 +188,30 @@ protected Object parseResponse(String responseBody) throws JSONException {
         }
     
         @Override
    -    protected void handleFailureMessage(Throwable e, String responseBody) {
    -        try {
    -            if (responseBody != null) {
    -                Object jsonResponse = parseResponse(responseBody);
    -                if (jsonResponse instanceof JSONObject) {
    -                    onFailure(e, (JSONObject) jsonResponse);
    -                } else if (jsonResponse instanceof JSONArray) {
    -                    onFailure(e, (JSONArray) jsonResponse);
    -                } else if (jsonResponse instanceof String) {
    -                    onFailure(e, (String) jsonResponse);
    -                } else {
    +    protected void handleFailureMessage(final Throwable e, final String responseBody) {
    +        new Thread(new Runnable() {
    +            @Override
    +            public void run() {
    +                try {
    +                    if (responseBody != null) {
    +                        Object jsonResponse = parseResponse(responseBody);
    +                        if (jsonResponse instanceof JSONObject) {
    +                            onFailure(e, (JSONObject) jsonResponse);
    +                        } else if (jsonResponse instanceof JSONArray) {
    +                            onFailure(e, (JSONArray) jsonResponse);
    +                        } else if (jsonResponse instanceof String) {
    +                            onFailure(e, (String) jsonResponse);
    +                        } else {
    +                            onFailure(e, responseBody);
    +                        }
    +                    } else {
    +                        onFailure(e, "");
    +                    }
    +                } catch (JSONException ex) {
                         onFailure(e, responseBody);
                     }
    -            } else {
    -                onFailure(e, "");
                 }
    -        } catch (JSONException ex) {
    -            onFailure(e, responseBody);
    -        }
    +        }).start();
    +
         }
     }
    
    From 5f47d1a35721b6d9cbdd37494c4983f414813afc Mon Sep 17 00:00:00 2001
    From: mareksebera 
    Date: Sat, 12 Oct 2013 05:34:52 +0200
    Subject: [PATCH 072/613] Fixes #184
    
    ---
     .../loopj/android/http/AsyncHttpClient.java   | 51 +++++++++++++------
     1 file changed, 36 insertions(+), 15 deletions(-)
    
    diff --git a/library/src/com/loopj/android/http/AsyncHttpClient.java b/library/src/com/loopj/android/http/AsyncHttpClient.java
    index f0e2a6e92..5f4c928f8 100644
    --- a/library/src/com/loopj/android/http/AsyncHttpClient.java
    +++ b/library/src/com/loopj/android/http/AsyncHttpClient.java
    @@ -48,6 +48,7 @@
     import org.apache.http.conn.ssl.SSLSocketFactory;
     import org.apache.http.entity.HttpEntityWrapper;
     import org.apache.http.impl.client.DefaultHttpClient;
    +import org.apache.http.impl.client.DefaultRedirectHandler;
     import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
     import org.apache.http.params.BasicHttpParams;
     import org.apache.http.params.HttpConnectionParams;
    @@ -92,7 +93,8 @@
      */
     public class AsyncHttpClient {
         // This property won't be available soon, don't use it
    -    @Deprecated private static final String VERSION = "1.4.4";
    +    @Deprecated
    +    private static final String VERSION = "1.4.4";
     
         private static final int DEFAULT_MAX_CONNECTIONS = 10;
         private static final int DEFAULT_SOCKET_TIMEOUT = 10 * 1000;
    @@ -216,6 +218,21 @@ public void setThreadPool(ThreadPoolExecutor threadPool) {
             this.threadPool = threadPool;
         }
     
    +    /**
    +     * Simple interface method, to enable or disable redirects.
    +     * If you set manually RedirectHandler on underlying HttpClient, effects of this method will be canceled.
    +     *
    +     * @param enableRedirects boolean
    +     */
    +    public void setEnableRedirects(final boolean enableRedirects) {
    +        httpClient.setRedirectHandler(new DefaultRedirectHandler() {
    +            @Override
    +            public boolean isRedirectRequested(HttpResponse response, HttpContext context) {
    +                return enableRedirects;
    +            }
    +        });
    +    }
    +
         /**
          * Sets the User-Agent header to be sent with each request. By default,
          * "Android Asynchronous Http Client/VERSION (http://loopj.com/android-async-http/)" is used.
    @@ -314,7 +331,8 @@ public void cancelRequests(Context context, boolean mayInterruptIfRunning) {
     
         /**
          * Perform a HTTP HEAD request, without any parameters.
    -     * @param url the URL to send the request to.
    +     *
    +     * @param url             the URL to send the request to.
          * @param responseHandler the response handler instance that should handle the response.
          */
         public void head(String url, AsyncHttpResponseHandler responseHandler) {
    @@ -323,8 +341,9 @@ public void head(String url, AsyncHttpResponseHandler responseHandler) {
     
         /**
          * Perform a HTTP HEAD request with parameters.
    -     * @param url the URL to send the request to.
    -     * @param params additional HEAD parameters to send with the request.
    +     *
    +     * @param url             the URL to send the request to.
    +     * @param params          additional HEAD parameters to send with the request.
          * @param responseHandler the response handler instance that should handle the response.
          */
         public void head(String url, RequestParams params, AsyncHttpResponseHandler responseHandler) {
    @@ -333,8 +352,9 @@ public void head(String url, RequestParams params, AsyncHttpResponseHandler resp
     
         /**
          * Perform a HTTP HEAD request without any parameters and track the Android Context which initiated the request.
    -     * @param context the Android Context which initiated the request.
    -     * @param url the URL to send the request to.
    +     *
    +     * @param context         the Android Context which initiated the request.
    +     * @param url             the URL to send the request to.
          * @param responseHandler the response handler instance that should handle the response.
          */
         public void head(Context context, String url, AsyncHttpResponseHandler responseHandler) {
    @@ -343,9 +363,10 @@ public void head(Context context, String url, AsyncHttpResponseHandler responseH
     
         /**
          * Perform a HTTP HEAD request and track the Android Context which initiated the request.
    -     * @param context the Android Context which initiated the request.
    -     * @param url the URL to send the request to.
    -     * @param params additional HEAD parameters to send with the request.
    +     *
    +     * @param context         the Android Context which initiated the request.
    +     * @param url             the URL to send the request to.
    +     * @param params          additional HEAD parameters to send with the request.
          * @param responseHandler the response handler instance that should handle the response.
          */
         public void head(Context context, String url, RequestParams params, AsyncHttpResponseHandler responseHandler) {
    @@ -356,16 +377,16 @@ public void head(Context context, String url, RequestParams params, AsyncHttpRes
          * Perform a HTTP HEAD request and track the Android Context which initiated
          * the request with customized headers
          *
    -     * @param context Context to execute request against
    -     * @param url the URL to send the request to.
    -     * @param headers set headers only for this request
    -     * @param params additional HEAD parameters to send with the request.
    +     * @param context         Context to execute request against
    +     * @param url             the URL to send the request to.
    +     * @param headers         set headers only for this request
    +     * @param params          additional HEAD parameters to send with the request.
          * @param responseHandler the response handler instance that should handle
    -     *        the response.
    +     *                        the response.
          */
         public void head(Context context, String url, Header[] headers, RequestParams params, AsyncHttpResponseHandler responseHandler) {
             HttpUriRequest request = new HttpHead(getUrlWithQueryString(url, params));
    -        if(headers != null) request.setHeaders(headers);
    +        if (headers != null) request.setHeaders(headers);
             sendRequest(httpClient, httpContext, request, null, responseHandler,
                     context);
         }
    
    From 8e2dc9a81efa460147265efe12a9ad2d0f753206 Mon Sep 17 00:00:00 2001
    From: mareksebera 
    Date: Sat, 12 Oct 2013 05:37:56 +0200
    Subject: [PATCH 073/613] Fixes #194
    
    ---
     library/src/com/loopj/android/http/AsyncHttpClient.java | 2 +-
     1 file changed, 1 insertion(+), 1 deletion(-)
    
    diff --git a/library/src/com/loopj/android/http/AsyncHttpClient.java b/library/src/com/loopj/android/http/AsyncHttpClient.java
    index 5f4c928f8..5804d3af0 100644
    --- a/library/src/com/loopj/android/http/AsyncHttpClient.java
    +++ b/library/src/com/loopj/android/http/AsyncHttpClient.java
    @@ -687,7 +687,7 @@ protected void sendRequest(DefaultHttpClient client, HttpContext httpContext, Ht
         public static String getUrlWithQueryString(String url, RequestParams params) {
             if (params != null) {
                 String paramString = params.getParamString();
    -            if (url.indexOf("?") == -1) {
    +            if (!url.contains("?")) {
                     url += "?" + paramString;
                 } else {
                     url += "&" + paramString;
    
    From fe7848fe73033972fe58fdecc64bccdfb981878a Mon Sep 17 00:00:00 2001
    From: mareksebera 
    Date: Sat, 12 Oct 2013 05:40:45 +0200
    Subject: [PATCH 074/613] Fixes #206
    
    ---
     .../loopj/android/http/AsyncHttpClient.java    | 18 +++++++++++++-----
     1 file changed, 13 insertions(+), 5 deletions(-)
    
    diff --git a/library/src/com/loopj/android/http/AsyncHttpClient.java b/library/src/com/loopj/android/http/AsyncHttpClient.java
    index 5804d3af0..50ff634ad 100644
    --- a/library/src/com/loopj/android/http/AsyncHttpClient.java
    +++ b/library/src/com/loopj/android/http/AsyncHttpClient.java
    @@ -136,6 +136,10 @@ public AsyncHttpClient() {
             schemeRegistry.register(new Scheme("https", SSLSocketFactory.getSocketFactory(), 443));
             ThreadSafeClientConnManager cm = new ThreadSafeClientConnManager(httpParams, schemeRegistry);
     
    +        threadPool = (ThreadPoolExecutor) Executors.newCachedThreadPool();
    +        requestMap = new WeakHashMap>>>();
    +        clientHeaderMap = new HashMap();
    +
             httpContext = new SyncBasicHttpContext(new BasicHttpContext());
             httpClient = new DefaultHttpClient(cm, httpParams);
             httpClient.addRequestInterceptor(new HttpRequestInterceptor() {
    @@ -170,11 +174,6 @@ public void process(HttpResponse response, HttpContext context) {
             });
     
             httpClient.setHttpRequestRetryHandler(new RetryHandler(DEFAULT_MAX_RETRIES));
    -
    -        threadPool = (ThreadPoolExecutor) Executors.newCachedThreadPool();
    -
    -        requestMap = new WeakHashMap>>>();
    -        clientHeaderMap = new HashMap();
         }
     
         /**
    @@ -275,6 +274,15 @@ public void addHeader(String header, String value) {
             clientHeaderMap.put(header, value);
         }
     
    +    /**
    +     * Remove header from all requests this client makes (before sending).
    +     *
    +     * @param header the name of the header
    +     */
    +    public void removeHeader(String header) {
    +        clientHeaderMap.remove(header);
    +    }
    +
         /**
          * Sets basic authentication for the request. Uses AuthScope.ANY. This is the same as
          * setBasicAuth('username','password',AuthScope.ANY)
    
    From 0758d022d3bb3ba4acec92946a7624f79a76e605 Mon Sep 17 00:00:00 2001
    From: mareksebera 
    Date: Sat, 12 Oct 2013 05:48:45 +0200
    Subject: [PATCH 075/613] Added Proxy to interface, Fixes #210
    
    ---
     .../com/loopj/android/http/AsyncHttpClient.java    | 14 ++++++++++++++
     1 file changed, 14 insertions(+)
    
    diff --git a/library/src/com/loopj/android/http/AsyncHttpClient.java b/library/src/com/loopj/android/http/AsyncHttpClient.java
    index 50ff634ad..b4c6fab8e 100644
    --- a/library/src/com/loopj/android/http/AsyncHttpClient.java
    +++ b/library/src/com/loopj/android/http/AsyncHttpClient.java
    @@ -23,6 +23,7 @@
     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;
    @@ -42,6 +43,7 @@
     import org.apache.http.client.protocol.ClientContext;
     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;
    @@ -254,6 +256,18 @@ public void setTimeout(int timeout) {
             HttpConnectionParams.setConnectionTimeout(httpParams, timeout);
         }
     
    +    /**
    +     * Sets the Proxy by it's hostname and port
    +     *
    +     * @param hostname  the hostname (IP or DNS name)
    +     * @param port  the port number. -1 indicates the scheme default port.
    +     */
    +    public void setProxy(String hostname, int port){
    +        final HttpHost proxy = new HttpHost(hostname, port);
    +        final HttpParams httpParams = this.httpClient.getParams();
    +        httpParams.setParameter(ConnRoutePNames.DEFAULT_PROXY, proxy);
    +    }
    +
         /**
          * Sets the SSLSocketFactory to user when making requests. By default,
          * a new, default SSLSocketFactory is used.
    
    From 43b39f53cff0f7a9758f9735b7f6657fb9a14c62 Mon Sep 17 00:00:00 2001
    From: mareksebera 
    Date: Sat, 12 Oct 2013 05:55:53 +0200
    Subject: [PATCH 076/613] Example from #236
    
    ---
     examples/TestCaseExampleUsage.java | 63 ++++++++++++++++++++++++++++++
     1 file changed, 63 insertions(+)
     create mode 100644 examples/TestCaseExampleUsage.java
    
    diff --git a/examples/TestCaseExampleUsage.java b/examples/TestCaseExampleUsage.java
    new file mode 100644
    index 000000000..ce9498179
    --- /dev/null
    +++ b/examples/TestCaseExampleUsage.java
    @@ -0,0 +1,63 @@
    +import java.util.concurrent.CountDownLatch;
    +import java.util.concurrent.TimeUnit;
    +
    +import com.loopj.android.http.AsyncHttpClient;
    +import com.loopj.android.http.AsyncHttpResponseHandler;
    +
    +import android.test.InstrumentationTestCase;
    +import android.util.Log;
    +
    +// Credits to Wuyexiong 
    +// See: https://github.com/loopj/android-async-http/pull/236
    +public class TestCaseExampleUsage extends InstrumentationTestCase
    +{
    +	protected String TAG = TestCaseExampleUsage.class.getSimpleName();
    +
    +	public void testAsync() throws Throwable
    +	{
    +		final CountDownLatch signal = new CountDownLatch(1);
    +		runTestOnUiThread(new Runnable()
    +		{
    +			@Override
    +			public void run()
    +			{
    +				AsyncHttpClient client = new AsyncHttpClient();
    +
    +				client.get("http://www.google.com/", new AsyncHttpResponseHandler()
    +				{
    +					@Override
    +					public void onStart()
    +					{
    +						Log.v(TAG , "onStart");
    +					}
    +
    +					@Override
    +					public void onSuccess(String response)
    +					{
    +						Log.v(TAG , "onSuccess");
    +						System.out.println(response);
    +					}
    +
    +					@Override
    +					public void onFailure(Throwable error, String content)
    +					{
    +						Log.e(TAG , "onFailure error : " + error.toString() + "content : " + content);
    +					}
    +
    +					@Override
    +					public void onFinish()
    +					{
    +						Log.v(TAG , "onFinish");
    +						signal.countDown();
    +					}
    +				});
    +
    +				try {
    +					signal.await(30, TimeUnit.SECONDS);
    +				} catch (InterruptedException e) {
    +				}
    +				Log.v(TAG , "TestCaseExampleUsage Over");
    +			}
    +		});
    +	}
    +}
    \ No newline at end of file
    
    From a1ad31c3dd7b5b112340111ea7601274262131ed Mon Sep 17 00:00:00 2001
    From: mareksebera 
    Date: Sat, 12 Oct 2013 06:12:42 +0200
    Subject: [PATCH 077/613] DELETE request with params, Fixed #259
    
    ---
     .../com/loopj/android/http/AsyncHttpClient.java   | 15 +++++++++++++++
     1 file changed, 15 insertions(+)
    
    diff --git a/library/src/com/loopj/android/http/AsyncHttpClient.java b/library/src/com/loopj/android/http/AsyncHttpClient.java
    index b4c6fab8e..6025b9e53 100644
    --- a/library/src/com/loopj/android/http/AsyncHttpClient.java
    +++ b/library/src/com/loopj/android/http/AsyncHttpClient.java
    @@ -683,6 +683,21 @@ public void delete(Context context, String url, Header[] headers, AsyncHttpRespo
             sendRequest(httpClient, httpContext, delete, null, responseHandler, context);
         }
     
    +    /**
    +     * Perform a HTTP DELETE request.
    +     *
    +     * @param context         the Android Context which initiated the request.
    +     * @param url             the URL to send the request to.
    +     * @param headers         set one-time headers for this request
    +     * @param params          additional DELETE parameters or files to send along with request
    +     * @param responseHandler the response handler instance that should handle the response.
    +     */
    +    public void delete(Context context, String url, Header[] headers, RequestParams params, AsyncHttpResponseHandler responseHandler) {
    +        HttpDelete httpDelete = new HttpDelete(getUrlWithQueryString(url, params));
    +        if(headers != null) httpDelete.setHeaders(headers);
    +        sendRequest(httpClient, httpContext, httpDelete, null, responseHandler, context);
    +    }
    +
     
         // Private stuff
         protected void sendRequest(DefaultHttpClient client, HttpContext httpContext, HttpUriRequest uriRequest, String contentType, AsyncHttpResponseHandler responseHandler, Context context) {
    
    From 7f9016e9e2a3a5f9c3a98f87ee5a70bc9ba18ae5 Mon Sep 17 00:00:00 2001
    From: Marek Sebera 
    Date: Sat, 12 Oct 2013 06:54:24 +0200
    Subject: [PATCH 078/613] Added Maven description to README file [skip ci]
    
    ---
     README.md | 24 ++++++++++++++++++++++++
     1 file changed, 24 insertions(+)
    
    diff --git a/README.md b/README.md
    index 3d7bd95ed..a576fb38e 100644
    --- a/README.md
    +++ b/README.md
    @@ -19,6 +19,30 @@ Features
     - Optional built-in response parsing into **JSON** (JsonHttpResponseHandler)
     - Optional **persistent cookie store**, saves cookies into your app's SharedPreferences
     
    +Maven
    +-----
    +You can now integrate this library in your project via Maven. There are available two kind of builds.
    +
    +**development snapshots**
    +https://oss.sonatype.org/content/repositories/snapshots/com/loopj/android/android-async-http/
    +```
    +Maven URL: https://oss.sonatype.org/content/repositories/snapshots/
    +GroupId: com.loopj.android
    +ArtifactId: async-http-client
    +Version: 1.4.4-SNAPSHOT
    +Packaging: JAR or AAR
    +```
    +
    +**releases, maven central**
    +
    +http://central.maven.org/maven2/com/loopj/android/android-async-http/
    +```
    +Maven URL: http://repo1.maven.org/maven2/
    +GroupId: com.loopj.android
    +ArtifactId: async-http-client
    +Version: 1.4.3
    +Packaging: JAR or AAR
    +```
     
     Documentation, Features and Examples
     ------------------------------------
    
    From 6389fa9bea52aa33b39421a8b97d679209c55292 Mon Sep 17 00:00:00 2001
    From: mareksebera 
    Date: Sat, 12 Oct 2013 21:14:04 +0200
    Subject: [PATCH 079/613] Fixes #295
    
    ---
     library/src/com/loopj/android/http/RetryHandler.java | 2 +-
     1 file changed, 1 insertion(+), 1 deletion(-)
    
    diff --git a/library/src/com/loopj/android/http/RetryHandler.java b/library/src/com/loopj/android/http/RetryHandler.java
    index ba33a38cf..59eb5ffa9 100644
    --- a/library/src/com/loopj/android/http/RetryHandler.java
    +++ b/library/src/com/loopj/android/http/RetryHandler.java
    @@ -88,7 +88,7 @@ public boolean retryRequest(IOException exception, int executionCount, HttpConte
             if (retry) {
                 // resend all idempotent requests
                 HttpUriRequest currentReq = (HttpUriRequest) context.getAttribute(ExecutionContext.HTTP_REQUEST);
    -            String requestType = currentReq.getMethod();
    +            String requestType = currentReq != null ? currentReq.getMethod() : "";
                 retry = !requestType.equals("POST");
             }
     
    
    From 334609426c376093d68d544bf6a8b97da8eaf038 Mon Sep 17 00:00:00 2001
    From: mareksebera 
    Date: Sat, 12 Oct 2013 21:15:40 +0200
    Subject: [PATCH 080/613] Fixes #287
    
    ---
     library/src/com/loopj/android/http/SyncHttpClient.java | 6 ++++++
     1 file changed, 6 insertions(+)
    
    diff --git a/library/src/com/loopj/android/http/SyncHttpClient.java b/library/src/com/loopj/android/http/SyncHttpClient.java
    index fdb3e3c39..8e73471a1 100644
    --- a/library/src/com/loopj/android/http/SyncHttpClient.java
    +++ b/library/src/com/loopj/android/http/SyncHttpClient.java
    @@ -3,6 +3,7 @@
     import android.content.Context;
     import android.os.Message;
     
    +import org.apache.http.HttpEntity;
     import org.apache.http.client.methods.HttpUriRequest;
     import org.apache.http.impl.client.DefaultHttpClient;
     import org.apache.http.protocol.HttpContext;
    @@ -100,6 +101,11 @@ public String put(String url) {
             return result;
         }
     
    +    public String post(String url, HttpEntity entity){
    +        this.post(null, url, entity, null, responseHandler);
    +        return result;
    +    }
    +
         public String post(String url, RequestParams params) {
             this.post(url, params, responseHandler);
             return result;
    
    From d3b52ee3367f9585da3a05142f830a3eb56fb68e Mon Sep 17 00:00:00 2001
    From: mareksebera 
    Date: Sat, 12 Oct 2013 21:17:23 +0200
    Subject: [PATCH 081/613] Fixes #294
    
    ---
     library/src/com/loopj/android/http/AsyncHttpResponseHandler.java | 1 +
     1 file changed, 1 insertion(+)
    
    diff --git a/library/src/com/loopj/android/http/AsyncHttpResponseHandler.java b/library/src/com/loopj/android/http/AsyncHttpResponseHandler.java
    index 5739a38e4..5fb2ffa1b 100644
    --- a/library/src/com/loopj/android/http/AsyncHttpResponseHandler.java
    +++ b/library/src/com/loopj/android/http/AsyncHttpResponseHandler.java
    @@ -253,6 +253,7 @@ protected void sendResponseMessage(HttpResponse response) {
                 }
             } catch (IOException e) {
                 sendFailureMessage(e, (String) null);
    +            return;
             }
     
             if (status.getStatusCode() >= 300) {
    
    From 6371c686ed14ab2b3497e660c4449252a0ef5a97 Mon Sep 17 00:00:00 2001
    From: mareksebera 
    Date: Sat, 12 Oct 2013 21:20:22 +0200
    Subject: [PATCH 082/613] Fixes #286
    
    ---
     .../android/http/AsyncHttpResponseHandler.java      | 13 ++++++++++++-
     1 file changed, 12 insertions(+), 1 deletion(-)
    
    diff --git a/library/src/com/loopj/android/http/AsyncHttpResponseHandler.java b/library/src/com/loopj/android/http/AsyncHttpResponseHandler.java
    index 5fb2ffa1b..df297bb83 100644
    --- a/library/src/com/loopj/android/http/AsyncHttpResponseHandler.java
    +++ b/library/src/com/loopj/android/http/AsyncHttpResponseHandler.java
    @@ -74,6 +74,17 @@ public class AsyncHttpResponseHandler {
         protected static final int FINISH_MESSAGE = 3;
     
         private Handler handler;
    +    private String responseCharset = "UTF-8";
    +
    +    /**
    +     * Sets the charset for the response string. If not set, the default is UTF-8.
    +     * @see Charset
    +     *
    +     * @param charset to be used for the response string.
    +     */
    +    public void setCharset(final String charset) {
    +        this.responseCharset = charset;
    +    }
     
         /**
          * Creates a new AsyncHttpResponseHandler
    @@ -249,7 +260,7 @@ protected void sendResponseMessage(HttpResponse response) {
                 HttpEntity temp = response.getEntity();
                 if (temp != null) {
                     entity = new BufferedHttpEntity(temp);
    -                responseBody = EntityUtils.toString(entity, "UTF-8");
    +                responseBody = EntityUtils.toString(entity, responseCharset);
                 }
             } catch (IOException e) {
                 sendFailureMessage(e, (String) null);
    
    From 29fe794e963d8fe5054deac42ea1c5355fafcdc4 Mon Sep 17 00:00:00 2001
    From: mareksebera 
    Date: Sat, 12 Oct 2013 21:25:18 +0200
    Subject: [PATCH 083/613] Closes #283
    
    ---
     examples/CookieVideoView.java | 682 ++++++++++++++++++++++++++++++++++
     1 file changed, 682 insertions(+)
     create mode 100644 examples/CookieVideoView.java
    
    diff --git a/examples/CookieVideoView.java b/examples/CookieVideoView.java
    new file mode 100644
    index 000000000..998daa1fa
    --- /dev/null
    +++ b/examples/CookieVideoView.java
    @@ -0,0 +1,682 @@
    +/*
    + * 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.
    + */
    +
    +import android.annotation.SuppressLint;
    +import android.annotation.TargetApi;
    +import android.app.AlertDialog;
    +import android.content.Context;
    +import android.content.DialogInterface;
    +import android.content.Intent;
    +import android.media.AudioManager;
    +import android.media.MediaPlayer;
    +import android.media.MediaPlayer.OnCompletionListener;
    +import android.media.MediaPlayer.OnErrorListener;
    +import android.media.MediaPlayer.OnInfoListener;
    +import android.net.Uri;
    +import android.os.Build;
    +import android.util.AttributeSet;
    +import android.util.Log;
    +import android.view.KeyEvent;
    +import android.view.MotionEvent;
    +import android.view.SurfaceHolder;
    +import android.view.SurfaceView;
    +import android.view.View;
    +import android.view.accessibility.AccessibilityEvent;
    +import android.view.accessibility.AccessibilityNodeInfo;
    +import android.widget.MediaController;
    +import android.widget.MediaController.MediaPlayerControl;
    +
    +import java.io.IOException;
    +import java.util.HashMap;
    +import java.util.List;
    +import java.util.Map;
    +
    +import org.apache.http.cookie.Cookie;
    +
    +/**
    + * Displays a video file.  The VideoView class
    + * can load images from various sources (such as resources or content
    + * providers), takes care of computing its measurement from the video so that
    + * it can be used in any layout manager, and provides various display options
    + * such as scaling and tinting.
    + *
    + * @author Jungho Bang 
    + */
    +public class CookieVideoView extends SurfaceView implements MediaPlayerControl {
    +    private String TAG = "CookieVideoView";
    +    // settable by the client
    +    private Uri         mUri;
    +    private Map mHeaders;
    +
    +    // all possible internal states
    +    private static final int STATE_ERROR              = -1;
    +    private static final int STATE_IDLE               = 0;
    +    private static final int STATE_PREPARING          = 1;
    +    private static final int STATE_PREPARED           = 2;
    +    private static final int STATE_PLAYING            = 3;
    +    private static final int STATE_PAUSED             = 4;
    +    private static final int STATE_PLAYBACK_COMPLETED = 5;
    +
    +    // mCurrentState is a VideoView object's current state.
    +    // mTargetState is the state that a method caller intends to reach.
    +    // For instance, regardless the VideoView object's current state,
    +    // calling pause() intends to bring the object to a target state
    +    // of STATE_PAUSED.
    +    private int mCurrentState = STATE_IDLE;
    +    private int mTargetState  = STATE_IDLE;
    +
    +    // All the stuff we need for playing and showing a video
    +    private SurfaceHolder mSurfaceHolder = null;
    +    private MediaPlayer mMediaPlayer = null;
    +    private int         mVideoWidth;
    +    private int         mVideoHeight;
    +    private int         mSurfaceWidth;
    +    private int         mSurfaceHeight;
    +    private MediaController mMediaController;
    +    private OnCompletionListener mOnCompletionListener;
    +    private MediaPlayer.OnPreparedListener mOnPreparedListener;
    +    private int         mCurrentBufferPercentage;
    +    private OnErrorListener mOnErrorListener;
    +    private OnInfoListener  mOnInfoListener;
    +    private int         mSeekWhenPrepared;  // recording the seek position while preparing
    +    private boolean     mCanPause;
    +    private boolean     mCanSeekBack;
    +    private boolean     mCanSeekForward;
    +	private Context 	mContext;
    +
    +    public CookieVideoView(Context context) {
    +        super(context);
    +        initVideoView(context);
    +    }
    +
    +    public CookieVideoView(Context context, AttributeSet attrs) {
    +        this(context, attrs, 0);
    +        initVideoView(context);
    +    }
    +
    +    public CookieVideoView(Context context, AttributeSet attrs, int defStyle) {
    +        super(context, attrs, defStyle);
    +        initVideoView(context);
    +    }
    +
    +    @Override
    +    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    +        //Log.i("@@@@", "onMeasure");
    +        int width = getDefaultSize(mVideoWidth, widthMeasureSpec);
    +        int height = getDefaultSize(mVideoHeight, heightMeasureSpec);
    +        if (mVideoWidth > 0 && mVideoHeight > 0) {
    +            if ( mVideoWidth * height  > width * mVideoHeight ) {
    +                //Log.i("@@@", "image too tall, correcting");
    +                height = width * mVideoHeight / mVideoWidth;
    +            } else if ( mVideoWidth * height  < width * mVideoHeight ) {
    +                //Log.i("@@@", "image too wide, correcting");
    +                width = height * mVideoWidth / mVideoHeight;
    +            } else {
    +                //Log.i("@@@", "aspect ratio is correct: " +
    +                        //width+"/"+height+"="+
    +                        //mVideoWidth+"/"+mVideoHeight);
    +            }
    +        }
    +        //Log.i("@@@@@@@@@@", "setting size: " + width + 'x' + height);
    +        setMeasuredDimension(width, height);
    +    }
    +
    +    @SuppressLint("NewApi")
    +	@Override
    +    public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
    +        super.onInitializeAccessibilityEvent(event);
    +        event.setClassName(CookieVideoView.class.getName());
    +    }
    +
    +    @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
    +	@Override
    +    public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
    +        super.onInitializeAccessibilityNodeInfo(info);
    +        info.setClassName(CookieVideoView.class.getName());
    +    }
    +
    +    public int resolveAdjustedSize(int desiredSize, int measureSpec) {
    +        int result = desiredSize;
    +        int specMode = MeasureSpec.getMode(measureSpec);
    +        int specSize =  MeasureSpec.getSize(measureSpec);
    +
    +        switch (specMode) {
    +            case MeasureSpec.UNSPECIFIED:
    +                /* Parent says we can be as big as we want. Just don't be larger
    +                 * than max size imposed on ourselves.
    +                 */
    +                result = desiredSize;
    +                break;
    +
    +            case MeasureSpec.AT_MOST:
    +                /* Parent says we can be as big as we want, up to specSize.
    +                 * Don't be larger than specSize, and don't be larger than
    +                 * the max size imposed on ourselves.
    +                 */
    +                result = Math.min(desiredSize, specSize);
    +                break;
    +
    +            case MeasureSpec.EXACTLY:
    +                // No choice. Do what we are told.
    +                result = specSize;
    +                break;
    +        }
    +        return result;
    +}
    +
    +    private void initVideoView(Context context) {
    +        mVideoWidth = 0;
    +        mVideoHeight = 0;
    +        getHolder().addCallback(mSHCallback);
    +        getHolder().setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
    +        setFocusable(true);
    +        setFocusableInTouchMode(true);
    +        requestFocus();
    +        mCurrentState = STATE_IDLE;
    +        mTargetState  = STATE_IDLE;
    +        mContext = context;
    +    }
    +
    +    public void setVideoPath(String path) {
    +        setVideoURI(Uri.parse(path));
    +    }
    +
    +    public void setVideoURI(Uri uri) {
    +    	mUri = uri;
    +        mHeaders = getCookieHeader();
    +    	mSeekWhenPrepared = 0;
    +        openVideo();
    +        requestLayout();
    +        invalidate();
    +    }
    +    
    +    private Map getCookieHeader() {
    +    	String hostDomain = mUri.getHost();
    +    	List cookieList = new PersistentCookieStore(mContext).getCookies();
    +    	for(Cookie cookie : cookieList) {
    +    		if (cookie.getDomain().equalsIgnoreCase(hostDomain)) {
    +    			Map header = new HashMap();
    +    	    	header.put("Cookie", cookie.getName() + "=" + cookie.getValue());
    +    	    	Log.d(TAG,"Cookie: "+header.toString());
    +    	        return header;
    +    	    }
    +    	}
    +    	return null;
    +    }
    +    
    +    public void stopPlayback() {
    +        if (mMediaPlayer != null) {
    +            mMediaPlayer.stop();
    +            mMediaPlayer.release();
    +            mMediaPlayer = null;
    +            mCurrentState = STATE_IDLE;
    +            mTargetState  = STATE_IDLE;
    +        }
    +    }
    +
    +    private void openVideo() {
    +        if (mUri == null || mSurfaceHolder == null) {
    +            // not ready for playback just yet, will try again later
    +            return;
    +        }
    +        // Tell the music playback service to pause
    +        // TODO: these constants need to be published somewhere in the framework.
    +        Intent i = new Intent("com.android.music.musicservicecommand");
    +        i.putExtra("command", "pause");
    +        mContext.sendBroadcast(i);
    +
    +        // we shouldn't clear the target state, because somebody might have
    +        // called start() previously
    +        release(false);
    +        try {
    +            mMediaPlayer = new MediaPlayer();
    +            mMediaPlayer.setOnPreparedListener(mPreparedListener);
    +            mMediaPlayer.setOnVideoSizeChangedListener(mSizeChangedListener);
    +            mMediaPlayer.setOnCompletionListener(mCompletionListener);
    +            mMediaPlayer.setOnErrorListener(mErrorListener);
    +            mMediaPlayer.setOnInfoListener(mOnInfoListener);
    +            mMediaPlayer.setOnBufferingUpdateListener(mBufferingUpdateListener);
    +            mCurrentBufferPercentage = 0;
    +            mMediaPlayer.setDataSource(mContext, mUri, mHeaders);
    +            mMediaPlayer.setDisplay(mSurfaceHolder);
    +            mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
    +            mMediaPlayer.setScreenOnWhilePlaying(true);
    +            mMediaPlayer.prepareAsync();
    +            // we don't set the target state here either, but preserve the
    +            // target state that was there before.
    +            mCurrentState = STATE_PREPARING;
    +            attachMediaController();
    +        } catch (IOException ex) {
    +            Log.w(TAG, "Unable to open content: " + mUri, ex);
    +            mCurrentState = STATE_ERROR;
    +            mTargetState = STATE_ERROR;
    +            mErrorListener.onError(mMediaPlayer, MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
    +            return;
    +        } catch (IllegalArgumentException ex) {
    +            Log.w(TAG, "Unable to open content: " + mUri, ex);
    +            mCurrentState = STATE_ERROR;
    +            mTargetState = STATE_ERROR;
    +            mErrorListener.onError(mMediaPlayer, MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
    +            return;
    +        }
    +    }
    +
    +    public void setMediaController(MediaController controller) {
    +        if (mMediaController != null) {
    +            mMediaController.hide();
    +        }
    +        mMediaController = controller;
    +        attachMediaController();
    +    }
    +
    +    private void attachMediaController() {
    +        if (mMediaPlayer != null && mMediaController != null) {
    +            mMediaController.setMediaPlayer(this);
    +            View anchorView = this.getParent() instanceof View ?
    +                    (View)this.getParent() : this;
    +            mMediaController.setAnchorView(anchorView);
    +            mMediaController.setEnabled(isInPlaybackState());
    +        }
    +    }
    +
    +    MediaPlayer.OnVideoSizeChangedListener mSizeChangedListener =
    +        new MediaPlayer.OnVideoSizeChangedListener() {
    +            public void onVideoSizeChanged(MediaPlayer mp, int width, int height) {
    +                mVideoWidth = mp.getVideoWidth();
    +                mVideoHeight = mp.getVideoHeight();
    +                if (mVideoWidth != 0 && mVideoHeight != 0) {
    +                    getHolder().setFixedSize(mVideoWidth, mVideoHeight);
    +                    requestLayout();
    +                }
    +            }
    +    };
    +
    +    MediaPlayer.OnPreparedListener mPreparedListener = new MediaPlayer.OnPreparedListener() {
    +        public void onPrepared(MediaPlayer mp) {
    +            mCurrentState = STATE_PREPARED;
    +
    +            // Get the capabilities of the player for this stream
    +//            Metadata data = mp.getMetadata(MediaPlayer.METADATA_ALL,
    +//                                      MediaPlayer.BYPASS_METADATA_FILTER);
    +//            
    +//          if (data != null) {
    +//                mCanPause = !data.has(Metadata.PAUSE_AVAILABLE)
    +//                        || data.getBoolean(Metadata.PAUSE_AVAILABLE);
    +//                mCanSeekBack = !data.has(Metadata.SEEK_BACKWARD_AVAILABLE)
    +//                        || data.getBoolean(Metadata.SEEK_BACKWARD_AVAILABLE);
    +//                mCanSeekForward = !data.has(Metadata.SEEK_FORWARD_AVAILABLE)
    +//                        || data.getBoolean(Metadata.SEEK_FORWARD_AVAILABLE);
    +//            } else {
    +                mCanPause = mCanSeekBack = mCanSeekForward = true;
    +//            }
    +
    +            if (mOnPreparedListener != null) {
    +                mOnPreparedListener.onPrepared(mMediaPlayer);
    +            }
    +            if (mMediaController != null) {
    +                mMediaController.setEnabled(true);
    +            }
    +            mVideoWidth = mp.getVideoWidth();
    +            mVideoHeight = mp.getVideoHeight();
    +
    +            int seekToPosition = mSeekWhenPrepared;  // mSeekWhenPrepared may be changed after seekTo() call
    +            if (seekToPosition != 0) {
    +                seekTo(seekToPosition);
    +            }
    +            if (mVideoWidth != 0 && mVideoHeight != 0) {
    +                //Log.i("@@@@", "video size: " + mVideoWidth +"/"+ mVideoHeight);
    +                getHolder().setFixedSize(mVideoWidth, mVideoHeight);
    +                if (mSurfaceWidth == mVideoWidth && mSurfaceHeight == mVideoHeight) {
    +                    // We didn't actually change the size (it was already at the size
    +                    // we need), so we won't get a "surface changed" callback, so
    +                    // start the video here instead of in the callback.
    +                    if (mTargetState == STATE_PLAYING) {
    +                        start();
    +                        if (mMediaController != null) {
    +                            mMediaController.show();
    +                        }
    +                    } else if (!isPlaying() &&
    +                               (seekToPosition != 0 || getCurrentPosition() > 0)) {
    +                       if (mMediaController != null) {
    +                           // Show the media controls when we're paused into a video and make 'em stick.
    +                           mMediaController.show(0);
    +                       }
    +                   }
    +                }
    +            } else {
    +                // We don't know the video size yet, but should start anyway.
    +                // The video size might be reported to us later.
    +                if (mTargetState == STATE_PLAYING) {
    +                    start();
    +                }
    +            }
    +        }
    +    };
    +
    +    private MediaPlayer.OnCompletionListener mCompletionListener =
    +        new MediaPlayer.OnCompletionListener() {
    +        public void onCompletion(MediaPlayer mp) {
    +            mCurrentState = STATE_PLAYBACK_COMPLETED;
    +            mTargetState = STATE_PLAYBACK_COMPLETED;
    +            if (mMediaController != null) {
    +                mMediaController.hide();
    +            }
    +            if (mOnCompletionListener != null) {
    +                mOnCompletionListener.onCompletion(mMediaPlayer);
    +            }
    +        }
    +    };
    +
    +    private MediaPlayer.OnErrorListener mErrorListener =
    +        new MediaPlayer.OnErrorListener() {
    +        public boolean onError(MediaPlayer mp, int framework_err, int impl_err) {
    +            Log.d(TAG, "Error: " + framework_err + "," + impl_err);
    +            mCurrentState = STATE_ERROR;
    +            mTargetState = STATE_ERROR;
    +            if (mMediaController != null) {
    +                mMediaController.hide();
    +            }
    +
    +            /* If an error handler has been supplied, use it and finish. */
    +            if (mOnErrorListener != null) {
    +                if (mOnErrorListener.onError(mMediaPlayer, framework_err, impl_err)) {
    +                    return true;
    +                }
    +            }
    +
    +            /* Otherwise, pop up an error dialog so the user knows that
    +             * something bad has happened. Only try and pop up the dialog
    +             * if we're attached to a window. When we're going away and no
    +             * longer have a window, don't bother showing the user an error.
    +             */
    +            if (getWindowToken() != null) {
    +//                Resources r = mContext.getResources();
    +                int messageId;
    +
    +                if (framework_err == MediaPlayer.MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK) {
    +                    //eg. This video isn\'t valid for streaming to this device.
    +                    messageId = R.string.VideoView_error_text_invalid_progressive_playback;
    +                } else {
    +                    //eg. Can\'t play this video.
    +                    messageId = R.string.VideoView_error_text_unknown;
    +                }
    +
    +                new AlertDialog.Builder(mContext)
    +                        .setMessage(messageId)
    +                        //eg. OK
    +                        .setPositiveButton(R.string.VideoView_error_button,
    +                                new DialogInterface.OnClickListener() {
    +                                    public void onClick(DialogInterface dialog, int whichButton) {
    +                                        /* If we get here, there is no onError listener, so
    +                                         * at least inform them that the video is over.
    +                                         */
    +                                        if (mOnCompletionListener != null) {
    +                                            mOnCompletionListener.onCompletion(mMediaPlayer);
    +                                        }
    +                                    }
    +                                })
    +                        .setCancelable(false)
    +                        .show();
    +            }
    +            return true;
    +        }
    +    };
    +
    +    private MediaPlayer.OnBufferingUpdateListener mBufferingUpdateListener =
    +        new MediaPlayer.OnBufferingUpdateListener() {
    +        public void onBufferingUpdate(MediaPlayer mp, int percent) {
    +            mCurrentBufferPercentage = percent;
    +        }
    +    };
    +
    +    /**
    +     * Register a callback to be invoked when the media file
    +     * is loaded and ready to go.
    +     *
    +     * @param l The callback that will be run
    +     */
    +    public void setOnPreparedListener(MediaPlayer.OnPreparedListener l)
    +    {
    +        mOnPreparedListener = l;
    +    }
    +
    +    /**
    +     * Register a callback to be invoked when the end of a media file
    +     * has been reached during playback.
    +     *
    +     * @param l The callback that will be run
    +     */
    +    public void setOnCompletionListener(OnCompletionListener l)
    +    {
    +        mOnCompletionListener = l;
    +    }
    +
    +    /**
    +     * Register a callback to be invoked when an error occurs
    +     * during playback or setup.  If no listener is specified,
    +     * or if the listener returned false, VideoView will inform
    +     * the user of any errors.
    +     *
    +     * @param l The callback that will be run
    +     */
    +    public void setOnErrorListener(OnErrorListener l)
    +    {
    +        mOnErrorListener = l;
    +    }
    +
    +    /**
    +     * Register a callback to be invoked when an informational event
    +     * occurs during playback or setup.
    +     *
    +     * @param l The callback that will be run
    +     */
    +    public void setOnInfoListener(OnInfoListener l) {
    +        mOnInfoListener = l;
    +    }
    +
    +    SurfaceHolder.Callback mSHCallback = new SurfaceHolder.Callback()
    +    {
    +        public void surfaceChanged(SurfaceHolder holder, int format,
    +                                    int w, int h)
    +        {
    +            mSurfaceWidth = w;
    +            mSurfaceHeight = h;
    +            boolean isValidState =  (mTargetState == STATE_PLAYING);
    +            boolean hasValidSize = (mVideoWidth == w && mVideoHeight == h);
    +            if (mMediaPlayer != null && isValidState && hasValidSize) {
    +                if (mSeekWhenPrepared != 0) {
    +                    seekTo(mSeekWhenPrepared);
    +                }
    +                start();
    +            }
    +        }
    +
    +        public void surfaceCreated(SurfaceHolder holder)
    +        {
    +            mSurfaceHolder = holder;
    +            openVideo();
    +        }
    +
    +        public void surfaceDestroyed(SurfaceHolder holder)
    +        {
    +            // after we return from this we can't use the surface any more
    +            mSurfaceHolder = null;
    +            if (mMediaController != null) mMediaController.hide();
    +            release(true);
    +        }
    +    };
    +
    +    /*
    +     * release the media player in any state
    +     */
    +    private void release(boolean cleartargetstate) {
    +        if (mMediaPlayer != null) {
    +            mMediaPlayer.reset();
    +            mMediaPlayer.release();
    +            mMediaPlayer = null;
    +            mCurrentState = STATE_IDLE;
    +            if (cleartargetstate) {
    +                mTargetState  = STATE_IDLE;
    +            }
    +        }
    +    }
    +
    +    @Override
    +    public boolean onTouchEvent(MotionEvent ev) {
    +        if (isInPlaybackState() && mMediaController != null) {
    +            toggleMediaControlsVisiblity();
    +        }
    +        return false;
    +    }
    +
    +    @Override
    +    public boolean onTrackballEvent(MotionEvent ev) {
    +        if (isInPlaybackState() && mMediaController != null) {
    +            toggleMediaControlsVisiblity();
    +        }
    +        return false;
    +    }
    +
    +    @Override
    +    public boolean onKeyDown(int keyCode, KeyEvent event)
    +    {
    +        boolean isKeyCodeSupported = keyCode != KeyEvent.KEYCODE_BACK &&
    +                                     keyCode != KeyEvent.KEYCODE_VOLUME_UP &&
    +                                     keyCode != KeyEvent.KEYCODE_VOLUME_DOWN &&
    +                                     keyCode != KeyEvent.KEYCODE_VOLUME_MUTE &&
    +                                     keyCode != KeyEvent.KEYCODE_MENU &&
    +                                     keyCode != KeyEvent.KEYCODE_CALL &&
    +                                     keyCode != KeyEvent.KEYCODE_ENDCALL;
    +        if (isInPlaybackState() && isKeyCodeSupported && mMediaController != null) {
    +            if (keyCode == KeyEvent.KEYCODE_HEADSETHOOK ||
    +                    keyCode == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE) {
    +                if (mMediaPlayer.isPlaying()) {
    +                    pause();
    +                    mMediaController.show();
    +                } else {
    +                    start();
    +                    mMediaController.hide();
    +                }
    +                return true;
    +            } else if (keyCode == KeyEvent.KEYCODE_MEDIA_PLAY) {
    +                if (!mMediaPlayer.isPlaying()) {
    +                    start();
    +                    mMediaController.hide();
    +                }
    +                return true;
    +            } else if (keyCode == KeyEvent.KEYCODE_MEDIA_STOP
    +                    || keyCode == KeyEvent.KEYCODE_MEDIA_PAUSE) {
    +                if (mMediaPlayer.isPlaying()) {
    +                    pause();
    +                    mMediaController.show();
    +                }
    +                return true;
    +            } else {
    +                toggleMediaControlsVisiblity();
    +            }
    +        }
    +
    +        return super.onKeyDown(keyCode, event);
    +    }
    +
    +    private void toggleMediaControlsVisiblity() {
    +        if (mMediaController.isShowing()) {
    +            mMediaController.hide();
    +        } else {
    +            mMediaController.show();
    +        }
    +    }
    +
    +    public void start() {
    +        if (isInPlaybackState()) {
    +            mMediaPlayer.start();
    +            mCurrentState = STATE_PLAYING;
    +        }
    +        mTargetState = STATE_PLAYING;
    +    }
    +
    +    public void pause() {
    +        if (isInPlaybackState()) {
    +            if (mMediaPlayer.isPlaying()) {
    +                mMediaPlayer.pause();
    +                mCurrentState = STATE_PAUSED;
    +            }
    +        }
    +        mTargetState = STATE_PAUSED;
    +    }
    +
    +    public void suspend() {
    +        release(false);
    +    }
    +
    +    public void resume() {
    +        openVideo();
    +    }
    +
    +    public int getDuration() {
    +        if (isInPlaybackState()) {
    +            return mMediaPlayer.getDuration();
    +        }
    +
    +        return -1;
    +    }
    +
    +    public int getCurrentPosition() {
    +        if (isInPlaybackState()) {
    +            return mMediaPlayer.getCurrentPosition();
    +        }
    +        return 0;
    +    }
    +
    +    public void seekTo(int msec) {
    +        if (isInPlaybackState()) {
    +            mMediaPlayer.seekTo(msec);
    +            mSeekWhenPrepared = 0;
    +        } else {
    +            mSeekWhenPrepared = msec;
    +        }
    +    }
    +
    +    public boolean isPlaying() {
    +        return isInPlaybackState() && mMediaPlayer.isPlaying();
    +    }
    +
    +    public int getBufferPercentage() {
    +        if (mMediaPlayer != null) {
    +            return mCurrentBufferPercentage;
    +        }
    +        return 0;
    +    }
    +
    +    private boolean isInPlaybackState() {
    +        return (mMediaPlayer != null &&
    +                mCurrentState != STATE_ERROR &&
    +                mCurrentState != STATE_IDLE &&
    +                mCurrentState != STATE_PREPARING);
    +    }
    +
    +    public boolean canPause() {
    +        return mCanPause;
    +    }
    +
    +    public boolean canSeekBackward() {
    +        return mCanSeekBack;
    +    }
    +
    +    public boolean canSeekForward() {
    +        return mCanSeekForward;
    +    }
    +}
    \ No newline at end of file
    
    From 624be66b3722e80ca9451f8a0a4f31bb0f152906 Mon Sep 17 00:00:00 2001
    From: mareksebera 
    Date: Sat, 12 Oct 2013 21:27:41 +0200
    Subject: [PATCH 084/613] Closes #266
    
    ---
     library/src/com/loopj/android/http/AsyncHttpClient.java | 8 ++++++++
     1 file changed, 8 insertions(+)
    
    diff --git a/library/src/com/loopj/android/http/AsyncHttpClient.java b/library/src/com/loopj/android/http/AsyncHttpClient.java
    index 6025b9e53..ff1e74d12 100644
    --- a/library/src/com/loopj/android/http/AsyncHttpClient.java
    +++ b/library/src/com/loopj/android/http/AsyncHttpClient.java
    @@ -278,6 +278,14 @@ public void setSSLSocketFactory(SSLSocketFactory sslSocketFactory) {
             this.httpClient.getConnectionManager().getSchemeRegistry().register(new Scheme("https", sslSocketFactory, 443));
         }
     
    +    /**
    +     * Sets the maximum number of retries for a particular Request.
    +     * @param retries maximum number of retries per request
    +     */
    +    public void setMaxRetries(int retries) {
    +        this.httpClient.setHttpRequestRetryHandler(new RetryHandler(retries));
    +    }
    +
         /**
          * Sets headers that will be added to all requests this client makes (before sending).
          *
    
    From cebd029a2f66107337f12be6d22b06b3bd616296 Mon Sep 17 00:00:00 2001
    From: mareksebera 
    Date: Sat, 12 Oct 2013 22:01:41 +0200
    Subject: [PATCH 085/613] #88 uncatched exception
    
    ---
     library/src/com/loopj/android/http/AsyncHttpRequest.java | 6 ++++++
     1 file changed, 6 insertions(+)
    
    diff --git a/library/src/com/loopj/android/http/AsyncHttpRequest.java b/library/src/com/loopj/android/http/AsyncHttpRequest.java
    index f7d9c6fb2..fa28d2544 100644
    --- a/library/src/com/loopj/android/http/AsyncHttpRequest.java
    +++ b/library/src/com/loopj/android/http/AsyncHttpRequest.java
    @@ -19,6 +19,7 @@
     package com.loopj.android.http;
     
     import org.apache.http.HttpResponse;
    +import org.apache.http.client.ClientProtocolException;
     import org.apache.http.client.HttpRequestRetryHandler;
     import org.apache.http.client.methods.HttpUriRequest;
     import org.apache.http.conn.ConnectTimeoutException;
    @@ -102,6 +103,11 @@ private void makeRequestWithRetries() throws ConnectException {
                 try {
                     makeRequest();
                     return;
    +            } catch (ClientProtocolException e) {
    +                if(responseHandler != null) {
    +                    responseHandler.sendFailureMessage(e, "cannot repeat the request");
    +                }
    +                return;
                 } catch (UnknownHostException e) {
                     if (responseHandler != null) {
                         responseHandler.sendFailureMessage(e, "can't resolve host");
    
    From 301cc9211ef5a5a9d61780f5ada82f7eaf62c5a3 Mon Sep 17 00:00:00 2001
    From: mareksebera 
    Date: Sun, 13 Oct 2013 12:01:57 +0200
    Subject: [PATCH 086/613] Fixes #143
    
    ---
     .../loopj/android/http/AsyncHttpClient.java   | 20 ++++-
     .../android/http/MySSLSocketFactory.java      | 83 +++++++++++++++++++
     2 files changed, 102 insertions(+), 1 deletion(-)
     create mode 100644 library/src/com/loopj/android/http/MySSLSocketFactory.java
    
    diff --git a/library/src/com/loopj/android/http/AsyncHttpClient.java b/library/src/com/loopj/android/http/AsyncHttpClient.java
    index ff1e74d12..a18ddfa05 100644
    --- a/library/src/com/loopj/android/http/AsyncHttpClient.java
    +++ b/library/src/com/loopj/android/http/AsyncHttpClient.java
    @@ -119,6 +119,15 @@ public class AsyncHttpClient {
          * Creates a new AsyncHttpClient.
          */
         public AsyncHttpClient() {
    +        this(false);
    +    }
    +
    +    /**
    +     * Creates a new AsyncHttpClient.
    +     *
    +     * @param fixNoHttpResponseException See issue https://github.com/loopj/android-async-http/issues/143
    +     */
    +    public AsyncHttpClient(boolean fixNoHttpResponseException) {
             BasicHttpParams httpParams = new BasicHttpParams();
     
             ConnManagerParams.setTimeout(httpParams, socketTimeout);
    @@ -133,9 +142,18 @@ public AsyncHttpClient() {
             HttpProtocolParams.setVersion(httpParams, HttpVersion.HTTP_1_1);
             HttpProtocolParams.setUserAgent(httpParams, String.format("android-async-http/%s (http://loopj.com/android-async-http)", VERSION));
     
    +        // Fix to SSL flaw in API < ICS
    +        // See https://code.google.com/p/android/issues/detail?id=13117
    +        SSLSocketFactory sslSocketFactory;
    +        if(fixNoHttpResponseException)
    +            sslSocketFactory = MySSLSocketFactory.getFixedSocketFactory();
    +        else
    +            sslSocketFactory = SSLSocketFactory.getSocketFactory();
    +
             SchemeRegistry schemeRegistry = new SchemeRegistry();
             schemeRegistry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
    -        schemeRegistry.register(new Scheme("https", SSLSocketFactory.getSocketFactory(), 443));
    +        schemeRegistry.register(new Scheme("https", sslSocketFactory, 443));
    +
             ThreadSafeClientConnManager cm = new ThreadSafeClientConnManager(httpParams, schemeRegistry);
     
             threadPool = (ThreadPoolExecutor) Executors.newCachedThreadPool();
    diff --git a/library/src/com/loopj/android/http/MySSLSocketFactory.java b/library/src/com/loopj/android/http/MySSLSocketFactory.java
    new file mode 100644
    index 000000000..99739fb96
    --- /dev/null
    +++ b/library/src/com/loopj/android/http/MySSLSocketFactory.java
    @@ -0,0 +1,83 @@
    +package com.loopj.android.http;
    +
    +import org.apache.http.conn.ssl.SSLSocketFactory;
    +
    +import java.io.IOException;
    +import java.net.Socket;
    +import java.net.UnknownHostException;
    +import java.security.KeyManagementException;
    +import java.security.KeyStore;
    +import java.security.KeyStoreException;
    +import java.security.NoSuchAlgorithmException;
    +import java.security.UnrecoverableKeyException;
    +
    +import javax.net.ssl.SSLContext;
    +import javax.net.ssl.TrustManager;
    +import javax.net.ssl.X509TrustManager;
    +
    +/**
    + * This file is introduced to fix HTTPS Post bug on API < ICS
    + * see http://code.google.com/p/android/issues/detail?id=13117#c14
    + */
    +public class MySSLSocketFactory extends SSLSocketFactory {
    +    SSLContext sslContext = SSLContext.getInstance("TLS");
    +
    +    public MySSLSocketFactory(KeyStore truststore)
    +            throws NoSuchAlgorithmException, KeyManagementException,
    +            KeyStoreException, UnrecoverableKeyException {
    +        super(truststore);
    +
    +        TrustManager tm = new X509TrustManager() {
    +            public java.security.cert.X509Certificate[] getAcceptedIssuers() {
    +                return null;
    +            }
    +
    +            @Override
    +            public void checkClientTrusted(
    +                    java.security.cert.X509Certificate[] chain, String authType)
    +                    throws java.security.cert.CertificateException {
    +            }
    +
    +            @Override
    +            public void checkServerTrusted(
    +                    java.security.cert.X509Certificate[] chain, String authType)
    +                    throws java.security.cert.CertificateException {
    +            }
    +        };
    +        sslContext.init(null, new TrustManager[]{tm}, null);
    +    }
    +
    +    @Override
    +    public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException, UnknownHostException {
    +        return sslContext.getSocketFactory().createSocket(socket, host, port, autoClose);
    +    }
    +
    +    @Override
    +    public Socket createSocket() throws IOException {
    +        return sslContext.getSocketFactory().createSocket();
    +    }
    +
    +    public static KeyStore getKeystore() {
    +        KeyStore trustStore = null;
    +        try {
    +            trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
    +            trustStore.load(null, null);
    +        } catch (Throwable t) {
    +            t.printStackTrace();
    +        }
    +        return trustStore;
    +    }
    +
    +    public static SSLSocketFactory getFixedSocketFactory() {
    +        SSLSocketFactory socketFactory;
    +        try {
    +            socketFactory = new MySSLSocketFactory(getKeystore());
    +            socketFactory.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
    +        } catch (Throwable t) {
    +            t.printStackTrace();
    +            socketFactory = SSLSocketFactory.getSocketFactory();
    +        }
    +        return socketFactory;
    +    }
    +
    +}
    \ No newline at end of file
    
    From 2d34e1ca3b341356463adae08334d9bb31277b24 Mon Sep 17 00:00:00 2001
    From: mareksebera 
    Date: Sun, 13 Oct 2013 12:47:54 +0200
    Subject: [PATCH 087/613] Fixes #14
    
    ---
     .../http/AsyncHttpResponseHandler.java        | 49 ++++++++++++++-----
     .../http/BinaryHttpResponseHandler.java       | 24 ++++-----
     .../http/FileAsyncHttpResponseHandler.java    | 27 +++++++---
     .../android/http/JsonHttpResponseHandler.java | 32 +++++++++---
     .../android/http/MySSLSocketFactory.java      |  2 +-
     5 files changed, 96 insertions(+), 38 deletions(-)
    
    diff --git a/library/src/com/loopj/android/http/AsyncHttpResponseHandler.java b/library/src/com/loopj/android/http/AsyncHttpResponseHandler.java
    index df297bb83..515831308 100644
    --- a/library/src/com/loopj/android/http/AsyncHttpResponseHandler.java
    +++ b/library/src/com/loopj/android/http/AsyncHttpResponseHandler.java
    @@ -78,9 +78,9 @@ public class AsyncHttpResponseHandler {
     
         /**
          * Sets the charset for the response string. If not set, the default is UTF-8.
    -     * @see Charset
          *
          * @param charset to be used for the response string.
    +     * @see Charset
          */
         public void setCharset(final String charset) {
             this.responseCharset = charset;
    @@ -168,6 +168,31 @@ public void onFailure(Throwable error, String content) {
             onFailure(error);
         }
     
    +    /**
    +     * Fired when a request fails to complete, override to handle in your own code
    +     *
    +     * @param statusCode return HTTP status code
    +     * @param error   the underlying cause of the failure
    +     * @param content the response body, if any
    +     */
    +    public void onFailure(int statusCode, Throwable error, String content) {
    +        // By default, call the chain method onFailure(Throwable,String)
    +        onFailure(error, content);
    +    }
    +
    +    /**
    +     * Fired when a request fails to complete, override to handle in your own code
    +     *
    +     * @param statusCode return HTTP status code
    +     * @param headers return headers, if any
    +     * @param error   the underlying cause of the failure
    +     * @param content the response body, if any
    +     */
    +    public void onFailure(int statusCode, Header[] headers, Throwable error, String content) {
    +        // By default, call the chain method onFailure(int,Throwable,String)
    +        onFailure(statusCode, error, content);
    +    }
    +
     
         //
         // Pre-processing of messages (executes in background threadpool thread)
    @@ -177,12 +202,12 @@ protected void sendSuccessMessage(int statusCode, Header[] headers, String respo
             sendMessage(obtainMessage(SUCCESS_MESSAGE, new Object[]{statusCode, headers, responseBody}));
         }
     
    -    protected void sendFailureMessage(Throwable e, String responseBody) {
    -        sendMessage(obtainMessage(FAILURE_MESSAGE, new Object[]{e, responseBody}));
    +    protected void sendFailureMessage(int statusCode, Header[] headers, Throwable e, String responseBody) {
    +        sendMessage(obtainMessage(FAILURE_MESSAGE, new Object[]{statusCode, headers, e, responseBody}));
         }
     
    -    protected void sendFailureMessage(Throwable e, byte[] responseBody) {
    -        sendMessage(obtainMessage(FAILURE_MESSAGE, new Object[]{e, responseBody}));
    +    protected void sendFailureMessage(int statusCode, Header[] headers, Throwable e, byte[] responseBody) {
    +        sendMessage(obtainMessage(FAILURE_MESSAGE, new Object[]{statusCode, headers, e, responseBody}));
         }
     
         protected void sendStartMessage() {
    @@ -202,8 +227,8 @@ protected void handleSuccessMessage(int statusCode, Header[] headers, String res
             onSuccess(statusCode, headers, responseBody);
         }
     
    -    protected void handleFailureMessage(Throwable e, String responseBody) {
    -        onFailure(e, responseBody);
    +    protected void handleFailureMessage(int statusCode, Header[] headers, Throwable e, String responseBody) {
    +        onFailure(statusCode, headers, e, responseBody);
         }
     
     
    @@ -218,7 +243,7 @@ protected void handleMessage(Message msg) {
                     break;
                 case FAILURE_MESSAGE:
                     response = (Object[]) msg.obj;
    -                handleFailureMessage((Throwable) response[0], (String) response[1]);
    +                handleFailureMessage((Integer) response[0], (Header[]) response[1], (Throwable) response[2], (String) response[3]);
                     break;
                 case START_MESSAGE:
                     onStart();
    @@ -238,7 +263,7 @@ protected void sendMessage(Message msg) {
         }
     
         protected Message obtainMessage(int responseMessage, Object response) {
    -        Message msg = null;
    +        Message msg;
             if (handler != null) {
                 msg = this.handler.obtainMessage(responseMessage, response);
             } else {
    @@ -256,19 +281,19 @@ protected void sendResponseMessage(HttpResponse response) {
             StatusLine status = response.getStatusLine();
             String responseBody = null;
             try {
    -            HttpEntity entity = null;
    +            HttpEntity entity;
                 HttpEntity temp = response.getEntity();
                 if (temp != null) {
                     entity = new BufferedHttpEntity(temp);
                     responseBody = EntityUtils.toString(entity, responseCharset);
                 }
             } catch (IOException e) {
    -            sendFailureMessage(e, (String) null);
    +            sendFailureMessage(status.getStatusCode(), response.getAllHeaders(), e, (String) null);
                 return;
             }
     
             if (status.getStatusCode() >= 300) {
    -            sendFailureMessage(new HttpResponseException(status.getStatusCode(), status.getReasonPhrase()), responseBody);
    +            sendFailureMessage(status.getStatusCode(), response.getAllHeaders(), new HttpResponseException(status.getStatusCode(), status.getReasonPhrase()), responseBody);
             } else {
                 sendSuccessMessage(status.getStatusCode(), response.getAllHeaders(), responseBody);
             }
    diff --git a/library/src/com/loopj/android/http/BinaryHttpResponseHandler.java b/library/src/com/loopj/android/http/BinaryHttpResponseHandler.java
    index 814dba6b0..08255419e 100644
    --- a/library/src/com/loopj/android/http/BinaryHttpResponseHandler.java
    +++ b/library/src/com/loopj/android/http/BinaryHttpResponseHandler.java
    @@ -106,14 +106,16 @@ public void onSuccess(int statusCode, byte[] binaryData) {
         /**
          * Fired when a request fails to complete, override to handle in your own code
          *
    +     * @param statusCode response HTTP statuse code
    +     * @param headers    response headers, if any
          * @param error      the underlying cause of the failure
          * @param binaryData the response body, if any
          * @deprecated
          */
         @Deprecated
    -    public void onFailure(Throwable error, byte[] binaryData) {
    +    public void onFailure(int statusCode, Header[] headers, Throwable error, byte[] binaryData) {
             // By default, call the deprecated onFailure(Throwable) for compatibility
    -        onFailure(error);
    +        onFailure(statusCode, error, null);
         }
     
     
    @@ -126,8 +128,8 @@ protected void sendSuccessMessage(int statusCode, byte[] responseBody) {
         }
     
         @Override
    -    protected void sendFailureMessage(Throwable e, byte[] responseBody) {
    -        sendMessage(obtainMessage(FAILURE_MESSAGE, new Object[]{e, responseBody}));
    +    protected void sendFailureMessage(int statusCode, Header[] headers, Throwable e, byte[] responseBody) {
    +        sendMessage(obtainMessage(FAILURE_MESSAGE, new Object[]{statusCode, headers, e, responseBody}));
         }
     
         //
    @@ -138,8 +140,8 @@ protected void handleSuccessMessage(int statusCode, byte[] responseBody) {
             onSuccess(statusCode, responseBody);
         }
     
    -    protected void handleFailureMessage(Throwable e, byte[] responseBody) {
    -        onFailure(e, responseBody);
    +    protected void handleFailureMessage(int statusCode, Header[] headers, Throwable e, byte[] responseBody) {
    +        onFailure(statusCode, headers, e, responseBody);
         }
     
         // Methods which emulate android's Handler and Message methods
    @@ -153,7 +155,7 @@ protected void handleMessage(Message msg) {
                     break;
                 case FAILURE_MESSAGE:
                     response = (Object[]) msg.obj;
    -                handleFailureMessage((Throwable) response[0], (byte[]) response[1]);
    +                handleFailureMessage((Integer) response[0], (Header[]) response[1], (Throwable) response[2], (byte[]) response[3]);
                     break;
                 default:
                     super.handleMessage(msg);
    @@ -169,7 +171,7 @@ protected void sendResponseMessage(HttpResponse response) {
             byte[] responseBody = null;
             if (contentTypeHeaders.length != 1) {
                 //malformed/ambiguous HTTP Header, ABORT!
    -            sendFailureMessage(new HttpResponseException(status.getStatusCode(), "None, or more than one, Content-Type Header found!"), (String) null);
    +            sendFailureMessage(status.getStatusCode(), response.getAllHeaders(), new HttpResponseException(status.getStatusCode(), "None, or more than one, Content-Type Header found!"), (String) null);
                 return;
             }
             Header contentTypeHeader = contentTypeHeaders[0];
    @@ -181,7 +183,7 @@ protected void sendResponseMessage(HttpResponse response) {
             }
             if (!foundAllowedContentType) {
                 //Content-Type not in allowed list, ABORT!
    -            sendFailureMessage(new HttpResponseException(status.getStatusCode(), "Content-Type not allowed!"), (String) null);
    +            sendFailureMessage(status.getStatusCode(), response.getAllHeaders(), new HttpResponseException(status.getStatusCode(), "Content-Type not allowed!"), (String) null);
                 return;
             }
             try {
    @@ -192,11 +194,11 @@ protected void sendResponseMessage(HttpResponse response) {
                 }
                 responseBody = EntityUtils.toByteArray(entity);
             } catch (IOException e) {
    -            sendFailureMessage(e, (byte[]) null);
    +            sendFailureMessage(status.getStatusCode(), response.getAllHeaders(), e, (byte[]) null);
             }
     
             if (status.getStatusCode() >= 300) {
    -            sendFailureMessage(new HttpResponseException(status.getStatusCode(), status.getReasonPhrase()), responseBody);
    +            sendFailureMessage(status.getStatusCode(), response.getAllHeaders(), new HttpResponseException(status.getStatusCode(), status.getReasonPhrase()), responseBody);
             } else {
                 sendSuccessMessage(status.getStatusCode(), responseBody);
             }
    diff --git a/library/src/com/loopj/android/http/FileAsyncHttpResponseHandler.java b/library/src/com/loopj/android/http/FileAsyncHttpResponseHandler.java
    index b3d477cae..7bb674fca 100644
    --- a/library/src/com/loopj/android/http/FileAsyncHttpResponseHandler.java
    +++ b/library/src/com/loopj/android/http/FileAsyncHttpResponseHandler.java
    @@ -2,6 +2,7 @@
     
     import android.os.Message;
     
    +import org.apache.http.Header;
     import org.apache.http.HttpResponse;
     import org.apache.http.StatusLine;
     import org.apache.http.client.HttpResponseException;
    @@ -29,6 +30,18 @@ public void onSuccess(int statusCode, File file) {
         }
     
         public void onFailure(Throwable e, File response) {
    +        // By default call lower chain method
    +        onFailure(e);
    +    }
    +
    +    public void onFailure(int statusCode, Throwable e, File response) {
    +        // By default call lower chain method
    +        onFailure(e, response);
    +    }
    +
    +    public void onFailure(int statusCode, Header[] headers, Throwable e, File response) {
    +        // By default call lower chain method
    +        onFailure(statusCode, e, response);
         }
     
     
    @@ -36,16 +49,16 @@ protected void sendSuccessMessage(int statusCode, File file) {
             sendMessage(obtainMessage(SUCCESS_MESSAGE, new Object[]{statusCode, file}));
         }
     
    -    protected void sendFailureMessage(Throwable e, File file) {
    -        sendMessage(obtainMessage(FAILURE_MESSAGE, new Object[]{e, file}));
    +    protected void sendFailureMessage(int statusCode, Header[] headers, Throwable e, File file) {
    +        sendMessage(obtainMessage(FAILURE_MESSAGE, new Object[]{statusCode, headers, e, file}));
         }
     
         protected void handleSuccessMessage(int statusCode, File responseBody) {
             onSuccess(statusCode, responseBody);
         }
     
    -    protected void handleFailureMessage(Throwable e, File responseBody) {
    -        onFailure(e, responseBody);
    +    protected void handleFailureMessage(int statusCode, Header[] headers, Throwable e, File responseBody) {
    +        onFailure(statusCode, headers, e, responseBody);
         }
     
         // Methods which emulate android's Handler and Message methods
    @@ -58,7 +71,7 @@ protected void handleMessage(Message msg) {
                     break;
                 case FAILURE_MESSAGE:
                     response = (Object[]) msg.obj;
    -                handleFailureMessage((Throwable) response[0], (File) response[1]);
    +                handleFailureMessage((Integer) response[0], (Header[]) response[1], (Throwable) response[2], (File) response[3]);
                     break;
                 default:
                     super.handleMessage(msg);
    @@ -84,11 +97,11 @@ protected void sendResponseMessage(HttpResponse response) {
                 buffer.close();
     
             } catch (IOException e) {
    -            sendFailureMessage(e, this.mFile);
    +            sendFailureMessage(status.getStatusCode(), response.getAllHeaders(), e, this.mFile);
             }
     
             if (status.getStatusCode() >= 300) {
    -            sendFailureMessage(new HttpResponseException(status.getStatusCode(), status.getReasonPhrase()), this.mFile);
    +            sendFailureMessage(status.getStatusCode(), response.getAllHeaders(), new HttpResponseException(status.getStatusCode(), status.getReasonPhrase()), this.mFile);
             } else {
                 sendSuccessMessage(status.getStatusCode(), this.mFile);
             }
    diff --git a/library/src/com/loopj/android/http/JsonHttpResponseHandler.java b/library/src/com/loopj/android/http/JsonHttpResponseHandler.java
    index 8c52bf757..7929958a3 100644
    --- a/library/src/com/loopj/android/http/JsonHttpResponseHandler.java
    +++ b/library/src/com/loopj/android/http/JsonHttpResponseHandler.java
    @@ -118,9 +118,27 @@ public void onSuccess(int statusCode, JSONArray response) {
         }
     
         public void onFailure(Throwable e, JSONObject errorResponse) {
    +        onFailure(e);
    +    }
    +
    +    public void onFailure(int statusCode, Throwable e, JSONObject errorResponse) {
    +        onFailure(e, errorResponse);
    +    }
    +
    +    public void onFailure(int statusCode, Header[] headers, Throwable e, JSONObject errorResponse) {
    +        onFailure(statusCode, e, errorResponse);
         }
     
         public void onFailure(Throwable e, JSONArray errorResponse) {
    +        onFailure(e);
    +    }
    +
    +    public void onFailure(int statusCode, Throwable e, JSONArray errorResponse) {
    +        onFailure(e, errorResponse);
    +    }
    +
    +    public void onFailure(int statusCode, Header[] headers, Throwable e, JSONArray errorResponse) {
    +        onFailure(statusCode, e, errorResponse);
         }
     
     
    @@ -138,7 +156,7 @@ public void run() {
                             Object jsonResponse = parseResponse(responseBody);
                             sendMessage(obtainMessage(SUCCESS_JSON_MESSAGE, new Object[]{statusCode, headers, jsonResponse}));
                         } catch (JSONException e) {
    -                        sendFailureMessage(e, responseBody);
    +                        sendFailureMessage(statusCode, headers, e, responseBody);
                         }
                     }
                 }).start();
    @@ -188,7 +206,7 @@ protected Object parseResponse(String responseBody) throws JSONException {
         }
     
         @Override
    -    protected void handleFailureMessage(final Throwable e, final String responseBody) {
    +    protected void handleFailureMessage(final int statusCode, final Header[] headers, final Throwable e, final String responseBody) {
             new Thread(new Runnable() {
                 @Override
                 public void run() {
    @@ -196,19 +214,19 @@ public void run() {
                         if (responseBody != null) {
                             Object jsonResponse = parseResponse(responseBody);
                             if (jsonResponse instanceof JSONObject) {
    -                            onFailure(e, (JSONObject) jsonResponse);
    +                            onFailure(statusCode, headers, e, (JSONObject) jsonResponse);
                             } else if (jsonResponse instanceof JSONArray) {
    -                            onFailure(e, (JSONArray) jsonResponse);
    +                            onFailure(statusCode, headers, e, (JSONArray) jsonResponse);
                             } else if (jsonResponse instanceof String) {
    -                            onFailure(e, (String) jsonResponse);
    +                            onFailure(statusCode, headers, e, (String) jsonResponse);
                             } else {
    -                            onFailure(e, responseBody);
    +                            onFailure(statusCode, headers, e, responseBody);
                             }
                         } else {
                             onFailure(e, "");
                         }
                     } catch (JSONException ex) {
    -                    onFailure(e, responseBody);
    +                    onFailure(statusCode, headers, e, responseBody);
                     }
                 }
             }).start();
    diff --git a/library/src/com/loopj/android/http/MySSLSocketFactory.java b/library/src/com/loopj/android/http/MySSLSocketFactory.java
    index 99739fb96..fdfbffc9d 100644
    --- a/library/src/com/loopj/android/http/MySSLSocketFactory.java
    +++ b/library/src/com/loopj/android/http/MySSLSocketFactory.java
    @@ -16,7 +16,7 @@
     import javax.net.ssl.X509TrustManager;
     
     /**
    - * This file is introduced to fix HTTPS Post bug on API < ICS
    + * This file is introduced to fix HTTPS Post bug on API < ICS
      * see http://code.google.com/p/android/issues/detail?id=13117#c14
      */
     public class MySSLSocketFactory extends SSLSocketFactory {
    
    From 62903f8d6e16a8fd14ca2d51a8c364b8f83536fe Mon Sep 17 00:00:00 2001
    From: mareksebera 
    Date: Sun, 13 Oct 2013 13:02:10 +0200
    Subject: [PATCH 088/613] Compatibility changes fix
    
    ---
     .../loopj/android/http/AsyncHttpResponseHandler.java   | 10 ++++++++++
     1 file changed, 10 insertions(+)
    
    diff --git a/library/src/com/loopj/android/http/AsyncHttpResponseHandler.java b/library/src/com/loopj/android/http/AsyncHttpResponseHandler.java
    index 515831308..7fdd98903 100644
    --- a/library/src/com/loopj/android/http/AsyncHttpResponseHandler.java
    +++ b/library/src/com/loopj/android/http/AsyncHttpResponseHandler.java
    @@ -206,10 +206,20 @@ protected void sendFailureMessage(int statusCode, Header[] headers, Throwable e,
             sendMessage(obtainMessage(FAILURE_MESSAGE, new Object[]{statusCode, headers, e, responseBody}));
         }
     
    +    @Deprecated
    +    protected void sendFailureMessage(Throwable e, String responseBody) {
    +        sendMessage(obtainMessage(FAILURE_MESSAGE, new Object[]{0, null, e, responseBody}));
    +    }
    +
         protected void sendFailureMessage(int statusCode, Header[] headers, Throwable e, byte[] responseBody) {
             sendMessage(obtainMessage(FAILURE_MESSAGE, new Object[]{statusCode, headers, e, responseBody}));
         }
     
    +    @Deprecated
    +    protected void sendFailureMessage( Throwable e, byte[] responseBody) {
    +        sendMessage(obtainMessage(FAILURE_MESSAGE, new Object[]{0, null, e, responseBody}));
    +    }
    +
         protected void sendStartMessage() {
             sendMessage(obtainMessage(START_MESSAGE, null));
         }
    
    From 68840e7fc8d090251504eded317ef1deee5376d6 Mon Sep 17 00:00:00 2001
    From: mareksebera 
    Date: Sun, 13 Oct 2013 13:26:05 +0200
    Subject: [PATCH 089/613] Fixes #61, allow removing of Basic Auth credentials
    
    ---
     library/src/com/loopj/android/http/AsyncHttpClient.java | 7 +++++++
     1 file changed, 7 insertions(+)
    
    diff --git a/library/src/com/loopj/android/http/AsyncHttpClient.java b/library/src/com/loopj/android/http/AsyncHttpClient.java
    index a18ddfa05..471e37bbf 100644
    --- a/library/src/com/loopj/android/http/AsyncHttpClient.java
    +++ b/library/src/com/loopj/android/http/AsyncHttpClient.java
    @@ -348,6 +348,13 @@ public void setBasicAuth(String username, String password, AuthScope scope) {
             this.httpClient.getCredentialsProvider().setCredentials(scope, credentials);
         }
     
    +    /**
    +     * Removes set basic auth credentials
    +     * */
    +    public void clearBasicAuth(){
    +        this.httpClient.getCredentialsProvider().clear();
    +    }
    +
         /**
          * Cancels any pending (or potentially active) requests associated with the
          * passed Context.
    
    From 44e8213bd862e34f8f3b65fb83eed80acc799019 Mon Sep 17 00:00:00 2001
    From: mareksebera 
    Date: Sun, 13 Oct 2013 13:30:24 +0200
    Subject: [PATCH 090/613] Related to #67, avoiding NPE in
     JsonHttpResponseHandler
    
    ---
     library/src/com/loopj/android/http/JsonHttpResponseHandler.java | 2 ++
     1 file changed, 2 insertions(+)
    
    diff --git a/library/src/com/loopj/android/http/JsonHttpResponseHandler.java b/library/src/com/loopj/android/http/JsonHttpResponseHandler.java
    index 7929958a3..1e9fa7a06 100644
    --- a/library/src/com/loopj/android/http/JsonHttpResponseHandler.java
    +++ b/library/src/com/loopj/android/http/JsonHttpResponseHandler.java
    @@ -193,6 +193,8 @@ protected void handleSuccessJsonMessage(int statusCode, Header[] headers, Object
         }
     
         protected Object parseResponse(String responseBody) throws JSONException {
    +        if(null == responseBody)
    +            return null;
             Object result = null;
             //trim the string to prevent start with blank, and test if the string is valid JSON, because the parser don't do this :(. If Json is not valid this will return null
             responseBody = responseBody.trim();
    
    From 51dfd5a3b078b438274a4a28c6ab3e2c09e4fb59 Mon Sep 17 00:00:00 2001
    From: mareksebera 
    Date: Sun, 13 Oct 2013 14:08:58 +0200
    Subject: [PATCH 091/613] Added warning about SSL bypass fix
    
    ---
     .../loopj/android/http/AsyncHttpClient.java   | 19 ++++++++++++-------
     1 file changed, 12 insertions(+), 7 deletions(-)
    
    diff --git a/library/src/com/loopj/android/http/AsyncHttpClient.java b/library/src/com/loopj/android/http/AsyncHttpClient.java
    index 471e37bbf..3fef99ddf 100644
    --- a/library/src/com/loopj/android/http/AsyncHttpClient.java
    +++ b/library/src/com/loopj/android/http/AsyncHttpClient.java
    @@ -19,6 +19,7 @@
     package com.loopj.android.http;
     
     import android.content.Context;
    +import android.util.Log;
     
     import org.apache.http.Header;
     import org.apache.http.HeaderElement;
    @@ -104,6 +105,7 @@ public class AsyncHttpClient {
         private static final int DEFAULT_SOCKET_BUFFER_SIZE = 8192;
         private static final String HEADER_ACCEPT_ENCODING = "Accept-Encoding";
         private static final String ENCODING_GZIP = "gzip";
    +    private static final String LOG_TAG = "AsyncHttpClient";
     
         private static int maxConnections = DEFAULT_MAX_CONNECTIONS;
         private static int socketTimeout = DEFAULT_SOCKET_TIMEOUT;
    @@ -128,6 +130,8 @@ public AsyncHttpClient() {
          * @param fixNoHttpResponseException See issue https://github.com/loopj/android-async-http/issues/143
          */
         public AsyncHttpClient(boolean fixNoHttpResponseException) {
    +        if(fixNoHttpResponseException)
    +            Log.d(LOG_TAG, "Beware! Using the fix is insecure, as it doesn't verify SSL certificates.");
             BasicHttpParams httpParams = new BasicHttpParams();
     
             ConnManagerParams.setTimeout(httpParams, socketTimeout);
    @@ -145,7 +149,7 @@ public AsyncHttpClient(boolean fixNoHttpResponseException) {
             // Fix to SSL flaw in API < ICS
             // See https://code.google.com/p/android/issues/detail?id=13117
             SSLSocketFactory sslSocketFactory;
    -        if(fixNoHttpResponseException)
    +        if (fixNoHttpResponseException)
                 sslSocketFactory = MySSLSocketFactory.getFixedSocketFactory();
             else
                 sslSocketFactory = SSLSocketFactory.getSocketFactory();
    @@ -277,10 +281,10 @@ public void setTimeout(int timeout) {
         /**
          * Sets the Proxy by it's hostname and port
          *
    -     * @param hostname  the hostname (IP or DNS name)
    -     * @param port  the port number. -1 indicates the scheme default port.
    +     * @param hostname the hostname (IP or DNS name)
    +     * @param port     the port number. -1 indicates the scheme default port.
          */
    -    public void setProxy(String hostname, int port){
    +    public void setProxy(String hostname, int port) {
             final HttpHost proxy = new HttpHost(hostname, port);
             final HttpParams httpParams = this.httpClient.getParams();
             httpParams.setParameter(ConnRoutePNames.DEFAULT_PROXY, proxy);
    @@ -298,6 +302,7 @@ public void setSSLSocketFactory(SSLSocketFactory sslSocketFactory) {
     
         /**
          * Sets the maximum number of retries for a particular Request.
    +     *
          * @param retries maximum number of retries per request
          */
         public void setMaxRetries(int retries) {
    @@ -350,8 +355,8 @@ public void setBasicAuth(String username, String password, AuthScope scope) {
     
         /**
          * Removes set basic auth credentials
    -     * */
    -    public void clearBasicAuth(){
    +     */
    +    public void clearBasicAuth() {
             this.httpClient.getCredentialsProvider().clear();
         }
     
    @@ -727,7 +732,7 @@ public void delete(Context context, String url, Header[] headers, AsyncHttpRespo
          */
         public void delete(Context context, String url, Header[] headers, RequestParams params, AsyncHttpResponseHandler responseHandler) {
             HttpDelete httpDelete = new HttpDelete(getUrlWithQueryString(url, params));
    -        if(headers != null) httpDelete.setHeaders(headers);
    +        if (headers != null) httpDelete.setHeaders(headers);
             sendRequest(httpClient, httpContext, httpDelete, null, responseHandler, context);
         }
     
    
    From 31e5d0295c13d2ac0c465acac676d2c3f6928448 Mon Sep 17 00:00:00 2001
    From: mareksebera 
    Date: Sun, 13 Oct 2013 14:51:05 +0200
    Subject: [PATCH 092/613] Fixing #79, Allowing non-standard HTTP and HTTPS
     ports
    
    ---
     .../loopj/android/http/AsyncHttpClient.java   | 33 +++++++++++++++----
     1 file changed, 27 insertions(+), 6 deletions(-)
    
    diff --git a/library/src/com/loopj/android/http/AsyncHttpClient.java b/library/src/com/loopj/android/http/AsyncHttpClient.java
    index 3fef99ddf..efe76848d 100644
    --- a/library/src/com/loopj/android/http/AsyncHttpClient.java
    +++ b/library/src/com/loopj/android/http/AsyncHttpClient.java
    @@ -118,19 +118,40 @@ public class AsyncHttpClient {
     
     
         /**
    -     * Creates a new AsyncHttpClient.
    +     * Creates a new AsyncHttpClient with default constructor arguments values
          */
         public AsyncHttpClient() {
    -        this(false);
    +        this(false, 80, 443);
    +    }
    +
    +    /**
    +     * Creates a new AsyncHttpClient.
    +     *
    +     * @param httpPort non-standard HTTP-only port
    +     */
    +    public AsyncHttpClient(int httpPort) {
    +        this(false, httpPort, 443);
    +    }
    +
    +    /**
    +     * Creates a new AsyncHttpClient.
    +     *
    +     * @param httpPort  non-standard HTTP-only port
    +     * @param httpsPort non-standard HTTPS-only port
    +     */
    +    public AsyncHttpClient(int httpPort, int httpsPort) {
    +        this(false, httpPort, httpsPort);
         }
     
         /**
          * Creates a new AsyncHttpClient.
          *
          * @param fixNoHttpResponseException See issue https://github.com/loopj/android-async-http/issues/143
    +     * @param httpPort  non-standard HTTP-only port
    +     * @param httpsPort non-standard HTTPS-only port
          */
    -    public AsyncHttpClient(boolean fixNoHttpResponseException) {
    -        if(fixNoHttpResponseException)
    +    public AsyncHttpClient(boolean fixNoHttpResponseException, int httpPort, int httpsPort) {
    +        if (fixNoHttpResponseException)
                 Log.d(LOG_TAG, "Beware! Using the fix is insecure, as it doesn't verify SSL certificates.");
             BasicHttpParams httpParams = new BasicHttpParams();
     
    @@ -155,8 +176,8 @@ public AsyncHttpClient(boolean fixNoHttpResponseException) {
                 sslSocketFactory = SSLSocketFactory.getSocketFactory();
     
             SchemeRegistry schemeRegistry = new SchemeRegistry();
    -        schemeRegistry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
    -        schemeRegistry.register(new Scheme("https", sslSocketFactory, 443));
    +        schemeRegistry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), httpPort));
    +        schemeRegistry.register(new Scheme("https", sslSocketFactory, httpsPort));
     
             ThreadSafeClientConnManager cm = new ThreadSafeClientConnManager(httpParams, schemeRegistry);
     
    
    From 7d6d637c9b58a70c596ea26cb83e651c7af496df Mon Sep 17 00:00:00 2001
    From: mareksebera 
    Date: Sun, 13 Oct 2013 15:05:18 +0200
    Subject: [PATCH 093/613] Fixed 2d34e1ca3b341356463adae08334d9bb31277b24
     leftover, Closes #86
    
    ---
     .../src/com/loopj/android/http/JsonHttpResponseHandler.java | 6 ++++--
     1 file changed, 4 insertions(+), 2 deletions(-)
    
    diff --git a/library/src/com/loopj/android/http/JsonHttpResponseHandler.java b/library/src/com/loopj/android/http/JsonHttpResponseHandler.java
    index 1e9fa7a06..33855fc96 100644
    --- a/library/src/com/loopj/android/http/JsonHttpResponseHandler.java
    +++ b/library/src/com/loopj/android/http/JsonHttpResponseHandler.java
    @@ -161,7 +161,7 @@ public void run() {
                     }
                 }).start();
             } else {
    -            sendMessage(obtainMessage(SUCCESS_JSON_MESSAGE, new Object[]{statusCode, new JSONObject()}));
    +            sendMessage(obtainMessage(SUCCESS_JSON_MESSAGE, new Object[]{statusCode, headers, new JSONObject()}));
             }
         }
     
    @@ -187,13 +187,15 @@ protected void handleSuccessJsonMessage(int statusCode, Header[] headers, Object
                 onSuccess(statusCode, headers, (JSONObject) jsonResponse);
             } else if (jsonResponse instanceof JSONArray) {
                 onSuccess(statusCode, headers, (JSONArray) jsonResponse);
    +        } else if (jsonResponse instanceof String) {
    +            onSuccess(statusCode, headers, (String) jsonResponse);
             } else {
                 onFailure(new JSONException("Unexpected type " + jsonResponse.getClass().getName()), (JSONObject) null);
             }
         }
     
         protected Object parseResponse(String responseBody) throws JSONException {
    -        if(null == responseBody)
    +        if (null == responseBody)
                 return null;
             Object result = null;
             //trim the string to prevent start with blank, and test if the string is valid JSON, because the parser don't do this :(. If Json is not valid this will return null
    
    From 9096a76b0072def5056d7ea170eb202f3a67f87c Mon Sep 17 00:00:00 2001
    From: mareksebera 
    Date: Sun, 13 Oct 2013 15:16:16 +0200
    Subject: [PATCH 094/613] Javadoc formatting
    
    ---
     library/src/com/loopj/android/http/AsyncHttpClient.java | 4 ++--
     1 file changed, 2 insertions(+), 2 deletions(-)
    
    diff --git a/library/src/com/loopj/android/http/AsyncHttpClient.java b/library/src/com/loopj/android/http/AsyncHttpClient.java
    index efe76848d..bbcd26a97 100644
    --- a/library/src/com/loopj/android/http/AsyncHttpClient.java
    +++ b/library/src/com/loopj/android/http/AsyncHttpClient.java
    @@ -147,8 +147,8 @@ public AsyncHttpClient(int httpPort, int httpsPort) {
          * Creates a new AsyncHttpClient.
          *
          * @param fixNoHttpResponseException See issue https://github.com/loopj/android-async-http/issues/143
    -     * @param httpPort  non-standard HTTP-only port
    -     * @param httpsPort non-standard HTTPS-only port
    +     * @param httpPort                   non-standard HTTP-only port
    +     * @param httpsPort                  non-standard HTTPS-only port
          */
         public AsyncHttpClient(boolean fixNoHttpResponseException, int httpPort, int httpsPort) {
             if (fixNoHttpResponseException)
    
    From 31dc58f302930ecc546bae23fd5d6ce93ad8a022 Mon Sep 17 00:00:00 2001
    From: mareksebera 
    Date: Sun, 13 Oct 2013 23:27:22 +0200
    Subject: [PATCH 095/613] Removed unnecessary dependency, #168
    
    ---
     library/build.gradle | 4 ----
     1 file changed, 4 deletions(-)
    
    diff --git a/library/build.gradle b/library/build.gradle
    index 680db873c..4126bb52e 100644
    --- a/library/build.gradle
    +++ b/library/build.gradle
    @@ -1,9 +1,5 @@
     apply plugin: 'android-library'
     
    -dependencies {
    -    compile 'com.android.support:support-v4:18.0.+'
    -}
    -
     android {
         compileSdkVersion 18
         buildToolsVersion '18.0.1'
    
    From 5a7a8354e6d507a5809b0c96c835469e75b565bb Mon Sep 17 00:00:00 2001
    From: mareksebera 
    Date: Mon, 14 Oct 2013 00:01:18 +0200
    Subject: [PATCH 096/613] Sample Application initial state
    
    ---
     sample/.gitignore                             |   1 +
     sample/AndroidManifest.xml                    |  18 ++
     sample/build.gradle                           |  30 ++++
     sample/src/main/AndroidManifest.xml           |  25 +++
     .../android/http/sample/MainActivity.java     | 160 ++++++++++++++++++
     .../main/res/drawable-hdpi/ic_launcher.png    | Bin 0 -> 9397 bytes
     .../main/res/drawable-mdpi/ic_launcher.png    | Bin 0 -> 5237 bytes
     .../main/res/drawable-xhdpi/ic_launcher.png   | Bin 0 -> 14383 bytes
     sample/src/main/res/layout/activity_main.xml  |  75 ++++++++
     sample/src/main/res/menu/main.xml             |   6 +
     sample/src/main/res/values-sw600dp/dimens.xml |   4 +
     .../main/res/values-sw720dp-land/dimens.xml   |   5 +
     sample/src/main/res/values-v11/styles.xml     |  11 ++
     sample/src/main/res/values-v14/styles.xml     |  12 ++
     sample/src/main/res/values/dimens.xml         |   5 +
     sample/src/main/res/values/strings.xml        |   8 +
     sample/src/main/res/values/styles.xml         |  20 +++
     settings.gradle                               |   3 +-
     18 files changed, 382 insertions(+), 1 deletion(-)
     create mode 100644 sample/.gitignore
     create mode 100644 sample/AndroidManifest.xml
     create mode 100644 sample/build.gradle
     create mode 100644 sample/src/main/AndroidManifest.xml
     create mode 100644 sample/src/main/java/com/loopj/android/http/sample/MainActivity.java
     create mode 100644 sample/src/main/res/drawable-hdpi/ic_launcher.png
     create mode 100644 sample/src/main/res/drawable-mdpi/ic_launcher.png
     create mode 100644 sample/src/main/res/drawable-xhdpi/ic_launcher.png
     create mode 100644 sample/src/main/res/layout/activity_main.xml
     create mode 100644 sample/src/main/res/menu/main.xml
     create mode 100644 sample/src/main/res/values-sw600dp/dimens.xml
     create mode 100644 sample/src/main/res/values-sw720dp-land/dimens.xml
     create mode 100644 sample/src/main/res/values-v11/styles.xml
     create mode 100644 sample/src/main/res/values-v14/styles.xml
     create mode 100644 sample/src/main/res/values/dimens.xml
     create mode 100644 sample/src/main/res/values/strings.xml
     create mode 100644 sample/src/main/res/values/styles.xml
    
    diff --git a/sample/.gitignore b/sample/.gitignore
    new file mode 100644
    index 000000000..796b96d1c
    --- /dev/null
    +++ b/sample/.gitignore
    @@ -0,0 +1 @@
    +/build
    diff --git a/sample/AndroidManifest.xml b/sample/AndroidManifest.xml
    new file mode 100644
    index 000000000..d7ef938fa
    --- /dev/null
    +++ b/sample/AndroidManifest.xml
    @@ -0,0 +1,18 @@
    +
    +
    +
    +    
    +
    +    
    +        
    +    
    +
    + 
    diff --git a/sample/build.gradle b/sample/build.gradle
    new file mode 100644
    index 000000000..f328c0e35
    --- /dev/null
    +++ b/sample/build.gradle
    @@ -0,0 +1,30 @@
    +buildscript {
    +    repositories {
    +        mavenCentral()
    +    }
    +    dependencies {
    +        classpath 'com.android.tools.build:gradle:0.5.+'
    +    }
    +}
    +apply plugin: 'android'
    +
    +repositories {
    +    mavenCentral()
    +    maven {
    +        url "https://oss.sonatype.org/content/repositories/snapshots/"
    +    }
    +}
    +
    +android {
    +    compileSdkVersion 18
    +    buildToolsVersion "18.0.1"
    +
    +    defaultConfig {
    +        minSdkVersion 3
    +        targetSdkVersion 18
    +    }
    +}
    +
    +dependencies {
    +    compile project(':library')
    +}
    diff --git a/sample/src/main/AndroidManifest.xml b/sample/src/main/AndroidManifest.xml
    new file mode 100644
    index 000000000..8ad41e765
    --- /dev/null
    +++ b/sample/src/main/AndroidManifest.xml
    @@ -0,0 +1,25 @@
    +
    +
    +
    +    
    +
    +    
    +
    +    
    +        
    +            
    +                
    +                
    +            
    +        
    +    
    +
    +
    diff --git a/sample/src/main/java/com/loopj/android/http/sample/MainActivity.java b/sample/src/main/java/com/loopj/android/http/sample/MainActivity.java
    new file mode 100644
    index 000000000..3e29e6c7f
    --- /dev/null
    +++ b/sample/src/main/java/com/loopj/android/http/sample/MainActivity.java
    @@ -0,0 +1,160 @@
    +package com.loopj.android.http.sample;
    +
    +import android.app.Activity;
    +import android.graphics.Color;
    +import android.os.Bundle;
    +import android.view.Menu;
    +import android.view.View;
    +import android.widget.Button;
    +import android.widget.EditText;
    +import android.widget.TextView;
    +import android.widget.Toast;
    +
    +import com.loopj.android.http.AsyncHttpClient;
    +import com.loopj.android.http.AsyncHttpResponseHandler;
    +
    +import org.apache.http.Header;
    +
    +import java.io.PrintWriter;
    +import java.io.StringWriter;
    +import java.net.URI;
    +
    +public class MainActivity extends Activity implements View.OnClickListener {
    +
    +    private AsyncHttpClient aclient = new AsyncHttpClient(false, 80, 443);
    +    private TextView statusCode, headers, contents, state, error;
    +    private EditText url;
    +
    +    @Override
    +    protected void onCreate(Bundle savedInstanceState) {
    +        super.onCreate(savedInstanceState);
    +        setContentView(R.layout.activity_main);
    +        Button get = (Button) findViewById(R.id.request_get);
    +        statusCode = (TextView) findViewById(R.id.return_code);
    +        headers = (TextView) findViewById(R.id.return_headers);
    +        contents = (TextView) findViewById(R.id.return_data);
    +        state = (TextView) findViewById(R.id.current_state);
    +        error = (TextView) findViewById(R.id.return_error);
    +        url = (EditText) findViewById(R.id.request_url);
    +
    +        get.setOnClickListener(this);
    +    }
    +
    +
    +    @Override
    +    public boolean onCreateOptionsMenu(Menu menu) {
    +        getMenuInflater().inflate(R.menu.main, menu);
    +        return true;
    +    }
    +
    +    @Override
    +    public void onClick(View v) {
    +        switch (v.getId()) {
    +            case R.id.request_get:
    +                if (verifyUrl()) {
    +                    startRequest();
    +                }
    +                break;
    +        }
    +    }
    +
    +    private void startRequest() {
    +        aclient.get(this, getURLString(), new AsyncHttpResponseHandler() {
    +
    +            @Override
    +            public void onSuccess(int statusCode, Header[] headers, String content) {
    +                setStatusMessage("Succeeded", Color.parseColor("#DD00FF00"));
    +                printHeaders(headers);
    +                printContents(content);
    +                printStatusCode(statusCode);
    +            }
    +
    +            @Override
    +            public void onFailure(int statusCode, Header[] headers, Throwable error, String content) {
    +                setStatusMessage("Failed", Color.parseColor("#99FF0000"));
    +                printThrowable(error);
    +                printHeaders(headers);
    +                printContents(content);
    +                printStatusCode(statusCode);
    +            }
    +
    +            @Override
    +            public void onStart() {
    +                setStatusMessage("Started", Color.parseColor("#EE00FF00"));
    +            }
    +
    +            @Override
    +            public void onFinish() {
    +                setStatusMessage("Finished", 0);
    +            }
    +        });
    +    }
    +
    +    private void printThrowable(Throwable error) {
    +        if (this.error != null) {
    +            if (error != null) {
    +                StringWriter sw = new StringWriter();
    +                error.printStackTrace(new PrintWriter(sw));
    +                this.error.setText(sw.toString());
    +            } else {
    +                this.error.setText(null);
    +            }
    +        }
    +    }
    +
    +    private void printStatusCode(int statusCode) {
    +        if (this.statusCode != null) {
    +            this.statusCode.setText(String.format("HTTP Status Code: %d", statusCode));
    +        }
    +    }
    +
    +    private void printContents(String content) {
    +        if (this.contents != null) {
    +            if (content == null)
    +                contents.setText("Return is NULL");
    +            else
    +                contents.setText(content);
    +        }
    +    }
    +
    +    private void printHeaders(Header[] headers) {
    +        if (this.headers != null) {
    +            StringBuilder sb = new StringBuilder();
    +            sb.append("Headers:");
    +            if (headers != null) {
    +                for (Header h : headers) {
    +                    sb.append("\n").append(h.getName()).append(": ").append(h.getValue());
    +                }
    +            }
    +            this.headers.setText(sb.toString());
    +        }
    +    }
    +
    +    private void setStatusMessage(String message, int color) {
    +        if (state != null) {
    +            state.setText(String.format("Status: %s", message));
    +            if (color != 0)
    +                state.setBackgroundColor(color);
    +        }
    +    }
    +
    +    private String getURLString() {
    +        return url.getText() != null ? url.getText().toString() : null;
    +    }
    +
    +    private boolean verifyUrl() {
    +        String contents = getURLString();
    +        if (contents != null) {
    +            try {
    +                URI.create(contents);
    +                return true;
    +            } catch (Throwable t) {
    +                Toast.makeText(this, "Given URL is not valid", Toast.LENGTH_SHORT).show();
    +                t.printStackTrace();
    +                return false;
    +            }
    +        }
    +        Toast.makeText(this, "You must fill in URL", Toast.LENGTH_SHORT).show();
    +        return false;
    +    }
    +}
    diff --git a/sample/src/main/res/drawable-hdpi/ic_launcher.png b/sample/src/main/res/drawable-hdpi/ic_launcher.png
    new file mode 100644
    index 0000000000000000000000000000000000000000..96a442e5b8e9394ccf50bab9988cb2316026245d
    GIT binary patch
    literal 9397
    zcmV;mBud+fP)L`9r|n3#ts(U@pVoQ)(ZPc(6i
    z8k}N`MvWQ78F(rhG(?6FnFXYo>28{yZ}%O}TvdDT_5P?j=iW=V`8=UNc_}`JbG!ST
    zs@lK(TWkH+P**sB$A`cEY%Y53cQ}1&6`x-M$Cz&{o9bLU^M-%^mY?+vedlvt$RT-^
    zu|w7}IaWaljBq#|I%Mpo!Wc2bbZF3KF9|D%wZe{YFM=hJAv$>j>nhx`=Wis#KG!cJA5x!4)f)
    zezMz1?Vn$GnZNjbFXH(pK83nn!^3=+^*kTTs5rV9Dq^XS(IKO!mKt5!dSmb3IVCxZ
    z8TTk5IE)F1V29$G7v#j9d-hy&_pdg8?kT4)zqr>?`}I%W>(?GO%*C&}?Fp|bI*~2&KZ$%^B6R&1~2kA{`CWy+>F-x=z-f{_&vyu_3yp{jtw(*syi%
    zu3t2|4{c~LJXRt2m>rMg2V_kLltCZ<`m>qcI?BPP?6hf``|e!rZEFszeYQ3f-*nAS
    zZ+h1$mFwy+7156lkB(k6)!1fUbJCxgIBK38$jj5cC$r&YXN)nr#PY=tJaLc?C_o?j+8H3Q>891JJ9&$l-r+-SG#q)*;r52%
    z@nlKflb65o%s*Jt)!pw1k{vIoQIvoJ0Y&Msiw0X!qJ)_47G*?aJ6bJFLh_4b$5&1k5wN>du*>6#i7R9T8;
    z7>EHOV=ue7mo77SJPwER4(A+s?n0JjYK)b}Om6n>ke?0JR=jTI+RFBg_iwb7k%n*2
    zR_M0DJ9x+0zxba4(B1y^JQ_Nj6dlP5PGXvSq8fF#mxrFYj3d9(V#jJwt+IqU9+8+D
    z6C6Us1OI$d8OF!3+Hm1
    zW5in
    zXV^%U35HooOpSmeqlG6e0kUMYNonKp1vr|My9}4-WO+uOxe_c-o&}%voNYHkqtle%
    z5yQ_^oozSUUNu30EQSAl!Q%(%3G1NXENSMjCL*Vx-Td2~rk(}d
    z8pT!HZe>1r5EGuz`pgsg@^yQEi=BIa#meLq0!?{TZ}q#}=7UC9_l=w|wv+pP!g4#!
    zRys6EN$Jv}#U47$k&)pDzvks}LGfPku6P9p!56Py)~1)W(11n7n}`Wx!=;_JTiu#d
    zpCqx=hEk@t4sp?!j{W}wP@V-=Pd=T^>6IKBy;#mLA7hCe{V7B3@I7Ipa}L`MbF|YQ
    z)$BNWsiEnoNHrtJli|n8cOnn4NyF=8MbVxgof0>Uv%wM_j94a;8(LMjlL~E(99gJ*2%JtNtAkD@j;^
    za~Y~&j6uY{=Rv5S4joH*RW_m9N{ZSN0HhAwFyJNok
    zS9kx$>wMf%tUi&Eb`6u0lWJ|k?A-42(lp2UmS(PrAc(24wexRiHUieMwf$o%m6$xs
    zp#-SdBUu2D5`v;(9-sm&kN2M74c&AvKe_v@tQ|dzJ2qSgQHpnUP(iQ?J%Il;Jdyp#
    z7}cpq6Kdm+FS~zS4Eo;fuO=DFP*UlpO|_CNt5&NUqBvQWxmg7#ARvMf=%#H@p%RZ`
    zjK$hMbNb+vVP3UlkfIt&ptJ<00Ic{Ka+lF+&w;OEs1O2#V8~O|R*Gq9TIgM&UqM&bZOXBwnbC?
    zDr))NR&g>lwVgcmnx`K1$)PTTw3m}-T11^ZkY{}jQ@lGD$XzJIcVFkYBBW=o_}TUU
    zt@yd{Jz;@~72x#!RG(#ira6}v-*J#<{@@^OI-Q2T^}=IKLubsa&V-%WwlF1s7fz~u
    zMdQTV7SnRet#^`VO0V7H(?59X{uy+S`(sorO@2-+qioUdo9+6r4#|jb=?t50oh42R
    z{}I>Krut|YKkOc|O|M>y#(3YA;I(i+MiHSfwbJA$jIUr$Y2i|u)*>@2eUYk`j4C5r
    z>61dKu!AqM_E7#DoDzbd-bfT%AYXUUB{SS|{b{`5^?wz1{PVQgTlvyqOX8(#GTz(U
    zNPhnj>$lC`xaD56`TjW&uW8p~qikP*F8kHFM0frzdk%UNGjb1O$%uLK`0-)2UsZ3L
    z#+j+CI_8k4VslL%$aVR@joX>M-@odbX!os$xY$HDIOCokY?{Q0v2kQErf|ZlN>D9w
    zC+2}E&?rDdi#%))$p%P4C_xGXu=@U~_<|V4L|{>TP$XBp$5pCPXLzK3!;gP>7=QNi
    zkNOur`>xY=@VSpB#LsN9JKpOz({ANcdv>?K+D_*_HZ<;9>kplj^Ph5!e&&a#?(3vK
    z_Q@}D_M5kGcx^AuaI~qKYUnb1Mj-n;MURXa)+x7~e2gbMW|gw?5Rg
    zTOMlo>6zIJ$VNVgn(@kTSL0eP)nR35IHpoHM2W#h6cNmTm@-9`dFJ$;k(S`7Lg@RY
    zp!hNmb9un!O4Wt05ANDGirv(B14gW|
    zwjP}C9bK{J`qZ_S2o)b`RonR-b8~y8)$H0`+gg6>#^wu8eCp9xA9B>>8(KRizI?+^
    zAJ#i>*({qM-c4gBB~5dzg(wj!HA`hkh!aDl5>u&J;>2K#Ax2)2wt|L!9X;(=*jy!`r4_FhCBoRxNjXNv(~jGQ|%<}%K6RimaBJcP0v}oCgRN3B;oiM)opj?
    zXm;;tv3q-yy}NqMOr^~3&1lW$w3}UK_IT2sCrkYx5$&6e2A%g;QZUX~A&L!2rFd0p
    z5%men@^zN_Xw2|v%*c2|wQfkN4r6u&k;LxYY+w3{KY#cie)!iz>(yAgt=&-+Sy2V&
    z9BJxI+VMKQ%dvY~x>gmEijj3ss_*NAT(8d1@DQ6e&#Ln&6Qk>wHrh>;V2nvomC`8&
    z(w?`?*_^3u-TJrMzv2~7dH(XLJvUOXk4U8oW6Ol)YsawhIB{GdvIzu1hzMTrE)cvB
    z%2GxMpaF89<9uF(?cfN(BNR?wwWvCZ6e62+G_{$+;`yjgLj{(^z*zzwd;K3RElb*%=??P
    zm+lLY0@Y}^kVdMYX5M)YJ~8h=i(S{q#NfU0xPTao4WPDQL=Y_;vg=p%iay1_`<0Ga
    zMG&<(pOU+bI2u9_g8IJBTqGX*3@G$Zc`pj0f@)vd2?Aj`ms>DHg>;w~p}HXV(*VJX
    zphd;fht9qL3E)D8h$$A;SGl22Ygv>`iU=A)z=1ZYN$|2`*$`R)?KD>$tw_e9h_x~eX_udS~Q%yz?48i*aIa+_wx|j{B
    zsG7mwZ)6M3dmvgMC3K-66;ML(9o2xU!F8+qF)>v{1;ip)6v_I)6law|rd_Dx2oV|n
    z(Qm_PUnTTuKFG)w%s|)lS!w~Lm$k|Al=0djocyHU;>1H=!N}0E0lSV^b2^6~^lUco
    zyoH+|_!li3#euHd4TJS8=CLaHG9H8g&h3Xm
    z#>BkpUBAmae(#)qO3)ZMG3irM=5IzA^s+)w86=tIMT{&?Awux<(k2>U#n`c&@Z?u=
    z%=#BoO-9Nc^?)hz*YW~~tU8rLR-MZBJsY_7fp2r~mY>q-O;L%5Fp?}V6CK=F(18U3
    znxB8ZR0TT{)T64RDt!+yFgp!JXGP0|It0Hz2Em#YfRv>O>8A?J=Sz!nq<|{&mW=?~
    zDQT{S6PH0|jwy37t+0Ob6izz)JdRlNEUbyk>-K?}FOT=Dj9SuS_0nTFd+A^D?Bo83
    zTkicXcW=IuZoZd(Dl;&#`LI;_s?e;OH9quf?*XuV0O$Qh0j~HWKpA|PXV4&b2zs
    z@W5<)dtovIRZ@gvsi$^s;v05(XwF3$lJ;wzYfE`46fnT7>!qt|hWHRE>yQP)i8=
    zVbC|O{Ud6%kwGcch>>|pE-=?cW;TDR0lE5Nw7l66lr-zIYT3bj^ujCn$b0{ZO;gwK
    z#}}W(*T3~in$6ZCpbB98pftPTo;!K>U;H*7_}t4m;;4i9#^2t`pS<=jsnx198);d3
    z-M6Mx{7-c0A-jhJQ`5mBy8TBnfbr2~sER5E5oz}=so34cg)GYarRWi8w#W$%G{?Z*4xDb#LX1B1
    zg!4G{m~*)H_J8J^SNt`XU-fxjea`>p_$Qyn*Dn18*WdPCp8oWw^XU)%kfRQHMgfQh
    z1j_ua@O4G%QK;&YH3Y9(q!hkgOUCkcVH5N0Ug(EPX%H6qCfPqg))qrd#ec^47dBu-
    z=sRkmjGS>3K(tfRTo;zCXO-74hV;y1!vCN}v|w?AWR$YpYXs@Dr?iNLKD9s|2)0aHY!TKTYhwMI
    z7b#54h!H6rUU9+xnL$g6h?t?Li5guXPY1g)$bI$~rHWP%QkYJ6Y-U^0C(@*$ruN2*zn0QRBOeVpgMFbT%k!Dn1*u#%J^y)enX1K;0~
    z%3Q
    zP(b%}P!Loj6M{v96(Qa~K!bq-V-P89U_K)0zHC_F#L==3IPh2hHG6&?rxvQ%|EljR
    zfGIDyu=rIrl1dyjuMfwuh?pXZmARwNZ?GbW;5BH5D#nN|WbGm+UGAh7_AcG>4&|{0
    zrg?k@h8zm!0A|5Zo%X%g|2tBPKHHB6`~4h?I@bepDe6?^f8w
    zBnzfOf|j{kR5m6BLRr0$!RZ$PHSk*)tyjkws*DpyHIiiL*8o(Smx(OKT7@D&Y3OI^
    zEUMtKa2*SLjt(eJsZsLsrgV`A+xL(~JN#JU6+L)gCe%VuSNbCzTr09w>eZ#779SKV
    z)m)@#TNVy|q3Tz_U`^7MY`l}`GU~OlQi|*cprX?tm@tIV+8kOGkaa=9Y<{N|RZ)ns
    zHlgnz2S%qwK9wXjest~Ux$YNNA{0?6Xpv{_mqYt8D`g&7Yb~>lX+HP&AK<=+Zl_kO
    z6a2g`^4=9W92GQ3e9Mk6?DlzlkIM`iOzwk*5L81TcuyYkI-<3^@49_+^XC7&N}SL1
    zh$kIBxb`9+v}acfV?FQ
    zN#04eHe0*j{pz=zOj3#EHLrT3e)O;3xqpCWrl$e)PcD9jQ4P-8_zyZg^M7i|*kOuj
    znsvlwNUsy5+01^P_sqMOjXjxKwHn4)$87t-MWZZ*5Dbit4|D9vL+spsJ0JPd?{Ms)
    zFW^<@yqjZ=IvG%$ck_Cu9|b8CvoV%5P5IZWzs>i4`~`N+-p`7a6RbLHJ;nxtSB#Mb
    z`1I552=9DrYWFNZ{-=Mt;SVo5@3cmv`IZT@@>#~zCe-=qENxsn+uHfL`e?SbT3IQ_
    zt~e)Lcirs_S5^X#?hDYmgV%8QQDe+?>*1&0e^BnaeZz(&D~3<)#QuUL8h*NlXgtr|
    z&a{_Z)o9FK_U5<0!E3N|yY1P2g%J9s*?!zF78+NSb%!ix)tbQ09oO&|U$~Bwk35^-
    zec9VN^xz{043e^xD}WEmzh8d^-~Pd8**bEfd+I?HuO~n4SksoN8LRPUy={E<@BjRMUh?X71Xaey>t^$&Eq2B7)u_r$
    z|IQwpG52G!F$J5fRo1LqLB7iKz_!bI@27skX~+Eze|Y}IBuRp?hR7z|eA~7B<99#7
    zrX4r2a_tCDUb_}Cg)g!OEVeJ5AEVRyb!9~f4OL68qhZZRP0l*>MdkxvxXeGWx$T>+
    zI^X!wnYQDnwK9?i)j)eLXJU2Cw>~>R?72@MecvT7;h~2gATow_cbc)$Ws+xNSB{++
    zo^tTp^y*(-Y-XF=$XyoBJnMN9+p!Qrep1)%ym_v7zZH{;u~L>T=4XP!f^?uC4ULUR
    zdl`>x+DVkHVd;|9#N*oubBFQEyRT#UK^0c7T}l)eEEFS)qvZl%f>#I;iCwAWb=kW0
    z(e#lm51o?d>D|kgtTscVQCNDAXMAjxSX&{_Qf)T((wMHWWLbz6WpPXP0(3_SBWwI19Vx?$i6WUqP$4O|wjNbYzst$z{58`cBhm
    z&F(N-KeXFzo#aC|6BbC($As#B8X=}ggpDyQUp|Q>9cG$47#>TQn%T(eHA`5se7KnZ
    zF_dj_6NN0xS-oZ%Nj%PTpK=MC
    zw*4IMGls_v)mokI)Dph*pD<)7prEF|j6I$2=XF=Ua3z;BN^yt&H@G%7&
    zWnL7*e0S9svjSP>kuc;VCbZXUN3G7D8`G@!Qnjt=p=7yC?QH0tsa@RsuPMLj@wf-c
    z|LV)H$Auga+MTAU#>)eeuh_L`!qC=Ls|{m}Cy)|w6#aP}w6_-ya~9LF
    z{dQAPa-|&ME858gIK=}lVK7MLT~Oye&UM9y?0X=8Qmvb*)=X}iv%Me)Gqav+FWdGT
    zuk&#ak~?2Kzf}w)xZuKGx%+`1?Ecoq?*H@EjFm%C6OT577vWKoJB
    z$A^sIasm!5TGOFFGmHkKNTE7KW3nveUq1bt4Uj)!1_6BJ
    zU6=EoPrjVdk+pQX+j-GTpQS&&^43tT43kuRlvE8fGdYc!1|m)3WCuwlqB>NeQc0**
    zYE&wTj*QpuPLfJ)j2$(`sI@k@oR!^9d(3&Kd6r3*<)pooPNzq=)1%#NQ;nAsF*5VR
    zOYXQC;B^4*Sik--jy?J`uDj-!
    zSep}9YT4*SOrT2I6MF4H+EZFRPh+}^b4@i8OYk9Y&86o*Y4(`Ax1W4#tX^5m6LjZPb61LF2?qBy?B_?1YE!nej)R5c8qG`2s_uF`Cu+
    z`X_$#2Ur#!Pw0WVd60fYG8A#y55LDyJ!Yt$5G6Efb<6Nr%-BTC_|llMB?%*A5%rOX
    z`fyBbD5g@4Ns^)P;F7zjv{t6u?k1J0kR*v#Dhair3iXjH^^qz=!xd`vm`W`oN-Wj_
    zNML7~t!rRbc|9I0mUjpEgOJ9XGg2;vjDZ;b~V638P!uVuejytg~ci-I(n9#M6AR=mQG0YjoLKGPgFp(jS4Pn7UJR)Et
    z-8ZsqWsRLXri#f_BSeWIat3P+Q3Td1#ws={2CLGpDdvrgP#KD7
    z&SnaR^#_Bsq;Xt;kyI^}iX~1WYzdHamc$tH1#Mz6f<2(WuH^s%^yXK78Gyg}{;LNA
    zoW%$)#R!a0wv&q%qj%+~i3^k&1jY!ljfi82Vr$~W5G6u&$Wp0VqR3*bDIWLE4Y64K
    ze08)CmeFrq2>QGFSDAk%Rhs}$r*rJVNuoO(~AJ!PG{T~d_i(dQ;OsQc+q&twwlJV|`Bv$N}R$K=uxCPyc!RBBXfRjRcZi5yAQk|YKj*>d`|Xw~ckP!!SW%^gsH
    z4oDR1AJt?S?}B;<&e0TPFsNAMQwxCt69o{uA>=K^qd1+MST3tptj8GHnN(upgb*ji
    zq`i%b+{{=o7ByB78@8!x_Gs&uqLOKv_6{gO2b4jbc8YT@EEzqBp!v_c?XXFx9Dq
    zb{!I|Nu<;4kZbyl3*LDg#$f7`nKwT9p9|2|t&fmAe64Of^c3TKI%Q?_^+uxaj|?xL
    zw5U4G#YlpQDngbfM)q85qt=DJt|y5nG){VqE;V8I&WBCAH+|pe@QT+};^BWB8(lGB
    zqe!DD7GqI`0pj%h;hm
    z;n?F&(5YS1X4{T?Hf24&;~ic?rDC*Zgk;*ga9b~Je`?R%gBQy3U5$!cEi-#s>T+d#
    zWH}Mbv|6p1R<`wiiPB32Gn*u}EQxC^LGJIR?H}~g*|#s5IQY`pJzcYP=0El5RWIen
    z8*k;5(^qldFJ}(enhxl1pnB_vPi5uu!@1|-9|Owd=%J>WPwQ>dkLW|!5WV<$<73Xb
    z{0CRJT1OpP567)vYea*J7*!3_M-nC`C)l*@dKzsw^5El5v)K$c-nf?sZ)?i>Gc=yt
    zg{xL=urnv{!j}h=hh{KFAjIS@=h9C!xJWW@nmR0Ns^Wrk)72_X;&VM@qLNZyn;-h1m-)j4PH{!#b7fObo=TF+Xw
    z)_t{JRqgNW{e9m)=MZ*rJl6A%IHK!gcqM)U)>TjF8ytMTRLpN39jns9J?@oOe47l4
    z1dw7d06;*nuu_+V$6Qs4K>#PCRHVFExV^duw#+4>?(j)
    z*AHP%*L5@qEpM#j?*@5nOq@HlBR^5M@^_J9)U!&MV7N?QAAfFbdJaGWPgRws)6~+R
    z-NrZmx0V*7Od$!{dkY1w*wll3j_1b``)C%NHS6N>yBU998+?y%)4SU2YA}
    zA%$NKSGVi)4!sVH=l1lla~XcBLKrfnO2~CXCa>$GlX_p?dYsM`3%)hidhs()bzlDL
    zr7zEG>kK#SwpW`1YyR;!pa1&-`0t?)V)3FnK7V~pCo%hYIQUj+f?7Oh#@-(|a?XKA
    zr;?n->{Mx?{fOYn3n4;UD5a5kBx9Z>DQ1SETOzUjjZ`HF0&e`i-6T<17qM|ec7?fBc
    z;0k&%hz+o?+KMG>1)PSqUSqTR@!luCa_YiGo3TkPUp^w8T}r$YFf$gPyy|ZYU`={9
    z3c4MNG|FgE6ETxVuw_~St-lefEMgF+NTdzZD8wWJ0s<69@frs3IxH*_A4`(dIZhJT
    z)TwApTxD36oOSS>-?;UKV^n{)k!mFpfWRL3*Rxl@V_bS?f`4@I!*C2lX%(H}L=`CT
    z0BxGtLQ@`yX#0U)3`bO@9NHBjM^*Gw64K=(1QdKEK*p+u<&qTSoUzKhfO`4Wz>@z)uK^Aw6m!k{QPq@f~bd?t)6?}
    z1bJ=k7!E&fDxUmP-(QVQ?F@i8a-dv4%Gg64haX`yNv^E%Ea<=YJ4SdqH4e{1~Sk?qbu|M;*f
    zbqpYh(szvQ9ev=Amrj8q0@9+|SbxTQw)=Lr&Hm@e_hY2mXXchai5dBmusvCYf%>!X
    zK>#8PKtTjx&+y*EIR|SkT*`=|2>VPq0kb=fM~F#u|GG<9sj?zc-#-8BqmC*-%N5t%
    z3v1um65bJjO9}`JV*qzjs9O-*vCma1qq%z0=Thg*sPtm8u4CiyU5H^JCTU0mH2?_M
    zGn{jci{Y)p`kvomV&MR6*th{{opqpyh3Ux4m)!GykUSWKMk@t>>SyNTwj2L%XZ{Nn
    z>Xv_j0zm+HA-wSFCJ4n;tqux{Z<*M!+ghP`mh}};q{({$d;y{&M#518E{~{H2e(KJ+~I!
    z(QA0${wLzt8F#!r1DoX%bYVIIT!6Y1
    zJctN_2;>9AahjEz5Cm@p&;a2*ykj`$0UrSH$QJ^n3By@S!UCJh5jS2|HIuruyXF34
    zRDv0v?9yEOYVFWR0jftU~yzAQIFKu_~N!vxLSpD
    zIxEmBpAwnRC3gEyg%Yon(xeEA2t*11fhfB~8i^HvMIcQOp5dF9V>l7DZ+tS31TC`?6B2!P-{Ai`NS%8sfWFCh_#
    z2!sJ<26G0;dxnUBNT3Wrj-j+52u(2zc*4ieoxAxfi_hFMD8$Dt*t4hHU+Z6a>y4`)
    z-dgRJ&wT2GICjQeJ24|X4P=?_kA+q7QY|L{F)
    z>E#!CslTU!sFuPzhBSJAZ4?NAGFdr600O~tQ;`JDd9Vkv#1X>KptUV8Q)hHgp)4=n
    zf7k1aF8a|v_e`5zKCDz~Nuz3ARYohScS~Kpws!0=fL0XBO0`T-YycqYn}yY@ZV?g2
    zlnDnM86|@t(hM=mC6W&G)j}8N_Fwtr#>s`2R4qD9xuZ_o&BU=o5&`up5LX5DnnxN7
    z(!|510_PdtJ9u$`Fq8(A0!#>KLogu_1c1^6@0sdRitRngzWe^er2PiAMIqpkE7Xj4
    zqSD0i@PNn2cHaUJ;)tnGEM^?Y2OX%5fOPNhi#0IY;la!zy_Gm@B#Lw#(Mo_^%=
    znu44{7-|HeMy{k$Y%?&%Kq&>KG_*4CK85oRio&-@sE4y2Y3h;2*%j9ragC&24JaC`
    z`!uzlS%RjYWaMg=C2{s!Ax`QU03w3c0Yn(2{;azYNJdU3mn!CrxI&4*JCC^T#}y}2
    zA`QzFa=EsmQ0RGvftbU
    zQ>{c90A|-98)Xj4nT0b0yyJf8t%xIraRd)QQ&z*I6o?d@PmrXe$eT_q-0f@}wCCAq
    zEl$Ss8*j&&jkjWZGSHg|Kx;aNPWFa9~0$jGSbWOU>XjH6xDc0w(iTEtcE6dO3#5TC{ScvW=I(b=Nv*)M5VtC-7j0@OiMO};u|K_aA+ua&Wy|G
    z0O?p6>sL7#>4bE^@$`cedW&;pHYGbq)cE=gVUygN~?!_hF|0teV`9}~ml+s!M!x_o7(s*;*
    zCVc-VU&If8em*{M)JJgGyiZ}QGSUDFC<*}~u!v@1)yzPXBMKoDa!^zNBmjHLN~pCo
    z86Fi-BjwE?n=_NmIA?K7liV3M;v_;xTNl23?ow=ga}EA*-%{NFA9)Ej6(HYiJs85m`CL9ANNz_7Wfw>}W{H&o
    zhy)^>0cdZXg2B-WvL1};5P}FJQvqpeDFK{}*W_F4Q?l}yJ$-+C<-Fxs|HfnZ?SC!9
    z1CQT|j+S@fx%Cg={YRgO&z2Z>i~diz*O?*BnAkIbU{QcAP}Z33z=$xNR5+KgfMs35xDG&i*Vb0Kg44zZ^zZ&
    zc>uXE4-p1))`B-&1MC}R(r5-n0MAaC)!S!3D{E#4D+*c5&ME_7bO-`vnhuJ0%rG^y
    z*MSI{U{o_J!WqGvFVAW?BdzlmMhBQRZ2?B+Z$U21!?_gN1W=^F4PGQ^jHW1{`Cb9o
    zLx~8DXBkZ|AhymqMH-oHxQxU~>&7f9WD8o#QYOvxW(yKUdVH3~XXbxdwyFjxt+lAv
    zZaWSag=@
    z=8P$&K}1lbY?iX@ee4?s0wKUBJ964=H$0STaA3T?n~R$9CTTo$W*+}*eEXdRL>ghx
    z0ulvhz0Z>9A)>e;5?WE{3wn~(Mxl@k5Z8vY60)g)Z7AM`NMj7L0~nqG?*MV$0cj#*
    zg?t%+Zb&IZs~iSLH{&P2T8vGbH$W*3fW~XQxiirODk4xy!&-;m-f<)T^zbbx6J$2bI!+g&Q(Tb>mTpfw(MhPbbX*24YD+xC~pjzlg4B?I0>ZG1eo;$GZ-@3q)Ayc(TT%9uB8CcO9K>t$rJ4+!Ga!{2blb3*{mJ?rAx;e_@g
    zW=}sb8SURhsg02gkr06Qo;))H{@ois2J0*E-a_ku;$#FwS}J2z^z{y5!Tf{u-m?$!
    zW7XmPw~xK}Y|U*DV-zVxM2Z?xn6(ROnxdy?JIXW%Qzy=WHv^~-wPRiPJ(xPPjP?m_
    zU@!3AH)Mt2y@NuFGk%)cvT4gxH~;vV!~gKarE2vv&(f8P@Ag++xft8kE4o&xvN3^V
    zhgKTPzIFc&iMV*lvDmVC6ReMr3kzh>qKs;xT2uwI^KCQwiCuxGcI>;nX1mYH6|D_I
    zV?e$kJ`M5;L7M=zY84}cF$$#|Dx-Bwp4xT+U;&*D<@0j8tMo%x5%Tg?~5R?T=3cv%@lt|5rbf!U~$$KWHR3?Xk
    zu&I|c5%P}XIIb@4XrJ=aC`y!W*}^Y88R7A}hVa+MJ05U+?`P+M8rvjM6j3edroqA2
    zxm4Kuj7oLnm$`fxbar$}K3^bGfWT*$Wd5R*hEfJ52%w-LATTp*YNZ}ksTNg7J=bnd
    z-Pkqa!RO=D(kYB&|Wjqg0rvF8kum{NfucTYqrP
    z`5U%u**G!G6{S=zQMp`3K3_yWUyzoz^2Q(tmC>3+s5Oq`4(BY=)S@2MFgiNo;u?&k
    zg`0}`37-~9P0%vHiA@+H2!cEy8o#>wuOImB)G_Pj7yce!TXGVt#ORn
    z(=jFB*q2Zp6$}lGp?}+$um^#4QjKaSEI75c$z6AAYL348>#uKEccl>fFbuUZ0R$d}
    zZ~}6sT!$|qC`YPurgrtQ76=RC$YS~T-}$t1r_YJ6x+vSq`|xwOl@gGLU>BhcFBv~FMie-ahi$Rz-LINpu0Hu~Za`}LYEdk2y0hQVU6k7}mB|~9e!x(}I6ii4k;VvE0
    z?|KG+Oj%0Bi3m(dlp;$c5Cu`1CM@ypLV(%bX9
    zr_WVSKiJ10x1!vdPr`gLXF?@f1r%~#N8UkH?XgO1p%e>?-DLnfb
    z=86?7j~f~sKElT8lSw^&-{|PJ_Z)D@o-cw6^yvN1aY@hS38meM!r|M7s_XW%93Aak
    za$IUh=gpcu=jzR`4$^18^F8_11#h4-#Jd^}{s&{CB`(>qac=+s03~!qSaf7zbY(hY
    za%Ew3WdJfTF)=MLIW00WR4_R@Gcr0eGA%GSIxsM(l48sN001R)MObuXVRU6WZEs|0
    vW_bWIFflPLFgYzTHdHV-Ix;spGd3+SH##sdcWUue00000NkvXXu0mjfB?gph
    
    literal 0
    HcmV?d00001
    
    diff --git a/sample/src/main/res/drawable-xhdpi/ic_launcher.png b/sample/src/main/res/drawable-xhdpi/ic_launcher.png
    new file mode 100644
    index 0000000000000000000000000000000000000000..71c6d760f05183ef8a47c614d8d13380c8528499
    GIT binary patch
    literal 14383
    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
    
    literal 0
    HcmV?d00001
    
    diff --git a/sample/src/main/res/layout/activity_main.xml b/sample/src/main/res/layout/activity_main.xml
    new file mode 100644
    index 000000000..27cb01e6e
    --- /dev/null
    +++ b/sample/src/main/res/layout/activity_main.xml
    @@ -0,0 +1,75 @@
    +
    +
    +    
    +
    +        
    +
    +            
    +
    +            
    */ public class RequestParams { - private static String ENCODING = "UTF-8"; + + private static final String LOG_TAG = "RequestParams"; protected ConcurrentHashMap urlParams; + protected ConcurrentHashMap streamParams; protected ConcurrentHashMap fileParams; protected ConcurrentHashMap> urlParamsWithArray; @@ -127,27 +130,28 @@ public void put(String key, String value) { } /** - * Adds a integer param to the request. + * Adds a file to the request. * - * @param key the key name for the new param. - * @param value the integer value for the new param. + * @param key the key name for the new param. + * @param file the file to add. + * @throws java.io.FileNotFoundException throws if wrong File argument was passed */ - public void put(String key, int value) { - if (key != null) { - urlParams.put(key, String.valueOf(value)); - } + public void put(String key, File file) throws FileNotFoundException { + put(key, file, null); } /** * Adds a file to the request. * - * @param key the key name for the new param. - * @param file the file to add. - * - * @throws java.io.FileNotFoundException if the file is not found + * @param key the key name for the new param. + * @param file the file to add. + * @param contentType the content type of the file, eg. application/json + * @throws java.io.FileNotFoundException throws if wrong File argument was passed */ - public void put(String key, File file) throws FileNotFoundException { - put(key, new FileInputStream(file), file.getName()); + public void put(String key, File file, String contentType) throws FileNotFoundException { + if (key != null && file != null) { + fileParams.put(key, new FileWrapper(file, contentType)); + } } /** @@ -162,23 +166,6 @@ public void put(String key, ArrayList values) { } } - /** - * Adds value to param which can have more than one value. - * - * @param key the key name for the param, either existing or new. - * @param value the value string for the new param. - */ - public void add(String key, String value) { - if (key != null && value != null) { - ArrayList paramArray = urlParamsWithArray.get(key); - if (paramArray == null) { - paramArray = new ArrayList(); - this.put(key, paramArray); - } - paramArray.add(value); - } - } - /** * Adds an input stream to the request. * @@ -192,12 +179,12 @@ public void put(String key, InputStream stream) { /** * Adds an input stream to the request. * - * @param key the key name for the new param. - * @param stream the input stream to add. - * @param fileName the name of the file. + * @param key the key name for the new param. + * @param stream the input stream to add. + * @param name the name of the stream. */ - public void put(String key, InputStream stream, String fileName) { - put(key, stream, fileName, null); + public void put(String key, InputStream stream, String name) { + put(key, stream, name, null); } /** @@ -205,12 +192,12 @@ public void put(String key, InputStream stream, String fileName) { * * @param key the key name for the new param. * @param stream the input stream to add. - * @param fileName the name of the file. + * @param name the name of the stream. * @param contentType the content type of the file, eg. application/json */ - public void put(String key, InputStream stream, String fileName, String contentType) { + public void put(String key, InputStream stream, String name, String contentType) { if (key != null && stream != null) { - fileParams.put(key, new FileWrapper(stream, fileName, contentType)); + streamParams.put(key, new StreamWrapper(stream, name, contentType)); } } @@ -221,6 +208,7 @@ public void put(String key, InputStream stream, String fileName, String contentT */ public void remove(String key) { urlParams.remove(key); + streamParams.remove(key); fileParams.remove(key); urlParamsWithArray.remove(key); } @@ -237,6 +225,15 @@ public String toString() { result.append(entry.getValue()); } + for (ConcurrentHashMap.Entry entry : streamParams.entrySet()) { + if (result.length() > 0) + result.append("&"); + + result.append(entry.getKey()); + result.append("="); + result.append("STREAM"); + } + for (ConcurrentHashMap.Entry entry : fileParams.entrySet()) { if (result.length() > 0) result.append("&"); @@ -266,57 +263,64 @@ public String toString() { /** * Returns an HttpEntity containing all request parameters * - * @return an HttpEntity containing all request parameters + * @param progressHandler HttpResponseHandler for reporting progress on entity submit + * @return HttpEntity resulting HttpEntity to be included along with {@link org.apache.http.client.methods.HttpEntityEnclosingRequestBase} + * @throws IOException if one of the streams cannot be read */ - public HttpEntity getEntity() { - HttpEntity entity = null; + public HttpEntity getEntity(AsyncHttpResponseHandler progressHandler) throws IOException { + if (streamParams.isEmpty() && fileParams.isEmpty()) { + return createFormEntity(); + } else { + return createMultipartEntity(progressHandler); + } + } - if (!fileParams.isEmpty()) { - SimpleMultipartEntity multipartEntity = new SimpleMultipartEntity(); + private HttpEntity createFormEntity() { + try { + return new UrlEncodedFormEntity(getParamsList(), HTTP.UTF_8); + } catch (UnsupportedEncodingException e) { + return null; // Actually cannot happen when using utf-8 + } + } - // Add string params - for (ConcurrentHashMap.Entry entry : urlParams.entrySet()) { - multipartEntity.addPart(entry.getKey(), entry.getValue()); - } + private HttpEntity createMultipartEntity(AsyncHttpResponseHandler progressHandler) throws IOException { + SimpleMultipartEntity entity = new SimpleMultipartEntity(progressHandler); - // Add dupe params - for (ConcurrentHashMap.Entry> entry : urlParamsWithArray.entrySet()) { - ArrayList values = entry.getValue(); - for (String value : values) { - multipartEntity.addPart(entry.getKey(), value); - } - } + // Add string params + for (ConcurrentHashMap.Entry entry : urlParams.entrySet()) { + entity.addPart(entry.getKey(), entry.getValue()); + } - // Add file params - int currentIndex = 0; - int lastIndex = fileParams.entrySet().size() - 1; - for (ConcurrentHashMap.Entry entry : fileParams.entrySet()) { - FileWrapper file = entry.getValue(); - if (file.inputStream != null) { - boolean isLast = currentIndex == lastIndex; - if (file.contentType != null) { - multipartEntity.addPart(entry.getKey(), file.getFileName(), file.inputStream, file.contentType, isLast); - } else { - multipartEntity.addPart(entry.getKey(), file.getFileName(), file.inputStream, isLast); - } - } - currentIndex++; + // Add dupe params + for (ConcurrentHashMap.Entry> entry : urlParamsWithArray + .entrySet()) { + ArrayList values = entry.getValue(); + for (String value : values) { + entity.addPart(entry.getKey(), value); } + } - entity = multipartEntity; - } else { - try { - entity = new UrlEncodedFormEntity(getParamsList(), ENCODING); - } catch (UnsupportedEncodingException e) { - e.printStackTrace(); + // Add stream params + for (ConcurrentHashMap.Entry entry : streamParams.entrySet()) { + StreamWrapper stream = entry.getValue(); + if (stream.inputStream != null) { + entity.addPart(entry.getKey(), stream.name, stream.inputStream, + stream.contentType); } } + // Add file params + for (ConcurrentHashMap.Entry entry : fileParams.entrySet()) { + FileWrapper fileWrapper = entry.getValue(); + entity.addPart(entry.getKey(), fileWrapper.file, fileWrapper.contentType); + } + return entity; } private void init() { urlParams = new ConcurrentHashMap(); + streamParams = new ConcurrentHashMap(); fileParams = new ConcurrentHashMap(); urlParamsWithArray = new ConcurrentHashMap>(); } @@ -339,26 +343,28 @@ protected List getParamsList() { } protected String getParamString() { - return URLEncodedUtils.format(getParamsList(), ENCODING); + return URLEncodedUtils.format(getParamsList(), HTTP.UTF_8); } private static class FileWrapper { - public InputStream inputStream; - public String fileName; + public File file; public String contentType; - public FileWrapper(InputStream inputStream, String fileName, String contentType) { - this.inputStream = inputStream; - this.fileName = fileName; + public FileWrapper(File file, String contentType) { + this.file = file; this.contentType = contentType; } + } - public String getFileName() { - if (fileName != null) { - return fileName; - } else { - return "nofilename"; - } + private static class StreamWrapper { + public InputStream inputStream; + public String name; + public String contentType; + + public StreamWrapper(InputStream inputStream, String name, String contentType) { + this.inputStream = inputStream; + this.name = name; + this.contentType = contentType; } } -} +} \ No newline at end of file diff --git a/library/src/com/loopj/android/http/SimpleMultipartEntity.java b/library/src/com/loopj/android/http/SimpleMultipartEntity.java index b072b8b0d..30346ffe0 100644 --- a/library/src/com/loopj/android/http/SimpleMultipartEntity.java +++ b/library/src/com/loopj/android/http/SimpleMultipartEntity.java @@ -23,128 +23,223 @@ package com.loopj.android.http; +import android.util.Log; + import org.apache.http.Header; import org.apache.http.HttpEntity; import org.apache.http.message.BasicHeader; -import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; -import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.util.ArrayList; +import java.util.List; import java.util.Random; +/** + * Simplified multipart entity mainly used for sending one or more files. + */ class SimpleMultipartEntity implements HttpEntity { + + private static final String LOG_TAG = "SimpleMultipartEntity"; + + private static final String APPLICATION_OCTET_STREAM = "application/octet-stream"; + private static final byte[] CR_LF = ("\r\n").getBytes(); + private static final byte[] TRANSFER_ENCODING_BINARY = "Content-Transfer-Encoding: binary\r\n" + .getBytes(); + private final static char[] MULTIPART_CHARS = "-_1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray(); - private String boundary = null; + private String boundary; + private byte[] boundaryLine; + private byte[] boundaryEnd; + + private List fileParts = new ArrayList(); + + // The buffer we use for building the message excluding files and the last + // boundary + private ByteArrayOutputStream out = new ByteArrayOutputStream(); + + private AsyncHttpResponseHandler progressHandler; - ByteArrayOutputStream out = new ByteArrayOutputStream(); - boolean isSetLast = false; - boolean isSetFirst = false; + private int bytesWritten; - public SimpleMultipartEntity() { + private int totalSize; + + public SimpleMultipartEntity(AsyncHttpResponseHandler progressHandler) { final StringBuilder buf = new StringBuilder(); final Random rand = new Random(); for (int i = 0; i < 30; i++) { buf.append(MULTIPART_CHARS[rand.nextInt(MULTIPART_CHARS.length)]); } - this.boundary = buf.toString(); + boundary = buf.toString(); + boundaryLine = ("--" + boundary + "\r\n").getBytes(); + boundaryEnd = ("--" + boundary + "--\r\n").getBytes(); + + this.progressHandler = progressHandler; } - public void writeFirstBoundaryIfNeeds() { - if (!isSetFirst) { - writeBoundary(); + public void addPart(final String key, final String value, final String contentType) { + try { + out.write(boundaryLine); + out.write(createContentDisposition(key)); + out.write(createContentType(contentType)); + out.write(CR_LF); + out.write(value.getBytes()); + out.write(CR_LF); + } catch (final IOException e) { + // Can't happen on ByteArrayOutputStream + Log.e(LOG_TAG, "addPart ByteArrayOutputStream exception", e); } + } - isSetFirst = true; + public void addPart(final String key, final String value) { + addPart(key, value, "text/plain; charset=UTF-8"); } - public void writeBoundary() { - try { - out.write(("--" + boundary + "\r\n").getBytes()); - } catch (final IOException e) { - e.printStackTrace(); + public void addPart(String key, File file) { + addPart(key, file, null); + } + + public void addPart(final String key, File file, String type) { + if (type == null) { + type = APPLICATION_OCTET_STREAM; } + fileParts.add(new FilePart(key, file, type)); } - public void writeLastBoundaryIfNeeds() { - if (isSetLast) { - return; + public void addPart(String key, String streamName, InputStream inputStream, String type) + throws IOException { + if (type == null) { + type = APPLICATION_OCTET_STREAM; + } + out.write(boundaryLine); + + // Headers + out.write(createContentDisposition(key, streamName)); + out.write(createContentType(type)); + out.write(TRANSFER_ENCODING_BINARY); + out.write(CR_LF); + + // Stream (file) + final byte[] tmp = new byte[4096]; + int l; + while ((l = inputStream.read(tmp)) != -1) { + out.write(tmp, 0, l); } + out.write(CR_LF); + out.flush(); try { - out.write(("--" + boundary + "--\r\n").getBytes()); - out.flush(); + inputStream.close(); } catch (final IOException e) { - e.printStackTrace(); + // Not important, just log it + Log.w(LOG_TAG, "Cannot close input stream", e); } + } - isSetLast = true; + private byte[] createContentType(String type) { + String result = "Content-Type: " + type + "\r\n"; + return result.getBytes(); } - public void addPart(final String key, final String value, final String contentType) { - writeBoundary(); - try { - out.write(("Content-Disposition: form-data; name=\"" + key + "\"\r\n").getBytes()); - out.write(("Content-Type: " + contentType + "\r\n\r\n").getBytes()); - out.write(value.getBytes()); - out.write(("\r\n").getBytes()); - } catch (final IOException e) { - e.printStackTrace(); - } + private byte[] createContentDisposition(final String key) { + return new StringBuilder() + .append("Content-Disposition: form-data; name=\"") + .append(key) + .append("\"\r\n") + .toString() + .getBytes(); } - public void addPart(final String key, final String value) { - addPart(key, value, "text/plain; charset=UTF-8"); + private byte[] createContentDisposition(final String key, final String fileName) { + return new StringBuilder() + .append("Content-Disposition: form-data; name=\"") + .append(key) + .append("\"; filename=\"") + .append(fileName) + .append("\"\r\n") + .toString() + .getBytes(); } - public void addPart(final String key, final String fileName, final InputStream fin, final boolean isLast) { - addPart(key, fileName, fin, "application/octet-stream", isLast); + private void updateProgress(int count) { + bytesWritten += count; + progressHandler.sendProgressMessage(bytesWritten, totalSize); } - public void addPart(final String key, final String fileName, final InputStream fin, String type, final boolean isLast) { - writeBoundary(); - try { - type = "Content-Type: " + type + "\r\n"; - out.write(("Content-Disposition: form-data; name=\"" + key + "\"; filename=\"" + fileName + "\"\r\n").getBytes()); - out.write(type.getBytes()); - out.write("Content-Transfer-Encoding: binary\r\n\r\n".getBytes()); + private class FilePart { + public File file; + public byte[] header; + public FilePart(String key, File file, String type) { + header = createHeader(key, file.getName(), type); + this.file = file; + } + + private byte[] createHeader(String key, String filename, String type) { + ByteArrayOutputStream headerStream = new ByteArrayOutputStream(); + try { + headerStream.write(boundaryLine); + + // Headers + headerStream.write(createContentDisposition(key, filename)); + headerStream.write(createContentType(type)); + headerStream.write(TRANSFER_ENCODING_BINARY); + headerStream.write(CR_LF); + } catch (IOException e) { + // Can't happen on ByteArrayOutputStream + Log.e(LOG_TAG, "createHeader ByteArrayOutputStream exception", e); + } + return headerStream.toByteArray(); + } + + public long getTotalLength() { + long streamLength = file.length(); + return header.length + streamLength; + } + + public void writeTo(OutputStream out) throws IOException { + out.write(header); + updateProgress(header.length); + + FileInputStream inputStream = new FileInputStream(file); final byte[] tmp = new byte[4096]; int l; - while ((l = fin.read(tmp)) != -1) { + while ((l = inputStream.read(tmp)) != -1) { out.write(tmp, 0, l); + updateProgress(l); } - out.write(("\r\n").getBytes()); - - } catch (final IOException e) { - e.printStackTrace(); - } finally { + out.write(CR_LF); + updateProgress(CR_LF.length); + out.flush(); try { - fin.close(); + inputStream.close(); } catch (final IOException e) { - e.printStackTrace(); + // Not important, just log it + Log.w(LOG_TAG, "Cannot close input stream", e); } } } - public void addPart(final String key, final File value, final boolean isLast) { - try { - addPart(key, value.getName(), new FileInputStream(value), isLast); - } catch (final FileNotFoundException e) { - e.printStackTrace(); - } - } + // The following methods are from the HttpEntity interface @Override public long getContentLength() { - writeLastBoundaryIfNeeds(); - return out.toByteArray().length; + long contentLen = out.size(); + for (FilePart filePart : fileParts) { + long len = filePart.getTotalLength(); + if (len < 0) { + return -1; // Should normally not happen + } + contentLen += len; + } + contentLen += boundaryEnd.length; + return contentLen; } @Override @@ -169,8 +264,16 @@ public boolean isStreaming() { @Override public void writeTo(final OutputStream outstream) throws IOException { - writeLastBoundaryIfNeeds(); - outstream.write(out.toByteArray()); + bytesWritten = 0; + totalSize = (int) getContentLength(); + out.writeTo(outstream); + updateProgress(out.size()); + + for (FilePart filePart : fileParts) { + filePart.writeTo(outstream); + } + outstream.write(boundaryEnd); + updateProgress(boundaryEnd.length); } @Override @@ -179,8 +282,7 @@ public Header getContentEncoding() { } @Override - public void consumeContent() throws IOException, - UnsupportedOperationException { + public void consumeContent() throws IOException, UnsupportedOperationException { if (isStreaming()) { throw new UnsupportedOperationException( "Streaming entity does not implement #consumeContent()"); @@ -188,9 +290,8 @@ public void consumeContent() throws IOException, } @Override - public InputStream getContent() throws IOException, - UnsupportedOperationException { - writeLastBoundaryIfNeeds(); - return new ByteArrayInputStream(out.toByteArray()); + public InputStream getContent() throws IOException, UnsupportedOperationException { + throw new UnsupportedOperationException( + "getContent() is not supported. Use writeTo() instead."); } } \ No newline at end of file From f1ab377b8505d5499781a919147ba83896d862b8 Mon Sep 17 00:00:00 2001 From: mareksebera Date: Mon, 14 Oct 2013 22:20:14 +0200 Subject: [PATCH 104/613] Fixed wrong static field, Closing #229 --- .../src/com/loopj/android/http/BinaryHttpResponseHandler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/src/com/loopj/android/http/BinaryHttpResponseHandler.java b/library/src/com/loopj/android/http/BinaryHttpResponseHandler.java index 08255419e..d8e5cdda3 100644 --- a/library/src/com/loopj/android/http/BinaryHttpResponseHandler.java +++ b/library/src/com/loopj/android/http/BinaryHttpResponseHandler.java @@ -57,7 +57,7 @@ */ public class BinaryHttpResponseHandler extends AsyncHttpResponseHandler { // Allow images by default - private static String[] mAllowedContentTypes = new String[]{ + private String[] mAllowedContentTypes = new String[]{ "image/jpeg", "image/png" }; From 2f341aa346dc7aa4970793b3498c17c10f657dd0 Mon Sep 17 00:00:00 2001 From: Ankush Sachdeva Date: Tue, 15 Oct 2013 04:03:35 +0530 Subject: [PATCH 105/613] Added Proxy Authentication ; fixes #328 --- .../com/loopj/android/http/AsyncHttpClient.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/library/src/com/loopj/android/http/AsyncHttpClient.java b/library/src/com/loopj/android/http/AsyncHttpClient.java index c9bb29aab..ea363e7ca 100644 --- a/library/src/com/loopj/android/http/AsyncHttpClient.java +++ b/library/src/com/loopj/android/http/AsyncHttpClient.java @@ -310,6 +310,23 @@ public void setProxy(String hostname, int port) { final HttpParams httpParams = this.httpClient.getParams(); httpParams.setParameter(ConnRoutePNames.DEFAULT_PROXY, proxy); } + /** + * Sets the Proxy by it's hostname,port,username and password + * + * @param hostname the hostname (IP or DNS name) + * @param port the port number. -1 indicates the scheme default port. + * @param username the username + * @param password the password + */ + public void setProxy(String hostname,int port,String username,String password){ + httpClient.getCredentialsProvider().setCredentials( + new AuthScope(hostname, port), + new UsernamePasswordCredentials(username, password)); + final HttpHost proxy = new HttpHost(hostname, port); + final HttpParams httpParams = this.httpClient.getParams(); + httpParams.setParameter(ConnRoutePNames.DEFAULT_PROXY, proxy); + } + /** * Sets the SSLSocketFactory to user when making requests. By default, From be2bac0d4e310106f81c6b50eda94d39cd091ff5 Mon Sep 17 00:00:00 2001 From: mareksebera Date: Tue, 15 Oct 2013 19:55:42 +0200 Subject: [PATCH 106/613] Fixes #253 --- .../loopj/android/http/AsyncHttpClient.java | 65 ++++++++++++++----- 1 file changed, 49 insertions(+), 16 deletions(-) diff --git a/library/src/com/loopj/android/http/AsyncHttpClient.java b/library/src/com/loopj/android/http/AsyncHttpClient.java index c9bb29aab..d5c2dedd5 100644 --- a/library/src/com/loopj/android/http/AsyncHttpClient.java +++ b/library/src/com/loopj/android/http/AsyncHttpClient.java @@ -144,28 +144,37 @@ public AsyncHttpClient(int httpPort, int httpsPort) { } /** - * Creates a new AsyncHttpClient. + * Creates new AsyncHttpClient using given params * - * @param fixNoHttpResponseException See issue https://github.com/loopj/android-async-http/issues/143 - * @param httpPort non-standard HTTP-only port - * @param httpsPort non-standard HTTPS-only port + * @param fixNoHttpResponseException Whether to fix or not issue, by ommiting SSL verification + * @param httpPort HTTP port to be used, must be greater than 0 + * @param httpsPort HTTPS port to be used, must be greater than 0 */ public AsyncHttpClient(boolean fixNoHttpResponseException, int httpPort, int httpsPort) { - if (fixNoHttpResponseException) - Log.d(LOG_TAG, "Beware! Using the fix is insecure, as it doesn't verify SSL certificates."); - BasicHttpParams httpParams = new BasicHttpParams(); + this(getDefaultSchemeRegistry(fixNoHttpResponseException, httpPort, httpsPort)); + } - ConnManagerParams.setTimeout(httpParams, socketTimeout); - ConnManagerParams.setMaxConnectionsPerRoute(httpParams, new ConnPerRouteBean(maxConnections)); - ConnManagerParams.setMaxTotalConnections(httpParams, DEFAULT_MAX_CONNECTIONS); + /** + * Returns default instance of SchemeRegistry + * + * @param fixNoHttpResponseException Whether to fix or not issue, by ommiting SSL verification + * @param httpPort HTTP port to be used, must be greater than 0 + * @param httpsPort HTTPS port to be used, must be greater than 0 + */ + private static SchemeRegistry getDefaultSchemeRegistry(boolean fixNoHttpResponseException, int httpPort, int httpsPort) { + if (fixNoHttpResponseException) { + Log.d(LOG_TAG, "Beware! Using the fix is insecure, as it doesn't verify SSL certificates."); + } - HttpConnectionParams.setSoTimeout(httpParams, socketTimeout); - HttpConnectionParams.setConnectionTimeout(httpParams, socketTimeout); - HttpConnectionParams.setTcpNoDelay(httpParams, true); - HttpConnectionParams.setSocketBufferSize(httpParams, DEFAULT_SOCKET_BUFFER_SIZE); + if (httpPort < 1) { + httpPort = 80; + Log.d(LOG_TAG, "You've given wront HTTP port number, defaulting to 80"); + } - HttpProtocolParams.setVersion(httpParams, HttpVersion.HTTP_1_1); - HttpProtocolParams.setUserAgent(httpParams, String.format("android-async-http/%s (http://loopj.com/android-async-http)", VERSION)); + if (httpsPort < 1) { + httpsPort = 443; + Log.d(LOG_TAG, "You've given wront HTTP port number, defaulting to 443"); + } // Fix to SSL flaw in API < ICS // See https://code.google.com/p/android/issues/detail?id=13117 @@ -179,6 +188,30 @@ public AsyncHttpClient(boolean fixNoHttpResponseException, int httpPort, int htt schemeRegistry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), httpPort)); schemeRegistry.register(new Scheme("https", sslSocketFactory, httpsPort)); + return schemeRegistry; + } + + /** + * Creates a new AsyncHttpClient. + * + * @param schemeRegistry SchemeRegistry to be used + */ + public AsyncHttpClient(SchemeRegistry schemeRegistry) { + + 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.setConnectionTimeout(httpParams, socketTimeout); + HttpConnectionParams.setTcpNoDelay(httpParams, true); + HttpConnectionParams.setSocketBufferSize(httpParams, DEFAULT_SOCKET_BUFFER_SIZE); + + HttpProtocolParams.setVersion(httpParams, HttpVersion.HTTP_1_1); + HttpProtocolParams.setUserAgent(httpParams, String.format("android-async-http/%s (http://loopj.com/android-async-http)", VERSION)); + ThreadSafeClientConnManager cm = new ThreadSafeClientConnManager(httpParams, schemeRegistry); threadPool = (ThreadPoolExecutor) Executors.newCachedThreadPool(); From 379980027cfcd4387b8475d0025c534b38c55bfb Mon Sep 17 00:00:00 2001 From: mareksebera Date: Tue, 15 Oct 2013 20:06:51 +0200 Subject: [PATCH 107/613] Fixes #227 --- .../loopj/android/http/AsyncHttpClient.java | 37 +++++++++++++------ 1 file changed, 26 insertions(+), 11 deletions(-) diff --git a/library/src/com/loopj/android/http/AsyncHttpClient.java b/library/src/com/loopj/android/http/AsyncHttpClient.java index 0eac46110..911828f94 100644 --- a/library/src/com/loopj/android/http/AsyncHttpClient.java +++ b/library/src/com/loopj/android/http/AsyncHttpClient.java @@ -115,6 +115,7 @@ public class AsyncHttpClient { private ThreadPoolExecutor threadPool; private final Map>>> requestMap; private final Map clientHeaderMap; + private boolean isUrlEncodingEnabled = true; /** @@ -343,6 +344,7 @@ public void setProxy(String hostname, int port) { final HttpParams httpParams = this.httpClient.getParams(); httpParams.setParameter(ConnRoutePNames.DEFAULT_PROXY, proxy); } + /** * Sets the Proxy by it's hostname,port,username and password * @@ -351,14 +353,14 @@ public void setProxy(String hostname, int port) { * @param username the username * @param password the password */ - public void setProxy(String hostname,int port,String username,String password){ - httpClient.getCredentialsProvider().setCredentials( - new AuthScope(hostname, port), - new UsernamePasswordCredentials(username, password)); + public void setProxy(String hostname, int port, String username, String password) { + httpClient.getCredentialsProvider().setCredentials( + new AuthScope(hostname, port), + new UsernamePasswordCredentials(username, password)); final HttpHost proxy = new HttpHost(hostname, port); final HttpParams httpParams = this.httpClient.getParams(); httpParams.setParameter(ConnRoutePNames.DEFAULT_PROXY, proxy); - } + } /** @@ -501,7 +503,7 @@ public void head(Context context, String url, AsyncHttpResponseHandler responseH * @param responseHandler the response handler instance that should handle the response. */ public void head(Context context, String url, RequestParams params, AsyncHttpResponseHandler responseHandler) { - sendRequest(httpClient, httpContext, new HttpHead(getUrlWithQueryString(url, params)), null, responseHandler, context); + sendRequest(httpClient, httpContext, new HttpHead(getUrlWithQueryString(isUrlEncodingEnabled, url, params)), null, responseHandler, context); } /** @@ -516,7 +518,7 @@ public void head(Context context, String url, RequestParams params, AsyncHttpRes * the response. */ public void head(Context context, String url, Header[] headers, RequestParams params, AsyncHttpResponseHandler responseHandler) { - HttpUriRequest request = new HttpHead(getUrlWithQueryString(url, params)); + HttpUriRequest request = new HttpHead(getUrlWithQueryString(isUrlEncodingEnabled, url, params)); if (headers != null) request.setHeaders(headers); sendRequest(httpClient, httpContext, request, null, responseHandler, context); @@ -568,7 +570,7 @@ public void get(Context context, String url, AsyncHttpResponseHandler responseHa * @param responseHandler the response handler instance that should handle the response. */ public void get(Context context, String url, RequestParams params, AsyncHttpResponseHandler responseHandler) { - sendRequest(httpClient, httpContext, new HttpGet(getUrlWithQueryString(url, params)), null, responseHandler, context); + sendRequest(httpClient, httpContext, new HttpGet(getUrlWithQueryString(isUrlEncodingEnabled, url, params)), null, responseHandler, context); } /** @@ -583,7 +585,7 @@ public void get(Context context, String url, RequestParams params, AsyncHttpResp * the response. */ public void get(Context context, String url, Header[] headers, RequestParams params, AsyncHttpResponseHandler responseHandler) { - HttpUriRequest request = new HttpGet(getUrlWithQueryString(url, params)); + HttpUriRequest request = new HttpGet(getUrlWithQueryString(isUrlEncodingEnabled, url, params)); if (headers != null) request.setHeaders(headers); sendRequest(httpClient, httpContext, request, null, responseHandler, context); @@ -802,7 +804,7 @@ public void delete(Context context, String url, Header[] headers, AsyncHttpRespo * @param responseHandler the response handler instance that should handle the response. */ public void delete(Context context, String url, Header[] headers, RequestParams params, AsyncHttpResponseHandler responseHandler) { - HttpDelete httpDelete = new HttpDelete(getUrlWithQueryString(url, params)); + HttpDelete httpDelete = new HttpDelete(getUrlWithQueryString(isUrlEncodingEnabled, url, params)); if (headers != null) httpDelete.setHeaders(headers); sendRequest(httpClient, httpContext, httpDelete, null, responseHandler, context); } @@ -830,7 +832,20 @@ protected void sendRequest(DefaultHttpClient client, HttpContext httpContext, Ht } } - public static String getUrlWithQueryString(String url, RequestParams params) { + public void setURLEncodingEnabled(boolean enabled) { + this.isUrlEncodingEnabled = enabled; + } + + /** + * Will encode url, if not disabled, and adds params on the end of it + * + * @param url String with URL, should be valid URL without params + * @param params RequestParams to be appended on the end of URL + */ + public static String getUrlWithQueryString(boolean isUrlEncodingEnabled, String url, RequestParams params) { + if (isUrlEncodingEnabled) + url = url.replace(" ", "%20"); + if (params != null) { String paramString = params.getParamString(); if (!url.contains("?")) { From 87601938f4dd06fefa47c32f9ef5084bf08001d2 Mon Sep 17 00:00:00 2001 From: mareksebera Date: Tue, 15 Oct 2013 20:16:19 +0200 Subject: [PATCH 108/613] Javadoc for AsyncHttpClient --- .../loopj/android/http/AsyncHttpClient.java | 34 +++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/library/src/com/loopj/android/http/AsyncHttpClient.java b/library/src/com/loopj/android/http/AsyncHttpClient.java index 911828f94..4ecfb7561 100644 --- a/library/src/com/loopj/android/http/AsyncHttpClient.java +++ b/library/src/com/loopj/android/http/AsyncHttpClient.java @@ -809,8 +809,16 @@ public void delete(Context context, String url, Header[] headers, RequestParams sendRequest(httpClient, httpContext, httpDelete, null, responseHandler, context); } - - // Private stuff + /** + * Puts a new request in queue as a new thread in pool to be executed + * + * @param client HttpClient to be used for request, can differ in single requests + * @param contentType MIME body type, for POST and PUT requests, may be null + * @param context Context of Android application, to hold the reference of request + * @param httpContext HttpContext in which the request will be executed + * @param responseHandler ResponseHandler or its subclass to put the response into + * @param uriRequest instance of HttpUriRequest, which means it must be of HttpDelete, HttpPost, HttpGet, HttpPut, etc. + */ protected void sendRequest(DefaultHttpClient client, HttpContext httpContext, HttpUriRequest uriRequest, String contentType, AsyncHttpResponseHandler responseHandler, Context context) { if (contentType != null) { uriRequest.addHeader("Content-Type", contentType); @@ -832,6 +840,12 @@ protected void sendRequest(DefaultHttpClient client, HttpContext httpContext, Ht } } + /** + * Sets state of URL encoding feature, see bug #227, this method + * allows you to turn off and on this auto-magic feature on-demand. + * + * @param enabled desired state of feature + */ public void setURLEncodingEnabled(boolean enabled) { this.isUrlEncodingEnabled = enabled; } @@ -858,6 +872,13 @@ public static String getUrlWithQueryString(boolean isUrlEncodingEnabled, String return url; } + /** + * Returns HttpEntity containing data from RequestParams included with request declaration. + * Allows also passing progress from upload via provided ResponseHandler + * + * @param params additional request params + * @param responseHandler AsyncHttpResponseHandler or its subclass to be notified on progress + */ private HttpEntity paramsToEntity(RequestParams params, AsyncHttpResponseHandler responseHandler) { HttpEntity entity = null; @@ -875,6 +896,12 @@ private HttpEntity paramsToEntity(RequestParams params, AsyncHttpResponseHandler return entity; } + /** + * Applicable only to HttpRequest methods extending HttpEntityEnclosingRequestBase, which is for example not DELETE + * + * @param entity entity to be included within the request + * @param requestBase HttpRequest instance, must not be null + */ private HttpEntityEnclosingRequestBase addEntityToRequestBase(HttpEntityEnclosingRequestBase requestBase, HttpEntity entity) { if (entity != null) { requestBase.setEntity(entity); @@ -883,6 +910,9 @@ private HttpEntityEnclosingRequestBase addEntityToRequestBase(HttpEntityEnclosin return requestBase; } + /** + * Enclosing entity to hold stream of gzip decoded data for accessing HttpEntity contents + */ private static class InflatingEntity extends HttpEntityWrapper { public InflatingEntity(HttpEntity wrapped) { super(wrapped); From e6d72f4c57e963adddfbbdaa01ac6ea6b3394f45 Mon Sep 17 00:00:00 2001 From: mareksebera Date: Tue, 15 Oct 2013 20:43:33 +0200 Subject: [PATCH 109/613] Throwing malformed URL fixes #115, Throwing Interrupted exception fixes #323 --- .../com/loopj/android/http/AsyncHttpRequest.java | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/library/src/com/loopj/android/http/AsyncHttpRequest.java b/library/src/com/loopj/android/http/AsyncHttpRequest.java index fa28d2544..339c6c0f5 100644 --- a/library/src/com/loopj/android/http/AsyncHttpRequest.java +++ b/library/src/com/loopj/android/http/AsyncHttpRequest.java @@ -28,6 +28,7 @@ import java.io.IOException; import java.net.ConnectException; +import java.net.MalformedURLException; import java.net.SocketException; import java.net.SocketTimeoutException; import java.net.UnknownHostException; @@ -74,16 +75,19 @@ public void run() { } } - private void makeRequest() throws IOException { + private void makeRequest() throws IOException, InterruptedException { if (!Thread.currentThread().isInterrupted()) { try { + // Fixes #115 + if (request.getURI().getScheme() == null) + throw new MalformedURLException("No valid URI scheme was provided"); HttpResponse response = client.execute(request, context); if (!Thread.currentThread().isInterrupted()) { if (responseHandler != null) { responseHandler.sendResponseMessage(response); } } else { - //TODO: should raise InterruptedException? this block is reached whenever the request is cancelled before its response is received + throw new InterruptedException("makeRequest was interrupted"); } } catch (IOException e) { if (!Thread.currentThread().isInterrupted()) { @@ -104,7 +108,7 @@ private void makeRequestWithRetries() throws ConnectException { makeRequest(); return; } catch (ClientProtocolException e) { - if(responseHandler != null) { + if (responseHandler != null) { responseHandler.sendFailureMessage(e, "cannot repeat the request"); } return; @@ -137,6 +141,9 @@ private void makeRequestWithRetries() throws ConnectException { // http://code.google.com/p/android/issues/detail?id=5255 cause = new IOException("NPE in HttpClient" + e.getMessage()); retry = retryHandler.retryRequest(cause, ++executionCount, context); + } catch (InterruptedException e) { + cause = new IOException("Request was interrupted while executing"); + retry = retryHandler.retryRequest(cause, ++executionCount, context); } } From a87843c34e7563e48a249a459a16365ca7a97cc9 Mon Sep 17 00:00:00 2001 From: mareksebera Date: Tue, 15 Oct 2013 21:59:59 +0200 Subject: [PATCH 110/613] Handling delete RequestParams in SyncHttpClient --- .../com/loopj/android/http/AsyncHttpClient.java | 4 ++++ .../src/com/loopj/android/http/SyncHttpClient.java | 14 +++++++------- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/library/src/com/loopj/android/http/AsyncHttpClient.java b/library/src/com/loopj/android/http/AsyncHttpClient.java index 4ecfb7561..d05f2f3eb 100644 --- a/library/src/com/loopj/android/http/AsyncHttpClient.java +++ b/library/src/com/loopj/android/http/AsyncHttpClient.java @@ -896,6 +896,10 @@ private HttpEntity paramsToEntity(RequestParams params, AsyncHttpResponseHandler return entity; } + public boolean isUrlEncodingEnabled(){ + return isUrlEncodingEnabled; + } + /** * Applicable only to HttpRequest methods extending HttpEntityEnclosingRequestBase, which is for example not DELETE * diff --git a/library/src/com/loopj/android/http/SyncHttpClient.java b/library/src/com/loopj/android/http/SyncHttpClient.java index 8e73471a1..879abb1f5 100644 --- a/library/src/com/loopj/android/http/SyncHttpClient.java +++ b/library/src/com/loopj/android/http/SyncHttpClient.java @@ -4,6 +4,7 @@ import android.os.Message; import org.apache.http.HttpEntity; +import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpUriRequest; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.protocol.HttpContext; @@ -19,7 +20,7 @@ public abstract class SyncHttpClient extends AsyncHttpClient { protected AsyncHttpResponseHandler responseHandler = new AsyncHttpResponseHandler() { @Override - protected void sendResponseMessage(org.apache.http.HttpResponse response) { + protected void sendResponseMessage(HttpResponse response) { responseCode = response.getStatusLine().getStatusCode(); super.sendResponseMessage(response); } @@ -27,7 +28,7 @@ protected void sendResponseMessage(org.apache.http.HttpResponse response) { @Override protected void sendMessage(Message msg) { /* - * Dont use the handler and send it directly to the analysis + * Dont use the handler and send it directly to the analysis * (because its all the same thread) */ handleMessage(msg); @@ -63,7 +64,7 @@ protected void sendRequest(DefaultHttpClient client, } /* - * will execute the request directly + * will execute the request directly */ new AsyncHttpRequest(client, httpContext, uriRequest, responseHandler) .run(); @@ -73,13 +74,12 @@ protected void sendRequest(DefaultHttpClient client, public void delete(String url, RequestParams queryParams, AsyncHttpResponseHandler responseHandler) { - // TODO what about query params?? - delete(url, responseHandler); + delete(getUrlWithQueryString(isUrlEncodingEnabled(), url, queryParams), responseHandler); } public String get(String url, RequestParams params) { this.get(url, params, responseHandler); - /* + /* * the response handler will have set the result when this line is * reached */ @@ -101,7 +101,7 @@ public String put(String url) { return result; } - public String post(String url, HttpEntity entity){ + public String post(String url, HttpEntity entity) { this.post(null, url, entity, null, responseHandler); return result; } From cb99009b08b1488d0293a96d258d2eea5abfbe98 Mon Sep 17 00:00:00 2001 From: Peter Edwards Date: Wed, 16 Oct 2013 14:39:33 +0200 Subject: [PATCH 111/613] Classpath change --- .classpath | 1 + 1 file changed, 1 insertion(+) diff --git a/.classpath b/.classpath index 69b7cb089..4595cb3a8 100644 --- a/.classpath +++ b/.classpath @@ -4,5 +4,6 @@ + From 898e18ea7c14b9e1f96deed857254712ed70287a Mon Sep 17 00:00:00 2001 From: Peter Edwards Date: Wed, 16 Oct 2013 15:03:20 +0200 Subject: [PATCH 112/613] Undid example change for now --- examples/ExampleUsage.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/ExampleUsage.java b/examples/ExampleUsage.java index c4226eed1..b5c4fc6f7 100644 --- a/examples/ExampleUsage.java +++ b/examples/ExampleUsage.java @@ -2,7 +2,7 @@ public class ExampleUsage { public static void makeRequest() { AsyncHttpClient client = new AsyncHttpClient(); - client.get("http://www.google.com/", new TextHttpResponseHandler() { + client.get("http://www.google.com/", new AsyncHttpResponseHandler() { @Override public void onSuccess(String response) { System.out.println(response); From cc38f5dbcb6cd8ee23215589015087a7ecf7a871 Mon Sep 17 00:00:00 2001 From: Peter Edwards Date: Thu, 17 Oct 2013 14:38:59 +0200 Subject: [PATCH 113/613] Major refactor on current code base including: - Rework of the handler hierarchy to support a single base class and remove instanceof checks. - A full implementation of Cancel behaviour with granular cancellation time (downloads are now chunked so can be canceled before completion). - Progress and retry notification - Fixed some small stability issues (unhandled exception, connection issues on network switch). - SyncHttpClient fixes to use any handler (all handlers can now be forced into sync mode) - Cleanup of JsonHttpResponseHandler.java removing all superfluous helper methods Ideally JsonHttpResponseHandler.java, TextHttpResponseHandler.java, BinaryHttpResponseHandler.java should be moved into the examples directory --- .../loopj/android/http/AsyncHttpClient.java | 5 +- .../loopj/android/http/AsyncHttpRequest.java | 130 +++----- .../http/AsyncHttpResponseHandler.java | 311 +++++++++--------- .../http/BinaryHttpResponseHandler.java | 96 +----- .../http/FileAsyncHttpResponseHandler.java | 138 +++----- .../android/http/JsonHttpResponseHandler.java | 192 +++-------- .../loopj/android/http/SyncHttpClient.java | 113 +------ .../android/http/TextHttpResponseHandler.java | 129 ++++++++ 8 files changed, 437 insertions(+), 677 deletions(-) create mode 100644 library/src/com/loopj/android/http/TextHttpResponseHandler.java diff --git a/library/src/com/loopj/android/http/AsyncHttpClient.java b/library/src/com/loopj/android/http/AsyncHttpClient.java index 2bfcabc4e..5db622edc 100644 --- a/library/src/com/loopj/android/http/AsyncHttpClient.java +++ b/library/src/com/loopj/android/http/AsyncHttpClient.java @@ -86,7 +86,7 @@ *

     

    *
      * AsyncHttpClient client = new AsyncHttpClient();
    - * client.get("http://www.google.com/", new AsyncHttpResponseHandler() {
    + * client.get("http://www.google.com/", new TextHttpResponseHandler() {
      *     @Override
      *     public void onSuccess(String response) {
      *         System.out.println(response);
    @@ -199,7 +199,6 @@ private static SchemeRegistry getDefaultSchemeRegistry(boolean fixNoHttpResponse
          * @param schemeRegistry SchemeRegistry to be used
          */
         public AsyncHttpClient(SchemeRegistry schemeRegistry) {
    -
             BasicHttpParams httpParams = new BasicHttpParams();
     
             ConnManagerParams.setTimeout(httpParams, socketTimeout);
    @@ -889,7 +888,7 @@ private HttpEntity paramsToEntity(RequestParams params, AsyncHttpResponseHandler
                 }
             } catch (Throwable t) {
                 if (responseHandler != null)
    -                responseHandler.sendFailureMessage(0, null, t, (String) null);
    +                responseHandler.sendFailureMessage(0, null, null, t);
                 else
                     t.printStackTrace();
             }
    diff --git a/library/src/com/loopj/android/http/AsyncHttpRequest.java b/library/src/com/loopj/android/http/AsyncHttpRequest.java
    index 339c6c0f5..cf34bc7ff 100644
    --- a/library/src/com/loopj/android/http/AsyncHttpRequest.java
    +++ b/library/src/com/loopj/android/http/AsyncHttpRequest.java
    @@ -38,7 +38,6 @@ class AsyncHttpRequest implements Runnable {
         private final HttpContext context;
         private final HttpUriRequest request;
         private final AsyncHttpResponseHandler responseHandler;
    -    private boolean isBinaryRequest;
         private int executionCount;
     
         public AsyncHttpRequest(AbstractHttpClient client, HttpContext context, HttpUriRequest request, AsyncHttpResponseHandler responseHandler) {
    @@ -46,110 +45,83 @@ public AsyncHttpRequest(AbstractHttpClient client, HttpContext context, HttpUriR
             this.context = context;
             this.request = request;
             this.responseHandler = responseHandler;
    -        if (responseHandler instanceof BinaryHttpResponseHandler) {
    -            this.isBinaryRequest = true;
    -        }
         }
     
         @Override
         public void run() {
    -        try {
    -            if (responseHandler != null) {
    -                responseHandler.sendStartMessage();
    -            }
    +        if (responseHandler != null) {
    +            responseHandler.sendStartMessage();
    +        }
     
    +        try {
                 makeRequestWithRetries();
    -
    -            if (responseHandler != null) {
    -                responseHandler.sendFinishMessage();
    -            }
             } catch (IOException e) {
                 if (responseHandler != null) {
    -                responseHandler.sendFinishMessage();
    -                if (this.isBinaryRequest) {
    -                    responseHandler.sendFailureMessage(e, (byte[]) null);
    -                } else {
    -                    responseHandler.sendFailureMessage(e, (String) null);
    -                }
    +                responseHandler.sendFailureMessage(0, null, null, e);
                 }
             }
    +        
    +        if (responseHandler != null) {
    +            responseHandler.sendFinishMessage();
    +        }
         }
     
    -    private void makeRequest() throws IOException, InterruptedException {
    +    private void makeRequest() throws IOException {
             if (!Thread.currentThread().isInterrupted()) {
    -            try {
    -                // Fixes #115
    -                if (request.getURI().getScheme() == null)
    -                    throw new MalformedURLException("No valid URI scheme was provided");
    -                HttpResponse response = client.execute(request, context);
    -                if (!Thread.currentThread().isInterrupted()) {
    -                    if (responseHandler != null) {
    -                        responseHandler.sendResponseMessage(response);
    -                    }
    -                } else {
    -                    throw new InterruptedException("makeRequest was interrupted");
    -                }
    -            } catch (IOException e) {
    -                if (!Thread.currentThread().isInterrupted()) {
    -                    throw e;
    +            // Fixes #115
    +            if (request.getURI().getScheme() == null) {
    +                // subclass of IOException so processed in the caller
    +                throw new MalformedURLException("No valid URI scheme was provided");
    +            }
    +
    +            HttpResponse response = client.execute(request, context);
    +
    +            if (!Thread.currentThread().isInterrupted()) {
    +                if (responseHandler != null) {
    +                    responseHandler.sendResponseMessage(response);
                     }
                 }
             }
         }
     
    -    private void makeRequestWithRetries() throws ConnectException {
    +    private void makeRequestWithRetries() throws IOException {
             // This is an additional layer of retry logic lifted from droid-fu
             // See: https://github.com/kaeppler/droid-fu/blob/master/src/main/java/com/github/droidfu/http/BetterHttpRequestBase.java
             boolean retry = true;
             IOException cause = null;
             HttpRequestRetryHandler retryHandler = client.getHttpRequestRetryHandler();
    -        while (retry) {
    -            try {
    -                makeRequest();
    -                return;
    -            } catch (ClientProtocolException e) {
    -                if (responseHandler != null) {
    -                    responseHandler.sendFailureMessage(e, "cannot repeat the request");
    -                }
    -                return;
    -            } catch (UnknownHostException e) {
    -                if (responseHandler != null) {
    -                    responseHandler.sendFailureMessage(e, "can't resolve host");
    +        try
    +        {
    +            while (retry) {
    +                try {
    +                    makeRequest();
    +                    return;
    +                } catch (UnknownHostException e) {
    +                    // switching between WI-FI and mobile data networks can cause a retry which then results in an UnknownHostException
    +                    // while the WI-FI is initialising. The retry logic will be invoked here, if this is NOT the first retry
    +                    // (to assist in genuine cases of unknown host) which seems better than outright failure
    +                    cause = new IOException("UnknownHostException exception: " + e.getMessage());
    +                    retry = (executionCount > 0) && retryHandler.retryRequest(cause, ++executionCount, context);
    +                } catch (NullPointerException e) {
    +                    // there's a bug in HttpClient 4.0.x that on some occasions causes
    +                    // DefaultRequestExecutor to throw an NPE, see
    +                    // http://code.google.com/p/android/issues/detail?id=5255
    +                    cause = new IOException("NPE in HttpClient: " + e.getMessage());
    +                    retry = retryHandler.retryRequest(cause, ++executionCount, context);
    +                } catch (IOException e) {
    +                    cause = e;
    +                    retry = retryHandler.retryRequest(cause, ++executionCount, context);
                     }
    -                return;
    -            } catch (ConnectTimeoutException e) {
    -                if (responseHandler != null) {
    -                    responseHandler.sendFailureMessage(e, "connection timed out");
    -                }
    -            } catch (SocketException e) {
    -                // Added to detect host unreachable
    -                if (responseHandler != null) {
    -                    responseHandler.sendFailureMessage(e, "can't resolve host");
    +                if(retry && (responseHandler != null)) {
    +                    responseHandler.sendRetryMessage();
                     }
    -                return;
    -            } catch (SocketTimeoutException e) {
    -                if (responseHandler != null) {
    -                    responseHandler.sendFailureMessage(e, "socket time out");
    -                }
    -                return;
    -            } catch (IOException e) {
    -                cause = e;
    -                retry = retryHandler.retryRequest(cause, ++executionCount, context);
    -            } catch (NullPointerException e) {
    -                // there's a bug in HttpClient 4.0.x that on some occasions causes
    -                // DefaultRequestExecutor to throw an NPE, see
    -                // http://code.google.com/p/android/issues/detail?id=5255
    -                cause = new IOException("NPE in HttpClient" + e.getMessage());
    -                retry = retryHandler.retryRequest(cause, ++executionCount, context);
    -            } catch (InterruptedException e) {
    -                cause = new IOException("Request was interrupted while executing");
    -                retry = retryHandler.retryRequest(cause, ++executionCount, context);
                 }
    +        } catch (Exception e) {
    +            // catch anything else to ensure failure message is propagated
    +            cause = new IOException("Unhandled exception: " + e.getMessage());
             }
    -
    -        // no retries left, crap out with exception
    -        ConnectException ex = new ConnectException();
    -        ex.initCause(cause);
    -        throw ex;
    +        
    +        // cleaned up to throw IOException
    +        throw(cause);
         }
     }
    diff --git a/library/src/com/loopj/android/http/AsyncHttpResponseHandler.java b/library/src/com/loopj/android/http/AsyncHttpResponseHandler.java
    index 10669285b..231987876 100644
    --- a/library/src/com/loopj/android/http/AsyncHttpResponseHandler.java
    +++ b/library/src/com/loopj/android/http/AsyncHttpResponseHandler.java
    @@ -27,17 +27,18 @@
     import org.apache.http.HttpResponse;
     import org.apache.http.StatusLine;
     import org.apache.http.client.HttpResponseException;
    -import org.apache.http.entity.BufferedHttpEntity;
    -import org.apache.http.util.EntityUtils;
    +import org.apache.http.util.ByteArrayBuffer;
     
     import java.io.IOException;
    +import java.io.InputStream;
    +import java.lang.ref.WeakReference;
     
     /**
      * Used to intercept and handle the responses from requests made using
    - * {@link AsyncHttpClient}. The {@link #onSuccess(String)} method is
    + * {@link AsyncHttpClient}. The {@link #onSuccess(int statusCode, Header[] headers, byte[] responseBody)} method is
      * designed to be anonymously overridden with your own response handling code.
      * 

     

    - * Additionally, you can override the {@link #onFailure(Throwable, String)}, + * Additionally, you can override the {@link #onFailure(int statusCode, Header[] headers, byte[] responseBody, Throwable error)}, * {@link #onStart()}, and {@link #onFinish()} methods as required. *

     

    * For example: @@ -51,12 +52,12 @@ * } * * @Override - * public void onSuccess(String response) { + * public void onSuccess(int statusCode, Header[] headers, byte[] responseBody) { * // Successfully got a response * } - * + * * @Override - * public void onFailure(Throwable e, String response) { + * public void onFailure(int statusCode, Header[] headers, byte[] responseBody, Throwable error) { * // Response failed :( * } * @@ -73,22 +74,43 @@ public class AsyncHttpResponseHandler { protected static final int START_MESSAGE = 2; protected static final int FINISH_MESSAGE = 3; protected static final int PROGRESS_MESSAGE = 4; + protected static final int RETRY_MESSAGE = 5; + + // ensure this is always at least 1 more than any message value + // used by derived classes to chain messages + protected static final int LAST_MESSAGE = 10; + + + protected static final int BUFFER_SIZE = 4096; private Handler handler; - private String responseCharset = "UTF-8"; - /** - * Sets the charset for the response string. If not set, the default is UTF-8. - * - * @param charset to be used for the response string. - * @see Charset - */ - public void setCharset(final String charset) { - this.responseCharset = charset; + private Boolean forceSynchronous = false; + + // avoid leaks by using a non-anonymous handler class + // with a weak reference + static class ResponderHandler extends Handler { + private final WeakReference mResponder; + + ResponderHandler(AsyncHttpResponseHandler service) { + mResponder = new WeakReference(service); + } + @Override + public void handleMessage(Message msg) + { + AsyncHttpResponseHandler service = mResponder.get(); + if (service != null) { + service.handleMessage(msg); + } + } + } + + public Boolean getForceSynchronous() { + return (forceSynchronous); } - public String getCharset() { - return this.responseCharset; + public void setForceSynchronous(Boolean value) { + forceSynchronous = value; } /** @@ -97,12 +119,7 @@ public String getCharset() { public AsyncHttpResponseHandler() { // Set up a handler to post events back to the correct thread if possible if (Looper.myLooper() != null) { - handler = new Handler() { - @Override - public void handleMessage(Message msg) { - AsyncHttpResponseHandler.this.handleMessage(msg); - } - }; + handler = new ResponderHandler(this); } } @@ -111,15 +128,6 @@ public void handleMessage(Message msg) { // Callbacks to be overridden, typically anonymously // - /** - * Fired when the request progress, override to handle in your own code - * - * @param bytesWritten offset from start of file - * @param totalSize total size of file - */ - public void onProgress(int bytesWritten, int totalSize) { - } - /** * Fired when the request is started, override to handle in your own code */ @@ -133,109 +141,55 @@ public void onFinish() { } /** - * Fired when a request returns successfully, override to handle in your own code - * - * @param content the body of the HTTP response from the server - */ - public void onSuccess(String content) { - } - - /** - * Fired when a request returns successfully, override to handle in your own code - * + * Fired when a request returns successfully, override to handle in your own + * code + * * @param statusCode the status code of the response - * @param headers the headers of the HTTP response - * @param content the body of the HTTP response from the server + * @param headers HTTP response headers + * @param responseBody the body of the HTTP response from the server */ - public void onSuccess(int statusCode, Header[] headers, String content) { - onSuccess(statusCode, content); + public void onSuccess(int statusCode, Header[] headers, byte[] responseBody) { } + /** * Fired when a request returns successfully, override to handle in your own code * * @param statusCode the status code of the response - * @param content the body of the HTTP response from the server - */ - public void onSuccess(int statusCode, String content) { - onSuccess(content); - } - - /** - * Fired when a request fails to complete, override to handle in your own code - * + * @param headers HTTP response headers + * @param responseBody the response body, if any * @param error the underlying cause of the failure - * @deprecated use {@link #onFailure(Throwable, String)} */ - @Deprecated - public void onFailure(Throwable error) { + public void onFailure(int statusCode, Header[] headers, byte[] responseBody, Throwable error) { } /** - * Fired when a request fails to complete, override to handle in your own code - * - * @param error the underlying cause of the failure - * @param content the response body, if any + * Fired when a bytes are received, override to handle in your own code + * + * @param current the current number of bytes loaded from the response + * @param total the total number of bytes in the response */ - public void onFailure(Throwable error, String content) { - // By default, call the deprecated onFailure(Throwable) for compatibility - onFailure(error); + public void onProgress(int current, int total) { } /** - * Fired when a request fails to complete, override to handle in your own code - * - * @param statusCode return HTTP status code - * @param error the underlying cause of the failure - * @param content the response body, if any + * Fired when a retry occurs, override to handle in your own code + * */ - public void onFailure(int statusCode, Throwable error, String content) { - // By default, call the chain method onFailure(Throwable,String) - onFailure(error, content); + public void onRetry() { } - - /** - * Fired when a request fails to complete, override to handle in your own code - * - * @param statusCode return HTTP status code - * @param headers return headers, if any - * @param error the underlying cause of the failure - * @param content the response body, if any - */ - public void onFailure(int statusCode, Header[] headers, Throwable error, String content) { - // By default, call the chain method onFailure(int,Throwable,String) - onFailure(statusCode, error, content); - } - + // // Pre-processing of messages (executes in background threadpool thread) // - protected void sendProgressMessage(int bytesWritten, int totalSize) { - sendMessage(obtainMessage(PROGRESS_MESSAGE, new Object[]{bytesWritten, totalSize})); - } - - protected void sendSuccessMessage(int statusCode, Header[] headers, String responseBody) { + protected void sendSuccessMessage(int statusCode, Header[] headers, byte[] responseBody) { sendMessage(obtainMessage(SUCCESS_MESSAGE, new Object[]{statusCode, headers, responseBody})); } - protected void sendFailureMessage(int statusCode, Header[] headers, Throwable e, String responseBody) { - sendMessage(obtainMessage(FAILURE_MESSAGE, new Object[]{statusCode, headers, e, responseBody})); - } - - @Deprecated - protected void sendFailureMessage(Throwable e, String responseBody) { - sendMessage(obtainMessage(FAILURE_MESSAGE, new Object[]{0, null, e, responseBody})); - } - - protected void sendFailureMessage(int statusCode, Header[] headers, Throwable e, byte[] responseBody) { - sendMessage(obtainMessage(FAILURE_MESSAGE, new Object[]{statusCode, headers, e, responseBody})); - } - - @Deprecated - protected void sendFailureMessage(Throwable e, byte[] responseBody) { - sendMessage(obtainMessage(FAILURE_MESSAGE, new Object[]{0, null, e, responseBody})); + protected void sendFailureMessage(int statusCode, Header[] headers, byte[] responseBody, Throwable error) { + sendMessage(obtainMessage(FAILURE_MESSAGE, new Object[] { statusCode, headers, responseBody, error })); } protected void sendStartMessage() { @@ -246,51 +200,68 @@ protected void sendFinishMessage() { sendMessage(obtainMessage(FINISH_MESSAGE, null)); } + protected void sendProgressMessage(int current, int total) { + sendMessage(obtainMessage(PROGRESS_MESSAGE, new Object[] { current, total })); + } + protected void sendRetryMessage() { + sendMessage(obtainMessage(RETRY_MESSAGE, null)); + } + // // Pre-processing of messages (in original calling thread, typically the UI thread) // - protected void handleSuccessMessage(int statusCode, Header[] headers, String responseBody) { + protected void handleSuccessMessage(int statusCode, Header[] headers, byte[] responseBody) { onSuccess(statusCode, headers, responseBody); } - protected void handleFailureMessage(int statusCode, Header[] headers, Throwable e, String responseBody) { - onFailure(statusCode, headers, e, responseBody); + protected void handleFailureMessage(int statusCode, Header[] headers, byte[] responseBody, Throwable error) { + onFailure(statusCode, headers, responseBody, error); } + protected void handleProgressMessage(int current, int total) { + onProgress(current, total); + } + protected void handleRetryMessage() { + onRetry(); + } + // Methods which emulate android's Handler and Message methods protected void handleMessage(Message msg) { Object[] response; switch (msg.what) { - case SUCCESS_MESSAGE: - response = (Object[]) msg.obj; - handleSuccessMessage((Integer) response[0], (Header[]) response[1], (String) response[2]); - break; - case FAILURE_MESSAGE: - response = (Object[]) msg.obj; - handleFailureMessage((Integer) response[0], (Header[]) response[1], (Throwable) response[2], (String) response[3]); - break; - case START_MESSAGE: - onStart(); - break; - case FINISH_MESSAGE: - onFinish(); - break; - case PROGRESS_MESSAGE: - response = (Object[]) msg.obj; - onProgress((Integer) response[0], (Integer) response[1]); - break; + case SUCCESS_MESSAGE: + response = (Object[]) msg.obj; + handleSuccessMessage((Integer) response[0], (Header[]) response[1], (byte[]) response[2]); + break; + case FAILURE_MESSAGE: + response = (Object[]) msg.obj; + handleFailureMessage((Integer) response[0], (Header[]) response[1], (byte[]) response[2], (Throwable) response[3]); + break; + case START_MESSAGE: + onStart(); + break; + case FINISH_MESSAGE: + onFinish(); + break; + case PROGRESS_MESSAGE: + response = (Object[]) msg.obj; + onProgress((Integer) response[0], (Integer) response[1]); + break; + case RETRY_MESSAGE: + handleRetryMessage(); + break; } } protected void sendMessage(Message msg) { - if (handler != null) { - handler.sendMessage(msg); - } else { + if (forceSynchronous || handler == null) { handleMessage(msg); + } else if (!Thread.currentThread().isInterrupted()) { // do not send messages if request has been cancelled + handler.sendMessage(msg); } } @@ -308,36 +279,54 @@ protected Message obtainMessage(int responseMessage, Object response) { return msg; } - // Interface to AsyncHttpRequest - protected void sendResponseMessage(HttpResponse response) { - if (response == null) { - sendFailureMessage(0, null, new IllegalStateException("No response"), (String) null); - return; - } - StatusLine status = response.getStatusLine(); - String responseBody = null; - try { - HttpEntity entity; - HttpEntity temp = response.getEntity(); - if (temp != null) { - entity = new BufferedHttpEntity(temp); - responseBody = EntityUtils.toString(entity, getCharset()); - } - } catch (IOException e) { - try { - if (response.getEntity() != null) - response.getEntity().consumeContent(); - } catch (Throwable t) { - t.printStackTrace(); + byte[] getResponseData(HttpEntity entity) throws IOException { + byte[] responseBody = null; + if (entity != null) { + InputStream instream = entity.getContent(); + if (instream != null) { + long contentLength = entity.getContentLength(); + if (contentLength > Integer.MAX_VALUE) { + throw new IllegalArgumentException("HTTP entity too large to be buffered in memory"); + } + if (contentLength < 0) { + contentLength = BUFFER_SIZE; + } + try{ + ByteArrayBuffer buffer = new ByteArrayBuffer((int) contentLength); + try { + byte[] tmp = new byte[BUFFER_SIZE]; + int l, count = 0; + // do not send messages if request has been cancelled + while ((l = instream.read(tmp)) != -1 && !Thread.currentThread().isInterrupted()) { + count += l; + buffer.append(tmp, 0, l); + sendProgressMessage(count, (int) contentLength); + } + } finally { + instream.close(); + } + responseBody = buffer.buffer(); + } catch( OutOfMemoryError e ) { + System.gc(); + throw new IOException("File too large to fit into available memory"); + } } - sendFailureMessage(status.getStatusCode(), response.getAllHeaders(), e, (String) null); - return; } - - if (status.getStatusCode() >= 300) { - sendFailureMessage(status.getStatusCode(), response.getAllHeaders(), new HttpResponseException(status.getStatusCode(), status.getReasonPhrase()), responseBody); - } else { - sendSuccessMessage(status.getStatusCode(), response.getAllHeaders(), responseBody); + return (responseBody); + } + + // Interface to AsyncHttpRequest + void sendResponseMessage(HttpResponse response) throws IOException { + // do not process if request has been cancelled + if (!Thread.currentThread().isInterrupted()) { + StatusLine status = response.getStatusLine(); + byte[] responseBody = null; + responseBody = getResponseData(response.getEntity()); + if (status.getStatusCode() >= 300) { + sendFailureMessage(status.getStatusCode(), response.getAllHeaders(), responseBody, new HttpResponseException(status.getStatusCode(), status.getReasonPhrase())); + } else { + sendSuccessMessage(status.getStatusCode(), response.getAllHeaders(), responseBody); + } } } } diff --git a/library/src/com/loopj/android/http/BinaryHttpResponseHandler.java b/library/src/com/loopj/android/http/BinaryHttpResponseHandler.java index d8e5cdda3..9c6241051 100644 --- a/library/src/com/loopj/android/http/BinaryHttpResponseHandler.java +++ b/library/src/com/loopj/android/http/BinaryHttpResponseHandler.java @@ -18,18 +18,13 @@ package com.loopj.android.http; -import android.os.Message; +import java.io.IOException; +import java.util.regex.Pattern; import org.apache.http.Header; -import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.StatusLine; import org.apache.http.client.HttpResponseException; -import org.apache.http.entity.BufferedHttpEntity; -import org.apache.http.util.EntityUtils; - -import java.io.IOException; -import java.util.regex.Pattern; /** * Used to intercept and handle the responses from requests made using @@ -57,9 +52,9 @@ */ public class BinaryHttpResponseHandler extends AsyncHttpResponseHandler { // Allow images by default - private String[] mAllowedContentTypes = new String[]{ - "image/jpeg", - "image/png" + private static String[] mAllowedContentTypes = new String[] { + "image/jpeg", + "image/png" }; /** @@ -103,75 +98,21 @@ public void onSuccess(int statusCode, byte[] binaryData) { onSuccess(binaryData); } - /** - * Fired when a request fails to complete, override to handle in your own code - * - * @param statusCode response HTTP statuse code - * @param headers response headers, if any - * @param error the underlying cause of the failure - * @param binaryData the response body, if any - * @deprecated - */ - @Deprecated - public void onFailure(int statusCode, Header[] headers, Throwable error, byte[] binaryData) { - // By default, call the deprecated onFailure(Throwable) for compatibility - onFailure(statusCode, error, null); - } - - - // - // Pre-processing of messages (executes in background threadpool thread) - // - - protected void sendSuccessMessage(int statusCode, byte[] responseBody) { - sendMessage(obtainMessage(SUCCESS_MESSAGE, new Object[]{statusCode, responseBody})); - } - @Override - protected void sendFailureMessage(int statusCode, Header[] headers, Throwable e, byte[] responseBody) { - sendMessage(obtainMessage(FAILURE_MESSAGE, new Object[]{statusCode, headers, e, responseBody})); - } - - // - // Pre-processing of messages (in original calling thread, typically the UI thread) - // - - protected void handleSuccessMessage(int statusCode, byte[] responseBody) { - onSuccess(statusCode, responseBody); + public void onSuccess(int statusCode, Header[] headers, byte[] binaryData) { + onSuccess(statusCode, binaryData); } - protected void handleFailureMessage(int statusCode, Header[] headers, Throwable e, byte[] responseBody) { - onFailure(statusCode, headers, e, responseBody); - } - - // Methods which emulate android's Handler and Message methods - @Override - protected void handleMessage(Message msg) { - Object[] response; - switch (msg.what) { - case SUCCESS_MESSAGE: - response = (Object[]) msg.obj; - handleSuccessMessage((Integer) response[0], (byte[]) response[1]); - break; - case FAILURE_MESSAGE: - response = (Object[]) msg.obj; - handleFailureMessage((Integer) response[0], (Header[]) response[1], (Throwable) response[2], (byte[]) response[3]); - break; - default: - super.handleMessage(msg); - break; - } - } // Interface to AsyncHttpRequest @Override - protected void sendResponseMessage(HttpResponse response) { + protected void sendResponseMessage(HttpResponse response) throws IOException { StatusLine status = response.getStatusLine(); Header[] contentTypeHeaders = response.getHeaders("Content-Type"); byte[] responseBody = null; if (contentTypeHeaders.length != 1) { //malformed/ambiguous HTTP Header, ABORT! - sendFailureMessage(status.getStatusCode(), response.getAllHeaders(), new HttpResponseException(status.getStatusCode(), "None, or more than one, Content-Type Header found!"), (String) null); + sendFailureMessage(status.getStatusCode(), response.getAllHeaders(), responseBody, new HttpResponseException(status.getStatusCode(), "None, or more than one, Content-Type Header found!")); return; } Header contentTypeHeader = contentTypeHeaders[0]; @@ -183,24 +124,9 @@ protected void sendResponseMessage(HttpResponse response) { } if (!foundAllowedContentType) { //Content-Type not in allowed list, ABORT! - sendFailureMessage(status.getStatusCode(), response.getAllHeaders(), new HttpResponseException(status.getStatusCode(), "Content-Type not allowed!"), (String) null); + sendFailureMessage(status.getStatusCode(), response.getAllHeaders(), responseBody, new HttpResponseException(status.getStatusCode(), "Content-Type not allowed!")); return; } - try { - HttpEntity entity = null; - HttpEntity temp = response.getEntity(); - if (temp != null) { - entity = new BufferedHttpEntity(temp); - } - responseBody = EntityUtils.toByteArray(entity); - } catch (IOException e) { - sendFailureMessage(status.getStatusCode(), response.getAllHeaders(), e, (byte[]) null); - } - - if (status.getStatusCode() >= 300) { - sendFailureMessage(status.getStatusCode(), response.getAllHeaders(), new HttpResponseException(status.getStatusCode(), status.getReasonPhrase()), responseBody); - } else { - sendSuccessMessage(status.getStatusCode(), responseBody); - } + super.sendResponseMessage( response ); } } diff --git a/library/src/com/loopj/android/http/FileAsyncHttpResponseHandler.java b/library/src/com/loopj/android/http/FileAsyncHttpResponseHandler.java index 7bb674fca..996099d3b 100644 --- a/library/src/com/loopj/android/http/FileAsyncHttpResponseHandler.java +++ b/library/src/com/loopj/android/http/FileAsyncHttpResponseHandler.java @@ -1,109 +1,59 @@ package com.loopj.android.http; -import android.os.Message; - -import org.apache.http.Header; -import org.apache.http.HttpResponse; -import org.apache.http.StatusLine; -import org.apache.http.client.HttpResponseException; - import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; - +import org.apache.http.HttpEntity; + +/** + * + * @author sweetlilmre + * + * Implements a response handler that will store the response in the provided + * File object. + * + * Events will be sent as per the AsyncHttpResponseHandler base class, however + * all byte[] values returned will be null. + */ public class FileAsyncHttpResponseHandler extends AsyncHttpResponseHandler { - + private File mFile; + public File getFile() { + return (mFile); + } + public FileAsyncHttpResponseHandler(File file) { super(); this.mFile = file; } - - public void onSuccess(File file) { - } - - public void onSuccess(int statusCode, File file) { - onSuccess(file); - } - - public void onFailure(Throwable e, File response) { - // By default call lower chain method - onFailure(e); - } - - public void onFailure(int statusCode, Throwable e, File response) { - // By default call lower chain method - onFailure(e, response); - } - - public void onFailure(int statusCode, Header[] headers, Throwable e, File response) { - // By default call lower chain method - onFailure(statusCode, e, response); - } - - - protected void sendSuccessMessage(int statusCode, File file) { - sendMessage(obtainMessage(SUCCESS_MESSAGE, new Object[]{statusCode, file})); - } - - protected void sendFailureMessage(int statusCode, Header[] headers, Throwable e, File file) { - sendMessage(obtainMessage(FAILURE_MESSAGE, new Object[]{statusCode, headers, e, file})); - } - - protected void handleSuccessMessage(int statusCode, File responseBody) { - onSuccess(statusCode, responseBody); - } - - protected void handleFailureMessage(int statusCode, Header[] headers, Throwable e, File responseBody) { - onFailure(statusCode, headers, e, responseBody); - } - - // Methods which emulate android's Handler and Message methods - protected void handleMessage(Message msg) { - Object[] response; - switch (msg.what) { - case SUCCESS_MESSAGE: - response = (Object[]) msg.obj; - handleSuccessMessage((Integer) response[0], (File) response[1]); - break; - case FAILURE_MESSAGE: - response = (Object[]) msg.obj; - handleFailureMessage((Integer) response[0], (Header[]) response[1], (Throwable) response[2], (File) response[3]); - break; - default: - super.handleMessage(msg); - break; - } - } - + @Override - protected void sendResponseMessage(HttpResponse response) { - StatusLine status = response.getStatusLine(); - - try { - FileOutputStream buffer = new FileOutputStream(this.mFile); - InputStream is = response.getEntity().getContent(); - - int nRead; - byte[] data = new byte[16384]; - - while ((nRead = is.read(data, 0, data.length)) != -1) - buffer.write(data, 0, nRead); - - buffer.flush(); - buffer.close(); - - } catch (IOException e) { - sendFailureMessage(status.getStatusCode(), response.getAllHeaders(), e, this.mFile); - } - - if (status.getStatusCode() >= 300) { - sendFailureMessage(status.getStatusCode(), response.getAllHeaders(), new HttpResponseException(status.getStatusCode(), status.getReasonPhrase()), this.mFile); - } else { - sendSuccessMessage(status.getStatusCode(), this.mFile); - } - } -} \ No newline at end of file + byte[] getResponseData(HttpEntity entity) throws IOException { + if (entity != null) { + InputStream instream = entity.getContent(); + long contentLength = entity.getContentLength(); + FileOutputStream buffer = new FileOutputStream(this.mFile); + if (instream != null) { + try { + byte[] tmp = new byte[BUFFER_SIZE]; + int l, count = 0;; + // do not send messages if request has been cancelled + while ((l = instream.read(tmp)) != -1 && !Thread.currentThread().isInterrupted()) { + count += l; + buffer.write(tmp, 0, l); + sendProgressMessage(count, (int) contentLength); + } + } finally { + instream.close(); + buffer.flush(); + buffer.close(); + } + } + } + return (null); + } + +} diff --git a/library/src/com/loopj/android/http/JsonHttpResponseHandler.java b/library/src/com/loopj/android/http/JsonHttpResponseHandler.java index 33855fc96..47fa3d9cb 100644 --- a/library/src/com/loopj/android/http/JsonHttpResponseHandler.java +++ b/library/src/com/loopj/android/http/JsonHttpResponseHandler.java @@ -18,7 +18,7 @@ package com.loopj.android.http; -import android.os.Message; +import java.io.UnsupportedEncodingException; import org.apache.http.Header; import org.apache.http.HttpStatus; @@ -27,20 +27,23 @@ import org.json.JSONObject; import org.json.JSONTokener; +import android.os.Message; + /** * Used to intercept and handle the responses from requests made using * {@link AsyncHttpClient}, with automatic parsing into a {@link JSONObject} * or {@link JSONArray}. *

     

    * This class is designed to be passed to get, post, put and delete requests - * with the {@link #onSuccess(JSONObject)} or {@link #onSuccess(JSONArray)} - * methods anonymously overridden. + * with the {@link #onSuccess(int, Object)} + * method anonymously overridden. *

     

    * Additionally, you can override the other event methods from the * parent class. */ public class JsonHttpResponseHandler extends AsyncHttpResponseHandler { - protected static final int SUCCESS_JSON_MESSAGE = 100; + // chain message values, lets not just make them up. + protected static final int SUCCESS_JSON_MESSAGE = AsyncHttpResponseHandler.LAST_MESSAGE; // // Callbacks to be overridden, typically anonymously @@ -48,133 +51,54 @@ public class JsonHttpResponseHandler extends AsyncHttpResponseHandler { /** * Fired when a request returns successfully and contains a json object - * at the base of the response string. Override to handle in your - * own code. - * - * @param response the parsed json object found in the server response (if any) - */ - public void onSuccess(JSONObject response) { - } - - - /** - * Fired when a request returns successfully and contains a json array - * at the base of the response string. Override to handle in your - * own code. - * - * @param response the parsed json array found in the server response (if any) - */ - public void onSuccess(JSONArray response) { - } - - /** - * Fired when a request returns successfully and contains a json object - * at the base of the response string. Override to handle in your - * own code. - * - * @param statusCode the status code of the response - * @param headers the headers of the HTTP response - * @param response the parsed json object found in the server response (if any) - */ - public void onSuccess(int statusCode, Header[] headers, JSONObject response) { - onSuccess(statusCode, response); - } - - /** - * Fired when a request returns successfully and contains a json object - * at the base of the response string. Override to handle in your - * own code. + * at the base of the response string. Override to handle in your own code. * - * @param statusCode the status code of the response - * @param response the parsed json object found in the server response (if any) + * @param statusCode the HTTP Status code for the response + * @param response the parsed json object found in the server response. + * Check the type of the object to determine if it is one + * of the valid types created by {@link JSONTokener#nextValue()} */ - public void onSuccess(int statusCode, JSONObject response) { - onSuccess(response); + public void onSuccess(int statusCode, Object response) { } /** - * Fired when a request returns successfully and contains a json array - * at the base of the response string. Override to handle in your - * own code. - * - * @param statusCode the status code of the response - * @param headers the headers of the HTTP response - * @param response the parsed json array found in the server response (if any) + * onSuccess is overridden here to perform background processing of the JSON packet */ - public void onSuccess(int statusCode, Header[] headers, JSONArray response) { - onSuccess(statusCode, response); - } - - /** - * Fired when a request returns successfully and contains a json array - * at the base of the response string. Override to handle in your - * own code. - * - * @param statusCode the status code of the response - * @param response the parsed json array found in the server response (if any) - */ - public void onSuccess(int statusCode, JSONArray response) { - onSuccess(response); - } - - public void onFailure(Throwable e, JSONObject errorResponse) { - onFailure(e); - } - - public void onFailure(int statusCode, Throwable e, JSONObject errorResponse) { - onFailure(e, errorResponse); - } - - public void onFailure(int statusCode, Header[] headers, Throwable e, JSONObject errorResponse) { - onFailure(statusCode, e, errorResponse); - } - - public void onFailure(Throwable e, JSONArray errorResponse) { - onFailure(e); - } - - public void onFailure(int statusCode, Throwable e, JSONArray errorResponse) { - onFailure(e, errorResponse); - } - - public void onFailure(int statusCode, Header[] headers, Throwable e, JSONArray errorResponse) { - onFailure(statusCode, e, errorResponse); - } - - - // - // Pre-processing of messages (executes in background threadpool thread) - // - @Override - protected void sendSuccessMessage(final int statusCode, final Header[] headers, final String responseBody) { + public void onSuccess(int statusCode, Header[] headers, byte[] responseBody) { + final int _statusCode = statusCode; + final Header[] _headers = headers; + final byte[] _responseBody = responseBody; + if (statusCode != HttpStatus.SC_NO_CONTENT) { new Thread(new Runnable() { @Override public void run() { try { - Object jsonResponse = parseResponse(responseBody); - sendMessage(obtainMessage(SUCCESS_JSON_MESSAGE, new Object[]{statusCode, headers, jsonResponse})); + Object jsonResponse = parseResponse(_responseBody); + sendMessage(obtainMessage(SUCCESS_JSON_MESSAGE, new Object[]{_statusCode, _headers, jsonResponse})); } catch (JSONException e) { - sendFailureMessage(statusCode, headers, e, responseBody); + // have to do this via sendFailureMessage so that onFailure will finally be called on the main / UI thread + sendFailureMessage(_statusCode, _headers, _responseBody, e); } } }).start(); } else { - sendMessage(obtainMessage(SUCCESS_JSON_MESSAGE, new Object[]{statusCode, headers, new JSONObject()})); + // already on the main / UI thread so lets just call onSuccess + onSuccess( statusCode, new JSONObject()); } } - // // Pre-processing of messages (in original calling thread, typically the UI thread) // @Override protected void handleMessage(Message msg) { - switch (msg.what) { + Object[] response; + switch(msg.what){ case SUCCESS_JSON_MESSAGE: - Object[] response = (Object[]) msg.obj; + response = (Object[]) msg.obj; handleSuccessJsonMessage((Integer) response[0], (Header[]) response[1], response[2]); break; default: @@ -183,57 +107,29 @@ protected void handleMessage(Message msg) { } protected void handleSuccessJsonMessage(int statusCode, Header[] headers, Object jsonResponse) { - if (jsonResponse instanceof JSONObject) { - onSuccess(statusCode, headers, (JSONObject) jsonResponse); - } else if (jsonResponse instanceof JSONArray) { - onSuccess(statusCode, headers, (JSONArray) jsonResponse); - } else if (jsonResponse instanceof String) { - onSuccess(statusCode, headers, (String) jsonResponse); - } else { - onFailure(new JSONException("Unexpected type " + jsonResponse.getClass().getName()), (JSONObject) null); - } + onSuccess(statusCode, jsonResponse); } - protected Object parseResponse(String responseBody) throws JSONException { - if (null == responseBody) - return null; + protected Object parseResponse(byte[] responseBody) throws JSONException { Object result = null; - //trim the string to prevent start with blank, and test if the string is valid JSON, because the parser don't do this :(. If Json is not valid this will return null - responseBody = responseBody.trim(); - if (responseBody.startsWith("{") || responseBody.startsWith("[")) { - result = new JSONTokener(responseBody).nextValue(); + String responseBodyText = null; + try { + responseBodyText = new String(responseBody, "UTF-8"); + } catch (UnsupportedEncodingException e) { + throw new JSONException("Unable to convert response to UTF-8 string"); + } + + // trim the string to prevent start with blank, and test if the string + // is valid JSON, because the parser don't do this :(. If Json is not + // valid this will return null + responseBodyText = responseBodyText.trim(); + if (responseBodyText.startsWith("{") || responseBodyText.startsWith("[")) { + result = new JSONTokener(responseBodyText).nextValue(); } if (result == null) { - result = responseBody; + result = responseBodyText; } return result; } - @Override - protected void handleFailureMessage(final int statusCode, final Header[] headers, final Throwable e, final String responseBody) { - new Thread(new Runnable() { - @Override - public void run() { - try { - if (responseBody != null) { - Object jsonResponse = parseResponse(responseBody); - if (jsonResponse instanceof JSONObject) { - onFailure(statusCode, headers, e, (JSONObject) jsonResponse); - } else if (jsonResponse instanceof JSONArray) { - onFailure(statusCode, headers, e, (JSONArray) jsonResponse); - } else if (jsonResponse instanceof String) { - onFailure(statusCode, headers, e, (String) jsonResponse); - } else { - onFailure(statusCode, headers, e, responseBody); - } - } else { - onFailure(e, ""); - } - } catch (JSONException ex) { - onFailure(statusCode, headers, e, responseBody); - } - } - }).start(); - - } } diff --git a/library/src/com/loopj/android/http/SyncHttpClient.java b/library/src/com/loopj/android/http/SyncHttpClient.java index 879abb1f5..deb82abcb 100644 --- a/library/src/com/loopj/android/http/SyncHttpClient.java +++ b/library/src/com/loopj/android/http/SyncHttpClient.java @@ -1,59 +1,14 @@ package com.loopj.android.http; import android.content.Context; -import android.os.Message; - -import org.apache.http.HttpEntity; -import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpUriRequest; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.protocol.HttpContext; -public abstract class SyncHttpClient extends AsyncHttpClient { - private int responseCode; - /* - * as this is a synchronous request this is just a helping mechanism to pass - * the result back to this method. Therefore the result object has to be a - * field to be accessible - */ - protected String result; - protected AsyncHttpResponseHandler responseHandler = new AsyncHttpResponseHandler() { +import java.io.IOException; - @Override - protected void sendResponseMessage(HttpResponse response) { - responseCode = response.getStatusLine().getStatusCode(); - super.sendResponseMessage(response); - } +public class SyncHttpClient extends AsyncHttpClient { - @Override - protected void sendMessage(Message msg) { - /* - * Dont use the handler and send it directly to the analysis - * (because its all the same thread) - */ - handleMessage(msg); - } - - @Override - public void onSuccess(String content) { - result = content; - } - - @Override - public void onFailure(Throwable error, String content) { - result = onRequestFailed(error, content); - } - }; - - /** - * @return the response code for the last request, might be usefull - * sometimes - */ - public int getResponseCode() { - return responseCode; - } - - // Private stuff @Override protected void sendRequest(DefaultHttpClient client, HttpContext httpContext, HttpUriRequest uriRequest, @@ -63,67 +18,11 @@ protected void sendRequest(DefaultHttpClient client, uriRequest.addHeader("Content-Type", contentType); } + responseHandler.setForceSynchronous(true); + /* * will execute the request directly - */ - new AsyncHttpRequest(client, httpContext, uriRequest, responseHandler) - .run(); - } - - public abstract String onRequestFailed(Throwable error, String content); - - public void delete(String url, RequestParams queryParams, - AsyncHttpResponseHandler responseHandler) { - delete(getUrlWithQueryString(isUrlEncodingEnabled(), url, queryParams), responseHandler); - } - - public String get(String url, RequestParams params) { - this.get(url, params, responseHandler); - /* - * the response handler will have set the result when this line is - * reached - */ - return result; + */ + new AsyncHttpRequest(client, httpContext, uriRequest, responseHandler).run(); } - - public String get(String url) { - this.get(url, null, responseHandler); - return result; - } - - public String put(String url, RequestParams params) { - this.put(url, params, responseHandler); - return result; - } - - public String put(String url) { - this.put(url, null, responseHandler); - return result; - } - - public String post(String url, HttpEntity entity) { - this.post(null, url, entity, null, responseHandler); - return result; - } - - public String post(String url, RequestParams params) { - this.post(url, params, responseHandler); - return result; - } - - public String post(String url) { - this.post(url, null, responseHandler); - return result; - } - - public String delete(String url, RequestParams params) { - this.delete(url, params, responseHandler); - return result; - } - - public String delete(String url) { - this.delete(url, null, responseHandler); - return result; - } - } diff --git a/library/src/com/loopj/android/http/TextHttpResponseHandler.java b/library/src/com/loopj/android/http/TextHttpResponseHandler.java new file mode 100644 index 000000000..e0818fea7 --- /dev/null +++ b/library/src/com/loopj/android/http/TextHttpResponseHandler.java @@ -0,0 +1,129 @@ +package com.loopj.android.http; + +import org.apache.http.Header; + +import java.io.UnsupportedEncodingException; + +/** + * Used to intercept and handle the responses from requests made using + * {@link AsyncHttpClient}. The {@link #onSuccess(String)} method is + * designed to be anonymously overridden with your own response handling code. + *

    + * Additionally, you can override the {@link #onFailure(String, Throwable)}, + * {@link #onStart()}, and {@link #onFinish()} methods as required. + *

    + * For example: + *

    + *

    + * AsyncHttpClient client = new AsyncHttpClient();
    + * client.get("http://www.google.com/", new AsyncHttpResponseHandler() {
    + *     @Override
    + *     public void onStart() {
    + *         // Initiated the request
    + *     }
    + *
    + *     @Override
    + *     public void onSuccess(String responseBody ) {
    + *         // Successfully got a response
    + *     }
    + * 
    + *     @Override
    + *     public void onFailure(String responseBody, Throwable e) {
    + *         // Response failed :(
    + *     }
    + *
    + *     @Override
    + *     public void onFinish() {
    + *         // Completed the request (either success or failure)
    + *     }
    + * });
    + * 
    + */ +public class TextHttpResponseHandler extends AsyncHttpResponseHandler { + + private String _encoding; + /** + * Creates a new TextHttpResponseHandler + */ + + public TextHttpResponseHandler() + { + this("UTF-8"); + } + + public TextHttpResponseHandler(String encoding) { + super(); + _encoding = encoding; + } + // + // Callbacks to be overridden, typically anonymously + // + + + /** + * Fired when a request returns successfully, override to handle in your own + * code + * + * @param responseBody the body of the HTTP response from the server + */ + public void onSuccess(String responseBody) { + } + + /** + * Fired when a request returns successfully, override to handle in your own + * code + * + * @param statusCode the status code of the response + * @param headers HTTP response headers + * @param responseBody the body of the HTTP response from the server + */ + public void onSuccess(int statusCode, Header[] headers, String responseBody) { + onSuccess( responseBody ); + } + + /** + * Fired when a request fails to complete, override to handle in your own + * code + * + * @param responseBody the response body, if any + * @param error the underlying cause of the failure + */ + public void onFailure(String responseBody, Throwable error) { + } + + /** + * Fired when a request fails to complete, override to handle in your own + * code + * + * @param statusCode the status code of the response + * @param headers HTTP response headers + * @param responseBody the response body, if any + * @param error the underlying cause of the failure + */ + public void onFailure(int statusCode, Header[] headers, String responseBody, Throwable error) { + onFailure( responseBody, error ); + } + + // + // Pre-processing of messages (in original calling thread, typically the UI thread) + // + + @Override + protected void handleSuccessMessage(int statusCode, Header[] headers, byte[] responseBody) { + try { + onSuccess(statusCode, headers, new String(responseBody, _encoding)); + } catch (UnsupportedEncodingException e) { + onFailure(0, headers, (String) null, e); + } + } + + @Override + protected void handleFailureMessage(int statusCode, Header[] headers, byte[] responseBody, Throwable error) { + try { + onFailure(statusCode, headers, new String(responseBody, _encoding), error); + } catch (UnsupportedEncodingException e) { + onFailure(0, headers, (String) null, e); + } + } + +} From cda3f4a16d916e39ab2a6a91184e407d7e536911 Mon Sep 17 00:00:00 2001 From: Peter Edwards Date: Thu, 17 Oct 2013 14:54:08 +0200 Subject: [PATCH 114/613] Fix minor formatting issue --- .../http/AsyncHttpResponseHandler.java | 44 +++++++++---------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/library/src/com/loopj/android/http/AsyncHttpResponseHandler.java b/library/src/com/loopj/android/http/AsyncHttpResponseHandler.java index 231987876..78cf504ba 100644 --- a/library/src/com/loopj/android/http/AsyncHttpResponseHandler.java +++ b/library/src/com/loopj/android/http/AsyncHttpResponseHandler.java @@ -233,28 +233,28 @@ protected void handleMessage(Message msg) { Object[] response; switch (msg.what) { - case SUCCESS_MESSAGE: - response = (Object[]) msg.obj; - handleSuccessMessage((Integer) response[0], (Header[]) response[1], (byte[]) response[2]); - break; - case FAILURE_MESSAGE: - response = (Object[]) msg.obj; - handleFailureMessage((Integer) response[0], (Header[]) response[1], (byte[]) response[2], (Throwable) response[3]); - break; - case START_MESSAGE: - onStart(); - break; - case FINISH_MESSAGE: - onFinish(); - break; - case PROGRESS_MESSAGE: - response = (Object[]) msg.obj; - onProgress((Integer) response[0], (Integer) response[1]); - break; - case RETRY_MESSAGE: - handleRetryMessage(); - break; - } + case SUCCESS_MESSAGE: + response = (Object[]) msg.obj; + handleSuccessMessage((Integer) response[0], (Header[]) response[1], (byte[]) response[2]); + break; + case FAILURE_MESSAGE: + response = (Object[]) msg.obj; + handleFailureMessage((Integer) response[0], (Header[]) response[1], (byte[]) response[2], (Throwable) response[3]); + break; + case START_MESSAGE: + onStart(); + break; + case FINISH_MESSAGE: + onFinish(); + break; + case PROGRESS_MESSAGE: + response = (Object[]) msg.obj; + onProgress((Integer) response[0], (Integer) response[1]); + break; + case RETRY_MESSAGE: + handleRetryMessage(); + break; + } } protected void sendMessage(Message msg) { From 6e6569fac9e26f06748131ed89fed993f6dffc68 Mon Sep 17 00:00:00 2001 From: Peter Edwards Date: Thu, 17 Oct 2013 14:58:43 +0200 Subject: [PATCH 115/613] Added another cancellation check --- .../loopj/android/http/AsyncHttpResponseHandler.java | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/library/src/com/loopj/android/http/AsyncHttpResponseHandler.java b/library/src/com/loopj/android/http/AsyncHttpResponseHandler.java index 78cf504ba..2410d0ca9 100644 --- a/library/src/com/loopj/android/http/AsyncHttpResponseHandler.java +++ b/library/src/com/loopj/android/http/AsyncHttpResponseHandler.java @@ -322,10 +322,13 @@ void sendResponseMessage(HttpResponse response) throws IOException { StatusLine status = response.getStatusLine(); byte[] responseBody = null; responseBody = getResponseData(response.getEntity()); - if (status.getStatusCode() >= 300) { - sendFailureMessage(status.getStatusCode(), response.getAllHeaders(), responseBody, new HttpResponseException(status.getStatusCode(), status.getReasonPhrase())); - } else { - sendSuccessMessage(status.getStatusCode(), response.getAllHeaders(), responseBody); + // additional cancellation check as getResponseData() can take non-zero time to process + if (!Thread.currentThread().isInterrupted()) { + if (status.getStatusCode() >= 300) { + sendFailureMessage(status.getStatusCode(), response.getAllHeaders(), responseBody, new HttpResponseException(status.getStatusCode(), status.getReasonPhrase())); + } else { + sendSuccessMessage(status.getStatusCode(), response.getAllHeaders(), responseBody); + } } } } From b20f6f54173c317953e2e7a7ebf5e12fd1cbfea7 Mon Sep 17 00:00:00 2001 From: Peter Edwards Date: Thu, 17 Oct 2013 15:41:33 +0200 Subject: [PATCH 116/613] Fix override issue in sample app --- .../java/com/loopj/android/http/sample/MainActivity.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/sample/src/main/java/com/loopj/android/http/sample/MainActivity.java b/sample/src/main/java/com/loopj/android/http/sample/MainActivity.java index f5096ae2f..853e610bb 100644 --- a/sample/src/main/java/com/loopj/android/http/sample/MainActivity.java +++ b/sample/src/main/java/com/loopj/android/http/sample/MainActivity.java @@ -12,6 +12,7 @@ import com.loopj.android.http.AsyncHttpClient; import com.loopj.android.http.AsyncHttpResponseHandler; +import com.loopj.android.http.TextHttpResponseHandler; import org.apache.http.Header; @@ -59,7 +60,7 @@ public void onClick(View v) { } private void startRequest() { - aclient.get(this, getURLString(), new AsyncHttpResponseHandler() { + aclient.get(this, getURLString(), new TextHttpResponseHandler() { @Override public void onSuccess(int statusCode, Header[] headers, String content) { @@ -71,7 +72,7 @@ public void onSuccess(int statusCode, Header[] headers, String content) { } @Override - public void onFailure(int statusCode, Header[] headers, Throwable error, String content) { + public void onFailure(int statusCode, Header[] headers, String content, Throwable error) { setStatusMessage("Failed", Color.parseColor("#99FF0000")); printThrowable(error); printHeaders(headers); From 3dfc05e533f88a47b7260146964db1c5d1ddb221 Mon Sep 17 00:00:00 2001 From: Peter Edwards Date: Thu, 17 Oct 2013 15:53:37 +0200 Subject: [PATCH 117/613] removed redundant methods --- .../android/http/AsyncHttpResponseHandler.java | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/library/src/com/loopj/android/http/AsyncHttpResponseHandler.java b/library/src/com/loopj/android/http/AsyncHttpResponseHandler.java index 2410d0ca9..783664b21 100644 --- a/library/src/com/loopj/android/http/AsyncHttpResponseHandler.java +++ b/library/src/com/loopj/android/http/AsyncHttpResponseHandler.java @@ -105,10 +105,6 @@ public void handleMessage(Message msg) } } - public Boolean getForceSynchronous() { - return (forceSynchronous); - } - public void setForceSynchronous(Boolean value) { forceSynchronous = value; } @@ -220,14 +216,6 @@ protected void handleFailureMessage(int statusCode, Header[] headers, byte[] res onFailure(statusCode, headers, responseBody, error); } - protected void handleProgressMessage(int current, int total) { - onProgress(current, total); - } - - protected void handleRetryMessage() { - onRetry(); - } - // Methods which emulate android's Handler and Message methods protected void handleMessage(Message msg) { Object[] response; @@ -252,7 +240,7 @@ protected void handleMessage(Message msg) { onProgress((Integer) response[0], (Integer) response[1]); break; case RETRY_MESSAGE: - handleRetryMessage(); + onRetry(); break; } } From f279cb53a309984ef084d0dc7894eba857174372 Mon Sep 17 00:00:00 2001 From: Peter Edwards Date: Thu, 17 Oct 2013 16:04:01 +0200 Subject: [PATCH 118/613] Fixed examples --- examples/CookieVideoView.java | 2 ++ examples/ExampleUsage.java | 5 ++++- examples/TestCaseExampleUsage.java | 6 +++--- examples/TwitterRestClient.java | 4 ++++ examples/TwitterRestClientUsage.java | 10 ++++++++-- 5 files changed, 21 insertions(+), 6 deletions(-) diff --git a/examples/CookieVideoView.java b/examples/CookieVideoView.java index 998daa1fa..82bfd210f 100644 --- a/examples/CookieVideoView.java +++ b/examples/CookieVideoView.java @@ -39,6 +39,8 @@ import android.widget.MediaController; import android.widget.MediaController.MediaPlayerControl; +import com.loopj.android.http.PersistentCookieStore; + import java.io.IOException; import java.util.HashMap; import java.util.List; diff --git a/examples/ExampleUsage.java b/examples/ExampleUsage.java index b5c4fc6f7..7cf943aee 100644 --- a/examples/ExampleUsage.java +++ b/examples/ExampleUsage.java @@ -1,8 +1,11 @@ +import com.loopj.android.http.AsyncHttpClient; +import com.loopj.android.http.TextHttpResponseHandler; + public class ExampleUsage { public static void makeRequest() { AsyncHttpClient client = new AsyncHttpClient(); - client.get("http://www.google.com/", new AsyncHttpResponseHandler() { + client.get("http://www.google.com/", new TextHttpResponseHandler() { @Override public void onSuccess(String response) { System.out.println(response); diff --git a/examples/TestCaseExampleUsage.java b/examples/TestCaseExampleUsage.java index ce9498179..4f31c5247 100644 --- a/examples/TestCaseExampleUsage.java +++ b/examples/TestCaseExampleUsage.java @@ -2,7 +2,7 @@ import java.util.concurrent.TimeUnit; import com.loopj.android.http.AsyncHttpClient; -import com.loopj.android.http.AsyncHttpResponseHandler; +import com.loopj.android.http.TextHttpResponseHandler; import android.test.InstrumentationTestCase; import android.util.Log; @@ -23,7 +23,7 @@ public void run() { AsyncHttpClient client = new AsyncHttpClient(); - client.get("http://www.google.com/", new AsyncHttpResponseHandler() + client.get("http://www.google.com/", new TextHttpResponseHandler() { @Override public void onStart() @@ -39,7 +39,7 @@ public void onSuccess(String response) } @Override - public void onFailure(Throwable error, String content) + public void onFailure(String content, Throwable error) { Log.e(TAG , "onFailure error : " + error.toString() + "content : " + content); } diff --git a/examples/TwitterRestClient.java b/examples/TwitterRestClient.java index 395273df0..f2b6c97b3 100644 --- a/examples/TwitterRestClient.java +++ b/examples/TwitterRestClient.java @@ -1,5 +1,9 @@ // Static wrapper library around AsyncHttpClient +import com.loopj.android.http.AsyncHttpClient; +import com.loopj.android.http.AsyncHttpResponseHandler; +import com.loopj.android.http.RequestParams; + public class TwitterRestClient { private static final String BASE_URL = "http://api.twitter.com/1/"; diff --git a/examples/TwitterRestClientUsage.java b/examples/TwitterRestClientUsage.java index 297bfecc4..c6fe157f7 100644 --- a/examples/TwitterRestClientUsage.java +++ b/examples/TwitterRestClientUsage.java @@ -1,10 +1,16 @@ +import com.loopj.android.http.*; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + class TwitterRestClientUsage { public void getPublicTimeline() { TwitterRestClient.get("statuses/public_timeline.json", null, new JsonHttpResponseHandler() { @Override - public void onSuccess(JSONArray timeline) { + public void onSuccess(int statusCode, Object timeline) { try { - JSONObject firstEvent = (JSONObject) timeline.get(0); + JSONObject firstEvent = (JSONObject) ((JSONArray)timeline).get(0); String tweetText = firstEvent.getString("text"); // Do something with the response From ac39f0d1e352ff566fd27463fad23c1f16933e64 Mon Sep 17 00:00:00 2001 From: Vince Mi Date: Thu, 17 Oct 2013 11:49:44 -0700 Subject: [PATCH 119/613] Correct POM_NAME --- library/gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/gradle.properties b/library/gradle.properties index d6aa0de5a..96e35d668 100644 --- a/library/gradle.properties +++ b/library/gradle.properties @@ -1,3 +1,3 @@ -POM_NAME=ActionBar-PullToRefresh Library +POM_NAME=android-async-http Library POM_ARTIFACT_ID=android-async-http POM_PACKAGING=aar From 2162987da4869b299f9d40d6bcec8f6d073d544f Mon Sep 17 00:00:00 2001 From: xAnubiSx Date: Thu, 17 Oct 2013 16:26:18 -0300 Subject: [PATCH 120/613] android:targetSdkVersion is unknown attribute and won't compile in eclipse --- library/AndroidManifest.xml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/library/AndroidManifest.xml b/library/AndroidManifest.xml index 1ad7c0fcf..dede71fe3 100644 --- a/library/AndroidManifest.xml +++ b/library/AndroidManifest.xml @@ -5,8 +5,7 @@ android:versionCode="144"> + android:minSdkVersion="3" /> From a65c7d64a8ea21a5bb7e5ce97c0e4a6847452b3b Mon Sep 17 00:00:00 2001 From: Peter Edwards Date: Fri, 18 Oct 2013 09:14:10 +0200 Subject: [PATCH 121/613] Clean rebase for proposed changes --- .../loopj/android/http/AsyncHttpClient.java | 5 +- .../loopj/android/http/AsyncHttpRequest.java | 130 +++++---- .../http/AsyncHttpResponseHandler.java | 274 ++++++++++-------- .../http/BinaryHttpResponseHandler.java | 96 +++++- .../http/FileAsyncHttpResponseHandler.java | 138 ++++++--- .../android/http/JsonHttpResponseHandler.java | 192 +++++++++--- .../loopj/android/http/SyncHttpClient.java | 113 +++++++- .../android/http/TextHttpResponseHandler.java | 129 --------- 8 files changed, 663 insertions(+), 414 deletions(-) delete mode 100644 library/src/com/loopj/android/http/TextHttpResponseHandler.java diff --git a/library/src/com/loopj/android/http/AsyncHttpClient.java b/library/src/com/loopj/android/http/AsyncHttpClient.java index 5db622edc..2bfcabc4e 100644 --- a/library/src/com/loopj/android/http/AsyncHttpClient.java +++ b/library/src/com/loopj/android/http/AsyncHttpClient.java @@ -86,7 +86,7 @@ *

     

    *
      * AsyncHttpClient client = new AsyncHttpClient();
    - * client.get("http://www.google.com/", new TextHttpResponseHandler() {
    + * client.get("http://www.google.com/", new AsyncHttpResponseHandler() {
      *     @Override
      *     public void onSuccess(String response) {
      *         System.out.println(response);
    @@ -199,6 +199,7 @@ private static SchemeRegistry getDefaultSchemeRegistry(boolean fixNoHttpResponse
          * @param schemeRegistry SchemeRegistry to be used
          */
         public AsyncHttpClient(SchemeRegistry schemeRegistry) {
    +
             BasicHttpParams httpParams = new BasicHttpParams();
     
             ConnManagerParams.setTimeout(httpParams, socketTimeout);
    @@ -888,7 +889,7 @@ private HttpEntity paramsToEntity(RequestParams params, AsyncHttpResponseHandler
                 }
             } catch (Throwable t) {
                 if (responseHandler != null)
    -                responseHandler.sendFailureMessage(0, null, null, t);
    +                responseHandler.sendFailureMessage(0, null, t, (String) null);
                 else
                     t.printStackTrace();
             }
    diff --git a/library/src/com/loopj/android/http/AsyncHttpRequest.java b/library/src/com/loopj/android/http/AsyncHttpRequest.java
    index cf34bc7ff..339c6c0f5 100644
    --- a/library/src/com/loopj/android/http/AsyncHttpRequest.java
    +++ b/library/src/com/loopj/android/http/AsyncHttpRequest.java
    @@ -38,6 +38,7 @@ class AsyncHttpRequest implements Runnable {
         private final HttpContext context;
         private final HttpUriRequest request;
         private final AsyncHttpResponseHandler responseHandler;
    +    private boolean isBinaryRequest;
         private int executionCount;
     
         public AsyncHttpRequest(AbstractHttpClient client, HttpContext context, HttpUriRequest request, AsyncHttpResponseHandler responseHandler) {
    @@ -45,83 +46,110 @@ public AsyncHttpRequest(AbstractHttpClient client, HttpContext context, HttpUriR
             this.context = context;
             this.request = request;
             this.responseHandler = responseHandler;
    +        if (responseHandler instanceof BinaryHttpResponseHandler) {
    +            this.isBinaryRequest = true;
    +        }
         }
     
         @Override
         public void run() {
    -        if (responseHandler != null) {
    -            responseHandler.sendStartMessage();
    -        }
    -
             try {
    +            if (responseHandler != null) {
    +                responseHandler.sendStartMessage();
    +            }
    +
                 makeRequestWithRetries();
    +
    +            if (responseHandler != null) {
    +                responseHandler.sendFinishMessage();
    +            }
             } catch (IOException e) {
                 if (responseHandler != null) {
    -                responseHandler.sendFailureMessage(0, null, null, e);
    +                responseHandler.sendFinishMessage();
    +                if (this.isBinaryRequest) {
    +                    responseHandler.sendFailureMessage(e, (byte[]) null);
    +                } else {
    +                    responseHandler.sendFailureMessage(e, (String) null);
    +                }
                 }
             }
    -        
    -        if (responseHandler != null) {
    -            responseHandler.sendFinishMessage();
    -        }
         }
     
    -    private void makeRequest() throws IOException {
    +    private void makeRequest() throws IOException, InterruptedException {
             if (!Thread.currentThread().isInterrupted()) {
    -            // Fixes #115
    -            if (request.getURI().getScheme() == null) {
    -                // subclass of IOException so processed in the caller
    -                throw new MalformedURLException("No valid URI scheme was provided");
    -            }
    -
    -            HttpResponse response = client.execute(request, context);
    -
    -            if (!Thread.currentThread().isInterrupted()) {
    -                if (responseHandler != null) {
    -                    responseHandler.sendResponseMessage(response);
    +            try {
    +                // Fixes #115
    +                if (request.getURI().getScheme() == null)
    +                    throw new MalformedURLException("No valid URI scheme was provided");
    +                HttpResponse response = client.execute(request, context);
    +                if (!Thread.currentThread().isInterrupted()) {
    +                    if (responseHandler != null) {
    +                        responseHandler.sendResponseMessage(response);
    +                    }
    +                } else {
    +                    throw new InterruptedException("makeRequest was interrupted");
    +                }
    +            } catch (IOException e) {
    +                if (!Thread.currentThread().isInterrupted()) {
    +                    throw e;
                     }
                 }
             }
         }
     
    -    private void makeRequestWithRetries() throws IOException {
    +    private void makeRequestWithRetries() throws ConnectException {
             // This is an additional layer of retry logic lifted from droid-fu
             // See: https://github.com/kaeppler/droid-fu/blob/master/src/main/java/com/github/droidfu/http/BetterHttpRequestBase.java
             boolean retry = true;
             IOException cause = null;
             HttpRequestRetryHandler retryHandler = client.getHttpRequestRetryHandler();
    -        try
    -        {
    -            while (retry) {
    -                try {
    -                    makeRequest();
    -                    return;
    -                } catch (UnknownHostException e) {
    -                    // switching between WI-FI and mobile data networks can cause a retry which then results in an UnknownHostException
    -                    // while the WI-FI is initialising. The retry logic will be invoked here, if this is NOT the first retry
    -                    // (to assist in genuine cases of unknown host) which seems better than outright failure
    -                    cause = new IOException("UnknownHostException exception: " + e.getMessage());
    -                    retry = (executionCount > 0) && retryHandler.retryRequest(cause, ++executionCount, context);
    -                } catch (NullPointerException e) {
    -                    // there's a bug in HttpClient 4.0.x that on some occasions causes
    -                    // DefaultRequestExecutor to throw an NPE, see
    -                    // http://code.google.com/p/android/issues/detail?id=5255
    -                    cause = new IOException("NPE in HttpClient: " + e.getMessage());
    -                    retry = retryHandler.retryRequest(cause, ++executionCount, context);
    -                } catch (IOException e) {
    -                    cause = e;
    -                    retry = retryHandler.retryRequest(cause, ++executionCount, context);
    +        while (retry) {
    +            try {
    +                makeRequest();
    +                return;
    +            } catch (ClientProtocolException e) {
    +                if (responseHandler != null) {
    +                    responseHandler.sendFailureMessage(e, "cannot repeat the request");
    +                }
    +                return;
    +            } catch (UnknownHostException e) {
    +                if (responseHandler != null) {
    +                    responseHandler.sendFailureMessage(e, "can't resolve host");
                     }
    -                if(retry && (responseHandler != null)) {
    -                    responseHandler.sendRetryMessage();
    +                return;
    +            } catch (ConnectTimeoutException e) {
    +                if (responseHandler != null) {
    +                    responseHandler.sendFailureMessage(e, "connection timed out");
    +                }
    +            } catch (SocketException e) {
    +                // Added to detect host unreachable
    +                if (responseHandler != null) {
    +                    responseHandler.sendFailureMessage(e, "can't resolve host");
                     }
    +                return;
    +            } catch (SocketTimeoutException e) {
    +                if (responseHandler != null) {
    +                    responseHandler.sendFailureMessage(e, "socket time out");
    +                }
    +                return;
    +            } catch (IOException e) {
    +                cause = e;
    +                retry = retryHandler.retryRequest(cause, ++executionCount, context);
    +            } catch (NullPointerException e) {
    +                // there's a bug in HttpClient 4.0.x that on some occasions causes
    +                // DefaultRequestExecutor to throw an NPE, see
    +                // http://code.google.com/p/android/issues/detail?id=5255
    +                cause = new IOException("NPE in HttpClient" + e.getMessage());
    +                retry = retryHandler.retryRequest(cause, ++executionCount, context);
    +            } catch (InterruptedException e) {
    +                cause = new IOException("Request was interrupted while executing");
    +                retry = retryHandler.retryRequest(cause, ++executionCount, context);
                 }
    -        } catch (Exception e) {
    -            // catch anything else to ensure failure message is propagated
    -            cause = new IOException("Unhandled exception: " + e.getMessage());
             }
    -        
    -        // cleaned up to throw IOException
    -        throw(cause);
    +
    +        // no retries left, crap out with exception
    +        ConnectException ex = new ConnectException();
    +        ex.initCause(cause);
    +        throw ex;
         }
     }
    diff --git a/library/src/com/loopj/android/http/AsyncHttpResponseHandler.java b/library/src/com/loopj/android/http/AsyncHttpResponseHandler.java
    index 783664b21..10669285b 100644
    --- a/library/src/com/loopj/android/http/AsyncHttpResponseHandler.java
    +++ b/library/src/com/loopj/android/http/AsyncHttpResponseHandler.java
    @@ -27,18 +27,17 @@
     import org.apache.http.HttpResponse;
     import org.apache.http.StatusLine;
     import org.apache.http.client.HttpResponseException;
    -import org.apache.http.util.ByteArrayBuffer;
    +import org.apache.http.entity.BufferedHttpEntity;
    +import org.apache.http.util.EntityUtils;
     
     import java.io.IOException;
    -import java.io.InputStream;
    -import java.lang.ref.WeakReference;
     
     /**
      * Used to intercept and handle the responses from requests made using
    - * {@link AsyncHttpClient}. The {@link #onSuccess(int statusCode, Header[] headers, byte[] responseBody)} method is
    + * {@link AsyncHttpClient}. The {@link #onSuccess(String)} method is
      * designed to be anonymously overridden with your own response handling code.
      * 

     

    - * Additionally, you can override the {@link #onFailure(int statusCode, Header[] headers, byte[] responseBody, Throwable error)}, + * Additionally, you can override the {@link #onFailure(Throwable, String)}, * {@link #onStart()}, and {@link #onFinish()} methods as required. *

     

    * For example: @@ -52,12 +51,12 @@ * } * * @Override - * public void onSuccess(int statusCode, Header[] headers, byte[] responseBody) { + * public void onSuccess(String response) { * // Successfully got a response * } - * + * * @Override - * public void onFailure(int statusCode, Header[] headers, byte[] responseBody, Throwable error) { + * public void onFailure(Throwable e, String response) { * // Response failed :( * } * @@ -74,39 +73,22 @@ public class AsyncHttpResponseHandler { protected static final int START_MESSAGE = 2; protected static final int FINISH_MESSAGE = 3; protected static final int PROGRESS_MESSAGE = 4; - protected static final int RETRY_MESSAGE = 5; - - // ensure this is always at least 1 more than any message value - // used by derived classes to chain messages - protected static final int LAST_MESSAGE = 10; - - - protected static final int BUFFER_SIZE = 4096; private Handler handler; + private String responseCharset = "UTF-8"; - private Boolean forceSynchronous = false; - - // avoid leaks by using a non-anonymous handler class - // with a weak reference - static class ResponderHandler extends Handler { - private final WeakReference mResponder; - - ResponderHandler(AsyncHttpResponseHandler service) { - mResponder = new WeakReference(service); - } - @Override - public void handleMessage(Message msg) - { - AsyncHttpResponseHandler service = mResponder.get(); - if (service != null) { - service.handleMessage(msg); - } - } + /** + * Sets the charset for the response string. If not set, the default is UTF-8. + * + * @param charset to be used for the response string. + * @see Charset + */ + public void setCharset(final String charset) { + this.responseCharset = charset; } - public void setForceSynchronous(Boolean value) { - forceSynchronous = value; + public String getCharset() { + return this.responseCharset; } /** @@ -115,7 +97,12 @@ public void setForceSynchronous(Boolean value) { public AsyncHttpResponseHandler() { // Set up a handler to post events back to the correct thread if possible if (Looper.myLooper() != null) { - handler = new ResponderHandler(this); + handler = new Handler() { + @Override + public void handleMessage(Message msg) { + AsyncHttpResponseHandler.this.handleMessage(msg); + } + }; } } @@ -124,6 +111,15 @@ public AsyncHttpResponseHandler() { // Callbacks to be overridden, typically anonymously // + /** + * Fired when the request progress, override to handle in your own code + * + * @param bytesWritten offset from start of file + * @param totalSize total size of file + */ + public void onProgress(int bytesWritten, int totalSize) { + } + /** * Fired when the request is started, override to handle in your own code */ @@ -137,55 +133,109 @@ public void onFinish() { } /** - * Fired when a request returns successfully, override to handle in your own - * code - * - * @param statusCode the status code of the response - * @param headers HTTP response headers - * @param responseBody the body of the HTTP response from the server + * Fired when a request returns successfully, override to handle in your own code + * + * @param content the body of the HTTP response from the server */ - public void onSuccess(int statusCode, Header[] headers, byte[] responseBody) { + public void onSuccess(String content) { } + /** + * Fired when a request returns successfully, override to handle in your own code + * + * @param statusCode the status code of the response + * @param headers the headers of the HTTP response + * @param content the body of the HTTP response from the server + */ + public void onSuccess(int statusCode, Header[] headers, String content) { + onSuccess(statusCode, content); + } /** * Fired when a request returns successfully, override to handle in your own code * * @param statusCode the status code of the response - * @param headers HTTP response headers - * @param responseBody the response body, if any + * @param content the body of the HTTP response from the server + */ + public void onSuccess(int statusCode, String content) { + onSuccess(content); + } + + /** + * Fired when a request fails to complete, override to handle in your own code + * * @param error the underlying cause of the failure + * @deprecated use {@link #onFailure(Throwable, String)} */ - public void onFailure(int statusCode, Header[] headers, byte[] responseBody, Throwable error) { + @Deprecated + public void onFailure(Throwable error) { } /** - * Fired when a bytes are received, override to handle in your own code - * - * @param current the current number of bytes loaded from the response - * @param total the total number of bytes in the response + * Fired when a request fails to complete, override to handle in your own code + * + * @param error the underlying cause of the failure + * @param content the response body, if any */ - public void onProgress(int current, int total) { + public void onFailure(Throwable error, String content) { + // By default, call the deprecated onFailure(Throwable) for compatibility + onFailure(error); } /** - * Fired when a retry occurs, override to handle in your own code - * + * Fired when a request fails to complete, override to handle in your own code + * + * @param statusCode return HTTP status code + * @param error the underlying cause of the failure + * @param content the response body, if any */ - public void onRetry() { + public void onFailure(int statusCode, Throwable error, String content) { + // By default, call the chain method onFailure(Throwable,String) + onFailure(error, content); } - + + /** + * Fired when a request fails to complete, override to handle in your own code + * + * @param statusCode return HTTP status code + * @param headers return headers, if any + * @param error the underlying cause of the failure + * @param content the response body, if any + */ + public void onFailure(int statusCode, Header[] headers, Throwable error, String content) { + // By default, call the chain method onFailure(int,Throwable,String) + onFailure(statusCode, error, content); + } + // // Pre-processing of messages (executes in background threadpool thread) // - protected void sendSuccessMessage(int statusCode, Header[] headers, byte[] responseBody) { + protected void sendProgressMessage(int bytesWritten, int totalSize) { + sendMessage(obtainMessage(PROGRESS_MESSAGE, new Object[]{bytesWritten, totalSize})); + } + + protected void sendSuccessMessage(int statusCode, Header[] headers, String responseBody) { sendMessage(obtainMessage(SUCCESS_MESSAGE, new Object[]{statusCode, headers, responseBody})); } - protected void sendFailureMessage(int statusCode, Header[] headers, byte[] responseBody, Throwable error) { - sendMessage(obtainMessage(FAILURE_MESSAGE, new Object[] { statusCode, headers, responseBody, error })); + protected void sendFailureMessage(int statusCode, Header[] headers, Throwable e, String responseBody) { + sendMessage(obtainMessage(FAILURE_MESSAGE, new Object[]{statusCode, headers, e, responseBody})); + } + + @Deprecated + protected void sendFailureMessage(Throwable e, String responseBody) { + sendMessage(obtainMessage(FAILURE_MESSAGE, new Object[]{0, null, e, responseBody})); + } + + protected void sendFailureMessage(int statusCode, Header[] headers, Throwable e, byte[] responseBody) { + sendMessage(obtainMessage(FAILURE_MESSAGE, new Object[]{statusCode, headers, e, responseBody})); + } + + @Deprecated + protected void sendFailureMessage(Throwable e, byte[] responseBody) { + sendMessage(obtainMessage(FAILURE_MESSAGE, new Object[]{0, null, e, responseBody})); } protected void sendStartMessage() { @@ -196,26 +246,20 @@ protected void sendFinishMessage() { sendMessage(obtainMessage(FINISH_MESSAGE, null)); } - protected void sendProgressMessage(int current, int total) { - sendMessage(obtainMessage(PROGRESS_MESSAGE, new Object[] { current, total })); - } - protected void sendRetryMessage() { - sendMessage(obtainMessage(RETRY_MESSAGE, null)); - } - // // Pre-processing of messages (in original calling thread, typically the UI thread) // - protected void handleSuccessMessage(int statusCode, Header[] headers, byte[] responseBody) { + protected void handleSuccessMessage(int statusCode, Header[] headers, String responseBody) { onSuccess(statusCode, headers, responseBody); } - protected void handleFailureMessage(int statusCode, Header[] headers, byte[] responseBody, Throwable error) { - onFailure(statusCode, headers, responseBody, error); + protected void handleFailureMessage(int statusCode, Header[] headers, Throwable e, String responseBody) { + onFailure(statusCode, headers, e, responseBody); } + // Methods which emulate android's Handler and Message methods protected void handleMessage(Message msg) { Object[] response; @@ -223,11 +267,11 @@ protected void handleMessage(Message msg) { switch (msg.what) { case SUCCESS_MESSAGE: response = (Object[]) msg.obj; - handleSuccessMessage((Integer) response[0], (Header[]) response[1], (byte[]) response[2]); + handleSuccessMessage((Integer) response[0], (Header[]) response[1], (String) response[2]); break; case FAILURE_MESSAGE: response = (Object[]) msg.obj; - handleFailureMessage((Integer) response[0], (Header[]) response[1], (byte[]) response[2], (Throwable) response[3]); + handleFailureMessage((Integer) response[0], (Header[]) response[1], (Throwable) response[2], (String) response[3]); break; case START_MESSAGE: onStart(); @@ -239,17 +283,14 @@ protected void handleMessage(Message msg) { response = (Object[]) msg.obj; onProgress((Integer) response[0], (Integer) response[1]); break; - case RETRY_MESSAGE: - onRetry(); - break; - } + } } protected void sendMessage(Message msg) { - if (forceSynchronous || handler == null) { - handleMessage(msg); - } else if (!Thread.currentThread().isInterrupted()) { // do not send messages if request has been cancelled + if (handler != null) { handler.sendMessage(msg); + } else { + handleMessage(msg); } } @@ -267,57 +308,36 @@ protected Message obtainMessage(int responseMessage, Object response) { return msg; } - byte[] getResponseData(HttpEntity entity) throws IOException { - byte[] responseBody = null; - if (entity != null) { - InputStream instream = entity.getContent(); - if (instream != null) { - long contentLength = entity.getContentLength(); - if (contentLength > Integer.MAX_VALUE) { - throw new IllegalArgumentException("HTTP entity too large to be buffered in memory"); - } - if (contentLength < 0) { - contentLength = BUFFER_SIZE; - } - try{ - ByteArrayBuffer buffer = new ByteArrayBuffer((int) contentLength); - try { - byte[] tmp = new byte[BUFFER_SIZE]; - int l, count = 0; - // do not send messages if request has been cancelled - while ((l = instream.read(tmp)) != -1 && !Thread.currentThread().isInterrupted()) { - count += l; - buffer.append(tmp, 0, l); - sendProgressMessage(count, (int) contentLength); - } - } finally { - instream.close(); - } - responseBody = buffer.buffer(); - } catch( OutOfMemoryError e ) { - System.gc(); - throw new IOException("File too large to fit into available memory"); - } - } - } - return (responseBody); - } - // Interface to AsyncHttpRequest - void sendResponseMessage(HttpResponse response) throws IOException { - // do not process if request has been cancelled - if (!Thread.currentThread().isInterrupted()) { - StatusLine status = response.getStatusLine(); - byte[] responseBody = null; - responseBody = getResponseData(response.getEntity()); - // additional cancellation check as getResponseData() can take non-zero time to process - if (!Thread.currentThread().isInterrupted()) { - if (status.getStatusCode() >= 300) { - sendFailureMessage(status.getStatusCode(), response.getAllHeaders(), responseBody, new HttpResponseException(status.getStatusCode(), status.getReasonPhrase())); - } else { - sendSuccessMessage(status.getStatusCode(), response.getAllHeaders(), responseBody); - } + protected void sendResponseMessage(HttpResponse response) { + if (response == null) { + sendFailureMessage(0, null, new IllegalStateException("No response"), (String) null); + return; + } + StatusLine status = response.getStatusLine(); + String responseBody = null; + try { + HttpEntity entity; + HttpEntity temp = response.getEntity(); + if (temp != null) { + entity = new BufferedHttpEntity(temp); + responseBody = EntityUtils.toString(entity, getCharset()); } + } catch (IOException e) { + try { + if (response.getEntity() != null) + response.getEntity().consumeContent(); + } catch (Throwable t) { + t.printStackTrace(); + } + sendFailureMessage(status.getStatusCode(), response.getAllHeaders(), e, (String) null); + return; + } + + if (status.getStatusCode() >= 300) { + sendFailureMessage(status.getStatusCode(), response.getAllHeaders(), new HttpResponseException(status.getStatusCode(), status.getReasonPhrase()), responseBody); + } else { + sendSuccessMessage(status.getStatusCode(), response.getAllHeaders(), responseBody); } } } diff --git a/library/src/com/loopj/android/http/BinaryHttpResponseHandler.java b/library/src/com/loopj/android/http/BinaryHttpResponseHandler.java index 9c6241051..d8e5cdda3 100644 --- a/library/src/com/loopj/android/http/BinaryHttpResponseHandler.java +++ b/library/src/com/loopj/android/http/BinaryHttpResponseHandler.java @@ -18,13 +18,18 @@ package com.loopj.android.http; -import java.io.IOException; -import java.util.regex.Pattern; +import android.os.Message; import org.apache.http.Header; +import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.StatusLine; import org.apache.http.client.HttpResponseException; +import org.apache.http.entity.BufferedHttpEntity; +import org.apache.http.util.EntityUtils; + +import java.io.IOException; +import java.util.regex.Pattern; /** * Used to intercept and handle the responses from requests made using @@ -52,9 +57,9 @@ */ public class BinaryHttpResponseHandler extends AsyncHttpResponseHandler { // Allow images by default - private static String[] mAllowedContentTypes = new String[] { - "image/jpeg", - "image/png" + private String[] mAllowedContentTypes = new String[]{ + "image/jpeg", + "image/png" }; /** @@ -98,21 +103,75 @@ public void onSuccess(int statusCode, byte[] binaryData) { onSuccess(binaryData); } + /** + * Fired when a request fails to complete, override to handle in your own code + * + * @param statusCode response HTTP statuse code + * @param headers response headers, if any + * @param error the underlying cause of the failure + * @param binaryData the response body, if any + * @deprecated + */ + @Deprecated + public void onFailure(int statusCode, Header[] headers, Throwable error, byte[] binaryData) { + // By default, call the deprecated onFailure(Throwable) for compatibility + onFailure(statusCode, error, null); + } + + + // + // Pre-processing of messages (executes in background threadpool thread) + // + + protected void sendSuccessMessage(int statusCode, byte[] responseBody) { + sendMessage(obtainMessage(SUCCESS_MESSAGE, new Object[]{statusCode, responseBody})); + } + @Override - public void onSuccess(int statusCode, Header[] headers, byte[] binaryData) { - onSuccess(statusCode, binaryData); + protected void sendFailureMessage(int statusCode, Header[] headers, Throwable e, byte[] responseBody) { + sendMessage(obtainMessage(FAILURE_MESSAGE, new Object[]{statusCode, headers, e, responseBody})); + } + + // + // Pre-processing of messages (in original calling thread, typically the UI thread) + // + + protected void handleSuccessMessage(int statusCode, byte[] responseBody) { + onSuccess(statusCode, responseBody); } + protected void handleFailureMessage(int statusCode, Header[] headers, Throwable e, byte[] responseBody) { + onFailure(statusCode, headers, e, responseBody); + } + + // Methods which emulate android's Handler and Message methods + @Override + protected void handleMessage(Message msg) { + Object[] response; + switch (msg.what) { + case SUCCESS_MESSAGE: + response = (Object[]) msg.obj; + handleSuccessMessage((Integer) response[0], (byte[]) response[1]); + break; + case FAILURE_MESSAGE: + response = (Object[]) msg.obj; + handleFailureMessage((Integer) response[0], (Header[]) response[1], (Throwable) response[2], (byte[]) response[3]); + break; + default: + super.handleMessage(msg); + break; + } + } // Interface to AsyncHttpRequest @Override - protected void sendResponseMessage(HttpResponse response) throws IOException { + protected void sendResponseMessage(HttpResponse response) { StatusLine status = response.getStatusLine(); Header[] contentTypeHeaders = response.getHeaders("Content-Type"); byte[] responseBody = null; if (contentTypeHeaders.length != 1) { //malformed/ambiguous HTTP Header, ABORT! - sendFailureMessage(status.getStatusCode(), response.getAllHeaders(), responseBody, new HttpResponseException(status.getStatusCode(), "None, or more than one, Content-Type Header found!")); + sendFailureMessage(status.getStatusCode(), response.getAllHeaders(), new HttpResponseException(status.getStatusCode(), "None, or more than one, Content-Type Header found!"), (String) null); return; } Header contentTypeHeader = contentTypeHeaders[0]; @@ -124,9 +183,24 @@ protected void sendResponseMessage(HttpResponse response) throws IOException { } if (!foundAllowedContentType) { //Content-Type not in allowed list, ABORT! - sendFailureMessage(status.getStatusCode(), response.getAllHeaders(), responseBody, new HttpResponseException(status.getStatusCode(), "Content-Type not allowed!")); + sendFailureMessage(status.getStatusCode(), response.getAllHeaders(), new HttpResponseException(status.getStatusCode(), "Content-Type not allowed!"), (String) null); return; } - super.sendResponseMessage( response ); + try { + HttpEntity entity = null; + HttpEntity temp = response.getEntity(); + if (temp != null) { + entity = new BufferedHttpEntity(temp); + } + responseBody = EntityUtils.toByteArray(entity); + } catch (IOException e) { + sendFailureMessage(status.getStatusCode(), response.getAllHeaders(), e, (byte[]) null); + } + + if (status.getStatusCode() >= 300) { + sendFailureMessage(status.getStatusCode(), response.getAllHeaders(), new HttpResponseException(status.getStatusCode(), status.getReasonPhrase()), responseBody); + } else { + sendSuccessMessage(status.getStatusCode(), responseBody); + } } } diff --git a/library/src/com/loopj/android/http/FileAsyncHttpResponseHandler.java b/library/src/com/loopj/android/http/FileAsyncHttpResponseHandler.java index 996099d3b..7bb674fca 100644 --- a/library/src/com/loopj/android/http/FileAsyncHttpResponseHandler.java +++ b/library/src/com/loopj/android/http/FileAsyncHttpResponseHandler.java @@ -1,59 +1,109 @@ package com.loopj.android.http; +import android.os.Message; + +import org.apache.http.Header; +import org.apache.http.HttpResponse; +import org.apache.http.StatusLine; +import org.apache.http.client.HttpResponseException; + import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; -import org.apache.http.HttpEntity; - -/** - * - * @author sweetlilmre - * - * Implements a response handler that will store the response in the provided - * File object. - * - * Events will be sent as per the AsyncHttpResponseHandler base class, however - * all byte[] values returned will be null. - */ + public class FileAsyncHttpResponseHandler extends AsyncHttpResponseHandler { - + private File mFile; - public File getFile() { - return (mFile); - } - public FileAsyncHttpResponseHandler(File file) { super(); this.mFile = file; } - + + public void onSuccess(File file) { + } + + public void onSuccess(int statusCode, File file) { + onSuccess(file); + } + + public void onFailure(Throwable e, File response) { + // By default call lower chain method + onFailure(e); + } + + public void onFailure(int statusCode, Throwable e, File response) { + // By default call lower chain method + onFailure(e, response); + } + + public void onFailure(int statusCode, Header[] headers, Throwable e, File response) { + // By default call lower chain method + onFailure(statusCode, e, response); + } + + + protected void sendSuccessMessage(int statusCode, File file) { + sendMessage(obtainMessage(SUCCESS_MESSAGE, new Object[]{statusCode, file})); + } + + protected void sendFailureMessage(int statusCode, Header[] headers, Throwable e, File file) { + sendMessage(obtainMessage(FAILURE_MESSAGE, new Object[]{statusCode, headers, e, file})); + } + + protected void handleSuccessMessage(int statusCode, File responseBody) { + onSuccess(statusCode, responseBody); + } + + protected void handleFailureMessage(int statusCode, Header[] headers, Throwable e, File responseBody) { + onFailure(statusCode, headers, e, responseBody); + } + + // Methods which emulate android's Handler and Message methods + protected void handleMessage(Message msg) { + Object[] response; + switch (msg.what) { + case SUCCESS_MESSAGE: + response = (Object[]) msg.obj; + handleSuccessMessage((Integer) response[0], (File) response[1]); + break; + case FAILURE_MESSAGE: + response = (Object[]) msg.obj; + handleFailureMessage((Integer) response[0], (Header[]) response[1], (Throwable) response[2], (File) response[3]); + break; + default: + super.handleMessage(msg); + break; + } + } + @Override - byte[] getResponseData(HttpEntity entity) throws IOException { - if (entity != null) { - InputStream instream = entity.getContent(); - long contentLength = entity.getContentLength(); - FileOutputStream buffer = new FileOutputStream(this.mFile); - if (instream != null) { - try { - byte[] tmp = new byte[BUFFER_SIZE]; - int l, count = 0;; - // do not send messages if request has been cancelled - while ((l = instream.read(tmp)) != -1 && !Thread.currentThread().isInterrupted()) { - count += l; - buffer.write(tmp, 0, l); - sendProgressMessage(count, (int) contentLength); - } - } finally { - instream.close(); - buffer.flush(); - buffer.close(); - } - } - } - return (null); - } - -} + protected void sendResponseMessage(HttpResponse response) { + StatusLine status = response.getStatusLine(); + + try { + FileOutputStream buffer = new FileOutputStream(this.mFile); + InputStream is = response.getEntity().getContent(); + + int nRead; + byte[] data = new byte[16384]; + + while ((nRead = is.read(data, 0, data.length)) != -1) + buffer.write(data, 0, nRead); + + buffer.flush(); + buffer.close(); + + } catch (IOException e) { + sendFailureMessage(status.getStatusCode(), response.getAllHeaders(), e, this.mFile); + } + + if (status.getStatusCode() >= 300) { + sendFailureMessage(status.getStatusCode(), response.getAllHeaders(), new HttpResponseException(status.getStatusCode(), status.getReasonPhrase()), this.mFile); + } else { + sendSuccessMessage(status.getStatusCode(), this.mFile); + } + } +} \ No newline at end of file diff --git a/library/src/com/loopj/android/http/JsonHttpResponseHandler.java b/library/src/com/loopj/android/http/JsonHttpResponseHandler.java index 47fa3d9cb..33855fc96 100644 --- a/library/src/com/loopj/android/http/JsonHttpResponseHandler.java +++ b/library/src/com/loopj/android/http/JsonHttpResponseHandler.java @@ -18,7 +18,7 @@ package com.loopj.android.http; -import java.io.UnsupportedEncodingException; +import android.os.Message; import org.apache.http.Header; import org.apache.http.HttpStatus; @@ -27,23 +27,20 @@ import org.json.JSONObject; import org.json.JSONTokener; -import android.os.Message; - /** * Used to intercept and handle the responses from requests made using * {@link AsyncHttpClient}, with automatic parsing into a {@link JSONObject} * or {@link JSONArray}. *

     

    * This class is designed to be passed to get, post, put and delete requests - * with the {@link #onSuccess(int, Object)} - * method anonymously overridden. + * with the {@link #onSuccess(JSONObject)} or {@link #onSuccess(JSONArray)} + * methods anonymously overridden. *

     

    * Additionally, you can override the other event methods from the * parent class. */ public class JsonHttpResponseHandler extends AsyncHttpResponseHandler { - // chain message values, lets not just make them up. - protected static final int SUCCESS_JSON_MESSAGE = AsyncHttpResponseHandler.LAST_MESSAGE; + protected static final int SUCCESS_JSON_MESSAGE = 100; // // Callbacks to be overridden, typically anonymously @@ -51,54 +48,133 @@ public class JsonHttpResponseHandler extends AsyncHttpResponseHandler { /** * Fired when a request returns successfully and contains a json object - * at the base of the response string. Override to handle in your own code. + * at the base of the response string. Override to handle in your + * own code. * - * @param statusCode the HTTP Status code for the response - * @param response the parsed json object found in the server response. - * Check the type of the object to determine if it is one - * of the valid types created by {@link JSONTokener#nextValue()} + * @param response the parsed json object found in the server response (if any) */ - public void onSuccess(int statusCode, Object response) { + public void onSuccess(JSONObject response) { } + /** - * onSuccess is overridden here to perform background processing of the JSON packet + * Fired when a request returns successfully and contains a json array + * at the base of the response string. Override to handle in your + * own code. + * + * @param response the parsed json array found in the server response (if any) */ - @Override - public void onSuccess(int statusCode, Header[] headers, byte[] responseBody) { - final int _statusCode = statusCode; - final Header[] _headers = headers; - final byte[] _responseBody = responseBody; + public void onSuccess(JSONArray response) { + } + + /** + * Fired when a request returns successfully and contains a json object + * at the base of the response string. Override to handle in your + * own code. + * + * @param statusCode the status code of the response + * @param headers the headers of the HTTP response + * @param response the parsed json object found in the server response (if any) + */ + public void onSuccess(int statusCode, Header[] headers, JSONObject response) { + onSuccess(statusCode, response); + } + + /** + * Fired when a request returns successfully and contains a json object + * at the base of the response string. Override to handle in your + * own code. + * + * @param statusCode the status code of the response + * @param response the parsed json object found in the server response (if any) + */ + public void onSuccess(int statusCode, JSONObject response) { + onSuccess(response); + } + + /** + * Fired when a request returns successfully and contains a json array + * at the base of the response string. Override to handle in your + * own code. + * + * @param statusCode the status code of the response + * @param headers the headers of the HTTP response + * @param response the parsed json array found in the server response (if any) + */ + public void onSuccess(int statusCode, Header[] headers, JSONArray response) { + onSuccess(statusCode, response); + } + /** + * Fired when a request returns successfully and contains a json array + * at the base of the response string. Override to handle in your + * own code. + * + * @param statusCode the status code of the response + * @param response the parsed json array found in the server response (if any) + */ + public void onSuccess(int statusCode, JSONArray response) { + onSuccess(response); + } + + public void onFailure(Throwable e, JSONObject errorResponse) { + onFailure(e); + } + + public void onFailure(int statusCode, Throwable e, JSONObject errorResponse) { + onFailure(e, errorResponse); + } + + public void onFailure(int statusCode, Header[] headers, Throwable e, JSONObject errorResponse) { + onFailure(statusCode, e, errorResponse); + } + + public void onFailure(Throwable e, JSONArray errorResponse) { + onFailure(e); + } + + public void onFailure(int statusCode, Throwable e, JSONArray errorResponse) { + onFailure(e, errorResponse); + } + + public void onFailure(int statusCode, Header[] headers, Throwable e, JSONArray errorResponse) { + onFailure(statusCode, e, errorResponse); + } + + + // + // Pre-processing of messages (executes in background threadpool thread) + // + + @Override + protected void sendSuccessMessage(final int statusCode, final Header[] headers, final String responseBody) { if (statusCode != HttpStatus.SC_NO_CONTENT) { new Thread(new Runnable() { @Override public void run() { try { - Object jsonResponse = parseResponse(_responseBody); - sendMessage(obtainMessage(SUCCESS_JSON_MESSAGE, new Object[]{_statusCode, _headers, jsonResponse})); + Object jsonResponse = parseResponse(responseBody); + sendMessage(obtainMessage(SUCCESS_JSON_MESSAGE, new Object[]{statusCode, headers, jsonResponse})); } catch (JSONException e) { - // have to do this via sendFailureMessage so that onFailure will finally be called on the main / UI thread - sendFailureMessage(_statusCode, _headers, _responseBody, e); + sendFailureMessage(statusCode, headers, e, responseBody); } } }).start(); } else { - // already on the main / UI thread so lets just call onSuccess - onSuccess( statusCode, new JSONObject()); + sendMessage(obtainMessage(SUCCESS_JSON_MESSAGE, new Object[]{statusCode, headers, new JSONObject()})); } } + // // Pre-processing of messages (in original calling thread, typically the UI thread) // @Override protected void handleMessage(Message msg) { - Object[] response; - switch(msg.what){ + switch (msg.what) { case SUCCESS_JSON_MESSAGE: - response = (Object[]) msg.obj; + Object[] response = (Object[]) msg.obj; handleSuccessJsonMessage((Integer) response[0], (Header[]) response[1], response[2]); break; default: @@ -107,29 +183,57 @@ protected void handleMessage(Message msg) { } protected void handleSuccessJsonMessage(int statusCode, Header[] headers, Object jsonResponse) { - onSuccess(statusCode, jsonResponse); + if (jsonResponse instanceof JSONObject) { + onSuccess(statusCode, headers, (JSONObject) jsonResponse); + } else if (jsonResponse instanceof JSONArray) { + onSuccess(statusCode, headers, (JSONArray) jsonResponse); + } else if (jsonResponse instanceof String) { + onSuccess(statusCode, headers, (String) jsonResponse); + } else { + onFailure(new JSONException("Unexpected type " + jsonResponse.getClass().getName()), (JSONObject) null); + } } - protected Object parseResponse(byte[] responseBody) throws JSONException { + protected Object parseResponse(String responseBody) throws JSONException { + if (null == responseBody) + return null; Object result = null; - String responseBodyText = null; - try { - responseBodyText = new String(responseBody, "UTF-8"); - } catch (UnsupportedEncodingException e) { - throw new JSONException("Unable to convert response to UTF-8 string"); - } - - // trim the string to prevent start with blank, and test if the string - // is valid JSON, because the parser don't do this :(. If Json is not - // valid this will return null - responseBodyText = responseBodyText.trim(); - if (responseBodyText.startsWith("{") || responseBodyText.startsWith("[")) { - result = new JSONTokener(responseBodyText).nextValue(); + //trim the string to prevent start with blank, and test if the string is valid JSON, because the parser don't do this :(. If Json is not valid this will return null + responseBody = responseBody.trim(); + if (responseBody.startsWith("{") || responseBody.startsWith("[")) { + result = new JSONTokener(responseBody).nextValue(); } if (result == null) { - result = responseBodyText; + result = responseBody; } return result; } + @Override + protected void handleFailureMessage(final int statusCode, final Header[] headers, final Throwable e, final String responseBody) { + new Thread(new Runnable() { + @Override + public void run() { + try { + if (responseBody != null) { + Object jsonResponse = parseResponse(responseBody); + if (jsonResponse instanceof JSONObject) { + onFailure(statusCode, headers, e, (JSONObject) jsonResponse); + } else if (jsonResponse instanceof JSONArray) { + onFailure(statusCode, headers, e, (JSONArray) jsonResponse); + } else if (jsonResponse instanceof String) { + onFailure(statusCode, headers, e, (String) jsonResponse); + } else { + onFailure(statusCode, headers, e, responseBody); + } + } else { + onFailure(e, ""); + } + } catch (JSONException ex) { + onFailure(statusCode, headers, e, responseBody); + } + } + }).start(); + + } } diff --git a/library/src/com/loopj/android/http/SyncHttpClient.java b/library/src/com/loopj/android/http/SyncHttpClient.java index deb82abcb..879abb1f5 100644 --- a/library/src/com/loopj/android/http/SyncHttpClient.java +++ b/library/src/com/loopj/android/http/SyncHttpClient.java @@ -1,14 +1,59 @@ package com.loopj.android.http; import android.content.Context; +import android.os.Message; + +import org.apache.http.HttpEntity; +import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpUriRequest; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.protocol.HttpContext; -import java.io.IOException; +public abstract class SyncHttpClient extends AsyncHttpClient { + private int responseCode; + /* + * as this is a synchronous request this is just a helping mechanism to pass + * the result back to this method. Therefore the result object has to be a + * field to be accessible + */ + protected String result; + protected AsyncHttpResponseHandler responseHandler = new AsyncHttpResponseHandler() { -public class SyncHttpClient extends AsyncHttpClient { + @Override + protected void sendResponseMessage(HttpResponse response) { + responseCode = response.getStatusLine().getStatusCode(); + super.sendResponseMessage(response); + } + @Override + protected void sendMessage(Message msg) { + /* + * Dont use the handler and send it directly to the analysis + * (because its all the same thread) + */ + handleMessage(msg); + } + + @Override + public void onSuccess(String content) { + result = content; + } + + @Override + public void onFailure(Throwable error, String content) { + result = onRequestFailed(error, content); + } + }; + + /** + * @return the response code for the last request, might be usefull + * sometimes + */ + public int getResponseCode() { + return responseCode; + } + + // Private stuff @Override protected void sendRequest(DefaultHttpClient client, HttpContext httpContext, HttpUriRequest uriRequest, @@ -18,11 +63,67 @@ protected void sendRequest(DefaultHttpClient client, uriRequest.addHeader("Content-Type", contentType); } - responseHandler.setForceSynchronous(true); - /* * will execute the request directly - */ - new AsyncHttpRequest(client, httpContext, uriRequest, responseHandler).run(); + */ + new AsyncHttpRequest(client, httpContext, uriRequest, responseHandler) + .run(); + } + + public abstract String onRequestFailed(Throwable error, String content); + + public void delete(String url, RequestParams queryParams, + AsyncHttpResponseHandler responseHandler) { + delete(getUrlWithQueryString(isUrlEncodingEnabled(), url, queryParams), responseHandler); + } + + public String get(String url, RequestParams params) { + this.get(url, params, responseHandler); + /* + * the response handler will have set the result when this line is + * reached + */ + return result; } + + public String get(String url) { + this.get(url, null, responseHandler); + return result; + } + + public String put(String url, RequestParams params) { + this.put(url, params, responseHandler); + return result; + } + + public String put(String url) { + this.put(url, null, responseHandler); + return result; + } + + public String post(String url, HttpEntity entity) { + this.post(null, url, entity, null, responseHandler); + return result; + } + + public String post(String url, RequestParams params) { + this.post(url, params, responseHandler); + return result; + } + + public String post(String url) { + this.post(url, null, responseHandler); + return result; + } + + public String delete(String url, RequestParams params) { + this.delete(url, params, responseHandler); + return result; + } + + public String delete(String url) { + this.delete(url, null, responseHandler); + return result; + } + } diff --git a/library/src/com/loopj/android/http/TextHttpResponseHandler.java b/library/src/com/loopj/android/http/TextHttpResponseHandler.java deleted file mode 100644 index e0818fea7..000000000 --- a/library/src/com/loopj/android/http/TextHttpResponseHandler.java +++ /dev/null @@ -1,129 +0,0 @@ -package com.loopj.android.http; - -import org.apache.http.Header; - -import java.io.UnsupportedEncodingException; - -/** - * Used to intercept and handle the responses from requests made using - * {@link AsyncHttpClient}. The {@link #onSuccess(String)} method is - * designed to be anonymously overridden with your own response handling code. - *

    - * Additionally, you can override the {@link #onFailure(String, Throwable)}, - * {@link #onStart()}, and {@link #onFinish()} methods as required. - *

    - * For example: - *

    - *

    - * AsyncHttpClient client = new AsyncHttpClient();
    - * client.get("http://www.google.com/", new AsyncHttpResponseHandler() {
    - *     @Override
    - *     public void onStart() {
    - *         // Initiated the request
    - *     }
    - *
    - *     @Override
    - *     public void onSuccess(String responseBody ) {
    - *         // Successfully got a response
    - *     }
    - * 
    - *     @Override
    - *     public void onFailure(String responseBody, Throwable e) {
    - *         // Response failed :(
    - *     }
    - *
    - *     @Override
    - *     public void onFinish() {
    - *         // Completed the request (either success or failure)
    - *     }
    - * });
    - * 
    - */ -public class TextHttpResponseHandler extends AsyncHttpResponseHandler { - - private String _encoding; - /** - * Creates a new TextHttpResponseHandler - */ - - public TextHttpResponseHandler() - { - this("UTF-8"); - } - - public TextHttpResponseHandler(String encoding) { - super(); - _encoding = encoding; - } - // - // Callbacks to be overridden, typically anonymously - // - - - /** - * Fired when a request returns successfully, override to handle in your own - * code - * - * @param responseBody the body of the HTTP response from the server - */ - public void onSuccess(String responseBody) { - } - - /** - * Fired when a request returns successfully, override to handle in your own - * code - * - * @param statusCode the status code of the response - * @param headers HTTP response headers - * @param responseBody the body of the HTTP response from the server - */ - public void onSuccess(int statusCode, Header[] headers, String responseBody) { - onSuccess( responseBody ); - } - - /** - * Fired when a request fails to complete, override to handle in your own - * code - * - * @param responseBody the response body, if any - * @param error the underlying cause of the failure - */ - public void onFailure(String responseBody, Throwable error) { - } - - /** - * Fired when a request fails to complete, override to handle in your own - * code - * - * @param statusCode the status code of the response - * @param headers HTTP response headers - * @param responseBody the response body, if any - * @param error the underlying cause of the failure - */ - public void onFailure(int statusCode, Header[] headers, String responseBody, Throwable error) { - onFailure( responseBody, error ); - } - - // - // Pre-processing of messages (in original calling thread, typically the UI thread) - // - - @Override - protected void handleSuccessMessage(int statusCode, Header[] headers, byte[] responseBody) { - try { - onSuccess(statusCode, headers, new String(responseBody, _encoding)); - } catch (UnsupportedEncodingException e) { - onFailure(0, headers, (String) null, e); - } - } - - @Override - protected void handleFailureMessage(int statusCode, Header[] headers, byte[] responseBody, Throwable error) { - try { - onFailure(statusCode, headers, new String(responseBody, _encoding), error); - } catch (UnsupportedEncodingException e) { - onFailure(0, headers, (String) null, e); - } - } - -} From 29883258e1d468054c6a5d2d5a6750215eba9b94 Mon Sep 17 00:00:00 2001 From: Peter Edwards Date: Fri, 18 Oct 2013 09:49:06 +0200 Subject: [PATCH 122/613] Examples and other files rebase --- examples/CookieVideoView.java | 2 -- examples/ExampleUsage.java | 5 +---- examples/TestCaseExampleUsage.java | 6 +++--- examples/TwitterRestClient.java | 4 ---- examples/TwitterRestClientUsage.java | 10 ++-------- library/AndroidManifest.xml | 3 +-- library/gradle.properties | 2 +- .../com/loopj/android/http/sample/MainActivity.java | 5 ++--- 8 files changed, 10 insertions(+), 27 deletions(-) diff --git a/examples/CookieVideoView.java b/examples/CookieVideoView.java index 82bfd210f..998daa1fa 100644 --- a/examples/CookieVideoView.java +++ b/examples/CookieVideoView.java @@ -39,8 +39,6 @@ import android.widget.MediaController; import android.widget.MediaController.MediaPlayerControl; -import com.loopj.android.http.PersistentCookieStore; - import java.io.IOException; import java.util.HashMap; import java.util.List; diff --git a/examples/ExampleUsage.java b/examples/ExampleUsage.java index 7cf943aee..b5c4fc6f7 100644 --- a/examples/ExampleUsage.java +++ b/examples/ExampleUsage.java @@ -1,11 +1,8 @@ -import com.loopj.android.http.AsyncHttpClient; -import com.loopj.android.http.TextHttpResponseHandler; - public class ExampleUsage { public static void makeRequest() { AsyncHttpClient client = new AsyncHttpClient(); - client.get("http://www.google.com/", new TextHttpResponseHandler() { + client.get("http://www.google.com/", new AsyncHttpResponseHandler() { @Override public void onSuccess(String response) { System.out.println(response); diff --git a/examples/TestCaseExampleUsage.java b/examples/TestCaseExampleUsage.java index 4f31c5247..ce9498179 100644 --- a/examples/TestCaseExampleUsage.java +++ b/examples/TestCaseExampleUsage.java @@ -2,7 +2,7 @@ import java.util.concurrent.TimeUnit; import com.loopj.android.http.AsyncHttpClient; -import com.loopj.android.http.TextHttpResponseHandler; +import com.loopj.android.http.AsyncHttpResponseHandler; import android.test.InstrumentationTestCase; import android.util.Log; @@ -23,7 +23,7 @@ public void run() { AsyncHttpClient client = new AsyncHttpClient(); - client.get("http://www.google.com/", new TextHttpResponseHandler() + client.get("http://www.google.com/", new AsyncHttpResponseHandler() { @Override public void onStart() @@ -39,7 +39,7 @@ public void onSuccess(String response) } @Override - public void onFailure(String content, Throwable error) + public void onFailure(Throwable error, String content) { Log.e(TAG , "onFailure error : " + error.toString() + "content : " + content); } diff --git a/examples/TwitterRestClient.java b/examples/TwitterRestClient.java index f2b6c97b3..395273df0 100644 --- a/examples/TwitterRestClient.java +++ b/examples/TwitterRestClient.java @@ -1,9 +1,5 @@ // Static wrapper library around AsyncHttpClient -import com.loopj.android.http.AsyncHttpClient; -import com.loopj.android.http.AsyncHttpResponseHandler; -import com.loopj.android.http.RequestParams; - public class TwitterRestClient { private static final String BASE_URL = "http://api.twitter.com/1/"; diff --git a/examples/TwitterRestClientUsage.java b/examples/TwitterRestClientUsage.java index c6fe157f7..297bfecc4 100644 --- a/examples/TwitterRestClientUsage.java +++ b/examples/TwitterRestClientUsage.java @@ -1,16 +1,10 @@ -import com.loopj.android.http.*; - -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - class TwitterRestClientUsage { public void getPublicTimeline() { TwitterRestClient.get("statuses/public_timeline.json", null, new JsonHttpResponseHandler() { @Override - public void onSuccess(int statusCode, Object timeline) { + public void onSuccess(JSONArray timeline) { try { - JSONObject firstEvent = (JSONObject) ((JSONArray)timeline).get(0); + JSONObject firstEvent = (JSONObject) timeline.get(0); String tweetText = firstEvent.getString("text"); // Do something with the response diff --git a/library/AndroidManifest.xml b/library/AndroidManifest.xml index 1ad7c0fcf..dede71fe3 100644 --- a/library/AndroidManifest.xml +++ b/library/AndroidManifest.xml @@ -5,8 +5,7 @@ android:versionCode="144"> + android:minSdkVersion="3" /> diff --git a/library/gradle.properties b/library/gradle.properties index d6aa0de5a..96e35d668 100644 --- a/library/gradle.properties +++ b/library/gradle.properties @@ -1,3 +1,3 @@ -POM_NAME=ActionBar-PullToRefresh Library +POM_NAME=android-async-http Library POM_ARTIFACT_ID=android-async-http POM_PACKAGING=aar diff --git a/sample/src/main/java/com/loopj/android/http/sample/MainActivity.java b/sample/src/main/java/com/loopj/android/http/sample/MainActivity.java index 853e610bb..f5096ae2f 100644 --- a/sample/src/main/java/com/loopj/android/http/sample/MainActivity.java +++ b/sample/src/main/java/com/loopj/android/http/sample/MainActivity.java @@ -12,7 +12,6 @@ import com.loopj.android.http.AsyncHttpClient; import com.loopj.android.http.AsyncHttpResponseHandler; -import com.loopj.android.http.TextHttpResponseHandler; import org.apache.http.Header; @@ -60,7 +59,7 @@ public void onClick(View v) { } private void startRequest() { - aclient.get(this, getURLString(), new TextHttpResponseHandler() { + aclient.get(this, getURLString(), new AsyncHttpResponseHandler() { @Override public void onSuccess(int statusCode, Header[] headers, String content) { @@ -72,7 +71,7 @@ public void onSuccess(int statusCode, Header[] headers, String content) { } @Override - public void onFailure(int statusCode, Header[] headers, String content, Throwable error) { + public void onFailure(int statusCode, Header[] headers, Throwable error, String content) { setStatusMessage("Failed", Color.parseColor("#99FF0000")); printThrowable(error); printHeaders(headers); From c1541be6adc2f8f81d169ca579d4de5ebf8aeb33 Mon Sep 17 00:00:00 2001 From: mareksebera Date: Fri, 18 Oct 2013 10:26:02 +0200 Subject: [PATCH 123/613] CalledFromWrongThreadException fixup, closes #337 --- .../http/AsyncHttpResponseHandler.java | 8 ++- .../android/http/JsonHttpResponseHandler.java | 72 ++++++++++++------- 2 files changed, 54 insertions(+), 26 deletions(-) diff --git a/library/src/com/loopj/android/http/AsyncHttpResponseHandler.java b/library/src/com/loopj/android/http/AsyncHttpResponseHandler.java index 10669285b..5525391a3 100644 --- a/library/src/com/loopj/android/http/AsyncHttpResponseHandler.java +++ b/library/src/com/loopj/android/http/AsyncHttpResponseHandler.java @@ -115,7 +115,7 @@ public void handleMessage(Message msg) { * Fired when the request progress, override to handle in your own code * * @param bytesWritten offset from start of file - * @param totalSize total size of file + * @param totalSize total size of file */ public void onProgress(int bytesWritten, int totalSize) { } @@ -294,6 +294,12 @@ protected void sendMessage(Message msg) { } } + protected void postRunnable(Runnable r) { + if (r != null) { + handler.post(r); + } + } + protected Message obtainMessage(int responseMessage, Object response) { Message msg; if (handler != null) { diff --git a/library/src/com/loopj/android/http/JsonHttpResponseHandler.java b/library/src/com/loopj/android/http/JsonHttpResponseHandler.java index 33855fc96..138b61162 100644 --- a/library/src/com/loopj/android/http/JsonHttpResponseHandler.java +++ b/library/src/com/loopj/android/http/JsonHttpResponseHandler.java @@ -153,10 +153,20 @@ protected void sendSuccessMessage(final int statusCode, final Header[] headers, @Override public void run() { try { - Object jsonResponse = parseResponse(responseBody); - sendMessage(obtainMessage(SUCCESS_JSON_MESSAGE, new Object[]{statusCode, headers, jsonResponse})); - } catch (JSONException e) { - sendFailureMessage(statusCode, headers, e, responseBody); + final Object jsonResponse = parseResponse(responseBody); + postRunnable(new Runnable() { + @Override + public void run() { + sendMessage(obtainMessage(SUCCESS_JSON_MESSAGE, new Object[]{statusCode, headers, jsonResponse})); + } + }); + } catch (final JSONException e) { + postRunnable(new Runnable() { + @Override + public void run() { + sendFailureMessage(statusCode, headers, e, responseBody); + } + }); } } }).start(); @@ -211,29 +221,41 @@ protected Object parseResponse(String responseBody) throws JSONException { @Override protected void handleFailureMessage(final int statusCode, final Header[] headers, final Throwable e, final String responseBody) { - new Thread(new Runnable() { - @Override - public void run() { - try { - if (responseBody != null) { - Object jsonResponse = parseResponse(responseBody); - if (jsonResponse instanceof JSONObject) { - onFailure(statusCode, headers, e, (JSONObject) jsonResponse); - } else if (jsonResponse instanceof JSONArray) { - onFailure(statusCode, headers, e, (JSONArray) jsonResponse); - } else if (jsonResponse instanceof String) { - onFailure(statusCode, headers, e, (String) jsonResponse); - } else { - onFailure(statusCode, headers, e, responseBody); - } - } else { - onFailure(e, ""); + if (responseBody != null) { + new Thread(new Runnable() { + @Override + public void run() { + try { + final Object jsonResponse = parseResponse(responseBody); + postRunnable(new Runnable() { + @Override + public void run() { + if (jsonResponse instanceof JSONObject) { + onFailure(statusCode, headers, e, (JSONObject) jsonResponse); + } else if (jsonResponse instanceof JSONArray) { + onFailure(statusCode, headers, e, (JSONArray) jsonResponse); + } else if (jsonResponse instanceof String) { + onFailure(statusCode, headers, e, (String) jsonResponse); + } else { + onFailure(statusCode, headers, e, responseBody); + } + } + }); + + } catch (JSONException ex) { + postRunnable(new Runnable() { + @Override + public void run() { + onFailure(statusCode, headers, e, responseBody); + } + }); + } - } catch (JSONException ex) { - onFailure(statusCode, headers, e, responseBody); } - } - }).start(); + }).start(); + } else { + onFailure(e, ""); + } } } From 6fe5ce640634c2d11d7828fabf706b7977f03674 Mon Sep 17 00:00:00 2001 From: Peter Edwards Date: Fri, 18 Oct 2013 13:59:14 +0200 Subject: [PATCH 124/613] Refactor preserving call chain for backward compatibility --- .../loopj/android/http/AsyncHttpClient.java | 2 +- .../loopj/android/http/AsyncHttpRequest.java | 132 ++++------ .../http/AsyncHttpResponseHandler.java | 248 ++++++++++++------ .../http/BinaryHttpResponseHandler.java | 93 ++----- .../http/FileAsyncHttpResponseHandler.java | 96 +++---- .../android/http/JsonHttpResponseHandler.java | 97 +++---- .../loopj/android/http/SyncHttpClient.java | 144 ++++------ .../android/http/TextHttpResponseHandler.java | 124 +++++++++ .../android/http/sample/MainActivity.java | 3 +- 9 files changed, 487 insertions(+), 452 deletions(-) create mode 100644 library/src/com/loopj/android/http/TextHttpResponseHandler.java diff --git a/library/src/com/loopj/android/http/AsyncHttpClient.java b/library/src/com/loopj/android/http/AsyncHttpClient.java index 2bfcabc4e..f9ab1ad05 100644 --- a/library/src/com/loopj/android/http/AsyncHttpClient.java +++ b/library/src/com/loopj/android/http/AsyncHttpClient.java @@ -889,7 +889,7 @@ private HttpEntity paramsToEntity(RequestParams params, AsyncHttpResponseHandler } } catch (Throwable t) { if (responseHandler != null) - responseHandler.sendFailureMessage(0, null, t, (String) null); + responseHandler.sendFailureMessage(0, null, (byte[]) null, t); else t.printStackTrace(); } diff --git a/library/src/com/loopj/android/http/AsyncHttpRequest.java b/library/src/com/loopj/android/http/AsyncHttpRequest.java index 339c6c0f5..db3891ed7 100644 --- a/library/src/com/loopj/android/http/AsyncHttpRequest.java +++ b/library/src/com/loopj/android/http/AsyncHttpRequest.java @@ -38,7 +38,6 @@ class AsyncHttpRequest implements Runnable { private final HttpContext context; private final HttpUriRequest request; private final AsyncHttpResponseHandler responseHandler; - private boolean isBinaryRequest; private int executionCount; public AsyncHttpRequest(AbstractHttpClient client, HttpContext context, HttpUriRequest request, AsyncHttpResponseHandler responseHandler) { @@ -46,110 +45,81 @@ public AsyncHttpRequest(AbstractHttpClient client, HttpContext context, HttpUriR this.context = context; this.request = request; this.responseHandler = responseHandler; - if (responseHandler instanceof BinaryHttpResponseHandler) { - this.isBinaryRequest = true; - } } @Override public void run() { - try { - if (responseHandler != null) { - responseHandler.sendStartMessage(); - } + if (responseHandler != null) { + responseHandler.sendStartMessage(); + } + try { makeRequestWithRetries(); - - if (responseHandler != null) { - responseHandler.sendFinishMessage(); - } } catch (IOException e) { if (responseHandler != null) { - responseHandler.sendFinishMessage(); - if (this.isBinaryRequest) { - responseHandler.sendFailureMessage(e, (byte[]) null); - } else { - responseHandler.sendFailureMessage(e, (String) null); - } + responseHandler.sendFailureMessage(0, null, (byte[]) null, e); } } + + if (responseHandler != null) { + responseHandler.sendFinishMessage(); + } } - private void makeRequest() throws IOException, InterruptedException { + private void makeRequest() throws IOException { if (!Thread.currentThread().isInterrupted()) { - try { - // Fixes #115 - if (request.getURI().getScheme() == null) - throw new MalformedURLException("No valid URI scheme was provided"); - HttpResponse response = client.execute(request, context); - if (!Thread.currentThread().isInterrupted()) { - if (responseHandler != null) { - responseHandler.sendResponseMessage(response); - } - } else { - throw new InterruptedException("makeRequest was interrupted"); - } - } catch (IOException e) { - if (!Thread.currentThread().isInterrupted()) { - throw e; + // Fixes #115 + if (request.getURI().getScheme() == null) { + // subclass of IOException so processed in the caller + throw new MalformedURLException("No valid URI scheme was provided"); + } + + HttpResponse response = client.execute(request, context); + + if (!Thread.currentThread().isInterrupted()) { + if (responseHandler != null) { + responseHandler.sendResponseMessage(response); } } } } - private void makeRequestWithRetries() throws ConnectException { - // This is an additional layer of retry logic lifted from droid-fu - // See: https://github.com/kaeppler/droid-fu/blob/master/src/main/java/com/github/droidfu/http/BetterHttpRequestBase.java + private void makeRequestWithRetries() throws IOException { boolean retry = true; IOException cause = null; HttpRequestRetryHandler retryHandler = client.getHttpRequestRetryHandler(); - while (retry) { - try { - makeRequest(); - return; - } catch (ClientProtocolException e) { - if (responseHandler != null) { - responseHandler.sendFailureMessage(e, "cannot repeat the request"); - } - return; - } catch (UnknownHostException e) { - if (responseHandler != null) { - responseHandler.sendFailureMessage(e, "can't resolve host"); + try + { + while (retry) { + try { + makeRequest(); + return; + } catch (UnknownHostException e) { + // switching between WI-FI and mobile data networks can cause a retry which then results in an UnknownHostException + // while the WI-FI is initialising. The retry logic will be invoked here, if this is NOT the first retry + // (to assist in genuine cases of unknown host) which seems better than outright failure + cause = new IOException("UnknownHostException exception: " + e.getMessage()); + retry = (executionCount > 0) && retryHandler.retryRequest(cause, ++executionCount, context); + } catch (NullPointerException e) { + // there's a bug in HttpClient 4.0.x that on some occasions causes + // DefaultRequestExecutor to throw an NPE, see + // http://code.google.com/p/android/issues/detail?id=5255 + cause = new IOException("NPE in HttpClient: " + e.getMessage()); + retry = retryHandler.retryRequest(cause, ++executionCount, context); + } catch (IOException e) { + cause = e; + retry = retryHandler.retryRequest(cause, ++executionCount, context); } - return; - } catch (ConnectTimeoutException e) { - if (responseHandler != null) { - responseHandler.sendFailureMessage(e, "connection timed out"); - } - } catch (SocketException e) { - // Added to detect host unreachable - if (responseHandler != null) { - responseHandler.sendFailureMessage(e, "can't resolve host"); + if(retry && (responseHandler != null)) { + responseHandler.sendRetryMessage(); } - return; - } catch (SocketTimeoutException e) { - if (responseHandler != null) { - responseHandler.sendFailureMessage(e, "socket time out"); - } - return; - } catch (IOException e) { - cause = e; - retry = retryHandler.retryRequest(cause, ++executionCount, context); - } catch (NullPointerException e) { - // there's a bug in HttpClient 4.0.x that on some occasions causes - // DefaultRequestExecutor to throw an NPE, see - // http://code.google.com/p/android/issues/detail?id=5255 - cause = new IOException("NPE in HttpClient" + e.getMessage()); - retry = retryHandler.retryRequest(cause, ++executionCount, context); - } catch (InterruptedException e) { - cause = new IOException("Request was interrupted while executing"); - retry = retryHandler.retryRequest(cause, ++executionCount, context); } + } catch (Exception e) { + // catch anything else to ensure failure message is propagated + cause = new IOException("Unhandled exception: " + e.getMessage()); } - - // no retries left, crap out with exception - ConnectException ex = new ConnectException(); - ex.initCause(cause); - throw ex; + + // cleaned up to throw IOException + throw(cause); } } diff --git a/library/src/com/loopj/android/http/AsyncHttpResponseHandler.java b/library/src/com/loopj/android/http/AsyncHttpResponseHandler.java index 10669285b..bd8bb3bf2 100644 --- a/library/src/com/loopj/android/http/AsyncHttpResponseHandler.java +++ b/library/src/com/loopj/android/http/AsyncHttpResponseHandler.java @@ -21,24 +21,27 @@ import android.os.Handler; import android.os.Looper; import android.os.Message; +import android.util.Log; import org.apache.http.Header; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.StatusLine; import org.apache.http.client.HttpResponseException; -import org.apache.http.entity.BufferedHttpEntity; -import org.apache.http.util.EntityUtils; +import org.apache.http.util.ByteArrayBuffer; import java.io.IOException; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import java.lang.ref.WeakReference; /** * Used to intercept and handle the responses from requests made using - * {@link AsyncHttpClient}. The {@link #onSuccess(String)} method is + * {@link AsyncHttpClient}. The {@link #onSuccess(int, org.apache.http.Header[], byte[])} method is * designed to be anonymously overridden with your own response handling code. *

     

    - * Additionally, you can override the {@link #onFailure(Throwable, String)}, - * {@link #onStart()}, and {@link #onFinish()} methods as required. + * Additionally, you can override the {@link #onFailure(int, org.apache.http.Header[], byte[], Throwable)}, + * {@link #onStart()}, {@link #onFinish()}, {@link #onRetry()} and {@link #onProgress(int, int)} methods as required. *

     

    * For example: *

     

    @@ -51,16 +54,26 @@ * } * * @Override - * public void onSuccess(String response) { + * public void onSuccess(int statusCode, Header[] headers, byte[] responseBody) { * // Successfully got a response * } * * @Override - * public void onFailure(Throwable e, String response) { + * public void onFailure(int statusCode, Header[] headers, byte[] responseBody, Throwable error) { * // Response failed :( * } * * @Override + * public void onRetry() { + * // Request was retried + * } + * + * @Override + * public void onProgress(int bytesWritten, int totalSize) { + * // Progress notification + * } + * + * @Override * public void onFinish() { * // Completed the request (either success or failure) * } @@ -68,20 +81,58 @@ *
    */ public class AsyncHttpResponseHandler { + private static final String LOG_TAG = "AsyncHttpResponseHandler"; + protected static final int SUCCESS_MESSAGE = 0; protected static final int FAILURE_MESSAGE = 1; protected static final int START_MESSAGE = 2; protected static final int FINISH_MESSAGE = 3; protected static final int PROGRESS_MESSAGE = 4; + protected static final int RETRY_MESSAGE = 5; + + protected static final int BUFFER_SIZE = 4096; private Handler handler; private String responseCharset = "UTF-8"; + private Boolean useSynchronousMode = false; + + // avoid leaks by using a non-anonymous handler class + // with a weak reference + static class ResponderHandler extends Handler { + private final WeakReference mResponder; + + ResponderHandler(AsyncHttpResponseHandler service) { + mResponder = new WeakReference(service); + } + @Override + public void handleMessage(Message msg) + { + AsyncHttpResponseHandler service = mResponder.get(); + if (service != null) { + service.handleMessage(msg); + } + } + } + + public boolean getUseSynchronousMode() { + return (useSynchronousMode); + } + + /** + * Set the response handler to use synchronous mode or not + * + * @param value true indicates that synchronous mode should be used + */ + public void setUseSynchronousMode(Boolean value) { + useSynchronousMode = value; + } /** * Sets the charset for the response string. If not set, the default is UTF-8. * * @param charset to be used for the response string. * @see Charset + * @deprecated use {@link com.loopj.android.http.TextHttpResponseHandler} instead */ public void setCharset(final String charset) { this.responseCharset = charset; @@ -97,12 +148,7 @@ public String getCharset() { public AsyncHttpResponseHandler() { // Set up a handler to post events back to the correct thread if possible if (Looper.myLooper() != null) { - handler = new Handler() { - @Override - public void handleMessage(Message msg) { - AsyncHttpResponseHandler.this.handleMessage(msg); - } - }; + handler = new ResponderHandler(this); } } @@ -136,7 +182,9 @@ public void onFinish() { * Fired when a request returns successfully, override to handle in your own code * * @param content the body of the HTTP response from the server + * @deprecated use {@link #onSuccess(int, Header[], byte[])} */ + @Deprecated public void onSuccess(String content) { } @@ -146,7 +194,9 @@ public void onSuccess(String content) { * @param statusCode the status code of the response * @param headers the headers of the HTTP response * @param content the body of the HTTP response from the server + * @deprecated use {@link #onSuccess(int, Header[], byte[])} */ + @Deprecated public void onSuccess(int statusCode, Header[] headers, String content) { onSuccess(statusCode, content); } @@ -156,11 +206,29 @@ public void onSuccess(int statusCode, Header[] headers, String content) { * * @param statusCode the status code of the response * @param content the body of the HTTP response from the server + * @deprecated use {@link #onSuccess(int, Header[], byte[])} */ + @Deprecated public void onSuccess(int statusCode, String content) { onSuccess(content); } + /** + * Fired when a request returns successfully, override to handle in your own code + * + * @param statusCode the status code of the response + * @param headers return headers, if any + * @param responseBody the body of the HTTP response from the server + */ + public void onSuccess(int statusCode, Header[] headers, byte[] responseBody) { + try { + String response = new String(responseBody, getCharset()); + onSuccess(statusCode, headers, response); + } catch (UnsupportedEncodingException e) { + Log.e(LOG_TAG, e.toString()); + onFailure(statusCode, headers, e, (String) null); + } + } /** * Fired when a request fails to complete, override to handle in your own code * @@ -176,7 +244,9 @@ public void onFailure(Throwable error) { * * @param error the underlying cause of the failure * @param content the response body, if any + * @deprecated use {@link #onFailure(int, Header[], byte[], Throwable)} */ + @Deprecated public void onFailure(Throwable error, String content) { // By default, call the deprecated onFailure(Throwable) for compatibility onFailure(error); @@ -188,7 +258,9 @@ public void onFailure(Throwable error, String content) { * @param statusCode return HTTP status code * @param error the underlying cause of the failure * @param content the response body, if any + * @deprecated use {@link #onFailure(int, Header[], byte[], Throwable)} */ + @Deprecated public void onFailure(int statusCode, Throwable error, String content) { // By default, call the chain method onFailure(Throwable,String) onFailure(error, content); @@ -201,12 +273,40 @@ public void onFailure(int statusCode, Throwable error, String content) { * @param headers return headers, if any * @param error the underlying cause of the failure * @param content the response body, if any + * @deprecated use {@link #onFailure(int, Header[], byte[], Throwable)} */ + @Deprecated public void onFailure(int statusCode, Header[] headers, Throwable error, String content) { // By default, call the chain method onFailure(int,Throwable,String) onFailure(statusCode, error, content); } + /** + * Fired when a request fails to complete, override to handle in your own code + * + * @param statusCode return HTTP status code + * @param headers return headers, if any + * @param responseBody the response body, if any + * @param error the underlying cause of the failure + */ + public void onFailure(int statusCode, Header[] headers, byte[] responseBody, Throwable error) { + String response = null; + try { + response = new String(responseBody, getCharset()); + } catch (UnsupportedEncodingException e) { + Log.e(LOG_TAG, e.toString()); + onFailure(statusCode, headers, e, null); + } + onFailure(statusCode, headers, error, response); + } + + /** + * Fired when a retry occurs, override to handle in your own code + * + */ + public void onRetry() { + } + // // Pre-processing of messages (executes in background threadpool thread) @@ -216,26 +316,12 @@ protected void sendProgressMessage(int bytesWritten, int totalSize) { sendMessage(obtainMessage(PROGRESS_MESSAGE, new Object[]{bytesWritten, totalSize})); } - protected void sendSuccessMessage(int statusCode, Header[] headers, String responseBody) { + protected void sendSuccessMessage(int statusCode, Header[] headers, byte[] responseBody) { sendMessage(obtainMessage(SUCCESS_MESSAGE, new Object[]{statusCode, headers, responseBody})); } - protected void sendFailureMessage(int statusCode, Header[] headers, Throwable e, String responseBody) { - sendMessage(obtainMessage(FAILURE_MESSAGE, new Object[]{statusCode, headers, e, responseBody})); - } - - @Deprecated - protected void sendFailureMessage(Throwable e, String responseBody) { - sendMessage(obtainMessage(FAILURE_MESSAGE, new Object[]{0, null, e, responseBody})); - } - - protected void sendFailureMessage(int statusCode, Header[] headers, Throwable e, byte[] responseBody) { - sendMessage(obtainMessage(FAILURE_MESSAGE, new Object[]{statusCode, headers, e, responseBody})); - } - - @Deprecated - protected void sendFailureMessage(Throwable e, byte[] responseBody) { - sendMessage(obtainMessage(FAILURE_MESSAGE, new Object[]{0, null, e, responseBody})); + protected void sendFailureMessage(int statusCode, Header[] headers, byte[] responseBody, Throwable error) { + sendMessage(obtainMessage(FAILURE_MESSAGE, new Object[]{statusCode, headers, responseBody, error})); } protected void sendStartMessage() { @@ -246,20 +332,10 @@ protected void sendFinishMessage() { sendMessage(obtainMessage(FINISH_MESSAGE, null)); } - - // - // Pre-processing of messages (in original calling thread, typically the UI thread) - // - - protected void handleSuccessMessage(int statusCode, Header[] headers, String responseBody) { - onSuccess(statusCode, headers, responseBody); + protected void sendRetryMessage() { + sendMessage(obtainMessage(RETRY_MESSAGE, null)); } - - protected void handleFailureMessage(int statusCode, Header[] headers, Throwable e, String responseBody) { - onFailure(statusCode, headers, e, responseBody); - } - - + // Methods which emulate android's Handler and Message methods protected void handleMessage(Message msg) { Object[] response; @@ -267,11 +343,11 @@ protected void handleMessage(Message msg) { switch (msg.what) { case SUCCESS_MESSAGE: response = (Object[]) msg.obj; - handleSuccessMessage((Integer) response[0], (Header[]) response[1], (String) response[2]); + onSuccess((Integer) response[0], (Header[]) response[1], (byte[]) response[2]); break; case FAILURE_MESSAGE: response = (Object[]) msg.obj; - handleFailureMessage((Integer) response[0], (Header[]) response[1], (Throwable) response[2], (String) response[3]); + onFailure((Integer) response[0], (Header[]) response[1], (byte[]) response[2], (Throwable) response[3]); break; case START_MESSAGE: onStart(); @@ -283,14 +359,17 @@ protected void handleMessage(Message msg) { response = (Object[]) msg.obj; onProgress((Integer) response[0], (Integer) response[1]); break; + case RETRY_MESSAGE: + onRetry(); + break; } } protected void sendMessage(Message msg) { - if (handler != null) { - handler.sendMessage(msg); - } else { + if (getUseSynchronousMode() || handler == null) { handleMessage(msg); + } else if (!Thread.currentThread().isInterrupted()) { // do not send messages if request has been cancelled + handler.sendMessage(msg); } } @@ -309,35 +388,56 @@ protected Message obtainMessage(int responseMessage, Object response) { } // Interface to AsyncHttpRequest - protected void sendResponseMessage(HttpResponse response) { - if (response == null) { - sendFailureMessage(0, null, new IllegalStateException("No response"), (String) null); - return; - } - StatusLine status = response.getStatusLine(); - String responseBody = null; - try { - HttpEntity entity; - HttpEntity temp = response.getEntity(); - if (temp != null) { - entity = new BufferedHttpEntity(temp); - responseBody = EntityUtils.toString(entity, getCharset()); - } - } catch (IOException e) { - try { - if (response.getEntity() != null) - response.getEntity().consumeContent(); - } catch (Throwable t) { - t.printStackTrace(); + void sendResponseMessage(HttpResponse response) throws IOException { + // do not process if request has been cancelled + if (!Thread.currentThread().isInterrupted()) { + StatusLine status = response.getStatusLine(); + byte[] responseBody = null; + responseBody = getResponseData(response.getEntity()); + // additional cancellation check as getResponseData() can take non-zero time to process + if (!Thread.currentThread().isInterrupted()) { + if (status.getStatusCode() >= 300) { + sendFailureMessage(status.getStatusCode(), response.getAllHeaders(), responseBody, new HttpResponseException(status.getStatusCode(), status.getReasonPhrase())); + } else { + sendSuccessMessage(status.getStatusCode(), response.getAllHeaders(), responseBody); + } } - sendFailureMessage(status.getStatusCode(), response.getAllHeaders(), e, (String) null); - return; } + } - if (status.getStatusCode() >= 300) { - sendFailureMessage(status.getStatusCode(), response.getAllHeaders(), new HttpResponseException(status.getStatusCode(), status.getReasonPhrase()), responseBody); - } else { - sendSuccessMessage(status.getStatusCode(), response.getAllHeaders(), responseBody); + byte[] getResponseData(HttpEntity entity) throws IOException { + byte[] responseBody = null; + if (entity != null) { + InputStream instream = entity.getContent(); + if (instream != null) { + long contentLength = entity.getContentLength(); + if (contentLength > Integer.MAX_VALUE) { + throw new IllegalArgumentException("HTTP entity too large to be buffered in memory"); + } + if (contentLength < 0) { + contentLength = BUFFER_SIZE; + } + try{ + ByteArrayBuffer buffer = new ByteArrayBuffer((int) contentLength); + try { + byte[] tmp = new byte[BUFFER_SIZE]; + int l, count = 0; + // do not send messages if request has been cancelled + while ((l = instream.read(tmp)) != -1 && !Thread.currentThread().isInterrupted()) { + count += l; + buffer.append(tmp, 0, l); + sendProgressMessage(count, (int) contentLength); + } + } finally { + instream.close(); + } + responseBody = buffer.buffer(); + } catch( OutOfMemoryError e ) { + System.gc(); + throw new IOException("File too large to fit into available memory"); + } + } } + return (responseBody); } } diff --git a/library/src/com/loopj/android/http/BinaryHttpResponseHandler.java b/library/src/com/loopj/android/http/BinaryHttpResponseHandler.java index d8e5cdda3..91adc5601 100644 --- a/library/src/com/loopj/android/http/BinaryHttpResponseHandler.java +++ b/library/src/com/loopj/android/http/BinaryHttpResponseHandler.java @@ -18,15 +18,10 @@ package com.loopj.android.http; -import android.os.Message; - import org.apache.http.Header; -import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.StatusLine; import org.apache.http.client.HttpResponseException; -import org.apache.http.entity.BufferedHttpEntity; -import org.apache.http.util.EntityUtils; import java.io.IOException; import java.util.regex.Pattern; @@ -104,74 +99,45 @@ public void onSuccess(int statusCode, byte[] binaryData) { } /** - * Fired when a request fails to complete, override to handle in your own code + * Fired when a request returns successfully, override to handle in your own code * - * @param statusCode response HTTP statuse code - * @param headers response headers, if any - * @param error the underlying cause of the failure - * @param binaryData the response body, if any - * @deprecated + * @param statusCode response HTTP statuse code + * @param headers response headers, if any + * @param responseData the response body, if any */ - @Deprecated - public void onFailure(int statusCode, Header[] headers, Throwable error, byte[] binaryData) { - // By default, call the deprecated onFailure(Throwable) for compatibility - onFailure(statusCode, error, null); - } - - - // - // Pre-processing of messages (executes in background threadpool thread) - // - protected void sendSuccessMessage(int statusCode, byte[] responseBody) { - sendMessage(obtainMessage(SUCCESS_MESSAGE, new Object[]{statusCode, responseBody})); + @Override + public void onSuccess(int statusCode, Header[] headers, byte[] responseData) { + onSuccess(statusCode, responseData); } + /** + * Fired when a request fails to complete, override to handle in your own code + * + * @param statusCode response HTTP statuse code + * @param headers response headers, if any + * @param responseData the response body, if any + * @param error the underlying cause of the failure + */ + @Override - protected void sendFailureMessage(int statusCode, Header[] headers, Throwable e, byte[] responseBody) { - sendMessage(obtainMessage(FAILURE_MESSAGE, new Object[]{statusCode, headers, e, responseBody})); + public void onFailure(int statusCode, Header[] headers, byte[] responseData, Throwable error) { + onFailure(statusCode, error, null); } // // Pre-processing of messages (in original calling thread, typically the UI thread) // - protected void handleSuccessMessage(int statusCode, byte[] responseBody) { - onSuccess(statusCode, responseBody); - } - - protected void handleFailureMessage(int statusCode, Header[] headers, Throwable e, byte[] responseBody) { - onFailure(statusCode, headers, e, responseBody); - } - - // Methods which emulate android's Handler and Message methods - @Override - protected void handleMessage(Message msg) { - Object[] response; - switch (msg.what) { - case SUCCESS_MESSAGE: - response = (Object[]) msg.obj; - handleSuccessMessage((Integer) response[0], (byte[]) response[1]); - break; - case FAILURE_MESSAGE: - response = (Object[]) msg.obj; - handleFailureMessage((Integer) response[0], (Header[]) response[1], (Throwable) response[2], (byte[]) response[3]); - break; - default: - super.handleMessage(msg); - break; - } - } - // Interface to AsyncHttpRequest @Override - protected void sendResponseMessage(HttpResponse response) { + protected void sendResponseMessage(HttpResponse response) throws IOException { StatusLine status = response.getStatusLine(); Header[] contentTypeHeaders = response.getHeaders("Content-Type"); byte[] responseBody = null; if (contentTypeHeaders.length != 1) { //malformed/ambiguous HTTP Header, ABORT! - sendFailureMessage(status.getStatusCode(), response.getAllHeaders(), new HttpResponseException(status.getStatusCode(), "None, or more than one, Content-Type Header found!"), (String) null); + sendFailureMessage(status.getStatusCode(), response.getAllHeaders(), (byte[]) null, new HttpResponseException(status.getStatusCode(), "None, or more than one, Content-Type Header found!")); return; } Header contentTypeHeader = contentTypeHeaders[0]; @@ -183,24 +149,9 @@ protected void sendResponseMessage(HttpResponse response) { } if (!foundAllowedContentType) { //Content-Type not in allowed list, ABORT! - sendFailureMessage(status.getStatusCode(), response.getAllHeaders(), new HttpResponseException(status.getStatusCode(), "Content-Type not allowed!"), (String) null); + sendFailureMessage(status.getStatusCode(), response.getAllHeaders(), (byte[]) null, new HttpResponseException(status.getStatusCode(), "Content-Type not allowed!")); return; } - try { - HttpEntity entity = null; - HttpEntity temp = response.getEntity(); - if (temp != null) { - entity = new BufferedHttpEntity(temp); - } - responseBody = EntityUtils.toByteArray(entity); - } catch (IOException e) { - sendFailureMessage(status.getStatusCode(), response.getAllHeaders(), e, (byte[]) null); - } - - if (status.getStatusCode() >= 300) { - sendFailureMessage(status.getStatusCode(), response.getAllHeaders(), new HttpResponseException(status.getStatusCode(), status.getReasonPhrase()), responseBody); - } else { - sendSuccessMessage(status.getStatusCode(), responseBody); - } + super.sendResponseMessage( response ); } } diff --git a/library/src/com/loopj/android/http/FileAsyncHttpResponseHandler.java b/library/src/com/loopj/android/http/FileAsyncHttpResponseHandler.java index 7bb674fca..f528982fe 100644 --- a/library/src/com/loopj/android/http/FileAsyncHttpResponseHandler.java +++ b/library/src/com/loopj/android/http/FileAsyncHttpResponseHandler.java @@ -1,11 +1,7 @@ package com.loopj.android.http; -import android.os.Message; - import org.apache.http.Header; -import org.apache.http.HttpResponse; -import org.apache.http.StatusLine; -import org.apache.http.client.HttpResponseException; +import org.apache.http.HttpEntity; import java.io.File; import java.io.FileOutputStream; @@ -44,66 +40,40 @@ public void onFailure(int statusCode, Header[] headers, Throwable e, File respon onFailure(statusCode, e, response); } - - protected void sendSuccessMessage(int statusCode, File file) { - sendMessage(obtainMessage(SUCCESS_MESSAGE, new Object[]{statusCode, file})); - } - - protected void sendFailureMessage(int statusCode, Header[] headers, Throwable e, File file) { - sendMessage(obtainMessage(FAILURE_MESSAGE, new Object[]{statusCode, headers, e, file})); - } - - protected void handleSuccessMessage(int statusCode, File responseBody) { - onSuccess(statusCode, responseBody); - } - - protected void handleFailureMessage(int statusCode, Header[] headers, Throwable e, File responseBody) { - onFailure(statusCode, headers, e, responseBody); + @Override + public void onFailure(int statusCode, Header[] headers, byte[] responseBody, Throwable error) { + onFailure(statusCode, headers, error, mFile); } - // Methods which emulate android's Handler and Message methods - protected void handleMessage(Message msg) { - Object[] response; - switch (msg.what) { - case SUCCESS_MESSAGE: - response = (Object[]) msg.obj; - handleSuccessMessage((Integer) response[0], (File) response[1]); - break; - case FAILURE_MESSAGE: - response = (Object[]) msg.obj; - handleFailureMessage((Integer) response[0], (Header[]) response[1], (Throwable) response[2], (File) response[3]); - break; - default: - super.handleMessage(msg); - break; - } + @Override + public void onSuccess(int statusCode, Header[] headers, byte[] responseBody) { + onSuccess(statusCode, mFile); } @Override - protected void sendResponseMessage(HttpResponse response) { - StatusLine status = response.getStatusLine(); - - try { - FileOutputStream buffer = new FileOutputStream(this.mFile); - InputStream is = response.getEntity().getContent(); - - int nRead; - byte[] data = new byte[16384]; - - while ((nRead = is.read(data, 0, data.length)) != -1) - buffer.write(data, 0, nRead); - - buffer.flush(); - buffer.close(); - - } catch (IOException e) { - sendFailureMessage(status.getStatusCode(), response.getAllHeaders(), e, this.mFile); - } - - if (status.getStatusCode() >= 300) { - sendFailureMessage(status.getStatusCode(), response.getAllHeaders(), new HttpResponseException(status.getStatusCode(), status.getReasonPhrase()), this.mFile); - } else { - sendSuccessMessage(status.getStatusCode(), this.mFile); - } - } -} \ No newline at end of file + byte[] getResponseData(HttpEntity entity) throws IOException { + if (entity != null) { + InputStream instream = entity.getContent(); + long contentLength = entity.getContentLength(); + FileOutputStream buffer = new FileOutputStream(this.mFile); + if (instream != null) { + try { + byte[] tmp = new byte[BUFFER_SIZE]; + int l, count = 0;; + // do not send messages if request has been cancelled + while ((l = instream.read(tmp)) != -1 && !Thread.currentThread().isInterrupted()) { + count += l; + buffer.write(tmp, 0, l); + sendProgressMessage(count, (int) contentLength); + } + } finally { + instream.close(); + buffer.flush(); + buffer.close(); + } + } + } + return (null); + } + +} diff --git a/library/src/com/loopj/android/http/JsonHttpResponseHandler.java b/library/src/com/loopj/android/http/JsonHttpResponseHandler.java index 33855fc96..f5a3df667 100644 --- a/library/src/com/loopj/android/http/JsonHttpResponseHandler.java +++ b/library/src/com/loopj/android/http/JsonHttpResponseHandler.java @@ -18,8 +18,6 @@ package com.loopj.android.http; -import android.os.Message; - import org.apache.http.Header; import org.apache.http.HttpStatus; import org.json.JSONArray; @@ -27,6 +25,8 @@ import org.json.JSONObject; import org.json.JSONTokener; +import java.io.UnsupportedEncodingException; + /** * Used to intercept and handle the responses from requests made using * {@link AsyncHttpClient}, with automatic parsing into a {@link JSONObject} @@ -40,8 +40,6 @@ * parent class. */ public class JsonHttpResponseHandler extends AsyncHttpResponseHandler { - protected static final int SUCCESS_JSON_MESSAGE = 100; - // // Callbacks to be overridden, typically anonymously // @@ -141,76 +139,35 @@ public void onFailure(int statusCode, Header[] headers, Throwable e, JSONArray e onFailure(statusCode, e, errorResponse); } - - // - // Pre-processing of messages (executes in background threadpool thread) - // - @Override - protected void sendSuccessMessage(final int statusCode, final Header[] headers, final String responseBody) { + public void onSuccess(final int statusCode, final Header[] headers, final byte[] responseBody) { if (statusCode != HttpStatus.SC_NO_CONTENT) { new Thread(new Runnable() { @Override public void run() { try { Object jsonResponse = parseResponse(responseBody); - sendMessage(obtainMessage(SUCCESS_JSON_MESSAGE, new Object[]{statusCode, headers, jsonResponse})); - } catch (JSONException e) { - sendFailureMessage(statusCode, headers, e, responseBody); + if (jsonResponse instanceof JSONObject) { + onSuccess(statusCode, headers, (JSONObject) jsonResponse); + } else if (jsonResponse instanceof JSONArray) { + onSuccess(statusCode, headers, (JSONArray) jsonResponse); + } else if (jsonResponse instanceof String) { + onSuccess(statusCode, headers, (String) jsonResponse); + } else { + onFailure(new JSONException("Unexpected type " + jsonResponse.getClass().getName()), (JSONObject) null); + } + } catch (JSONException ex) { + onFailure(ex, (JSONObject) null); } } }).start(); } else { - sendMessage(obtainMessage(SUCCESS_JSON_MESSAGE, new Object[]{statusCode, headers, new JSONObject()})); + onSuccess(statusCode, headers, new JSONObject()); } } - - // - // Pre-processing of messages (in original calling thread, typically the UI thread) - // - @Override - protected void handleMessage(Message msg) { - switch (msg.what) { - case SUCCESS_JSON_MESSAGE: - Object[] response = (Object[]) msg.obj; - handleSuccessJsonMessage((Integer) response[0], (Header[]) response[1], response[2]); - break; - default: - super.handleMessage(msg); - } - } - - protected void handleSuccessJsonMessage(int statusCode, Header[] headers, Object jsonResponse) { - if (jsonResponse instanceof JSONObject) { - onSuccess(statusCode, headers, (JSONObject) jsonResponse); - } else if (jsonResponse instanceof JSONArray) { - onSuccess(statusCode, headers, (JSONArray) jsonResponse); - } else if (jsonResponse instanceof String) { - onSuccess(statusCode, headers, (String) jsonResponse); - } else { - onFailure(new JSONException("Unexpected type " + jsonResponse.getClass().getName()), (JSONObject) null); - } - } - - protected Object parseResponse(String responseBody) throws JSONException { - if (null == responseBody) - return null; - Object result = null; - //trim the string to prevent start with blank, and test if the string is valid JSON, because the parser don't do this :(. If Json is not valid this will return null - responseBody = responseBody.trim(); - if (responseBody.startsWith("{") || responseBody.startsWith("[")) { - result = new JSONTokener(responseBody).nextValue(); - } - if (result == null) { - result = responseBody; - } - return result; - } - - @Override - protected void handleFailureMessage(final int statusCode, final Header[] headers, final Throwable e, final String responseBody) { + public void onFailure(final int statusCode, final Header[] headers, final byte[] responseBody, final Throwable e) { new Thread(new Runnable() { @Override public void run() { @@ -224,16 +181,34 @@ public void run() { } else if (jsonResponse instanceof String) { onFailure(statusCode, headers, e, (String) jsonResponse); } else { - onFailure(statusCode, headers, e, responseBody); + onFailure(new JSONException("Unexpected type " + jsonResponse.getClass().getName()), (JSONObject) null); } } else { onFailure(e, ""); } } catch (JSONException ex) { - onFailure(statusCode, headers, e, responseBody); + onFailure(ex, (JSONObject) null); } } }).start(); } + + protected Object parseResponse(byte[] responseBody) throws JSONException { + if (null == responseBody) + return null; + Object result = null; + try { + //trim the string to prevent start with blank, and test if the string is valid JSON, because the parser don't do this :(. If Json is not valid this will return null + String jsonString = new String(responseBody, "UTF-8").trim(); + if (jsonString.startsWith("{") || jsonString.startsWith("[")) { + result = new JSONTokener(jsonString).nextValue(); + } + if (result == null) { + result = jsonString; + } + } catch (UnsupportedEncodingException ex) { + } + return result; + } } diff --git a/library/src/com/loopj/android/http/SyncHttpClient.java b/library/src/com/loopj/android/http/SyncHttpClient.java index 879abb1f5..fbaae7ab4 100644 --- a/library/src/com/loopj/android/http/SyncHttpClient.java +++ b/library/src/com/loopj/android/http/SyncHttpClient.java @@ -1,59 +1,59 @@ package com.loopj.android.http; import android.content.Context; -import android.os.Message; - -import org.apache.http.HttpEntity; -import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpUriRequest; +import org.apache.http.conn.scheme.SchemeRegistry; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.protocol.HttpContext; -public abstract class SyncHttpClient extends AsyncHttpClient { - private int responseCode; - /* - * as this is a synchronous request this is just a helping mechanism to pass - * the result back to this method. Therefore the result object has to be a - * field to be accessible - */ - protected String result; - protected AsyncHttpResponseHandler responseHandler = new AsyncHttpResponseHandler() { +public class SyncHttpClient extends AsyncHttpClient { - @Override - protected void sendResponseMessage(HttpResponse response) { - responseCode = response.getStatusLine().getStatusCode(); - super.sendResponseMessage(response); - } + /** + * Creates a new SyncHttpClient with default constructor arguments values + */ + public SyncHttpClient() { + super(false, 80, 443); + } - @Override - protected void sendMessage(Message msg) { - /* - * Dont use the handler and send it directly to the analysis - * (because its all the same thread) - */ - handleMessage(msg); - } + /** + * Creates a new SyncHttpClient. + * + * @param httpPort non-standard HTTP-only port + */ + public SyncHttpClient(int httpPort) { + super(false, httpPort, 443); + } - @Override - public void onSuccess(String content) { - result = content; - } + /** + * Creates a new SyncHttpClient. + * + * @param httpPort non-standard HTTP-only port + * @param httpsPort non-standard HTTPS-only port + */ + public SyncHttpClient(int httpPort, int httpsPort) { + super(false, httpPort, httpsPort); + } - @Override - public void onFailure(Throwable error, String content) { - result = onRequestFailed(error, content); - } - }; + /** + * Creates new SyncHttpClient using given params + * + * @param fixNoHttpResponseException Whether to fix or not issue, by ommiting SSL verification + * @param httpPort HTTP port to be used, must be greater than 0 + * @param httpsPort HTTPS port to be used, must be greater than 0 + */ + public SyncHttpClient(boolean fixNoHttpResponseException, int httpPort, int httpsPort) { + super(fixNoHttpResponseException, httpPort, httpsPort); + } /** - * @return the response code for the last request, might be usefull - * sometimes + * Creates a new SyncHttpClient. + * + * @param schemeRegistry SchemeRegistry to be used */ - public int getResponseCode() { - return responseCode; + public SyncHttpClient(SchemeRegistry schemeRegistry) { + super(schemeRegistry); } - // Private stuff @Override protected void sendRequest(DefaultHttpClient client, HttpContext httpContext, HttpUriRequest uriRequest, @@ -63,67 +63,11 @@ protected void sendRequest(DefaultHttpClient client, uriRequest.addHeader("Content-Type", contentType); } + responseHandler.setUseSynchronousMode(true); + /* * will execute the request directly - */ - new AsyncHttpRequest(client, httpContext, uriRequest, responseHandler) - .run(); + */ + new AsyncHttpRequest(client, httpContext, uriRequest, responseHandler).run(); } - - public abstract String onRequestFailed(Throwable error, String content); - - public void delete(String url, RequestParams queryParams, - AsyncHttpResponseHandler responseHandler) { - delete(getUrlWithQueryString(isUrlEncodingEnabled(), url, queryParams), responseHandler); - } - - public String get(String url, RequestParams params) { - this.get(url, params, responseHandler); - /* - * the response handler will have set the result when this line is - * reached - */ - return result; - } - - public String get(String url) { - this.get(url, null, responseHandler); - return result; - } - - public String put(String url, RequestParams params) { - this.put(url, params, responseHandler); - return result; - } - - public String put(String url) { - this.put(url, null, responseHandler); - return result; - } - - public String post(String url, HttpEntity entity) { - this.post(null, url, entity, null, responseHandler); - return result; - } - - public String post(String url, RequestParams params) { - this.post(url, params, responseHandler); - return result; - } - - public String post(String url) { - this.post(url, null, responseHandler); - return result; - } - - public String delete(String url, RequestParams params) { - this.delete(url, params, responseHandler); - return result; - } - - public String delete(String url) { - this.delete(url, null, responseHandler); - return result; - } - } diff --git a/library/src/com/loopj/android/http/TextHttpResponseHandler.java b/library/src/com/loopj/android/http/TextHttpResponseHandler.java new file mode 100644 index 000000000..ff7bfc872 --- /dev/null +++ b/library/src/com/loopj/android/http/TextHttpResponseHandler.java @@ -0,0 +1,124 @@ +package com.loopj.android.http; + +import org.apache.http.Header; + +import java.io.UnsupportedEncodingException; + +/** + * Used to intercept and handle the responses from requests made using + * {@link AsyncHttpClient}. The {@link #onSuccess(String)} method is + * designed to be anonymously overridden with your own response handling code. + *

    + * Additionally, you can override the {@link #onFailure(String, Throwable)}, + * {@link #onStart()}, and {@link #onFinish()} methods as required. + *

    + * For example: + *

    + *

    + * AsyncHttpClient client = new AsyncHttpClient();
    + * client.get("http://www.google.com/", new TextHttpResponseHandler() {
    + *     @Override
    + *     public void onStart() {
    + *         // Initiated the request
    + *     }
    + *
    + *     @Override
    + *     public void onSuccess(String responseBody) {
    + *         // Successfully got a response
    + *     }
    + * 
    + *     @Override
    + *     public void onFailure(String responseBody, Throwable e) {
    + *         // Response failed :(
    + *     }
    + *
    + *     @Override
    + *     public void onFinish() {
    + *         // Completed the request (either success or failure)
    + *     }
    + * });
    + * 
    + */ +public class TextHttpResponseHandler extends AsyncHttpResponseHandler { + + private String _encoding; + /** + * Creates a new TextHttpResponseHandler + */ + + public TextHttpResponseHandler() + { + this("UTF-8"); + } + + public TextHttpResponseHandler(String encoding) { + super(); + _encoding = encoding; + } + // + // Callbacks to be overridden, typically anonymously + // + + /** + * Fired when a request returns successfully, override to handle in your own + * code + * + * @param responseBody the body of the HTTP response from the server + */ + public void onSuccess(String responseBody) { + } + + /** + * Fired when a request returns successfully, override to handle in your own + * code + * + * @param statusCode the status code of the response + * @param headers HTTP response headers + * @param responseBody the body of the HTTP response from the server + */ + public void onSuccess(int statusCode, Header[] headers, String responseBody) { + onSuccess( responseBody ); + } + + /** + * Fired when a request fails to complete, override to handle in your own + * code + * + * @param responseBody the response body, if any + * @param error the underlying cause of the failure + */ + public void onFailure(String responseBody, Throwable error) { + } + + /** + * Fired when a request fails to complete, override to handle in your own + * code + * + * @param statusCode the status code of the response + * @param headers HTTP response headers + * @param responseBody the response body, if any + * @param error the underlying cause of the failure + */ + public void onFailure(int statusCode, Header[] headers, String responseBody, Throwable error) { + onFailure( responseBody, error ); + } + + @Override + public void onSuccess(int statusCode, Header[] headers, byte[] responseBody) { + try { + onSuccess(statusCode, headers, new String(responseBody, _encoding)); + } catch (UnsupportedEncodingException e) { + onFailure(0, headers, (String) null, e); + } + } + + @Override + public void onFailure(int statusCode, Header[] headers, byte[] responseBody, Throwable error) { + try { + onFailure(statusCode, headers, new String(responseBody, _encoding), error); + } catch (UnsupportedEncodingException e) { + onFailure(0, headers, (String) null, e); + } + } + +} diff --git a/sample/src/main/java/com/loopj/android/http/sample/MainActivity.java b/sample/src/main/java/com/loopj/android/http/sample/MainActivity.java index f5096ae2f..c5b373e6a 100644 --- a/sample/src/main/java/com/loopj/android/http/sample/MainActivity.java +++ b/sample/src/main/java/com/loopj/android/http/sample/MainActivity.java @@ -12,6 +12,7 @@ import com.loopj.android.http.AsyncHttpClient; import com.loopj.android.http.AsyncHttpResponseHandler; +import com.loopj.android.http.TextHttpResponseHandler; import org.apache.http.Header; @@ -59,7 +60,7 @@ public void onClick(View v) { } private void startRequest() { - aclient.get(this, getURLString(), new AsyncHttpResponseHandler() { + aclient.get(this, getURLString(), new TextHttpResponseHandler() { @Override public void onSuccess(int statusCode, Header[] headers, String content) { From dfd8661690261b897fb448a94e2619f2de5359b2 Mon Sep 17 00:00:00 2001 From: Peter Edwards Date: Fri, 18 Oct 2013 14:30:26 +0200 Subject: [PATCH 125/613] Rebasing to latest commit from upstream --- .../http/AsyncHttpResponseHandler.java | 8 +- .../android/http/JsonHttpResponseHandler.java | 90 ++++++++++++------- 2 files changed, 63 insertions(+), 35 deletions(-) diff --git a/library/src/com/loopj/android/http/AsyncHttpResponseHandler.java b/library/src/com/loopj/android/http/AsyncHttpResponseHandler.java index bd8bb3bf2..ffef41ee2 100644 --- a/library/src/com/loopj/android/http/AsyncHttpResponseHandler.java +++ b/library/src/com/loopj/android/http/AsyncHttpResponseHandler.java @@ -161,7 +161,7 @@ public AsyncHttpResponseHandler() { * Fired when the request progress, override to handle in your own code * * @param bytesWritten offset from start of file - * @param totalSize total size of file + * @param totalSize total size of file */ public void onProgress(int bytesWritten, int totalSize) { } @@ -373,6 +373,12 @@ protected void sendMessage(Message msg) { } } + protected void postRunnable(Runnable r) { + if (r != null) { + handler.post(r); + } + } + protected Message obtainMessage(int responseMessage, Object response) { Message msg; if (handler != null) { diff --git a/library/src/com/loopj/android/http/JsonHttpResponseHandler.java b/library/src/com/loopj/android/http/JsonHttpResponseHandler.java index f5a3df667..a5a0bd953 100644 --- a/library/src/com/loopj/android/http/JsonHttpResponseHandler.java +++ b/library/src/com/loopj/android/http/JsonHttpResponseHandler.java @@ -146,18 +146,29 @@ public void onSuccess(final int statusCode, final Header[] headers, final byte[] @Override public void run() { try { - Object jsonResponse = parseResponse(responseBody); - if (jsonResponse instanceof JSONObject) { - onSuccess(statusCode, headers, (JSONObject) jsonResponse); - } else if (jsonResponse instanceof JSONArray) { - onSuccess(statusCode, headers, (JSONArray) jsonResponse); - } else if (jsonResponse instanceof String) { - onSuccess(statusCode, headers, (String) jsonResponse); - } else { - onFailure(new JSONException("Unexpected type " + jsonResponse.getClass().getName()), (JSONObject) null); - } - } catch (JSONException ex) { - onFailure(ex, (JSONObject) null); + final Object jsonResponse = parseResponse(responseBody); + postRunnable(new Runnable() { + @Override + public void run() { + if (jsonResponse instanceof JSONObject) { + onSuccess(statusCode, headers, (JSONObject) jsonResponse); + } else if (jsonResponse instanceof JSONArray) { + onSuccess(statusCode, headers, (JSONArray) jsonResponse); + } else if (jsonResponse instanceof String) { + onSuccess(statusCode, headers, (String) jsonResponse); + } else { + onFailure(new JSONException("Unexpected type " + jsonResponse.getClass().getName()), (JSONObject) null); + } + + } + }); + } catch (final JSONException ex) { + postRunnable(new Runnable() { + @Override + public void run() { + onFailure(ex, (JSONObject) null); + } + }); } } }).start(); @@ -168,30 +179,41 @@ public void run() { @Override public void onFailure(final int statusCode, final Header[] headers, final byte[] responseBody, final Throwable e) { - new Thread(new Runnable() { - @Override - public void run() { - try { - if (responseBody != null) { - Object jsonResponse = parseResponse(responseBody); - if (jsonResponse instanceof JSONObject) { - onFailure(statusCode, headers, e, (JSONObject) jsonResponse); - } else if (jsonResponse instanceof JSONArray) { - onFailure(statusCode, headers, e, (JSONArray) jsonResponse); - } else if (jsonResponse instanceof String) { - onFailure(statusCode, headers, e, (String) jsonResponse); - } else { - onFailure(new JSONException("Unexpected type " + jsonResponse.getClass().getName()), (JSONObject) null); - } - } else { - onFailure(e, ""); + if (responseBody != null) { + new Thread(new Runnable() { + @Override + public void run() { + try { + final Object jsonResponse = parseResponse(responseBody); + postRunnable(new Runnable() { + @Override + public void run() { + if (jsonResponse instanceof JSONObject) { + onFailure(statusCode, headers, e, (JSONObject) jsonResponse); + } else if (jsonResponse instanceof JSONArray) { + onFailure(statusCode, headers, e, (JSONArray) jsonResponse); + } else if (jsonResponse instanceof String) { + onFailure(statusCode, headers, e, (String) jsonResponse); + } else { + onFailure(new JSONException("Unexpected type " + jsonResponse.getClass().getName()), (JSONObject) null); + } + } + }); + + } catch (final JSONException ex) { + postRunnable(new Runnable() { + @Override + public void run() { + onFailure(ex, (JSONObject) null ); + } + }); + } - } catch (JSONException ex) { - onFailure(ex, (JSONObject) null); } - } - }).start(); - + }).start(); + } else { + onFailure(e, ""); + } } protected Object parseResponse(byte[] responseBody) throws JSONException { From 2d6622315a2a420c2cce7aea15b85232fd6b23c0 Mon Sep 17 00:00:00 2001 From: davidlee Date: Fri, 18 Oct 2013 22:53:32 -0700 Subject: [PATCH 126/613] Fixing ArtifactID I was trying to compile from Maven using 'com.loopj.android:async-http-client:1.4.3' which doesn't work. It should be: 'com.loopj.android:android-async-http:1.4.3' --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a576fb38e..bcb461161 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ http://central.maven.org/maven2/com/loopj/android/android-async-http/ ``` Maven URL: http://repo1.maven.org/maven2/ GroupId: com.loopj.android -ArtifactId: async-http-client +ArtifactId: android-async-http Version: 1.4.3 Packaging: JAR or AAR ``` From 2b91948deb0cd903d3d7e96263eaff3e599c6d89 Mon Sep 17 00:00:00 2001 From: mareksebera Date: Sat, 19 Oct 2013 08:09:35 +0200 Subject: [PATCH 127/613] Invalid duplicate onFailure call --- .../http/AsyncHttpResponseHandler.java | 40 +++++++++---------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/library/src/com/loopj/android/http/AsyncHttpResponseHandler.java b/library/src/com/loopj/android/http/AsyncHttpResponseHandler.java index ffef41ee2..65582041e 100644 --- a/library/src/com/loopj/android/http/AsyncHttpResponseHandler.java +++ b/library/src/com/loopj/android/http/AsyncHttpResponseHandler.java @@ -104,13 +104,13 @@ static class ResponderHandler extends Handler { ResponderHandler(AsyncHttpResponseHandler service) { mResponder = new WeakReference(service); } + @Override - public void handleMessage(Message msg) - { + public void handleMessage(Message msg) { AsyncHttpResponseHandler service = mResponder.get(); - if (service != null) { - service.handleMessage(msg); - } + if (service != null) { + service.handleMessage(msg); + } } } @@ -216,9 +216,9 @@ public void onSuccess(int statusCode, String content) { /** * Fired when a request returns successfully, override to handle in your own code * - * @param statusCode the status code of the response - * @param headers return headers, if any - * @param responseBody the body of the HTTP response from the server + * @param statusCode the status code of the response + * @param headers return headers, if any + * @param responseBody the body of the HTTP response from the server */ public void onSuccess(int statusCode, Header[] headers, byte[] responseBody) { try { @@ -229,6 +229,7 @@ public void onSuccess(int statusCode, Header[] headers, byte[] responseBody) { onFailure(statusCode, headers, e, (String) null); } } + /** * Fired when a request fails to complete, override to handle in your own code * @@ -284,29 +285,28 @@ public void onFailure(int statusCode, Header[] headers, Throwable error, String /** * Fired when a request fails to complete, override to handle in your own code * - * @param statusCode return HTTP status code - * @param headers return headers, if any - * @param responseBody the response body, if any - * @param error the underlying cause of the failure + * @param statusCode return HTTP status code + * @param headers return headers, if any + * @param responseBody the response body, if any + * @param error the underlying cause of the failure */ public void onFailure(int statusCode, Header[] headers, byte[] responseBody, Throwable error) { String response = null; try { response = new String(responseBody, getCharset()); + onFailure(statusCode, headers, error, response); } catch (UnsupportedEncodingException e) { Log.e(LOG_TAG, e.toString()); onFailure(statusCode, headers, e, null); } - onFailure(statusCode, headers, error, response); } /** * Fired when a retry occurs, override to handle in your own code - * */ public void onRetry() { } - + // // Pre-processing of messages (executes in background threadpool thread) @@ -333,9 +333,9 @@ protected void sendFinishMessage() { } protected void sendRetryMessage() { - sendMessage(obtainMessage(RETRY_MESSAGE, null)); + sendMessage(obtainMessage(RETRY_MESSAGE, null)); } - + // Methods which emulate android's Handler and Message methods protected void handleMessage(Message msg) { Object[] response; @@ -361,7 +361,7 @@ protected void handleMessage(Message msg) { break; case RETRY_MESSAGE: onRetry(); - break; + break; } } @@ -423,7 +423,7 @@ byte[] getResponseData(HttpEntity entity) throws IOException { if (contentLength < 0) { contentLength = BUFFER_SIZE; } - try{ + try { ByteArrayBuffer buffer = new ByteArrayBuffer((int) contentLength); try { byte[] tmp = new byte[BUFFER_SIZE]; @@ -438,7 +438,7 @@ byte[] getResponseData(HttpEntity entity) throws IOException { instream.close(); } responseBody = buffer.buffer(); - } catch( OutOfMemoryError e ) { + } catch (OutOfMemoryError e) { System.gc(); throw new IOException("File too large to fit into available memory"); } From 92d4d01ce72c5937e71dd6b71b9d76ac969b1691 Mon Sep 17 00:00:00 2001 From: mareksebera Date: Sat, 19 Oct 2013 08:11:45 +0200 Subject: [PATCH 128/613] Not just javadoc --- library/src/com/loopj/android/http/AsyncHttpResponseHandler.java | 1 + 1 file changed, 1 insertion(+) diff --git a/library/src/com/loopj/android/http/AsyncHttpResponseHandler.java b/library/src/com/loopj/android/http/AsyncHttpResponseHandler.java index 65582041e..bb6ba5380 100644 --- a/library/src/com/loopj/android/http/AsyncHttpResponseHandler.java +++ b/library/src/com/loopj/android/http/AsyncHttpResponseHandler.java @@ -134,6 +134,7 @@ public void setUseSynchronousMode(Boolean value) { * @see Charset * @deprecated use {@link com.loopj.android.http.TextHttpResponseHandler} instead */ + @Deprecated public void setCharset(final String charset) { this.responseCharset = charset; } From 69be8ed3f2ea8c09cfc026f17077e30340354ad3 Mon Sep 17 00:00:00 2001 From: mareksebera Date: Sat, 19 Oct 2013 08:17:45 +0200 Subject: [PATCH 129/613] Code style --- .../src/com/loopj/android/http/AsyncHttpResponseHandler.java | 2 +- .../com/loopj/android/http/FileAsyncHttpResponseHandler.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/library/src/com/loopj/android/http/AsyncHttpResponseHandler.java b/library/src/com/loopj/android/http/AsyncHttpResponseHandler.java index bb6ba5380..e8ff7d919 100644 --- a/library/src/com/loopj/android/http/AsyncHttpResponseHandler.java +++ b/library/src/com/loopj/android/http/AsyncHttpResponseHandler.java @@ -445,6 +445,6 @@ byte[] getResponseData(HttpEntity entity) throws IOException { } } } - return (responseBody); + return responseBody; } } diff --git a/library/src/com/loopj/android/http/FileAsyncHttpResponseHandler.java b/library/src/com/loopj/android/http/FileAsyncHttpResponseHandler.java index f528982fe..76049ccdf 100644 --- a/library/src/com/loopj/android/http/FileAsyncHttpResponseHandler.java +++ b/library/src/com/loopj/android/http/FileAsyncHttpResponseHandler.java @@ -59,7 +59,7 @@ byte[] getResponseData(HttpEntity entity) throws IOException { if (instream != null) { try { byte[] tmp = new byte[BUFFER_SIZE]; - int l, count = 0;; + int l, count = 0; // do not send messages if request has been cancelled while ((l = instream.read(tmp)) != -1 && !Thread.currentThread().isInterrupted()) { count += l; @@ -73,7 +73,7 @@ byte[] getResponseData(HttpEntity entity) throws IOException { } } } - return (null); + return null; } } From b634d4c94749a6cb10ee645c03c7c684ac0c3daf Mon Sep 17 00:00:00 2001 From: mareksebera Date: Sat, 19 Oct 2013 08:17:57 +0200 Subject: [PATCH 130/613] Not ommiting exception --- .../src/com/loopj/android/http/JsonHttpResponseHandler.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/library/src/com/loopj/android/http/JsonHttpResponseHandler.java b/library/src/com/loopj/android/http/JsonHttpResponseHandler.java index a5a0bd953..8bdb2ea60 100644 --- a/library/src/com/loopj/android/http/JsonHttpResponseHandler.java +++ b/library/src/com/loopj/android/http/JsonHttpResponseHandler.java @@ -18,6 +18,8 @@ package com.loopj.android.http; +import android.util.Log; + import org.apache.http.Header; import org.apache.http.HttpStatus; import org.json.JSONArray; @@ -40,6 +42,7 @@ * parent class. */ public class JsonHttpResponseHandler extends AsyncHttpResponseHandler { + private static final String LOG_TAG = "JsonHttpResponseHandler"; // // Callbacks to be overridden, typically anonymously // @@ -204,7 +207,7 @@ public void run() { postRunnable(new Runnable() { @Override public void run() { - onFailure(ex, (JSONObject) null ); + onFailure(ex, (JSONObject) null); } }); @@ -230,6 +233,7 @@ protected Object parseResponse(byte[] responseBody) throws JSONException { result = jsonString; } } catch (UnsupportedEncodingException ex) { + Log.d(LOG_TAG, "JSON Parsing failed", ex); } return result; } From aa00c992fe9b261ec564118f0a3c125f649b6bf0 Mon Sep 17 00:00:00 2001 From: mareksebera Date: Sat, 19 Oct 2013 08:19:57 +0200 Subject: [PATCH 131/613] Default charset --- .../http/AsyncHttpResponseHandler.java | 3 +- .../android/http/TextHttpResponseHandler.java | 56 +++++++++---------- 2 files changed, 30 insertions(+), 29 deletions(-) diff --git a/library/src/com/loopj/android/http/AsyncHttpResponseHandler.java b/library/src/com/loopj/android/http/AsyncHttpResponseHandler.java index e8ff7d919..7d67bb595 100644 --- a/library/src/com/loopj/android/http/AsyncHttpResponseHandler.java +++ b/library/src/com/loopj/android/http/AsyncHttpResponseHandler.java @@ -93,7 +93,8 @@ public class AsyncHttpResponseHandler { protected static final int BUFFER_SIZE = 4096; private Handler handler; - private String responseCharset = "UTF-8"; + public static final String DEFAULT_CHARSET = "UTF-8"; + private String responseCharset = DEFAULT_CHARSET; private Boolean useSynchronousMode = false; // avoid leaks by using a non-anonymous handler class diff --git a/library/src/com/loopj/android/http/TextHttpResponseHandler.java b/library/src/com/loopj/android/http/TextHttpResponseHandler.java index ff7bfc872..f482095af 100644 --- a/library/src/com/loopj/android/http/TextHttpResponseHandler.java +++ b/library/src/com/loopj/android/http/TextHttpResponseHandler.java @@ -5,15 +5,15 @@ import java.io.UnsupportedEncodingException; /** - * Used to intercept and handle the responses from requests made using - * {@link AsyncHttpClient}. The {@link #onSuccess(String)} method is + * Used to intercept and handle the responses from requests made using + * {@link AsyncHttpClient}. The {@link #onSuccess(String)} method is * designed to be anonymously overridden with your own response handling code. - *

    + *

    * Additionally, you can override the {@link #onFailure(String, Throwable)}, * {@link #onStart()}, and {@link #onFinish()} methods as required. - *

    + *

    * For example: - *

    + *

    *

      * AsyncHttpClient client = new AsyncHttpClient();
      * client.get("http://www.google.com/", new TextHttpResponseHandler() {
    @@ -26,7 +26,7 @@
      *     public void onSuccess(String responseBody) {
      *         // Successfully got a response
      *     }
    - * 
    + *
      *     @Override
      *     public void onFailure(String responseBody, Throwable e) {
      *         // Response failed :(
    @@ -42,15 +42,15 @@
     public class TextHttpResponseHandler extends AsyncHttpResponseHandler {
     
         private String _encoding;
    +
         /**
          * Creates a new TextHttpResponseHandler
          */
    -    
    -    public TextHttpResponseHandler()
    -    {
    -        this("UTF-8");
    +
    +    public TextHttpResponseHandler() {
    +        this(DEFAULT_CHARSET);
         }
    -    
    +
         public TextHttpResponseHandler(String encoding) {
             super();
             _encoding = encoding;
    @@ -62,45 +62,45 @@ public TextHttpResponseHandler(String encoding) {
         /**
          * Fired when a request returns successfully, override to handle in your own
          * code
    -     * 
    +     *
          * @param responseBody the body of the HTTP response from the server
          */
         public void onSuccess(String responseBody) {
         }
    -    
    +
         /**
          * Fired when a request returns successfully, override to handle in your own
          * code
    -     * 
    -     * @param statusCode the status code of the response
    -     * @param headers HTTP response headers
    +     *
    +     * @param statusCode   the status code of the response
    +     * @param headers      HTTP response headers
          * @param responseBody the body of the HTTP response from the server
          */
         public void onSuccess(int statusCode, Header[] headers, String responseBody) {
    -      onSuccess( responseBody );
    +        onSuccess(responseBody);
         }
     
         /**
          * Fired when a request fails to complete, override to handle in your own
          * code
    -     * 
    +     *
          * @param responseBody the response body, if any
    -     * @param error the underlying cause of the failure
    -     */    
    +     * @param error        the underlying cause of the failure
    +     */
         public void onFailure(String responseBody, Throwable error) {
         }
    -    
    +
         /**
          * Fired when a request fails to complete, override to handle in your own
          * code
    -     * 
    -     * @param statusCode the status code of the response
    -     * @param headers HTTP response headers
    +     *
    +     * @param statusCode   the status code of the response
    +     * @param headers      HTTP response headers
          * @param responseBody the response body, if any
    -     * @param error the underlying cause of the failure
    -     */    
    +     * @param error        the underlying cause of the failure
    +     */
         public void onFailure(int statusCode, Header[] headers, String responseBody, Throwable error) {
    -      onFailure( responseBody, error );
    +        onFailure(responseBody, error);
         }
     
         @Override
    @@ -120,5 +120,5 @@ public void onFailure(int statusCode, Header[] headers, byte[] responseBody, Thr
                 onFailure(0, headers, (String) null, e);
             }
         }
    -    
    +
     }
    
    From 23db9dcb0bb7ae1647414138c1f0d618255adeda Mon Sep 17 00:00:00 2001
    From: mareksebera 
    Date: Sat, 19 Oct 2013 08:34:38 +0200
    Subject: [PATCH 132/613] Timeout and maxConnections gets/sets
    
    ---
     .../loopj/android/http/AsyncHttpClient.java   | 58 +++++++++++++++----
     1 file changed, 46 insertions(+), 12 deletions(-)
    
    diff --git a/library/src/com/loopj/android/http/AsyncHttpClient.java b/library/src/com/loopj/android/http/AsyncHttpClient.java
    index f9ab1ad05..ee4191ebf 100644
    --- a/library/src/com/loopj/android/http/AsyncHttpClient.java
    +++ b/library/src/com/loopj/android/http/AsyncHttpClient.java
    @@ -108,8 +108,8 @@ public class AsyncHttpClient {
         private static final String ENCODING_GZIP = "gzip";
         private static final String LOG_TAG = "AsyncHttpClient";
     
    -    private static int maxConnections = DEFAULT_MAX_CONNECTIONS;
    -    private static int socketTimeout = DEFAULT_SOCKET_TIMEOUT;
    +    private int maxConnections = DEFAULT_MAX_CONNECTIONS;
    +    private int timeout = DEFAULT_SOCKET_TIMEOUT;
     
         private final DefaultHttpClient httpClient;
         private final HttpContext httpContext;
    @@ -118,7 +118,6 @@ public class AsyncHttpClient {
         private final Map clientHeaderMap;
         private boolean isUrlEncodingEnabled = true;
     
    -
         /**
          * Creates a new AsyncHttpClient with default constructor arguments values
          */
    @@ -202,12 +201,12 @@ public AsyncHttpClient(SchemeRegistry schemeRegistry) {
     
             BasicHttpParams httpParams = new BasicHttpParams();
     
    -        ConnManagerParams.setTimeout(httpParams, socketTimeout);
    +        ConnManagerParams.setTimeout(httpParams, timeout);
             ConnManagerParams.setMaxConnectionsPerRoute(httpParams, new ConnPerRouteBean(maxConnections));
             ConnManagerParams.setMaxTotalConnections(httpParams, DEFAULT_MAX_CONNECTIONS);
     
    -        HttpConnectionParams.setSoTimeout(httpParams, socketTimeout);
    -        HttpConnectionParams.setConnectionTimeout(httpParams, socketTimeout);
    +        HttpConnectionParams.setSoTimeout(httpParams, timeout);
    +        HttpConnectionParams.setConnectionTimeout(httpParams, timeout);
             HttpConnectionParams.setTcpNoDelay(httpParams, true);
             HttpConnectionParams.setSocketBufferSize(httpParams, DEFAULT_SOCKET_BUFFER_SIZE);
     
    @@ -322,16 +321,51 @@ public void setUserAgent(String userAgent) {
             HttpProtocolParams.setUserAgent(this.httpClient.getParams(), userAgent);
         }
     
    +
    +    /**
    +     * Returns current limit of parallel connections
    +     *
    +     * @return maximum limit of parallel connections, default is 10
    +     */
    +    public int getMaxConnections() {
    +        return maxConnections;
    +    }
    +
    +    /**
    +     * Sets maximum limit of parallel connections
    +     *
    +     * @param maxConnections maximum parallel connections, must be at least 1
    +     */
    +    public void setMaxConnections(int maxConnections) {
    +        if (maxConnections < 1)
    +            maxConnections = DEFAULT_MAX_CONNECTIONS;
    +        this.maxConnections = maxConnections;
    +        final HttpParams httpParams = this.httpClient.getParams();
    +        ConnManagerParams.setMaxConnectionsPerRoute(httpParams, new ConnPerRouteBean(this.maxConnections));
    +    }
    +
    +    /**
    +     * Returns current socket timeout limit (milliseconds), default is 10000 (10sec)
    +     *
    +     * @return Socket Timeout limit in milliseconds
    +     */
    +    public int getTimeout() {
    +        return timeout;
    +    }
    +
         /**
    -     * Set the connection timeout. By default, 10 seconds.
    +     * Set the connection and socket timeout. By default, 10 seconds.
          *
    -     * @param timeout the connect/socket timeout in milliseconds
    +     * @param timeout the connect/socket timeout in milliseconds, at least 1 second
          */
         public void setTimeout(int timeout) {
    +        if (timeout < 1000)
    +            timeout = DEFAULT_SOCKET_TIMEOUT;
    +        this.timeout = timeout;
             final HttpParams httpParams = this.httpClient.getParams();
    -        ConnManagerParams.setTimeout(httpParams, timeout);
    -        HttpConnectionParams.setSoTimeout(httpParams, timeout);
    -        HttpConnectionParams.setConnectionTimeout(httpParams, timeout);
    +        ConnManagerParams.setTimeout(httpParams, this.timeout);
    +        HttpConnectionParams.setSoTimeout(httpParams, this.timeout);
    +        HttpConnectionParams.setConnectionTimeout(httpParams, this.timeout);
         }
     
         /**
    @@ -897,7 +931,7 @@ private HttpEntity paramsToEntity(RequestParams params, AsyncHttpResponseHandler
             return entity;
         }
     
    -    public boolean isUrlEncodingEnabled(){
    +    public boolean isUrlEncodingEnabled() {
             return isUrlEncodingEnabled;
         }
     
    
    From a17426d59427a64e6e8d164fe1d7f733d6f757e5 Mon Sep 17 00:00:00 2001
    From: mareksebera 
    Date: Sat, 19 Oct 2013 08:49:21 +0200
    Subject: [PATCH 133/613] Fixed sample
    
    ---
     sample/AndroidManifest.xml                     | 18 ------------------
     .../android/http/sample/MainActivity.java      |  1 -
     sample/src/main/res/layout/activity_main.xml   |  6 +++---
     sample/src/main/res/values/strings.xml         |  4 +++-
     4 files changed, 6 insertions(+), 23 deletions(-)
     delete mode 100644 sample/AndroidManifest.xml
    
    diff --git a/sample/AndroidManifest.xml b/sample/AndroidManifest.xml
    deleted file mode 100644
    index d7ef938fa..000000000
    --- a/sample/AndroidManifest.xml
    +++ /dev/null
    @@ -1,18 +0,0 @@
    -
    -
    -
    -    
    -
    -    
    -        
    -    
    -
    - 
    diff --git a/sample/src/main/java/com/loopj/android/http/sample/MainActivity.java b/sample/src/main/java/com/loopj/android/http/sample/MainActivity.java
    index c5b373e6a..c49198ac1 100644
    --- a/sample/src/main/java/com/loopj/android/http/sample/MainActivity.java
    +++ b/sample/src/main/java/com/loopj/android/http/sample/MainActivity.java
    @@ -11,7 +11,6 @@
     import android.widget.Toast;
     
     import com.loopj.android.http.AsyncHttpClient;
    -import com.loopj.android.http.AsyncHttpResponseHandler;
     import com.loopj.android.http.TextHttpResponseHandler;
     
     import org.apache.http.Header;
    diff --git a/sample/src/main/res/layout/activity_main.xml b/sample/src/main/res/layout/activity_main.xml
    index 27cb01e6e..7d92ae614 100644
    --- a/sample/src/main/res/layout/activity_main.xml
    +++ b/sample/src/main/res/layout/activity_main.xml
    @@ -25,13 +25,13 @@
                     android:layout_weight="1"
                     android:inputType="textUri"
                     android:saveEnabled="true"
    -                android:text="https://raw.github.com/loopj/android-async-http/master/README.md" />
    +                android:text="@string/default_url" />
     
                 
    */ -public class TextHttpResponseHandler extends AsyncHttpResponseHandler { - - private String _encoding; +public abstract class TextHttpResponseHandler extends AsyncHttpResponseHandler { /** * Creates a new TextHttpResponseHandler @@ -53,35 +51,13 @@ public TextHttpResponseHandler() { public TextHttpResponseHandler(String encoding) { super(); - _encoding = encoding; + setCharset(encoding); } + // // Callbacks to be overridden, typically anonymously // - /** - * Fired when a request returns successfully, override to handle in your own - * code - * - * @param responseBody the body of the HTTP response from the server - */ - @Override - public void onSuccess(String responseBody) { - } - - /** - * Fired when a request returns successfully, override to handle in your own - * code - * - * @param statusCode the status code of the response - * @param headers HTTP response headers - * @param responseBody the body of the HTTP response from the server - */ - @Override - public void onSuccess(int statusCode, Header[] headers, String responseBody) { - onSuccess(responseBody); - } - /** * Fired when a request fails to complete, override to handle in your own * code @@ -108,7 +84,7 @@ public void onFailure(int statusCode, Header[] headers, String responseBody, Thr @Override public void onSuccess(int statusCode, Header[] headers, byte[] responseBody) { try { - onSuccess(statusCode, headers, new String(responseBody, _encoding)); + onSuccess(statusCode, headers, new String(responseBody, getCharset())); } catch (UnsupportedEncodingException e) { onFailure(0, headers, (String) null, e); } @@ -117,7 +93,7 @@ public void onSuccess(int statusCode, Header[] headers, byte[] responseBody) { @Override public void onFailure(int statusCode, Header[] headers, byte[] responseBody, Throwable error) { try { - onFailure(statusCode, headers, new String(responseBody, _encoding), error); + onFailure(statusCode, headers, new String(responseBody, getCharset()), error); } catch (UnsupportedEncodingException e) { onFailure(0, headers, (String) null, e); } From c4a3c61186bba617fd8be7d5ccdb56b32c069b9c Mon Sep 17 00:00:00 2001 From: mareksebera Date: Sat, 19 Oct 2013 09:09:36 +0200 Subject: [PATCH 136/613] Code cleanup --- library/src/com/loopj/android/http/AsyncHttpRequest.java | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/library/src/com/loopj/android/http/AsyncHttpRequest.java b/library/src/com/loopj/android/http/AsyncHttpRequest.java index db3891ed7..1f7c8a3a9 100644 --- a/library/src/com/loopj/android/http/AsyncHttpRequest.java +++ b/library/src/com/loopj/android/http/AsyncHttpRequest.java @@ -19,18 +19,13 @@ package com.loopj.android.http; import org.apache.http.HttpResponse; -import org.apache.http.client.ClientProtocolException; import org.apache.http.client.HttpRequestRetryHandler; import org.apache.http.client.methods.HttpUriRequest; -import org.apache.http.conn.ConnectTimeoutException; import org.apache.http.impl.client.AbstractHttpClient; import org.apache.http.protocol.HttpContext; import java.io.IOException; -import java.net.ConnectException; import java.net.MalformedURLException; -import java.net.SocketException; -import java.net.SocketTimeoutException; import java.net.UnknownHostException; class AsyncHttpRequest implements Runnable { @@ -57,7 +52,7 @@ public void run() { makeRequestWithRetries(); } catch (IOException e) { if (responseHandler != null) { - responseHandler.sendFailureMessage(0, null, (byte[]) null, e); + responseHandler.sendFailureMessage(0, null, null, e); } } From de38f39d9890628296edb44c4b3027190388c5f8 Mon Sep 17 00:00:00 2001 From: mareksebera Date: Sat, 19 Oct 2013 09:10:39 +0200 Subject: [PATCH 137/613] De-deprecating hidden attribute --- library/src/com/loopj/android/http/AsyncHttpClient.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/library/src/com/loopj/android/http/AsyncHttpClient.java b/library/src/com/loopj/android/http/AsyncHttpClient.java index ee4191ebf..cc1f97d6c 100644 --- a/library/src/com/loopj/android/http/AsyncHttpClient.java +++ b/library/src/com/loopj/android/http/AsyncHttpClient.java @@ -95,8 +95,6 @@ *
    */ public class AsyncHttpClient { - // This property won't be available soon, don't use it - @Deprecated private static final String VERSION = "1.4.4"; private static final int DEFAULT_MAX_CONNECTIONS = 10; From 40a07c77a9a7fe9ea5f04d089bb64b2595d70576 Mon Sep 17 00:00:00 2001 From: mareksebera Date: Sat, 19 Oct 2013 09:14:55 +0200 Subject: [PATCH 138/613] NPE avoid with error log --- .../android/http/AsyncHttpResponseHandler.java | 17 +++++++++++++---- .../android/http/BinaryHttpResponseHandler.java | 5 ++--- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/library/src/com/loopj/android/http/AsyncHttpResponseHandler.java b/library/src/com/loopj/android/http/AsyncHttpResponseHandler.java index db93412e4..34b7d79f4 100644 --- a/library/src/com/loopj/android/http/AsyncHttpResponseHandler.java +++ b/library/src/com/loopj/android/http/AsyncHttpResponseHandler.java @@ -343,11 +343,17 @@ protected void handleMessage(Message msg) { switch (msg.what) { case SUCCESS_MESSAGE: response = (Object[]) msg.obj; - onSuccess((Integer) response[0], (Header[]) response[1], (byte[]) response[2]); + if (response != null && response.length >= 3) + onSuccess((Integer) response[0], (Header[]) response[1], (byte[]) response[2]); + else + Log.e(LOG_TAG, "SUCCESS_MESSAGE didn't got enough params"); break; case FAILURE_MESSAGE: response = (Object[]) msg.obj; - onFailure((Integer) response[0], (Header[]) response[1], (byte[]) response[2], (Throwable) response[3]); + if (response != null && response.length >= 4) + onFailure((Integer) response[0], (Header[]) response[1], (byte[]) response[2], (Throwable) response[3]); + else + Log.e(LOG_TAG, "FAILURE_MESSAGE didn't got enough params"); break; case START_MESSAGE: onStart(); @@ -357,7 +363,10 @@ protected void handleMessage(Message msg) { break; case PROGRESS_MESSAGE: response = (Object[]) msg.obj; - onProgress((Integer) response[0], (Integer) response[1]); + if (response != null && response.length >= 2) + onProgress((Integer) response[0], (Integer) response[1]); + else + Log.e(LOG_TAG, "PROGRESS_MESSAGE didn't got enough params"); break; case RETRY_MESSAGE: onRetry(); @@ -398,7 +407,7 @@ void sendResponseMessage(HttpResponse response) throws IOException { // do not process if request has been cancelled if (!Thread.currentThread().isInterrupted()) { StatusLine status = response.getStatusLine(); - byte[] responseBody = null; + byte[] responseBody; responseBody = getResponseData(response.getEntity()); // additional cancellation check as getResponseData() can take non-zero time to process if (!Thread.currentThread().isInterrupted()) { diff --git a/library/src/com/loopj/android/http/BinaryHttpResponseHandler.java b/library/src/com/loopj/android/http/BinaryHttpResponseHandler.java index 91adc5601..5293b6556 100644 --- a/library/src/com/loopj/android/http/BinaryHttpResponseHandler.java +++ b/library/src/com/loopj/android/http/BinaryHttpResponseHandler.java @@ -134,10 +134,9 @@ public void onFailure(int statusCode, Header[] headers, byte[] responseData, Thr protected void sendResponseMessage(HttpResponse response) throws IOException { StatusLine status = response.getStatusLine(); Header[] contentTypeHeaders = response.getHeaders("Content-Type"); - byte[] responseBody = null; if (contentTypeHeaders.length != 1) { //malformed/ambiguous HTTP Header, ABORT! - sendFailureMessage(status.getStatusCode(), response.getAllHeaders(), (byte[]) null, new HttpResponseException(status.getStatusCode(), "None, or more than one, Content-Type Header found!")); + sendFailureMessage(status.getStatusCode(), response.getAllHeaders(), null, new HttpResponseException(status.getStatusCode(), "None, or more than one, Content-Type Header found!")); return; } Header contentTypeHeader = contentTypeHeaders[0]; @@ -149,7 +148,7 @@ protected void sendResponseMessage(HttpResponse response) throws IOException { } if (!foundAllowedContentType) { //Content-Type not in allowed list, ABORT! - sendFailureMessage(status.getStatusCode(), response.getAllHeaders(), (byte[]) null, new HttpResponseException(status.getStatusCode(), "Content-Type not allowed!")); + sendFailureMessage(status.getStatusCode(), response.getAllHeaders(), null, new HttpResponseException(status.getStatusCode(), "Content-Type not allowed!")); return; } super.sendResponseMessage( response ); From bab2a050052403b44ee129eb4c6e0424614db2ac Mon Sep 17 00:00:00 2001 From: mareksebera Date: Sat, 19 Oct 2013 09:19:45 +0200 Subject: [PATCH 139/613] Unused throw --- library/src/com/loopj/android/http/MySSLSocketFactory.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/library/src/com/loopj/android/http/MySSLSocketFactory.java b/library/src/com/loopj/android/http/MySSLSocketFactory.java index fdfbffc9d..86eeaab98 100644 --- a/library/src/com/loopj/android/http/MySSLSocketFactory.java +++ b/library/src/com/loopj/android/http/MySSLSocketFactory.java @@ -4,7 +4,6 @@ import java.io.IOException; import java.net.Socket; -import java.net.UnknownHostException; import java.security.KeyManagementException; import java.security.KeyStore; import java.security.KeyStoreException; @@ -48,7 +47,7 @@ public void checkServerTrusted( } @Override - public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException, UnknownHostException { + public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException { return sslContext.getSocketFactory().createSocket(socket, host, port, autoClose); } From 5ea559237340a1fcf3169304f639294ea024d051 Mon Sep 17 00:00:00 2001 From: mareksebera Date: Sat, 19 Oct 2013 09:21:29 +0200 Subject: [PATCH 140/613] Added log message, to notify about target method --- .../src/com/loopj/android/http/JsonHttpResponseHandler.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/library/src/com/loopj/android/http/JsonHttpResponseHandler.java b/library/src/com/loopj/android/http/JsonHttpResponseHandler.java index 8bdb2ea60..e92867b0f 100644 --- a/library/src/com/loopj/android/http/JsonHttpResponseHandler.java +++ b/library/src/com/loopj/android/http/JsonHttpResponseHandler.java @@ -215,7 +215,8 @@ public void run() { } }).start(); } else { - onFailure(e, ""); + Log.v(LOG_TAG, "response body is null, calling onFailure(Throwable, JSONObject)"); + onFailure(e, (JSONObject) null); } } @@ -233,7 +234,8 @@ protected Object parseResponse(byte[] responseBody) throws JSONException { result = jsonString; } } catch (UnsupportedEncodingException ex) { - Log.d(LOG_TAG, "JSON Parsing failed", ex); + Log.v(LOG_TAG, "JSON parsing failed, calling onFailure(Throwable, JSONObject)"); + onFailure(ex, (JSONObject) null); } return result; } From 9440204ab03608d67c69053728515de6927845cf Mon Sep 17 00:00:00 2001 From: mareksebera Date: Sat, 19 Oct 2013 09:22:43 +0200 Subject: [PATCH 141/613] Simplified --- .../loopj/android/http/SimpleMultipartEntity.java | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/library/src/com/loopj/android/http/SimpleMultipartEntity.java b/library/src/com/loopj/android/http/SimpleMultipartEntity.java index 30346ffe0..9f8614afc 100644 --- a/library/src/com/loopj/android/http/SimpleMultipartEntity.java +++ b/library/src/com/loopj/android/http/SimpleMultipartEntity.java @@ -148,22 +148,12 @@ private byte[] createContentType(String type) { } private byte[] createContentDisposition(final String key) { - return new StringBuilder() - .append("Content-Disposition: form-data; name=\"") - .append(key) - .append("\"\r\n") - .toString() + return ("Content-Disposition: form-data; name=\"" + key + "\"\r\n") .getBytes(); } private byte[] createContentDisposition(final String key, final String fileName) { - return new StringBuilder() - .append("Content-Disposition: form-data; name=\"") - .append(key) - .append("\"; filename=\"") - .append(fileName) - .append("\"\r\n") - .toString() + return ("Content-Disposition: form-data; name=\"" + key + "\"; filename=\"" + fileName + "\"\r\n") .getBytes(); } From 034a0e260f8764d7bbf6b741b81fa3e6b7bc841e Mon Sep 17 00:00:00 2001 From: mareksebera Date: Sat, 19 Oct 2013 09:23:25 +0200 Subject: [PATCH 142/613] Wrong class modifier --- library/src/com/loopj/android/http/TextHttpResponseHandler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/src/com/loopj/android/http/TextHttpResponseHandler.java b/library/src/com/loopj/android/http/TextHttpResponseHandler.java index 00c038b26..0476b7d8b 100644 --- a/library/src/com/loopj/android/http/TextHttpResponseHandler.java +++ b/library/src/com/loopj/android/http/TextHttpResponseHandler.java @@ -39,7 +39,7 @@ * }); *
    */ -public abstract class TextHttpResponseHandler extends AsyncHttpResponseHandler { +public class TextHttpResponseHandler extends AsyncHttpResponseHandler { /** * Creates a new TextHttpResponseHandler From 1dea1e60678ecf3c396ef86b8f79e81cce9955d3 Mon Sep 17 00:00:00 2001 From: mareksebera Date: Sat, 19 Oct 2013 09:33:42 +0200 Subject: [PATCH 143/613] Proper chain calling --- .../android/http/AsyncHttpResponseHandler.java | 1 - .../loopj/android/http/TextHttpResponseHandler.java | 13 +++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/library/src/com/loopj/android/http/AsyncHttpResponseHandler.java b/library/src/com/loopj/android/http/AsyncHttpResponseHandler.java index 34b7d79f4..276c797e0 100644 --- a/library/src/com/loopj/android/http/AsyncHttpResponseHandler.java +++ b/library/src/com/loopj/android/http/AsyncHttpResponseHandler.java @@ -196,7 +196,6 @@ public void onSuccess(String content) { * @param content the body of the HTTP response from the server * @deprecated use {@link #onSuccess(int, Header[], byte[])} */ - @Deprecated public void onSuccess(int statusCode, Header[] headers, String content) { onSuccess(statusCode, content); } diff --git a/library/src/com/loopj/android/http/TextHttpResponseHandler.java b/library/src/com/loopj/android/http/TextHttpResponseHandler.java index 0476b7d8b..f99ba1d6f 100644 --- a/library/src/com/loopj/android/http/TextHttpResponseHandler.java +++ b/library/src/com/loopj/android/http/TextHttpResponseHandler.java @@ -81,6 +81,19 @@ public void onFailure(int statusCode, Header[] headers, String responseBody, Thr onFailure(responseBody, error); } + /** + * Fired when a request returns successfully, override to handle in your own + * code + * + * @param statusCode the status code of the response + * @param headers HTTP response headers + * @param responseBody the body of the HTTP response from the server + */ + @Override + public void onSuccess(int statusCode, Header[] headers, String responseBody) { + onSuccess( statusCode, responseBody ); + } + @Override public void onSuccess(int statusCode, Header[] headers, byte[] responseBody) { try { From c6746ab4876e327a01f5bbac13ee5a248b1fe991 Mon Sep 17 00:00:00 2001 From: mareksebera Date: Sat, 19 Oct 2013 09:33:52 +0200 Subject: [PATCH 144/613] Fixed sample to not use deprecated methods --- .../java/com/loopj/android/http/sample/MainActivity.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/sample/src/main/java/com/loopj/android/http/sample/MainActivity.java b/sample/src/main/java/com/loopj/android/http/sample/MainActivity.java index c49198ac1..62571c727 100644 --- a/sample/src/main/java/com/loopj/android/http/sample/MainActivity.java +++ b/sample/src/main/java/com/loopj/android/http/sample/MainActivity.java @@ -62,20 +62,20 @@ private void startRequest() { aclient.get(this, getURLString(), new TextHttpResponseHandler() { @Override - public void onSuccess(int statusCode, Header[] headers, String content) { + public void onSuccess(int statusCode, Header[] headers, String responseBody) { setStatusMessage("Succeeded", Color.parseColor("#DD00FF00")); printThrowable(null); printHeaders(headers); - printContents(content); + printContents(responseBody); printStatusCode(statusCode); } @Override - public void onFailure(int statusCode, Header[] headers, Throwable error, String content) { + public void onFailure(int statusCode, Header[] headers, String responseBody, Throwable error) { setStatusMessage("Failed", Color.parseColor("#99FF0000")); printThrowable(error); printHeaders(headers); - printContents(content); + printContents(responseBody); printStatusCode(statusCode); } From 9e5f4e69ddac7875d8db4b0f76a7e19aeb3233ff Mon Sep 17 00:00:00 2001 From: mareksebera Date: Sat, 19 Oct 2013 09:38:30 +0200 Subject: [PATCH 145/613] Code cleanup --- library/src/com/loopj/android/http/AsyncHttpClient.java | 2 +- .../src/com/loopj/android/http/AsyncHttpResponseHandler.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/library/src/com/loopj/android/http/AsyncHttpClient.java b/library/src/com/loopj/android/http/AsyncHttpClient.java index cc1f97d6c..14e13a7fe 100644 --- a/library/src/com/loopj/android/http/AsyncHttpClient.java +++ b/library/src/com/loopj/android/http/AsyncHttpClient.java @@ -921,7 +921,7 @@ private HttpEntity paramsToEntity(RequestParams params, AsyncHttpResponseHandler } } catch (Throwable t) { if (responseHandler != null) - responseHandler.sendFailureMessage(0, null, (byte[]) null, t); + responseHandler.sendFailureMessage(0, null, null, t); else t.printStackTrace(); } diff --git a/library/src/com/loopj/android/http/AsyncHttpResponseHandler.java b/library/src/com/loopj/android/http/AsyncHttpResponseHandler.java index 276c797e0..c81a6b5a7 100644 --- a/library/src/com/loopj/android/http/AsyncHttpResponseHandler.java +++ b/library/src/com/loopj/android/http/AsyncHttpResponseHandler.java @@ -225,7 +225,7 @@ public void onSuccess(int statusCode, Header[] headers, byte[] responseBody) { onSuccess(statusCode, headers, response); } catch (UnsupportedEncodingException e) { Log.e(LOG_TAG, e.toString()); - onFailure(statusCode, headers, e, (String) null); + onFailure(statusCode, headers, e, null); } } @@ -290,7 +290,7 @@ public void onFailure(int statusCode, Header[] headers, Throwable error, String * @param error the underlying cause of the failure */ public void onFailure(int statusCode, Header[] headers, byte[] responseBody, Throwable error) { - String response = null; + String response; try { response = new String(responseBody, getCharset()); onFailure(statusCode, headers, error, response); From daef5dea5d5ead0d1585b738623f9e395c0e7d90 Mon Sep 17 00:00:00 2001 From: sweetlilmre Date: Sat, 19 Oct 2013 16:57:53 +0200 Subject: [PATCH 146/613] Added missing @Deprecated --- library/src/com/loopj/android/http/AsyncHttpResponseHandler.java | 1 + 1 file changed, 1 insertion(+) diff --git a/library/src/com/loopj/android/http/AsyncHttpResponseHandler.java b/library/src/com/loopj/android/http/AsyncHttpResponseHandler.java index 276c797e0..34b7d79f4 100644 --- a/library/src/com/loopj/android/http/AsyncHttpResponseHandler.java +++ b/library/src/com/loopj/android/http/AsyncHttpResponseHandler.java @@ -196,6 +196,7 @@ public void onSuccess(String content) { * @param content the body of the HTTP response from the server * @deprecated use {@link #onSuccess(int, Header[], byte[])} */ + @Deprecated public void onSuccess(int statusCode, Header[] headers, String content) { onSuccess(statusCode, content); } From c49fc5b2fbebbf996bb3cd1f697105c059e659a1 Mon Sep 17 00:00:00 2001 From: sweetlilmre Date: Sat, 19 Oct 2013 17:33:12 +0200 Subject: [PATCH 147/613] Changed JsonHttpResponseHandler.java to derive from TextHttpResponseHandler.java --- .../android/http/JsonHttpResponseHandler.java | 40 +++++++++++-------- .../android/http/TextHttpResponseHandler.java | 5 +++ 2 files changed, 29 insertions(+), 16 deletions(-) diff --git a/library/src/com/loopj/android/http/JsonHttpResponseHandler.java b/library/src/com/loopj/android/http/JsonHttpResponseHandler.java index e92867b0f..3d2019ed6 100644 --- a/library/src/com/loopj/android/http/JsonHttpResponseHandler.java +++ b/library/src/com/loopj/android/http/JsonHttpResponseHandler.java @@ -41,8 +41,21 @@ * Additionally, you can override the other event methods from the * parent class. */ -public class JsonHttpResponseHandler extends AsyncHttpResponseHandler { +public class JsonHttpResponseHandler extends TextHttpResponseHandler { private static final String LOG_TAG = "JsonHttpResponseHandler"; + + /** + * Creates a new TextHttpResponseHandler + */ + + public JsonHttpResponseHandler() { + super(DEFAULT_CHARSET); + } + + public JsonHttpResponseHandler(String encoding) { + super(encoding); + } + // // Callbacks to be overridden, typically anonymously // @@ -143,7 +156,7 @@ public void onFailure(int statusCode, Header[] headers, Throwable e, JSONArray e } @Override - public void onSuccess(final int statusCode, final Header[] headers, final byte[] responseBody) { + public void onSuccess(final int statusCode, final Header[] headers, final String responseBody) { if (statusCode != HttpStatus.SC_NO_CONTENT) { new Thread(new Runnable() { @Override @@ -181,7 +194,7 @@ public void run() { } @Override - public void onFailure(final int statusCode, final Header[] headers, final byte[] responseBody, final Throwable e) { + public void onFailure(final int statusCode, final Header[] headers, final String responseBody, final Throwable e) { if (responseBody != null) { new Thread(new Runnable() { @Override @@ -220,22 +233,17 @@ public void run() { } } - protected Object parseResponse(byte[] responseBody) throws JSONException { + protected Object parseResponse(String responseBody) throws JSONException { if (null == responseBody) return null; Object result = null; - try { - //trim the string to prevent start with blank, and test if the string is valid JSON, because the parser don't do this :(. If Json is not valid this will return null - String jsonString = new String(responseBody, "UTF-8").trim(); - if (jsonString.startsWith("{") || jsonString.startsWith("[")) { - result = new JSONTokener(jsonString).nextValue(); - } - if (result == null) { - result = jsonString; - } - } catch (UnsupportedEncodingException ex) { - Log.v(LOG_TAG, "JSON parsing failed, calling onFailure(Throwable, JSONObject)"); - onFailure(ex, (JSONObject) null); + //trim the string to prevent start with blank, and test if the string is valid JSON, because the parser don't do this :(. If Json is not valid this will return null + String jsonString = responseBody.trim(); + if (jsonString.startsWith("{") || jsonString.startsWith("[")) { + result = new JSONTokener(jsonString).nextValue(); + } + if (result == null) { + result = jsonString; } return result; } diff --git a/library/src/com/loopj/android/http/TextHttpResponseHandler.java b/library/src/com/loopj/android/http/TextHttpResponseHandler.java index f99ba1d6f..313ebd1da 100644 --- a/library/src/com/loopj/android/http/TextHttpResponseHandler.java +++ b/library/src/com/loopj/android/http/TextHttpResponseHandler.java @@ -1,5 +1,7 @@ package com.loopj.android.http; +import android.util.Log; + import org.apache.http.Header; import java.io.UnsupportedEncodingException; @@ -40,6 +42,7 @@ *
    */ public class TextHttpResponseHandler extends AsyncHttpResponseHandler { + private static final String LOG_TAG = "TextHttpResponseHandler"; /** * Creates a new TextHttpResponseHandler @@ -99,6 +102,7 @@ public void onSuccess(int statusCode, Header[] headers, byte[] responseBody) { try { onSuccess(statusCode, headers, new String(responseBody, getCharset())); } catch (UnsupportedEncodingException e) { + Log.v(LOG_TAG, "String encoding failed, calling onFailure(int, Header[], String, Throwable)"); onFailure(0, headers, (String) null, e); } } @@ -108,6 +112,7 @@ public void onFailure(int statusCode, Header[] headers, byte[] responseBody, Thr try { onFailure(statusCode, headers, new String(responseBody, getCharset()), error); } catch (UnsupportedEncodingException e) { + Log.v(LOG_TAG, "String encoding failed, calling onFailure(int, Header[], String, Throwable)"); onFailure(0, headers, (String) null, e); } } From 3cfffa2adc18b1f3674cda61ead64277ecf6e295 Mon Sep 17 00:00:00 2001 From: sweetlilmre Date: Sat, 19 Oct 2013 17:38:49 +0200 Subject: [PATCH 148/613] Fix naming issue in comment --- library/src/com/loopj/android/http/JsonHttpResponseHandler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/src/com/loopj/android/http/JsonHttpResponseHandler.java b/library/src/com/loopj/android/http/JsonHttpResponseHandler.java index 3d2019ed6..3a00efccc 100644 --- a/library/src/com/loopj/android/http/JsonHttpResponseHandler.java +++ b/library/src/com/loopj/android/http/JsonHttpResponseHandler.java @@ -45,7 +45,7 @@ public class JsonHttpResponseHandler extends TextHttpResponseHandler { private static final String LOG_TAG = "JsonHttpResponseHandler"; /** - * Creates a new TextHttpResponseHandler + * Creates a new JsonHttpResponseHandler */ public JsonHttpResponseHandler() { From daf1b2196b510d333e02ead713afee0e4a5ea097 Mon Sep 17 00:00:00 2001 From: mareksebera Date: Sat, 19 Oct 2013 17:26:46 +0200 Subject: [PATCH 149/613] Samples progress --- sample/src/main/AndroidManifest.xml | 9 +- .../android/http/sample/BinarySample.java | 7 + .../android/http/sample/DeleteSample.java | 7 + .../loopj/android/http/sample/FileSample.java | 7 + .../loopj/android/http/sample/GetSample.java | 26 +++ .../loopj/android/http/sample/JsonSample.java | 7 + .../android/http/sample/MainActivity.java | 161 ------------------ .../loopj/android/http/sample/PostSample.java | 7 + .../loopj/android/http/sample/PutSample.java | 7 + .../http/sample/SampleParentActivity.java | 80 +++++++++ .../http/sample/WaypointsActivity.java | 50 ++++++ sample/src/main/res/layout/activity_main.xml | 75 -------- sample/src/main/res/menu/main.xml | 6 - sample/src/main/res/values-sw600dp/dimens.xml | 4 - .../main/res/values-sw720dp-land/dimens.xml | 5 - sample/src/main/res/values/dimens.xml | 5 - sample/src/main/res/values/strings.xml | 5 +- 17 files changed, 207 insertions(+), 261 deletions(-) create mode 100644 sample/src/main/java/com/loopj/android/http/sample/BinarySample.java create mode 100644 sample/src/main/java/com/loopj/android/http/sample/DeleteSample.java create mode 100644 sample/src/main/java/com/loopj/android/http/sample/FileSample.java create mode 100644 sample/src/main/java/com/loopj/android/http/sample/GetSample.java create mode 100644 sample/src/main/java/com/loopj/android/http/sample/JsonSample.java delete mode 100644 sample/src/main/java/com/loopj/android/http/sample/MainActivity.java create mode 100644 sample/src/main/java/com/loopj/android/http/sample/PostSample.java create mode 100644 sample/src/main/java/com/loopj/android/http/sample/PutSample.java create mode 100644 sample/src/main/java/com/loopj/android/http/sample/SampleParentActivity.java create mode 100644 sample/src/main/java/com/loopj/android/http/sample/WaypointsActivity.java delete mode 100644 sample/src/main/res/layout/activity_main.xml delete mode 100644 sample/src/main/res/menu/main.xml delete mode 100644 sample/src/main/res/values-sw600dp/dimens.xml delete mode 100644 sample/src/main/res/values-sw720dp-land/dimens.xml delete mode 100644 sample/src/main/res/values/dimens.xml diff --git a/sample/src/main/AndroidManifest.xml b/sample/src/main/AndroidManifest.xml index 8ad41e765..10326c540 100644 --- a/sample/src/main/AndroidManifest.xml +++ b/sample/src/main/AndroidManifest.xml @@ -14,12 +14,19 @@ android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:theme="@style/AppTheme"> - + + + + + + + + diff --git a/sample/src/main/java/com/loopj/android/http/sample/BinarySample.java b/sample/src/main/java/com/loopj/android/http/sample/BinarySample.java new file mode 100644 index 000000000..a21c3815e --- /dev/null +++ b/sample/src/main/java/com/loopj/android/http/sample/BinarySample.java @@ -0,0 +1,7 @@ +package com.loopj.android.http.sample; + +import android.app.Activity; + +public class BinarySample extends Activity { + +} diff --git a/sample/src/main/java/com/loopj/android/http/sample/DeleteSample.java b/sample/src/main/java/com/loopj/android/http/sample/DeleteSample.java new file mode 100644 index 000000000..9b5ef20b5 --- /dev/null +++ b/sample/src/main/java/com/loopj/android/http/sample/DeleteSample.java @@ -0,0 +1,7 @@ +package com.loopj.android.http.sample; + +import android.app.Activity; + +public class DeleteSample extends Activity { + +} diff --git a/sample/src/main/java/com/loopj/android/http/sample/FileSample.java b/sample/src/main/java/com/loopj/android/http/sample/FileSample.java new file mode 100644 index 000000000..b457ffbe5 --- /dev/null +++ b/sample/src/main/java/com/loopj/android/http/sample/FileSample.java @@ -0,0 +1,7 @@ +package com.loopj.android.http.sample; + +import android.app.Activity; + +public class FileSample extends Activity { + +} diff --git a/sample/src/main/java/com/loopj/android/http/sample/GetSample.java b/sample/src/main/java/com/loopj/android/http/sample/GetSample.java new file mode 100644 index 000000000..d16f5fa0c --- /dev/null +++ b/sample/src/main/java/com/loopj/android/http/sample/GetSample.java @@ -0,0 +1,26 @@ +package com.loopj.android.http.sample; + +import android.os.Bundle; + +public class GetSample extends SampleParentActivity { + + @Override + protected int getSampleTitle() { + return R.string.get_sample; + } + + @Override + protected boolean isRequestBodyAllowed() { + return false; + } + + @Override + protected boolean isRequestHeadersAllowed() { + return true; + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + } +} diff --git a/sample/src/main/java/com/loopj/android/http/sample/JsonSample.java b/sample/src/main/java/com/loopj/android/http/sample/JsonSample.java new file mode 100644 index 000000000..ed99331d1 --- /dev/null +++ b/sample/src/main/java/com/loopj/android/http/sample/JsonSample.java @@ -0,0 +1,7 @@ +package com.loopj.android.http.sample; + +import android.app.Activity; + +public class JsonSample extends Activity { + +} diff --git a/sample/src/main/java/com/loopj/android/http/sample/MainActivity.java b/sample/src/main/java/com/loopj/android/http/sample/MainActivity.java deleted file mode 100644 index 62571c727..000000000 --- a/sample/src/main/java/com/loopj/android/http/sample/MainActivity.java +++ /dev/null @@ -1,161 +0,0 @@ -package com.loopj.android.http.sample; - -import android.app.Activity; -import android.graphics.Color; -import android.os.Bundle; -import android.view.Menu; -import android.view.View; -import android.widget.Button; -import android.widget.EditText; -import android.widget.TextView; -import android.widget.Toast; - -import com.loopj.android.http.AsyncHttpClient; -import com.loopj.android.http.TextHttpResponseHandler; - -import org.apache.http.Header; - -import java.io.PrintWriter; -import java.io.StringWriter; -import java.net.URI; - -public class MainActivity extends Activity implements View.OnClickListener { - - private AsyncHttpClient aclient = new AsyncHttpClient(false, 80, 443); - private TextView statusCode, headers, contents, state, error; - private EditText url; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_main); - Button get = (Button) findViewById(R.id.request_get); - statusCode = (TextView) findViewById(R.id.return_code); - headers = (TextView) findViewById(R.id.return_headers); - contents = (TextView) findViewById(R.id.return_data); - state = (TextView) findViewById(R.id.current_state); - error = (TextView) findViewById(R.id.return_error); - url = (EditText) findViewById(R.id.request_url); - - get.setOnClickListener(this); - } - - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - getMenuInflater().inflate(R.menu.main, menu); - return true; - } - - @Override - public void onClick(View v) { - switch (v.getId()) { - case R.id.request_get: - if (verifyUrl()) { - startRequest(); - } - break; - } - } - - private void startRequest() { - aclient.get(this, getURLString(), new TextHttpResponseHandler() { - - @Override - public void onSuccess(int statusCode, Header[] headers, String responseBody) { - setStatusMessage("Succeeded", Color.parseColor("#DD00FF00")); - printThrowable(null); - printHeaders(headers); - printContents(responseBody); - printStatusCode(statusCode); - } - - @Override - public void onFailure(int statusCode, Header[] headers, String responseBody, Throwable error) { - setStatusMessage("Failed", Color.parseColor("#99FF0000")); - printThrowable(error); - printHeaders(headers); - printContents(responseBody); - printStatusCode(statusCode); - } - - @Override - public void onStart() { - setStatusMessage("Started", Color.parseColor("#EE00FF00")); - } - - @Override - public void onFinish() { - setStatusMessage("Finished", 0); - } - }); - } - - private void printThrowable(Throwable error) { - if (this.error != null) { - if (error != null) { - StringWriter sw = new StringWriter(); - error.printStackTrace(new PrintWriter(sw)); - this.error.setText(sw.toString()); - } else { - this.error.setText(null); - } - } - } - - private void printStatusCode(int statusCode) { - if (this.statusCode != null) { - this.statusCode.setText(String.format("HTTP Status Code: %d", statusCode)); - } - } - - private void printContents(String content) { - if (this.contents != null) { - if (content == null) - contents.setText("Return is NULL"); - else - contents.setText(content); - } - } - - private void printHeaders(Header[] headers) { - if (this.headers != null) { - StringBuilder sb = new StringBuilder(); - sb.append("Headers:"); - if (headers != null) { - for (Header h : headers) { - sb.append("\n").append(h.getName()).append(": ").append(h.getValue()); - } - } - this.headers.setText(sb.toString()); - } - } - - private void setStatusMessage(String message, int color) { - if (state != null) { - state.setText(String.format("Status: %s", message)); - if (color != 0) - state.setBackgroundColor(color); - } - } - - private String getURLString() { - return url.getText() != null ? url.getText().toString() : null; - } - - private boolean verifyUrl() { - String contents = getURLString(); - if (contents != null) { - try { - URI.create(contents); - return true; - } catch (Throwable t) { - Toast.makeText(this, "Given URL is not valid", Toast.LENGTH_SHORT).show(); - t.printStackTrace(); - return false; - } - } - Toast.makeText(this, "You must fill in URL", Toast.LENGTH_SHORT).show(); - return false; - } -} diff --git a/sample/src/main/java/com/loopj/android/http/sample/PostSample.java b/sample/src/main/java/com/loopj/android/http/sample/PostSample.java new file mode 100644 index 000000000..02726ca0b --- /dev/null +++ b/sample/src/main/java/com/loopj/android/http/sample/PostSample.java @@ -0,0 +1,7 @@ +package com.loopj.android.http.sample; + +import android.app.Activity; + +public class PostSample extends Activity { + +} diff --git a/sample/src/main/java/com/loopj/android/http/sample/PutSample.java b/sample/src/main/java/com/loopj/android/http/sample/PutSample.java new file mode 100644 index 000000000..a6d0aefba --- /dev/null +++ b/sample/src/main/java/com/loopj/android/http/sample/PutSample.java @@ -0,0 +1,7 @@ +package com.loopj.android.http.sample; + +import android.app.Activity; + +public class PutSample extends Activity { + +} diff --git a/sample/src/main/java/com/loopj/android/http/sample/SampleParentActivity.java b/sample/src/main/java/com/loopj/android/http/sample/SampleParentActivity.java new file mode 100644 index 000000000..aadc2b8ee --- /dev/null +++ b/sample/src/main/java/com/loopj/android/http/sample/SampleParentActivity.java @@ -0,0 +1,80 @@ +package com.loopj.android.http.sample; + +import android.app.Activity; +import android.os.Bundle; +import android.text.InputType; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.EditText; +import android.widget.LinearLayout; +import android.widget.ScrollView; + +import com.loopj.android.http.AsyncHttpClient; + +public abstract class SampleParentActivity extends Activity { + + private LinearLayout headers; // Sample header, inputs and buttons + private LinearLayout contents; // Sample output, states, errors, ... + private AsyncHttpClient asyncHttpClient = new AsyncHttpClient(); + private EditText urlEditText; + private Button executeButton; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + final LinearLayout.LayoutParams lParams = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); + final LinearLayout content_wrapper = new LinearLayout(this); + content_wrapper.setOrientation(LinearLayout.VERTICAL); + content_wrapper.setLayoutParams(lParams); + contents = new LinearLayout(this); + contents.setLayoutParams(lParams); + contents.setOrientation(LinearLayout.VERTICAL); + headers = new LinearLayout(this); + headers.setLayoutParams(lParams); + headers.setOrientation(LinearLayout.VERTICAL); + ScrollView contents_scroll = new ScrollView(this); + contents_scroll.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); + contents_scroll.setFillViewport(true); + content_wrapper.addView(headers); + content_wrapper.addView(contents); + contents_scroll.addView(content_wrapper); + setContentView(contents_scroll); + setTitle(getSampleTitle()); + setupHeaders(); + } + + private void setupHeaders() { + LinearLayout urlLayout = new LinearLayout(this); + urlLayout.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); + urlLayout.setOrientation(LinearLayout.HORIZONTAL); + urlEditText = new EditText(this); + urlEditText.setHint("URL for request"); + urlEditText.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_URI); + urlEditText.setLayoutParams(new LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 1f)); + urlLayout.addView(urlEditText); + executeButton = new Button(this); + executeButton.setText("Run"); + executeButton.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)); + urlLayout.addView(executeButton); + headers.addView(urlLayout); + } + + protected final void addView(View v) { + contents.addView(v); + } + + protected final void clearOutputs() { + contents.removeAllViews(); + } + + protected abstract int getSampleTitle(); + + protected abstract boolean isRequestBodyAllowed(); + + protected abstract boolean isRequestHeadersAllowed(); + + protected AsyncHttpClient getAsyncHttpClient() { + return this.asyncHttpClient; + } +} diff --git a/sample/src/main/java/com/loopj/android/http/sample/WaypointsActivity.java b/sample/src/main/java/com/loopj/android/http/sample/WaypointsActivity.java new file mode 100644 index 000000000..736c54249 --- /dev/null +++ b/sample/src/main/java/com/loopj/android/http/sample/WaypointsActivity.java @@ -0,0 +1,50 @@ +package com.loopj.android.http.sample; + +import android.app.ListActivity; +import android.content.Intent; +import android.os.Bundle; +import android.view.View; +import android.widget.ArrayAdapter; +import android.widget.ListView; + +public class WaypointsActivity extends ListActivity { + + private static final String[] samples = new String[]{"GET", "POST", "DELETE", "PUT", "JSON", "FILE", "BINARY"}; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setListAdapter(new ArrayAdapter(this, android.R.layout.simple_list_item_1, samples)); + } + + @Override + protected void onListItemClick(ListView l, View v, int position, long id) { + Class targetClass; + switch (position) { + case 0: + default: + targetClass = GetSample.class; + break; + case 1: + targetClass = PostSample.class; + break; + case 2: + targetClass = DeleteSample.class; + break; + case 3: + targetClass = PutSample.class; + break; + case 4: + targetClass = JsonSample.class; + break; + case 5: + targetClass = FileSample.class; + break; + case 6: + targetClass = BinarySample.class; + break; + } + if (targetClass != null) + startActivity(new Intent(this, targetClass)); + } +} diff --git a/sample/src/main/res/layout/activity_main.xml b/sample/src/main/res/layout/activity_main.xml deleted file mode 100644 index 7d92ae614..000000000 --- a/sample/src/main/res/layout/activity_main.xml +++ /dev/null @@ -1,75 +0,0 @@ - - - - - - - - -
    */ public class BinaryHttpResponseHandler extends AsyncHttpResponseHandler { - // Allow images by default + private String[] mAllowedContentTypes = new String[]{ "image/jpeg", "image/png" }; + /** + * Method can be overriden to return allowed content types, + * can be sometimes better than passing data in constructor + */ + public String[] getAllowedContentTypes() { + return mAllowedContentTypes; + } + /** * Creates a new BinaryHttpResponseHandler */ @@ -101,27 +109,27 @@ public void onSuccess(int statusCode, byte[] binaryData) { /** * Fired when a request returns successfully, override to handle in your own code * - * @param statusCode response HTTP statuse code - * @param headers response headers, if any - * @param responseData the response body, if any + * @param statusCode response HTTP statuse code + * @param headers response headers, if any + * @param binaryData the response body, if any */ @Override - public void onSuccess(int statusCode, Header[] headers, byte[] responseData) { - onSuccess(statusCode, responseData); + public void onSuccess(int statusCode, Header[] headers, byte[] binaryData) { + onSuccess(statusCode, binaryData); } /** * Fired when a request fails to complete, override to handle in your own code * - * @param statusCode response HTTP statuse code - * @param headers response headers, if any - * @param responseData the response body, if any - * @param error the underlying cause of the failure + * @param statusCode response HTTP statuse code + * @param headers response headers, if any + * @param binaryData the response body, if any + * @param error the underlying cause of the failure */ @Override - public void onFailure(int statusCode, Header[] headers, byte[] responseData, Throwable error) { + public void onFailure(int statusCode, Header[] headers, byte[] binaryData, Throwable error) { onFailure(statusCode, error, null); } @@ -141,7 +149,7 @@ protected void sendResponseMessage(HttpResponse response) throws IOException { } Header contentTypeHeader = contentTypeHeaders[0]; boolean foundAllowedContentType = false; - for (String anAllowedContentType : mAllowedContentTypes) { + for (String anAllowedContentType : getAllowedContentTypes()) { if (Pattern.matches(anAllowedContentType, contentTypeHeader.getValue())) { foundAllowedContentType = true; } @@ -151,6 +159,6 @@ protected void sendResponseMessage(HttpResponse response) throws IOException { sendFailureMessage(status.getStatusCode(), response.getAllHeaders(), null, new HttpResponseException(status.getStatusCode(), "Content-Type not allowed!")); return; } - super.sendResponseMessage( response ); + super.sendResponseMessage(response); } } From 085db38db9b94ebc3d0f8f32507518241d305176 Mon Sep 17 00:00:00 2001 From: mareksebera Date: Mon, 28 Oct 2013 17:13:37 +0100 Subject: [PATCH 185/613] Handling content-types in BinaryHttpResponseHandler --- .../com/loopj/android/http/AsyncHttpRequest.java | 3 +++ .../android/http/BinaryHttpResponseHandler.java | 15 ++++++++++++--- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/library/src/com/loopj/android/http/AsyncHttpRequest.java b/library/src/com/loopj/android/http/AsyncHttpRequest.java index 26a11d70b..34381c0da 100644 --- a/library/src/com/loopj/android/http/AsyncHttpRequest.java +++ b/library/src/com/loopj/android/http/AsyncHttpRequest.java @@ -18,6 +18,8 @@ package com.loopj.android.http; +import android.util.Log; + import org.apache.http.HttpResponse; import org.apache.http.client.HttpRequestRetryHandler; import org.apache.http.client.methods.HttpUriRequest; @@ -110,6 +112,7 @@ private void makeRequestWithRetries() throws IOException { } } catch (Exception e) { // catch anything else to ensure failure message is propagated + Log.e("AsyncHttpRequest", "Unhandled exception origin cause", e); cause = new IOException("Unhandled exception: " + e.getMessage()); } diff --git a/library/src/com/loopj/android/http/BinaryHttpResponseHandler.java b/library/src/com/loopj/android/http/BinaryHttpResponseHandler.java index c979cc370..d429d5124 100644 --- a/library/src/com/loopj/android/http/BinaryHttpResponseHandler.java +++ b/library/src/com/loopj/android/http/BinaryHttpResponseHandler.java @@ -18,6 +18,8 @@ package com.loopj.android.http; +import android.util.Log; + import org.apache.http.Header; import org.apache.http.HttpResponse; import org.apache.http.StatusLine; @@ -25,6 +27,7 @@ import java.io.IOException; import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; /** * Used to intercept and handle the responses from requests made using @@ -60,6 +63,8 @@ public class BinaryHttpResponseHandler extends AsyncHttpResponseHandler { /** * Method can be overriden to return allowed content types, * can be sometimes better than passing data in constructor + * + * @return array of content-types or Pattern string templates (eg. '.*' to match every response) */ public String[] getAllowedContentTypes() { return mAllowedContentTypes; @@ -76,7 +81,7 @@ public BinaryHttpResponseHandler() { * Creates a new BinaryHttpResponseHandler, and overrides the default allowed * content types with passed String array (hopefully) of content types. * - * @param allowedContentTypes content types array, eg. 'image/jpeg' + * @param allowedContentTypes content types array, eg. 'image/jpeg' or pattern '.*' */ public BinaryHttpResponseHandler(String[] allowedContentTypes) { this(); @@ -150,8 +155,12 @@ protected void sendResponseMessage(HttpResponse response) throws IOException { Header contentTypeHeader = contentTypeHeaders[0]; boolean foundAllowedContentType = false; for (String anAllowedContentType : getAllowedContentTypes()) { - if (Pattern.matches(anAllowedContentType, contentTypeHeader.getValue())) { - foundAllowedContentType = true; + try { + if (Pattern.matches(anAllowedContentType, contentTypeHeader.getValue())) { + foundAllowedContentType = true; + } + } catch (PatternSyntaxException e) { + Log.e("BinaryHttpResponseHandler", "Given pattern is not valid: " + anAllowedContentType, e); } } if (!foundAllowedContentType) { From 22780b5ec4ff3ea5ba984c0b07801244f3c0fb68 Mon Sep 17 00:00:00 2001 From: mareksebera Date: Mon, 28 Oct 2013 17:14:11 +0100 Subject: [PATCH 186/613] Implemented BinarySample --- .../java/com/loopj/android/http/sample/BinarySample.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/sample/src/main/java/com/loopj/android/http/sample/BinarySample.java b/sample/src/main/java/com/loopj/android/http/sample/BinarySample.java index 5b59f8ef0..e134a1b67 100644 --- a/sample/src/main/java/com/loopj/android/http/sample/BinarySample.java +++ b/sample/src/main/java/com/loopj/android/http/sample/BinarySample.java @@ -38,6 +38,12 @@ public void onStart() { clearOutputs(); } + @Override + public String[] getAllowedContentTypes() { + // Allowing all data for debug purposes + return new String[]{".*"}; + } + public void onSuccess(int statusCode, Header[] headers, byte[] binaryData) { debugStatusCode(LOG_TAG, statusCode); debugHeaders(LOG_TAG, headers); From 32e725ab817a8d2bf0d1096940c196c07d9add4a Mon Sep 17 00:00:00 2001 From: mareksebera Date: Mon, 28 Oct 2013 17:48:53 +0100 Subject: [PATCH 187/613] Refactored to include interface, to allow completely own implementation of response handler, Closes #280 --- .../loopj/android/http/AsyncHttpClient.java | 65 ++++++------- .../loopj/android/http/AsyncHttpRequest.java | 4 +- .../http/AsyncHttpResponseHandler.java | 52 +++++++---- .../http/BinaryHttpResponseHandler.java | 2 +- .../com/loopj/android/http/RequestParams.java | 4 +- .../http/ResponseHandlerInterface.java | 93 +++++++++++++++++++ .../android/http/SimpleMultipartEntity.java | 4 +- .../loopj/android/http/SyncHttpClient.java | 2 +- 8 files changed, 171 insertions(+), 55 deletions(-) create mode 100644 library/src/com/loopj/android/http/ResponseHandlerInterface.java diff --git a/library/src/com/loopj/android/http/AsyncHttpClient.java b/library/src/com/loopj/android/http/AsyncHttpClient.java index bc8c0afcc..004d7a8c9 100644 --- a/library/src/com/loopj/android/http/AsyncHttpClient.java +++ b/library/src/com/loopj/android/http/AsyncHttpClient.java @@ -80,13 +80,13 @@ * DELETE HTTP requests in your Android applications. Requests can be made * with additional parameters by passing a {@link RequestParams} instance, * and responses can be handled by passing an anonymously overridden - * {@link AsyncHttpResponseHandler} instance. + * {@link ResponseHandlerInterface} instance. *

     

    * For example: *

     

    *
      * AsyncHttpClient client = new AsyncHttpClient();
    - * client.get("http://www.google.com/", new AsyncHttpResponseHandler() {
    + * client.get("http://www.google.com/", new ResponseHandlerInterface() {
      *     @Override
      *     public void onSuccess(String response) {
      *         System.out.println(response);
    @@ -501,7 +501,7 @@ public void cancelRequests(Context context, boolean mayInterruptIfRunning) {
          * @param url             the URL to send the request to.
          * @param responseHandler the response handler instance that should handle the response.
          */
    -    public RequestHandle head(String url, AsyncHttpResponseHandler responseHandler) {
    +    public RequestHandle head(String url, ResponseHandlerInterface responseHandler) {
             return head(null, url, null, responseHandler);
         }
     
    @@ -512,7 +512,7 @@ public RequestHandle head(String url, AsyncHttpResponseHandler responseHandler)
          * @param params          additional HEAD parameters to send with the request.
          * @param responseHandler the response handler instance that should handle the response.
          */
    -    public RequestHandle head(String url, RequestParams params, AsyncHttpResponseHandler responseHandler) {
    +    public RequestHandle head(String url, RequestParams params, ResponseHandlerInterface responseHandler) {
             return head(null, url, params, responseHandler);
         }
     
    @@ -523,7 +523,7 @@ public RequestHandle head(String url, RequestParams params, AsyncHttpResponseHan
          * @param url             the URL to send the request to.
          * @param responseHandler the response handler instance that should handle the response.
          */
    -    public RequestHandle head(Context context, String url, AsyncHttpResponseHandler responseHandler) {
    +    public RequestHandle head(Context context, String url, ResponseHandlerInterface responseHandler) {
             return head(context, url, null, responseHandler);
         }
     
    @@ -535,7 +535,7 @@ public RequestHandle head(Context context, String url, AsyncHttpResponseHandler
          * @param params          additional HEAD parameters to send with the request.
          * @param responseHandler the response handler instance that should handle the response.
          */
    -    public RequestHandle head(Context context, String url, RequestParams params, AsyncHttpResponseHandler responseHandler) {
    +    public RequestHandle head(Context context, String url, RequestParams params, ResponseHandlerInterface responseHandler) {
             return sendRequest(httpClient, httpContext, new HttpHead(getUrlWithQueryString(isUrlEncodingEnabled, url, params)), null, responseHandler, context);
         }
     
    @@ -550,7 +550,7 @@ public RequestHandle head(Context context, String url, RequestParams params, Asy
          * @param responseHandler the response handler instance that should handle
          *                        the response.
          */
    -    public RequestHandle head(Context context, String url, Header[] headers, RequestParams params, AsyncHttpResponseHandler responseHandler) {
    +    public RequestHandle head(Context context, String url, Header[] headers, RequestParams params, ResponseHandlerInterface responseHandler) {
             HttpUriRequest request = new HttpHead(getUrlWithQueryString(isUrlEncodingEnabled, url, params));
             if (headers != null) request.setHeaders(headers);
             return sendRequest(httpClient, httpContext, request, null, responseHandler,
    @@ -568,7 +568,7 @@ public RequestHandle head(Context context, String url, Header[] headers, Request
          * @param url             the URL to send the request to.
          * @param responseHandler the response handler instance that should handle the response.
          */
    -    public RequestHandle get(String url, AsyncHttpResponseHandler responseHandler) {
    +    public RequestHandle get(String url, ResponseHandlerInterface responseHandler) {
             return get(null, url, null, responseHandler);
         }
     
    @@ -579,7 +579,7 @@ public RequestHandle get(String url, AsyncHttpResponseHandler responseHandler) {
          * @param params          additional GET parameters to send with the request.
          * @param responseHandler the response handler instance that should handle the response.
          */
    -    public RequestHandle get(String url, RequestParams params, AsyncHttpResponseHandler responseHandler) {
    +    public RequestHandle get(String url, RequestParams params, ResponseHandlerInterface responseHandler) {
             return get(null, url, params, responseHandler);
         }
     
    @@ -590,7 +590,7 @@ public RequestHandle get(String url, RequestParams params, AsyncHttpResponseHand
          * @param url             the URL to send the request to.
          * @param responseHandler the response handler instance that should handle the response.
          */
    -    public RequestHandle get(Context context, String url, AsyncHttpResponseHandler responseHandler) {
    +    public RequestHandle get(Context context, String url, ResponseHandlerInterface responseHandler) {
             return get(context, url, null, responseHandler);
         }
     
    @@ -602,7 +602,7 @@ public RequestHandle get(Context context, String url, AsyncHttpResponseHandler r
          * @param params          additional GET parameters to send with the request.
          * @param responseHandler the response handler instance that should handle the response.
          */
    -    public RequestHandle get(Context context, String url, RequestParams params, AsyncHttpResponseHandler responseHandler) {
    +    public RequestHandle get(Context context, String url, RequestParams params, ResponseHandlerInterface responseHandler) {
             return sendRequest(httpClient, httpContext, new HttpGet(getUrlWithQueryString(isUrlEncodingEnabled, url, params)), null, responseHandler, context);
         }
     
    @@ -617,7 +617,7 @@ public RequestHandle get(Context context, String url, RequestParams params, Asyn
          * @param responseHandler the response handler instance that should handle
          *                        the response.
          */
    -    public RequestHandle get(Context context, String url, Header[] headers, RequestParams params, AsyncHttpResponseHandler responseHandler) {
    +    public RequestHandle get(Context context, String url, Header[] headers, RequestParams params, ResponseHandlerInterface responseHandler) {
             HttpUriRequest request = new HttpGet(getUrlWithQueryString(isUrlEncodingEnabled, url, params));
             if (headers != null) request.setHeaders(headers);
             return sendRequest(httpClient, httpContext, request, null, responseHandler,
    @@ -635,7 +635,7 @@ public RequestHandle get(Context context, String url, Header[] headers, RequestP
          * @param url             the URL to send the request to.
          * @param responseHandler the response handler instance that should handle the response.
          */
    -    public RequestHandle post(String url, AsyncHttpResponseHandler responseHandler) {
    +    public RequestHandle post(String url, ResponseHandlerInterface responseHandler) {
             return post(null, url, null, responseHandler);
         }
     
    @@ -646,7 +646,7 @@ public RequestHandle post(String url, AsyncHttpResponseHandler responseHandler)
          * @param params          additional POST parameters or files to send with the request.
          * @param responseHandler the response handler instance that should handle the response.
          */
    -    public RequestHandle post(String url, RequestParams params, AsyncHttpResponseHandler responseHandler) {
    +    public RequestHandle post(String url, RequestParams params, ResponseHandlerInterface responseHandler) {
             return post(null, url, params, responseHandler);
         }
     
    @@ -658,7 +658,7 @@ public RequestHandle post(String url, RequestParams params, AsyncHttpResponseHan
          * @param params          additional POST parameters or files to send with the request.
          * @param responseHandler the response handler instance that should handle the response.
          */
    -    public RequestHandle post(Context context, String url, RequestParams params, AsyncHttpResponseHandler responseHandler) {
    +    public RequestHandle post(Context context, String url, RequestParams params, ResponseHandlerInterface responseHandler) {
             return post(context, url, paramsToEntity(params, responseHandler), null, responseHandler);
         }
     
    @@ -671,7 +671,7 @@ public RequestHandle post(Context context, String url, RequestParams params, Asy
          * @param contentType     the content type of the payload you are sending, for example application/json if sending a json payload.
          * @param responseHandler the response ha   ndler instance that should handle the response.
          */
    -    public RequestHandle post(Context context, String url, HttpEntity entity, String contentType, AsyncHttpResponseHandler responseHandler) {
    +    public RequestHandle post(Context context, String url, HttpEntity entity, String contentType, ResponseHandlerInterface responseHandler) {
             return sendRequest(httpClient, httpContext, addEntityToRequestBase(new HttpPost(url), entity), contentType, responseHandler, context);
         }
     
    @@ -689,7 +689,7 @@ public RequestHandle post(Context context, String url, HttpEntity entity, String
          *                        the response.
          */
         public RequestHandle post(Context context, String url, Header[] headers, RequestParams params, String contentType,
    -                     AsyncHttpResponseHandler responseHandler) {
    +                              ResponseHandlerInterface responseHandler) {
             HttpEntityEnclosingRequestBase request = new HttpPost(url);
             if (params != null) request.setEntity(paramsToEntity(params, responseHandler));
             if (headers != null) request.setHeaders(headers);
    @@ -713,7 +713,7 @@ public RequestHandle post(Context context, String url, Header[] headers, Request
          *                        the response.
          */
         public RequestHandle post(Context context, String url, Header[] headers, HttpEntity entity, String contentType,
    -                     AsyncHttpResponseHandler responseHandler) {
    +                              ResponseHandlerInterface responseHandler) {
             HttpEntityEnclosingRequestBase request = addEntityToRequestBase(new HttpPost(url), entity);
             if (headers != null) request.setHeaders(headers);
             return sendRequest(httpClient, httpContext, request, contentType, responseHandler, context);
    @@ -729,7 +729,7 @@ public RequestHandle post(Context context, String url, Header[] headers, HttpEnt
          * @param url             the URL to send the request to.
          * @param responseHandler the response handler instance that should handle the response.
          */
    -    public RequestHandle put(String url, AsyncHttpResponseHandler responseHandler) {
    +    public RequestHandle put(String url, ResponseHandlerInterface responseHandler) {
             return put(null, url, null, responseHandler);
         }
     
    @@ -740,7 +740,7 @@ public RequestHandle put(String url, AsyncHttpResponseHandler responseHandler) {
          * @param params          additional PUT parameters or files to send with the request.
          * @param responseHandler the response handler instance that should handle the response.
          */
    -    public RequestHandle put(String url, RequestParams params, AsyncHttpResponseHandler responseHandler) {
    +    public RequestHandle put(String url, RequestParams params, ResponseHandlerInterface responseHandler) {
             return put(null, url, params, responseHandler);
         }
     
    @@ -752,7 +752,7 @@ public RequestHandle put(String url, RequestParams params, AsyncHttpResponseHand
          * @param params          additional PUT parameters or files to send with the request.
          * @param responseHandler the response handler instance that should handle the response.
          */
    -    public RequestHandle put(Context context, String url, RequestParams params, AsyncHttpResponseHandler responseHandler) {
    +    public RequestHandle put(Context context, String url, RequestParams params, ResponseHandlerInterface responseHandler) {
             return put(context, url, paramsToEntity(params, responseHandler), null, responseHandler);
         }
     
    @@ -766,7 +766,7 @@ public RequestHandle put(Context context, String url, RequestParams params, Asyn
          * @param contentType     the content type of the payload you are sending, for example application/json if sending a json payload.
          * @param responseHandler the response handler instance that should handle the response.
          */
    -    public RequestHandle put(Context context, String url, HttpEntity entity, String contentType, AsyncHttpResponseHandler responseHandler) {
    +    public RequestHandle put(Context context, String url, HttpEntity entity, String contentType, ResponseHandlerInterface responseHandler) {
             return sendRequest(httpClient, httpContext, addEntityToRequestBase(new HttpPut(url), entity), contentType, responseHandler, context);
         }
     
    @@ -781,7 +781,7 @@ public RequestHandle put(Context context, String url, HttpEntity entity, String
          * @param contentType     the content type of the payload you are sending, for example application/json if sending a json payload.
          * @param responseHandler the response handler instance that should handle the response.
          */
    -    public RequestHandle put(Context context, String url, Header[] headers, HttpEntity entity, String contentType, AsyncHttpResponseHandler responseHandler) {
    +    public RequestHandle put(Context context, String url, Header[] headers, HttpEntity entity, String contentType, ResponseHandlerInterface responseHandler) {
             HttpEntityEnclosingRequestBase request = addEntityToRequestBase(new HttpPut(url), entity);
             if (headers != null) request.setHeaders(headers);
             return sendRequest(httpClient, httpContext, request, contentType, responseHandler, context);
    @@ -797,7 +797,7 @@ public RequestHandle put(Context context, String url, Header[] headers, HttpEnti
          * @param url             the URL to send the request to.
          * @param responseHandler the response handler instance that should handle the response.
          */
    -    public RequestHandle delete(String url, AsyncHttpResponseHandler responseHandler) {
    +    public RequestHandle delete(String url, ResponseHandlerInterface responseHandler) {
             return delete(null, url, responseHandler);
         }
     
    @@ -808,7 +808,7 @@ public RequestHandle delete(String url, AsyncHttpResponseHandler responseHandler
          * @param url             the URL to send the request to.
          * @param responseHandler the response handler instance that should handle the response.
          */
    -    public RequestHandle delete(Context context, String url, AsyncHttpResponseHandler responseHandler) {
    +    public RequestHandle delete(Context context, String url, ResponseHandlerInterface responseHandler) {
             final HttpDelete delete = new HttpDelete(url);
             return sendRequest(httpClient, httpContext, delete, null, responseHandler, context);
         }
    @@ -821,7 +821,7 @@ public RequestHandle delete(Context context, String url, AsyncHttpResponseHandle
          * @param headers         set one-time headers for this request
          * @param responseHandler the response handler instance that should handle the response.
          */
    -    public RequestHandle delete(Context context, String url, Header[] headers, AsyncHttpResponseHandler responseHandler) {
    +    public RequestHandle delete(Context context, String url, Header[] headers, ResponseHandlerInterface responseHandler) {
             final HttpDelete delete = new HttpDelete(url);
             if (headers != null) delete.setHeaders(headers);
             return sendRequest(httpClient, httpContext, delete, null, responseHandler, context);
    @@ -836,7 +836,7 @@ public RequestHandle delete(Context context, String url, Header[] headers, Async
          * @param params          additional DELETE parameters or files to send along with request
          * @param responseHandler the response handler instance that should handle the response.
          */
    -    public RequestHandle delete(Context context, String url, Header[] headers, RequestParams params, AsyncHttpResponseHandler responseHandler) {
    +    public RequestHandle delete(Context context, String url, Header[] headers, RequestParams params, ResponseHandlerInterface responseHandler) {
             HttpDelete httpDelete = new HttpDelete(getUrlWithQueryString(isUrlEncodingEnabled, url, params));
             if (headers != null) httpDelete.setHeaders(headers);
             return sendRequest(httpClient, httpContext, httpDelete, null, responseHandler, context);
    @@ -852,11 +852,14 @@ public RequestHandle delete(Context context, String url, Header[] headers, Reque
          * @param responseHandler ResponseHandler or its subclass to put the response into
          * @param uriRequest      instance of HttpUriRequest, which means it must be of HttpDelete, HttpPost, HttpGet, HttpPut, etc.
          */
    -    protected RequestHandle sendRequest(DefaultHttpClient client, HttpContext httpContext, HttpUriRequest uriRequest, String contentType, AsyncHttpResponseHandler responseHandler, Context context) {
    +    protected RequestHandle sendRequest(DefaultHttpClient client, HttpContext httpContext, HttpUriRequest uriRequest, String contentType, ResponseHandlerInterface responseHandler, Context context) {
             if (contentType != null) {
                 uriRequest.addHeader("Content-Type", contentType);
             }
     
    +        responseHandler.setRequestHeaders(uriRequest.getAllHeaders());
    +        responseHandler.setRequestURI(uriRequest.getURI());
    +
             Future request = threadPool.submit(new AsyncHttpRequest(client, httpContext, uriRequest, responseHandler));
     
             if (context != null) {
    @@ -871,7 +874,7 @@ protected RequestHandle sendRequest(DefaultHttpClient client, HttpContext httpCo
     
                 // TODO: Remove dead weakrefs from requestLists?
             }
    -        
    +
             return new RequestHandle(request);
         }
     
    @@ -912,9 +915,9 @@ public static String getUrlWithQueryString(boolean isUrlEncodingEnabled, String
          * Allows also passing progress from upload via provided ResponseHandler
          *
          * @param params          additional request params
    -     * @param responseHandler AsyncHttpResponseHandler or its subclass to be notified on progress
    +     * @param responseHandler ResponseHandlerInterface or its subclass to be notified on progress
          */
    -    private HttpEntity paramsToEntity(RequestParams params, AsyncHttpResponseHandler responseHandler) {
    +    private HttpEntity paramsToEntity(RequestParams params, ResponseHandlerInterface responseHandler) {
             HttpEntity entity = null;
     
             try {
    diff --git a/library/src/com/loopj/android/http/AsyncHttpRequest.java b/library/src/com/loopj/android/http/AsyncHttpRequest.java
    index 34381c0da..d6c36f2af 100644
    --- a/library/src/com/loopj/android/http/AsyncHttpRequest.java
    +++ b/library/src/com/loopj/android/http/AsyncHttpRequest.java
    @@ -34,10 +34,10 @@ class AsyncHttpRequest implements Runnable {
         private final AbstractHttpClient client;
         private final HttpContext context;
         private final HttpUriRequest request;
    -    private final AsyncHttpResponseHandler responseHandler;
    +    private final ResponseHandlerInterface responseHandler;
         private int executionCount;
     
    -    public AsyncHttpRequest(AbstractHttpClient client, HttpContext context, HttpUriRequest request, AsyncHttpResponseHandler responseHandler) {
    +    public AsyncHttpRequest(AbstractHttpClient client, HttpContext context, HttpUriRequest request, ResponseHandlerInterface responseHandler) {
             this.client = client;
             this.context = context;
             this.request = request;
    diff --git a/library/src/com/loopj/android/http/AsyncHttpResponseHandler.java b/library/src/com/loopj/android/http/AsyncHttpResponseHandler.java
    index 662df6012..8bc207440 100644
    --- a/library/src/com/loopj/android/http/AsyncHttpResponseHandler.java
    +++ b/library/src/com/loopj/android/http/AsyncHttpResponseHandler.java
    @@ -34,6 +34,7 @@
     import java.io.InputStream;
     import java.io.UnsupportedEncodingException;
     import java.lang.ref.WeakReference;
    +import java.net.URI;
     
     /**
      * Used to intercept and handle the responses from requests made using
    @@ -80,7 +81,7 @@
      * });
      * 
    */ -public class AsyncHttpResponseHandler { +public class AsyncHttpResponseHandler implements ResponseHandlerInterface { private static final String LOG_TAG = "AsyncHttpResponseHandler"; protected static final int SUCCESS_MESSAGE = 0; @@ -97,6 +98,29 @@ public class AsyncHttpResponseHandler { private String responseCharset = DEFAULT_CHARSET; private Boolean useSynchronousMode = false; + private URI requestURI = null; + private Header[] requestHeaders = null; + + @Override + public URI getRequestURI() { + return this.requestURI; + } + + @Override + public Header[] getRequestHeaders() { + return this.requestHeaders; + } + + @Override + public void setRequestURI(URI requestURI) { + this.requestURI = requestURI; + } + + @Override + public void setRequestHeaders(Header[] requestHeaders) { + this.requestHeaders = requestHeaders; + } + // avoid leaks by using a non-anonymous handler class // with a weak reference static class ResponderHandler extends Handler { @@ -119,12 +143,8 @@ public boolean getUseSynchronousMode() { return (useSynchronousMode); } - /** - * Set the response handler to use synchronous mode or not - * - * @param value true indicates that synchronous mode should be used - */ - public void setUseSynchronousMode(Boolean value) { + @Override + public void setUseSynchronousMode(boolean value) { useSynchronousMode = value; } @@ -311,27 +331,27 @@ public void onRetry() { // Pre-processing of messages (executes in background threadpool thread) // - protected void sendProgressMessage(int bytesWritten, int totalSize) { - sendMessage(obtainMessage(PROGRESS_MESSAGE, new Object[]{bytesWritten, totalSize})); + final public void sendProgressMessage(int bytesWritten, int bytesTotal) { + sendMessage(obtainMessage(PROGRESS_MESSAGE, new Object[]{bytesWritten, bytesTotal})); } - protected void sendSuccessMessage(int statusCode, Header[] headers, byte[] responseBody) { + final public void sendSuccessMessage(int statusCode, Header[] headers, byte[] responseBody) { sendMessage(obtainMessage(SUCCESS_MESSAGE, new Object[]{statusCode, headers, responseBody})); } - protected void sendFailureMessage(int statusCode, Header[] headers, byte[] responseBody, Throwable error) { + final public void sendFailureMessage(int statusCode, Header[] headers, byte[] responseBody, Throwable error) { sendMessage(obtainMessage(FAILURE_MESSAGE, new Object[]{statusCode, headers, responseBody, error})); } - protected void sendStartMessage() { + final public void sendStartMessage() { sendMessage(obtainMessage(START_MESSAGE, null)); } - protected void sendFinishMessage() { + final public void sendFinishMessage() { sendMessage(obtainMessage(FINISH_MESSAGE, null)); } - protected void sendRetryMessage() { + final public void sendRetryMessage() { sendMessage(obtainMessage(RETRY_MESSAGE, null)); } @@ -408,8 +428,8 @@ protected Message obtainMessage(int responseMessage, Object response) { return msg; } - // Interface to AsyncHttpRequest - void sendResponseMessage(HttpResponse response) throws IOException { + @Override + public void sendResponseMessage(HttpResponse response) throws IOException { // do not process if request has been cancelled if (!Thread.currentThread().isInterrupted()) { StatusLine status = response.getStatusLine(); diff --git a/library/src/com/loopj/android/http/BinaryHttpResponseHandler.java b/library/src/com/loopj/android/http/BinaryHttpResponseHandler.java index d429d5124..988b7220e 100644 --- a/library/src/com/loopj/android/http/BinaryHttpResponseHandler.java +++ b/library/src/com/loopj/android/http/BinaryHttpResponseHandler.java @@ -144,7 +144,7 @@ public void onFailure(int statusCode, Header[] headers, byte[] binaryData, Throw // Interface to AsyncHttpRequest @Override - protected void sendResponseMessage(HttpResponse response) throws IOException { + public final void sendResponseMessage(HttpResponse response) throws IOException { StatusLine status = response.getStatusLine(); Header[] contentTypeHeaders = response.getHeaders("Content-Type"); if (contentTypeHeaders.length != 1) { diff --git a/library/src/com/loopj/android/http/RequestParams.java b/library/src/com/loopj/android/http/RequestParams.java index f790f5f32..3d840c1c6 100644 --- a/library/src/com/loopj/android/http/RequestParams.java +++ b/library/src/com/loopj/android/http/RequestParams.java @@ -322,7 +322,7 @@ public void setHttpEntityIsRepeatable(boolean isRepeatable) { * @return HttpEntity resulting HttpEntity to be included along with {@link org.apache.http.client.methods.HttpEntityEnclosingRequestBase} * @throws IOException if one of the streams cannot be read */ - public HttpEntity getEntity(AsyncHttpResponseHandler progressHandler) throws IOException { + public HttpEntity getEntity(ResponseHandlerInterface progressHandler) throws IOException { if (streamParams.isEmpty() && fileParams.isEmpty()) { return createFormEntity(); } else { @@ -338,7 +338,7 @@ private HttpEntity createFormEntity() { } } - private HttpEntity createMultipartEntity(AsyncHttpResponseHandler progressHandler) throws IOException { + private HttpEntity createMultipartEntity(ResponseHandlerInterface progressHandler) throws IOException { SimpleMultipartEntity entity = new SimpleMultipartEntity(progressHandler); entity.setIsRepeatable(isRepeatable); diff --git a/library/src/com/loopj/android/http/ResponseHandlerInterface.java b/library/src/com/loopj/android/http/ResponseHandlerInterface.java new file mode 100644 index 000000000..71d5cb2b7 --- /dev/null +++ b/library/src/com/loopj/android/http/ResponseHandlerInterface.java @@ -0,0 +1,93 @@ +package com.loopj.android.http; + +import org.apache.http.Header; +import org.apache.http.HttpResponse; + +import java.io.IOException; +import java.net.URI; + +/** + * Interface to standardize implementations + */ +public interface ResponseHandlerInterface { + + /** + * Returns data whether request completed successfully + * + * @param response HttpResponse object with data + */ + void sendResponseMessage(HttpResponse response) throws IOException; + + /** + * Notifies callback, that request started execution + */ + void sendStartMessage(); + + /** + * Notifies callback, that request was completed and is being removed from thread pool + */ + void sendFinishMessage(); + + /** + * Notifies callback, that request (mainly uploading) has progressed + * + * @param bytesWritten number of written bytes + * @param bytesTotal number of total bytes to be written + */ + void sendProgressMessage(int bytesWritten, int bytesTotal); + + /** + * Notifies callback, that request was handled successfully + * + * @param statusCode HTTP status code + * @param headers returned headers + * @param responseBody returned data + */ + void sendSuccessMessage(int statusCode, Header[] headers, byte[] responseBody); + + /** + * Returns if request was completed with error code or failure of implementation + * + * @param statusCode returned HTTP status code + * @param headers returned headers + * @param responseBody returned data + * @param error cause of request failure + */ + void sendFailureMessage(int statusCode, Header[] headers, byte[] responseBody, Throwable error); + + /** + * Notifies callback of retrying request + */ + void sendRetryMessage(); + + /** + * Returns URI which was used to request + * + * @return uri of origin request + */ + public URI getRequestURI(); + + /** + * Returns Header[] which were used to request + * + * @return headers from origin request + */ + public Header[] getRequestHeaders(); + + /** + * Helper for handlers to receive Request URI info + */ + public void setRequestURI(URI requestURI); + + /** + * Helper for handlers to receive Request Header[] info + */ + public void setRequestHeaders(Header[] requestHeaders); + + /** + * Can set, whether the handler should be asynchronous or synchronous + * + * @param useSynchronousMode whether data should be handled on background Thread on UI Thread + */ + void setUseSynchronousMode(boolean useSynchronousMode); +} diff --git a/library/src/com/loopj/android/http/SimpleMultipartEntity.java b/library/src/com/loopj/android/http/SimpleMultipartEntity.java index cb252b80e..d41351394 100644 --- a/library/src/com/loopj/android/http/SimpleMultipartEntity.java +++ b/library/src/com/loopj/android/http/SimpleMultipartEntity.java @@ -64,13 +64,13 @@ class SimpleMultipartEntity implements HttpEntity { // boundary private ByteArrayOutputStream out = new ByteArrayOutputStream(); - private AsyncHttpResponseHandler progressHandler; + private ResponseHandlerInterface progressHandler; private int bytesWritten; private int totalSize; - public SimpleMultipartEntity(AsyncHttpResponseHandler progressHandler) { + public SimpleMultipartEntity(ResponseHandlerInterface progressHandler) { final StringBuilder buf = new StringBuilder(); final Random rand = new Random(); for (int i = 0; i < 30; i++) { diff --git a/library/src/com/loopj/android/http/SyncHttpClient.java b/library/src/com/loopj/android/http/SyncHttpClient.java index 55e74eb85..22466eddf 100644 --- a/library/src/com/loopj/android/http/SyncHttpClient.java +++ b/library/src/com/loopj/android/http/SyncHttpClient.java @@ -58,7 +58,7 @@ public SyncHttpClient(SchemeRegistry schemeRegistry) { @Override protected RequestHandle sendRequest(DefaultHttpClient client, HttpContext httpContext, HttpUriRequest uriRequest, - String contentType, AsyncHttpResponseHandler responseHandler, + String contentType, ResponseHandlerInterface responseHandler, Context context) { if (contentType != null) { uriRequest.addHeader("Content-Type", contentType); From 9af418694837e9aa960936839cf9017372c09fe2 Mon Sep 17 00:00:00 2001 From: mareksebera Date: Mon, 28 Oct 2013 19:03:25 +0100 Subject: [PATCH 188/613] Javadoc fixes --- library/AndroidManifest.xml | 2 +- .../loopj/android/http/AsyncHttpClient.java | 187 ++++++++++-------- .../http/AsyncHttpResponseHandler.java | 21 +- .../http/BinaryHttpResponseHandler.java | 18 +- .../android/http/JsonHttpResponseHandler.java | 45 ++--- .../android/http/MySSLSocketFactory.java | 4 +- .../android/http/PersistentCookieStore.java | 11 +- .../com/loopj/android/http/RequestHandle.java | 28 +-- .../com/loopj/android/http/RequestParams.java | 50 +++-- .../http/ResponseHandlerInterface.java | 5 + .../android/http/SerializableCookie.java | 4 +- .../android/http/TextHttpResponseHandler.java | 23 +-- 12 files changed, 204 insertions(+), 194 deletions(-) diff --git a/library/AndroidManifest.xml b/library/AndroidManifest.xml index dede71fe3..0705929fd 100644 --- a/library/AndroidManifest.xml +++ b/library/AndroidManifest.xml @@ -1,7 +1,7 @@  

    - * For example: - *

     

    + * The AsyncHttpClient can be used to make asynchronous GET, POST, PUT and DELETE HTTP requests in + * your Android applications. Requests can be made with additional parameters by passing a {@link + * RequestParams} instance, and responses can be handled by passing an anonymously overridden {@link + * ResponseHandlerInterface} instance.

     

    For example:

     

    *
      * AsyncHttpClient client = new AsyncHttpClient();
      * client.get("http://www.google.com/", new ResponseHandlerInterface() {
    @@ -254,9 +250,9 @@ public void process(HttpResponse response, HttpContext context) {
         }
     
         /**
    -     * Get the underlying HttpClient instance. This is useful for setting
    -     * additional fine-grained settings for requests by accessing the
    -     * client's ConnectionManager, HttpParams and SchemeRegistry.
    +     * Get the underlying HttpClient instance. This is useful for setting additional fine-grained
    +     * settings for requests by accessing the client's ConnectionManager, HttpParams and
    +     * SchemeRegistry.
          *
          * @return underlying HttpClient instance
          */
    @@ -265,9 +261,8 @@ public HttpClient getHttpClient() {
         }
     
         /**
    -     * Get the underlying HttpContext instance. This is useful for getting
    -     * and setting fine-grained settings for requests by accessing the
    -     * context's attributes such as the CookieStore.
    +     * Get the underlying HttpContext instance. This is useful for getting and setting fine-grained
    +     * settings for requests by accessing the context's attributes such as the CookieStore.
          *
          * @return underlying HttpContext instance
          */
    @@ -278,25 +273,27 @@ public HttpContext getHttpContext() {
         /**
          * Sets an optional CookieStore to use when making requests
          *
    -     * @param cookieStore The CookieStore implementation to use, usually an instance of {@link PersistentCookieStore}
    +     * @param cookieStore The CookieStore implementation to use, usually an instance of {@link
    +     *                    PersistentCookieStore}
          */
         public void setCookieStore(CookieStore cookieStore) {
             httpContext.setAttribute(ClientContext.COOKIE_STORE, cookieStore);
         }
     
         /**
    -     * Overrides the threadpool implementation used when queuing/pooling
    -     * requests. By default, Executors.newFixedThreadPool() is used.
    +     * Overrides the threadpool implementation used when queuing/pooling requests. By default,
    +     * Executors.newFixedThreadPool() is used.
          *
    -     * @param threadPool an instance of {@link ThreadPoolExecutor} to use for queuing/pooling requests.
    +     * @param threadPool an instance of {@link ThreadPoolExecutor} to use for queuing/pooling
    +     *                   requests.
          */
         public void setThreadPool(ThreadPoolExecutor threadPool) {
             this.threadPool = threadPool;
         }
     
         /**
    -     * Simple interface method, to enable or disable redirects.
    -     * If you set manually RedirectHandler on underlying HttpClient, effects of this method will be canceled.
    +     * Simple interface method, to enable or disable redirects. If you set manually RedirectHandler
    +     * on underlying HttpClient, effects of this method will be canceled.
          *
          * @param enableRedirects boolean
          */
    @@ -310,8 +307,8 @@ public boolean isRedirectRequested(HttpResponse response, HttpContext context) {
         }
     
         /**
    -     * Sets the User-Agent header to be sent with each request. By default,
    -     * "Android Asynchronous Http Client/VERSION (http://loopj.com/android-async-http/)" is used.
    +     * Sets the User-Agent header to be sent with each request. By default, "Android Asynchronous
    +     * Http Client/VERSION (http://loopj.com/android-async-http/)" is used.
          *
          * @param userAgent the string to use in the User-Agent header.
          */
    @@ -397,8 +394,8 @@ public void setProxy(String hostname, int port, String username, String password
     
     
         /**
    -     * Sets the SSLSocketFactory to user when making requests. By default,
    -     * a new, default SSLSocketFactory is used.
    +     * Sets the SSLSocketFactory to user when making requests. By default, a new, default
    +     * SSLSocketFactory is used.
          *
          * @param sslSocketFactory the socket factory to use for https requests.
          */
    @@ -410,6 +407,7 @@ public void setSSLSocketFactory(SSLSocketFactory sslSocketFactory) {
          * Sets the maximum number of retries and timeout for a particular Request.
          *
          * @param retries maximum number of retries per request
    +     * @param timeout sleep between retries in milliseconds
          */
         public void setMaxRetriesAndTimeout(int retries, int timeout) {
             this.httpClient.setHttpRequestRetryHandler(new RetryHandler(retries, timeout));
    @@ -447,8 +445,8 @@ public void setBasicAuth(String username, String password) {
         }
     
         /**
    -     * Sets basic authentication for the request. You should pass in your AuthScope for security. It should be like this
    -     * setBasicAuth("username","password", new AuthScope("host",port,AuthScope.ANY_REALM))
    +     * Sets basic authentication for the request. You should pass in your AuthScope for security. It
    +     * should be like this setBasicAuth("username","password", new AuthScope("host",port,AuthScope.ANY_REALM))
          *
          * @param username Basic Auth username
          * @param password Basic Auth password
    @@ -467,16 +465,14 @@ public void clearBasicAuth() {
         }
     
         /**
    -     * Cancels any pending (or potentially active) requests associated with the
    -     * passed Context.
    -     * 

     

    - * Note: This will only affect requests which were created with a non-null - * android Context. This method is intended to be used in the onDestroy - * method of your android activities to destroy all requests which are no - * longer required. + * Cancels any pending (or potentially active) requests associated with the passed Context. + *

     

    Note: This will only affect requests which were created with a non-null + * android Context. This method is intended to be used in the onDestroy method of your android + * activities to destroy all requests which are no longer required. * * @param context the android Context instance associated to the request. - * @param mayInterruptIfRunning specifies if active requests should be cancelled along with pending requests. + * @param mayInterruptIfRunning specifies if active requests should be cancelled along with + * pending requests. */ public void cancelRequests(Context context, boolean mayInterruptIfRunning) { List>> requestList = requestMap.get(context); @@ -500,6 +496,7 @@ public void cancelRequests(Context context, boolean mayInterruptIfRunning) { * * @param url the URL to send the request to. * @param responseHandler the response handler instance that should handle the response. + * @return RequestHandle of future request process */ public RequestHandle head(String url, ResponseHandlerInterface responseHandler) { return head(null, url, null, responseHandler); @@ -511,17 +508,20 @@ public RequestHandle head(String url, ResponseHandlerInterface responseHandler) * @param url the URL to send the request to. * @param params additional HEAD parameters to send with the request. * @param responseHandler the response handler instance that should handle the response. + * @return RequestHandle of future request process */ public RequestHandle head(String url, RequestParams params, ResponseHandlerInterface responseHandler) { return head(null, url, params, responseHandler); } /** - * Perform a HTTP HEAD request without any parameters and track the Android Context which initiated the request. + * Perform a HTTP HEAD request without any parameters and track the Android Context which + * initiated the request. * * @param context the Android Context which initiated the request. * @param url the URL to send the request to. * @param responseHandler the response handler instance that should handle the response. + * @return RequestHandle of future request process */ public RequestHandle head(Context context, String url, ResponseHandlerInterface responseHandler) { return head(context, url, null, responseHandler); @@ -534,21 +534,22 @@ public RequestHandle head(Context context, String url, ResponseHandlerInterface * @param url the URL to send the request to. * @param params additional HEAD parameters to send with the request. * @param responseHandler the response handler instance that should handle the response. + * @return RequestHandle of future request process */ public RequestHandle head(Context context, String url, RequestParams params, ResponseHandlerInterface responseHandler) { return sendRequest(httpClient, httpContext, new HttpHead(getUrlWithQueryString(isUrlEncodingEnabled, url, params)), null, responseHandler, context); } /** - * Perform a HTTP HEAD request and track the Android Context which initiated - * the request with customized headers + * Perform a HTTP HEAD request and track the Android Context which initiated the request with + * customized headers * * @param context Context to execute request against * @param url the URL to send the request to. * @param headers set headers only for this request * @param params additional HEAD parameters to send with the request. - * @param responseHandler the response handler instance that should handle - * the response. + * @param responseHandler the response handler instance that should handle the response. + * @return RequestHandle of future request process */ public RequestHandle head(Context context, String url, Header[] headers, RequestParams params, ResponseHandlerInterface responseHandler) { HttpUriRequest request = new HttpHead(getUrlWithQueryString(isUrlEncodingEnabled, url, params)); @@ -567,6 +568,7 @@ public RequestHandle head(Context context, String url, Header[] headers, Request * * @param url the URL to send the request to. * @param responseHandler the response handler instance that should handle the response. + * @return RequestHandle of future request process */ public RequestHandle get(String url, ResponseHandlerInterface responseHandler) { return get(null, url, null, responseHandler); @@ -578,17 +580,20 @@ public RequestHandle get(String url, ResponseHandlerInterface responseHandler) { * @param url the URL to send the request to. * @param params additional GET parameters to send with the request. * @param responseHandler the response handler instance that should handle the response. + * @return RequestHandle of future request process */ public RequestHandle get(String url, RequestParams params, ResponseHandlerInterface responseHandler) { return get(null, url, params, responseHandler); } /** - * Perform a HTTP GET request without any parameters and track the Android Context which initiated the request. + * Perform a HTTP GET request without any parameters and track the Android Context which + * initiated the request. * * @param context the Android Context which initiated the request. * @param url the URL to send the request to. * @param responseHandler the response handler instance that should handle the response. + * @return RequestHandle of future request process */ public RequestHandle get(Context context, String url, ResponseHandlerInterface responseHandler) { return get(context, url, null, responseHandler); @@ -601,21 +606,22 @@ public RequestHandle get(Context context, String url, ResponseHandlerInterface r * @param url the URL to send the request to. * @param params additional GET parameters to send with the request. * @param responseHandler the response handler instance that should handle the response. + * @return RequestHandle of future request process */ public RequestHandle get(Context context, String url, RequestParams params, ResponseHandlerInterface responseHandler) { return sendRequest(httpClient, httpContext, new HttpGet(getUrlWithQueryString(isUrlEncodingEnabled, url, params)), null, responseHandler, context); } /** - * Perform a HTTP GET request and track the Android Context which initiated - * the request with customized headers + * Perform a HTTP GET request and track the Android Context which initiated the request with + * customized headers * * @param context Context to execute request against * @param url the URL to send the request to. * @param headers set headers only for this request * @param params additional GET parameters to send with the request. - * @param responseHandler the response handler instance that should handle - * the response. + * @param responseHandler the response handler instance that should handle the response. + * @return RequestHandle of future request process */ public RequestHandle get(Context context, String url, Header[] headers, RequestParams params, ResponseHandlerInterface responseHandler) { HttpUriRequest request = new HttpGet(getUrlWithQueryString(isUrlEncodingEnabled, url, params)); @@ -634,6 +640,7 @@ public RequestHandle get(Context context, String url, Header[] headers, RequestP * * @param url the URL to send the request to. * @param responseHandler the response handler instance that should handle the response. + * @return RequestHandle of future request process */ public RequestHandle post(String url, ResponseHandlerInterface responseHandler) { return post(null, url, null, responseHandler); @@ -645,6 +652,7 @@ public RequestHandle post(String url, ResponseHandlerInterface responseHandler) * @param url the URL to send the request to. * @param params additional POST parameters or files to send with the request. * @param responseHandler the response handler instance that should handle the response. + * @return RequestHandle of future request process */ public RequestHandle post(String url, RequestParams params, ResponseHandlerInterface responseHandler) { return post(null, url, params, responseHandler); @@ -657,6 +665,7 @@ public RequestHandle post(String url, RequestParams params, ResponseHandlerInter * @param url the URL to send the request to. * @param params additional POST parameters or files to send with the request. * @param responseHandler the response handler instance that should handle the response. + * @return RequestHandle of future request process */ public RequestHandle post(Context context, String url, RequestParams params, ResponseHandlerInterface responseHandler) { return post(context, url, paramsToEntity(params, responseHandler), null, responseHandler); @@ -667,26 +676,30 @@ public RequestHandle post(Context context, String url, RequestParams params, Res * * @param context the Android Context which initiated the request. * @param url the URL to send the request to. - * @param entity a raw {@link org.apache.http.HttpEntity} to send with the request, for example, use this to send string/json/xml payloads to a server by passing a {@link org.apache.http.entity.StringEntity}. - * @param contentType the content type of the payload you are sending, for example application/json if sending a json payload. + * @param entity a raw {@link org.apache.http.HttpEntity} to send with the request, for + * example, use this to send string/json/xml payloads to a server by + * passing a {@link org.apache.http.entity.StringEntity}. + * @param contentType the content type of the payload you are sending, for example + * application/json if sending a json payload. * @param responseHandler the response ha ndler instance that should handle the response. + * @return RequestHandle of future request process */ public RequestHandle post(Context context, String url, HttpEntity entity, String contentType, ResponseHandlerInterface responseHandler) { return sendRequest(httpClient, httpContext, addEntityToRequestBase(new HttpPost(url), entity), contentType, responseHandler, context); } /** - * Perform a HTTP POST request and track the Android Context which initiated - * the request. Set headers only for this request + * Perform a HTTP POST request and track the Android Context which initiated the request. Set + * headers only for this request * * @param context the Android Context which initiated the request. * @param url the URL to send the request to. * @param headers set headers only for this request * @param params additional POST parameters to send with the request. - * @param contentType the content type of the payload you are sending, for - * example application/json if sending a json payload. - * @param responseHandler the response handler instance that should handle - * the response. + * @param contentType the content type of the payload you are sending, for example + * application/json if sending a json payload. + * @param responseHandler the response handler instance that should handle the response. + * @return RequestHandle of future request process */ public RequestHandle post(Context context, String url, Header[] headers, RequestParams params, String contentType, ResponseHandlerInterface responseHandler) { @@ -698,19 +711,19 @@ public RequestHandle post(Context context, String url, Header[] headers, Request } /** - * Perform a HTTP POST request and track the Android Context which initiated - * the request. Set headers only for this request + * Perform a HTTP POST request and track the Android Context which initiated the request. Set + * headers only for this request * * @param context the Android Context which initiated the request. * @param url the URL to send the request to. * @param headers set headers only for this request - * @param entity a raw {@link HttpEntity} to send with the request, for - * example, use this to send string/json/xml payloads to a server by - * passing a {@link org.apache.http.entity.StringEntity}. - * @param contentType the content type of the payload you are sending, for - * example application/json if sending a json payload. - * @param responseHandler the response handler instance that should handle - * the response. + * @param entity a raw {@link HttpEntity} to send with the request, for example, use + * this to send string/json/xml payloads to a server by passing a {@link + * org.apache.http.entity.StringEntity}. + * @param contentType the content type of the payload you are sending, for example + * application/json if sending a json payload. + * @param responseHandler the response handler instance that should handle the response. + * @return RequestHandle of future request process */ public RequestHandle post(Context context, String url, Header[] headers, HttpEntity entity, String contentType, ResponseHandlerInterface responseHandler) { @@ -728,6 +741,7 @@ public RequestHandle post(Context context, String url, Header[] headers, HttpEnt * * @param url the URL to send the request to. * @param responseHandler the response handler instance that should handle the response. + * @return RequestHandle of future request process */ public RequestHandle put(String url, ResponseHandlerInterface responseHandler) { return put(null, url, null, responseHandler); @@ -739,6 +753,7 @@ public RequestHandle put(String url, ResponseHandlerInterface responseHandler) { * @param url the URL to send the request to. * @param params additional PUT parameters or files to send with the request. * @param responseHandler the response handler instance that should handle the response. + * @return RequestHandle of future request process */ public RequestHandle put(String url, RequestParams params, ResponseHandlerInterface responseHandler) { return put(null, url, params, responseHandler); @@ -751,35 +766,44 @@ public RequestHandle put(String url, RequestParams params, ResponseHandlerInterf * @param url the URL to send the request to. * @param params additional PUT parameters or files to send with the request. * @param responseHandler the response handler instance that should handle the response. + * @return RequestHandle of future request process */ public RequestHandle put(Context context, String url, RequestParams params, ResponseHandlerInterface responseHandler) { return put(context, url, paramsToEntity(params, responseHandler), null, responseHandler); } /** - * Perform a HTTP PUT request and track the Android Context which initiated the request. - * And set one-time headers for the request + * Perform a HTTP PUT request and track the Android Context which initiated the request. And set + * one-time headers for the request * * @param context the Android Context which initiated the request. * @param url the URL to send the request to. - * @param entity a raw {@link HttpEntity} to send with the request, for example, use this to send string/json/xml payloads to a server by passing a {@link org.apache.http.entity.StringEntity}. - * @param contentType the content type of the payload you are sending, for example application/json if sending a json payload. + * @param entity a raw {@link HttpEntity} to send with the request, for example, use + * this to send string/json/xml payloads to a server by passing a {@link + * org.apache.http.entity.StringEntity}. + * @param contentType the content type of the payload you are sending, for example + * application/json if sending a json payload. * @param responseHandler the response handler instance that should handle the response. + * @return RequestHandle of future request process */ public RequestHandle put(Context context, String url, HttpEntity entity, String contentType, ResponseHandlerInterface responseHandler) { return sendRequest(httpClient, httpContext, addEntityToRequestBase(new HttpPut(url), entity), contentType, responseHandler, context); } /** - * Perform a HTTP PUT request and track the Android Context which initiated the request. - * And set one-time headers for the request + * Perform a HTTP PUT request and track the Android Context which initiated the request. And set + * one-time headers for the request * * @param context the Android Context which initiated the request. * @param url the URL to send the request to. * @param headers set one-time headers for this request - * @param entity a raw {@link HttpEntity} to send with the request, for example, use this to send string/json/xml payloads to a server by passing a {@link org.apache.http.entity.StringEntity}. - * @param contentType the content type of the payload you are sending, for example application/json if sending a json payload. + * @param entity a raw {@link HttpEntity} to send with the request, for example, use + * this to send string/json/xml payloads to a server by passing a {@link + * org.apache.http.entity.StringEntity}. + * @param contentType the content type of the payload you are sending, for example + * application/json if sending a json payload. * @param responseHandler the response handler instance that should handle the response. + * @return RequestHandle of future request process */ public RequestHandle put(Context context, String url, Header[] headers, HttpEntity entity, String contentType, ResponseHandlerInterface responseHandler) { HttpEntityEnclosingRequestBase request = addEntityToRequestBase(new HttpPut(url), entity); @@ -796,6 +820,7 @@ public RequestHandle put(Context context, String url, Header[] headers, HttpEnti * * @param url the URL to send the request to. * @param responseHandler the response handler instance that should handle the response. + * @return RequestHandle of future request process */ public RequestHandle delete(String url, ResponseHandlerInterface responseHandler) { return delete(null, url, responseHandler); @@ -807,6 +832,7 @@ public RequestHandle delete(String url, ResponseHandlerInterface responseHandler * @param context the Android Context which initiated the request. * @param url the URL to send the request to. * @param responseHandler the response handler instance that should handle the response. + * @return RequestHandle of future request process */ public RequestHandle delete(Context context, String url, ResponseHandlerInterface responseHandler) { final HttpDelete delete = new HttpDelete(url); @@ -820,6 +846,7 @@ public RequestHandle delete(Context context, String url, ResponseHandlerInterfac * @param url the URL to send the request to. * @param headers set one-time headers for this request * @param responseHandler the response handler instance that should handle the response. + * @return RequestHandle of future request process */ public RequestHandle delete(Context context, String url, Header[] headers, ResponseHandlerInterface responseHandler) { final HttpDelete delete = new HttpDelete(url); @@ -835,6 +862,7 @@ public RequestHandle delete(Context context, String url, Header[] headers, Respo * @param headers set one-time headers for this request * @param params additional DELETE parameters or files to send along with request * @param responseHandler the response handler instance that should handle the response. + * @return RequestHandle of future request process */ public RequestHandle delete(Context context, String url, Header[] headers, RequestParams params, ResponseHandlerInterface responseHandler) { HttpDelete httpDelete = new HttpDelete(getUrlWithQueryString(isUrlEncodingEnabled, url, params)); @@ -850,7 +878,9 @@ public RequestHandle delete(Context context, String url, Header[] headers, Reque * @param context Context of Android application, to hold the reference of request * @param httpContext HttpContext in which the request will be executed * @param responseHandler ResponseHandler or its subclass to put the response into - * @param uriRequest instance of HttpUriRequest, which means it must be of HttpDelete, HttpPost, HttpGet, HttpPut, etc. + * @param uriRequest instance of HttpUriRequest, which means it must be of HttpDelete, + * HttpPost, HttpGet, HttpPut, etc. + * @return RequestHandle of future request process */ protected RequestHandle sendRequest(DefaultHttpClient client, HttpContext httpContext, HttpUriRequest uriRequest, String contentType, ResponseHandlerInterface responseHandler, Context context) { if (contentType != null) { @@ -879,8 +909,8 @@ protected RequestHandle sendRequest(DefaultHttpClient client, HttpContext httpCo } /** - * Sets state of URL encoding feature, see bug #227, this method - * allows you to turn off and on this auto-magic feature on-demand. + * Sets state of URL encoding feature, see bug #227, this method allows you to turn off and on + * this auto-magic feature on-demand. * * @param enabled desired state of feature */ @@ -891,11 +921,13 @@ public void setURLEncodingEnabled(boolean enabled) { /** * Will encode url, if not disabled, and adds params on the end of it * - * @param url String with URL, should be valid URL without params - * @param params RequestParams to be appended on the end of URL + * @param url String with URL, should be valid URL without params + * @param params RequestParams to be appended on the end of URL + * @param shouldEncodeUrl whether url should be encoded (replaces spaces with %20) + * @return encoded url if requested with params appended if any available */ - public static String getUrlWithQueryString(boolean isUrlEncodingEnabled, String url, RequestParams params) { - if (isUrlEncodingEnabled) + public static String getUrlWithQueryString(boolean shouldEncodeUrl, String url, RequestParams params) { + if (shouldEncodeUrl) url = url.replace(" ", "%20"); if (params != null) { @@ -939,7 +971,8 @@ public boolean isUrlEncodingEnabled() { } /** - * Applicable only to HttpRequest methods extending HttpEntityEnclosingRequestBase, which is for example not DELETE + * Applicable only to HttpRequest methods extending HttpEntityEnclosingRequestBase, which is for + * example not DELETE * * @param entity entity to be included within the request * @param requestBase HttpRequest instance, must not be null diff --git a/library/src/com/loopj/android/http/AsyncHttpResponseHandler.java b/library/src/com/loopj/android/http/AsyncHttpResponseHandler.java index 8bc207440..3937e1cca 100644 --- a/library/src/com/loopj/android/http/AsyncHttpResponseHandler.java +++ b/library/src/com/loopj/android/http/AsyncHttpResponseHandler.java @@ -37,15 +37,12 @@ import java.net.URI; /** - * Used to intercept and handle the responses from requests made using - * {@link AsyncHttpClient}. The {@link #onSuccess(int, org.apache.http.Header[], byte[])} method is - * designed to be anonymously overridden with your own response handling code. - *

     

    - * Additionally, you can override the {@link #onFailure(int, org.apache.http.Header[], byte[], Throwable)}, - * {@link #onStart()}, {@link #onFinish()}, {@link #onRetry()} and {@link #onProgress(int, int)} methods as required. - *

     

    - * For example: - *

     

    + * Used to intercept and handle the responses from requests made using {@link AsyncHttpClient}. The + * {@link #onSuccess(int, org.apache.http.Header[], byte[])} method is designed to be anonymously + * overridden with your own response handling code.

     

    Additionally, you can override the + * {@link #onFailure(int, org.apache.http.Header[], byte[], Throwable)}, {@link #onStart()}, {@link + * #onFinish()}, {@link #onRetry()} and {@link #onProgress(int, int)} methods as required. + *

     

    For example:

     

    *
      * AsyncHttpClient client = new AsyncHttpClient();
      * client.get("http://www.google.com/", new AsyncHttpResponseHandler() {
    @@ -60,7 +57,8 @@
      *     }
      *
      *     @Override
    - *     public void onFailure(int statusCode, Header[] headers, byte[] responseBody, Throwable error) {
    + *     public void onFailure(int statusCode, Header[] headers, byte[] responseBody, Throwable error)
    + * {
      *         // Response failed :(
      *     }
      *
    @@ -193,7 +191,8 @@ public void onStart() {
         }
     
         /**
    -     * Fired in all cases when the request is finished, after both success and failure, override to handle in your own code
    +     * Fired in all cases when the request is finished, after both success and failure, override to
    +     * handle in your own code
          */
         public void onFinish() {
         }
    diff --git a/library/src/com/loopj/android/http/BinaryHttpResponseHandler.java b/library/src/com/loopj/android/http/BinaryHttpResponseHandler.java
    index 988b7220e..58055ac34 100644
    --- a/library/src/com/loopj/android/http/BinaryHttpResponseHandler.java
    +++ b/library/src/com/loopj/android/http/BinaryHttpResponseHandler.java
    @@ -30,13 +30,9 @@
     import java.util.regex.PatternSyntaxException;
     
     /**
    - * Used to intercept and handle the responses from requests made using
    - * {@link AsyncHttpClient}. Receives response body as byte array with a
    - * content-type whitelist. (e.g. checks Content-Type against allowed list,
    - * Content-length).
    - * 

     

    - * For example: - *

     

    + * Used to intercept and handle the responses from requests made using {@link AsyncHttpClient}. + * Receives response body as byte array with a content-type whitelist. (e.g. checks Content-Type + * against allowed list, Content-length).

     

    For example:

     

    *
      * AsyncHttpClient client = new AsyncHttpClient();
      * String[] allowedTypes = new String[] { "image/png" };
    @@ -61,8 +57,8 @@ public class BinaryHttpResponseHandler extends AsyncHttpResponseHandler {
         };
     
         /**
    -     * Method can be overriden to return allowed content types,
    -     * can be sometimes better than passing data in constructor
    +     * Method can be overriden to return allowed content types, can be sometimes better than passing
    +     * data in constructor
          *
          * @return array of content-types or Pattern string templates (eg. '.*' to match every response)
          */
    @@ -78,8 +74,8 @@ public BinaryHttpResponseHandler() {
         }
     
         /**
    -     * Creates a new BinaryHttpResponseHandler, and overrides the default allowed
    -     * content types with passed String array (hopefully) of content types.
    +     * Creates a new BinaryHttpResponseHandler, and overrides the default allowed content types with
    +     * passed String array (hopefully) of content types.
          *
          * @param allowedContentTypes content types array, eg. 'image/jpeg' or pattern '.*'
          */
    diff --git a/library/src/com/loopj/android/http/JsonHttpResponseHandler.java b/library/src/com/loopj/android/http/JsonHttpResponseHandler.java
    index 1e55eb35d..6778596ce 100644
    --- a/library/src/com/loopj/android/http/JsonHttpResponseHandler.java
    +++ b/library/src/com/loopj/android/http/JsonHttpResponseHandler.java
    @@ -28,16 +28,11 @@
     import org.json.JSONTokener;
     
     /**
    - * Used to intercept and handle the responses from requests made using
    - * {@link AsyncHttpClient}, with automatic parsing into a {@link JSONObject}
    - * or {@link JSONArray}.
    - * 

     

    - * This class is designed to be passed to get, post, put and delete requests - * with the {@link #onSuccess(JSONObject)} or {@link #onSuccess(JSONArray)} - * methods anonymously overridden. - *

     

    - * Additionally, you can override the other event methods from the - * parent class. + * Used to intercept and handle the responses from requests made using {@link AsyncHttpClient}, with + * automatic parsing into a {@link JSONObject} or {@link JSONArray}.

     

    This class is + * designed to be passed to get, post, put and delete requests with the {@link + * #onSuccess(JSONObject)} or {@link #onSuccess(JSONArray)} methods anonymously overridden. + *

     

    Additionally, you can override the other event methods from the parent class. */ public class JsonHttpResponseHandler extends TextHttpResponseHandler { private static final String LOG_TAG = "JsonHttpResponseHandler"; @@ -59,9 +54,8 @@ public JsonHttpResponseHandler(String encoding) { // /** - * Fired when a request returns successfully and contains a json object - * at the base of the response string. Override to handle in your - * own code. + * Fired when a request returns successfully and contains a json object at the base of the + * response string. Override to handle in your own code. * * @param response the parsed json object found in the server response (if any) */ @@ -70,9 +64,8 @@ public void onSuccess(JSONObject response) { /** - * Fired when a request returns successfully and contains a json array - * at the base of the response string. Override to handle in your - * own code. + * Fired when a request returns successfully and contains a json array at the base of the + * response string. Override to handle in your own code. * * @param response the parsed json array found in the server response (if any) */ @@ -80,9 +73,8 @@ public void onSuccess(JSONArray response) { } /** - * Fired when a request returns successfully and contains a json object - * at the base of the response string. Override to handle in your - * own code. + * Fired when a request returns successfully and contains a json object at the base of the + * response string. Override to handle in your own code. * * @param statusCode the status code of the response * @param headers the headers of the HTTP response @@ -93,9 +85,8 @@ public void onSuccess(int statusCode, Header[] headers, JSONObject response) { } /** - * Fired when a request returns successfully and contains a json object - * at the base of the response string. Override to handle in your - * own code. + * Fired when a request returns successfully and contains a json object at the base of the + * response string. Override to handle in your own code. * * @param statusCode the status code of the response * @param response the parsed json object found in the server response (if any) @@ -105,9 +96,8 @@ public void onSuccess(int statusCode, JSONObject response) { } /** - * Fired when a request returns successfully and contains a json array - * at the base of the response string. Override to handle in your - * own code. + * Fired when a request returns successfully and contains a json array at the base of the + * response string. Override to handle in your own code. * * @param statusCode the status code of the response * @param headers the headers of the HTTP response @@ -118,9 +108,8 @@ public void onSuccess(int statusCode, Header[] headers, JSONArray response) { } /** - * Fired when a request returns successfully and contains a json array - * at the base of the response string. Override to handle in your - * own code. + * Fired when a request returns successfully and contains a json array at the base of the + * response string. Override to handle in your own code. * * @param statusCode the status code of the response * @param response the parsed json array found in the server response (if any) diff --git a/library/src/com/loopj/android/http/MySSLSocketFactory.java b/library/src/com/loopj/android/http/MySSLSocketFactory.java index 86eeaab98..eb4aa97e1 100644 --- a/library/src/com/loopj/android/http/MySSLSocketFactory.java +++ b/library/src/com/loopj/android/http/MySSLSocketFactory.java @@ -15,8 +15,8 @@ import javax.net.ssl.X509TrustManager; /** - * This file is introduced to fix HTTPS Post bug on API < ICS - * see http://code.google.com/p/android/issues/detail?id=13117#c14 + * This file is introduced to fix HTTPS Post bug on API < ICS see + * http://code.google.com/p/android/issues/detail?id=13117#c14 */ public class MySSLSocketFactory extends SSLSocketFactory { SSLContext sslContext = SSLContext.getInstance("TLS"); diff --git a/library/src/com/loopj/android/http/PersistentCookieStore.java b/library/src/com/loopj/android/http/PersistentCookieStore.java index 69d790fae..963169913 100644 --- a/library/src/com/loopj/android/http/PersistentCookieStore.java +++ b/library/src/com/loopj/android/http/PersistentCookieStore.java @@ -35,13 +35,10 @@ import java.util.concurrent.ConcurrentHashMap; /** - * A persistent cookie store which implements the Apache HttpClient - * {@link CookieStore} interface. Cookies are stored and will persist on the - * user's device between application sessions since they are serialized and - * stored in {@link SharedPreferences}. - *

     

    - * Instances of this class are designed to be used with - * {@link AsyncHttpClient#setCookieStore}, but can also be used with a + * A persistent cookie store which implements the Apache HttpClient {@link CookieStore} interface. + * Cookies are stored and will persist on the user's device between application sessions since they + * are serialized and stored in {@link SharedPreferences}.

     

    Instances of this class are + * designed to be used with {@link AsyncHttpClient#setCookieStore}, but can also be used with a * regular old apache HttpClient/HttpContext if you prefer. */ public class PersistentCookieStore implements CookieStore { diff --git a/library/src/com/loopj/android/http/RequestHandle.java b/library/src/com/loopj/android/http/RequestHandle.java index 20f700420..08cb7824b 100644 --- a/library/src/com/loopj/android/http/RequestHandle.java +++ b/library/src/com/loopj/android/http/RequestHandle.java @@ -13,27 +13,27 @@ public RequestHandle(Future request) { } /** - * Attempts to cancel this request. This attempt will fail if the request has - * already completed, has already been cancelled, or could not be cancelled - * for some other reason. If successful, and this request has not started - * when cancel is called, this request should never run. If the request has - * already started, then the mayInterruptIfRunning parameter determines - * whether the thread executing this request should be interrupted in an - * attempt to stop the request. - *

    - * After this method returns, subsequent calls to isDone() will always - * return true. Subsequent calls to isCancelled() will always return true - * if this method returned true. + * Attempts to cancel this request. This attempt will fail if the request has already completed, + * has already been cancelled, or could not be cancelled for some other reason. If successful, + * and this request has not started when cancel is called, this request should never run. If the + * request has already started, then the mayInterruptIfRunning parameter determines whether the + * thread executing this request should be interrupted in an attempt to stop the request. + *

     

    After this method returns, subsequent calls to isDone() will always return + * true. Subsequent calls to isCancelled() will always return true if this method returned + * true. * - * @param mayInterruptIfRunning true if the thread executing this request should be interrupted; otherwise, in-progress requests are allowed to complete - * @return false if the request could not be cancelled, typically because it has already completed normally; true otherwise + * @param mayInterruptIfRunning true if the thread executing this request should be interrupted; + * otherwise, in-progress requests are allowed to complete + * @return false if the request could not be cancelled, typically because it has already + * completed normally; true otherwise */ public boolean cancel(boolean mayInterruptIfRunning) { return this.request != null && request.cancel(mayInterruptIfRunning); } /** - * Returns true if this task completed. Completion may be due to normal termination, an exception, or cancellation -- in all of these cases, this method will return true. + * Returns true if this task completed. Completion may be due to normal termination, an + * exception, or cancellation -- in all of these cases, this method will return true. * * @return true if this task completed */ diff --git a/library/src/com/loopj/android/http/RequestParams.java b/library/src/com/loopj/android/http/RequestParams.java index 3d840c1c6..c7eedce6d 100644 --- a/library/src/com/loopj/android/http/RequestParams.java +++ b/library/src/com/loopj/android/http/RequestParams.java @@ -40,11 +40,8 @@ import java.util.concurrent.ConcurrentHashMap; /** - * A collection of string request parameters or files to send along with - * requests made from an {@link AsyncHttpClient} instance. - *

     

    - * For example: - *

     

    + * A collection of string request parameters or files to send along with requests made from an + * {@link AsyncHttpClient} instance.

     

    For example:

     

    *
      * RequestParams params = new RequestParams();
      * params.put("username", "james");
    @@ -54,34 +51,34 @@
      * params.put("profile_picture2", someInputStream); // Upload an InputStream
      * params.put("profile_picture3", new ByteArrayInputStream(someBytes)); // Upload some bytes
      *
    - * Map map = new HashMap();
    + * Map<String, String> map = new HashMap<String, String>();
      * map.put("first_name", "James");
      * map.put("last_name", "Smith");
    - * params.put("user", map); // url params: "user[first_name]=James&user[last_name]=Smith"
    + * params.put("user", map); // url params: "user[first_name]=James&user[last_name]=Smith"
      *
    - * Set set = new HashSet(); // unordered collection
    + * Set<String> set = new HashSet<String>(); // unordered collection
      * set.add("music");
      * set.add("art");
    - * params.put("like", set); // url params: "like=music&like=art"
    + * params.put("like", set); // url params: "like=music&like=art"
      *
    - * List list = new ArrayList(); // Ordered collection
    + * List<String> list = new ArrayList<String>(); // Ordered collection
      * list.add("Java");
      * list.add("C");
    - * params.put("languages", list); // url params: "languages[]=Java&languages[]=C"
    + * params.put("languages", list); // url params: "languages[]=Java&languages[]=C"
      *
      * String[] colors = { "blue", "yellow" }; // Ordered collection
    - * params.put("colors", colors); // url params: "colors[]=blue&colors[]=yellow"
    + * params.put("colors", colors); // url params: "colors[]=blue&colors[]=yellow"
      *
    - * List> listOfMaps = new ArrayList>();
    - * Map user1 = new HashMap();
    + * List<Map<String, String>> listOfMaps = new ArrayList<Map<String, String>>();
    + * Map<String, String> user1 = new HashMap<String, String>();
      * user1.put("age", "30");
      * user1.put("gender", "male");
    - * Map user2 = new HashMap();
    + * Map<String, String> user2 = new HashMap<String, String>();
      * user2.put("age", "25");
      * user2.put("gender", "female");
      * listOfMaps.add(user1);
      * listOfMaps.add(user2);
    - * params.put("users", listOfMaps); // url params: "users[][age]=30&users[][gender]=male&users[][age]=25&users[][gender]=female"
    + * params.put("users", listOfMaps); // url params: "users[][age]=30&users[][gender]=male&users[][age]=25&users[][gender]=female"
      *
      * AsyncHttpClient client = new AsyncHttpClient();
      * client.post("http://myendpoint.com/", params, responseHandler);
    @@ -96,15 +93,15 @@ public class RequestParams {
         protected ConcurrentHashMap urlParamsWithObjects;
     
         /**
    -     * Constructs a new empty RequestParams instance.
    +     * Constructs a new empty {@code RequestParams} instance.
          */
         public RequestParams() {
             this((Map) null);
         }
     
         /**
    -     * Constructs a new RequestParams instance containing the key/value
    -     * string params from the specified map.
    +     * Constructs a new RequestParams instance containing the key/value string params from the
    +     * specified map.
          *
          * @param source the source key/value string map to add.
          */
    @@ -118,8 +115,8 @@ public RequestParams(Map source) {
         }
     
         /**
    -     * Constructs a new RequestParams instance and populate it with a single
    -     * initial key/value string param.
    +     * Constructs a new RequestParams instance and populate it with a single initial key/value
    +     * string param.
          *
          * @param key   the key name for the intial param.
          * @param value the value string for the initial param.
    @@ -131,11 +128,11 @@ public RequestParams(final String key, final String value) {
         }
     
         /**
    -     * Constructs a new RequestParams instance and populate it with multiple
    -     * initial key/value string param.
    +     * Constructs a new RequestParams instance and populate it with multiple initial key/value
    +     * string param.
          *
    -     * @param keysAndValues a sequence of keys and values. Objects are
    -     *                      automatically converted to Strings (including the value {@code null}).
    +     * @param keysAndValues a sequence of keys and values. Objects are automatically converted to
    +     *                      Strings (including the value {@code null}).
          * @throws IllegalArgumentException if the number of arguments isn't even.
          */
         public RequestParams(Object... keysAndValues) {
    @@ -319,7 +316,8 @@ public void setHttpEntityIsRepeatable(boolean isRepeatable) {
          * Returns an HttpEntity containing all request parameters
          *
          * @param progressHandler HttpResponseHandler for reporting progress on entity submit
    -     * @return HttpEntity resulting HttpEntity to be included along with {@link org.apache.http.client.methods.HttpEntityEnclosingRequestBase}
    +     * @return HttpEntity resulting HttpEntity to be included along with {@link
    +     * org.apache.http.client.methods.HttpEntityEnclosingRequestBase}
          * @throws IOException if one of the streams cannot be read
          */
         public HttpEntity getEntity(ResponseHandlerInterface progressHandler) throws IOException {
    diff --git a/library/src/com/loopj/android/http/ResponseHandlerInterface.java b/library/src/com/loopj/android/http/ResponseHandlerInterface.java
    index 71d5cb2b7..06b2145b6 100644
    --- a/library/src/com/loopj/android/http/ResponseHandlerInterface.java
    +++ b/library/src/com/loopj/android/http/ResponseHandlerInterface.java
    @@ -15,6 +15,7 @@ public interface ResponseHandlerInterface {
          * Returns data whether request completed successfully
          *
          * @param response HttpResponse object with data
    +     * @throws java.io.IOException if retrieving data from response fails
          */
         void sendResponseMessage(HttpResponse response) throws IOException;
     
    @@ -76,11 +77,15 @@ public interface ResponseHandlerInterface {
     
         /**
          * Helper for handlers to receive Request URI info
    +     *
    +     * @param requestURI claimed request URI
          */
         public void setRequestURI(URI requestURI);
     
         /**
          * Helper for handlers to receive Request Header[] info
    +     *
    +     * @param requestHeaders Headers, claimed to be from original request
          */
         public void setRequestHeaders(Header[] requestHeaders);
     
    diff --git a/library/src/com/loopj/android/http/SerializableCookie.java b/library/src/com/loopj/android/http/SerializableCookie.java
    index d9730d212..855105b1c 100644
    --- a/library/src/com/loopj/android/http/SerializableCookie.java
    +++ b/library/src/com/loopj/android/http/SerializableCookie.java
    @@ -28,8 +28,8 @@
     import java.util.Date;
     
     /**
    - * A wrapper class around {@link Cookie} and/or {@link BasicClientCookie}
    - * designed for use in {@link PersistentCookieStore}.
    + * A wrapper class around {@link Cookie} and/or {@link BasicClientCookie} designed for use in {@link
    + * PersistentCookieStore}.
      */
     public class SerializableCookie implements Serializable {
         private static final long serialVersionUID = 6374381828722046732L;
    diff --git a/library/src/com/loopj/android/http/TextHttpResponseHandler.java b/library/src/com/loopj/android/http/TextHttpResponseHandler.java
    index 310990ffc..bf93d8435 100644
    --- a/library/src/com/loopj/android/http/TextHttpResponseHandler.java
    +++ b/library/src/com/loopj/android/http/TextHttpResponseHandler.java
    @@ -7,15 +7,11 @@
     import java.io.UnsupportedEncodingException;
     
     /**
    - * Used to intercept and handle the responses from requests made using
    - * {@link AsyncHttpClient}. The {@link #onSuccess(String)} method is
    - * designed to be anonymously overridden with your own response handling code.
    - * 

    - * Additionally, you can override the {@link #onFailure(String, Throwable)}, - * {@link #onStart()}, and {@link #onFinish()} methods as required. - *

    - * For example: - *

    + * Used to intercept and handle the responses from requests made using {@link AsyncHttpClient}. The + * {@link #onSuccess(String)} method is designed to be anonymously overridden with your own response + * handling code.

     

    Additionally, you can override the {@link #onFailure(String, + * Throwable)}, {@link #onStart()}, and {@link #onFinish()} methods as required.

     

    For + * example:

     

    *
      * AsyncHttpClient client = new AsyncHttpClient();
      * client.get("http://www.google.com/", new TextHttpResponseHandler() {
    @@ -62,8 +58,7 @@ public TextHttpResponseHandler(String encoding) {
         //
     
         /**
    -     * Fired when a request fails to complete, override to handle in your own
    -     * code
    +     * Fired when a request fails to complete, override to handle in your own code
          *
          * @param responseBody the response body, if any
          * @param error        the underlying cause of the failure
    @@ -72,8 +67,7 @@ public void onFailure(String responseBody, Throwable error) {
         }
     
         /**
    -     * Fired when a request fails to complete, override to handle in your own
    -     * code
    +     * Fired when a request fails to complete, override to handle in your own code
          *
          * @param statusCode   the status code of the response
          * @param headers      HTTP response headers
    @@ -85,8 +79,7 @@ public void onFailure(int statusCode, Header[] headers, String responseBody, Thr
         }
     
         /**
    -     * Fired when a request returns successfully, override to handle in your own
    -     * code
    +     * Fired when a request returns successfully, override to handle in your own code
          *
          * @param statusCode   the status code of the response
          * @param headers      HTTP response headers
    
    From c2f1701db3e3ae14a537da567705a409980e26b0 Mon Sep 17 00:00:00 2001
    From: mareksebera 
    Date: Mon, 28 Oct 2013 19:03:43 +0100
    Subject: [PATCH 189/613] Prepare to release 1.4.4 to Maven Central
    
    ---
     README.md         | 2 +-
     build.gradle      | 2 +-
     gradle.properties | 2 +-
     3 files changed, 3 insertions(+), 3 deletions(-)
    
    diff --git a/README.md b/README.md
    index bcb461161..2dd17dd7c 100644
    --- a/README.md
    +++ b/README.md
    @@ -40,7 +40,7 @@ http://central.maven.org/maven2/com/loopj/android/android-async-http/
     Maven URL: http://repo1.maven.org/maven2/
     GroupId: com.loopj.android
     ArtifactId: android-async-http
    -Version: 1.4.3
    +Version: 1.4.4
     Packaging: JAR or AAR
     ```
     
    diff --git a/build.gradle b/build.gradle
    index d239f5ad6..9fe1e6adc 100644
    --- a/build.gradle
    +++ b/build.gradle
    @@ -14,7 +14,7 @@ def isReleaseBuild() {
     
     allprojects {
         group = 'com.loopj.android'
    -    version = '1.4.4-SNAPSHOT'
    +    version = '1.4.4'
     
         repositories {
             mavenCentral()
    diff --git a/gradle.properties b/gradle.properties
    index 0de12ce95..2c0fb8c88 100644
    --- a/gradle.properties
    +++ b/gradle.properties
    @@ -1,4 +1,4 @@
    -VERSION_NAME=1.4.4-SNAPSHOT
    +VERSION_NAME=1.4.4
     VERSION_CODE=144
     GROUP=com.loopj.android
     
    
    From c5b3bb96afedbad911963be52715ad28fe98fc00 Mon Sep 17 00:00:00 2001
    From: mareksebera 
    Date: Mon, 28 Oct 2013 19:14:07 +0100
    Subject: [PATCH 190/613] Maven release process fixup
    
    ---
     maven_push.gradle | 2 +-
     1 file changed, 1 insertion(+), 1 deletion(-)
    
    diff --git a/maven_push.gradle b/maven_push.gradle
    index 7a583627c..6fc6fb30f 100644
    --- a/maven_push.gradle
    +++ b/maven_push.gradle
    @@ -82,7 +82,7 @@ afterEvaluate { project ->
             sign configurations.archives
         }
     
    -    task androidJavadocsJar(type: Jar) {
    +    task androidJavadocsJar(type: Jar, dependsOn: generateReleaseJavadoc) {
             classifier = 'javadoc'
             from generateReleaseJavadoc.destinationDir
         }
    
    From 5088ce0f0375bac8d54a9143b14f5222a3a2bc41 Mon Sep 17 00:00:00 2001
    From: mareksebera 
    Date: Mon, 28 Oct 2013 20:28:01 +0100
    Subject: [PATCH 191/613] Added JAR for 1.4.4 release
    
    ---
     releases/android-async-http-1.4.4.jar | Bin 0 -> 60962 bytes
     1 file changed, 0 insertions(+), 0 deletions(-)
     create mode 100644 releases/android-async-http-1.4.4.jar
    
    diff --git a/releases/android-async-http-1.4.4.jar b/releases/android-async-http-1.4.4.jar
    new file mode 100644
    index 0000000000000000000000000000000000000000..75af7015db5f770102182bfc35c9e6fe167d83a7
    GIT binary patch
    literal 60962
    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
    Date: Tue, 29 Oct 2013 18:27:33 +0100
    Subject: [PATCH 192/613] Library code structure
    
    ---
     library/build.gradle                                      | 8 --------
     library/{ => src/main}/AndroidManifest.xml                | 0
     .../java}/com/loopj/android/http/AsyncHttpClient.java     | 0
     .../java}/com/loopj/android/http/AsyncHttpRequest.java    | 0
     .../com/loopj/android/http/AsyncHttpResponseHandler.java  | 0
     .../loopj/android/http/BaseJsonHttpResponseHandler.java   | 0
     .../com/loopj/android/http/BinaryHttpResponseHandler.java | 0
     .../loopj/android/http/FileAsyncHttpResponseHandler.java  | 0
     .../com/loopj/android/http/JsonHttpResponseHandler.java   | 0
     .../java}/com/loopj/android/http/MySSLSocketFactory.java  | 0
     .../com/loopj/android/http/PersistentCookieStore.java     | 0
     .../java}/com/loopj/android/http/RequestHandle.java       | 0
     .../java}/com/loopj/android/http/RequestParams.java       | 0
     .../com/loopj/android/http/ResponseHandlerInterface.java  | 0
     .../java}/com/loopj/android/http/RetryHandler.java        | 0
     .../java}/com/loopj/android/http/SerializableCookie.java  | 0
     .../com/loopj/android/http/SimpleMultipartEntity.java     | 0
     .../java}/com/loopj/android/http/SyncHttpClient.java      | 0
     .../com/loopj/android/http/TextHttpResponseHandler.java   | 0
     19 files changed, 8 deletions(-)
     rename library/{ => src/main}/AndroidManifest.xml (100%)
     rename library/src/{ => main/java}/com/loopj/android/http/AsyncHttpClient.java (100%)
     rename library/src/{ => main/java}/com/loopj/android/http/AsyncHttpRequest.java (100%)
     rename library/src/{ => main/java}/com/loopj/android/http/AsyncHttpResponseHandler.java (100%)
     rename library/src/{ => main/java}/com/loopj/android/http/BaseJsonHttpResponseHandler.java (100%)
     rename library/src/{ => main/java}/com/loopj/android/http/BinaryHttpResponseHandler.java (100%)
     rename library/src/{ => main/java}/com/loopj/android/http/FileAsyncHttpResponseHandler.java (100%)
     rename library/src/{ => main/java}/com/loopj/android/http/JsonHttpResponseHandler.java (100%)
     rename library/src/{ => main/java}/com/loopj/android/http/MySSLSocketFactory.java (100%)
     rename library/src/{ => main/java}/com/loopj/android/http/PersistentCookieStore.java (100%)
     rename library/src/{ => main/java}/com/loopj/android/http/RequestHandle.java (100%)
     rename library/src/{ => main/java}/com/loopj/android/http/RequestParams.java (100%)
     rename library/src/{ => main/java}/com/loopj/android/http/ResponseHandlerInterface.java (100%)
     rename library/src/{ => main/java}/com/loopj/android/http/RetryHandler.java (100%)
     rename library/src/{ => main/java}/com/loopj/android/http/SerializableCookie.java (100%)
     rename library/src/{ => main/java}/com/loopj/android/http/SimpleMultipartEntity.java (100%)
     rename library/src/{ => main/java}/com/loopj/android/http/SyncHttpClient.java (100%)
     rename library/src/{ => main/java}/com/loopj/android/http/TextHttpResponseHandler.java (100%)
    
    diff --git a/library/build.gradle b/library/build.gradle
    index 30abc6b28..3c0599675 100644
    --- a/library/build.gradle
    +++ b/library/build.gradle
    @@ -3,14 +3,6 @@ apply plugin: 'android-library'
     android {
         compileSdkVersion 18
         buildToolsVersion '18.0.1'
    -
    -    sourceSets {
    -        main {
    -            manifest.srcFile 'AndroidManifest.xml'
    -            java.srcDirs = ['src']
    -            res.srcDirs = ['res']
    -        }
    -    }
     }
     
     android.libraryVariants.all { variant ->
    diff --git a/library/AndroidManifest.xml b/library/src/main/AndroidManifest.xml
    similarity index 100%
    rename from library/AndroidManifest.xml
    rename to library/src/main/AndroidManifest.xml
    diff --git a/library/src/com/loopj/android/http/AsyncHttpClient.java b/library/src/main/java/com/loopj/android/http/AsyncHttpClient.java
    similarity index 100%
    rename from library/src/com/loopj/android/http/AsyncHttpClient.java
    rename to library/src/main/java/com/loopj/android/http/AsyncHttpClient.java
    diff --git a/library/src/com/loopj/android/http/AsyncHttpRequest.java b/library/src/main/java/com/loopj/android/http/AsyncHttpRequest.java
    similarity index 100%
    rename from library/src/com/loopj/android/http/AsyncHttpRequest.java
    rename to library/src/main/java/com/loopj/android/http/AsyncHttpRequest.java
    diff --git a/library/src/com/loopj/android/http/AsyncHttpResponseHandler.java b/library/src/main/java/com/loopj/android/http/AsyncHttpResponseHandler.java
    similarity index 100%
    rename from library/src/com/loopj/android/http/AsyncHttpResponseHandler.java
    rename to library/src/main/java/com/loopj/android/http/AsyncHttpResponseHandler.java
    diff --git a/library/src/com/loopj/android/http/BaseJsonHttpResponseHandler.java b/library/src/main/java/com/loopj/android/http/BaseJsonHttpResponseHandler.java
    similarity index 100%
    rename from library/src/com/loopj/android/http/BaseJsonHttpResponseHandler.java
    rename to library/src/main/java/com/loopj/android/http/BaseJsonHttpResponseHandler.java
    diff --git a/library/src/com/loopj/android/http/BinaryHttpResponseHandler.java b/library/src/main/java/com/loopj/android/http/BinaryHttpResponseHandler.java
    similarity index 100%
    rename from library/src/com/loopj/android/http/BinaryHttpResponseHandler.java
    rename to library/src/main/java/com/loopj/android/http/BinaryHttpResponseHandler.java
    diff --git a/library/src/com/loopj/android/http/FileAsyncHttpResponseHandler.java b/library/src/main/java/com/loopj/android/http/FileAsyncHttpResponseHandler.java
    similarity index 100%
    rename from library/src/com/loopj/android/http/FileAsyncHttpResponseHandler.java
    rename to library/src/main/java/com/loopj/android/http/FileAsyncHttpResponseHandler.java
    diff --git a/library/src/com/loopj/android/http/JsonHttpResponseHandler.java b/library/src/main/java/com/loopj/android/http/JsonHttpResponseHandler.java
    similarity index 100%
    rename from library/src/com/loopj/android/http/JsonHttpResponseHandler.java
    rename to library/src/main/java/com/loopj/android/http/JsonHttpResponseHandler.java
    diff --git a/library/src/com/loopj/android/http/MySSLSocketFactory.java b/library/src/main/java/com/loopj/android/http/MySSLSocketFactory.java
    similarity index 100%
    rename from library/src/com/loopj/android/http/MySSLSocketFactory.java
    rename to library/src/main/java/com/loopj/android/http/MySSLSocketFactory.java
    diff --git a/library/src/com/loopj/android/http/PersistentCookieStore.java b/library/src/main/java/com/loopj/android/http/PersistentCookieStore.java
    similarity index 100%
    rename from library/src/com/loopj/android/http/PersistentCookieStore.java
    rename to library/src/main/java/com/loopj/android/http/PersistentCookieStore.java
    diff --git a/library/src/com/loopj/android/http/RequestHandle.java b/library/src/main/java/com/loopj/android/http/RequestHandle.java
    similarity index 100%
    rename from library/src/com/loopj/android/http/RequestHandle.java
    rename to library/src/main/java/com/loopj/android/http/RequestHandle.java
    diff --git a/library/src/com/loopj/android/http/RequestParams.java b/library/src/main/java/com/loopj/android/http/RequestParams.java
    similarity index 100%
    rename from library/src/com/loopj/android/http/RequestParams.java
    rename to library/src/main/java/com/loopj/android/http/RequestParams.java
    diff --git a/library/src/com/loopj/android/http/ResponseHandlerInterface.java b/library/src/main/java/com/loopj/android/http/ResponseHandlerInterface.java
    similarity index 100%
    rename from library/src/com/loopj/android/http/ResponseHandlerInterface.java
    rename to library/src/main/java/com/loopj/android/http/ResponseHandlerInterface.java
    diff --git a/library/src/com/loopj/android/http/RetryHandler.java b/library/src/main/java/com/loopj/android/http/RetryHandler.java
    similarity index 100%
    rename from library/src/com/loopj/android/http/RetryHandler.java
    rename to library/src/main/java/com/loopj/android/http/RetryHandler.java
    diff --git a/library/src/com/loopj/android/http/SerializableCookie.java b/library/src/main/java/com/loopj/android/http/SerializableCookie.java
    similarity index 100%
    rename from library/src/com/loopj/android/http/SerializableCookie.java
    rename to library/src/main/java/com/loopj/android/http/SerializableCookie.java
    diff --git a/library/src/com/loopj/android/http/SimpleMultipartEntity.java b/library/src/main/java/com/loopj/android/http/SimpleMultipartEntity.java
    similarity index 100%
    rename from library/src/com/loopj/android/http/SimpleMultipartEntity.java
    rename to library/src/main/java/com/loopj/android/http/SimpleMultipartEntity.java
    diff --git a/library/src/com/loopj/android/http/SyncHttpClient.java b/library/src/main/java/com/loopj/android/http/SyncHttpClient.java
    similarity index 100%
    rename from library/src/com/loopj/android/http/SyncHttpClient.java
    rename to library/src/main/java/com/loopj/android/http/SyncHttpClient.java
    diff --git a/library/src/com/loopj/android/http/TextHttpResponseHandler.java b/library/src/main/java/com/loopj/android/http/TextHttpResponseHandler.java
    similarity index 100%
    rename from library/src/com/loopj/android/http/TextHttpResponseHandler.java
    rename to library/src/main/java/com/loopj/android/http/TextHttpResponseHandler.java
    
    From 7b30552a3e9bb034dd94b8bb614e54e4d8f3fef9 Mon Sep 17 00:00:00 2001
    From: mareksebera 
    Date: Fri, 1 Nov 2013 09:09:35 +0100
    Subject: [PATCH 193/613] Updated dependencies and version of library
    
    ---
     .travis.yml                                     | 17 +++++------------
     README.md                                       |  2 +-
     build.gradle                                    |  2 +-
     gradle.properties                               |  4 ++--
     library/build.gradle                            |  4 ++--
     library/src/main/AndroidManifest.xml            |  4 ++--
     .../com/loopj/android/http/AsyncHttpClient.java |  2 +-
     sample/build.gradle                             |  6 +++---
     .../loopj/android/http/sample/PostSample.java   |  2 ++
     9 files changed, 19 insertions(+), 24 deletions(-)
    
    diff --git a/.travis.yml b/.travis.yml
    index a7560a7bb..40336a55d 100644
    --- a/.travis.yml
    +++ b/.travis.yml
    @@ -22,22 +22,15 @@ before_install:
       - export PATH=$GRADLE_HOME/bin:$PATH
       # just to test gradle version, against our provided one
       - gradle -v
    -  # newest android SDK 22.0.5
    -  - wget http://dl.google.com/android/android-sdk_r22.0.5-linux.tgz
    -  - tar -zxf android-sdk_r22.0.5-linux.tgz
    +  # newest android SDK 22.3
    +  - wget http://dl.google.com/android/android-sdk_r22.3-linux.tgz
    +  - tar -zxf android-sdk_r22.3-linux.tgz
       - export ANDROID_HOME=`pwd`/android-sdk-linux
       - export PATH=${PATH}:${ANDROID_HOME}/tools:${ANDROID_HOME}/platform-tools
       # manually set sdk.dir variable, according to local paths
       - echo "sdk.dir=$ANDROID_HOME" > local.properties
    -  - echo yes | android update sdk -t tools,platform-tools,extra-android-support,extra-android-m2repository,android-18 --force --no-ui
    -  # build tools cannot be installed through "android update sdk" as of now
    -  - wget http://dl.google.com/android/repository/build-tools_r18.0.1-linux.zip
    -  - mkdir -p $ANDROID_HOME/build-tools
    -  - unzip -qq build-tools_r18.0.1-linux.zip -d $ANDROID_HOME/build-tools/
    -  - mv $ANDROID_HOME/build-tools/android-4.3 $ANDROID_HOME/build-tools/18.0.1
    -  # verify files exist in right paths
    -  - find $ANDROID_HOME/build-tools
    -  - file $ANDROID_HOME/build-tools/18.0.1/aapt
    +  - echo yes | android update sdk -t tools,platform-tools,extra-android-support,extra-android-m2repository,android-19,build-tools-19.0.0 --force --no-ui
    +  # Sonatype bypass
       - echo "nexusUsername=dummy" >> library/gradle.properties
       - echo "nexusPassword=dummy" >> library/gradle.properties
     
    diff --git a/README.md b/README.md
    index 2dd17dd7c..670b0313e 100644
    --- a/README.md
    +++ b/README.md
    @@ -29,7 +29,7 @@ https://oss.sonatype.org/content/repositories/snapshots/com/loopj/android/androi
     Maven URL: https://oss.sonatype.org/content/repositories/snapshots/
     GroupId: com.loopj.android
     ArtifactId: async-http-client
    -Version: 1.4.4-SNAPSHOT
    +Version: 1.4.5-SNAPSHOT
     Packaging: JAR or AAR
     ```
     
    diff --git a/build.gradle b/build.gradle
    index 9fe1e6adc..868085567 100644
    --- a/build.gradle
    +++ b/build.gradle
    @@ -14,7 +14,7 @@ def isReleaseBuild() {
     
     allprojects {
         group = 'com.loopj.android'
    -    version = '1.4.4'
    +    version = '1.4.5-SNAPSHOT'
     
         repositories {
             mavenCentral()
    diff --git a/gradle.properties b/gradle.properties
    index 2c0fb8c88..faf4d9555 100644
    --- a/gradle.properties
    +++ b/gradle.properties
    @@ -1,5 +1,5 @@
    -VERSION_NAME=1.4.4
    -VERSION_CODE=144
    +VERSION_NAME=1.4.5-SNAPSHOT
    +VERSION_CODE=145
     GROUP=com.loopj.android
     
     POM_DESCRIPTION=An Asynchronous HTTP Library for Android
    diff --git a/library/build.gradle b/library/build.gradle
    index 3c0599675..7cd3e2af7 100644
    --- a/library/build.gradle
    +++ b/library/build.gradle
    @@ -1,8 +1,8 @@
     apply plugin: 'android-library'
     
     android {
    -    compileSdkVersion 18
    -    buildToolsVersion '18.0.1'
    +    compileSdkVersion 19
    +    buildToolsVersion '19.0.0'
     }
     
     android.libraryVariants.all { variant ->
    diff --git a/library/src/main/AndroidManifest.xml b/library/src/main/AndroidManifest.xml
    index 0705929fd..13d6f26ed 100644
    --- a/library/src/main/AndroidManifest.xml
    +++ b/library/src/main/AndroidManifest.xml
    @@ -1,8 +1,8 @@
     
     
    +    android:versionName="1.4.5-SNAPSHOT"
    +    android:versionCode="145">
     
         
    diff --git a/library/src/main/java/com/loopj/android/http/AsyncHttpClient.java b/library/src/main/java/com/loopj/android/http/AsyncHttpClient.java
    index 92116f4bf..c11445898 100644
    --- a/library/src/main/java/com/loopj/android/http/AsyncHttpClient.java
    +++ b/library/src/main/java/com/loopj/android/http/AsyncHttpClient.java
    @@ -91,7 +91,7 @@
      * 
    */ public class AsyncHttpClient { - private static final String VERSION = "1.4.4"; + private static final String VERSION = "1.4.5"; private static final int DEFAULT_MAX_CONNECTIONS = 10; private static final int DEFAULT_SOCKET_TIMEOUT = 10 * 1000; diff --git a/sample/build.gradle b/sample/build.gradle index d388761dc..0792285ef 100644 --- a/sample/build.gradle +++ b/sample/build.gradle @@ -16,12 +16,12 @@ repositories { } android { - compileSdkVersion 18 - buildToolsVersion "18.0.1" + compileSdkVersion 19 + buildToolsVersion '19.0.0' defaultConfig { minSdkVersion 3 - targetSdkVersion 18 + targetSdkVersion 19 } } diff --git a/sample/src/main/java/com/loopj/android/http/sample/PostSample.java b/sample/src/main/java/com/loopj/android/http/sample/PostSample.java index 202fd480c..8ecad2b08 100644 --- a/sample/src/main/java/com/loopj/android/http/sample/PostSample.java +++ b/sample/src/main/java/com/loopj/android/http/sample/PostSample.java @@ -5,12 +5,14 @@ import org.apache.http.Header; import org.apache.http.HttpEntity; +import org.apache.http.message.BasicHeader; public class PostSample extends SampleParentActivity { private static final String LOG_TAG = "PostSample"; @Override protected void executeSample(AsyncHttpClient client, String URL, Header[] headers, HttpEntity entity, AsyncHttpResponseHandler responseHandler) { + headers = new Header[]{ new BasicHeader("Content-Type", "ee") }; client.post(this, URL, headers, entity, null, responseHandler); } From 665a0eb8d1b823559154107368a44eae4aa900ab Mon Sep 17 00:00:00 2001 From: mareksebera Date: Fri, 1 Nov 2013 09:34:47 +0100 Subject: [PATCH 194/613] More priority to contentType param --- .../src/main/java/com/loopj/android/http/AsyncHttpClient.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/src/main/java/com/loopj/android/http/AsyncHttpClient.java b/library/src/main/java/com/loopj/android/http/AsyncHttpClient.java index c11445898..4577aa3dc 100644 --- a/library/src/main/java/com/loopj/android/http/AsyncHttpClient.java +++ b/library/src/main/java/com/loopj/android/http/AsyncHttpClient.java @@ -884,7 +884,7 @@ public RequestHandle delete(Context context, String url, Header[] headers, Reque */ protected RequestHandle sendRequest(DefaultHttpClient client, HttpContext httpContext, HttpUriRequest uriRequest, String contentType, ResponseHandlerInterface responseHandler, Context context) { if (contentType != null) { - uriRequest.addHeader("Content-Type", contentType); + uriRequest.setHeader("Content-Type", contentType); } responseHandler.setRequestHeaders(uriRequest.getAllHeaders()); From 11f08f6177ee8ae4dd495f141020292ff0773c08 Mon Sep 17 00:00:00 2001 From: mareksebera Date: Fri, 1 Nov 2013 09:38:14 +0100 Subject: [PATCH 195/613] Fixes #368 --- .../java/com/loopj/android/http/AsyncHttpResponseHandler.java | 2 +- .../java/com/loopj/android/http/TextHttpResponseHandler.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/library/src/main/java/com/loopj/android/http/AsyncHttpResponseHandler.java b/library/src/main/java/com/loopj/android/http/AsyncHttpResponseHandler.java index 3937e1cca..58371ecab 100644 --- a/library/src/main/java/com/loopj/android/http/AsyncHttpResponseHandler.java +++ b/library/src/main/java/com/loopj/android/http/AsyncHttpResponseHandler.java @@ -315,7 +315,7 @@ public void onFailure(int statusCode, Header[] headers, byte[] responseBody, Thr onFailure(statusCode, headers, error, response); } catch (UnsupportedEncodingException e) { Log.e(LOG_TAG, e.toString()); - onFailure(statusCode, headers, e, null); + onFailure(statusCode, headers, error, null); } } diff --git a/library/src/main/java/com/loopj/android/http/TextHttpResponseHandler.java b/library/src/main/java/com/loopj/android/http/TextHttpResponseHandler.java index bf93d8435..c476814d1 100644 --- a/library/src/main/java/com/loopj/android/http/TextHttpResponseHandler.java +++ b/library/src/main/java/com/loopj/android/http/TextHttpResponseHandler.java @@ -107,8 +107,8 @@ public void onFailure(int statusCode, Header[] headers, byte[] responseBody, Thr String response = responseBody == null ? null : new String(responseBody, getCharset()); onFailure(statusCode, headers, response, error); } catch (UnsupportedEncodingException e) { - Log.v(LOG_TAG, "String encoding failed, calling onFailure(int, Header[], String, Throwable)"); - onFailure(0, headers, (String) null, e); + Log.e(LOG_TAG, "String encoding failed, calling onFailure(int, Header[], String, Throwable)", e); + onFailure(0, headers, (String) null, error); } } From 92bbda3f22c27491b4d289c07f9ea8b5adae9114 Mon Sep 17 00:00:00 2001 From: mareksebera Date: Fri, 1 Nov 2013 10:50:24 +0100 Subject: [PATCH 196/613] Logging and threadpool size at limits set --- .../main/java/com/loopj/android/http/AsyncHttpClient.java | 1 + .../main/java/com/loopj/android/http/AsyncHttpRequest.java | 2 ++ .../loopj/android/http/sample/ThreadingTimeoutSample.java | 7 +++++++ 3 files changed, 10 insertions(+) create mode 100644 sample/src/main/java/com/loopj/android/http/sample/ThreadingTimeoutSample.java diff --git a/library/src/main/java/com/loopj/android/http/AsyncHttpClient.java b/library/src/main/java/com/loopj/android/http/AsyncHttpClient.java index 4577aa3dc..210958c0d 100644 --- a/library/src/main/java/com/loopj/android/http/AsyncHttpClient.java +++ b/library/src/main/java/com/loopj/android/http/AsyncHttpClient.java @@ -337,6 +337,7 @@ public void setMaxConnections(int maxConnections) { this.maxConnections = maxConnections; final HttpParams httpParams = this.httpClient.getParams(); ConnManagerParams.setMaxConnectionsPerRoute(httpParams, new ConnPerRouteBean(this.maxConnections)); + this.threadPool.setCorePoolSize(maxConnections); } /** diff --git a/library/src/main/java/com/loopj/android/http/AsyncHttpRequest.java b/library/src/main/java/com/loopj/android/http/AsyncHttpRequest.java index d6c36f2af..a3d7afd2f 100644 --- a/library/src/main/java/com/loopj/android/http/AsyncHttpRequest.java +++ b/library/src/main/java/com/loopj/android/http/AsyncHttpRequest.java @@ -55,6 +55,8 @@ public void run() { } catch (IOException e) { if (responseHandler != null) { responseHandler.sendFailureMessage(0, null, null, e); + } else { + Log.e("AsyncHttpRequest", "makeRequestWithRetries returned error, but handler is null", e); } } diff --git a/sample/src/main/java/com/loopj/android/http/sample/ThreadingTimeoutSample.java b/sample/src/main/java/com/loopj/android/http/sample/ThreadingTimeoutSample.java new file mode 100644 index 000000000..0238a9d9e --- /dev/null +++ b/sample/src/main/java/com/loopj/android/http/sample/ThreadingTimeoutSample.java @@ -0,0 +1,7 @@ +package com.loopj.android.http.sample; + +/** + * Created by msebera on 01/11/13. + */ +public class ThreadingTimeoutSample { +} From 042bd5a8cd11c6753336caba2775fe869fc8e347 Mon Sep 17 00:00:00 2001 From: mareksebera Date: Fri, 1 Nov 2013 10:50:46 +0100 Subject: [PATCH 197/613] Test for threading --- sample/src/main/AndroidManifest.xml | 1 + .../loopj/android/http/sample/PostSample.java | 2 - .../http/sample/ThreadingTimeoutSample.java | 80 ++++++++++++++++++- .../http/sample/WaypointsActivity.java | 5 +- 4 files changed, 81 insertions(+), 7 deletions(-) diff --git a/sample/src/main/AndroidManifest.xml b/sample/src/main/AndroidManifest.xml index 3e0a2fa82..23b6ee77d 100644 --- a/sample/src/main/AndroidManifest.xml +++ b/sample/src/main/AndroidManifest.xml @@ -27,6 +27,7 @@ + diff --git a/sample/src/main/java/com/loopj/android/http/sample/PostSample.java b/sample/src/main/java/com/loopj/android/http/sample/PostSample.java index 8ecad2b08..202fd480c 100644 --- a/sample/src/main/java/com/loopj/android/http/sample/PostSample.java +++ b/sample/src/main/java/com/loopj/android/http/sample/PostSample.java @@ -5,14 +5,12 @@ import org.apache.http.Header; import org.apache.http.HttpEntity; -import org.apache.http.message.BasicHeader; public class PostSample extends SampleParentActivity { private static final String LOG_TAG = "PostSample"; @Override protected void executeSample(AsyncHttpClient client, String URL, Header[] headers, HttpEntity entity, AsyncHttpResponseHandler responseHandler) { - headers = new Header[]{ new BasicHeader("Content-Type", "ee") }; client.post(this, URL, headers, entity, null, responseHandler); } diff --git a/sample/src/main/java/com/loopj/android/http/sample/ThreadingTimeoutSample.java b/sample/src/main/java/com/loopj/android/http/sample/ThreadingTimeoutSample.java index 0238a9d9e..aeb131a5c 100644 --- a/sample/src/main/java/com/loopj/android/http/sample/ThreadingTimeoutSample.java +++ b/sample/src/main/java/com/loopj/android/http/sample/ThreadingTimeoutSample.java @@ -1,7 +1,79 @@ package com.loopj.android.http.sample; -/** - * Created by msebera on 01/11/13. - */ -public class ThreadingTimeoutSample { +import android.util.SparseArray; + +import com.loopj.android.http.AsyncHttpClient; +import com.loopj.android.http.AsyncHttpResponseHandler; + +import org.apache.http.Header; +import org.apache.http.HttpEntity; + +import java.util.Random; + +public class ThreadingTimeoutSample extends SampleParentActivity { + + private static final String LOG_TAG = "ThreadingTimeoutSample"; + private SparseArray states = new SparseArray(); + + @Override + protected int getSampleTitle() { + return R.string.app_name; + } + + @Override + protected boolean isRequestBodyAllowed() { + return false; + } + + @Override + protected boolean isRequestHeadersAllowed() { + return false; + } + + @Override + protected String getDefaultURL() { + return "http://httpbin.org/delay/6"; + } + + private synchronized void setStatus(int id, String status) { + String current = states.get(id, null); + states.put(id, current == null ? status : current + "," + status); + clearOutputs(); + for (int i = 0; i < states.size(); i++) { + debugResponse(LOG_TAG, states.keyAt(i) + ": " + states.get(states.keyAt(i))); + } + } + + @Override + protected AsyncHttpResponseHandler getResponseHandler() { + return new AsyncHttpResponseHandler() { + + private int id = new Random().nextInt(1000); + + @Override + public void onStart() { + setStatus(id, "START"); + } + + @Override + public void onSuccess(int statusCode, Header[] headers, byte[] responseBody) { + setStatus(id, "SUCCESS"); + } + + @Override + public void onFinish() { + setStatus(id, "FINISH"); + } + + @Override + public void onFailure(int statusCode, Header[] headers, byte[] responseBody, Throwable error) { + setStatus(id, "FAILURE"); + } + }; + } + + @Override + protected void executeSample(AsyncHttpClient client, String URL, Header[] headers, HttpEntity entity, AsyncHttpResponseHandler responseHandler) { + client.get(this, URL, headers, null, responseHandler); + } } diff --git a/sample/src/main/java/com/loopj/android/http/sample/WaypointsActivity.java b/sample/src/main/java/com/loopj/android/http/sample/WaypointsActivity.java index 736c54249..857a0ffb9 100644 --- a/sample/src/main/java/com/loopj/android/http/sample/WaypointsActivity.java +++ b/sample/src/main/java/com/loopj/android/http/sample/WaypointsActivity.java @@ -9,7 +9,7 @@ public class WaypointsActivity extends ListActivity { - private static final String[] samples = new String[]{"GET", "POST", "DELETE", "PUT", "JSON", "FILE", "BINARY"}; + private static final String[] samples = new String[]{"GET", "POST", "DELETE", "PUT", "JSON", "FILE", "BINARY", "THREADING TIMEOUTS"}; @Override protected void onCreate(Bundle savedInstanceState) { @@ -43,6 +43,9 @@ protected void onListItemClick(ListView l, View v, int position, long id) { case 6: targetClass = BinarySample.class; break; + case 7: + targetClass = ThreadingTimeoutSample.class; + break; } if (targetClass != null) startActivity(new Intent(this, targetClass)); From e523eb3cec85af7bf5ec30f7df9fec417c2eb785 Mon Sep 17 00:00:00 2001 From: mareksebera Date: Tue, 5 Nov 2013 17:35:50 +0100 Subject: [PATCH 198/613] Simplified overriding, removed deprecated, forced overriding of response handler Removed all deprecated methods marked in 1.4.4 and earlier Made response handlers abstract to force overriding correct methods (with exception on JsonHttpResponseHandler) Updated Sample application accorindgly --- .../http/AsyncHttpResponseHandler.java | 119 +----------------- .../http/BaseJsonHttpResponseHandler.java | 60 +-------- .../http/BinaryHttpResponseHandler.java | 47 ++----- .../http/FileAsyncHttpResponseHandler.java | 42 ++----- .../android/http/JsonHttpResponseHandler.java | 119 ++++-------------- .../android/http/TextHttpResponseHandler.java | 64 +++------- .../loopj/android/http/sample/FileSample.java | 10 +- 7 files changed, 74 insertions(+), 387 deletions(-) diff --git a/library/src/main/java/com/loopj/android/http/AsyncHttpResponseHandler.java b/library/src/main/java/com/loopj/android/http/AsyncHttpResponseHandler.java index 58371ecab..836988471 100644 --- a/library/src/main/java/com/loopj/android/http/AsyncHttpResponseHandler.java +++ b/library/src/main/java/com/loopj/android/http/AsyncHttpResponseHandler.java @@ -32,7 +32,6 @@ import java.io.IOException; import java.io.InputStream; -import java.io.UnsupportedEncodingException; import java.lang.ref.WeakReference; import java.net.URI; @@ -57,7 +56,8 @@ * } * * @Override - * public void onFailure(int statusCode, Header[] headers, byte[] responseBody, Throwable error) + * public void onFailure(int statusCode, Header[] headers, byte[] responseBody, Throwable + * error) * { * // Response failed :( * } @@ -79,7 +79,7 @@ * }); *
    */ -public class AsyncHttpResponseHandler implements ResponseHandlerInterface { +public abstract class AsyncHttpResponseHandler implements ResponseHandlerInterface { private static final String LOG_TAG = "AsyncHttpResponseHandler"; protected static final int SUCCESS_MESSAGE = 0; @@ -170,11 +170,6 @@ public AsyncHttpResponseHandler() { } } - - // - // Callbacks to be overridden, typically anonymously - // - /** * Fired when the request progress, override to handle in your own code * @@ -182,6 +177,7 @@ public AsyncHttpResponseHandler() { * @param totalSize total size of file */ public void onProgress(int bytesWritten, int totalSize) { + Log.d(LOG_TAG, String.format("Progress %d from %d (%d)", bytesWritten, totalSize, bytesWritten / (totalSize / 100))); } /** @@ -197,41 +193,6 @@ public void onStart() { public void onFinish() { } - /** - * Fired when a request returns successfully, override to handle in your own code - * - * @param content the body of the HTTP response from the server - * @deprecated use {@link #onSuccess(int, Header[], byte[])} - */ - @Deprecated - public void onSuccess(String content) { - } - - /** - * Fired when a request returns successfully, override to handle in your own code - * - * @param statusCode the status code of the response - * @param headers the headers of the HTTP response - * @param content the body of the HTTP response from the server - * @deprecated use {@link #onSuccess(int, Header[], byte[])} - */ - @Deprecated - public void onSuccess(int statusCode, Header[] headers, String content) { - onSuccess(statusCode, content); - } - - /** - * Fired when a request returns successfully, override to handle in your own code - * - * @param statusCode the status code of the response - * @param content the body of the HTTP response from the server - * @deprecated use {@link #onSuccess(int, Header[], byte[])} - */ - @Deprecated - public void onSuccess(int statusCode, String content) { - onSuccess(content); - } - /** * Fired when a request returns successfully, override to handle in your own code * @@ -239,67 +200,7 @@ public void onSuccess(int statusCode, String content) { * @param headers return headers, if any * @param responseBody the body of the HTTP response from the server */ - public void onSuccess(int statusCode, Header[] headers, byte[] responseBody) { - try { - String response = responseBody == null ? null : new String(responseBody, getCharset()); - onSuccess(statusCode, headers, response); - } catch (UnsupportedEncodingException e) { - Log.e(LOG_TAG, e.toString()); - onFailure(statusCode, headers, e, null); - } - } - - /** - * Fired when a request fails to complete, override to handle in your own code - * - * @param error the underlying cause of the failure - * @deprecated use {@link #onFailure(Throwable, String)} - */ - @Deprecated - public void onFailure(Throwable error) { - } - - /** - * Fired when a request fails to complete, override to handle in your own code - * - * @param error the underlying cause of the failure - * @param content the response body, if any - * @deprecated use {@link #onFailure(int, Header[], byte[], Throwable)} - */ - @Deprecated - public void onFailure(Throwable error, String content) { - // By default, call the deprecated onFailure(Throwable) for compatibility - onFailure(error); - } - - /** - * Fired when a request fails to complete, override to handle in your own code - * - * @param statusCode return HTTP status code - * @param error the underlying cause of the failure - * @param content the response body, if any - * @deprecated use {@link #onFailure(int, Header[], byte[], Throwable)} - */ - @Deprecated - public void onFailure(int statusCode, Throwable error, String content) { - // By default, call the chain method onFailure(Throwable,String) - onFailure(error, content); - } - - /** - * Fired when a request fails to complete, override to handle in your own code - * - * @param statusCode return HTTP status code - * @param headers return headers, if any - * @param error the underlying cause of the failure - * @param content the response body, if any - * @deprecated use {@link #onFailure(int, Header[], byte[], Throwable)} - */ - @Deprecated - public void onFailure(int statusCode, Header[] headers, Throwable error, String content) { - // By default, call the chain method onFailure(int,Throwable,String) - onFailure(statusCode, error, content); - } + public abstract void onSuccess(int statusCode, Header[] headers, byte[] responseBody); /** * Fired when a request fails to complete, override to handle in your own code @@ -309,15 +210,7 @@ public void onFailure(int statusCode, Header[] headers, Throwable error, String * @param responseBody the response body, if any * @param error the underlying cause of the failure */ - public void onFailure(int statusCode, Header[] headers, byte[] responseBody, Throwable error) { - try { - String response = responseBody == null ? null : new String(responseBody, getCharset()); - onFailure(statusCode, headers, error, response); - } catch (UnsupportedEncodingException e) { - Log.e(LOG_TAG, e.toString()); - onFailure(statusCode, headers, error, null); - } - } + public abstract void onFailure(int statusCode, Header[] headers, byte[] responseBody, Throwable error); /** * Fired when a retry occurs, override to handle in your own code diff --git a/library/src/main/java/com/loopj/android/http/BaseJsonHttpResponseHandler.java b/library/src/main/java/com/loopj/android/http/BaseJsonHttpResponseHandler.java index c9a89091e..1de41440d 100644 --- a/library/src/main/java/com/loopj/android/http/BaseJsonHttpResponseHandler.java +++ b/library/src/main/java/com/loopj/android/http/BaseJsonHttpResponseHandler.java @@ -31,73 +31,19 @@ public abstract class BaseJsonHttpResponseHandler extends TextHttpRes */ public BaseJsonHttpResponseHandler() { - super(DEFAULT_CHARSET); + this(DEFAULT_CHARSET); } public BaseJsonHttpResponseHandler(String encoding) { super(encoding); } - @Override - public final void onSuccess(int statusCode, Header[] headers, byte[] responseBody) { - // Disabling this method - super.onSuccess(statusCode, headers, responseBody); - } - - @Override - public final void onSuccess(String content) { - // Disabling usage of this method, until removed from parent - super.onSuccess(content); - } - - @Override - public final void onSuccess(int statusCode, String content) { - // Disabling usage of this method, until removed from parent - super.onSuccess(statusCode, content); - } - - @Override - public final void onFailure(String responseBody, Throwable error) { - // Disabling usage of this method, until removed from parent - super.onFailure(responseBody, error); - } - - @Override - public final void onFailure(int statusCode, Header[] headers, byte[] responseBody, Throwable error) { - //Disabling this method - super.onFailure(statusCode, headers, responseBody, error); - } - - @Override - public final void onFailure(Throwable error) { - // Disabling usage of this method, until removed from parent - super.onFailure(error); - } - - @Override - public final void onFailure(Throwable error, String content) { - // Disabling usage of this method, until removed from parent - super.onFailure(error, content); - } - - @Override - public final void onFailure(int statusCode, Throwable error, String content) { - // Disabling usage of this method, until removed from parent - super.onFailure(statusCode, error, content); - } - - @Override - public final void onFailure(int statusCode, Header[] headers, Throwable error, String content) { - // Disabling usage of this method, until removed from parent - super.onFailure(statusCode, headers, error, content); - } - public abstract void onSuccess(int statusCode, Header[] headers, String rawResponse, JSON_TYPE response); public abstract void onFailure(int statusCode, Header[] headers, Throwable e, String rawData, JSON_TYPE errorResponse); @Override - public void onSuccess(final int statusCode, final Header[] headers, final String responseBody) { + public final void onSuccess(final int statusCode, final Header[] headers, final String responseBody) { if (statusCode != HttpStatus.SC_NO_CONTENT) { new Thread(new Runnable() { @Override @@ -127,7 +73,7 @@ public void run() { } @Override - public void onFailure(final int statusCode, final Header[] headers, final String responseBody, final Throwable e) { + public final void onFailure(final int statusCode, final Header[] headers, final String responseBody, final Throwable e) { if (responseBody != null) { new Thread(new Runnable() { @Override diff --git a/library/src/main/java/com/loopj/android/http/BinaryHttpResponseHandler.java b/library/src/main/java/com/loopj/android/http/BinaryHttpResponseHandler.java index 58055ac34..9f7079136 100644 --- a/library/src/main/java/com/loopj/android/http/BinaryHttpResponseHandler.java +++ b/library/src/main/java/com/loopj/android/http/BinaryHttpResponseHandler.java @@ -49,7 +49,9 @@ * }); *
    */ -public class BinaryHttpResponseHandler extends AsyncHttpResponseHandler { +public abstract class BinaryHttpResponseHandler extends AsyncHttpResponseHandler { + + private static final String LOG_TAG = "BinaryHttpResponseHandler"; private String[] mAllowedContentTypes = new String[]{ "image/jpeg", @@ -80,31 +82,11 @@ public BinaryHttpResponseHandler() { * @param allowedContentTypes content types array, eg. 'image/jpeg' or pattern '.*' */ public BinaryHttpResponseHandler(String[] allowedContentTypes) { - this(); - mAllowedContentTypes = allowedContentTypes; - } - - - // - // Callbacks to be overridden, typically anonymously - // - - /** - * Fired when a request returns successfully, override to handle in your own code - * - * @param binaryData the body of the HTTP response from the server - */ - public void onSuccess(byte[] binaryData) { - } - - /** - * Fired when a request returns successfully, override to handle in your own code - * - * @param statusCode the status code of the response - * @param binaryData the body of the HTTP response from the server - */ - public void onSuccess(int statusCode, byte[] binaryData) { - onSuccess(binaryData); + super(); + if (allowedContentTypes != null) + mAllowedContentTypes = allowedContentTypes; + else + Log.e(LOG_TAG, "Constructor passed allowedContentTypes was null !"); } /** @@ -116,9 +98,7 @@ public void onSuccess(int statusCode, byte[] binaryData) { */ @Override - public void onSuccess(int statusCode, Header[] headers, byte[] binaryData) { - onSuccess(statusCode, binaryData); - } + public abstract void onSuccess(int statusCode, Header[] headers, byte[] binaryData); /** * Fired when a request fails to complete, override to handle in your own code @@ -130,15 +110,8 @@ public void onSuccess(int statusCode, Header[] headers, byte[] binaryData) { */ @Override - public void onFailure(int statusCode, Header[] headers, byte[] binaryData, Throwable error) { - onFailure(statusCode, error, null); - } - - // - // Pre-processing of messages (in original calling thread, typically the UI thread) - // + public abstract void onFailure(int statusCode, Header[] headers, byte[] binaryData, Throwable error); - // Interface to AsyncHttpRequest @Override public final void sendResponseMessage(HttpResponse response) throws IOException { StatusLine status = response.getStatusLine(); diff --git a/library/src/main/java/com/loopj/android/http/FileAsyncHttpResponseHandler.java b/library/src/main/java/com/loopj/android/http/FileAsyncHttpResponseHandler.java index 51177c93b..846d76e25 100644 --- a/library/src/main/java/com/loopj/android/http/FileAsyncHttpResponseHandler.java +++ b/library/src/main/java/com/loopj/android/http/FileAsyncHttpResponseHandler.java @@ -12,7 +12,7 @@ import java.io.InputStream; -public class FileAsyncHttpResponseHandler extends AsyncHttpResponseHandler { +public abstract class FileAsyncHttpResponseHandler extends AsyncHttpResponseHandler { private File mFile; private static final String LOG_TAG = "FileAsyncHttpResponseHandler"; @@ -29,6 +29,10 @@ public FileAsyncHttpResponseHandler(Context c) { this.mFile = getTemporaryFile(c); } + public boolean deleteTargetFile() { + return getTargetFile() == null || getTargetFile().delete(); + } + protected File getTemporaryFile(Context c) { try { return File.createTempFile("temp_", "_handled", c.getCacheDir()); @@ -43,44 +47,22 @@ protected File getTargetFile() { return mFile; } - public void onSuccess(File file) { - } - - public void onSuccess(int statusCode, File file) { - onSuccess(file); - } - - public void onSuccess(int statusCode, Header[] headers, File file) { - onSuccess(statusCode, file); - } - - public void onFailure(Throwable e, File response) { - // By default call lower chain method - onFailure(e); - } - - public void onFailure(int statusCode, Throwable e, File response) { - // By default call lower chain method - onFailure(e, response); - } - - public void onFailure(int statusCode, Header[] headers, Throwable e, File response) { - // By default call lower chain method - onFailure(statusCode, e, response); - } - @Override - public void onFailure(int statusCode, Header[] headers, byte[] responseBody, Throwable error) { + public final void onFailure(int statusCode, Header[] headers, byte[] responseBody, Throwable error) { onFailure(statusCode, headers, error, getTargetFile()); } + public abstract void onFailure(int statusCode, Header[] headers, Throwable e, File response); + @Override - public void onSuccess(int statusCode, Header[] headers, byte[] responseBody) { + public final void onSuccess(int statusCode, Header[] headers, byte[] responseBody) { onSuccess(statusCode, headers, getTargetFile()); } + public abstract void onSuccess(int statusCode, Header[] headers, File file); + @Override - byte[] getResponseData(HttpEntity entity) throws IOException { + protected byte[] getResponseData(HttpEntity entity) throws IOException { if (entity != null) { InputStream instream = entity.getContent(); long contentLength = entity.getContentLength(); diff --git a/library/src/main/java/com/loopj/android/http/JsonHttpResponseHandler.java b/library/src/main/java/com/loopj/android/http/JsonHttpResponseHandler.java index 6778596ce..673a0e6c1 100644 --- a/library/src/main/java/com/loopj/android/http/JsonHttpResponseHandler.java +++ b/library/src/main/java/com/loopj/android/http/JsonHttpResponseHandler.java @@ -30,9 +30,10 @@ /** * Used to intercept and handle the responses from requests made using {@link AsyncHttpClient}, with * automatic parsing into a {@link JSONObject} or {@link JSONArray}.

     

    This class is - * designed to be passed to get, post, put and delete requests with the {@link - * #onSuccess(JSONObject)} or {@link #onSuccess(JSONArray)} methods anonymously overridden. - *

     

    Additionally, you can override the other event methods from the parent class. + * designed to be passed to get, post, put and delete requests with the {@link #onSuccess(int, + * org.apache.http.Header[], org.json.JSONArray)} or {@link #onSuccess(int, + * org.apache.http.Header[], org.json.JSONObject)} methods anonymously overridden.

     

    + * Additionally, you can override the other event methods from the parent class. */ public class JsonHttpResponseHandler extends TextHttpResponseHandler { private static final String LOG_TAG = "JsonHttpResponseHandler"; @@ -49,101 +50,24 @@ public JsonHttpResponseHandler(String encoding) { super(encoding); } - // - // Callbacks to be overridden, typically anonymously - // - - /** - * Fired when a request returns successfully and contains a json object at the base of the - * response string. Override to handle in your own code. - * - * @param response the parsed json object found in the server response (if any) - */ - public void onSuccess(JSONObject response) { - } - - - /** - * Fired when a request returns successfully and contains a json array at the base of the - * response string. Override to handle in your own code. - * - * @param response the parsed json array found in the server response (if any) - */ - public void onSuccess(JSONArray response) { - } - - /** - * Fired when a request returns successfully and contains a json object at the base of the - * response string. Override to handle in your own code. - * - * @param statusCode the status code of the response - * @param headers the headers of the HTTP response - * @param response the parsed json object found in the server response (if any) - */ public void onSuccess(int statusCode, Header[] headers, JSONObject response) { - onSuccess(statusCode, response); - } - /** - * Fired when a request returns successfully and contains a json object at the base of the - * response string. Override to handle in your own code. - * - * @param statusCode the status code of the response - * @param response the parsed json object found in the server response (if any) - */ - public void onSuccess(int statusCode, JSONObject response) { - onSuccess(response); } - /** - * Fired when a request returns successfully and contains a json array at the base of the - * response string. Override to handle in your own code. - * - * @param statusCode the status code of the response - * @param headers the headers of the HTTP response - * @param response the parsed json array found in the server response (if any) - */ public void onSuccess(int statusCode, Header[] headers, JSONArray response) { - onSuccess(statusCode, response); - } - - /** - * Fired when a request returns successfully and contains a json array at the base of the - * response string. Override to handle in your own code. - * - * @param statusCode the status code of the response - * @param response the parsed json array found in the server response (if any) - */ - public void onSuccess(int statusCode, JSONArray response) { - onSuccess(response); - } - public void onFailure(Throwable e, JSONObject errorResponse) { - onFailure(e); - } - - public void onFailure(int statusCode, Throwable e, JSONObject errorResponse) { - onFailure(e, errorResponse); } public void onFailure(int statusCode, Header[] headers, Throwable e, JSONObject errorResponse) { - onFailure(statusCode, e, errorResponse); - } - - public void onFailure(Throwable e, JSONArray errorResponse) { - onFailure(e); - } - public void onFailure(int statusCode, Throwable e, JSONArray errorResponse) { - onFailure(e, errorResponse); } public void onFailure(int statusCode, Header[] headers, Throwable e, JSONArray errorResponse) { - onFailure(statusCode, e, errorResponse); + } @Override - public void onSuccess(final int statusCode, final Header[] headers, final String responseBody) { + public final void onSuccess(final int statusCode, final Header[] headers, final byte[] responseBody) { if (statusCode != HttpStatus.SC_NO_CONTENT) { new Thread(new Runnable() { @Override @@ -153,14 +77,14 @@ public void run() { postRunnable(new Runnable() { @Override public void run() { - if (jsonResponse instanceof JSONObject) { + if (jsonResponse == null) { + onSuccess(statusCode, headers, (JSONObject) null); + } else if (jsonResponse instanceof JSONObject) { onSuccess(statusCode, headers, (JSONObject) jsonResponse); } else if (jsonResponse instanceof JSONArray) { onSuccess(statusCode, headers, (JSONArray) jsonResponse); - } else if (jsonResponse instanceof String) { - onSuccess(statusCode, headers, (String) jsonResponse); } else { - onFailure(new JSONException("Unexpected type " + jsonResponse.getClass().getName()), (JSONObject) null); + onFailure(statusCode, headers, new JSONException("Unexpected response type " + jsonResponse.getClass().getName()), (JSONObject) null); } } @@ -169,7 +93,7 @@ public void run() { postRunnable(new Runnable() { @Override public void run() { - onFailure(ex, (JSONObject) null); + onFailure(statusCode, headers, ex, (JSONObject) null); } }); } @@ -181,7 +105,7 @@ public void run() { } @Override - public void onFailure(final int statusCode, final Header[] headers, final String responseBody, final Throwable e) { + public final void onFailure(final int statusCode, final Header[] headers, final byte[] responseBody, final Throwable e) { if (responseBody != null) { new Thread(new Runnable() { @Override @@ -191,14 +115,16 @@ public void run() { postRunnable(new Runnable() { @Override public void run() { - if (jsonResponse instanceof JSONObject) { + if (jsonResponse == null) { + onFailure(statusCode, headers, e, (JSONObject) null); + } else if (jsonResponse instanceof JSONObject) { onFailure(statusCode, headers, e, (JSONObject) jsonResponse); } else if (jsonResponse instanceof JSONArray) { onFailure(statusCode, headers, e, (JSONArray) jsonResponse); } else if (jsonResponse instanceof String) { - onFailure(statusCode, headers, e, (String) jsonResponse); + onFailure(statusCode, headers, (String) jsonResponse, e); } else { - onFailure(new JSONException("Unexpected type " + jsonResponse.getClass().getName()), (JSONObject) null); + onFailure(statusCode, headers, new JSONException("Unexpected response type " + jsonResponse.getClass().getName()), (JSONObject) null); } } }); @@ -220,14 +146,17 @@ public void run() { } } - protected Object parseResponse(String responseBody) throws JSONException { + protected Object parseResponse(byte[] responseBody) throws JSONException { if (null == responseBody) return null; Object result = null; //trim the string to prevent start with blank, and test if the string is valid JSON, because the parser don't do this :(. If Json is not valid this will return null - String jsonString = responseBody.trim(); - if (jsonString.startsWith("{") || jsonString.startsWith("[")) { - result = new JSONTokener(jsonString).nextValue(); + String jsonString = getResponseString(responseBody, getCharset()); + if (jsonString != null) { + jsonString = jsonString.trim(); + if (jsonString.startsWith("{") || jsonString.startsWith("[")) { + result = new JSONTokener(jsonString).nextValue(); + } } if (result == null) { result = jsonString; diff --git a/library/src/main/java/com/loopj/android/http/TextHttpResponseHandler.java b/library/src/main/java/com/loopj/android/http/TextHttpResponseHandler.java index c476814d1..060fed761 100644 --- a/library/src/main/java/com/loopj/android/http/TextHttpResponseHandler.java +++ b/library/src/main/java/com/loopj/android/http/TextHttpResponseHandler.java @@ -8,10 +8,10 @@ /** * Used to intercept and handle the responses from requests made using {@link AsyncHttpClient}. The - * {@link #onSuccess(String)} method is designed to be anonymously overridden with your own response - * handling code.

     

    Additionally, you can override the {@link #onFailure(String, - * Throwable)}, {@link #onStart()}, and {@link #onFinish()} methods as required.

     

    For - * example:

     

    + * {@link #onSuccess(int, org.apache.http.Header[], String)} method is designed to be anonymously + * overridden with your own response handling code.

     

    Additionally, you can override the + * {@link #onFailure(int, org.apache.http.Header[], String, Throwable)}, {@link #onStart()}, and + * {@link #onFinish()} methods as required.

     

    For example:

     

    *
      * AsyncHttpClient client = new AsyncHttpClient();
      * client.get("http://www.google.com/", new TextHttpResponseHandler() {
    @@ -40,10 +40,6 @@
     public class TextHttpResponseHandler extends AsyncHttpResponseHandler {
         private static final String LOG_TAG = "TextHttpResponseHandler";
     
    -    /**
    -     * Creates a new TextHttpResponseHandler
    -     */
    -
         public TextHttpResponseHandler() {
             this(DEFAULT_CHARSET);
         }
    @@ -53,62 +49,30 @@ public TextHttpResponseHandler(String encoding) {
             setCharset(encoding);
         }
     
    -    //
    -    // Callbacks to be overridden, typically anonymously
    -    //
    -
    -    /**
    -     * Fired when a request fails to complete, override to handle in your own code
    -     *
    -     * @param responseBody the response body, if any
    -     * @param error        the underlying cause of the failure
    -     */
    -    public void onFailure(String responseBody, Throwable error) {
    -    }
    -
    -    /**
    -     * Fired when a request fails to complete, override to handle in your own code
    -     *
    -     * @param statusCode   the status code of the response
    -     * @param headers      HTTP response headers
    -     * @param responseBody the response body, if any
    -     * @param error        the underlying cause of the failure
    -     */
         public void onFailure(int statusCode, Header[] headers, String responseBody, Throwable error) {
    -        onFailure(responseBody, error);
    +
         }
     
    -    /**
    -     * Fired when a request returns successfully, override to handle in your own code
    -     *
    -     * @param statusCode   the status code of the response
    -     * @param headers      HTTP response headers
    -     * @param responseBody the body of the HTTP response from the server
    -     */
    -    @Override
         public void onSuccess(int statusCode, Header[] headers, String responseBody) {
    -        onSuccess(statusCode, responseBody);
    +
         }
     
         @Override
         public void onSuccess(int statusCode, Header[] headers, byte[] responseBody) {
    -        try {
    -            String response = responseBody == null ? null : new String(responseBody, getCharset());
    -            onSuccess(statusCode, headers, response);
    -        } catch (UnsupportedEncodingException e) {
    -            Log.v(LOG_TAG, "String encoding failed, calling onFailure(int, Header[], String, Throwable)");
    -            onFailure(0, headers, (String) null, e);
    -        }
    +        onSuccess(statusCode, headers, getResponseString(responseBody, getCharset()));
         }
     
         @Override
         public void onFailure(int statusCode, Header[] headers, byte[] responseBody, Throwable error) {
    +        onFailure(statusCode, headers, getResponseString(responseBody, getCharset()), error);
    +    }
    +
    +    public static String getResponseString(byte[] stringData, String charset) {
             try {
    -            String response = responseBody == null ? null : new String(responseBody, getCharset());
    -            onFailure(statusCode, headers, response, error);
    +            return stringData == null ? null : new String(stringData, charset);
             } catch (UnsupportedEncodingException e) {
    -            Log.e(LOG_TAG, "String encoding failed, calling onFailure(int, Header[], String, Throwable)", e);
    -            onFailure(0, headers, (String) null, error);
    +            Log.e(LOG_TAG, "Encoding response into string failed", e);
    +            return null;
             }
         }
     
    diff --git a/sample/src/main/java/com/loopj/android/http/sample/FileSample.java b/sample/src/main/java/com/loopj/android/http/sample/FileSample.java
    index 46912aaab..eb3447226 100644
    --- a/sample/src/main/java/com/loopj/android/http/sample/FileSample.java
    +++ b/sample/src/main/java/com/loopj/android/http/sample/FileSample.java
    @@ -44,18 +44,18 @@ public void onStart() {
                 }
     
                 @Override
    -            public void onSuccess(int statusCode, Header[] headers, File file) {
    +            public void onSuccess(int statusCode, Header[] headers, File response) {
                     debugHeaders(LOG_TAG, headers);
                     debugStatusCode(LOG_TAG, statusCode);
    -                debugFile(getTargetFile());
    +                debugFile(response);
                 }
     
                 @Override
    -            public void onFailure(int statusCode, Header[] headers, byte[] errorResponse, Throwable e) {
    +            public void onFailure(int statusCode, Header[] headers, Throwable e, File response) {
                     debugHeaders(LOG_TAG, headers);
                     debugStatusCode(LOG_TAG, statusCode);
                     debugThrowable(LOG_TAG, e);
    -                debugFile(getTargetFile());
    +                debugFile(response);
                 }
     
                 private void debugFile(File file) {
    @@ -68,7 +68,7 @@ private void debugFile(File file) {
                     } catch (Throwable t) {
                         Log.e(LOG_TAG, "Cannot debug file contents", t);
                     }
    -                if (!file.delete()) {
    +                if (!deleteTargetFile()) {
                         Log.d(LOG_TAG, "Could not delete response file " + file.getAbsolutePath());
                     }
                 }
    
    From b998b3b1673960cb91f452550be7707ecca12f3e Mon Sep 17 00:00:00 2001
    From: mareksebera 
    Date: Tue, 5 Nov 2013 19:21:05 +0100
    Subject: [PATCH 199/613] Updated all javadocs, modified retry callback to
     receive retry number
    
    Modified and updated javadocs, updated method attributes names
    ---
     .../loopj/android/http/AsyncHttpClient.java   | 46 +++++-----
     .../loopj/android/http/AsyncHttpRequest.java  |  5 +-
     .../http/AsyncHttpResponseHandler.java        | 81 ++++++++++-------
     .../http/BaseJsonHttpResponseHandler.java     | 67 +++++++++++----
     .../http/BinaryHttpResponseHandler.java       | 17 ----
     .../http/FileAsyncHttpResponseHandler.java    | 62 ++++++++++---
     .../android/http/JsonHttpResponseHandler.java | 86 +++++++++++++++----
     .../android/http/MySSLSocketFactory.java      |  3 +-
     .../android/http/PersistentCookieStore.java   |  6 +-
     .../http/ResponseHandlerInterface.java        | 11 ++-
     .../android/http/SimpleMultipartEntity.java   |  2 +-
     .../loopj/android/http/SyncHttpClient.java    |  6 ++
     .../android/http/TextHttpResponseHandler.java | 52 ++++++++---
     .../loopj/android/http/sample/FileSample.java |  6 +-
     .../loopj/android/http/sample/JsonSample.java | 15 ++--
     15 files changed, 320 insertions(+), 145 deletions(-)
    
    diff --git a/library/src/main/java/com/loopj/android/http/AsyncHttpClient.java b/library/src/main/java/com/loopj/android/http/AsyncHttpClient.java
    index 210958c0d..e403850b4 100644
    --- a/library/src/main/java/com/loopj/android/http/AsyncHttpClient.java
    +++ b/library/src/main/java/com/loopj/android/http/AsyncHttpClient.java
    @@ -91,16 +91,16 @@
      * 
    */ public class AsyncHttpClient { - private static final String VERSION = "1.4.5"; - private static final int DEFAULT_MAX_CONNECTIONS = 10; - private static final int DEFAULT_SOCKET_TIMEOUT = 10 * 1000; - private static final int DEFAULT_MAX_RETRIES = 5; - private static final int DEFAULT_RETRY_SLEEP_TIME_MILLIS = 1500; - private static final int DEFAULT_SOCKET_BUFFER_SIZE = 8192; - private static final String HEADER_ACCEPT_ENCODING = "Accept-Encoding"; - private static final String ENCODING_GZIP = "gzip"; - private static final String LOG_TAG = "AsyncHttpClient"; + public static final String VERSION = "1.4.5"; + public static final int DEFAULT_MAX_CONNECTIONS = 10; + public static final int DEFAULT_SOCKET_TIMEOUT = 10 * 1000; + public static final int DEFAULT_MAX_RETRIES = 5; + public static final int DEFAULT_RETRY_SLEEP_TIME_MILLIS = 1500; + public static final int DEFAULT_SOCKET_BUFFER_SIZE = 8192; + public static final String HEADER_ACCEPT_ENCODING = "Accept-Encoding"; + public static final String ENCODING_GZIP = "gzip"; + public static final String LOG_TAG = "AsyncHttpClient"; private int maxConnections = DEFAULT_MAX_CONNECTIONS; private int timeout = DEFAULT_SOCKET_TIMEOUT; @@ -488,9 +488,7 @@ public void cancelRequests(Context context, boolean mayInterruptIfRunning) { requestMap.remove(context); } - // - // HTTP HEAD Requests - // + // [+] HTTP HEAD /** * Perform a HTTP HEAD request, without any parameters. @@ -559,10 +557,8 @@ public RequestHandle head(Context context, String url, Header[] headers, Request context); } - - // - // HTTP GET Requests - // + // [-] HTTP HEAD + // [+] HTTP GET /** * Perform a HTTP GET request, without any parameters. @@ -631,10 +627,8 @@ public RequestHandle get(Context context, String url, Header[] headers, RequestP context); } - - // - // HTTP POST Requests - // + // [-] HTTP GET + // [+] HTTP POST /** * Perform a HTTP POST request, without any parameters. @@ -733,9 +727,8 @@ public RequestHandle post(Context context, String url, Header[] headers, HttpEnt return sendRequest(httpClient, httpContext, request, contentType, responseHandler, context); } - // - // HTTP PUT Requests - // + // [-] HTTP POST + // [+] HTTP PUT /** * Perform a HTTP PUT request, without any parameters. @@ -812,9 +805,8 @@ public RequestHandle put(Context context, String url, Header[] headers, HttpEnti return sendRequest(httpClient, httpContext, request, contentType, responseHandler, context); } - // - // HTTP DELETE Requests - // + // [-] HTTP PUT + // [+] HTTP DELETE /** * Perform a HTTP DELETE request. @@ -871,6 +863,8 @@ public RequestHandle delete(Context context, String url, Header[] headers, Reque return sendRequest(httpClient, httpContext, httpDelete, null, responseHandler, context); } + // [-] HTTP DELETE + /** * Puts a new request in queue as a new thread in pool to be executed * diff --git a/library/src/main/java/com/loopj/android/http/AsyncHttpRequest.java b/library/src/main/java/com/loopj/android/http/AsyncHttpRequest.java index a3d7afd2f..55791f18a 100644 --- a/library/src/main/java/com/loopj/android/http/AsyncHttpRequest.java +++ b/library/src/main/java/com/loopj/android/http/AsyncHttpRequest.java @@ -30,6 +30,9 @@ import java.net.MalformedURLException; import java.net.UnknownHostException; +/** + * Internal class, representing the HttpRequest, done in asynchronous manner + */ class AsyncHttpRequest implements Runnable { private final AbstractHttpClient client; private final HttpContext context; @@ -109,7 +112,7 @@ private void makeRequestWithRetries() throws IOException { retry = retryHandler.retryRequest(cause, ++executionCount, context); } if (retry && (responseHandler != null)) { - responseHandler.sendRetryMessage(); + responseHandler.sendRetryMessage(executionCount); } } } catch (Exception e) { diff --git a/library/src/main/java/com/loopj/android/http/AsyncHttpResponseHandler.java b/library/src/main/java/com/loopj/android/http/AsyncHttpResponseHandler.java index 836988471..3d312e26b 100644 --- a/library/src/main/java/com/loopj/android/http/AsyncHttpResponseHandler.java +++ b/library/src/main/java/com/loopj/android/http/AsyncHttpResponseHandler.java @@ -40,7 +40,7 @@ * {@link #onSuccess(int, org.apache.http.Header[], byte[])} method is designed to be anonymously * overridden with your own response handling code.

     

    Additionally, you can override the * {@link #onFailure(int, org.apache.http.Header[], byte[], Throwable)}, {@link #onStart()}, {@link - * #onFinish()}, {@link #onRetry()} and {@link #onProgress(int, int)} methods as required. + * #onFinish()}, {@link #onRetry(int)} and {@link #onProgress(int, int)} methods as required. *

     

    For example:

     

    *
      * AsyncHttpClient client = new AsyncHttpClient();
    @@ -119,8 +119,9 @@ public void setRequestHeaders(Header[] requestHeaders) {
             this.requestHeaders = requestHeaders;
         }
     
    -    // avoid leaks by using a non-anonymous handler class
    -    // with a weak reference
    +    /**
    +     * Avoid leaks by using a non-anonymous handler class with a weak reference
    +     */
         static class ResponderHandler extends Handler {
             private final WeakReference mResponder;
     
    @@ -137,8 +138,9 @@ public void handleMessage(Message msg) {
             }
         }
     
    +    @Override
         public boolean getUseSynchronousMode() {
    -        return (useSynchronousMode);
    +        return useSynchronousMode;
         }
     
         @Override
    @@ -214,25 +216,23 @@ public void onFinish() {
     
         /**
          * Fired when a retry occurs, override to handle in your own code
    +     *
    +     * @param retryNo number of retry
          */
    -    public void onRetry() {
    +    public void onRetry(int retryNo) {
    +        Log.d(LOG_TAG, String.format("Request retry no. %d", retryNo));
         }
     
    -
    -    //
    -    // Pre-processing of messages (executes in background threadpool thread)
    -    //
    -
         final public void sendProgressMessage(int bytesWritten, int bytesTotal) {
             sendMessage(obtainMessage(PROGRESS_MESSAGE, new Object[]{bytesWritten, bytesTotal}));
         }
     
    -    final public void sendSuccessMessage(int statusCode, Header[] headers, byte[] responseBody) {
    -        sendMessage(obtainMessage(SUCCESS_MESSAGE, new Object[]{statusCode, headers, responseBody}));
    +    final public void sendSuccessMessage(int statusCode, Header[] headers, byte[] responseBytes) {
    +        sendMessage(obtainMessage(SUCCESS_MESSAGE, new Object[]{statusCode, headers, responseBytes}));
         }
     
    -    final public void sendFailureMessage(int statusCode, Header[] headers, byte[] responseBody, Throwable error) {
    -        sendMessage(obtainMessage(FAILURE_MESSAGE, new Object[]{statusCode, headers, responseBody, error}));
    +    final public void sendFailureMessage(int statusCode, Header[] headers, byte[] responseBody, Throwable throwable) {
    +        sendMessage(obtainMessage(FAILURE_MESSAGE, new Object[]{statusCode, headers, responseBody, throwable}));
         }
     
         final public void sendStartMessage() {
    @@ -243,17 +243,17 @@ final public void sendFinishMessage() {
             sendMessage(obtainMessage(FINISH_MESSAGE, null));
         }
     
    -    final public void sendRetryMessage() {
    -        sendMessage(obtainMessage(RETRY_MESSAGE, null));
    +    final public void sendRetryMessage(int retryNo) {
    +        sendMessage(obtainMessage(RETRY_MESSAGE, new Object[]{retryNo}));
         }
     
         // Methods which emulate android's Handler and Message methods
    -    protected void handleMessage(Message msg) {
    +    protected void handleMessage(Message message) {
             Object[] response;
     
    -        switch (msg.what) {
    +        switch (message.what) {
                 case SUCCESS_MESSAGE:
    -                response = (Object[]) msg.obj;
    +                response = (Object[]) message.obj;
                     if (response != null && response.length >= 3) {
                         onSuccess((Integer) response[0], (Header[]) response[1], (byte[]) response[2]);
                     } else {
    @@ -261,7 +261,7 @@ protected void handleMessage(Message msg) {
                     }
                     break;
                 case FAILURE_MESSAGE:
    -                response = (Object[]) msg.obj;
    +                response = (Object[]) message.obj;
                     if (response != null && response.length >= 4) {
                         onFailure((Integer) response[0], (Header[]) response[1], (byte[]) response[2], (Throwable) response[3]);
                     } else {
    @@ -275,7 +275,7 @@ protected void handleMessage(Message msg) {
                     onFinish();
                     break;
                 case PROGRESS_MESSAGE:
    -                response = (Object[]) msg.obj;
    +                response = (Object[]) message.obj;
                     if (response != null && response.length >= 2) {
                         try {
                             onProgress((Integer) response[0], (Integer) response[1]);
    @@ -287,7 +287,11 @@ protected void handleMessage(Message msg) {
                     }
                     break;
                 case RETRY_MESSAGE:
    -                onRetry();
    +                response = (Object[]) message.obj;
    +                if (response != null && response.length == 1)
    +                    onRetry((Integer) response[0]);
    +                else
    +                    Log.e(LOG_TAG, "RETRY_MESSAGE didn't get enough params");
                     break;
             }
         }
    @@ -300,21 +304,33 @@ protected void sendMessage(Message msg) {
             }
         }
     
    -    protected void postRunnable(Runnable r) {
    -        if (r != null) {
    -            handler.post(r);
    +    /**
    +     * Helper method to send runnable into local handler loop
    +     *
    +     * @param runnable runnable instance, can be null
    +     */
    +    protected void postRunnable(Runnable runnable) {
    +        if (runnable != null) {
    +            handler.post(runnable);
             }
         }
     
    -    protected Message obtainMessage(int responseMessage, Object response) {
    +    /**
    +     * Helper method to create Message instance from handler
    +     *
    +     * @param responseMessageId   constant to identify Handler message
    +     * @param responseMessageData object to be passed to message receiver
    +     * @return Message instance, should not be null
    +     */
    +    protected Message obtainMessage(int responseMessageId, Object responseMessageData) {
             Message msg;
             if (handler != null) {
    -            msg = handler.obtainMessage(responseMessage, response);
    +            msg = handler.obtainMessage(responseMessageId, responseMessageData);
             } else {
                 msg = Message.obtain();
                 if (msg != null) {
    -                msg.what = responseMessage;
    -                msg.obj = response;
    +                msg.what = responseMessageId;
    +                msg.obj = responseMessageData;
                 }
             }
             return msg;
    @@ -338,6 +354,13 @@ public void sendResponseMessage(HttpResponse response) throws IOException {
             }
         }
     
    +    /**
    +     * Returns byte array of response HttpEntity contents
    +     *
    +     * @param entity can be null
    +     * @return response entity body or null
    +     * @throws java.io.IOException if reading entity or creating byte array failed
    +     */
         byte[] getResponseData(HttpEntity entity) throws IOException {
             byte[] responseBody = null;
             if (entity != null) {
    diff --git a/library/src/main/java/com/loopj/android/http/BaseJsonHttpResponseHandler.java b/library/src/main/java/com/loopj/android/http/BaseJsonHttpResponseHandler.java
    index 1de41440d..03f98345b 100644
    --- a/library/src/main/java/com/loopj/android/http/BaseJsonHttpResponseHandler.java
    +++ b/library/src/main/java/com/loopj/android/http/BaseJsonHttpResponseHandler.java
    @@ -23,37 +23,65 @@
     import org.apache.http.Header;
     import org.apache.http.HttpStatus;
     
    +/**
    + * Class meant to be used with custom JSON parser (such as GSON or Jackson JSON) 

     

    + * {@link #parseResponse(String, boolean)} should be overriden and must return type of generic param + * class, response will be then handled to implementation of abstract methods {@link #onSuccess(int, + * org.apache.http.Header[], String, Object)} or {@link #onFailure(int, org.apache.http.Header[], + * Throwable, String, Object)}, depending of response HTTP status line (result http code) + */ public abstract class BaseJsonHttpResponseHandler extends TextHttpResponseHandler { private static final String LOG_TAG = "BaseJsonHttpResponseHandler"; /** - * Creates a new JsonHttpResponseHandler + * Creates a new JsonHttpResponseHandler with default charset "UTF-8" */ - public BaseJsonHttpResponseHandler() { this(DEFAULT_CHARSET); } + /** + * Creates a new JsonHttpResponseHandler with given string encoding + * + * @param encoding result string encoding, see Charset + */ public BaseJsonHttpResponseHandler(String encoding) { super(encoding); } - public abstract void onSuccess(int statusCode, Header[] headers, String rawResponse, JSON_TYPE response); + /** + * Base abstract method, handling defined generic type + * + * @param statusCode HTTP status line + * @param headers response headers + * @param rawJsonResponse string of response, can be null + * @param response response returned by {@link #parseResponse(String, boolean)} + */ + public abstract void onSuccess(int statusCode, Header[] headers, String rawJsonResponse, JSON_TYPE response); - public abstract void onFailure(int statusCode, Header[] headers, Throwable e, String rawData, JSON_TYPE errorResponse); + /** + * Base abstract method, handling defined generic type + * + * @param statusCode HTTP status line + * @param headers response headers + * @param throwable error thrown while processing request + * @param rawJsonData raw string data returned if any + * @param errorResponse response returned by {@link #parseResponse(String, boolean)} + */ + public abstract void onFailure(int statusCode, Header[] headers, Throwable throwable, String rawJsonData, JSON_TYPE errorResponse); @Override - public final void onSuccess(final int statusCode, final Header[] headers, final String responseBody) { + public final void onSuccess(final int statusCode, final Header[] headers, final String responseString) { if (statusCode != HttpStatus.SC_NO_CONTENT) { new Thread(new Runnable() { @Override public void run() { try { - final JSON_TYPE jsonResponse = parseResponse(responseBody); + final JSON_TYPE jsonResponse = parseResponse(responseString, false); postRunnable(new Runnable() { @Override public void run() { - onSuccess(statusCode, headers, responseBody, jsonResponse); + onSuccess(statusCode, headers, responseString, jsonResponse); } }); } catch (final Throwable t) { @@ -61,7 +89,7 @@ public void run() { postRunnable(new Runnable() { @Override public void run() { - onFailure(statusCode, headers, t, responseBody, null); + onFailure(statusCode, headers, t, responseString, null); } }); } @@ -73,17 +101,17 @@ public void run() { } @Override - public final void onFailure(final int statusCode, final Header[] headers, final String responseBody, final Throwable e) { - if (responseBody != null) { + public final void onFailure(final int statusCode, final Header[] headers, final String responseString, final Throwable throwable) { + if (responseString != null) { new Thread(new Runnable() { @Override public void run() { try { - final JSON_TYPE jsonResponse = parseResponse(responseBody); + final JSON_TYPE jsonResponse = parseResponse(responseString, true); postRunnable(new Runnable() { @Override public void run() { - onFailure(statusCode, headers, e, responseBody, jsonResponse); + onFailure(statusCode, headers, throwable, responseString, jsonResponse); } }); } catch (Throwable t) { @@ -91,16 +119,25 @@ public void run() { postRunnable(new Runnable() { @Override public void run() { - onFailure(statusCode, headers, e, responseBody, null); + onFailure(statusCode, headers, throwable, responseString, null); } }); } } }).start(); } else { - onFailure(statusCode, headers, e, null, null); + onFailure(statusCode, headers, throwable, null, null); } } - protected abstract JSON_TYPE parseResponse(String responseBody) throws Throwable; + /** + * Should return deserialized instance of generic type, may return object for more vague + * handling + * + * @param rawJsonData response string, may be null + * @param isFailure indicating if this method is called from onFailure or not + * @return object of generic type or possibly null if you choose so + * @throws Throwable allows you to throw anything from within deserializing JSON response + */ + protected abstract JSON_TYPE parseResponse(String rawJsonData, boolean isFailure) throws Throwable; } diff --git a/library/src/main/java/com/loopj/android/http/BinaryHttpResponseHandler.java b/library/src/main/java/com/loopj/android/http/BinaryHttpResponseHandler.java index 9f7079136..1959ec6d1 100644 --- a/library/src/main/java/com/loopj/android/http/BinaryHttpResponseHandler.java +++ b/library/src/main/java/com/loopj/android/http/BinaryHttpResponseHandler.java @@ -89,26 +89,9 @@ public BinaryHttpResponseHandler(String[] allowedContentTypes) { Log.e(LOG_TAG, "Constructor passed allowedContentTypes was null !"); } - /** - * Fired when a request returns successfully, override to handle in your own code - * - * @param statusCode response HTTP statuse code - * @param headers response headers, if any - * @param binaryData the response body, if any - */ - @Override public abstract void onSuccess(int statusCode, Header[] headers, byte[] binaryData); - /** - * Fired when a request fails to complete, override to handle in your own code - * - * @param statusCode response HTTP statuse code - * @param headers response headers, if any - * @param binaryData the response body, if any - * @param error the underlying cause of the failure - */ - @Override public abstract void onFailure(int statusCode, Header[] headers, byte[] binaryData, Throwable error); diff --git a/library/src/main/java/com/loopj/android/http/FileAsyncHttpResponseHandler.java b/library/src/main/java/com/loopj/android/http/FileAsyncHttpResponseHandler.java index 846d76e25..6223890ed 100644 --- a/library/src/main/java/com/loopj/android/http/FileAsyncHttpResponseHandler.java +++ b/library/src/main/java/com/loopj/android/http/FileAsyncHttpResponseHandler.java @@ -17,48 +17,90 @@ public abstract class FileAsyncHttpResponseHandler extends AsyncHttpResponseHand private File mFile; private static final String LOG_TAG = "FileAsyncHttpResponseHandler"; + /** + * Obtains new FileAsyncHttpResponseHandler and stores response in passed file + * + * @param file File to store response within, must not be null + */ public FileAsyncHttpResponseHandler(File file) { super(); assert (file != null); this.mFile = file; } - public FileAsyncHttpResponseHandler(Context c) { + /** + * Obtains new FileAsyncHttpResponseHandler against context with target being temporary file + * + * @param context Context, must not be null + */ + public FileAsyncHttpResponseHandler(Context context) { super(); - assert (c != null); - this.mFile = getTemporaryFile(c); + this.mFile = getTemporaryFile(context); } + /** + * Attempts to delete file with stored response + * + * @return false if the file does not exist or is null, true if it was successfully deleted + */ public boolean deleteTargetFile() { - return getTargetFile() == null || getTargetFile().delete(); + return getTargetFile() != null && getTargetFile().delete(); } - protected File getTemporaryFile(Context c) { + /** + * Used when there is no file to be used when calling constructor + * + * @param context Context, must not be null + * @return temporary file or null if creating file failed + */ + protected File getTemporaryFile(Context context) { + assert (context != null); try { - return File.createTempFile("temp_", "_handled", c.getCacheDir()); + return File.createTempFile("temp_", "_handled", context.getCacheDir()); } catch (Throwable t) { Log.e(LOG_TAG, "Cannot create temporary file", t); } return null; } + /** + * Retrieves File object in which the response is stored + * + * @return File file in which the response is stored + */ protected File getTargetFile() { assert (mFile != null); return mFile; } @Override - public final void onFailure(int statusCode, Header[] headers, byte[] responseBody, Throwable error) { - onFailure(statusCode, headers, error, getTargetFile()); + public final void onFailure(int statusCode, Header[] headers, byte[] responseBytes, Throwable throwable) { + onFailure(statusCode, headers, throwable, getTargetFile()); } - public abstract void onFailure(int statusCode, Header[] headers, Throwable e, File response); + /** + * Method to be overriden, receives as much of file as possible Called when the file is + * considered failure or if there is error when retrieving file + * + * @param statusCode http file status line + * @param headers file http headers if any + * @param throwable returned throwable + * @param file file in which the file is stored + */ + public abstract void onFailure(int statusCode, Header[] headers, Throwable throwable, File file); @Override - public final void onSuccess(int statusCode, Header[] headers, byte[] responseBody) { + public final void onSuccess(int statusCode, Header[] headers, byte[] responseBytes) { onSuccess(statusCode, headers, getTargetFile()); } + /** + * Method to be overriden, receives as much of response as possible + * + * @param statusCode http response status line + * @param headers response http headers if any + * @param file file in which the response is stored + */ public abstract void onSuccess(int statusCode, Header[] headers, File file); @Override diff --git a/library/src/main/java/com/loopj/android/http/JsonHttpResponseHandler.java b/library/src/main/java/com/loopj/android/http/JsonHttpResponseHandler.java index 673a0e6c1..0b26fb1b0 100644 --- a/library/src/main/java/com/loopj/android/http/JsonHttpResponseHandler.java +++ b/library/src/main/java/com/loopj/android/http/JsonHttpResponseHandler.java @@ -39,47 +39,89 @@ public class JsonHttpResponseHandler extends TextHttpResponseHandler { private static final String LOG_TAG = "JsonHttpResponseHandler"; /** - * Creates a new JsonHttpResponseHandler + * Creates new JsonHttpResponseHandler, with Json String encoding UTF-8 */ - public JsonHttpResponseHandler() { super(DEFAULT_CHARSET); } + /** + * Creates new JsonHttpRespnseHandler with given Json String encoding + * + * @param encoding String encoding to be used when parsing JSON + */ public JsonHttpResponseHandler(String encoding) { super(encoding); } + /** + * Returns when request succeeds + * + * @param statusCode http response status line + * @param headers response headers if any + * @param response parsed response if any + */ public void onSuccess(int statusCode, Header[] headers, JSONObject response) { } + /** + * Returns when request succeeds + * + * @param statusCode http response status line + * @param headers response headers if any + * @param response parsed response if any + */ public void onSuccess(int statusCode, Header[] headers, JSONArray response) { } - public void onFailure(int statusCode, Header[] headers, Throwable e, JSONObject errorResponse) { + /** + * Returns when request failed + * + * @param statusCode http response status line + * @param headers response headers if any + * @param throwable throwable describing the way request failed + * @param errorResponse parsed response if any + */ + public void onFailure(int statusCode, Header[] headers, Throwable throwable, JSONObject errorResponse) { } - public void onFailure(int statusCode, Header[] headers, Throwable e, JSONArray errorResponse) { + /** + * Returns when request failed + * + * @param statusCode http response status line + * @param headers response headers if any + * @param throwable throwable describing the way request failed + * @param errorResponse parsed response if any + */ + public void onFailure(int statusCode, Header[] headers, Throwable throwable, JSONArray errorResponse) { } @Override - public final void onSuccess(final int statusCode, final Header[] headers, final byte[] responseBody) { + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + + } + + @Override + public final void onSuccess(final int statusCode, final Header[] headers, final byte[] responseBytes) { if (statusCode != HttpStatus.SC_NO_CONTENT) { new Thread(new Runnable() { @Override public void run() { try { - final Object jsonResponse = parseResponse(responseBody); + final Object jsonResponse = parseResponse(responseBytes); postRunnable(new Runnable() { @Override public void run() { - if (jsonResponse == null) { - onSuccess(statusCode, headers, (JSONObject) null); - } else if (jsonResponse instanceof JSONObject) { + if (jsonResponse instanceof JSONObject) { onSuccess(statusCode, headers, (JSONObject) jsonResponse); } else if (jsonResponse instanceof JSONArray) { onSuccess(statusCode, headers, (JSONArray) jsonResponse); @@ -105,24 +147,22 @@ public void run() { } @Override - public final void onFailure(final int statusCode, final Header[] headers, final byte[] responseBody, final Throwable e) { - if (responseBody != null) { + public final void onFailure(final int statusCode, final Header[] headers, final byte[] responseBytes, final Throwable throwable) { + if (responseBytes != null) { new Thread(new Runnable() { @Override public void run() { try { - final Object jsonResponse = parseResponse(responseBody); + final Object jsonResponse = parseResponse(responseBytes); postRunnable(new Runnable() { @Override public void run() { - if (jsonResponse == null) { - onFailure(statusCode, headers, e, (JSONObject) null); - } else if (jsonResponse instanceof JSONObject) { - onFailure(statusCode, headers, e, (JSONObject) jsonResponse); + if (jsonResponse instanceof JSONObject) { + onFailure(statusCode, headers, throwable, (JSONObject) jsonResponse); } else if (jsonResponse instanceof JSONArray) { - onFailure(statusCode, headers, e, (JSONArray) jsonResponse); + onFailure(statusCode, headers, throwable, (JSONArray) jsonResponse); } else if (jsonResponse instanceof String) { - onFailure(statusCode, headers, (String) jsonResponse, e); + onFailure(statusCode, headers, (String) jsonResponse, throwable); } else { onFailure(statusCode, headers, new JSONException("Unexpected response type " + jsonResponse.getClass().getName()), (JSONObject) null); } @@ -142,10 +182,18 @@ public void run() { }).start(); } else { Log.v(LOG_TAG, "response body is null, calling onFailure(Throwable, JSONObject)"); - onFailure(statusCode, headers, e, (JSONObject) null); + onFailure(statusCode, headers, throwable, (JSONObject) null); } } + /** + * Returns Object of type {@link JSONObject}, {@link JSONArray}, String, Boolean, Integer, Long, + * Double or {@link JSONObject#NULL}, see {@link org.json.JSONTokener#nextValue()} + * + * @param responseBody response bytes to be assembled in String and parsed as JSON + * @return Object parsedResponse + * @throws org.json.JSONException exception if thrown while parsing JSON + */ protected Object parseResponse(byte[] responseBody) throws JSONException { if (null == responseBody) return null; diff --git a/library/src/main/java/com/loopj/android/http/MySSLSocketFactory.java b/library/src/main/java/com/loopj/android/http/MySSLSocketFactory.java index eb4aa97e1..03115afc3 100644 --- a/library/src/main/java/com/loopj/android/http/MySSLSocketFactory.java +++ b/library/src/main/java/com/loopj/android/http/MySSLSocketFactory.java @@ -16,7 +16,8 @@ /** * This file is introduced to fix HTTPS Post bug on API < ICS see - * http://code.google.com/p/android/issues/detail?id=13117#c14 + * http://code.google.com/p/android/issues/detail?id=13117#c14

     

    Warning! This omits SSL + * certificate validation on every device, use with caution */ public class MySSLSocketFactory extends SSLSocketFactory { SSLContext sslContext = SSLContext.getInstance("TLS"); diff --git a/library/src/main/java/com/loopj/android/http/PersistentCookieStore.java b/library/src/main/java/com/loopj/android/http/PersistentCookieStore.java index 963169913..378c29a7c 100644 --- a/library/src/main/java/com/loopj/android/http/PersistentCookieStore.java +++ b/library/src/main/java/com/loopj/android/http/PersistentCookieStore.java @@ -174,8 +174,10 @@ protected Cookie decodeCookie(String cookieStr) { return cookie; } - // Using some super basic byte array <-> hex conversions so we don't have - // to rely on any large Base64 libraries. Can be overridden if you like! + /** + * Using some super basic byte array <-> hex conversions so we don't have to rely on any large + * Base64 libraries. Can be overridden if you like! + */ protected String byteArrayToHexString(byte[] b) { StringBuilder sb = new StringBuilder(b.length * 2); for (byte element : b) { diff --git a/library/src/main/java/com/loopj/android/http/ResponseHandlerInterface.java b/library/src/main/java/com/loopj/android/http/ResponseHandlerInterface.java index 06b2145b6..d563a20cb 100644 --- a/library/src/main/java/com/loopj/android/http/ResponseHandlerInterface.java +++ b/library/src/main/java/com/loopj/android/http/ResponseHandlerInterface.java @@ -58,8 +58,10 @@ public interface ResponseHandlerInterface { /** * Notifies callback of retrying request + * + * @param retryNo number of retry within one request */ - void sendRetryMessage(); + void sendRetryMessage(int retryNo); /** * Returns URI which was used to request @@ -95,4 +97,11 @@ public interface ResponseHandlerInterface { * @param useSynchronousMode whether data should be handled on background Thread on UI Thread */ void setUseSynchronousMode(boolean useSynchronousMode); + + /** + * Can set, whether the handler should be asynchronous or synchronous + * + * @return boolean if the ResponseHandler is running in synchronous mode + */ + boolean getUseSynchronousMode(); } diff --git a/library/src/main/java/com/loopj/android/http/SimpleMultipartEntity.java b/library/src/main/java/com/loopj/android/http/SimpleMultipartEntity.java index d41351394..ef2d42ecf 100644 --- a/library/src/main/java/com/loopj/android/http/SimpleMultipartEntity.java +++ b/library/src/main/java/com/loopj/android/http/SimpleMultipartEntity.java @@ -93,7 +93,7 @@ public void addPart(final String key, final String value, final String contentTy out.write(value.getBytes()); out.write(CR_LF); } catch (final IOException e) { - // Can't happen on ByteArrayOutputStream + // Shall not happen on ByteArrayOutputStream Log.e(LOG_TAG, "addPart ByteArrayOutputStream exception", e); } } diff --git a/library/src/main/java/com/loopj/android/http/SyncHttpClient.java b/library/src/main/java/com/loopj/android/http/SyncHttpClient.java index 22466eddf..ac5ca66d4 100644 --- a/library/src/main/java/com/loopj/android/http/SyncHttpClient.java +++ b/library/src/main/java/com/loopj/android/http/SyncHttpClient.java @@ -7,6 +7,12 @@ import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.protocol.HttpContext; +/** + * Processes http requests in synchronous mode, so your caller thread will be blocked on each + * request + * + * @see com.loopj.android.http.AsyncHttpClient + */ public class SyncHttpClient extends AsyncHttpClient { /** diff --git a/library/src/main/java/com/loopj/android/http/TextHttpResponseHandler.java b/library/src/main/java/com/loopj/android/http/TextHttpResponseHandler.java index 060fed761..b96a263aa 100644 --- a/library/src/main/java/com/loopj/android/http/TextHttpResponseHandler.java +++ b/library/src/main/java/com/loopj/android/http/TextHttpResponseHandler.java @@ -37,39 +37,65 @@ * }); *
    */ -public class TextHttpResponseHandler extends AsyncHttpResponseHandler { +public abstract class TextHttpResponseHandler extends AsyncHttpResponseHandler { private static final String LOG_TAG = "TextHttpResponseHandler"; + /** + * Creates new instance with default UTF-8 encoding + */ public TextHttpResponseHandler() { this(DEFAULT_CHARSET); } + /** + * Creates new instance with given string encoding + * + * @param encoding String encoding, see {@link #setCharset(String)} + */ public TextHttpResponseHandler(String encoding) { super(); setCharset(encoding); } - public void onFailure(int statusCode, Header[] headers, String responseBody, Throwable error) { + /** + * Called when request fails + * + * @param statusCode http response status line + * @param headers response headers if any + * @param responseString string response of given charset + * @param throwable throwable returned when processing request + */ + public abstract void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable); - } - - public void onSuccess(int statusCode, Header[] headers, String responseBody) { - - } + /** + * Called when request succeeds + * + * @param statusCode http response status line + * @param headers response headers if any + * @param responseString string response of given charset + */ + public abstract void onSuccess(int statusCode, Header[] headers, String responseString); @Override - public void onSuccess(int statusCode, Header[] headers, byte[] responseBody) { - onSuccess(statusCode, headers, getResponseString(responseBody, getCharset())); + public void onSuccess(int statusCode, Header[] headers, byte[] responseBytes) { + onSuccess(statusCode, headers, getResponseString(responseBytes, getCharset())); } @Override - public void onFailure(int statusCode, Header[] headers, byte[] responseBody, Throwable error) { - onFailure(statusCode, headers, getResponseString(responseBody, getCharset()), error); + public void onFailure(int statusCode, Header[] headers, byte[] responseBytes, Throwable throwable) { + onFailure(statusCode, headers, getResponseString(responseBytes, getCharset()), throwable); } - public static String getResponseString(byte[] stringData, String charset) { + /** + * Attempts to encode response bytes as string of set encoding + * + * @param charset charset to create string with + * @param stringBytes response bytes + * @return String of set encoding or null + */ + public static String getResponseString(byte[] stringBytes, String charset) { try { - return stringData == null ? null : new String(stringData, charset); + return stringBytes == null ? null : new String(stringBytes, charset); } catch (UnsupportedEncodingException e) { Log.e(LOG_TAG, "Encoding response into string failed", e); return null; diff --git a/sample/src/main/java/com/loopj/android/http/sample/FileSample.java b/sample/src/main/java/com/loopj/android/http/sample/FileSample.java index eb3447226..68c3620b3 100644 --- a/sample/src/main/java/com/loopj/android/http/sample/FileSample.java +++ b/sample/src/main/java/com/loopj/android/http/sample/FileSample.java @@ -51,11 +51,11 @@ public void onSuccess(int statusCode, Header[] headers, File response) { } @Override - public void onFailure(int statusCode, Header[] headers, Throwable e, File response) { + public void onFailure(int statusCode, Header[] headers, Throwable throwable, File file) { debugHeaders(LOG_TAG, headers); debugStatusCode(LOG_TAG, statusCode); - debugThrowable(LOG_TAG, e); - debugFile(response); + debugThrowable(LOG_TAG, throwable); + debugFile(file); } private void debugFile(File file) { diff --git a/sample/src/main/java/com/loopj/android/http/sample/JsonSample.java b/sample/src/main/java/com/loopj/android/http/sample/JsonSample.java index 154804f85..ae701c91a 100644 --- a/sample/src/main/java/com/loopj/android/http/sample/JsonSample.java +++ b/sample/src/main/java/com/loopj/android/http/sample/JsonSample.java @@ -49,28 +49,29 @@ public void onStart() { } @Override - public void onSuccess(int statusCode, Header[] headers, String rawResponse, SampleJSON response) { + public void onSuccess(int statusCode, Header[] headers, String rawJsonResponse, SampleJSON response) { debugHeaders(LOG_TAG, headers); debugStatusCode(LOG_TAG, statusCode); if (response != null) { - debugResponse(LOG_TAG, rawResponse); + debugResponse(LOG_TAG, rawJsonResponse); } } @Override - public void onFailure(int statusCode, Header[] headers, Throwable e, String rawResponse, SampleJSON errorResponse) { + public void onFailure(int statusCode, Header[] headers, Throwable throwable, String rawJsonData, SampleJSON errorResponse) { debugHeaders(LOG_TAG, headers); debugStatusCode(LOG_TAG, statusCode); - debugThrowable(LOG_TAG, e); + debugThrowable(LOG_TAG, throwable); if (errorResponse != null) { - debugResponse(LOG_TAG, rawResponse); + debugResponse(LOG_TAG, rawJsonData); } } @Override - protected SampleJSON parseResponse(String responseBody) throws Throwable { - return new ObjectMapper().readValues(new JsonFactory().createParser(responseBody), SampleJSON.class).next(); + protected SampleJSON parseResponse(String rawJsonData, boolean isFailure) throws Throwable { + return new ObjectMapper().readValues(new JsonFactory().createParser(rawJsonData), SampleJSON.class).next(); } + }; } } From ea227cb254c4cfbc5b29d66c61e3607fd4391526 Mon Sep 17 00:00:00 2001 From: mareksebera Date: Tue, 5 Nov 2013 21:04:18 +0100 Subject: [PATCH 200/613] Updated threadpool service creating --- .../main/java/com/loopj/android/http/AsyncHttpClient.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/library/src/main/java/com/loopj/android/http/AsyncHttpClient.java b/library/src/main/java/com/loopj/android/http/AsyncHttpClient.java index e403850b4..ee52cac80 100644 --- a/library/src/main/java/com/loopj/android/http/AsyncHttpClient.java +++ b/library/src/main/java/com/loopj/android/http/AsyncHttpClient.java @@ -69,6 +69,7 @@ import java.util.List; import java.util.Map; import java.util.WeakHashMap; +import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.ThreadPoolExecutor; @@ -107,7 +108,7 @@ public class AsyncHttpClient { private final DefaultHttpClient httpClient; private final HttpContext httpContext; - private ThreadPoolExecutor threadPool; + private ExecutorService threadPool; private final Map>>> requestMap; private final Map clientHeaderMap; private boolean isUrlEncodingEnabled = true; @@ -209,7 +210,7 @@ public AsyncHttpClient(SchemeRegistry schemeRegistry) { ThreadSafeClientConnManager cm = new ThreadSafeClientConnManager(httpParams, schemeRegistry); - threadPool = (ThreadPoolExecutor) Executors.newFixedThreadPool(DEFAULT_MAX_CONNECTIONS); + threadPool = Executors.newFixedThreadPool(DEFAULT_MAX_CONNECTIONS); requestMap = new WeakHashMap>>>(); clientHeaderMap = new HashMap(); @@ -337,7 +338,6 @@ public void setMaxConnections(int maxConnections) { this.maxConnections = maxConnections; final HttpParams httpParams = this.httpClient.getParams(); ConnManagerParams.setMaxConnectionsPerRoute(httpParams, new ConnPerRouteBean(this.maxConnections)); - this.threadPool.setCorePoolSize(maxConnections); } /** From 30607d4c8f0ce6c98b45a380fc6e71cdb793374e Mon Sep 17 00:00:00 2001 From: mareksebera Date: Tue, 5 Nov 2013 21:04:31 +0100 Subject: [PATCH 201/613] Completed javadoc for PersistentCookieStore --- .../android/http/PersistentCookieStore.java | 60 ++++++++++++------- 1 file changed, 40 insertions(+), 20 deletions(-) diff --git a/library/src/main/java/com/loopj/android/http/PersistentCookieStore.java b/library/src/main/java/com/loopj/android/http/PersistentCookieStore.java index 378c29a7c..d7766910e 100644 --- a/library/src/main/java/com/loopj/android/http/PersistentCookieStore.java +++ b/library/src/main/java/com/loopj/android/http/PersistentCookieStore.java @@ -21,6 +21,7 @@ import android.content.Context; import android.content.SharedPreferences; import android.text.TextUtils; +import android.util.Log; import org.apache.http.client.CookieStore; import org.apache.http.cookie.Cookie; @@ -42,6 +43,7 @@ * regular old apache HttpClient/HttpContext if you prefer. */ public class PersistentCookieStore implements CookieStore { + private static final String LOG_TAG = "PersistentCookieStore"; private static final String COOKIE_PREFS = "CookiePrefsFile"; private static final String COOKIE_NAME_STORE = "names"; private static final String COOKIE_NAME_PREFIX = "cookie_"; @@ -143,12 +145,15 @@ public List getCookies() { return new ArrayList(cookies.values()); } - - // - // Cookie serialization/deserialization - // - + /** + * Serializes Cookie object into String + * + * @param cookie cookie to be encoded, can be null + * @return cookie encoded as String + */ protected String encodeCookie(SerializableCookie cookie) { + if (cookie == null) + return null; ByteArrayOutputStream os = new ByteArrayOutputStream(); try { ObjectOutputStream outputStream = new ObjectOutputStream(os); @@ -160,27 +165,36 @@ protected String encodeCookie(SerializableCookie cookie) { return byteArrayToHexString(os.toByteArray()); } - protected Cookie decodeCookie(String cookieStr) { - byte[] bytes = hexStringToByteArray(cookieStr); - ByteArrayInputStream is = new ByteArrayInputStream(bytes); + /** + * Returns cookie decoded from cookie string + * + * @param cookieString string of cookie as returned from http request + * @return decoded cookie or null if exception occured + */ + protected Cookie decodeCookie(String cookieString) { + byte[] bytes = hexStringToByteArray(cookieString); + ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes); Cookie cookie = null; try { - ObjectInputStream ois = new ObjectInputStream(is); - cookie = ((SerializableCookie) ois.readObject()).getCookie(); - } catch (Exception e) { - e.printStackTrace(); + ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream); + cookie = ((SerializableCookie) objectInputStream.readObject()).getCookie(); + } catch (Exception exception) { + Log.d(LOG_TAG, "decodeCookie failed", exception); } return cookie; } /** - * Using some super basic byte array <-> hex conversions so we don't have to rely on any large - * Base64 libraries. Can be overridden if you like! + * Using some super basic byte array <-> hex conversions so we don't have to rely on any + * large Base64 libraries. Can be overridden if you like! + * + * @param bytes byte array to be converted + * @return string containing hex values */ - protected String byteArrayToHexString(byte[] b) { - StringBuilder sb = new StringBuilder(b.length * 2); - for (byte element : b) { + protected String byteArrayToHexString(byte[] bytes) { + StringBuilder sb = new StringBuilder(bytes.length * 2); + for (byte element : bytes) { int v = element & 0xff; if (v < 16) { sb.append('0'); @@ -190,11 +204,17 @@ protected String byteArrayToHexString(byte[] b) { return sb.toString().toUpperCase(); } - protected byte[] hexStringToByteArray(String s) { - int len = s.length(); + /** + * Converts hex values from strings to byte arra + * + * @param hexString string of hex-encoded values + * @return decoded byte array + */ + protected byte[] hexStringToByteArray(String hexString) { + int len = hexString.length(); byte[] data = new byte[len / 2]; for (int i = 0; i < len; i += 2) { - data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + Character.digit(s.charAt(i + 1), 16)); + data[i / 2] = (byte) ((Character.digit(hexString.charAt(i), 16) << 4) + Character.digit(hexString.charAt(i + 1), 16)); } return data; } From ebdab84b1e7043940bb63bc81de0758f4a1edb9c Mon Sep 17 00:00:00 2001 From: Oriental Sensation Date: Thu, 14 Nov 2013 21:12:59 +0100 Subject: [PATCH 202/613] Classes are declared private even though it's exposed as non-private. --- .../src/main/java/com/loopj/android/http/RequestParams.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/src/main/java/com/loopj/android/http/RequestParams.java b/library/src/main/java/com/loopj/android/http/RequestParams.java index c7eedce6d..1f9c7e5ae 100644 --- a/library/src/main/java/com/loopj/android/http/RequestParams.java +++ b/library/src/main/java/com/loopj/android/http/RequestParams.java @@ -427,7 +427,7 @@ protected String getParamString() { return URLEncodedUtils.format(getParamsList(), HTTP.UTF_8); } - private static class FileWrapper { + public static class FileWrapper { public File file; public String contentType; @@ -437,7 +437,7 @@ public FileWrapper(File file, String contentType) { } } - private static class StreamWrapper { + public static class StreamWrapper { public InputStream inputStream; public String name; public String contentType; From 9b6e50955851bc909c99a663f5b653752cafa5a3 Mon Sep 17 00:00:00 2001 From: Oriental Sensation Date: Thu, 14 Nov 2013 22:01:00 +0100 Subject: [PATCH 203/613] Let the user add further exception classes to RetryHandler's whitelist/blacklist. --- .../java/com/loopj/android/http/AsyncHttpClient.java | 12 ++++++++++++ .../java/com/loopj/android/http/RetryHandler.java | 8 ++++++++ 2 files changed, 20 insertions(+) diff --git a/library/src/main/java/com/loopj/android/http/AsyncHttpClient.java b/library/src/main/java/com/loopj/android/http/AsyncHttpClient.java index ee52cac80..afc3a6dbe 100644 --- a/library/src/main/java/com/loopj/android/http/AsyncHttpClient.java +++ b/library/src/main/java/com/loopj/android/http/AsyncHttpClient.java @@ -187,6 +187,18 @@ private static SchemeRegistry getDefaultSchemeRegistry(boolean fixNoHttpResponse return schemeRegistry; } + public static void allowRetryExceptionClass(Class cls) { + if (cls != null) { + RetryHandler.addToWhitelist(cls); + } + } + + public static void blockRetryExceptionClass(Class cls) { + if (cls != null) { + RetryHandler.addToBlacklist(cls); + } + } + /** * Creates a new AsyncHttpClient. * diff --git a/library/src/main/java/com/loopj/android/http/RetryHandler.java b/library/src/main/java/com/loopj/android/http/RetryHandler.java index e8ea7ccac..38f725cd0 100644 --- a/library/src/main/java/com/loopj/android/http/RetryHandler.java +++ b/library/src/main/java/com/loopj/android/http/RetryHandler.java @@ -105,6 +105,14 @@ public boolean retryRequest(IOException exception, int executionCount, HttpConte return retry; } + static void addClassToWhitelist(Class cls) { + exceptionWhitelist.add(cls); + } + + static void addClassToBlacklist(Class cls) { + exceptionBlacklist.add(cls); + } + protected boolean isInList(HashSet> list, Throwable error) { for (Class aList : list) { if (aList.isInstance(error)) { From 5e912dd16b01179538e9125dff727c71ead214ec Mon Sep 17 00:00:00 2001 From: Oriental Sensation Date: Thu, 14 Nov 2013 22:17:51 +0100 Subject: [PATCH 204/613] Let the user add further exception classes to RetryHandler's whitelist/blacklist. --- .../loopj/android/http/AsyncHttpClient.java | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/library/src/main/java/com/loopj/android/http/AsyncHttpClient.java b/library/src/main/java/com/loopj/android/http/AsyncHttpClient.java index afc3a6dbe..6263127dd 100644 --- a/library/src/main/java/com/loopj/android/http/AsyncHttpClient.java +++ b/library/src/main/java/com/loopj/android/http/AsyncHttpClient.java @@ -187,18 +187,6 @@ private static SchemeRegistry getDefaultSchemeRegistry(boolean fixNoHttpResponse return schemeRegistry; } - public static void allowRetryExceptionClass(Class cls) { - if (cls != null) { - RetryHandler.addToWhitelist(cls); - } - } - - public static void blockRetryExceptionClass(Class cls) { - if (cls != null) { - RetryHandler.addToBlacklist(cls); - } - } - /** * Creates a new AsyncHttpClient. * @@ -262,6 +250,18 @@ public void process(HttpResponse response, HttpContext context) { httpClient.setHttpRequestRetryHandler(new RetryHandler(DEFAULT_MAX_RETRIES, DEFAULT_RETRY_SLEEP_TIME_MILLIS)); } + public static void allowRetryExceptionClass(Class cls) { + if (cls != null) { + RetryHandler.addClassToWhitelist(cls); + } + } + + public static void blockRetryExceptionClass(Class cls) { + if (cls != null) { + RetryHandler.addClassToBlacklist(cls); + } + } + /** * Get the underlying HttpClient instance. This is useful for setting additional fine-grained * settings for requests by accessing the client's ConnectionManager, HttpParams and From 831e5f4a5ae12358abee35b8ef3031fe9da0d1da Mon Sep 17 00:00:00 2001 From: Oriental Sensation Date: Fri, 15 Nov 2013 22:18:04 +0100 Subject: [PATCH 205/613] Added ability to upload data as JSON object using streams. --- .../android/http/JsonStreamerEntity.java | 311 ++++++++++++++++++ .../com/loopj/android/http/RequestParams.java | 48 ++- 2 files changed, 355 insertions(+), 4 deletions(-) create mode 100644 library/src/main/java/com/loopj/android/http/JsonStreamerEntity.java diff --git a/library/src/main/java/com/loopj/android/http/JsonStreamerEntity.java b/library/src/main/java/com/loopj/android/http/JsonStreamerEntity.java new file mode 100644 index 000000000..6b66ffccb --- /dev/null +++ b/library/src/main/java/com/loopj/android/http/JsonStreamerEntity.java @@ -0,0 +1,311 @@ +/* + Android Asynchronous Http Client + Copyright (c) 2011 James Smith + http://loopj.com + + 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. +*/ + +/* + This code is taken from Rafael Sanches' blog. + http://blog.rafaelsanches.com/2011/01/29/upload-using-multipart-post-using-httpclient-in-android/ +*/ + +package com.loopj.android.http; + +import org.apache.http.Header; +import org.apache.http.HttpEntity; +import org.apache.http.message.BasicHeader; + +import android.util.Log; +import android.util.Base64; +import android.util.Base64OutputStream; + +import java.io.File; +import java.io.FileInputStream; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.BufferedOutputStream; +import java.io.IOException; + +import java.util.Set; +import java.util.Map; +import java.util.HashMap; +import java.util.zip.GZIPOutputStream; + +/** + * HTTP entity to upload JSON data using streams. + * This has very low memory footprint; suitable for uploading large + * files using base64 encoding. + */ +class JsonStreamerEntity implements HttpEntity { + + private static final String LOG_TAG = "JsonStreamerEntity"; + + private static final UnsupportedOperationException ERR_UNSUPPORTED = + new UnsupportedOperationException("Unsupported operation in this implementation."); + + private static final byte[] JSON_TRUE = "true".getBytes(); + private static final byte[] JSON_FALSE = "false".getBytes(); + private static final byte[] STREAM_NAME = escape("name", true).getBytes(); + private static final byte[] STREAM_TYPE = escape("type", true).getBytes(); + private static final byte[] STREAM_CONTENTS = escape("contents", true).getBytes(); + private static final byte[] STREAM_ELAPSED = escape("_elapsed", true).getBytes(); + + private static final Header HEADER_JSON = + new BasicHeader("Content-Type", "application/json"); + private static final String APPLICATION_OCTET_STREAM = + "application/octet-stream"; + + // Size of the byte-array buffer used to read from files. + private static int BUFFER_SIZE = 2048; + + // K/V objects to be uploaded. + private final Map kvParams = new HashMap(); + + // Streams and their associated meta-data to be uploaded. + private final Map streamParams = new HashMap(); + + // Whether to use gzip compression while uploading + private final Header contentEncoding; + + public JsonStreamerEntity(boolean contentEncoding) { + this.contentEncoding = contentEncoding + ? new BasicHeader("Content-Encoding", "gzip") + : null; + } + + public void addPart(String key, Object value) { + kvParams.put(key, value); + } + + public void addPart(String key, File file, String type) throws IOException { + addPart(key, new FileInputStream(file), file.getName(), type); + } + + public void addPart(String key, InputStream inputStream, String name, String type) { + if (type == null) { + type = APPLICATION_OCTET_STREAM; + } + streamParams.put(key, new RequestParams.StreamWrapper(inputStream, name, type)); + } + + @Override + public boolean isRepeatable() { + return false; + } + + @Override + public boolean isChunked() { + return false; + } + + @Override + public boolean isStreaming() { + return false; + } + + @Override + public long getContentLength() { + return -1; + } + + @Override + public Header getContentEncoding() { + return contentEncoding; + } + + @Override + public Header getContentType() { + return HEADER_JSON; + } + + @Override + public void consumeContent() throws IOException, UnsupportedOperationException { + } + + @Override + public InputStream getContent() throws IOException, UnsupportedOperationException { + throw ERR_UNSUPPORTED; + } + + @Override + public void writeTo(final OutputStream outstream) throws IOException { + if (outstream == null) { + throw new IllegalStateException("Output stream cannot be null."); + } + + long now = System.currentTimeMillis(); + Log.i(LOG_TAG, "Started dumping at: " + now); + + OutputStream upload; + + // GZIPOutputStream is available only from API level 8 and onward. + if(null != contentEncoding) { + upload = new GZIPOutputStream(new BufferedOutputStream(outstream), BUFFER_SIZE); + } else { + upload = new BufferedOutputStream(outstream); + } + + // Always send a JSON object. + upload.write('{'); + + // Keys used by the HashMaps. + Set keys; + + // Send the K/V values. + keys = kvParams.keySet(); + for (String key : keys) { + // Write the JSON object's key. + upload.write(escape(key, true).getBytes()); + upload.write(':'); + + // Evaluate the value (which cannot be null). + Object value = kvParams.get(key); + + if (value instanceof Boolean) { + upload.write(((Boolean)value).booleanValue() ? JSON_TRUE : JSON_FALSE); + } else if (value instanceof Long) { + upload.write((((Number)value).longValue() + "").getBytes()); + } else if (value instanceof Double) { + upload.write((((Number)value).doubleValue() + "").getBytes()); + } else if (value instanceof Float) { + upload.write((((Number)value).floatValue() + "").getBytes()); + } else if (value instanceof Integer) { + upload.write((((Number)value).intValue() + "").getBytes()); + } else { + upload.write(value.toString().getBytes()); + } + + upload.write(','); + } + + // Buffer used for reading from input streams. + byte[] buffer = new byte[BUFFER_SIZE]; + + // Send the stream params. + keys = streamParams.keySet(); + for(String key : keys) { + RequestParams.StreamWrapper entry = streamParams.get(key); + + // Write the JSON object's key. + upload.write(escape(key, true).getBytes()); + + // All uploads are sent as an object containing the file's details. + upload.write(":{".getBytes()); + + // Send the streams's name. + upload.write(STREAM_NAME); + upload.write(':'); + upload.write(escape(entry.name, true).getBytes()); + + // Send the streams's content type. + upload.write(STREAM_TYPE); + upload.write(':'); + upload.write(escape(entry.contentType, true).getBytes()); + + // Prepare the file content's key. + upload.write(STREAM_CONTENTS); + upload.write(':'); + upload.write('"'); + + // Write the file's contents in Base64. + Base64OutputStream outputStream = new Base64OutputStream(upload, Base64.NO_CLOSE | Base64.NO_WRAP); + int bytesRead; + while(-1 != (bytesRead = entry.inputStream.read(buffer))) { + outputStream.write(buffer, 0, bytesRead); + } + + // Close the output stream. + outputStream.close(); + + // Close the file's object. + upload.write('"'); + upload.write('}'); + upload.write(','); + } + + // GC. + keys = null; + buffer = null; + + // Include the elapsed time taken to upload everything. + upload.write(STREAM_ELAPSED); + upload.write(':'); + long elapsedTime = System.currentTimeMillis() - now; + upload.write((elapsedTime + "}").getBytes()); + + Log.i(LOG_TAG, "JSON was uploaded in " + Math.floor(elapsedTime / 1000) + " seconds"); + + // Flush the contents up the stream. + upload.flush(); + upload.close(); + } + + // Curtosy of Simple-JSON: + // http://goo.gl/XoW8RF + private static String escape(String string, boolean quotes) { + StringBuilder sb = new StringBuilder(); + int length = string.length(), pos = -1; + if (quotes) { + sb.append('"'); + } + while (++pos < length) { + char ch = string.charAt(pos); + switch (ch) { + case '"': + sb.append("\\\""); + break; + case '\\': + sb.append("\\\\"); + break; + case '\b': + sb.append("\\b"); + break; + case '\f': + sb.append("\\f"); + break; + case '\n': + sb.append("\\n"); + break; + case '\r': + sb.append("\\r"); + break; + case '\t': + sb.append("\\t"); + break; + case '/': + sb.append("\\/"); + break; + default: + // Reference: http://www.unicode.org/versions/Unicode5.1.0/ + if((ch >= '\u0000' && ch <= '\u001F') || (ch >= '\u007F' && ch <= '\u009F') || (ch >= '\u2000' && ch <= '\u20FF')) { + String intString = Integer.toHexString(ch); + sb.append("\\u"); + int intLength = 4 - intString.length(); + for (int zero = 0; zero < intLength; zero++) { + sb.append('0'); + } + sb.append(intString.toUpperCase()); + } else { + sb.append(ch); + } + break; + } + } + if (quotes) { + sb.append('"'); + } + return sb.toString(); + } +} diff --git a/library/src/main/java/com/loopj/android/http/RequestParams.java b/library/src/main/java/com/loopj/android/http/RequestParams.java index c7eedce6d..0d7e92f7b 100644 --- a/library/src/main/java/com/loopj/android/http/RequestParams.java +++ b/library/src/main/java/com/loopj/android/http/RequestParams.java @@ -86,7 +86,8 @@ */ public class RequestParams { - protected boolean isRepeatable = false; + protected boolean isRepeatable; + protected boolean useJsonStreamer; protected ConcurrentHashMap urlParams; protected ConcurrentHashMap streamParams; protected ConcurrentHashMap fileParams; @@ -312,6 +313,13 @@ public void setHttpEntityIsRepeatable(boolean isRepeatable) { this.isRepeatable = isRepeatable; } + public void setUseJsonStreamer(boolean useJsonStreamer) { + if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.FROYO) { + throw new IllegalStateException("Use of JSON streamer is available for API level 8 and later."); + } + this.useJsonStreamer = useJsonStreamer; + } + /** * Returns an HttpEntity containing all request parameters * @@ -321,13 +329,45 @@ public void setHttpEntityIsRepeatable(boolean isRepeatable) { * @throws IOException if one of the streams cannot be read */ public HttpEntity getEntity(ResponseHandlerInterface progressHandler) throws IOException { - if (streamParams.isEmpty() && fileParams.isEmpty()) { + if (useJsonStreamer) { + return createJsonStreamerEntity(); + } else if (streamParams.isEmpty() && fileParams.isEmpty()) { return createFormEntity(); } else { return createMultipartEntity(progressHandler); } } + private HttpEntity createJsonStreamerEntity() throws IOException { + JsonStreamerEntity entity = new JsonStreamerEntity(!fileParams.isEmpty() || !streamParams.isEmpty()); + + // Add string params + for (ConcurrentHashMap.Entry entry : urlParams.entrySet()) { + entity.addPart(entry.getKey(), entry.getValue()); + } + + // Add non-string params + for (ConcurrentHashMap.Entry entry : urlParamsWithObjects.entrySet()) { + entity.addPart(entry.getKey(), entry.getValue()); + } + + // Add file params + for (ConcurrentHashMap.Entry entry : fileParams.entrySet()) { + FileWrapper fileWrapper = entry.getValue(); + entity.addPart(entry.getKey(), fileWrapper.file, fileWrapper.contentType); + } + + // Add stream params + for (ConcurrentHashMap.Entry entry : streamParams.entrySet()) { + StreamWrapper stream = entry.getValue(); + if (stream.inputStream != null) { + entity.addPart(entry.getKey(), stream.inputStream, stream.name, stream.contentType); + } + } + + return entity; + } + private HttpEntity createFormEntity() { try { return new UrlEncodedFormEntity(getParamsList(), HTTP.UTF_8); @@ -427,7 +467,7 @@ protected String getParamString() { return URLEncodedUtils.format(getParamsList(), HTTP.UTF_8); } - private static class FileWrapper { + public static class FileWrapper { public File file; public String contentType; @@ -437,7 +477,7 @@ public FileWrapper(File file, String contentType) { } } - private static class StreamWrapper { + public static class StreamWrapper { public InputStream inputStream; public String name; public String contentType; From 8393ea76ad48bc4f95edb5f093d0e328da6618b2 Mon Sep 17 00:00:00 2001 From: Oriental Sensation Date: Fri, 15 Nov 2013 22:32:24 +0100 Subject: [PATCH 206/613] Removed erroneous comment. --- .../main/java/com/loopj/android/http/JsonStreamerEntity.java | 5 ----- 1 file changed, 5 deletions(-) diff --git a/library/src/main/java/com/loopj/android/http/JsonStreamerEntity.java b/library/src/main/java/com/loopj/android/http/JsonStreamerEntity.java index 6b66ffccb..0540cd285 100644 --- a/library/src/main/java/com/loopj/android/http/JsonStreamerEntity.java +++ b/library/src/main/java/com/loopj/android/http/JsonStreamerEntity.java @@ -16,11 +16,6 @@ limitations under the License. */ -/* - This code is taken from Rafael Sanches' blog. - http://blog.rafaelsanches.com/2011/01/29/upload-using-multipart-post-using-httpclient-in-android/ -*/ - package com.loopj.android.http; import org.apache.http.Header; From cd5f0e8545c290f091e56f3f9127b7da975d4b91 Mon Sep 17 00:00:00 2001 From: mareksebera Date: Sat, 16 Nov 2013 11:56:04 +0100 Subject: [PATCH 207/613] Backported Base64 and referenced classes for JsonStreamerEntity to work on API lower than 8 --- .../java/com/loopj/android/http/Base64.java | 741 ++++++++++++++++++ .../android/http/Base64DataException.java | 9 + .../android/http/Base64OutputStream.java | 130 +++ .../android/http/JsonStreamerEntity.java | 29 +- 4 files changed, 892 insertions(+), 17 deletions(-) create mode 100644 library/src/main/java/com/loopj/android/http/Base64.java create mode 100644 library/src/main/java/com/loopj/android/http/Base64DataException.java create mode 100644 library/src/main/java/com/loopj/android/http/Base64OutputStream.java diff --git a/library/src/main/java/com/loopj/android/http/Base64.java b/library/src/main/java/com/loopj/android/http/Base64.java new file mode 100644 index 000000000..e9bc18092 --- /dev/null +++ b/library/src/main/java/com/loopj/android/http/Base64.java @@ -0,0 +1,741 @@ +/* + * Copyright (C) 2010 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. + */ + +package com.loopj.android.http; + +import java.io.UnsupportedEncodingException; + +/** + * Utilities for encoding and decoding the Base64 representation of + * binary data. See RFCs 2045 and 3548. + */ +public class Base64 { + /** + * Default values for encoder/decoder flags. + */ + public static final int DEFAULT = 0; + + /** + * Encoder flag bit to omit the padding '=' characters at the end + * of the output (if any). + */ + public static final int NO_PADDING = 1; + + /** + * Encoder flag bit to omit all line terminators (i.e., the output + * will be on one long line). + */ + public static final int NO_WRAP = 2; + + /** + * Encoder flag bit to indicate lines should be terminated with a + * CRLF pair instead of just an LF. Has no effect if {@code + * NO_WRAP} is specified as well. + */ + public static final int CRLF = 4; + + /** + * Encoder/decoder flag bit to indicate using the "URL and + * filename safe" variant of Base64 (see RFC 3548 section 4) where + * {@code -} and {@code _} are used in place of {@code +} and + * {@code /}. + */ + public static final int URL_SAFE = 8; + + /** + * Flag to pass to {@link Base64OutputStream} to indicate that it + * should not close the output stream it is wrapping when it + * itself is closed. + */ + public static final int NO_CLOSE = 16; + + // -------------------------------------------------------- + // shared code + // -------------------------------------------------------- + + /* package */ static abstract class Coder { + public byte[] output; + public int op; + + /** + * Encode/decode another block of input data. this.output is + * provided by the caller, and must be big enough to hold all + * the coded data. On exit, this.opwill be set to the length + * of the coded data. + * + * @param finish true if this is the final call to process for + * this object. Will finalize the coder state and + * include any final bytes in the output. + * + * @return true if the input so far is good; false if some + * error has been detected in the input stream.. + */ + public abstract boolean process(byte[] input, int offset, int len, boolean finish); + + /** + * @return the maximum number of bytes a call to process() + * could produce for the given number of input bytes. This may + * be an overestimate. + */ + public abstract int maxOutputSize(int len); + } + + // -------------------------------------------------------- + // decoding + // -------------------------------------------------------- + + /** + * Decode the Base64-encoded data in input and return the data in + * a new byte array. + * + *

    The padding '=' characters at the end are considered optional, but + * if any are present, there must be the correct number of them. + * + * @param str the input String to decode, which is converted to + * bytes using the default charset + * @param flags controls certain features of the decoded output. + * Pass {@code DEFAULT} to decode standard Base64. + * + * @throws IllegalArgumentException if the input contains + * incorrect padding + */ + public static byte[] decode(String str, int flags) { + return decode(str.getBytes(), flags); + } + + /** + * Decode the Base64-encoded data in input and return the data in + * a new byte array. + * + *

    The padding '=' characters at the end are considered optional, but + * if any are present, there must be the correct number of them. + * + * @param input the input array to decode + * @param flags controls certain features of the decoded output. + * Pass {@code DEFAULT} to decode standard Base64. + * + * @throws IllegalArgumentException if the input contains + * incorrect padding + */ + public static byte[] decode(byte[] input, int flags) { + return decode(input, 0, input.length, flags); + } + + /** + * Decode the Base64-encoded data in input and return the data in + * a new byte array. + * + *

    The padding '=' characters at the end are considered optional, but + * if any are present, there must be the correct number of them. + * + * @param input the data to decode + * @param offset the position within the input array at which to start + * @param len the number of bytes of input to decode + * @param flags controls certain features of the decoded output. + * Pass {@code DEFAULT} to decode standard Base64. + * + * @throws IllegalArgumentException if the input contains + * incorrect padding + */ + public static byte[] decode(byte[] input, int offset, int len, int flags) { + // Allocate space for the most data the input could represent. + // (It could contain less if it contains whitespace, etc.) + Decoder decoder = new Decoder(flags, new byte[len*3/4]); + + if (!decoder.process(input, offset, len, true)) { + throw new IllegalArgumentException("bad base-64"); + } + + // Maybe we got lucky and allocated exactly enough output space. + if (decoder.op == decoder.output.length) { + return decoder.output; + } + + // Need to shorten the array, so allocate a new one of the + // right size and copy. + byte[] temp = new byte[decoder.op]; + System.arraycopy(decoder.output, 0, temp, 0, decoder.op); + return temp; + } + + /* package */ static class Decoder extends Coder { + /** + * Lookup table for turning bytes into their position in the + * Base64 alphabet. + */ + private static final int DECODE[] = { + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -2, -1, -1, + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, + -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + }; + + /** + * Decode lookup table for the "web safe" variant (RFC 3548 + * sec. 4) where - and _ replace + and /. + */ + private static final int DECODE_WEBSAFE[] = { + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -2, -1, -1, + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, 63, + -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + }; + + /** Non-data values in the DECODE arrays. */ + private static final int SKIP = -1; + private static final int EQUALS = -2; + + /** + * States 0-3 are reading through the next input tuple. + * State 4 is having read one '=' and expecting exactly + * one more. + * State 5 is expecting no more data or padding characters + * in the input. + * State 6 is the error state; an error has been detected + * in the input and no future input can "fix" it. + */ + private int state; // state number (0 to 6) + private int value; + + final private int[] alphabet; + + public Decoder(int flags, byte[] output) { + this.output = output; + + alphabet = ((flags & URL_SAFE) == 0) ? DECODE : DECODE_WEBSAFE; + state = 0; + value = 0; + } + + /** + * @return an overestimate for the number of bytes {@code + * len} bytes could decode to. + */ + public int maxOutputSize(int len) { + return len * 3/4 + 10; + } + + /** + * Decode another block of input data. + * + * @return true if the state machine is still healthy. false if + * bad base-64 data has been detected in the input stream. + */ + public boolean process(byte[] input, int offset, int len, boolean finish) { + if (this.state == 6) return false; + + int p = offset; + len += offset; + + // Using local variables makes the decoder about 12% + // faster than if we manipulate the member variables in + // the loop. (Even alphabet makes a measurable + // difference, which is somewhat surprising to me since + // the member variable is final.) + int state = this.state; + int value = this.value; + int op = 0; + final byte[] output = this.output; + final int[] alphabet = this.alphabet; + + while (p < len) { + // Try the fast path: we're starting a new tuple and the + // next four bytes of the input stream are all data + // bytes. This corresponds to going through states + // 0-1-2-3-0. We expect to use this method for most of + // the data. + // + // If any of the next four bytes of input are non-data + // (whitespace, etc.), value will end up negative. (All + // the non-data values in decode are small negative + // numbers, so shifting any of them up and or'ing them + // together will result in a value with its top bit set.) + // + // You can remove this whole block and the output should + // be the same, just slower. + if (state == 0) { + while (p+4 <= len && + (value = ((alphabet[input[p] & 0xff] << 18) | + (alphabet[input[p+1] & 0xff] << 12) | + (alphabet[input[p+2] & 0xff] << 6) | + (alphabet[input[p+3] & 0xff]))) >= 0) { + output[op+2] = (byte) value; + output[op+1] = (byte) (value >> 8); + output[op] = (byte) (value >> 16); + op += 3; + p += 4; + } + if (p >= len) break; + } + + // The fast path isn't available -- either we've read a + // partial tuple, or the next four input bytes aren't all + // data, or whatever. Fall back to the slower state + // machine implementation. + + int d = alphabet[input[p++] & 0xff]; + + switch (state) { + case 0: + if (d >= 0) { + value = d; + ++state; + } else if (d != SKIP) { + this.state = 6; + return false; + } + break; + + case 1: + if (d >= 0) { + value = (value << 6) | d; + ++state; + } else if (d != SKIP) { + this.state = 6; + return false; + } + break; + + case 2: + if (d >= 0) { + value = (value << 6) | d; + ++state; + } else if (d == EQUALS) { + // Emit the last (partial) output tuple; + // expect exactly one more padding character. + output[op++] = (byte) (value >> 4); + state = 4; + } else if (d != SKIP) { + this.state = 6; + return false; + } + break; + + case 3: + if (d >= 0) { + // Emit the output triple and return to state 0. + value = (value << 6) | d; + output[op+2] = (byte) value; + output[op+1] = (byte) (value >> 8); + output[op] = (byte) (value >> 16); + op += 3; + state = 0; + } else if (d == EQUALS) { + // Emit the last (partial) output tuple; + // expect no further data or padding characters. + output[op+1] = (byte) (value >> 2); + output[op] = (byte) (value >> 10); + op += 2; + state = 5; + } else if (d != SKIP) { + this.state = 6; + return false; + } + break; + + case 4: + if (d == EQUALS) { + ++state; + } else if (d != SKIP) { + this.state = 6; + return false; + } + break; + + case 5: + if (d != SKIP) { + this.state = 6; + return false; + } + break; + } + } + + if (!finish) { + // We're out of input, but a future call could provide + // more. + this.state = state; + this.value = value; + this.op = op; + return true; + } + + // Done reading input. Now figure out where we are left in + // the state machine and finish up. + + switch (state) { + case 0: + // Output length is a multiple of three. Fine. + break; + case 1: + // Read one extra input byte, which isn't enough to + // make another output byte. Illegal. + this.state = 6; + return false; + case 2: + // Read two extra input bytes, enough to emit 1 more + // output byte. Fine. + output[op++] = (byte) (value >> 4); + break; + case 3: + // Read three extra input bytes, enough to emit 2 more + // output bytes. Fine. + output[op++] = (byte) (value >> 10); + output[op++] = (byte) (value >> 2); + break; + case 4: + // Read one padding '=' when we expected 2. Illegal. + this.state = 6; + return false; + case 5: + // Read all the padding '='s we expected and no more. + // Fine. + break; + } + + this.state = state; + this.op = op; + return true; + } + } + + // -------------------------------------------------------- + // encoding + // -------------------------------------------------------- + + /** + * Base64-encode the given data and return a newly allocated + * String with the result. + * + * @param input the data to encode + * @param flags controls certain features of the encoded output. + * Passing {@code DEFAULT} results in output that + * adheres to RFC 2045. + */ + public static String encodeToString(byte[] input, int flags) { + try { + return new String(encode(input, flags), "US-ASCII"); + } catch (UnsupportedEncodingException e) { + // US-ASCII is guaranteed to be available. + throw new AssertionError(e); + } + } + + /** + * Base64-encode the given data and return a newly allocated + * String with the result. + * + * @param input the data to encode + * @param offset the position within the input array at which to + * start + * @param len the number of bytes of input to encode + * @param flags controls certain features of the encoded output. + * Passing {@code DEFAULT} results in output that + * adheres to RFC 2045. + */ + public static String encodeToString(byte[] input, int offset, int len, int flags) { + try { + return new String(encode(input, offset, len, flags), "US-ASCII"); + } catch (UnsupportedEncodingException e) { + // US-ASCII is guaranteed to be available. + throw new AssertionError(e); + } + } + + /** + * Base64-encode the given data and return a newly allocated + * byte[] with the result. + * + * @param input the data to encode + * @param flags controls certain features of the encoded output. + * Passing {@code DEFAULT} results in output that + * adheres to RFC 2045. + */ + public static byte[] encode(byte[] input, int flags) { + return encode(input, 0, input.length, flags); + } + + /** + * Base64-encode the given data and return a newly allocated + * byte[] with the result. + * + * @param input the data to encode + * @param offset the position within the input array at which to + * start + * @param len the number of bytes of input to encode + * @param flags controls certain features of the encoded output. + * Passing {@code DEFAULT} results in output that + * adheres to RFC 2045. + */ + public static byte[] encode(byte[] input, int offset, int len, int flags) { + Encoder encoder = new Encoder(flags, null); + + // Compute the exact length of the array we will produce. + int output_len = len / 3 * 4; + + // Account for the tail of the data and the padding bytes, if any. + if (encoder.do_padding) { + if (len % 3 > 0) { + output_len += 4; + } + } else { + switch (len % 3) { + case 0: break; + case 1: output_len += 2; break; + case 2: output_len += 3; break; + } + } + + // Account for the newlines, if any. + if (encoder.do_newline && len > 0) { + output_len += (((len-1) / (3 * Encoder.LINE_GROUPS)) + 1) * + (encoder.do_cr ? 2 : 1); + } + + encoder.output = new byte[output_len]; + encoder.process(input, offset, len, true); + + assert encoder.op == output_len; + + return encoder.output; + } + + /* package */ static class Encoder extends Coder { + /** + * Emit a new line every this many output tuples. Corresponds to + * a 76-character line length (the maximum allowable according to + * RFC 2045). + */ + public static final int LINE_GROUPS = 19; + + /** + * Lookup table for turning Base64 alphabet positions (6 bits) + * into output bytes. + */ + private static final byte ENCODE[] = { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', + 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', + 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', + 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/', + }; + + /** + * Lookup table for turning Base64 alphabet positions (6 bits) + * into output bytes. + */ + private static final byte ENCODE_WEBSAFE[] = { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', + 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', + 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', + 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '_', + }; + + final private byte[] tail; + /* package */ int tailLen; + private int count; + + final public boolean do_padding; + final public boolean do_newline; + final public boolean do_cr; + final private byte[] alphabet; + + public Encoder(int flags, byte[] output) { + this.output = output; + + do_padding = (flags & NO_PADDING) == 0; + do_newline = (flags & NO_WRAP) == 0; + do_cr = (flags & CRLF) != 0; + alphabet = ((flags & URL_SAFE) == 0) ? ENCODE : ENCODE_WEBSAFE; + + tail = new byte[2]; + tailLen = 0; + + count = do_newline ? LINE_GROUPS : -1; + } + + /** + * @return an overestimate for the number of bytes {@code + * len} bytes could encode to. + */ + public int maxOutputSize(int len) { + return len * 8/5 + 10; + } + + public boolean process(byte[] input, int offset, int len, boolean finish) { + // Using local variables makes the encoder about 9% faster. + final byte[] alphabet = this.alphabet; + final byte[] output = this.output; + int op = 0; + int count = this.count; + + int p = offset; + len += offset; + int v = -1; + + // First we need to concatenate the tail of the previous call + // with any input bytes available now and see if we can empty + // the tail. + + switch (tailLen) { + case 0: + // There was no tail. + break; + + case 1: + if (p+2 <= len) { + // A 1-byte tail with at least 2 bytes of + // input available now. + v = ((tail[0] & 0xff) << 16) | + ((input[p++] & 0xff) << 8) | + (input[p++] & 0xff); + tailLen = 0; + } + break; + + case 2: + if (p+1 <= len) { + // A 2-byte tail with at least 1 byte of input. + v = ((tail[0] & 0xff) << 16) | + ((tail[1] & 0xff) << 8) | + (input[p++] & 0xff); + tailLen = 0; + } + break; + } + + if (v != -1) { + output[op++] = alphabet[(v >> 18) & 0x3f]; + output[op++] = alphabet[(v >> 12) & 0x3f]; + output[op++] = alphabet[(v >> 6) & 0x3f]; + output[op++] = alphabet[v & 0x3f]; + if (--count == 0) { + if (do_cr) output[op++] = '\r'; + output[op++] = '\n'; + count = LINE_GROUPS; + } + } + + // At this point either there is no tail, or there are fewer + // than 3 bytes of input available. + + // The main loop, turning 3 input bytes into 4 output bytes on + // each iteration. + while (p+3 <= len) { + v = ((input[p] & 0xff) << 16) | + ((input[p+1] & 0xff) << 8) | + (input[p+2] & 0xff); + output[op] = alphabet[(v >> 18) & 0x3f]; + output[op+1] = alphabet[(v >> 12) & 0x3f]; + output[op+2] = alphabet[(v >> 6) & 0x3f]; + output[op+3] = alphabet[v & 0x3f]; + p += 3; + op += 4; + if (--count == 0) { + if (do_cr) output[op++] = '\r'; + output[op++] = '\n'; + count = LINE_GROUPS; + } + } + + if (finish) { + // Finish up the tail of the input. Note that we need to + // consume any bytes in tail before any bytes + // remaining in input; there should be at most two bytes + // total. + + if (p-tailLen == len-1) { + int t = 0; + v = ((tailLen > 0 ? tail[t++] : input[p++]) & 0xff) << 4; + tailLen -= t; + output[op++] = alphabet[(v >> 6) & 0x3f]; + output[op++] = alphabet[v & 0x3f]; + if (do_padding) { + output[op++] = '='; + output[op++] = '='; + } + if (do_newline) { + if (do_cr) output[op++] = '\r'; + output[op++] = '\n'; + } + } else if (p-tailLen == len-2) { + int t = 0; + v = (((tailLen > 1 ? tail[t++] : input[p++]) & 0xff) << 10) | + (((tailLen > 0 ? tail[t++] : input[p++]) & 0xff) << 2); + tailLen -= t; + output[op++] = alphabet[(v >> 12) & 0x3f]; + output[op++] = alphabet[(v >> 6) & 0x3f]; + output[op++] = alphabet[v & 0x3f]; + if (do_padding) { + output[op++] = '='; + } + if (do_newline) { + if (do_cr) output[op++] = '\r'; + output[op++] = '\n'; + } + } else if (do_newline && op > 0 && count != LINE_GROUPS) { + if (do_cr) output[op++] = '\r'; + output[op++] = '\n'; + } + + assert tailLen == 0; + assert p == len; + } else { + // Save the leftovers in tail to be consumed on the next + // call to encodeInternal. + + if (p == len-1) { + tail[tailLen++] = input[p]; + } else if (p == len-2) { + tail[tailLen++] = input[p]; + tail[tailLen++] = input[p+1]; + } + } + + this.op = op; + this.count = count; + + return true; + } + } + + private Base64() { } // don't instantiate +} diff --git a/library/src/main/java/com/loopj/android/http/Base64DataException.java b/library/src/main/java/com/loopj/android/http/Base64DataException.java new file mode 100644 index 000000000..ead54c561 --- /dev/null +++ b/library/src/main/java/com/loopj/android/http/Base64DataException.java @@ -0,0 +1,9 @@ +package com.loopj.android.http; + +import java.io.IOException; + +public class Base64DataException extends IOException { + public Base64DataException(String detailMessage) { + super(detailMessage); + } +} \ No newline at end of file diff --git a/library/src/main/java/com/loopj/android/http/Base64OutputStream.java b/library/src/main/java/com/loopj/android/http/Base64OutputStream.java new file mode 100644 index 000000000..61afb9317 --- /dev/null +++ b/library/src/main/java/com/loopj/android/http/Base64OutputStream.java @@ -0,0 +1,130 @@ +package com.loopj.android.http; + + +import java.io.FilterOutputStream; +import java.io.IOException; +import java.io.OutputStream; + +public class Base64OutputStream extends FilterOutputStream { + private final Base64.Coder coder; + private final int flags; + + private byte[] buffer = null; + private int bpos = 0; + + private static byte[] EMPTY = new byte[0]; + + /** + * Performs Base64 encoding on the data written to the stream, writing the encoded data to + * another OutputStream. + * + * @param out the OutputStream to write the encoded data to + * @param flags bit flags for controlling the encoder; see the constants in {@link Base64} + */ + public Base64OutputStream(OutputStream out, int flags) { + this(out, flags, true); + } + + /** + * Performs Base64 encoding or decoding on the data written to the stream, writing the + * encoded/decoded data to another OutputStream. + * + * @param out the OutputStream to write the encoded data to + * @param flags bit flags for controlling the encoder; see the constants in {@link Base64} + * @param encode true to encode, false to decode + */ + public Base64OutputStream(OutputStream out, int flags, boolean encode) { + super(out); + this.flags = flags; + if (encode) { + coder = new Base64.Encoder(flags, null); + } else { + coder = new Base64.Decoder(flags, null); + } + } + + public void write(int b) throws IOException { + // To avoid invoking the encoder/decoder routines for single + // bytes, we buffer up calls to write(int) in an internal + // byte array to transform them into writes of decently-sized + // arrays. + + if (buffer == null) { + buffer = new byte[1024]; + } + if (bpos >= buffer.length) { + // internal buffer full; write it out. + internalWrite(buffer, 0, bpos, false); + bpos = 0; + } + buffer[bpos++] = (byte) b; + } + + /** + * Flush any buffered data from calls to write(int). Needed before doing a write(byte[], int, + * int) or a close(). + */ + private void flushBuffer() throws IOException { + if (bpos > 0) { + internalWrite(buffer, 0, bpos, false); + bpos = 0; + } + } + + public void write(byte[] b, int off, int len) throws IOException { + if (len <= 0) return; + flushBuffer(); + internalWrite(b, off, len, false); + } + + public void close() throws IOException { + IOException thrown = null; + try { + flushBuffer(); + internalWrite(EMPTY, 0, 0, true); + } catch (IOException e) { + thrown = e; + } + + try { + if ((flags & Base64.NO_CLOSE) == 0) { + out.close(); + } else { + out.flush(); + } + } catch (IOException e) { + if (thrown != null) { + thrown = e; + } + } + + if (thrown != null) { + throw thrown; + } + } + + /** + * Write the given bytes to the encoder/decoder. + * + * @param finish true if this is the last batch of input, to cause encoder/decoder state to be + * finalized. + */ + private void internalWrite(byte[] b, int off, int len, boolean finish) throws IOException { + coder.output = embiggen(coder.output, coder.maxOutputSize(len)); + if (!coder.process(b, off, len, finish)) { + throw new Base64DataException("bad base-64"); + } + out.write(coder.output, 0, coder.op); + } + + /** + * If b.length is at least len, return b. Otherwise return a new byte array of length len. + */ + private byte[] embiggen(byte[] b, int len) { + if (b == null || b.length < len) { + return new byte[len]; + } else { + return b; + } + } +} \ No newline at end of file diff --git a/library/src/main/java/com/loopj/android/http/JsonStreamerEntity.java b/library/src/main/java/com/loopj/android/http/JsonStreamerEntity.java index 0540cd285..c17b15b41 100644 --- a/library/src/main/java/com/loopj/android/http/JsonStreamerEntity.java +++ b/library/src/main/java/com/loopj/android/http/JsonStreamerEntity.java @@ -18,24 +18,23 @@ package com.loopj.android.http; +import android.util.Base64; +import android.util.Base64OutputStream; +import android.util.Log; + import org.apache.http.Header; import org.apache.http.HttpEntity; import org.apache.http.message.BasicHeader; -import android.util.Log; -import android.util.Base64; -import android.util.Base64OutputStream; - +import java.io.BufferedOutputStream; import java.io.File; import java.io.FileInputStream; +import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.io.BufferedOutputStream; -import java.io.IOException; - -import java.util.Set; -import java.util.Map; import java.util.HashMap; +import java.util.Map; +import java.util.Set; import java.util.zip.GZIPOutputStream; /** @@ -63,13 +62,13 @@ class JsonStreamerEntity implements HttpEntity { "application/octet-stream"; // Size of the byte-array buffer used to read from files. - private static int BUFFER_SIZE = 2048; + private static final int BUFFER_SIZE = 2048; // K/V objects to be uploaded. - private final Map kvParams = new HashMap(); + private final Map kvParams = new HashMap(); // Streams and their associated meta-data to be uploaded. - private final Map streamParams = new HashMap(); + private final Map streamParams = new HashMap(); // Whether to use gzip compression while uploading private final Header contentEncoding; @@ -169,7 +168,7 @@ public void writeTo(final OutputStream outstream) throws IOException { Object value = kvParams.get(key); if (value instanceof Boolean) { - upload.write(((Boolean)value).booleanValue() ? JSON_TRUE : JSON_FALSE); + upload.write((Boolean) value ? JSON_TRUE : JSON_FALSE); } else if (value instanceof Long) { upload.write((((Number)value).longValue() + "").getBytes()); } else if (value instanceof Double) { @@ -230,10 +229,6 @@ public void writeTo(final OutputStream outstream) throws IOException { upload.write(','); } - // GC. - keys = null; - buffer = null; - // Include the elapsed time taken to upload everything. upload.write(STREAM_ELAPSED); upload.write(':'); From f6628196aa34eae1aa943f966883497fc86f4199 Mon Sep 17 00:00:00 2001 From: Oriental Sensation Date: Sat, 16 Nov 2013 14:05:46 +0100 Subject: [PATCH 208/613] Used backported Base64 implementation, and enhanced speed in escape() method. --- .../android/http/JsonStreamerEntity.java | 137 ++++++++++-------- .../com/loopj/android/http/RequestParams.java | 14 +- 2 files changed, 84 insertions(+), 67 deletions(-) diff --git a/library/src/main/java/com/loopj/android/http/JsonStreamerEntity.java b/library/src/main/java/com/loopj/android/http/JsonStreamerEntity.java index c17b15b41..ac74b70bc 100644 --- a/library/src/main/java/com/loopj/android/http/JsonStreamerEntity.java +++ b/library/src/main/java/com/loopj/android/http/JsonStreamerEntity.java @@ -18,8 +18,6 @@ package com.loopj.android.http; -import android.util.Base64; -import android.util.Base64OutputStream; import android.util.Log; import org.apache.http.Header; @@ -27,8 +25,6 @@ import org.apache.http.message.BasicHeader; import java.io.BufferedOutputStream; -import java.io.File; -import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; @@ -51,42 +47,47 @@ class JsonStreamerEntity implements HttpEntity { private static final byte[] JSON_TRUE = "true".getBytes(); private static final byte[] JSON_FALSE = "false".getBytes(); - private static final byte[] STREAM_NAME = escape("name", true).getBytes(); - private static final byte[] STREAM_TYPE = escape("type", true).getBytes(); - private static final byte[] STREAM_CONTENTS = escape("contents", true).getBytes(); - private static final byte[] STREAM_ELAPSED = escape("_elapsed", true).getBytes(); + private static final byte[] STREAM_NAME = escape("name"); + private static final byte[] STREAM_TYPE = escape("type"); + private static final byte[] STREAM_CONTENTS = escape("contents"); + private static final byte[] STREAM_ELAPSED = escape("_elapsed"); - private static final Header HEADER_JSON = + private static final Header HEADER_JSON_CONTENT = new BasicHeader("Content-Type", "application/json"); + private static final Header HEADER_GZIP_ENCODING = + new BasicHeader("Content-Encoding", "gzip"); private static final String APPLICATION_OCTET_STREAM = "application/octet-stream"; - // Size of the byte-array buffer used to read from files. + // Size of the byte-array buffer used to read from streams. private static final int BUFFER_SIZE = 2048; + // Reusable StringBuilder used by escape() method. + // Base64, at worst, will make a binary stream grow in size by approximately + // (n + 2 - ((n + 2) % 3)) / 3 * 4, which is roughly 1.3333333% for a + // large 'n'. + private static final StringBuilder BUILDER = + new StringBuilder((int)(BUFFER_SIZE * 1.35f)); + // K/V objects to be uploaded. - private final Map kvParams = new HashMap(); + private final Map kvParams = + new HashMap(); // Streams and their associated meta-data to be uploaded. - private final Map streamParams = new HashMap(); + private final Map streamParams = + new HashMap(); // Whether to use gzip compression while uploading private final Header contentEncoding; public JsonStreamerEntity(boolean contentEncoding) { - this.contentEncoding = contentEncoding - ? new BasicHeader("Content-Encoding", "gzip") - : null; + this.contentEncoding = contentEncoding ? HEADER_GZIP_ENCODING : null; } public void addPart(String key, Object value) { kvParams.put(key, value); } - public void addPart(String key, File file, String type) throws IOException { - addPart(key, new FileInputStream(file), file.getName(), type); - } - public void addPart(String key, InputStream inputStream, String name, String type) { if (type == null) { type = APPLICATION_OCTET_STREAM; @@ -121,7 +122,7 @@ public Header getContentEncoding() { @Override public Header getContentType() { - return HEADER_JSON; + return HEADER_JSON_CONTENT; } @Override @@ -139,13 +140,16 @@ public void writeTo(final OutputStream outstream) throws IOException { throw new IllegalStateException("Output stream cannot be null."); } + // Record the time when uploading started. long now = System.currentTimeMillis(); - Log.i(LOG_TAG, "Started dumping at: " + now); - OutputStream upload; + // Keys used by the HashMaps. + Set keys; - // GZIPOutputStream is available only from API level 8 and onward. - if(null != contentEncoding) { + // Use GZIP compression when sending streams, otherwise just use + // a buffered output stream to speed things up a bit. + OutputStream upload; + if (null != contentEncoding) { upload = new GZIPOutputStream(new BufferedOutputStream(outstream), BUFFER_SIZE); } else { upload = new BufferedOutputStream(outstream); @@ -154,21 +158,18 @@ public void writeTo(final OutputStream outstream) throws IOException { // Always send a JSON object. upload.write('{'); - // Keys used by the HashMaps. - Set keys; - // Send the K/V values. keys = kvParams.keySet(); for (String key : keys) { // Write the JSON object's key. - upload.write(escape(key, true).getBytes()); + upload.write(escape(key)); upload.write(':'); // Evaluate the value (which cannot be null). Object value = kvParams.get(key); if (value instanceof Boolean) { - upload.write((Boolean) value ? JSON_TRUE : JSON_FALSE); + upload.write((Boolean)value ? JSON_TRUE : JSON_FALSE); } else if (value instanceof Long) { upload.write((((Number)value).longValue() + "").getBytes()); } else if (value instanceof Double) { @@ -189,113 +190,125 @@ public void writeTo(final OutputStream outstream) throws IOException { // Send the stream params. keys = streamParams.keySet(); - for(String key : keys) { + for (String key : keys) { RequestParams.StreamWrapper entry = streamParams.get(key); // Write the JSON object's key. - upload.write(escape(key, true).getBytes()); + upload.write(escape(key)); // All uploads are sent as an object containing the file's details. - upload.write(":{".getBytes()); + upload.write(':'); + upload.write('{'); // Send the streams's name. upload.write(STREAM_NAME); upload.write(':'); - upload.write(escape(entry.name, true).getBytes()); + upload.write(escape(entry.name)); // Send the streams's content type. upload.write(STREAM_TYPE); upload.write(':'); - upload.write(escape(entry.contentType, true).getBytes()); + upload.write(escape(entry.contentType)); // Prepare the file content's key. upload.write(STREAM_CONTENTS); upload.write(':'); upload.write('"'); - // Write the file's contents in Base64. - Base64OutputStream outputStream = new Base64OutputStream(upload, Base64.NO_CLOSE | Base64.NO_WRAP); + // Upload the file's contents in Base64. + Base64OutputStream outputStream = + new Base64OutputStream(upload, Base64.NO_CLOSE | Base64.NO_WRAP); + + // Read from input stream until no more data's left to read. int bytesRead; - while(-1 != (bytesRead = entry.inputStream.read(buffer))) { + while ((bytesRead = entry.inputStream.read(buffer)) != -1) { outputStream.write(buffer, 0, bytesRead); } - // Close the output stream. + // Close the Base64 output stream. outputStream.close(); - // Close the file's object. + // End the file's object and prepare for next one. upload.write('"'); upload.write('}'); upload.write(','); } // Include the elapsed time taken to upload everything. + // This might be useful for somebody, but it serves us well since + // there will almost always be a ',' as the last sent character. upload.write(STREAM_ELAPSED); upload.write(':'); long elapsedTime = System.currentTimeMillis() - now; upload.write((elapsedTime + "}").getBytes()); - Log.i(LOG_TAG, "JSON was uploaded in " + Math.floor(elapsedTime / 1000) + " seconds"); + Log.i(LOG_TAG, "Uploaded JSON in " + Math.floor(elapsedTime / 1000) + " seconds"); // Flush the contents up the stream. upload.flush(); upload.close(); } - // Curtosy of Simple-JSON: - // http://goo.gl/XoW8RF - private static String escape(String string, boolean quotes) { - StringBuilder sb = new StringBuilder(); + // Curtosy of Simple-JSON: http://goo.gl/XoW8RF + // Changed a bit to suit our needs in this class. + static byte[] escape(String string) { + // Surround with quotations. + BUILDER.append('"'); + int length = string.length(), pos = -1; - if (quotes) { - sb.append('"'); - } while (++pos < length) { char ch = string.charAt(pos); switch (ch) { case '"': - sb.append("\\\""); + BUILDER.append("\\\""); break; case '\\': - sb.append("\\\\"); + BUILDER.append("\\\\"); break; case '\b': - sb.append("\\b"); + BUILDER.append("\\b"); break; case '\f': - sb.append("\\f"); + BUILDER.append("\\f"); break; case '\n': - sb.append("\\n"); + BUILDER.append("\\n"); break; case '\r': - sb.append("\\r"); + BUILDER.append("\\r"); break; case '\t': - sb.append("\\t"); + BUILDER.append("\\t"); break; case '/': - sb.append("\\/"); + BUILDER.append("\\/"); break; default: // Reference: http://www.unicode.org/versions/Unicode5.1.0/ if((ch >= '\u0000' && ch <= '\u001F') || (ch >= '\u007F' && ch <= '\u009F') || (ch >= '\u2000' && ch <= '\u20FF')) { String intString = Integer.toHexString(ch); - sb.append("\\u"); + BUILDER.append("\\u"); int intLength = 4 - intString.length(); for (int zero = 0; zero < intLength; zero++) { - sb.append('0'); + BUILDER.append('0'); } - sb.append(intString.toUpperCase()); + BUILDER.append(intString.toUpperCase()); } else { - sb.append(ch); + BUILDER.append(ch); } break; } } - if (quotes) { - sb.append('"'); + + // Surround with quotations. + BUILDER.append('"'); + + try { + return BUILDER.toString().getBytes(); + } finally { + // Empty the String buffer. + // This is 20-30% faster than instantiating a new object. + BUILDER.setLength(0); } - return sb.toString(); } } diff --git a/library/src/main/java/com/loopj/android/http/RequestParams.java b/library/src/main/java/com/loopj/android/http/RequestParams.java index 0d7e92f7b..c77627226 100644 --- a/library/src/main/java/com/loopj/android/http/RequestParams.java +++ b/library/src/main/java/com/loopj/android/http/RequestParams.java @@ -25,6 +25,7 @@ import org.apache.http.protocol.HTTP; import java.io.File; +import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; @@ -314,9 +315,6 @@ public void setHttpEntityIsRepeatable(boolean isRepeatable) { } public void setUseJsonStreamer(boolean useJsonStreamer) { - if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.FROYO) { - throw new IllegalStateException("Use of JSON streamer is available for API level 8 and later."); - } this.useJsonStreamer = useJsonStreamer; } @@ -354,14 +352,20 @@ private HttpEntity createJsonStreamerEntity() throws IOException { // Add file params for (ConcurrentHashMap.Entry entry : fileParams.entrySet()) { FileWrapper fileWrapper = entry.getValue(); - entity.addPart(entry.getKey(), fileWrapper.file, fileWrapper.contentType); + entity.addPart(entry.getKey(), + new FileInputStream(fileWrapper.file), + fileWrapper.file.getName(), + fileWrapper.contentType); } // Add stream params for (ConcurrentHashMap.Entry entry : streamParams.entrySet()) { StreamWrapper stream = entry.getValue(); if (stream.inputStream != null) { - entity.addPart(entry.getKey(), stream.inputStream, stream.name, stream.contentType); + entity.addPart(entry.getKey(), + stream.inputStream, + stream.name, + stream.contentType); } } From 0f9d4c6cf47b51cca3a9bd84a9bd3f2274a48ec1 Mon Sep 17 00:00:00 2001 From: Oriental Sensation Date: Sat, 16 Nov 2013 14:47:22 +0100 Subject: [PATCH 209/613] Fixed a misaligned method call. --- .../src/main/java/com/loopj/android/http/RequestParams.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/src/main/java/com/loopj/android/http/RequestParams.java b/library/src/main/java/com/loopj/android/http/RequestParams.java index c77627226..825132fda 100644 --- a/library/src/main/java/com/loopj/android/http/RequestParams.java +++ b/library/src/main/java/com/loopj/android/http/RequestParams.java @@ -354,8 +354,8 @@ private HttpEntity createJsonStreamerEntity() throws IOException { FileWrapper fileWrapper = entry.getValue(); entity.addPart(entry.getKey(), new FileInputStream(fileWrapper.file), - fileWrapper.file.getName(), - fileWrapper.contentType); + fileWrapper.file.getName(), + fileWrapper.contentType); } // Add stream params From 5c08a48eda20b564dd8a4bc631cf09b36648ea2e Mon Sep 17 00:00:00 2001 From: Oriental Sensation Date: Sat, 16 Nov 2013 15:17:48 +0100 Subject: [PATCH 210/613] Fixed a small bug where BUILDER is used in escape() before being instantiated. --- .../android/http/JsonStreamerEntity.java | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/library/src/main/java/com/loopj/android/http/JsonStreamerEntity.java b/library/src/main/java/com/loopj/android/http/JsonStreamerEntity.java index ac74b70bc..d6582e488 100644 --- a/library/src/main/java/com/loopj/android/http/JsonStreamerEntity.java +++ b/library/src/main/java/com/loopj/android/http/JsonStreamerEntity.java @@ -45,6 +45,16 @@ class JsonStreamerEntity implements HttpEntity { private static final UnsupportedOperationException ERR_UNSUPPORTED = new UnsupportedOperationException("Unsupported operation in this implementation."); + // Size of the byte-array buffer used to read from streams. + private static final int BUFFER_SIZE = 2048; + + // Reusable StringBuilder used by escape() method. + // Base64, at worst, will make a binary stream grow in size by approximately + // (n + 2 - ((n + 2) % 3)) / 3 * 4, which is roughly 1.3333333% for a + // large 'n'. + private static final StringBuilder BUILDER = + new StringBuilder((int)(BUFFER_SIZE * 1.35f)); + private static final byte[] JSON_TRUE = "true".getBytes(); private static final byte[] JSON_FALSE = "false".getBytes(); private static final byte[] STREAM_NAME = escape("name"); @@ -59,16 +69,6 @@ class JsonStreamerEntity implements HttpEntity { private static final String APPLICATION_OCTET_STREAM = "application/octet-stream"; - // Size of the byte-array buffer used to read from streams. - private static final int BUFFER_SIZE = 2048; - - // Reusable StringBuilder used by escape() method. - // Base64, at worst, will make a binary stream grow in size by approximately - // (n + 2 - ((n + 2) % 3)) / 3 * 4, which is roughly 1.3333333% for a - // large 'n'. - private static final StringBuilder BUILDER = - new StringBuilder((int)(BUFFER_SIZE * 1.35f)); - // K/V objects to be uploaded. private final Map kvParams = new HashMap(); From c53f350ad42809d0549c260f838d1fbe273c2609 Mon Sep 17 00:00:00 2001 From: mareksebera Date: Sat, 16 Nov 2013 16:59:48 +0100 Subject: [PATCH 211/613] Thread pool reverted, cached thread pool contains keepAlive timeout 60seconds and behaves better, Fixes #382 --- .../src/main/java/com/loopj/android/http/AsyncHttpClient.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/src/main/java/com/loopj/android/http/AsyncHttpClient.java b/library/src/main/java/com/loopj/android/http/AsyncHttpClient.java index 6263127dd..24f409e28 100644 --- a/library/src/main/java/com/loopj/android/http/AsyncHttpClient.java +++ b/library/src/main/java/com/loopj/android/http/AsyncHttpClient.java @@ -210,7 +210,7 @@ public AsyncHttpClient(SchemeRegistry schemeRegistry) { ThreadSafeClientConnManager cm = new ThreadSafeClientConnManager(httpParams, schemeRegistry); - threadPool = Executors.newFixedThreadPool(DEFAULT_MAX_CONNECTIONS); + threadPool = Executors.newCachedThreadPool(); requestMap = new WeakHashMap>>>(); clientHeaderMap = new HashMap(); From df9b891452c6bb59f0b40f41b80390133da3f14f Mon Sep 17 00:00:00 2001 From: Oriental Sensation Date: Sat, 16 Nov 2013 17:00:02 +0100 Subject: [PATCH 212/613] Fixed a bug when sending streams; meta data wasn't separated by commas. --- .../java/com/loopj/android/http/JsonStreamerEntity.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/library/src/main/java/com/loopj/android/http/JsonStreamerEntity.java b/library/src/main/java/com/loopj/android/http/JsonStreamerEntity.java index d6582e488..3a16d11ab 100644 --- a/library/src/main/java/com/loopj/android/http/JsonStreamerEntity.java +++ b/library/src/main/java/com/loopj/android/http/JsonStreamerEntity.java @@ -57,6 +57,7 @@ class JsonStreamerEntity implements HttpEntity { private static final byte[] JSON_TRUE = "true".getBytes(); private static final byte[] JSON_FALSE = "false".getBytes(); + private static final byte[] JSON_NULL = "null".getBytes(); private static final byte[] STREAM_NAME = escape("name"); private static final byte[] STREAM_TYPE = escape("type"); private static final byte[] STREAM_CONTENTS = escape("contents"); @@ -204,11 +205,13 @@ public void writeTo(final OutputStream outstream) throws IOException { upload.write(STREAM_NAME); upload.write(':'); upload.write(escape(entry.name)); + upload.write(','); // Send the streams's content type. upload.write(STREAM_TYPE); upload.write(':'); upload.write(escape(entry.contentType)); + upload.write(','); // Prepare the file content's key. upload.write(STREAM_CONTENTS); @@ -252,6 +255,11 @@ public void writeTo(final OutputStream outstream) throws IOException { // Curtosy of Simple-JSON: http://goo.gl/XoW8RF // Changed a bit to suit our needs in this class. static byte[] escape(String string) { + // If it's null, just return prematurely. + if (string == null) { + return JSON_NULL; + } + // Surround with quotations. BUILDER.append('"'); From 300cab8c4f39c49a0e4c03829a8312cd66f8b4ea Mon Sep 17 00:00:00 2001 From: mareksebera Date: Sat, 16 Nov 2013 17:09:41 +0100 Subject: [PATCH 213/613] Allowing to retry POST requests if underlying HttpEntity isRepeatable returns true --- library/src/main/java/com/loopj/android/http/RetryHandler.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/library/src/main/java/com/loopj/android/http/RetryHandler.java b/library/src/main/java/com/loopj/android/http/RetryHandler.java index 38f725cd0..f777d1fe7 100644 --- a/library/src/main/java/com/loopj/android/http/RetryHandler.java +++ b/library/src/main/java/com/loopj/android/http/RetryHandler.java @@ -92,8 +92,6 @@ public boolean retryRequest(IOException exception, int executionCount, HttpConte if (currentReq == null) { return false; } - String requestType = currentReq.getMethod(); - retry = !requestType.equals("POST"); } if (retry) { From 70ccbd656f531bf30ca223e6b91d42607fe413e9 Mon Sep 17 00:00:00 2001 From: Oriental Sensation Date: Sat, 16 Nov 2013 17:20:13 +0100 Subject: [PATCH 214/613] There's no need to escape slash character since content is already base64-encoded. --- .../main/java/com/loopj/android/http/JsonStreamerEntity.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/library/src/main/java/com/loopj/android/http/JsonStreamerEntity.java b/library/src/main/java/com/loopj/android/http/JsonStreamerEntity.java index 3a16d11ab..181019e75 100644 --- a/library/src/main/java/com/loopj/android/http/JsonStreamerEntity.java +++ b/library/src/main/java/com/loopj/android/http/JsonStreamerEntity.java @@ -288,9 +288,6 @@ static byte[] escape(String string) { case '\t': BUILDER.append("\\t"); break; - case '/': - BUILDER.append("\\/"); - break; default: // Reference: http://www.unicode.org/versions/Unicode5.1.0/ if((ch >= '\u0000' && ch <= '\u001F') || (ch >= '\u007F' && ch <= '\u009F') || (ch >= '\u2000' && ch <= '\u20FF')) { From 6d5e8ac1c518bfaa81d70f55cec7b1a979ffd1e3 Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 18 Nov 2013 14:45:27 +0100 Subject: [PATCH 215/613] Added modifications to allow for progress notifications with data --- .../http/AsyncHttpResponseHandler.java | 3 + .../http/DataAsyncHttpResponseHandler.java | 405 ++++++++++++++++++ .../http/ResponseHandlerInterface.java | 4 + 3 files changed, 412 insertions(+) create mode 100644 library/src/main/java/com/loopj/android/http/DataAsyncHttpResponseHandler.java diff --git a/library/src/main/java/com/loopj/android/http/AsyncHttpResponseHandler.java b/library/src/main/java/com/loopj/android/http/AsyncHttpResponseHandler.java index 3d312e26b..36e14fb59 100644 --- a/library/src/main/java/com/loopj/android/http/AsyncHttpResponseHandler.java +++ b/library/src/main/java/com/loopj/android/http/AsyncHttpResponseHandler.java @@ -227,6 +227,9 @@ final public void sendProgressMessage(int bytesWritten, int bytesTotal) { sendMessage(obtainMessage(PROGRESS_MESSAGE, new Object[]{bytesWritten, bytesTotal})); } + final public void sendProgressDataMessage(byte[] responseBytes) { + } + final public void sendSuccessMessage(int statusCode, Header[] headers, byte[] responseBytes) { sendMessage(obtainMessage(SUCCESS_MESSAGE, new Object[]{statusCode, headers, responseBytes})); } diff --git a/library/src/main/java/com/loopj/android/http/DataAsyncHttpResponseHandler.java b/library/src/main/java/com/loopj/android/http/DataAsyncHttpResponseHandler.java new file mode 100644 index 000000000..d900f6a18 --- /dev/null +++ b/library/src/main/java/com/loopj/android/http/DataAsyncHttpResponseHandler.java @@ -0,0 +1,405 @@ +package com.loopj.android.http; + +/* + Android Asynchronous Http Client + Copyright (c) 2011 James Smith + http://loopj.com + + 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. +*/ + +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.util.Log; + +import org.apache.http.Header; +import org.apache.http.HttpEntity; +import org.apache.http.HttpResponse; +import org.apache.http.StatusLine; +import org.apache.http.client.HttpResponseException; +import org.apache.http.util.ByteArrayBuffer; + +import java.io.IOException; +import java.io.InputStream; +import java.lang.ref.WeakReference; +import java.net.URI; +import java.util.Arrays; + +/** + * Used to intercept and handle the responses from requests made using {@link AsyncHttpClient}. The + * {@link #onSuccess(int, org.apache.http.Header[], byte[])} method is designed to be anonymously + * overridden with your own response handling code.

     

    Additionally, you can override the + * {@link #onFailure(int, org.apache.http.Header[], byte[], Throwable)}, {@link #onStart()}, {@link + * #onFinish()}, {@link #onRetry(int)} and {@link #onProgress(int, int)} methods as required. + *

     

    For example:

     

    + *
    + * AsyncHttpClient client = new AsyncHttpClient();
    + * client.get("http://www.google.com/", new AsyncHttpResponseHandler() {
    + *     @Override
    + *     public void onStart() {
    + *         // Initiated the request
    + *     }
    + *
    + *     @Override
    + *     public void onSuccess(int statusCode, Header[] headers, byte[] responseBody) {
    + *         // Successfully got a response
    + *     }
    + *
    + *     @Override
    + *     public void onFailure(int statusCode, Header[] headers, byte[] responseBody, Throwable
    + * error)
    + * {
    + *         // Response failed :(
    + *     }
    + *
    + *     @Override
    + *     public void onRetry() {
    + *         // Request was retried
    + *     }
    + *
    + *     @Override
    + *     public void onProgress(int bytesWritten, int totalSize) {
    + *         // Progress notification
    + *     }
    + *
    + *     @Override
    + *     public void onFinish() {
    + *         // Completed the request (either success or failure)
    + *     }
    + * });
    + * 
    + */ +/** + * Created by vkr on 14.11.13. + */ +public abstract class DataAsyncHttpResponseHandler implements ResponseHandlerInterface { + private static final String LOG_TAG = "AsyncHttpResponseHandler"; + + protected static final int SUCCESS_MESSAGE = 0; + protected static final int FAILURE_MESSAGE = 1; + protected static final int START_MESSAGE = 2; + protected static final int FINISH_MESSAGE = 3; + protected static final int PROGRESS_DATA_MESSAGE = 4; + protected static final int RETRY_MESSAGE = 5; + + protected static final int BUFFER_SIZE = 4096; + + private Handler handler; + public static final String DEFAULT_CHARSET = "UTF-8"; + private String responseCharset = DEFAULT_CHARSET; + private Boolean useSynchronousMode = false; + + private URI requestURI = null; + private Header[] requestHeaders = null; + + @Override + public URI getRequestURI() { + return this.requestURI; + } + + @Override + public Header[] getRequestHeaders() { + return this.requestHeaders; + } + + @Override + public void setRequestURI(URI requestURI) { + this.requestURI = requestURI; + } + + @Override + public void setRequestHeaders(Header[] requestHeaders) { + this.requestHeaders = requestHeaders; + } + + /** + * Avoid leaks by using a non-anonymous handler class with a weak reference + */ + static class ResponderHandler extends Handler { + private final WeakReference mResponder; + + ResponderHandler(DataAsyncHttpResponseHandler service) { + mResponder = new WeakReference(service); + } + + @Override + public void handleMessage(Message msg) { + DataAsyncHttpResponseHandler service = mResponder.get(); + if (service != null) { + service.handleMessage(msg); + } + } + } + + @Override + public boolean getUseSynchronousMode() { + return useSynchronousMode; + } + + @Override + public void setUseSynchronousMode(boolean value) { + useSynchronousMode = value; + } + + /** + * Sets the charset for the response string. If not set, the default is UTF-8. + * + * @param charset to be used for the response string. + * @see Charset + */ + public void setCharset(final String charset) { + this.responseCharset = charset; + } + + public String getCharset() { + return this.responseCharset == null ? DEFAULT_CHARSET : this.responseCharset; + } + + /** + * Creates a new AsyncHttpResponseHandler + */ + public DataAsyncHttpResponseHandler() { + // Set up a handler to post events back to the correct thread if possible + if (Looper.myLooper() != null) { + handler = new ResponderHandler(this); + } + } + + /** + * Fired when the request progress, override to handle in your own code + * + * @param responseBody + */ + public void onProgressData(byte[] responseBody) { + } + + /** + * Fired when the request is started, override to handle in your own code + */ + public void onStart() { + } + + /** + * Fired in all cases when the request is finished, after both success and failure, override to + * handle in your own code + */ + public void onFinish() { + } + + /** + * Fired when a request returns successfully, override to handle in your own code + * + * @param statusCode the status code of the response + * @param headers return headers, if any + * @param responseBody the body of the HTTP response from the server + */ + public abstract void onSuccess(int statusCode, Header[] headers, byte[] responseBody); + + /** + * Fired when a request fails to complete, override to handle in your own code + * + * @param statusCode return HTTP status code + * @param headers return headers, if any + * @param responseBody the response body, if any + * @param error the underlying cause of the failure + */ + public abstract void onFailure(int statusCode, Header[] headers, byte[] responseBody, Throwable error); + + /** + * Fired when a retry occurs, override to handle in your own code + * + * @param retryNo number of retry + */ + public void onRetry(int retryNo) { + Log.d(LOG_TAG, String.format("Request retry no. %d", retryNo)); + } + + final public void sendProgressMessage(int bytesWritten, int bytesTotal) { + } + + final public void sendProgressDataMessage(byte[] responseBytes) { + sendMessage(obtainMessage(PROGRESS_DATA_MESSAGE, new Object[]{responseBytes})); + } + + final public void sendSuccessMessage(int statusCode, Header[] headers, byte[] responseBytes) { + sendMessage(obtainMessage(SUCCESS_MESSAGE, new Object[]{statusCode, headers, responseBytes})); + } + + final public void sendFailureMessage(int statusCode, Header[] headers, byte[] responseBody, Throwable throwable) { + sendMessage(obtainMessage(FAILURE_MESSAGE, new Object[]{statusCode, headers, responseBody, throwable})); + } + + final public void sendStartMessage() { + sendMessage(obtainMessage(START_MESSAGE, null)); + } + + final public void sendFinishMessage() { + sendMessage(obtainMessage(FINISH_MESSAGE, null)); + } + + final public void sendRetryMessage(int retryNo) { + sendMessage(obtainMessage(RETRY_MESSAGE, new Object[]{retryNo})); + } + + // Methods which emulate android's Handler and Message methods + protected void handleMessage(Message message) { + Object[] response; + + switch (message.what) { + case SUCCESS_MESSAGE: + response = (Object[]) message.obj; + if (response != null && response.length >= 3) { + onSuccess((Integer) response[0], (Header[]) response[1], (byte[]) response[2]); + } else { + Log.e(LOG_TAG, "SUCCESS_MESSAGE didn't got enough params"); + } + break; + case FAILURE_MESSAGE: + response = (Object[]) message.obj; + if (response != null && response.length >= 4) { + onFailure((Integer) response[0], (Header[]) response[1], (byte[]) response[2], (Throwable) response[3]); + } else { + Log.e(LOG_TAG, "FAILURE_MESSAGE didn't got enough params"); + } + break; + case START_MESSAGE: + onStart(); + break; + case FINISH_MESSAGE: + onFinish(); + break; + case PROGRESS_DATA_MESSAGE: + response = (Object[]) message.obj; + if (response != null && response.length >= 1) { + try { + onProgressData((byte[])response[0]); + } catch (Throwable t) { + Log.e(LOG_TAG, "custom onProgressData contains an error", t); + } + } else { + Log.e(LOG_TAG, "PROGRESS_DATA_MESSAGE didn't got enough params"); + } + break; + case RETRY_MESSAGE: + response = (Object[]) message.obj; + if (response != null && response.length == 1) + onRetry((Integer) response[0]); + else + Log.e(LOG_TAG, "RETRY_MESSAGE didn't get enough params"); + break; + } + } + + protected void sendMessage(Message msg) { + if (getUseSynchronousMode() || handler == null) { + handleMessage(msg); + } else if (!Thread.currentThread().isInterrupted()) { // do not send messages if request has been cancelled + handler.sendMessage(msg); + } + } + + /** + * Helper method to send runnable into local handler loop + * + * @param runnable runnable instance, can be null + */ + protected void postRunnable(Runnable runnable) { + if (runnable != null) { + handler.post(runnable); + } + } + + /** + * Helper method to create Message instance from handler + * + * @param responseMessageId constant to identify Handler message + * @param responseMessageData object to be passed to message receiver + * @return Message instance, should not be null + */ + protected Message obtainMessage(int responseMessageId, Object responseMessageData) { + Message msg; + if (handler != null) { + msg = handler.obtainMessage(responseMessageId, responseMessageData); + } else { + msg = Message.obtain(); + if (msg != null) { + msg.what = responseMessageId; + msg.obj = responseMessageData; + } + } + return msg; + } + + @Override + public void sendResponseMessage(HttpResponse response) throws IOException { + // do not process if request has been cancelled + if (!Thread.currentThread().isInterrupted()) { + StatusLine status = response.getStatusLine(); + byte[] responseBody; + responseBody = getResponseData(response.getEntity()); + // additional cancellation check as getResponseData() can take non-zero time to process + if (!Thread.currentThread().isInterrupted()) { + if (status.getStatusCode() >= 300) { + sendFailureMessage(status.getStatusCode(), response.getAllHeaders(), responseBody, new HttpResponseException(status.getStatusCode(), status.getReasonPhrase())); + } else { + sendSuccessMessage(status.getStatusCode(), response.getAllHeaders(), responseBody); + } + } + } + } + + /** + * Returns byte array of response HttpEntity contents + * + * @param entity can be null + * @return response entity body or null + * @throws java.io.IOException if reading entity or creating byte array failed + */ + byte[] getResponseData(HttpEntity entity) throws IOException { + byte[] responseBody = null; + if (entity != null) { + InputStream instream = entity.getContent(); + if (instream != null) { + long contentLength = entity.getContentLength(); + if (contentLength > Integer.MAX_VALUE) { + throw new IllegalArgumentException("HTTP entity too large to be buffered in memory"); + } + if (contentLength < 0) { + contentLength = BUFFER_SIZE; + } + try { + ByteArrayBuffer buffer = new ByteArrayBuffer((int) contentLength); + try { + byte[] tmp = new byte[BUFFER_SIZE]; + int l, count = 0; + // do not send messages if request has been cancelled + while ((l = instream.read(tmp)) != -1 && !Thread.currentThread().isInterrupted()) { + count += l; + buffer.append(tmp, 0, l); + sendProgressDataMessage(Arrays.copyOfRange(tmp, 0, l)); + } + } finally { + instream.close(); + } + responseBody = buffer.toByteArray(); + } catch (OutOfMemoryError e) { + System.gc(); + throw new IOException("File too large to fit into available memory"); + } + } + } + return responseBody; + } +} + diff --git a/library/src/main/java/com/loopj/android/http/ResponseHandlerInterface.java b/library/src/main/java/com/loopj/android/http/ResponseHandlerInterface.java index d563a20cb..f61fa1122 100644 --- a/library/src/main/java/com/loopj/android/http/ResponseHandlerInterface.java +++ b/library/src/main/java/com/loopj/android/http/ResponseHandlerInterface.java @@ -37,6 +37,10 @@ public interface ResponseHandlerInterface { */ void sendProgressMessage(int bytesWritten, int bytesTotal); + + void sendProgressDataMessage(byte[] responseBody); + + /** * Notifies callback, that request was handled successfully * From 2e0e8644590bd168c797b2b7e3861708c05a5b2f Mon Sep 17 00:00:00 2001 From: mareksebera Date: Sun, 24 Nov 2013 23:25:38 +0100 Subject: [PATCH 216/613] Fixes #394 --- .../com/loopj/android/http/AsyncHttpResponseHandler.java | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/library/src/main/java/com/loopj/android/http/AsyncHttpResponseHandler.java b/library/src/main/java/com/loopj/android/http/AsyncHttpResponseHandler.java index 3d312e26b..fece5b2b6 100644 --- a/library/src/main/java/com/loopj/android/http/AsyncHttpResponseHandler.java +++ b/library/src/main/java/com/loopj/android/http/AsyncHttpResponseHandler.java @@ -179,7 +179,7 @@ public AsyncHttpResponseHandler() { * @param totalSize total size of file */ public void onProgress(int bytesWritten, int totalSize) { - Log.d(LOG_TAG, String.format("Progress %d from %d (%d)", bytesWritten, totalSize, bytesWritten / (totalSize / 100))); + Log.d(LOG_TAG, String.format("Progress %d from %d (%d)", bytesWritten, totalSize, totalSize > 0 ? bytesWritten / (totalSize / 100) : -1)); } /** @@ -370,11 +370,9 @@ byte[] getResponseData(HttpEntity entity) throws IOException { if (contentLength > Integer.MAX_VALUE) { throw new IllegalArgumentException("HTTP entity too large to be buffered in memory"); } - if (contentLength < 0) { - contentLength = BUFFER_SIZE; - } + int buffersize = (contentLength < 0) ? BUFFER_SIZE : (int) contentLength; try { - ByteArrayBuffer buffer = new ByteArrayBuffer((int) contentLength); + ByteArrayBuffer buffer = new ByteArrayBuffer((int) buffersize); try { byte[] tmp = new byte[BUFFER_SIZE]; int l, count = 0; From 1b720a51bfb98463ddb4147a649739447599f492 Mon Sep 17 00:00:00 2001 From: mareksebera Date: Sun, 24 Nov 2013 23:43:10 +0100 Subject: [PATCH 217/613] Fixup for #394 --- .../java/com/loopj/android/http/AsyncHttpResponseHandler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/src/main/java/com/loopj/android/http/AsyncHttpResponseHandler.java b/library/src/main/java/com/loopj/android/http/AsyncHttpResponseHandler.java index fece5b2b6..b16e42137 100644 --- a/library/src/main/java/com/loopj/android/http/AsyncHttpResponseHandler.java +++ b/library/src/main/java/com/loopj/android/http/AsyncHttpResponseHandler.java @@ -179,7 +179,7 @@ public AsyncHttpResponseHandler() { * @param totalSize total size of file */ public void onProgress(int bytesWritten, int totalSize) { - Log.d(LOG_TAG, String.format("Progress %d from %d (%d)", bytesWritten, totalSize, totalSize > 0 ? bytesWritten / (totalSize / 100) : -1)); + Log.d(LOG_TAG, String.format("Progress %d from %d (%d%%)", bytesWritten, totalSize, (totalSize > 0) ? (bytesWritten / totalSize) * 100 : -1)); } /** From 626af0804c64a5c0dbcb057a9f479ed3f9073296 Mon Sep 17 00:00:00 2001 From: mareksebera Date: Sat, 14 Dec 2013 17:07:25 +0100 Subject: [PATCH 218/613] Fixed SDK targeting --- sample/src/main/AndroidManifest.xml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/sample/src/main/AndroidManifest.xml b/sample/src/main/AndroidManifest.xml index 23b6ee77d..cde763770 100644 --- a/sample/src/main/AndroidManifest.xml +++ b/sample/src/main/AndroidManifest.xml @@ -7,8 +7,7 @@ + android:minSdkVersion="3" /> Date: Sat, 14 Dec 2013 17:08:31 +0100 Subject: [PATCH 219/613] Removed hardcoded strings --- sample/src/main/res/layout/parent_layout.xml | 6 +++--- sample/src/main/res/values/strings.xml | 3 +++ 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/sample/src/main/res/layout/parent_layout.xml b/sample/src/main/res/layout/parent_layout.xml index a7ad216e3..f6872a064 100644 --- a/sample/src/main/res/layout/parent_layout.xml +++ b/sample/src/main/res/layout/parent_layout.xml @@ -30,7 +30,7 @@ android:id="@+id/button_run" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:text="Run" /> + android:text="@string/button_run" /> + android:text="@string/label_headers" /> + android:text="@string/label_req_body" /> DELETE
    GET to File GET binary data + Run + Headers (key=val, one per line) + Request body From 816578f806c69d3378ca91fbfd6c3b37ac382755 Mon Sep 17 00:00:00 2001 From: mareksebera Date: Sat, 14 Dec 2013 17:10:43 +0100 Subject: [PATCH 220/613] Throwing exception to fill expectations --- .../com/loopj/android/http/RequestParams.java | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/library/src/main/java/com/loopj/android/http/RequestParams.java b/library/src/main/java/com/loopj/android/http/RequestParams.java index 825132fda..ded71e337 100644 --- a/library/src/main/java/com/loopj/android/http/RequestParams.java +++ b/library/src/main/java/com/loopj/android/http/RequestParams.java @@ -70,7 +70,8 @@ * String[] colors = { "blue", "yellow" }; // Ordered collection * params.put("colors", colors); // url params: "colors[]=blue&colors[]=yellow" * - * List<Map<String, String>> listOfMaps = new ArrayList<Map<String, String>>(); + * List<Map<String, String>> listOfMaps = new ArrayList<Map<String, + * String>>(); * Map<String, String> user1 = new HashMap<String, String>(); * user1.put("age", "30"); * user1.put("gender", "male"); @@ -181,7 +182,10 @@ public void put(String key, File file) throws FileNotFoundException { * @throws java.io.FileNotFoundException throws if wrong File argument was passed */ public void put(String key, File file, String contentType) throws FileNotFoundException { - if (key != null && file != null) { + if (file == null || !file.exists()) { + throw new FileNotFoundException(); + } + if (key != null) { fileParams.put(key, new FileWrapper(file, contentType)); } } @@ -353,9 +357,9 @@ private HttpEntity createJsonStreamerEntity() throws IOException { for (ConcurrentHashMap.Entry entry : fileParams.entrySet()) { FileWrapper fileWrapper = entry.getValue(); entity.addPart(entry.getKey(), - new FileInputStream(fileWrapper.file), - fileWrapper.file.getName(), - fileWrapper.contentType); + new FileInputStream(fileWrapper.file), + fileWrapper.file.getName(), + fileWrapper.contentType); } // Add stream params @@ -363,9 +367,9 @@ private HttpEntity createJsonStreamerEntity() throws IOException { StreamWrapper stream = entry.getValue(); if (stream.inputStream != null) { entity.addPart(entry.getKey(), - stream.inputStream, - stream.name, - stream.contentType); + stream.inputStream, + stream.name, + stream.contentType); } } From 4578068c28ae7162372a4bd4b60fc375e2db1178 Mon Sep 17 00:00:00 2001 From: mareksebera Date: Sat, 14 Dec 2013 17:11:45 +0100 Subject: [PATCH 221/613] Unnecessary casting --- .../java/com/loopj/android/http/AsyncHttpResponseHandler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/src/main/java/com/loopj/android/http/AsyncHttpResponseHandler.java b/library/src/main/java/com/loopj/android/http/AsyncHttpResponseHandler.java index b16e42137..6b6261d64 100644 --- a/library/src/main/java/com/loopj/android/http/AsyncHttpResponseHandler.java +++ b/library/src/main/java/com/loopj/android/http/AsyncHttpResponseHandler.java @@ -372,7 +372,7 @@ byte[] getResponseData(HttpEntity entity) throws IOException { } int buffersize = (contentLength < 0) ? BUFFER_SIZE : (int) contentLength; try { - ByteArrayBuffer buffer = new ByteArrayBuffer((int) buffersize); + ByteArrayBuffer buffer = new ByteArrayBuffer(buffersize); try { byte[] tmp = new byte[BUFFER_SIZE]; int l, count = 0; From 71556bdaa55a7c6bc4eee026c862e6af7c891f4e Mon Sep 17 00:00:00 2001 From: mareksebera Date: Sat, 14 Dec 2013 17:16:05 +0100 Subject: [PATCH 222/613] Possibility to use library on non-UI thread (calling Looper.prepare) #397 --- .../http/AsyncHttpResponseHandler.java | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/library/src/main/java/com/loopj/android/http/AsyncHttpResponseHandler.java b/library/src/main/java/com/loopj/android/http/AsyncHttpResponseHandler.java index 6b6261d64..f3c6945e6 100644 --- a/library/src/main/java/com/loopj/android/http/AsyncHttpResponseHandler.java +++ b/library/src/main/java/com/loopj/android/http/AsyncHttpResponseHandler.java @@ -166,10 +166,8 @@ public String getCharset() { * Creates a new AsyncHttpResponseHandler */ public AsyncHttpResponseHandler() { - // Set up a handler to post events back to the correct thread if possible - if (Looper.myLooper() != null) { - handler = new ResponderHandler(this); - } + // Init Looper by calling postRunnable without argument + postRunnable(null); } /** @@ -310,9 +308,19 @@ protected void sendMessage(Message msg) { * @param runnable runnable instance, can be null */ protected void postRunnable(Runnable runnable) { - if (runnable != null) { + boolean missingLooper = null != Looper.myLooper(); + if (missingLooper) { + Looper.prepare(); + } + if (null == handler) { + handler = new ResponderHandler(this); + } + if (null != runnable) { handler.post(runnable); } + if (missingLooper) { + Looper.loop(); + } } /** @@ -370,7 +378,7 @@ byte[] getResponseData(HttpEntity entity) throws IOException { if (contentLength > Integer.MAX_VALUE) { throw new IllegalArgumentException("HTTP entity too large to be buffered in memory"); } - int buffersize = (contentLength < 0) ? BUFFER_SIZE : (int) contentLength; + int buffersize = (contentLength < 0) ? BUFFER_SIZE : (int) contentLength; try { ByteArrayBuffer buffer = new ByteArrayBuffer(buffersize); try { From a3eb0534cab8f12821d812efeff11f30760d0bb9 Mon Sep 17 00:00:00 2001 From: mareksebera Date: Sat, 14 Dec 2013 17:38:12 +0100 Subject: [PATCH 223/613] Refactoring gone wrong, flipped condition #397 --- .../java/com/loopj/android/http/AsyncHttpResponseHandler.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/src/main/java/com/loopj/android/http/AsyncHttpResponseHandler.java b/library/src/main/java/com/loopj/android/http/AsyncHttpResponseHandler.java index f3c6945e6..a2cb8b3d5 100644 --- a/library/src/main/java/com/loopj/android/http/AsyncHttpResponseHandler.java +++ b/library/src/main/java/com/loopj/android/http/AsyncHttpResponseHandler.java @@ -132,7 +132,7 @@ static class ResponderHandler extends Handler { @Override public void handleMessage(Message msg) { AsyncHttpResponseHandler service = mResponder.get(); - if (service != null) { + if (null != service) { service.handleMessage(msg); } } @@ -308,7 +308,7 @@ protected void sendMessage(Message msg) { * @param runnable runnable instance, can be null */ protected void postRunnable(Runnable runnable) { - boolean missingLooper = null != Looper.myLooper(); + boolean missingLooper = null == Looper.myLooper(); if (missingLooper) { Looper.prepare(); } From 71749b627c0f6fa692bb8e9999e11e5f79ace9b2 Mon Sep 17 00:00:00 2001 From: unknown Date: Tue, 17 Dec 2013 16:28:23 +0100 Subject: [PATCH 224/613] Changed DataAsyncHttpResponseHandler into subclass to minimize changes of existing library. --- .../http/AsyncHttpResponseHandler.java | 3 - .../http/DataAsyncHttpResponseHandler.java | 298 +----------------- .../http/ResponseHandlerInterface.java | 4 - 3 files changed, 8 insertions(+), 297 deletions(-) diff --git a/library/src/main/java/com/loopj/android/http/AsyncHttpResponseHandler.java b/library/src/main/java/com/loopj/android/http/AsyncHttpResponseHandler.java index 36e14fb59..3d312e26b 100644 --- a/library/src/main/java/com/loopj/android/http/AsyncHttpResponseHandler.java +++ b/library/src/main/java/com/loopj/android/http/AsyncHttpResponseHandler.java @@ -227,9 +227,6 @@ final public void sendProgressMessage(int bytesWritten, int bytesTotal) { sendMessage(obtainMessage(PROGRESS_MESSAGE, new Object[]{bytesWritten, bytesTotal})); } - final public void sendProgressDataMessage(byte[] responseBytes) { - } - final public void sendSuccessMessage(int statusCode, Header[] headers, byte[] responseBytes) { sendMessage(obtainMessage(SUCCESS_MESSAGE, new Object[]{statusCode, headers, responseBytes})); } diff --git a/library/src/main/java/com/loopj/android/http/DataAsyncHttpResponseHandler.java b/library/src/main/java/com/loopj/android/http/DataAsyncHttpResponseHandler.java index d900f6a18..69fb48457 100644 --- a/library/src/main/java/com/loopj/android/http/DataAsyncHttpResponseHandler.java +++ b/library/src/main/java/com/loopj/android/http/DataAsyncHttpResponseHandler.java @@ -18,162 +18,26 @@ limitations under the License. */ -import android.os.Handler; -import android.os.Looper; import android.os.Message; import android.util.Log; -import org.apache.http.Header; import org.apache.http.HttpEntity; -import org.apache.http.HttpResponse; -import org.apache.http.StatusLine; -import org.apache.http.client.HttpResponseException; import org.apache.http.util.ByteArrayBuffer; import java.io.IOException; import java.io.InputStream; -import java.lang.ref.WeakReference; -import java.net.URI; import java.util.Arrays; -/** - * Used to intercept and handle the responses from requests made using {@link AsyncHttpClient}. The - * {@link #onSuccess(int, org.apache.http.Header[], byte[])} method is designed to be anonymously - * overridden with your own response handling code.

     

    Additionally, you can override the - * {@link #onFailure(int, org.apache.http.Header[], byte[], Throwable)}, {@link #onStart()}, {@link - * #onFinish()}, {@link #onRetry(int)} and {@link #onProgress(int, int)} methods as required. - *

     

    For example:

     

    - *
    - * AsyncHttpClient client = new AsyncHttpClient();
    - * client.get("http://www.google.com/", new AsyncHttpResponseHandler() {
    - *     @Override
    - *     public void onStart() {
    - *         // Initiated the request
    - *     }
    - *
    - *     @Override
    - *     public void onSuccess(int statusCode, Header[] headers, byte[] responseBody) {
    - *         // Successfully got a response
    - *     }
    - *
    - *     @Override
    - *     public void onFailure(int statusCode, Header[] headers, byte[] responseBody, Throwable
    - * error)
    - * {
    - *         // Response failed :(
    - *     }
    - *
    - *     @Override
    - *     public void onRetry() {
    - *         // Request was retried
    - *     }
    - *
    - *     @Override
    - *     public void onProgress(int bytesWritten, int totalSize) {
    - *         // Progress notification
    - *     }
    - *
    - *     @Override
    - *     public void onFinish() {
    - *         // Completed the request (either success or failure)
    - *     }
    - * });
    - * 
    - */ -/** - * Created by vkr on 14.11.13. - */ -public abstract class DataAsyncHttpResponseHandler implements ResponseHandlerInterface { - private static final String LOG_TAG = "AsyncHttpResponseHandler"; +public abstract class DataAsyncHttpResponseHandler extends AsyncHttpResponseHandler { + private static final String LOG_TAG = "DataAsyncHttpResponseHandler"; - protected static final int SUCCESS_MESSAGE = 0; - protected static final int FAILURE_MESSAGE = 1; - protected static final int START_MESSAGE = 2; - protected static final int FINISH_MESSAGE = 3; - protected static final int PROGRESS_DATA_MESSAGE = 4; - protected static final int RETRY_MESSAGE = 5; - - protected static final int BUFFER_SIZE = 4096; - - private Handler handler; - public static final String DEFAULT_CHARSET = "UTF-8"; - private String responseCharset = DEFAULT_CHARSET; - private Boolean useSynchronousMode = false; - - private URI requestURI = null; - private Header[] requestHeaders = null; - - @Override - public URI getRequestURI() { - return this.requestURI; - } - - @Override - public Header[] getRequestHeaders() { - return this.requestHeaders; - } - - @Override - public void setRequestURI(URI requestURI) { - this.requestURI = requestURI; - } - - @Override - public void setRequestHeaders(Header[] requestHeaders) { - this.requestHeaders = requestHeaders; - } - - /** - * Avoid leaks by using a non-anonymous handler class with a weak reference - */ - static class ResponderHandler extends Handler { - private final WeakReference mResponder; - - ResponderHandler(DataAsyncHttpResponseHandler service) { - mResponder = new WeakReference(service); - } - - @Override - public void handleMessage(Message msg) { - DataAsyncHttpResponseHandler service = mResponder.get(); - if (service != null) { - service.handleMessage(msg); - } - } - } - - @Override - public boolean getUseSynchronousMode() { - return useSynchronousMode; - } - - @Override - public void setUseSynchronousMode(boolean value) { - useSynchronousMode = value; - } - - /** - * Sets the charset for the response string. If not set, the default is UTF-8. - * - * @param charset to be used for the response string. - * @see Charset - */ - public void setCharset(final String charset) { - this.responseCharset = charset; - } - - public String getCharset() { - return this.responseCharset == null ? DEFAULT_CHARSET : this.responseCharset; - } + protected static final int PROGRESS_DATA_MESSAGE = 6; /** * Creates a new AsyncHttpResponseHandler */ public DataAsyncHttpResponseHandler() { - // Set up a handler to post events back to the correct thread if possible - if (Looper.myLooper() != null) { - handler = new ResponderHandler(this); - } + super(); } /** @@ -184,101 +48,18 @@ public DataAsyncHttpResponseHandler() { public void onProgressData(byte[] responseBody) { } - /** - * Fired when the request is started, override to handle in your own code - */ - public void onStart() { - } - - /** - * Fired in all cases when the request is finished, after both success and failure, override to - * handle in your own code - */ - public void onFinish() { - } - - /** - * Fired when a request returns successfully, override to handle in your own code - * - * @param statusCode the status code of the response - * @param headers return headers, if any - * @param responseBody the body of the HTTP response from the server - */ - public abstract void onSuccess(int statusCode, Header[] headers, byte[] responseBody); - - /** - * Fired when a request fails to complete, override to handle in your own code - * - * @param statusCode return HTTP status code - * @param headers return headers, if any - * @param responseBody the response body, if any - * @param error the underlying cause of the failure - */ - public abstract void onFailure(int statusCode, Header[] headers, byte[] responseBody, Throwable error); - - /** - * Fired when a retry occurs, override to handle in your own code - * - * @param retryNo number of retry - */ - public void onRetry(int retryNo) { - Log.d(LOG_TAG, String.format("Request retry no. %d", retryNo)); - } - - final public void sendProgressMessage(int bytesWritten, int bytesTotal) { - } final public void sendProgressDataMessage(byte[] responseBytes) { sendMessage(obtainMessage(PROGRESS_DATA_MESSAGE, new Object[]{responseBytes})); } - final public void sendSuccessMessage(int statusCode, Header[] headers, byte[] responseBytes) { - sendMessage(obtainMessage(SUCCESS_MESSAGE, new Object[]{statusCode, headers, responseBytes})); - } - - final public void sendFailureMessage(int statusCode, Header[] headers, byte[] responseBody, Throwable throwable) { - sendMessage(obtainMessage(FAILURE_MESSAGE, new Object[]{statusCode, headers, responseBody, throwable})); - } - - final public void sendStartMessage() { - sendMessage(obtainMessage(START_MESSAGE, null)); - } - - final public void sendFinishMessage() { - sendMessage(obtainMessage(FINISH_MESSAGE, null)); - } - - final public void sendRetryMessage(int retryNo) { - sendMessage(obtainMessage(RETRY_MESSAGE, new Object[]{retryNo})); - } - // Methods which emulate android's Handler and Message methods + @Override protected void handleMessage(Message message) { + super.handleMessage(message); Object[] response; switch (message.what) { - case SUCCESS_MESSAGE: - response = (Object[]) message.obj; - if (response != null && response.length >= 3) { - onSuccess((Integer) response[0], (Header[]) response[1], (byte[]) response[2]); - } else { - Log.e(LOG_TAG, "SUCCESS_MESSAGE didn't got enough params"); - } - break; - case FAILURE_MESSAGE: - response = (Object[]) message.obj; - if (response != null && response.length >= 4) { - onFailure((Integer) response[0], (Header[]) response[1], (byte[]) response[2], (Throwable) response[3]); - } else { - Log.e(LOG_TAG, "FAILURE_MESSAGE didn't got enough params"); - } - break; - case START_MESSAGE: - onStart(); - break; - case FINISH_MESSAGE: - onFinish(); - break; case PROGRESS_DATA_MESSAGE: response = (Object[]) message.obj; if (response != null && response.length >= 1) { @@ -291,71 +72,6 @@ protected void handleMessage(Message message) { Log.e(LOG_TAG, "PROGRESS_DATA_MESSAGE didn't got enough params"); } break; - case RETRY_MESSAGE: - response = (Object[]) message.obj; - if (response != null && response.length == 1) - onRetry((Integer) response[0]); - else - Log.e(LOG_TAG, "RETRY_MESSAGE didn't get enough params"); - break; - } - } - - protected void sendMessage(Message msg) { - if (getUseSynchronousMode() || handler == null) { - handleMessage(msg); - } else if (!Thread.currentThread().isInterrupted()) { // do not send messages if request has been cancelled - handler.sendMessage(msg); - } - } - - /** - * Helper method to send runnable into local handler loop - * - * @param runnable runnable instance, can be null - */ - protected void postRunnable(Runnable runnable) { - if (runnable != null) { - handler.post(runnable); - } - } - - /** - * Helper method to create Message instance from handler - * - * @param responseMessageId constant to identify Handler message - * @param responseMessageData object to be passed to message receiver - * @return Message instance, should not be null - */ - protected Message obtainMessage(int responseMessageId, Object responseMessageData) { - Message msg; - if (handler != null) { - msg = handler.obtainMessage(responseMessageId, responseMessageData); - } else { - msg = Message.obtain(); - if (msg != null) { - msg.what = responseMessageId; - msg.obj = responseMessageData; - } - } - return msg; - } - - @Override - public void sendResponseMessage(HttpResponse response) throws IOException { - // do not process if request has been cancelled - if (!Thread.currentThread().isInterrupted()) { - StatusLine status = response.getStatusLine(); - byte[] responseBody; - responseBody = getResponseData(response.getEntity()); - // additional cancellation check as getResponseData() can take non-zero time to process - if (!Thread.currentThread().isInterrupted()) { - if (status.getStatusCode() >= 300) { - sendFailureMessage(status.getStatusCode(), response.getAllHeaders(), responseBody, new HttpResponseException(status.getStatusCode(), status.getReasonPhrase())); - } else { - sendSuccessMessage(status.getStatusCode(), response.getAllHeaders(), responseBody); - } - } } } @@ -366,7 +82,9 @@ public void sendResponseMessage(HttpResponse response) throws IOException { * @return response entity body or null * @throws java.io.IOException if reading entity or creating byte array failed */ + @Override byte[] getResponseData(HttpEntity entity) throws IOException { + byte[] responseBody = null; if (entity != null) { InputStream instream = entity.getContent(); diff --git a/library/src/main/java/com/loopj/android/http/ResponseHandlerInterface.java b/library/src/main/java/com/loopj/android/http/ResponseHandlerInterface.java index f61fa1122..d563a20cb 100644 --- a/library/src/main/java/com/loopj/android/http/ResponseHandlerInterface.java +++ b/library/src/main/java/com/loopj/android/http/ResponseHandlerInterface.java @@ -37,10 +37,6 @@ public interface ResponseHandlerInterface { */ void sendProgressMessage(int bytesWritten, int bytesTotal); - - void sendProgressDataMessage(byte[] responseBody); - - /** * Notifies callback, that request was handled successfully * From 81f4402e68b8438b6b9e383d1897b4a613c39f9c Mon Sep 17 00:00:00 2001 From: mareksebera Date: Tue, 24 Dec 2013 13:52:48 +0100 Subject: [PATCH 225/613] Fixed debug build, simplified sonatype for release only and updated gradle to 1.9 (gradle build tools 7.+ --- build.gradle | 2 +- library/build.gradle | 4 ++++ maven_push.gradle | 3 ++- sample/build.gradle | 12 +++++++++++- 4 files changed, 18 insertions(+), 3 deletions(-) diff --git a/build.gradle b/build.gradle index 868085567..5f3653e40 100644 --- a/build.gradle +++ b/build.gradle @@ -4,7 +4,7 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:0.6.+' + classpath 'com.android.tools.build:gradle:0.7.+' } } diff --git a/library/build.gradle b/library/build.gradle index 7cd3e2af7..5ac1d7e9f 100644 --- a/library/build.gradle +++ b/library/build.gradle @@ -3,6 +3,10 @@ apply plugin: 'android-library' android { compileSdkVersion 19 buildToolsVersion '19.0.0' + + lintOptions { + abortOnError false + } } android.libraryVariants.all { variant -> diff --git a/maven_push.gradle b/maven_push.gradle index 6fc6fb30f..7131da650 100644 --- a/maven_push.gradle +++ b/maven_push.gradle @@ -15,7 +15,7 @@ if (isReleaseBuild()) { println 'DEBUG BUILD' sonatypeRepositoryUrl = "https://oss.sonatype.org/content/repositories/snapshots/" } - +if(isReleaseBuild()){ if (ext.properties.containsKey('signing.keyId') && !ext.properties.containsKey('signing.password')) { if (System.console()) ext.set('signing.password', System.console().readPassword("\n\$ Type in GPG key password: ")) @@ -98,3 +98,4 @@ afterEvaluate { project -> archives androidJavadocsJar } } +} diff --git a/sample/build.gradle b/sample/build.gradle index 0792285ef..57c4aada3 100644 --- a/sample/build.gradle +++ b/sample/build.gradle @@ -3,7 +3,7 @@ buildscript { mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:0.6.+' + classpath 'com.android.tools.build:gradle:0.7.+' } } apply plugin: 'android' @@ -23,6 +23,16 @@ android { minSdkVersion 3 targetSdkVersion 19 } + + lintOptions { + abortOnError false + } + + packagingOptions { + exclude 'META-INF/DEPENDENCIES' + exclude 'META-INF/LICENSE' + exclude 'META-INF/NOTICE' + } } dependencies { From 835b5f5ba1c42916b55405b36cd8805e32dddcc1 Mon Sep 17 00:00:00 2001 From: mareksebera Date: Tue, 24 Dec 2013 14:05:13 +0100 Subject: [PATCH 226/613] Fixed Lint about formatting strings --- .../com/loopj/android/http/sample/SampleParentActivity.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/sample/src/main/java/com/loopj/android/http/sample/SampleParentActivity.java b/sample/src/main/java/com/loopj/android/http/sample/SampleParentActivity.java index eae9011ba..2592ee81a 100644 --- a/sample/src/main/java/com/loopj/android/http/sample/SampleParentActivity.java +++ b/sample/src/main/java/com/loopj/android/http/sample/SampleParentActivity.java @@ -24,6 +24,7 @@ import java.io.UnsupportedEncodingException; import java.util.ArrayList; import java.util.List; +import java.util.Locale; public abstract class SampleParentActivity extends Activity { @@ -109,7 +110,7 @@ protected final void debugHeaders(String TAG, Header[] headers) { Log.d(TAG, "Return Headers:"); StringBuilder builder = new StringBuilder(); for (Header h : headers) { - String _h = String.format("%s : %s", h.getName(), h.getValue()); + String _h = String.format(Locale.US, "%s : %s", h.getName(), h.getValue()); Log.d(TAG, _h); builder.append(_h); builder.append("\n"); @@ -143,7 +144,7 @@ protected final void debugResponse(String TAG, String response) { } protected final void debugStatusCode(String TAG, int statusCode) { - String msg = String.format("Return Status Code: %d", statusCode); + String msg = String.format(Locale.US, "Return Status Code: %d", statusCode); Log.d(TAG, msg); addView(getColoredView(LIGHTBLUE, msg)); } From 1292fef9711d969fa4d013ddd06b17192bdc0f66 Mon Sep 17 00:00:00 2001 From: mareksebera Date: Tue, 24 Dec 2013 14:10:26 +0100 Subject: [PATCH 227/613] Fixed lint about toUpperCase, and backported Arrays.copyOfRange(byte[],int,int) for API < 9 --- .../http/DataAsyncHttpResponseHandler.java | 40 +++++++++++++++---- .../android/http/JsonStreamerEntity.java | 3 +- .../android/http/PersistentCookieStore.java | 3 +- 3 files changed, 37 insertions(+), 9 deletions(-) diff --git a/library/src/main/java/com/loopj/android/http/DataAsyncHttpResponseHandler.java b/library/src/main/java/com/loopj/android/http/DataAsyncHttpResponseHandler.java index 69fb48457..dc35b42da 100644 --- a/library/src/main/java/com/loopj/android/http/DataAsyncHttpResponseHandler.java +++ b/library/src/main/java/com/loopj/android/http/DataAsyncHttpResponseHandler.java @@ -26,7 +26,6 @@ import java.io.IOException; import java.io.InputStream; -import java.util.Arrays; public abstract class DataAsyncHttpResponseHandler extends AsyncHttpResponseHandler { private static final String LOG_TAG = "DataAsyncHttpResponseHandler"; @@ -42,8 +41,6 @@ public DataAsyncHttpResponseHandler() { /** * Fired when the request progress, override to handle in your own code - * - * @param responseBody */ public void onProgressData(byte[] responseBody) { } @@ -64,7 +61,7 @@ protected void handleMessage(Message message) { response = (Object[]) message.obj; if (response != null && response.length >= 1) { try { - onProgressData((byte[])response[0]); + onProgressData((byte[]) response[0]); } catch (Throwable t) { Log.e(LOG_TAG, "custom onProgressData contains an error", t); } @@ -100,12 +97,11 @@ byte[] getResponseData(HttpEntity entity) throws IOException { ByteArrayBuffer buffer = new ByteArrayBuffer((int) contentLength); try { byte[] tmp = new byte[BUFFER_SIZE]; - int l, count = 0; + int l; // do not send messages if request has been cancelled while ((l = instream.read(tmp)) != -1 && !Thread.currentThread().isInterrupted()) { - count += l; buffer.append(tmp, 0, l); - sendProgressDataMessage(Arrays.copyOfRange(tmp, 0, l)); + sendProgressDataMessage(copyOfRange(tmp, 0, l)); } } finally { instream.close(); @@ -119,5 +115,35 @@ byte[] getResponseData(HttpEntity entity) throws IOException { } return responseBody; } + + /** + * Copies elements from {@code original} into a new array, from indexes start (inclusive) to end + * (exclusive). The original order of elements is preserved. If {@code end} is greater than + * {@code original.length}, the result is padded with the value {@code (byte) 0}. + * + * @param original the original array + * @param start the start index, inclusive + * @param end the end index, exclusive + * @return the new array + * @throws ArrayIndexOutOfBoundsException if {@code start < 0 || start > original.length} + * @throws IllegalArgumentException if {@code start > end} + * @throws NullPointerException if {@code original == null} + * @see java.util.Arrays + * @since 1.6 + */ + public static byte[] copyOfRange(byte[] original, int start, int end) throws ArrayIndexOutOfBoundsException, IllegalArgumentException, NullPointerException { + if (start > end) { + throw new IllegalArgumentException(); + } + int originalLength = original.length; + if (start < 0 || start > originalLength) { + throw new ArrayIndexOutOfBoundsException(); + } + int resultLength = end - start; + int copyLength = Math.min(resultLength, originalLength - start); + byte[] result = new byte[resultLength]; + System.arraycopy(original, start, result, 0, copyLength); + return result; + } } diff --git a/library/src/main/java/com/loopj/android/http/JsonStreamerEntity.java b/library/src/main/java/com/loopj/android/http/JsonStreamerEntity.java index 181019e75..493750617 100644 --- a/library/src/main/java/com/loopj/android/http/JsonStreamerEntity.java +++ b/library/src/main/java/com/loopj/android/http/JsonStreamerEntity.java @@ -29,6 +29,7 @@ import java.io.InputStream; import java.io.OutputStream; import java.util.HashMap; +import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.zip.GZIPOutputStream; @@ -297,7 +298,7 @@ static byte[] escape(String string) { for (int zero = 0; zero < intLength; zero++) { BUILDER.append('0'); } - BUILDER.append(intString.toUpperCase()); + BUILDER.append(intString.toUpperCase(Locale.US)); } else { BUILDER.append(ch); } diff --git a/library/src/main/java/com/loopj/android/http/PersistentCookieStore.java b/library/src/main/java/com/loopj/android/http/PersistentCookieStore.java index d7766910e..ee87bd239 100644 --- a/library/src/main/java/com/loopj/android/http/PersistentCookieStore.java +++ b/library/src/main/java/com/loopj/android/http/PersistentCookieStore.java @@ -33,6 +33,7 @@ import java.util.ArrayList; import java.util.Date; import java.util.List; +import java.util.Locale; import java.util.concurrent.ConcurrentHashMap; /** @@ -201,7 +202,7 @@ protected String byteArrayToHexString(byte[] bytes) { } sb.append(Integer.toHexString(v)); } - return sb.toString().toUpperCase(); + return sb.toString().toUpperCase(Locale.US); } /** From 95258be39721ea4c1076990ad3e7cc7af5133fff Mon Sep 17 00:00:00 2001 From: mareksebera Date: Tue, 24 Dec 2013 14:16:33 +0100 Subject: [PATCH 228/613] Updated Gradle in Travis CI build config --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 40336a55d..e1bcf1840 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,7 +16,7 @@ before_install: # for gradle output style - export TERM=dumb # newer version of gradle - - wget http://services.gradle.org/distributions/gradle-1.8-bin.zip + - wget http://services.gradle.org/distributions/gradle-1.9-bin.zip - unzip -qq gradle-1.8-bin.zip - export GRADLE_HOME=$PWD/gradle-1.8 - export PATH=$GRADLE_HOME/bin:$PATH From cd1855f2c4e204a7cffd65ea755a5eaa700ee3c6 Mon Sep 17 00:00:00 2001 From: mareksebera Date: Tue, 24 Dec 2013 14:24:45 +0100 Subject: [PATCH 229/613] Provided setter for encoding in RequestParams, closes #406 --- .../com/loopj/android/http/RequestParams.java | 24 ++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/library/src/main/java/com/loopj/android/http/RequestParams.java b/library/src/main/java/com/loopj/android/http/RequestParams.java index ded71e337..42ba1706f 100644 --- a/library/src/main/java/com/loopj/android/http/RequestParams.java +++ b/library/src/main/java/com/loopj/android/http/RequestParams.java @@ -18,6 +18,8 @@ package com.loopj.android.http; +import android.util.Log; + import org.apache.http.HttpEntity; import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.utils.URLEncodedUtils; @@ -88,12 +90,27 @@ */ public class RequestParams { + protected final static String LOG_TAG = "RequestParams"; protected boolean isRepeatable; protected boolean useJsonStreamer; protected ConcurrentHashMap urlParams; protected ConcurrentHashMap streamParams; protected ConcurrentHashMap fileParams; protected ConcurrentHashMap urlParamsWithObjects; + protected String contentEncoding = HTTP.UTF_8; + + /** + * Sets content encoding for return value of {@link #getParamString()} and {@link + * #createFormEntity()}

     

    Default encoding is "UTF-8" + * + * @param encoding String constant from {@link org.apache.http.protocol.HTTP} + */ + public void setContentEncoding(final String encoding) { + if (encoding != null) + this.contentEncoding = encoding; + else + Log.d(LOG_TAG, "setContentEncoding called with null attribute"); + } /** * Constructs a new empty {@code RequestParams} instance. @@ -378,9 +395,10 @@ private HttpEntity createJsonStreamerEntity() throws IOException { private HttpEntity createFormEntity() { try { - return new UrlEncodedFormEntity(getParamsList(), HTTP.UTF_8); + return new UrlEncodedFormEntity(getParamsList(), contentEncoding); } catch (UnsupportedEncodingException e) { - return null; // Actually cannot happen when using utf-8 + Log.e(LOG_TAG, "createFormEntity failed", e); + return null; // Can happen, if the 'contentEncoding' won't be HTTP.UTF_8 } } @@ -472,7 +490,7 @@ private List getParamsList(String key, Object value) { } protected String getParamString() { - return URLEncodedUtils.format(getParamsList(), HTTP.UTF_8); + return URLEncodedUtils.format(getParamsList(), contentEncoding); } public static class FileWrapper { From 71cb695e70085dcb6d32ccc30c6e5cbd623012f6 Mon Sep 17 00:00:00 2001 From: mareksebera Date: Tue, 24 Dec 2013 14:30:35 +0100 Subject: [PATCH 230/613] sorry for spamming, this should finally fix Travis --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index e1bcf1840..9ae67ada6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -17,8 +17,8 @@ before_install: - export TERM=dumb # newer version of gradle - wget http://services.gradle.org/distributions/gradle-1.9-bin.zip - - unzip -qq gradle-1.8-bin.zip - - export GRADLE_HOME=$PWD/gradle-1.8 + - unzip -qq gradle-1.9-bin.zip + - export GRADLE_HOME=$PWD/gradle-1.9 - export PATH=$GRADLE_HOME/bin:$PATH # just to test gradle version, against our provided one - gradle -v From 9d68452cff5616107d1ebd24d8d94d40e89ec736 Mon Sep 17 00:00:00 2001 From: Shaleen Jain Date: Thu, 26 Dec 2013 00:19:57 +0530 Subject: [PATCH 231/613] Update MySSLSocketFactory.java --- .../android/http/MySSLSocketFactory.java | 160 ++++++++++++++---- 1 file changed, 131 insertions(+), 29 deletions(-) diff --git a/library/src/main/java/com/loopj/android/http/MySSLSocketFactory.java b/library/src/main/java/com/loopj/android/http/MySSLSocketFactory.java index 03115afc3..4ea141523 100644 --- a/library/src/main/java/com/loopj/android/http/MySSLSocketFactory.java +++ b/library/src/main/java/com/loopj/android/http/MySSLSocketFactory.java @@ -1,18 +1,37 @@ package com.loopj.android.http; -import org.apache.http.conn.ssl.SSLSocketFactory; - +import java.io.BufferedInputStream; import java.io.IOException; +import java.io.InputStream; import java.net.Socket; +import java.net.UnknownHostException; import java.security.KeyManagementException; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.UnrecoverableKeyException; - +import java.security.cert.Certificate; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; + +import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.SSLContext; import javax.net.ssl.TrustManager; import javax.net.ssl.X509TrustManager; + +import org.apache.http.HttpVersion; +import org.apache.http.conn.ClientConnectionManager; +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.impl.client.DefaultHttpClient; +import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager; +import org.apache.http.params.BasicHttpParams; +import org.apache.http.params.HttpParams; +import org.apache.http.params.HttpProtocolParams; +import org.apache.http.protocol.HTTP; /** * This file is introduced to fix HTTPS Post bug on API < ICS see @@ -22,42 +41,94 @@ public class MySSLSocketFactory extends SSLSocketFactory { SSLContext sslContext = SSLContext.getInstance("TLS"); - public MySSLSocketFactory(KeyStore truststore) - throws NoSuchAlgorithmException, KeyManagementException, - KeyStoreException, UnrecoverableKeyException { + /** + * Creates a new SSL Socket Factory with the given KeyStore. + * + * @param truststore A KeyStore to create the SSL Socket Factory in context of + * @throws NoSuchAlgorithmException + * @throws KeyManagementException + * @throws KeyStoreException + * @throws UnrecoverableKeyException + */ + public MySSLSocketFactory(KeyStore truststore) throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException { super(truststore); - - TrustManager tm = new X509TrustManager() { - public java.security.cert.X509Certificate[] getAcceptedIssuers() { - return null; + + X509TrustManager tm = new X509TrustManager() { + public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { } - - @Override - public void checkClientTrusted( - java.security.cert.X509Certificate[] chain, String authType) - throws java.security.cert.CertificateException { + + public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { } - - @Override - public void checkServerTrusted( - java.security.cert.X509Certificate[] chain, String authType) - throws java.security.cert.CertificateException { + + public X509Certificate[] getAcceptedIssuers() { + return null; } }; - sslContext.init(null, new TrustManager[]{tm}, null); + + sslContext.init(null, new TrustManager[] { tm }, null); } - - @Override - public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException { + + public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException, UnknownHostException { return sslContext.getSocketFactory().createSocket(socket, host, port, autoClose); } - + @Override public Socket createSocket() throws IOException { return sslContext.getSocketFactory().createSocket(); + } + + /** + * Makes HttpsURLConnection trusts a set of certificates specified by the KeyStore + */ + public void fixHttpsURLConnection() { + HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.getSocketFactory()); } - - public static KeyStore getKeystore() { + + /** + * Gets a KeyStore containing the Certificate + * + * @param cert InputStream of the Certificate + * @return KeyStore + */ + public static KeyStore getKeystoreOfCA(InputStream cert) { + + // Load CAs from an InputStream + InputStream caInput = null; + Certificate ca = null; + try { + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + caInput = new BufferedInputStream(cert); + ca = (Certificate) cf.generateCertificate(caInput); + } catch (CertificateException e1) { + e1.printStackTrace(); + } finally { + try { + caInput.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + // Create a KeyStore containing our trusted CAs + String keyStoreType = KeyStore.getDefaultType(); + KeyStore keyStore = null; + try { + keyStore = KeyStore.getInstance(keyStoreType); + keyStore.load(null, null); + keyStore.setCertificateEntry("ca", + (java.security.cert.Certificate) ca); + } catch (Exception e) { + e.printStackTrace(); + } + return keyStore; + } + + /** + * Gets a Default KeyStore + * + * @return KeyStore + */ + public static KeyStore getKeystore() { KeyStore trustStore = null; try { trustStore = KeyStore.getInstance(KeyStore.getDefaultType()); @@ -67,7 +138,12 @@ public static KeyStore getKeystore() { } return trustStore; } - + + /** + * Returns a SSlSocketFactory which trusts all certificates + * + * @return + */ public static SSLSocketFactory getFixedSocketFactory() { SSLSocketFactory socketFactory; try { @@ -79,5 +155,31 @@ public static SSLSocketFactory getFixedSocketFactory() { } return socketFactory; } + + /** + * Gets a DefaultHttpClient which trusts a set of certificates specified by the KeyStore + * + * @param keyStore + * @return + */ + public static DefaultHttpClient getNewHttpClient(KeyStore keyStore) { + + try { + SSLSocketFactory sf = new MySSLSocketFactory(keyStore); + SchemeRegistry registry = new SchemeRegistry(); + registry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80)); + registry.register(new Scheme("https", sf, 443)); + + HttpParams params = new BasicHttpParams(); + HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1); + HttpProtocolParams.setContentCharset(params, HTTP.UTF_8); + + ClientConnectionManager ccm = new ThreadSafeClientConnManager(params, registry); + + return new DefaultHttpClient(ccm, params); + } catch (Exception e) { + return new DefaultHttpClient(); + } + } -} \ No newline at end of file +} From bc6fd28a016e380c49e2a6a1d288dca96195877a Mon Sep 17 00:00:00 2001 From: realxu Date: Sat, 28 Dec 2013 14:20:21 +0800 Subject: [PATCH 232/613] fix bug: upload more than 2 files in a single post request will be failed --- .../main/java/com/loopj/android/http/SimpleMultipartEntity.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/src/main/java/com/loopj/android/http/SimpleMultipartEntity.java b/library/src/main/java/com/loopj/android/http/SimpleMultipartEntity.java index ef2d42ecf..7d134599f 100644 --- a/library/src/main/java/com/loopj/android/http/SimpleMultipartEntity.java +++ b/library/src/main/java/com/loopj/android/http/SimpleMultipartEntity.java @@ -190,7 +190,7 @@ private byte[] createHeader(String key, String filename, String type) { } public long getTotalLength() { - long streamLength = file.length(); + long streamLength = file.length() + CR_LF.length; return header.length + streamLength; } From 2bc4b727f655ddc89e803d750cd1af1074dc25a1 Mon Sep 17 00:00:00 2001 From: mareksebera Date: Sat, 28 Dec 2013 23:14:06 +0100 Subject: [PATCH 233/613] Modified cancel function, thanks to @douo, https://github.com/douo/android-async-http/commits/cancel-ability --- .../loopj/android/http/AsyncHttpClient.java | 37 +++++----- .../loopj/android/http/AsyncHttpRequest.java | 73 +++++++++++++++---- .../http/AsyncHttpResponseHandler.java | 12 +++ .../com/loopj/android/http/RequestHandle.java | 24 ++++-- .../http/ResponseHandlerInterface.java | 5 ++ 5 files changed, 114 insertions(+), 37 deletions(-) diff --git a/library/src/main/java/com/loopj/android/http/AsyncHttpClient.java b/library/src/main/java/com/loopj/android/http/AsyncHttpClient.java index 24f409e28..dc3f9c100 100644 --- a/library/src/main/java/com/loopj/android/http/AsyncHttpClient.java +++ b/library/src/main/java/com/loopj/android/http/AsyncHttpClient.java @@ -63,15 +63,14 @@ import java.io.IOException; import java.io.InputStream; -import java.lang.ref.WeakReference; import java.util.HashMap; +import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.WeakHashMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; -import java.util.concurrent.Future; import java.util.concurrent.ThreadPoolExecutor; import java.util.zip.GZIPInputStream; @@ -109,7 +108,7 @@ public class AsyncHttpClient { private final DefaultHttpClient httpClient; private final HttpContext httpContext; private ExecutorService threadPool; - private final Map>>> requestMap; + private final Map> requestMap; private final Map clientHeaderMap; private boolean isUrlEncodingEnabled = true; @@ -211,7 +210,7 @@ public AsyncHttpClient(SchemeRegistry schemeRegistry) { ThreadSafeClientConnManager cm = new ThreadSafeClientConnManager(httpParams, schemeRegistry); threadPool = Executors.newCachedThreadPool(); - requestMap = new WeakHashMap>>>(); + requestMap = new WeakHashMap>(); clientHeaderMap = new HashMap(); httpContext = new SyncBasicHttpContext(new BasicHttpContext()); @@ -488,16 +487,13 @@ public void clearBasicAuth() { * pending requests. */ public void cancelRequests(Context context, boolean mayInterruptIfRunning) { - List>> requestList = requestMap.get(context); + List requestList = requestMap.get(context); if (requestList != null) { - for (WeakReference> requestRef : requestList) { - Future request = requestRef.get(); - if (request != null) { - request.cancel(mayInterruptIfRunning); - } + for (RequestHandle requestHandle : requestList) { + requestHandle.cancel(mayInterruptIfRunning); } + requestMap.remove(context); } - requestMap.remove(context); } // [+] HTTP HEAD @@ -897,22 +893,29 @@ protected RequestHandle sendRequest(DefaultHttpClient client, HttpContext httpCo responseHandler.setRequestHeaders(uriRequest.getAllHeaders()); responseHandler.setRequestURI(uriRequest.getURI()); - Future request = threadPool.submit(new AsyncHttpRequest(client, httpContext, uriRequest, responseHandler)); + AsyncHttpRequest request = new AsyncHttpRequest(client, httpContext, uriRequest, responseHandler); + threadPool.submit(request); + RequestHandle requestHandle = new RequestHandle(request); if (context != null) { // Add request to request map - List>> requestList = requestMap.get(context); + List requestList = requestMap.get(context); if (requestList == null) { - requestList = new LinkedList>>(); + requestList = new LinkedList(); requestMap.put(context, requestList); } - requestList.add(new WeakReference>(request)); + requestList.add(requestHandle); - // TODO: Remove dead weakrefs from requestLists? + Iterator iterator = requestList.iterator(); + while (iterator.hasNext()) { + if (iterator.next().shouldBeGarbageCollected()) { + iterator.remove(); + } + } } - return new RequestHandle(request); + return requestHandle; } /** diff --git a/library/src/main/java/com/loopj/android/http/AsyncHttpRequest.java b/library/src/main/java/com/loopj/android/http/AsyncHttpRequest.java index 55791f18a..b9dc45cab 100644 --- a/library/src/main/java/com/loopj/android/http/AsyncHttpRequest.java +++ b/library/src/main/java/com/loopj/android/http/AsyncHttpRequest.java @@ -39,6 +39,9 @@ class AsyncHttpRequest implements Runnable { private final HttpUriRequest request; private final ResponseHandlerInterface responseHandler; private int executionCount; + private boolean isCancelled = false; + private boolean cancelIsNotified = false; + private boolean isFinished = false; public AsyncHttpRequest(AbstractHttpClient client, HttpContext context, HttpUriRequest request, ResponseHandlerInterface responseHandler) { this.client = client; @@ -49,40 +52,53 @@ public AsyncHttpRequest(AbstractHttpClient client, HttpContext context, HttpUriR @Override public void run() { + if (isCancelled()) { + return; + } + if (responseHandler != null) { responseHandler.sendStartMessage(); } + if (isCancelled()) { + return; + } + try { makeRequestWithRetries(); } catch (IOException e) { - if (responseHandler != null) { + if (!isCancelled() && responseHandler != null) { responseHandler.sendFailureMessage(0, null, null, e); } else { Log.e("AsyncHttpRequest", "makeRequestWithRetries returned error, but handler is null", e); } } + if (isCancelled()) { + return; + } + if (responseHandler != null) { responseHandler.sendFinishMessage(); } + + isFinished = true; } private void makeRequest() throws IOException { - if (!Thread.currentThread().isInterrupted()) { - // Fixes #115 - if (request.getURI().getScheme() == null) { - // subclass of IOException so processed in the caller - throw new MalformedURLException("No valid URI scheme was provided"); - } + if (isCancelled()) { + return; + } + // Fixes #115 + if (request.getURI().getScheme() == null) { + // subclass of IOException so processed in the caller + throw new MalformedURLException("No valid URI scheme was provided"); + } - HttpResponse response = client.execute(request, context); + HttpResponse response = client.execute(request, context); - if (!Thread.currentThread().isInterrupted()) { - if (responseHandler != null) { - responseHandler.sendResponseMessage(response); - } - } + if (!isCancelled() && responseHandler != null) { + responseHandler.sendResponseMessage(response); } } @@ -108,6 +124,10 @@ private void makeRequestWithRetries() throws IOException { cause = new IOException("NPE in HttpClient: " + e.getMessage()); retry = retryHandler.retryRequest(cause, ++executionCount, context); } catch (IOException e) { + if (isCancelled()) { + // Eating exception, as the request was cancelled + return; + } cause = e; retry = retryHandler.retryRequest(cause, ++executionCount, context); } @@ -124,4 +144,31 @@ private void makeRequestWithRetries() throws IOException { // cleaned up to throw IOException throw (cause); } + + public boolean isCancelled() { + if (isCancelled) { + sendCancelNotification(); + } + return isCancelled; + } + + private synchronized void sendCancelNotification() { + if (!isFinished && isCancelled && !cancelIsNotified) { + cancelIsNotified = true; + if (responseHandler != null) + responseHandler.sendCancelMessage(); + } + } + + public boolean isDone() { + return isCancelled() || isFinished; + } + + public boolean cancel(boolean mayInterruptIfRunning) { + isCancelled = true; + if (mayInterruptIfRunning && request != null && !request.isAborted()) { + request.abort(); + } + return isCancelled(); + } } diff --git a/library/src/main/java/com/loopj/android/http/AsyncHttpResponseHandler.java b/library/src/main/java/com/loopj/android/http/AsyncHttpResponseHandler.java index a2cb8b3d5..66404cc39 100644 --- a/library/src/main/java/com/loopj/android/http/AsyncHttpResponseHandler.java +++ b/library/src/main/java/com/loopj/android/http/AsyncHttpResponseHandler.java @@ -88,6 +88,7 @@ public abstract class AsyncHttpResponseHandler implements ResponseHandlerInterfa protected static final int FINISH_MESSAGE = 3; protected static final int PROGRESS_MESSAGE = 4; protected static final int RETRY_MESSAGE = 5; + protected static final int CANCEL_MESSAGE = 6; protected static final int BUFFER_SIZE = 4096; @@ -221,6 +222,10 @@ public void onRetry(int retryNo) { Log.d(LOG_TAG, String.format("Request retry no. %d", retryNo)); } + public void onCancel() { + Log.d(LOG_TAG, "Request got cancelled"); + } + final public void sendProgressMessage(int bytesWritten, int bytesTotal) { sendMessage(obtainMessage(PROGRESS_MESSAGE, new Object[]{bytesWritten, bytesTotal})); } @@ -245,6 +250,10 @@ final public void sendRetryMessage(int retryNo) { sendMessage(obtainMessage(RETRY_MESSAGE, new Object[]{retryNo})); } + final public void sendCancelMessage() { + sendMessage(obtainMessage(CANCEL_MESSAGE, null)); + } + // Methods which emulate android's Handler and Message methods protected void handleMessage(Message message) { Object[] response; @@ -291,6 +300,9 @@ protected void handleMessage(Message message) { else Log.e(LOG_TAG, "RETRY_MESSAGE didn't get enough params"); break; + case CANCEL_MESSAGE: + onCancel(); + break; } } diff --git a/library/src/main/java/com/loopj/android/http/RequestHandle.java b/library/src/main/java/com/loopj/android/http/RequestHandle.java index 08cb7824b..bd9acb299 100644 --- a/library/src/main/java/com/loopj/android/http/RequestHandle.java +++ b/library/src/main/java/com/loopj/android/http/RequestHandle.java @@ -1,15 +1,15 @@ package com.loopj.android.http; -import java.util.concurrent.Future; +import java.lang.ref.WeakReference; /** * A Handle to an AsyncRequest which can be used to cancel a running request. */ public class RequestHandle { - private final Future request; + private final WeakReference request; - public RequestHandle(Future request) { - this.request = request; + public RequestHandle(AsyncHttpRequest request) { + this.request = new WeakReference(request); } /** @@ -28,7 +28,8 @@ public RequestHandle(Future request) { * completed normally; true otherwise */ public boolean cancel(boolean mayInterruptIfRunning) { - return this.request != null && request.cancel(mayInterruptIfRunning); + AsyncHttpRequest _request = request.get(); + return _request == null || _request.cancel(mayInterruptIfRunning); } /** @@ -38,7 +39,8 @@ public boolean cancel(boolean mayInterruptIfRunning) { * @return true if this task completed */ public boolean isFinished() { - return this.request == null || request.isDone(); + AsyncHttpRequest _request = request.get(); + return _request == null || _request.isDone(); } /** @@ -47,6 +49,14 @@ public boolean isFinished() { * @return true if this task was cancelled before it completed */ public boolean isCancelled() { - return this.request != null && request.isCancelled(); + AsyncHttpRequest _request = request.get(); + return _request == null || _request.isCancelled(); + } + + public boolean shouldBeGarbageCollected() { + boolean should = isCancelled() || isFinished(); + if (should) + request.clear(); + return should; } } \ No newline at end of file diff --git a/library/src/main/java/com/loopj/android/http/ResponseHandlerInterface.java b/library/src/main/java/com/loopj/android/http/ResponseHandlerInterface.java index d563a20cb..4641a8682 100644 --- a/library/src/main/java/com/loopj/android/http/ResponseHandlerInterface.java +++ b/library/src/main/java/com/loopj/android/http/ResponseHandlerInterface.java @@ -37,6 +37,11 @@ public interface ResponseHandlerInterface { */ void sendProgressMessage(int bytesWritten, int bytesTotal); + /** + * Notifies callback, that request was cancelled + */ + void sendCancelMessage(); + /** * Notifies callback, that request was handled successfully * From 9c836c611cacfccc6a03fc923cb07769de58aa72 Mon Sep 17 00:00:00 2001 From: mareksebera Date: Sat, 28 Dec 2013 23:16:06 +0100 Subject: [PATCH 234/613] Added cancel function to sample --- .../android/http/sample/SampleParentActivity.java | 12 ++++++++++++ .../http/sample/ThreadingTimeoutSample.java | 15 ++++++++++++--- sample/src/main/res/layout/parent_layout.xml | 7 +++++++ sample/src/main/res/values/strings.xml | 1 + 4 files changed, 32 insertions(+), 3 deletions(-) diff --git a/sample/src/main/java/com/loopj/android/http/sample/SampleParentActivity.java b/sample/src/main/java/com/loopj/android/http/sample/SampleParentActivity.java index 2592ee81a..182425d25 100644 --- a/sample/src/main/java/com/loopj/android/http/sample/SampleParentActivity.java +++ b/sample/src/main/java/com/loopj/android/http/sample/SampleParentActivity.java @@ -47,6 +47,7 @@ protected void onCreate(Bundle savedInstanceState) { headersEditText = (EditText) findViewById(R.id.edit_headers); bodyEditText = (EditText) findViewById(R.id.edit_body); Button runButton = (Button) findViewById(R.id.button_run); + Button cancelButton = (Button) findViewById(R.id.button_cancel); LinearLayout headersLayout = (LinearLayout) findViewById(R.id.layout_headers); LinearLayout bodyLayout = (LinearLayout) findViewById(R.id.layout_body); responseLayout = (LinearLayout) findViewById(R.id.layout_response); @@ -57,6 +58,10 @@ protected void onCreate(Bundle savedInstanceState) { headersLayout.setVisibility(isRequestHeadersAllowed() ? View.VISIBLE : View.GONE); runButton.setOnClickListener(onClickListener); + if (isCancelButtonAllowed() && cancelButton != null) { + cancelButton.setVisibility(View.VISIBLE); + cancelButton.setOnClickListener(onClickListener); + } } private View.OnClickListener onClickListener = new View.OnClickListener() { @@ -70,6 +75,9 @@ public void onClick(View v) { getRequestEntity(), getResponseHandler()); break; + case R.id.button_cancel: + asyncHttpClient.cancelRequests(SampleParentActivity.this, true); + break; } } }; @@ -172,6 +180,10 @@ protected final void clearOutputs() { responseLayout.removeAllViews(); } + protected boolean isCancelButtonAllowed() { + return false; + } + protected abstract int getSampleTitle(); protected abstract boolean isRequestBodyAllowed(); diff --git a/sample/src/main/java/com/loopj/android/http/sample/ThreadingTimeoutSample.java b/sample/src/main/java/com/loopj/android/http/sample/ThreadingTimeoutSample.java index aeb131a5c..568537fe4 100644 --- a/sample/src/main/java/com/loopj/android/http/sample/ThreadingTimeoutSample.java +++ b/sample/src/main/java/com/loopj/android/http/sample/ThreadingTimeoutSample.java @@ -8,12 +8,11 @@ import org.apache.http.Header; import org.apache.http.HttpEntity; -import java.util.Random; - public class ThreadingTimeoutSample extends SampleParentActivity { private static final String LOG_TAG = "ThreadingTimeoutSample"; private SparseArray states = new SparseArray(); + private int counter = 0; @Override protected int getSampleTitle() { @@ -30,6 +29,11 @@ protected boolean isRequestHeadersAllowed() { return false; } + @Override + protected boolean isCancelButtonAllowed() { + return true; + } + @Override protected String getDefaultURL() { return "http://httpbin.org/delay/6"; @@ -48,7 +52,7 @@ private synchronized void setStatus(int id, String status) { protected AsyncHttpResponseHandler getResponseHandler() { return new AsyncHttpResponseHandler() { - private int id = new Random().nextInt(1000); + private int id = counter++; @Override public void onStart() { @@ -69,6 +73,11 @@ public void onFinish() { public void onFailure(int statusCode, Header[] headers, byte[] responseBody, Throwable error) { setStatus(id, "FAILURE"); } + + @Override + public void onCancel() { + setStatus(id, "CANCEL"); + } }; } diff --git a/sample/src/main/res/layout/parent_layout.xml b/sample/src/main/res/layout/parent_layout.xml index f6872a064..b32b7b740 100644 --- a/sample/src/main/res/layout/parent_layout.xml +++ b/sample/src/main/res/layout/parent_layout.xml @@ -31,6 +31,13 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/button_run" /> + +