Skip to content

Commit 7ca84b8

Browse files
authored
Fixed an issue that the SDK unnecessarily bufferred the entire content for streaming operations (#5855)
1 parent bf0f2c7 commit 7ca84b8

File tree

6 files changed

+67
-9
lines changed

6 files changed

+67
-9
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": "AWS SDK for Java v2",
4+
"contributor": "",
5+
"description": "Fixed an issue in the SDK where it unnecessarily buffers the entire content for streaming operations, causing OOM error. See [#5850](https://github.com/aws/aws-sdk-java-v2/issues/5850)."
6+
}

build-tools/src/main/resources/software/amazon/awssdk/checkstyle.xml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -392,6 +392,15 @@
392392
<property name="ignoreComments" value="true"/>
393393
</module>
394394

395+
<!-- Checks that we don't use RequestBody.fromContentProvider in the SDK core -->
396+
<module name="Regexp">
397+
<property name="format" value="RequestBody\.fromContentProvider"/>
398+
<property name="illegalPattern" value="true"/>
399+
<property name="message" value="DO NOT use fromContentProvider for streaming operations because it will buffer the entire content.
400+
Add suppression if it's for a non-streaming operation"/>
401+
<property name="ignoreComments" value="true"/>
402+
</module>
403+
395404
<!-- Checks that we don't use AttributeKey.newInstance directly -->
396405
<module name="Regexp">
397406
<property name="format" value="AttributeKey\.newInstance"/>

core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/handler/BaseClientHandler.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -139,9 +139,9 @@ private static RequestBody getBody(SdkHttpFullRequest request) {
139139
streamProvider = () -> new SdkLengthAwareInputStream(toWrap.newStream(), contentLength);
140140
}
141141

142-
return RequestBody.fromContentProvider(streamProvider,
143-
contentLength,
144-
contentType);
142+
return new SdkInternalOnlyRequestBody(streamProvider,
143+
contentLength,
144+
contentType);
145145
}
146146

147147
return null;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
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.core.internal.handler;
17+
18+
import software.amazon.awssdk.annotations.SdkInternalApi;
19+
import software.amazon.awssdk.core.sync.RequestBody;
20+
import software.amazon.awssdk.http.ContentStreamProvider;
21+
22+
/**
23+
* This class is needed as an alternative to {@link RequestBody#fromContentProvider(ContentStreamProvider, long, String)},
24+
* which buffers the entire content.
25+
*
26+
* <p>
27+
* THIS IS AN INTERNAL API. DO NOT USE IT OUTSIDE THE AWS SDK FOR JAVA V2.
28+
*/
29+
@SdkInternalApi
30+
class SdkInternalOnlyRequestBody extends RequestBody {
31+
32+
protected SdkInternalOnlyRequestBody(ContentStreamProvider contentStreamProvider, Long contentLength, String contentType) {
33+
super(contentStreamProvider, contentLength, contentType);
34+
}
35+
}

core/sdk-core/src/main/java/software/amazon/awssdk/core/sync/RequestBody.java

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import java.nio.file.Path;
3131
import java.util.Arrays;
3232
import java.util.Optional;
33+
import software.amazon.awssdk.annotations.SdkInternalApi;
3334
import software.amazon.awssdk.annotations.SdkPublicApi;
3435
import software.amazon.awssdk.core.internal.sync.BufferingContentStreamProvider;
3536
import software.amazon.awssdk.core.internal.sync.FileContentStreamProvider;
@@ -43,16 +44,20 @@
4344
/**
4445
* Represents the body of an HTTP request. Must be provided for operations that have a streaming input.
4546
* Offers various convenience factory methods from common sources of data (File, String, byte[], etc).
47+
*
48+
* <p>
49+
* This class is NOT intended to be overridden.
4650
*/
4751
@SdkPublicApi
48-
public final class RequestBody {
52+
public class RequestBody {
4953

5054
// TODO Handle stream management (progress listener, orig input stream tracking, etc
5155
private final ContentStreamProvider contentStreamProvider;
5256
private final Long contentLength;
5357
private final String contentType;
5458

55-
private RequestBody(ContentStreamProvider contentStreamProvider, Long contentLength, String contentType) {
59+
@SdkInternalApi
60+
protected RequestBody(ContentStreamProvider contentStreamProvider, Long contentLength, String contentType) {
5661
this.contentStreamProvider = paramNotNull(contentStreamProvider, "contentStreamProvider");
5762
this.contentLength = contentLength != null ? isNotNegative(contentLength, "Content-length") : null;
5863
this.contentType = paramNotNull(contentType, "contentType");
@@ -61,7 +66,7 @@ private RequestBody(ContentStreamProvider contentStreamProvider, Long contentLen
6166
/**
6267
* @return RequestBody as an {@link InputStream}.
6368
*/
64-
public ContentStreamProvider contentStreamProvider() {
69+
public final ContentStreamProvider contentStreamProvider() {
6570
return contentStreamProvider;
6671
}
6772

@@ -70,7 +75,7 @@ public ContentStreamProvider contentStreamProvider() {
7075
* @return Content length of {@link RequestBody}.
7176
*/
7277
@Deprecated
73-
public long contentLength() {
78+
public final long contentLength() {
7479
validState(this.contentLength != null,
7580
"Content length is invalid, please use optionalContentLength() for your case.");
7681
return contentLength;
@@ -79,14 +84,14 @@ public long contentLength() {
7984
/**
8085
* @return Optional object of content length of {@link RequestBody}.
8186
*/
82-
public Optional<Long> optionalContentLength() {
87+
public final Optional<Long> optionalContentLength() {
8388
return Optional.ofNullable(contentLength);
8489
}
8590

8691
/**
8792
* @return Content type of {@link RequestBody}.
8893
*/
89-
public String contentType() {
94+
public final String contentType() {
9095
return contentType;
9196
}
9297

services/cloudsearchdomain/src/main/java/software/amazon/awssdk/services/cloudsearchdomain/internal/SwitchToPostInterceptor.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,10 +54,13 @@ public Optional<RequestBody> modifyHttpContent(Context.ModifyHttpRequest context
5454
if (context.request() instanceof SearchRequest) {
5555
byte[] params = context.httpRequest().encodedQueryParametersAsFormData().orElse("")
5656
.getBytes(StandardCharsets.UTF_8);
57+
// CHECKSTYLE:OFF - Avoid flagging the use of fromContentProvider. This is fine here because it's non-streaming
58+
// operation
5759
return Optional.of(RequestBody.fromContentProvider(() -> new ByteArrayInputStream(params),
5860
params.length,
5961
"application/x-www-form-urlencoded; charset=" +
6062
lowerCase(StandardCharsets.UTF_8.toString())));
63+
// CHECKSTYLE:ON
6164
}
6265
return context.requestBody();
6366
}

0 commit comments

Comments
 (0)