Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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 @@ -129,35 +129,25 @@ public void handle(final HttpExchange exchange) throws IOException {
final MockGcsBlobStore.BlobVersion blob = mockGcsBlobStore.getBlob(path, ifGenerationMatch);
if (blob != null) {
final String rangeHeader = exchange.getRequestHeaders().getFirst("Range");
final long offset;
final long end;
final BytesReference response;
if (rangeHeader == null) {
offset = 0L;
end = blob.contents().length() - 1;
response = blob.contents();
} else {
final HttpHeaderParser.Range range = HttpHeaderParser.parseRangeHeader(rangeHeader);
if (range == null) {
throw new AssertionError("Range bytes header does not match expected format: " + rangeHeader);
}
offset = range.start();
end = range.end();
}

if (offset >= blob.contents().length()) {
exchange.getResponseHeaders().add("Content-Type", "application/octet-stream");
exchange.sendResponseHeaders(RestStatus.REQUESTED_RANGE_NOT_SATISFIED.getStatus(), -1);
return;
}
if (range.start() >= blob.contents().length()) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder, should we be checking the range end here? If you ask for bytes 0-10 of an 8-byte blob, we cannot satisfy that range.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, probably. I'll have a look at what the spec says, currently we just send as much of it as we have.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm also adding ETags, because it looks like that's the mechanism that the BlobReadChannel uses to detect that the file it's streaming has changed under it. We can implement them trivially using generation I think.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So it looks like

  • We don't yet check that the file hasn't changed when resuming a download (I raised ES-11432 to address at some point)
  • We do need to allow ranges beyond the end of actual content (streaming uses this to request a chunk). There may be more to the story, but if we start enforcing the ranges all that breaks.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I confirmed this testing against the actual API

  • If the start of the range is past the end of the file, you get a 416
  • If the end of the range is past the end of the file, but the start is in it, you get a 206 and as much of the file as can be delivered

exchange.getResponseHeaders().add("Content-Type", "application/octet-stream");
exchange.sendResponseHeaders(RestStatus.REQUESTED_RANGE_NOT_SATISFIED.getStatus(), -1);
return;
}

BytesReference response = blob.contents();
exchange.getResponseHeaders().add("Content-Type", "application/octet-stream");
final int bufferedLength = response.length();
if (offset > 0 || bufferedLength > end) {
response = response.slice(
Math.toIntExact(offset),
Math.toIntExact(Math.min(end + 1 - offset, bufferedLength - offset))
);
final long lastIndex = Math.min(range.end(), blob.contents().length() - 1);
response = blob.contents().slice(Math.toIntExact(range.start()), Math.toIntExact(lastIndex - range.start() + 1));
}
exchange.getResponseHeaders().add("Content-Type", "application/octet-stream");
exchange.sendResponseHeaders(RestStatus.OK.getStatus(), response.length());
response.writeTo(exchange.getResponseBody());
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,33 @@ public void testGetWithBytesRange() {
);
}

public void testZeroLengthObjectGets() {
final var bucket = randomIdentifier();
final var handler = new GoogleCloudStorageHttpHandler(bucket);
final var blobName = "blob_name_" + randomIdentifier();
final var blobBytes = BytesArray.EMPTY;

assertEquals(RestStatus.OK, executeMultipartUpload(handler, bucket, blobName, blobBytes, 0L).restStatus());

assertEquals(
"No Range",
new TestHttpResponse(RestStatus.OK, blobBytes, TestHttpExchange.EMPTY_HEADERS),
getBlobContents(handler, bucket, blobName, null, null)
);

assertEquals(
"Range 0-0",
new TestHttpResponse(RestStatus.REQUESTED_RANGE_NOT_SATISFIED, BytesArray.EMPTY, TestHttpExchange.EMPTY_HEADERS),
getBlobContents(handler, bucket, blobName, null, new HttpHeaderParser.Range(0, 0))
);

assertEquals(
"Random range x-y",
new TestHttpResponse(RestStatus.REQUESTED_RANGE_NOT_SATISFIED, BytesArray.EMPTY, TestHttpExchange.EMPTY_HEADERS),
getBlobContents(handler, bucket, blobName, null, new HttpHeaderParser.Range(randomIntBetween(0, 30), randomIntBetween(31, 100)))
);
}

public void testResumableUpload() {
final var bucket = randomIdentifier();
final var handler = new GoogleCloudStorageHttpHandler(bucket);
Expand Down Expand Up @@ -545,7 +572,6 @@ private static TestHttpResponse executeUpload(
BytesReference bytes,
Long ifGenerationMatch
) {
assert bytes.length() > 20;
if (randomBoolean()) {
return executeResumableUpload(handler, bucket, blobName, bytes, ifGenerationMatch);
} else {
Expand All @@ -560,6 +586,7 @@ private static TestHttpResponse executeResumableUpload(
BytesReference bytes,
Long ifGenerationMatch
) {
assert bytes.length() >= 2 : "We can't split anything smaller than two";
final var createUploadResponse = handleRequest(
handler,
"POST",
Expand All @@ -572,7 +599,7 @@ private static TestHttpResponse executeResumableUpload(
final var sessionURI = locationHeader.substring(locationHeader.indexOf(HOST) + HOST.length());
assertEquals(RestStatus.OK, createUploadResponse.restStatus());

final int partBoundary = randomIntBetween(10, bytes.length() - 1);
final int partBoundary = randomIntBetween(1, bytes.length() - 1);
final var part1 = bytes.slice(0, partBoundary);
final var uploadPart1Response = handleRequest(handler, "PUT", sessionURI, part1, contentRangeHeader(0, partBoundary - 1, null));
assertEquals(RESUME_INCOMPLETE, uploadPart1Response.status());
Expand Down