Skip to content

core: support chunked transfer for image files #10820

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

Merged
merged 3 commits into from
May 12, 2025
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import java.io.RandomAccessFile;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Arrays;
import java.util.Date;
import java.util.List;

Expand Down Expand Up @@ -80,6 +81,18 @@
private ResourceType resourceType = ResourceType.TEMPLATE;
private final HttpMethodRetryHandler myretryhandler;
private boolean followRedirects = false;
private boolean isChunkedTransfer;

protected static final List<String> CUSTOM_HEADERS_FOR_CHUNKED_TRANSFER_SIZE = Arrays.asList(

Check warning on line 86 in core/src/main/java/com/cloud/storage/template/HttpTemplateDownloader.java

View check run for this annotation

Codecov / codecov/patch

core/src/main/java/com/cloud/storage/template/HttpTemplateDownloader.java#L86

Added line #L86 was not covered by tests
"x-goog-stored-content-length",
"x-goog-meta-size",
"x-amz-meta-size",
"x-amz-meta-content-length",
"x-object-meta-size",
"x-original-content-length",
"x-oss-meta-content-length",
"x-file-size");
private static final long MIN_FORMAT_VERIFICATION_SIZE = 1024 * 1024;

public HttpTemplateDownloader(StorageLayer storageLayer, String downloadUrl, String toDir, DownloadCompleteCallback callback, long maxTemplateSizeInBytes,
String user, String password, Proxy proxy, ResourceType resourceType) {
Expand Down Expand Up @@ -205,13 +218,11 @@
RandomAccessFile out = new RandomAccessFile(file, "rw");
) {
out.seek(localFileSize);

logger.info("Starting download from " + downloadUrl + " to " + toFile + " remoteSize=" + toHumanReadableSize(remoteSize) + " , max size=" + toHumanReadableSize(maxTemplateSizeInBytes));

if (copyBytes(file, in, out)) return 0;

logger.info("Starting download from {} to {} remoteSize={} , max size={}",downloadUrl, toFile,
toHumanReadableSize(remoteSize), toHumanReadableSize(maxTemplateSizeInBytes));
boolean eof = copyBytes(file, in, out);

Check warning on line 223 in core/src/main/java/com/cloud/storage/template/HttpTemplateDownloader.java

View check run for this annotation

Codecov / codecov/patch

core/src/main/java/com/cloud/storage/template/HttpTemplateDownloader.java#L221-L223

Added lines #L221 - L223 were not covered by tests
Date finish = new Date();
checkDowloadCompletion();
checkDownloadCompletion(eof);

Check warning on line 225 in core/src/main/java/com/cloud/storage/template/HttpTemplateDownloader.java

View check run for this annotation

Codecov / codecov/patch

core/src/main/java/com/cloud/storage/template/HttpTemplateDownloader.java#L225

Added line #L225 was not covered by tests
downloadTime += finish.getTime() - start.getTime();
} finally { /* in.close() and out.close() */ }
return totalBytes;
Expand All @@ -237,28 +248,32 @@
}

private boolean copyBytes(File file, InputStream in, RandomAccessFile out) throws IOException {
int bytes;
byte[] block = new byte[CHUNK_SIZE];
byte[] buffer = new byte[CHUNK_SIZE];

Check warning on line 251 in core/src/main/java/com/cloud/storage/template/HttpTemplateDownloader.java

View check run for this annotation

Codecov / codecov/patch

core/src/main/java/com/cloud/storage/template/HttpTemplateDownloader.java#L251

Added line #L251 was not covered by tests
long offset = 0;
boolean done = false;
VerifyFormat verifyFormat = new VerifyFormat(file);
status = Status.IN_PROGRESS;
while (!done && status != Status.ABORTED && offset <= remoteSize) {
if ((bytes = in.read(block, 0, CHUNK_SIZE)) > -1) {
offset = writeBlock(bytes, out, block, offset);
if (!ResourceType.SNAPSHOT.equals(resourceType) &&
!verifyFormat.isVerifiedFormat() &&
(offset >= 1048576 || offset >= remoteSize)) { //let's check format after we get 1MB or full file
verifyFormat.invoke();
}
} else {
done = true;
while (status != Status.ABORTED) {
int bytesRead = in.read(buffer, 0, CHUNK_SIZE);

Check warning on line 256 in core/src/main/java/com/cloud/storage/template/HttpTemplateDownloader.java

View check run for this annotation

Codecov / codecov/patch

core/src/main/java/com/cloud/storage/template/HttpTemplateDownloader.java#L256

Added line #L256 was not covered by tests
if (bytesRead == -1) {
logger.debug("Reached EOF on input stream");
break;

Check warning on line 259 in core/src/main/java/com/cloud/storage/template/HttpTemplateDownloader.java

View check run for this annotation

Codecov / codecov/patch

core/src/main/java/com/cloud/storage/template/HttpTemplateDownloader.java#L258-L259

Added lines #L258 - L259 were not covered by tests
}
offset = writeBlock(bytesRead, out, buffer, offset);

Check warning on line 261 in core/src/main/java/com/cloud/storage/template/HttpTemplateDownloader.java

View check run for this annotation

Codecov / codecov/patch

core/src/main/java/com/cloud/storage/template/HttpTemplateDownloader.java#L261

Added line #L261 was not covered by tests
if (!ResourceType.SNAPSHOT.equals(resourceType)
&& !verifyFormat.isVerifiedFormat()
&& (offset >= MIN_FORMAT_VERIFICATION_SIZE || offset >= remoteSize)) {
verifyFormat.invoke();

Check warning on line 265 in core/src/main/java/com/cloud/storage/template/HttpTemplateDownloader.java

View check run for this annotation

Codecov / codecov/patch

core/src/main/java/com/cloud/storage/template/HttpTemplateDownloader.java#L265

Added line #L265 was not covered by tests
}
if (offset >= remoteSize) {
logger.debug("Reached expected remote size limit: {} bytes", remoteSize);
break;

Check warning on line 269 in core/src/main/java/com/cloud/storage/template/HttpTemplateDownloader.java

View check run for this annotation

Codecov / codecov/patch

core/src/main/java/com/cloud/storage/template/HttpTemplateDownloader.java#L268-L269

Added lines #L268 - L269 were not covered by tests
}
}
out.getFD().sync();
return false;
return !Status.ABORTED.equals(status);
}


private long writeBlock(int bytes, RandomAccessFile out, byte[] block, long offset) throws IOException {
out.write(block, 0, bytes);
offset += bytes;
Expand All @@ -267,11 +282,13 @@
return offset;
}

private void checkDowloadCompletion() {
private void checkDownloadCompletion(boolean eof) {

Check warning on line 285 in core/src/main/java/com/cloud/storage/template/HttpTemplateDownloader.java

View check run for this annotation

Codecov / codecov/patch

core/src/main/java/com/cloud/storage/template/HttpTemplateDownloader.java#L285

Added line #L285 was not covered by tests
String downloaded = "(incomplete download)";
if (totalBytes >= remoteSize) {
if (eof && ((totalBytes >= remoteSize) || (isChunkedTransfer && remoteSize == maxTemplateSizeInBytes))) {
status = Status.DOWNLOAD_FINISHED;
downloaded = "(download complete remote=" + toHumanReadableSize(remoteSize) + " bytes)";
downloaded = "(download complete remote=" +

Check warning on line 289 in core/src/main/java/com/cloud/storage/template/HttpTemplateDownloader.java

View check run for this annotation

Codecov / codecov/patch

core/src/main/java/com/cloud/storage/template/HttpTemplateDownloader.java#L289

Added line #L289 was not covered by tests
(remoteSize == maxTemplateSizeInBytes ? toHumanReadableSize(remoteSize) : "unknown") +
" bytes)";
}
errorString = "Downloaded " + toHumanReadableSize(totalBytes) + " bytes " + downloaded;
}
Expand All @@ -293,18 +310,42 @@
}
}

protected long getRemoteSizeForChunkedTransfer() {

Check warning on line 313 in core/src/main/java/com/cloud/storage/template/HttpTemplateDownloader.java

View check run for this annotation

Codecov / codecov/patch

core/src/main/java/com/cloud/storage/template/HttpTemplateDownloader.java#L313

Added line #L313 was not covered by tests
for (String headerKey : CUSTOM_HEADERS_FOR_CHUNKED_TRANSFER_SIZE) {
Header header = request.getResponseHeader(headerKey);

Check warning on line 315 in core/src/main/java/com/cloud/storage/template/HttpTemplateDownloader.java

View check run for this annotation

Codecov / codecov/patch

core/src/main/java/com/cloud/storage/template/HttpTemplateDownloader.java#L315

Added line #L315 was not covered by tests
if (header == null) {
continue;

Check warning on line 317 in core/src/main/java/com/cloud/storage/template/HttpTemplateDownloader.java

View check run for this annotation

Codecov / codecov/patch

core/src/main/java/com/cloud/storage/template/HttpTemplateDownloader.java#L317

Added line #L317 was not covered by tests
}
try {
return Long.parseLong(header.getValue());
} catch (NumberFormatException ignored) {}
}
Header contentRangeHeader = request.getResponseHeader("Content-Range");

Check warning on line 323 in core/src/main/java/com/cloud/storage/template/HttpTemplateDownloader.java

View check run for this annotation

Codecov / codecov/patch

core/src/main/java/com/cloud/storage/template/HttpTemplateDownloader.java#L319-L323

Added lines #L319 - L323 were not covered by tests
if (contentRangeHeader != null) {
String contentRange = contentRangeHeader.getValue();

Check warning on line 325 in core/src/main/java/com/cloud/storage/template/HttpTemplateDownloader.java

View check run for this annotation

Codecov / codecov/patch

core/src/main/java/com/cloud/storage/template/HttpTemplateDownloader.java#L325

Added line #L325 was not covered by tests
if (contentRange != null && contentRange.contains("/")) {
String totalSize = contentRange.substring(contentRange.indexOf('/') + 1).trim();
return Long.parseLong(totalSize);

Check warning on line 328 in core/src/main/java/com/cloud/storage/template/HttpTemplateDownloader.java

View check run for this annotation

Codecov / codecov/patch

core/src/main/java/com/cloud/storage/template/HttpTemplateDownloader.java#L327-L328

Added lines #L327 - L328 were not covered by tests
}
}
return 0;
}

Check warning on line 332 in core/src/main/java/com/cloud/storage/template/HttpTemplateDownloader.java

View check run for this annotation

Codecov / codecov/patch

core/src/main/java/com/cloud/storage/template/HttpTemplateDownloader.java#L331-L332

Added lines #L331 - L332 were not covered by tests

private boolean tryAndGetRemoteSize() {
Header contentLengthHeader = request.getResponseHeader("content-length");
boolean chunked = false;
isChunkedTransfer = false;

Check warning on line 336 in core/src/main/java/com/cloud/storage/template/HttpTemplateDownloader.java

View check run for this annotation

Codecov / codecov/patch

core/src/main/java/com/cloud/storage/template/HttpTemplateDownloader.java#L336

Added line #L336 was not covered by tests
long reportedRemoteSize = 0;
if (contentLengthHeader == null) {
Header chunkedHeader = request.getResponseHeader("Transfer-Encoding");
if (chunkedHeader == null || !"chunked".equalsIgnoreCase(chunkedHeader.getValue())) {
if (chunkedHeader != null && "chunked".equalsIgnoreCase(chunkedHeader.getValue())) {
isChunkedTransfer = true;
reportedRemoteSize = getRemoteSizeForChunkedTransfer();
logger.debug("{} is using chunked transfer encoding, possible remote size: {}", downloadUrl,
reportedRemoteSize);

Check warning on line 344 in core/src/main/java/com/cloud/storage/template/HttpTemplateDownloader.java

View check run for this annotation

Codecov / codecov/patch

core/src/main/java/com/cloud/storage/template/HttpTemplateDownloader.java#L341-L344

Added lines #L341 - L344 were not covered by tests
} else {
status = Status.UNRECOVERABLE_ERROR;
errorString = " Failed to receive length of download ";
return false;
} else if ("chunked".equalsIgnoreCase(chunkedHeader.getValue())) {
chunked = true;
}
} else {
reportedRemoteSize = Long.parseLong(contentLengthHeader.getValue());
Expand All @@ -316,9 +357,11 @@
return false;
}
}

if (remoteSize == 0) {
remoteSize = reportedRemoteSize;
if (remoteSize != 0) {
logger.debug("Remote size for {} found to be {}", downloadUrl, toHumanReadableSize(remoteSize));

Check warning on line 363 in core/src/main/java/com/cloud/storage/template/HttpTemplateDownloader.java

View check run for this annotation

Codecov / codecov/patch

core/src/main/java/com/cloud/storage/template/HttpTemplateDownloader.java#L363

Added line #L363 was not covered by tests
}
}
return true;
}
Expand Down
Loading