Skip to content

Commit dbabef7

Browse files
committed
Support ListObjectsV2 in S3HttpHandler
`ListObjects` and `ListObjectsV2` only really differ in their approach to pagination, but today `S3HttpHandler` does not simulate pagination anyway so we can use the same handling code for both APIs. The only practical difference is that the v2 SDK requires the `<IsTruncated>` element in a `ListObjectsV2` response, but this element is permitted in both APIs so we add it here.
1 parent ecaa0b1 commit dbabef7

File tree

2 files changed

+56
-33
lines changed

2 files changed

+56
-33
lines changed

test/fixtures/s3-fixture/src/main/java/fixture/s3/S3HttpHandler.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -207,8 +207,9 @@ public void handle(final HttpExchange exchange) throws IOException {
207207
exchange.sendResponseHeaders(RestStatus.OK.getStatus(), -1);
208208

209209
} else if (request.isListObjectsRequest()) {
210-
if (request.queryParameters().containsKey("list-type")) {
211-
throw new AssertionError("Test must be adapted for GET Bucket (List Objects) Version 2");
210+
final var listType = request.queryParameters().getOrDefault("list-type", List.of("1")).get(0);
211+
if (request.queryParameters().containsKey("list-type") && listType.equals("2") == false) {
212+
throw new AssertionError("Unsupported list-type, must be absent or `2` but got " + listType);
212213
}
213214

214215
final StringBuilder list = new StringBuilder();
@@ -223,6 +224,9 @@ public void handle(final HttpExchange exchange) throws IOException {
223224
if (delimiter != null) {
224225
list.append("<Delimiter>").append(delimiter).append("</Delimiter>");
225226
}
227+
// Would be good to test pagination here (the only real difference between ListObjects and ListObjectsV2) but for now
228+
// we return all the results at once.
229+
list.append("<IsTruncated>false</IsTruncated>");
226230
for (Map.Entry<String, BytesReference> blob : blobs.entrySet()) {
227231
if (prefix != null && blob.getKey().startsWith("/" + bucket + "/" + prefix) == false) {
228232
continue;

test/fixtures/s3-fixture/src/test/java/fixture/s3/S3HttpHandlerTests.java

Lines changed: 50 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import com.sun.net.httpserver.HttpExchange;
1515
import com.sun.net.httpserver.HttpPrincipal;
1616

17+
import org.elasticsearch.common.Randomness;
1718
import org.elasticsearch.common.Strings;
1819
import org.elasticsearch.common.bytes.BytesArray;
1920
import org.elasticsearch.common.bytes.BytesReference;
@@ -28,6 +29,7 @@
2829
import java.net.InetSocketAddress;
2930
import java.net.URI;
3031
import java.nio.charset.StandardCharsets;
32+
import java.util.ArrayList;
3133
import java.util.List;
3234
import java.util.Objects;
3335

@@ -45,63 +47,80 @@ public void testRejectsBadUri() {
4547
);
4648
}
4749

50+
private static void assertListObjectsResponse(
51+
S3HttpHandler handler,
52+
@Nullable String prefix,
53+
@Nullable String delimiter,
54+
String expectedResponse
55+
) {
56+
final var queryParts = new ArrayList<String>(3);
57+
if (prefix != null) {
58+
queryParts.add("prefix=" + prefix);
59+
}
60+
if (delimiter != null) {
61+
queryParts.add("delimiter=" + delimiter);
62+
}
63+
if (randomBoolean()) {
64+
queryParts.add("list-type=2");
65+
}
66+
Randomness.shuffle(queryParts);
67+
68+
final var requestUri = "/bucket" + randomFrom("", "/") + (queryParts.isEmpty() ? "" : "?" + String.join("&", queryParts));
69+
assertEquals("GET " + requestUri, new TestHttpResponse(RestStatus.OK, expectedResponse), handleRequest(handler, "GET", requestUri));
70+
}
71+
4872
public void testSimpleObjectOperations() {
4973
final var handler = new S3HttpHandler("bucket", "path");
5074

5175
assertEquals(RestStatus.NOT_FOUND, handleRequest(handler, "GET", "/bucket/path/blob").status());
5276

53-
assertEquals(
54-
new TestHttpResponse(RestStatus.OK, """
55-
<?xml version="1.0" encoding="UTF-8"?><ListBucketResult><Prefix></Prefix></ListBucketResult>"""),
56-
handleRequest(handler, "GET", "/bucket/?prefix=")
57-
);
77+
assertListObjectsResponse(handler, "", null, """
78+
<?xml version="1.0" encoding="UTF-8"?><ListBucketResult><Prefix></Prefix><IsTruncated>false</IsTruncated>\
79+
</ListBucketResult>""");
5880

5981
final var body = randomAlphaOfLength(50);
6082
assertEquals(RestStatus.OK, handleRequest(handler, "PUT", "/bucket/path/blob", body).status());
6183
assertEquals(new TestHttpResponse(RestStatus.OK, body), handleRequest(handler, "GET", "/bucket/path/blob"));
6284

63-
assertEquals(new TestHttpResponse(RestStatus.OK, """
64-
<?xml version="1.0" encoding="UTF-8"?><ListBucketResult><Prefix></Prefix>\
85+
assertListObjectsResponse(handler, "", null, """
86+
<?xml version="1.0" encoding="UTF-8"?><ListBucketResult><Prefix></Prefix><IsTruncated>false</IsTruncated>\
6587
<Contents><Key>path/blob</Key><Size>50</Size></Contents>\
66-
</ListBucketResult>"""), handleRequest(handler, "GET", "/bucket/?prefix="));
88+
</ListBucketResult>""");
6789

68-
assertEquals(new TestHttpResponse(RestStatus.OK, """
69-
<?xml version="1.0" encoding="UTF-8"?><ListBucketResult><Prefix>path/</Prefix>\
90+
assertListObjectsResponse(handler, "path/", null, """
91+
<?xml version="1.0" encoding="UTF-8"?><ListBucketResult><Prefix>path/</Prefix><IsTruncated>false</IsTruncated>\
7092
<Contents><Key>path/blob</Key><Size>50</Size></Contents>\
71-
</ListBucketResult>"""), handleRequest(handler, "GET", "/bucket/?prefix=path/"));
93+
</ListBucketResult>""");
7294

73-
assertEquals(
74-
new TestHttpResponse(RestStatus.OK, """
75-
<?xml version="1.0" encoding="UTF-8"?><ListBucketResult><Prefix>path/other</Prefix></ListBucketResult>"""),
76-
handleRequest(handler, "GET", "/bucket/?prefix=path/other")
77-
);
95+
assertListObjectsResponse(handler, "path/other", null, """
96+
<?xml version="1.0" encoding="UTF-8"?><ListBucketResult><Prefix>path/other</Prefix><IsTruncated>false</IsTruncated>\
97+
</ListBucketResult>""");
7898

7999
assertEquals(RestStatus.OK, handleRequest(handler, "PUT", "/bucket/path/subpath1/blob", randomAlphaOfLength(50)).status());
80100
assertEquals(RestStatus.OK, handleRequest(handler, "PUT", "/bucket/path/subpath2/blob", randomAlphaOfLength(50)).status());
81-
assertEquals(new TestHttpResponse(RestStatus.OK, """
82-
<?xml version="1.0" encoding="UTF-8"?><ListBucketResult><Prefix>path/</Prefix>\
83-
<Delimiter>/</Delimiter><Contents><Key>path/blob</Key><Size>50</Size></Contents>\
101+
assertListObjectsResponse(handler, "path/", "/", """
102+
<?xml version="1.0" encoding="UTF-8"?><ListBucketResult>\
103+
<Prefix>path/</Prefix><Delimiter>/</Delimiter><IsTruncated>false</IsTruncated>\
104+
<Contents><Key>path/blob</Key><Size>50</Size></Contents>\
84105
<CommonPrefixes><Prefix>path/subpath1/</Prefix></CommonPrefixes>\
85106
<CommonPrefixes><Prefix>path/subpath2/</Prefix></CommonPrefixes>\
86-
</ListBucketResult>"""), handleRequest(handler, "GET", "/bucket/?delimiter=/&prefix=path/"));
107+
</ListBucketResult>""");
87108

88109
assertEquals(RestStatus.OK, handleRequest(handler, "DELETE", "/bucket/path/blob").status());
89110
assertEquals(RestStatus.NO_CONTENT, handleRequest(handler, "DELETE", "/bucket/path/blob").status());
90111

91-
assertEquals(new TestHttpResponse(RestStatus.OK, """
92-
<?xml version="1.0" encoding="UTF-8"?><ListBucketResult><Prefix></Prefix>\
112+
assertListObjectsResponse(handler, "", null, """
113+
<?xml version="1.0" encoding="UTF-8"?><ListBucketResult><Prefix></Prefix><IsTruncated>false</IsTruncated>\
93114
<Contents><Key>path/subpath1/blob</Key><Size>50</Size></Contents>\
94115
<Contents><Key>path/subpath2/blob</Key><Size>50</Size></Contents>\
95-
</ListBucketResult>"""), handleRequest(handler, "GET", "/bucket/?prefix="));
116+
</ListBucketResult>""");
96117

97118
assertEquals(RestStatus.OK, handleRequest(handler, "DELETE", "/bucket/path/subpath1/blob").status());
98119
assertEquals(RestStatus.OK, handleRequest(handler, "DELETE", "/bucket/path/subpath2/blob").status());
99120

100-
assertEquals(
101-
new TestHttpResponse(RestStatus.OK, """
102-
<?xml version="1.0" encoding="UTF-8"?><ListBucketResult><Prefix></Prefix></ListBucketResult>"""),
103-
handleRequest(handler, "GET", "/bucket/?prefix=")
104-
);
121+
assertListObjectsResponse(handler, "", null, """
122+
<?xml version="1.0" encoding="UTF-8"?><ListBucketResult><Prefix></Prefix><IsTruncated>false</IsTruncated>\
123+
</ListBucketResult>""");
105124
}
106125

107126
public void testGetWithBytesRange() {
@@ -216,10 +235,10 @@ public void testSingleMultipartUpload() {
216235
</CompleteMultipartUpload>""", part1Etag, part2Etag))
217236
);
218237

219-
assertEquals(new TestHttpResponse(RestStatus.OK, """
220-
<?xml version="1.0" encoding="UTF-8"?><ListBucketResult><Prefix></Prefix>\
238+
assertListObjectsResponse(handler, "", null, """
239+
<?xml version="1.0" encoding="UTF-8"?><ListBucketResult><Prefix></Prefix><IsTruncated>false</IsTruncated>\
221240
<Contents><Key>path/blob</Key><Size>100</Size></Contents>\
222-
</ListBucketResult>"""), handleRequest(handler, "GET", "/bucket/?prefix="));
241+
</ListBucketResult>""");
223242

224243
assertEquals(new TestHttpResponse(RestStatus.OK, part1 + part2), handleRequest(handler, "GET", "/bucket/path/blob"));
225244

0 commit comments

Comments
 (0)