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 5525391a3..ffef41ee2 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); } } @@ -315,35 +394,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 138b61162..a5a0bd953 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,13 +139,8 @@ 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 @@ -157,70 +150,35 @@ public void run() { postRunnable(new Runnable() { @Override public void run() { - sendMessage(obtainMessage(SUCCESS_JSON_MESSAGE, new Object[]{statusCode, headers, 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); + } + } }); - } catch (final JSONException e) { + } catch (final JSONException ex) { postRunnable(new Runnable() { @Override public void run() { - sendFailureMessage(statusCode, headers, e, responseBody); + 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) { if (responseBody != null) { new Thread(new Runnable() { @Override @@ -237,16 +195,16 @@ 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); } } }); - } catch (JSONException ex) { + } catch (final JSONException ex) { postRunnable(new Runnable() { @Override public void run() { - onFailure(statusCode, headers, e, responseBody); + onFailure(ex, (JSONObject) null ); } }); @@ -256,6 +214,23 @@ public void run() { } else { onFailure(e, ""); } + } + 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) {