From a6af758f66249bb4ede8f874188dbcecb61b4f2a Mon Sep 17 00:00:00 2001 From: Peter Edwards Date: Thu, 8 Nov 2012 18:01:09 +0200 Subject: [PATCH 001/584] 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 002/584] 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 003/584] 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 c2cbe6b6474678a3ba194838707b4573ed71cb74 Mon Sep 17 00:00:00 2001 From: Peter Edwards Date: Fri, 14 Dec 2012 13:46:10 +0200 Subject: [PATCH 004/584] 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 33809bf10d860eee6fa67ef00958da0eae1aef01 Mon Sep 17 00:00:00 2001 From: Mindaugas Kuprionis Date: Sun, 20 Jan 2013 17:13:34 +0200 Subject: [PATCH 005/584] 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 33f64e61373492aee54503b3ef91c374ec4d7597 Mon Sep 17 00:00:00 2001 From: Peter Edwards Date: Thu, 28 Mar 2013 15:02:10 +0200 Subject: [PATCH 006/584] 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 2ef2b28ad3b061dda7980ffc3a9e030712aefccc Mon Sep 17 00:00:00 2001 From: James Smith Date: Mon, 22 Apr 2013 08:05:11 -0700 Subject: [PATCH 007/584] 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 008/584] 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 009/584] 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 010/584] 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 011/584] 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 012/584] 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 013/584] 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 014/584] 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 015/584] 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 016/584] 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 017/584] 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 018/584] 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 019/584] 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 020/584] 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 021/584] 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 022/584] 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 023/584] 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 024/584] 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 025/584] 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 026/584] 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 027/584] 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 028/584] 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 029/584] 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 030/584] 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 031/584] 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 032/584] 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 033/584] 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 034/584] 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 035/584] 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 036/584] 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 037/584] 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 038/584] 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 039/584] 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 040/584] 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 041/584] 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 042/584] 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 043/584] 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 044/584] 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 045/584] 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 046/584] 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 047/584] 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 048/584] 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 049/584] 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 050/584] 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 051/584] 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 052/584] 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 053/584] 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 054/584] 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 055/584] 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 056/584] #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 057/584] 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 058/584] 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 059/584] 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 060/584] 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 061/584] 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 062/584] 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 063/584] 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 064/584] 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 065/584] 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 066/584] 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 067/584] 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 075/584] 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 076/584] 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 077/584] 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 078/584] 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 079/584] 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 080/584] 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 081/584] 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 082/584] 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 083/584] 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 084/584] 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 085/584] 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 086/584] 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 087/584] 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 088/584] 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 089/584] 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 090/584] 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 091/584] 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 092/584] 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 093/584] 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 094/584] 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 095/584] 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 096/584] 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 097/584] 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 098/584] 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 099/584] 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 100/584] 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 101/584] 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 102/584] 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 103/584] 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 104/584] 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 107/584] 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 108/584] 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 109/584] 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 110/584] 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 111/584] 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 112/584] 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 113/584] 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 114/584] 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 115/584] 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 116/584] 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 117/584] 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 118/584] 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 119/584] 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 120/584] 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 156/584] 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 157/584] 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 158/584] 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 159/584] 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 160/584] 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 161/584] 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 162/584] 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 163/584] 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 164/584] 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 165/584] 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 166/584] 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 167/584] 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 168/584] 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 169/584] 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 170/584] 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 171/584] 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 172/584] 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 173/584] 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 174/584] 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 175/584] 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 176/584] 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 177/584] 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 178/584] 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 179/584] 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 180/584] 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 181/584] 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 182/584] 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 183/584] 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 184/584] 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 185/584] 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 186/584] 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 187/584] 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 188/584] 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 189/584] 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 190/584] 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 191/584] 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 192/584] 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 193/584] 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 194/584] 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 195/584] 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 196/584] 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 197/584] 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 198/584] 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 199/584] 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 200/584] 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 201/584] 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 202/584] 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 203/584] 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 204/584] 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 205/584] 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" /> + +