Skip to content

Commit 65a5766

Browse files
shwstpprdhslove
authored andcommitted
core: support chunked transfer for image files (apache#10820)
1 parent 8e4024b commit 65a5766

File tree

1 file changed

+71
-28
lines changed

1 file changed

+71
-28
lines changed

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

Lines changed: 71 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import java.io.RandomAccessFile;
2828
import java.net.URI;
2929
import java.net.URISyntaxException;
30+
import java.util.Arrays;
3031
import java.util.Date;
3132
import java.util.List;
3233

@@ -80,6 +81,18 @@ public class HttpTemplateDownloader extends ManagedContextRunnable implements Te
8081
private ResourceType resourceType = ResourceType.TEMPLATE;
8182
private final HttpMethodRetryHandler myretryhandler;
8283
private boolean followRedirects = false;
84+
private boolean isChunkedTransfer;
85+
86+
protected static final List<String> CUSTOM_HEADERS_FOR_CHUNKED_TRANSFER_SIZE = Arrays.asList(
87+
"x-goog-stored-content-length",
88+
"x-goog-meta-size",
89+
"x-amz-meta-size",
90+
"x-amz-meta-content-length",
91+
"x-object-meta-size",
92+
"x-original-content-length",
93+
"x-oss-meta-content-length",
94+
"x-file-size");
95+
private static final long MIN_FORMAT_VERIFICATION_SIZE = 1024 * 1024;
8396

8497
public HttpTemplateDownloader(StorageLayer storageLayer, String downloadUrl, String toDir, DownloadCompleteCallback callback, long maxTemplateSizeInBytes,
8598
String user, String password, Proxy proxy, ResourceType resourceType) {
@@ -205,13 +218,11 @@ public long download(boolean resume, DownloadCompleteCallback callback) {
205218
RandomAccessFile out = new RandomAccessFile(file, "rw");
206219
) {
207220
out.seek(localFileSize);
208-
209-
logger.info("Starting download from " + downloadUrl + " to " + toFile + " remoteSize=" + toHumanReadableSize(remoteSize) + " , max size=" + toHumanReadableSize(maxTemplateSizeInBytes));
210-
211-
if (copyBytes(file, in, out)) return 0;
212-
221+
logger.info("Starting download from {} to {} remoteSize={} , max size={}",downloadUrl, toFile,
222+
toHumanReadableSize(remoteSize), toHumanReadableSize(maxTemplateSizeInBytes));
223+
boolean eof = copyBytes(file, in, out);
213224
Date finish = new Date();
214-
checkDowloadCompletion();
225+
checkDownloadCompletion(eof);
215226
downloadTime += finish.getTime() - start.getTime();
216227
} finally { /* in.close() and out.close() */ }
217228
return totalBytes;
@@ -237,28 +248,32 @@ public long download(boolean resume, DownloadCompleteCallback callback) {
237248
}
238249

239250
private boolean copyBytes(File file, InputStream in, RandomAccessFile out) throws IOException {
240-
int bytes;
241-
byte[] block = new byte[CHUNK_SIZE];
251+
byte[] buffer = new byte[CHUNK_SIZE];
242252
long offset = 0;
243-
boolean done = false;
244253
VerifyFormat verifyFormat = new VerifyFormat(file);
245254
status = Status.IN_PROGRESS;
246-
while (!done && status != Status.ABORTED && offset <= remoteSize) {
247-
if ((bytes = in.read(block, 0, CHUNK_SIZE)) > -1) {
248-
offset = writeBlock(bytes, out, block, offset);
249-
if (!ResourceType.SNAPSHOT.equals(resourceType) &&
250-
!verifyFormat.isVerifiedFormat() &&
251-
(offset >= 1048576 || offset >= remoteSize)) { //let's check format after we get 1MB or full file
252-
verifyFormat.invoke();
253-
}
254-
} else {
255-
done = true;
255+
while (status != Status.ABORTED) {
256+
int bytesRead = in.read(buffer, 0, CHUNK_SIZE);
257+
if (bytesRead == -1) {
258+
logger.debug("Reached EOF on input stream");
259+
break;
260+
}
261+
offset = writeBlock(bytesRead, out, buffer, offset);
262+
if (!ResourceType.SNAPSHOT.equals(resourceType)
263+
&& !verifyFormat.isVerifiedFormat()
264+
&& (offset >= MIN_FORMAT_VERIFICATION_SIZE || offset >= remoteSize)) {
265+
verifyFormat.invoke();
266+
}
267+
if (offset >= remoteSize) {
268+
logger.debug("Reached expected remote size limit: {} bytes", remoteSize);
269+
break;
256270
}
257271
}
258272
out.getFD().sync();
259-
return false;
273+
return !Status.ABORTED.equals(status);
260274
}
261275

276+
262277
private long writeBlock(int bytes, RandomAccessFile out, byte[] block, long offset) throws IOException {
263278
out.write(block, 0, bytes);
264279
offset += bytes;
@@ -267,11 +282,13 @@ private long writeBlock(int bytes, RandomAccessFile out, byte[] block, long offs
267282
return offset;
268283
}
269284

270-
private void checkDowloadCompletion() {
285+
private void checkDownloadCompletion(boolean eof) {
271286
String downloaded = "(incomplete download)";
272-
if (totalBytes >= remoteSize) {
287+
if (eof && ((totalBytes >= remoteSize) || (isChunkedTransfer && remoteSize == maxTemplateSizeInBytes))) {
273288
status = Status.DOWNLOAD_FINISHED;
274-
downloaded = "(download complete remote=" + toHumanReadableSize(remoteSize) + " bytes)";
289+
downloaded = "(download complete remote=" +
290+
(remoteSize == maxTemplateSizeInBytes ? toHumanReadableSize(remoteSize) : "unknown") +
291+
" bytes)";
275292
}
276293
errorString = "Downloaded " + toHumanReadableSize(totalBytes) + " bytes " + downloaded;
277294
}
@@ -293,18 +310,42 @@ private void checkAndSetDownloadSize() {
293310
}
294311
}
295312

313+
protected long getRemoteSizeForChunkedTransfer() {
314+
for (String headerKey : CUSTOM_HEADERS_FOR_CHUNKED_TRANSFER_SIZE) {
315+
Header header = request.getResponseHeader(headerKey);
316+
if (header == null) {
317+
continue;
318+
}
319+
try {
320+
return Long.parseLong(header.getValue());
321+
} catch (NumberFormatException ignored) {}
322+
}
323+
Header contentRangeHeader = request.getResponseHeader("Content-Range");
324+
if (contentRangeHeader != null) {
325+
String contentRange = contentRangeHeader.getValue();
326+
if (contentRange != null && contentRange.contains("/")) {
327+
String totalSize = contentRange.substring(contentRange.indexOf('/') + 1).trim();
328+
return Long.parseLong(totalSize);
329+
}
330+
}
331+
return 0;
332+
}
333+
296334
private boolean tryAndGetRemoteSize() {
297335
Header contentLengthHeader = request.getResponseHeader("content-length");
298-
boolean chunked = false;
336+
isChunkedTransfer = false;
299337
long reportedRemoteSize = 0;
300338
if (contentLengthHeader == null) {
301339
Header chunkedHeader = request.getResponseHeader("Transfer-Encoding");
302-
if (chunkedHeader == null || !"chunked".equalsIgnoreCase(chunkedHeader.getValue())) {
340+
if (chunkedHeader != null && "chunked".equalsIgnoreCase(chunkedHeader.getValue())) {
341+
isChunkedTransfer = true;
342+
reportedRemoteSize = getRemoteSizeForChunkedTransfer();
343+
logger.debug("{} is using chunked transfer encoding, possible remote size: {}", downloadUrl,
344+
reportedRemoteSize);
345+
} else {
303346
status = Status.UNRECOVERABLE_ERROR;
304347
errorString = " Failed to receive length of download ";
305348
return false;
306-
} else if ("chunked".equalsIgnoreCase(chunkedHeader.getValue())) {
307-
chunked = true;
308349
}
309350
} else {
310351
reportedRemoteSize = Long.parseLong(contentLengthHeader.getValue());
@@ -316,9 +357,11 @@ private boolean tryAndGetRemoteSize() {
316357
return false;
317358
}
318359
}
319-
320360
if (remoteSize == 0) {
321361
remoteSize = reportedRemoteSize;
362+
if (remoteSize != 0) {
363+
logger.debug("Remote size for {} found to be {}", downloadUrl, toHumanReadableSize(remoteSize));
364+
}
322365
}
323366
return true;
324367
}

0 commit comments

Comments
 (0)