From cc38f5dbcb6cd8ee23215589015087a7ecf7a871 Mon Sep 17 00:00:00 2001 From: Peter Edwards Date: Thu, 17 Oct 2013 14:38:59 +0200 Subject: [PATCH 01/10] 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 02/10] 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 03/10] 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 04/10] 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 05/10] 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 06/10] 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 a65c7d64a8ea21a5bb7e5ce97c0e4a6847452b3b Mon Sep 17 00:00:00 2001 From: Peter Edwards Date: Fri, 18 Oct 2013 09:14:10 +0200 Subject: [PATCH 07/10] 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 08/10] 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 6fe5ce640634c2d11d7828fabf706b7977f03674 Mon Sep 17 00:00:00 2001 From: Peter Edwards Date: Fri, 18 Oct 2013 13:59:14 +0200 Subject: [PATCH 09/10] 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 10/10] 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 {