1
0
mirror of https://github.com/SKCraft/Launcher.git synced 2024-11-30 13:13:58 +01:00

Fix bad logic surrounding download resuming

An oversight in the implementation of partial download resumption led
to cross-contamination in the presence of multiple request URLs, where
an `Accept-Ranges` header from one server would cause a `Range` header
to be supplied to the next. Compounded with a bad ternary expression
in `HttpRequest` which couldn't handle non-200 success codes, this
was causing cryptic "Stream closed" errors on library downloads.
This commit is contained in:
Henry Le Grys 2021-03-31 22:22:00 +01:00
parent f7bbe82dfb
commit f23d387609
2 changed files with 33 additions and 11 deletions

View File

@ -238,7 +238,6 @@ public class HttpDownloader implements Downloader {
int trial = 0; int trial = 0;
boolean first = true; boolean first = true;
IOException lastException = null; IOException lastException = null;
HttpRequest.PartialDownloadInfo retryDetails = null;
do { do {
for (URL url : urls) { for (URL url : urls) {
@ -249,17 +248,10 @@ public class HttpDownloader implements Downloader {
first = false; first = false;
try { try {
request = HttpRequest.get(url); tryDownloadFrom(url, file, null, 0);
request.setResumeInfo(retryDetails).execute().expectResponseCode(200).saveContent(file);
return; return;
} catch (IOException e) { } catch (IOException e) {
lastException = e; lastException = e;
log.log(Level.WARNING, "Failed to download " + url, e);
Optional<HttpRequest.PartialDownloadInfo> byteRangeSupport = request.canRetryPartial();
if (byteRangeSupport.isPresent()) {
retryDetails = byteRangeSupport.get();
}
} }
} }
} while (++trial < tryCount); } while (++trial < tryCount);
@ -267,6 +259,27 @@ public class HttpDownloader implements Downloader {
throw new IOException("Failed to download from " + urls, lastException); throw new IOException("Failed to download from " + urls, lastException);
} }
private void tryDownloadFrom(URL url, File file, HttpRequest.PartialDownloadInfo retryDetails, int tries)
throws InterruptedException, IOException {
try {
request = HttpRequest.get(url);
request.setResumeInfo(retryDetails).execute().expectResponseCode(200).saveContent(file);
} catch (IOException e) {
log.log(Level.WARNING, "Failed to download " + url, e);
// We only want to try to resume a partial download if the request succeeded before
// throwing an exception halfway through. If it didn't succeed, just throw the error.
if (tries >= tryCount || !request.isSuccessCode()) {
throw e;
}
Optional<HttpRequest.PartialDownloadInfo> byteRangeSupport = request.canRetryPartial();
if (byteRangeSupport.isPresent()) {
tryDownloadFrom(url, file, byteRangeSupport.get(), tries + 1);
}
}
}
@Override @Override
public double getProgress() { public double getProgress() {
HttpRequest request = this.request; HttpRequest request = this.request;

View File

@ -117,8 +117,7 @@ public class HttpRequest implements Closeable, ProgressObservable {
conn = this.runRequest(url); conn = this.runRequest(url);
inputStream = conn.getResponseCode() == HttpURLConnection.HTTP_OK ? inputStream = isSuccessCode() ? conn.getInputStream() : conn.getErrorStream();
conn.getInputStream() : conn.getErrorStream();
successful = true; successful = true;
} finally { } finally {
@ -247,6 +246,16 @@ public class HttpRequest implements Closeable, ProgressObservable {
return conn.getResponseCode(); return conn.getResponseCode();
} }
/**
* Check if the response code indicates a successful request.
* @return True if response code is 2xx, false otherwise.
* @throws IOException on I/O error getting the response code.
*/
public boolean isSuccessCode() throws IOException {
int code = getResponseCode();
return code >= 200 && code < 300;
}
/** /**
* Get the input stream. * Get the input stream.
* *