Skip to content

Commit 97bd139

Browse files
committed
Fix for S3Client with httpCLient as url-connection-client fails with EOFException when executing HeadObjectRequest for gzip encoded objec
1 parent f3265c6 commit 97bd139

File tree

4 files changed

+215
-1
lines changed

4 files changed

+215
-1
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"type": "bugfix",
3+
"category": "URL Connection HTTP Client",
4+
"contributor": "",
5+
"description": "Fix for S3Client with URL Connection http client fails with EOFException when executing HeadObjectRequest for gzip encodeFix to S3Client fails with EOFException."
6+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License").
5+
* You may not use this file except in compliance with the License.
6+
* A copy of the License is located at
7+
*
8+
* http://aws.amazon.com/apache2.0
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed
11+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
* express or implied. See the License for the specific language governing
13+
* permissions and limitations under the License.
14+
*/
15+
16+
package software.amazon.awssdk.http.urlconnection;
17+
18+
import static org.assertj.core.api.Assertions.assertThat;
19+
import static software.amazon.awssdk.testutils.service.S3BucketUtils.temporaryBucketName;
20+
21+
import java.io.ByteArrayOutputStream;
22+
import java.io.IOException;
23+
import java.nio.charset.StandardCharsets;
24+
import java.util.zip.GZIPOutputStream;
25+
import org.junit.AfterClass;
26+
import org.junit.BeforeClass;
27+
import org.junit.Test;
28+
import software.amazon.awssdk.core.sync.RequestBody;
29+
import software.amazon.awssdk.services.s3.model.HeadObjectResponse;
30+
import software.amazon.awssdk.services.s3.model.PutObjectRequest;
31+
32+
public class HeadObjectIntegrationTest extends UrlHttpConnectionS3IntegrationTestBase {
33+
private static final String BUCKET = temporaryBucketName(HeadObjectIntegrationTest.class);
34+
35+
private static final String GZIPPED_KEY = "some-key";
36+
37+
@BeforeClass
38+
public static void setupFixture() throws IOException {
39+
createBucket(BUCKET);
40+
41+
ByteArrayOutputStream baos = new ByteArrayOutputStream();
42+
GZIPOutputStream gzos = new GZIPOutputStream(baos);
43+
gzos.write("Test".getBytes(StandardCharsets.UTF_8));
44+
45+
s3.putObject(PutObjectRequest.builder()
46+
.bucket(BUCKET)
47+
.key(GZIPPED_KEY)
48+
.contentEncoding("gzip")
49+
.build(),
50+
RequestBody.fromBytes(baos.toByteArray()));
51+
}
52+
53+
@Test
54+
public void syncClientSupportsGzippedObjects() {
55+
HeadObjectResponse response = s3.headObject(r -> r.bucket(BUCKET).key(GZIPPED_KEY));
56+
assertThat(response.contentEncoding()).isEqualTo("gzip");
57+
}
58+
59+
@AfterClass
60+
public static void cleanup() {
61+
deleteBucketAndAllContents(BUCKET);
62+
}
63+
64+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License").
5+
* You may not use this file except in compliance with the License.
6+
* A copy of the License is located at
7+
*
8+
* http://aws.amazon.com/apache2.0
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed
11+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
* express or implied. See the License for the specific language governing
13+
* permissions and limitations under the License.
14+
*/
15+
16+
package software.amazon.awssdk.http.urlconnection;
17+
18+
import static org.assertj.core.api.Assertions.assertThat;
19+
20+
import java.util.Iterator;
21+
import java.util.List;
22+
import org.junit.BeforeClass;
23+
import software.amazon.awssdk.core.ClientType;
24+
import software.amazon.awssdk.core.interceptor.Context;
25+
import software.amazon.awssdk.core.interceptor.ExecutionAttributes;
26+
import software.amazon.awssdk.core.interceptor.ExecutionInterceptor;
27+
import software.amazon.awssdk.http.apache.ApacheHttpClient;
28+
import software.amazon.awssdk.regions.Region;
29+
import software.amazon.awssdk.services.s3.S3AsyncClient;
30+
import software.amazon.awssdk.services.s3.S3AsyncClientBuilder;
31+
import software.amazon.awssdk.services.s3.S3Client;
32+
import software.amazon.awssdk.services.s3.S3ClientBuilder;
33+
import software.amazon.awssdk.services.s3.model.BucketLocationConstraint;
34+
import software.amazon.awssdk.services.s3.model.CreateBucketConfiguration;
35+
import software.amazon.awssdk.services.s3.model.CreateBucketRequest;
36+
import software.amazon.awssdk.services.s3.model.DeleteBucketRequest;
37+
import software.amazon.awssdk.services.s3.model.DeleteObjectRequest;
38+
import software.amazon.awssdk.services.s3.model.ListObjectVersionsRequest;
39+
import software.amazon.awssdk.services.s3.model.ListObjectVersionsResponse;
40+
import software.amazon.awssdk.services.s3.model.ListObjectsRequest;
41+
import software.amazon.awssdk.services.s3.model.ListObjectsResponse;
42+
import software.amazon.awssdk.services.s3.model.NoSuchBucketException;
43+
import software.amazon.awssdk.services.s3.model.S3Exception;
44+
import software.amazon.awssdk.services.s3.model.S3Object;
45+
import software.amazon.awssdk.testutils.Waiter;
46+
import software.amazon.awssdk.testutils.service.AwsTestBase;
47+
48+
/**
49+
* Base class for S3 integration tests. Loads AWS credentials from a properties
50+
* file and creates an S3 client for callers to use.
51+
*/
52+
public class UrlHttpConnectionS3IntegrationTestBase extends AwsTestBase {
53+
54+
protected static final Region DEFAULT_REGION = Region.US_WEST_2;
55+
/**
56+
* The S3 client for all tests to use.
57+
*/
58+
protected static S3Client s3;
59+
60+
/**
61+
* Loads the AWS account info for the integration tests and creates an S3
62+
* client for tests to use.
63+
*/
64+
@BeforeClass
65+
public static void setUp() throws Exception {
66+
s3 = s3ClientBuilder().build();
67+
}
68+
69+
protected static S3ClientBuilder s3ClientBuilder() {
70+
return S3Client.builder()
71+
.httpClient(UrlConnectionHttpClient.create())
72+
.region(DEFAULT_REGION)
73+
.credentialsProvider(CREDENTIALS_PROVIDER_CHAIN);
74+
75+
}
76+
77+
78+
protected static void createBucket(String bucket) {
79+
Waiter.run(() -> s3.createBucket(r -> r.bucket(bucket)))
80+
.ignoringException(NoSuchBucketException.class)
81+
.orFail();
82+
}
83+
84+
protected static void deleteBucketAndAllContents(String bucketName) {
85+
deleteBucketAndAllContents(s3, bucketName);
86+
}
87+
88+
89+
public static void deleteBucketAndAllContents(S3Client s3, String bucketName) {
90+
try {
91+
System.out.println("Deleting S3 bucket: " + bucketName);
92+
ListObjectsResponse response = Waiter.run(() -> s3.listObjects(r -> r.bucket(bucketName)))
93+
.ignoringException(NoSuchBucketException.class)
94+
.orFail();
95+
List<S3Object> objectListing = response.contents();
96+
97+
if (objectListing != null) {
98+
while (true) {
99+
for (Iterator<?> iterator = objectListing.iterator(); iterator.hasNext(); ) {
100+
S3Object objectSummary = (S3Object) iterator.next();
101+
s3.deleteObject(DeleteObjectRequest.builder().bucket(bucketName).key(objectSummary.key()).build());
102+
}
103+
104+
if (response.isTruncated()) {
105+
objectListing = s3.listObjects(ListObjectsRequest.builder()
106+
.bucket(bucketName)
107+
.marker(response.marker())
108+
.build())
109+
.contents();
110+
} else {
111+
break;
112+
}
113+
}
114+
}
115+
116+
117+
ListObjectVersionsResponse versions = s3
118+
.listObjectVersions(ListObjectVersionsRequest.builder().bucket(bucketName).build());
119+
120+
if (versions.deleteMarkers() != null) {
121+
versions.deleteMarkers().forEach(v -> s3.deleteObject(DeleteObjectRequest.builder()
122+
.versionId(v.versionId())
123+
.bucket(bucketName)
124+
.key(v.key())
125+
.build()));
126+
}
127+
128+
if (versions.versions() != null) {
129+
versions.versions().forEach(v -> s3.deleteObject(DeleteObjectRequest.builder()
130+
.versionId(v.versionId())
131+
.bucket(bucketName)
132+
.key(v.key())
133+
.build()));
134+
}
135+
136+
s3.deleteBucket(DeleteBucketRequest.builder().bucket(bucketName).build());
137+
} catch (Exception e) {
138+
System.err.println("Failed to delete bucket: " + bucketName);
139+
e.printStackTrace();
140+
}
141+
}
142+
}

http-clients/url-connection-client/src/main/java/software/amazon/awssdk/http/urlconnection/UrlConnectionHttpClient.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -320,7 +320,9 @@ private Optional<OutputStream> tryGetOutputStream() {
320320
}
321321

322322
private Optional<InputStream> tryGetInputStream() {
323-
return getAndHandle100Bug(() -> invokeSafely(connection::getInputStream), true);
323+
return responseHasNoContent()
324+
? Optional.empty()
325+
: getAndHandle100Bug(() -> invokeSafely(connection::getInputStream), true);
324326
}
325327

326328
private Optional<InputStream> tryGetErrorStream() {

0 commit comments

Comments
 (0)