-
Notifications
You must be signed in to change notification settings - Fork 4.1k
Refactor attempt #3 #339
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Refactor attempt #3 #339
Changes from all commits
cc38f5d
cda3f4a
6e6569f
b20f6f5
3dfc05e
f279cb5
a65c7d6
2988325
6fe5ce6
dfd8661
4b90839
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -38,118 +38,88 @@ 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) { | ||
this.client = client; | ||
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(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I see you removed throwing request interruption and so calling onFailed in handler subclass, how will this be handled now? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The interruption check will cause a silent exit (as intended from cancelRequests with the mayInterruptIfRunning flag set) For any other exceptions: they are caught in the base handler and wrapped as IOExceptions. |
||
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) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is sincerely not only case of connectivity switching, first it happens if you don't enlist INTERNET permission, second, the DNS or data-flow on the way can be corrupted somehow, and this shouldn't drive you into more than 2-3 tries to resolve. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This bugged me for ages (our client needed to be resilient to network switch) and this seemed the best of a bad set of ways to handle it. Hopefully this will catch most base issues. It would be interesting to try and develop some definitive test cases for this. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ok, I like it that way, we'll see how it behaves in the wild. |
||
// 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); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is great refactoring, it has been driving me nuts too