Skip to content

Commit 4958a32

Browse files
committed
Add changelog files, fix tests, update to throw IllegalStateException and add more tests
1 parent 5f2af77 commit 4958a32

File tree

9 files changed

+71
-69
lines changed

9 files changed

+71
-69
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"type": "feature",
3+
"category": "AWS SDK for Java v2",
4+
"contributor": "",
5+
"description": "The SDK now throws exception for input streaming operation if the stream has fewer bytes (i.e. reaches EOF) before the expected length is reached."
6+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"type": "feature",
3+
"category": "AWS SDK for Java v2",
4+
"contributor": "",
5+
"description": "The SDK now does not buffer input data from `RequestBody#fromInputStream` in cases where the InputStream does not support mark and reset."
6+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"type": "feature",
3+
"category": "AWS SDK for Java v2",
4+
"contributor": "",
5+
"description": "The SDK now does not buffer input data from ContentStreamProvider in cases where content length is known."
6+
}

core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/http/pipeline/stages/MakeHttpRequestStage.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515

1616
package software.amazon.awssdk.core.internal.http.pipeline.stages;
1717

18+
import static software.amazon.awssdk.http.Header.CONTENT_LENGTH;
19+
1820
import java.time.Duration;
1921
import java.util.Optional;
2022
import software.amazon.awssdk.annotations.SdkInternalApi;
@@ -111,7 +113,7 @@ private static SdkHttpFullRequest enforceContentLengthIfPresent(SdkHttpFullReque
111113

112114
Optional<Long> contentLength = contentLength(request);
113115
if (!contentLength.isPresent()) {
114-
LOG.warn(() -> String.format("Request contains a body but does not have a Content-Length header. Not validating "
116+
LOG.debug(() -> String.format("Request contains a body but does not have a Content-Length header. Not validating "
115117
+ "the amount of data sent to the service: %s", request));
116118
return request;
117119
}
@@ -125,7 +127,7 @@ private static SdkHttpFullRequest enforceContentLengthIfPresent(SdkHttpFullReque
125127
}
126128

127129
private static Optional<Long> contentLength(SdkHttpFullRequest request) {
128-
Optional<String> contentLengthHeader = request.firstMatchingHeader("Content-Length");
130+
Optional<String> contentLengthHeader = request.firstMatchingHeader(CONTENT_LENGTH);
129131

130132
if (contentLengthHeader.isPresent()) {
131133
try {

core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/io/SdkLengthAwareInputStream.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,9 @@ public int read() throws IOException {
5353
if (read != -1) {
5454
remaining--;
5555
} else if (remaining != 0) { // EOF, ensure we've read the number of expected bytes
56-
throw new IOException("Reached EOF before reading entire expected content");
56+
throw new IllegalStateException("The request content has fewer bytes than the "
57+
+ "specified "
58+
+ "content-length: " + length + " bytes.");
5759
}
5860
return read;
5961
}
@@ -74,7 +76,9 @@ public int read(byte[] b, int off, int len) throws IOException {
7476

7577
// EOF, ensure we've read the number of expected bytes
7678
if (read == -1 && remaining != 0) {
77-
throw new IOException("Reached EOF before reading entire expected content");
79+
throw new IllegalStateException("The request content has fewer bytes than the "
80+
+ "specified "
81+
+ "content-length: " + length + " bytes.");
7882
}
7983

8084
return read;

core/sdk-core/src/test/java/software/amazon/awssdk/core/internal/io/SdkLengthAwareInputStreamTest.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -179,8 +179,8 @@ void readArray_delegateShorterThanExpected_throws() {
179179
while ((read = is.read(buff, 0, buff.length)) != -1) {
180180
}
181181
})
182-
.isInstanceOf(IOException.class)
183-
.hasMessage("Reached EOF before reading entire expected content");
182+
.isInstanceOf(IllegalStateException.class)
183+
.hasMessageContaining("The request content has fewer bytes than the specified content-length");
184184
}
185185

186186
@Test
@@ -230,8 +230,8 @@ void readByte_delegateShorterThanExpected_throws() {
230230
while ((read = is.read()) != -1) {
231231
}
232232
})
233-
.isInstanceOf(IOException.class)
234-
.hasMessage("Reached EOF before reading entire expected content");
233+
.isInstanceOf(IllegalStateException.class)
234+
.hasMessageContaining("The request content has fewer bytes than the specified content-length");
235235
}
236236

237237
@Test

core/sdk-core/src/test/java/software/amazon/awssdk/core/internal/sync/BufferingContentStreamProviderTest.java

Lines changed: 2 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -122,49 +122,11 @@ public void newStream_delegateStreamClosedOnBufferingStreamClose() throws IOExce
122122
}
123123

124124
@Test
125-
public void newStream_lengthKnown_readUpToLengthThenClosed_newStreamUsesBufferedData() throws IOException {
125+
public void newStream_lengthKnown_doesNotBuffer() throws IOException {
126126
ByteArrayInputStream stream = new ByteArrayInputStream(TEST_DATA);
127127
requestBody = RequestBody.fromContentProvider(() -> stream, TEST_DATA.length, "text/plain");
128-
129-
int totalRead = 0;
130-
int read;
131-
132-
InputStream stream1 = requestBody.contentStreamProvider().newStream();
133-
do {
134-
read = stream1.read();
135-
if (read != -1) {
136-
++totalRead;
137-
}
138-
} while (read != -1);
139-
140-
assertThat(totalRead).isEqualTo(TEST_DATA.length);
141-
142-
stream1.close();
143-
144128
assertThat(requestBody.contentStreamProvider().newStream())
145-
.isInstanceOf(BufferingContentStreamProvider.ByteArrayStream.class);
146-
}
147-
148-
@Test
149-
public void newStream_lengthKnown_partialRead_close_doesNotBufferData() throws IOException {
150-
// We need a large buffer because BufferedInputStream buffers data in chunks. If the buffer is small enough, a single
151-
// read() on the BufferedInputStream might actually buffer all the delegate's data.
152-
153-
byte[] newData = new byte[16536];
154-
new Random().nextBytes(newData);
155-
ByteArrayInputStream stream = new ByteArrayInputStream(newData);
156-
requestBody = RequestBody.fromContentProvider(() -> stream, newData.length, "text/plain");
157-
158-
InputStream stream1 = requestBody.contentStreamProvider().newStream();
159-
int read = stream1.read();
160-
assertThat(read).isNotEqualTo(-1);
161-
162-
stream1.close();
163-
164-
InputStream stream2 = requestBody.contentStreamProvider().newStream();
165-
assertThat(stream2).isInstanceOf(BufferingContentStreamProvider.BufferStream.class);
166-
167-
assertThat(getCrc32(stream2)).isEqualTo(getCrc32(new ByteArrayInputStream(newData)));
129+
.isInstanceOf(ByteArrayInputStream.class);
168130
}
169131

170132
@Test

test/architecture-tests/src/test/java/software/amazon/awssdk/archtests/CodingConventionWithSuppressionTest.java

Lines changed: 5 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import java.util.Set;
3333
import java.util.regex.Pattern;
3434
import org.junit.jupiter.api.Test;
35+
import software.amazon.awssdk.core.internal.http.pipeline.stages.MakeHttpRequestStage;
3536
import software.amazon.awssdk.metrics.publishers.emf.EmfMetricLoggingPublisher;
3637
import software.amazon.awssdk.metrics.publishers.emf.internal.MetricEmfConverter;
3738
import software.amazon.awssdk.utils.Logger;
@@ -44,25 +45,13 @@
4445
*/
4546
public class CodingConventionWithSuppressionTest {
4647

47-
/**
48-
* Suppressions for APIs used in generated code to avoid having to update archunit_store for new services. Unfortunately, we
49-
* can't change the following because it may break people :(
50-
* <p>
51-
* DO NOT ADD NEW EXCEPTIONS
52-
*/
5348
private static final Set<Pattern> ALLOWED_WARN_LOG_SUPPRESSION = new HashSet<>(
54-
Arrays.asList(ArchUtils.classNameToPattern(EmfMetricLoggingPublisher.class), ArchUtils.classNameToPattern(MetricEmfConverter.class))
55-
);
49+
Arrays.asList(ArchUtils.classNameToPattern(EmfMetricLoggingPublisher.class),
50+
ArchUtils.classNameToPattern(MetricEmfConverter.class),
51+
ArchUtils.classNameToPattern(MakeHttpRequestStage.class)));
5652

57-
/**
58-
* Suppressions for APIs used in generated code to avoid having to update archunit_store for new services. Unfortunately, we
59-
* can't change the following because it may break people :(
60-
* <p>
61-
* DO NOT ADD NEW EXCEPTIONS
62-
*/
6353
private static final Set<Pattern> ALLOWED_ERROR_LOG_SUPPRESSION = new HashSet<>(
64-
Arrays.asList(ArchUtils.classNameToPattern(EmfMetricLoggingPublisher.class))
65-
);
54+
Arrays.asList(ArchUtils.classNameToPattern(EmfMetricLoggingPublisher.class)));
6655

6756
@Test
6857
void shouldNotAbuseWarnLog() {

test/protocol-tests/src/test/java/software/amazon/awssdk/protocol/tests/TruncatedRequestStreamTest.java renamed to test/protocol-tests/src/test/java/software/amazon/awssdk/protocol/tests/StreamingRequestInputStreamLengthAwareTest.java

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
package software.amazon.awssdk.protocol.tests;
1717

1818
import static org.assertj.core.api.Assertions.assertThat;
19+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
1920
import static org.mockito.ArgumentMatchers.any;
2021
import static org.mockito.Mockito.mock;
2122
import static org.mockito.Mockito.verify;
@@ -25,12 +26,17 @@
2526
import java.io.IOException;
2627
import java.io.InputStream;
2728
import java.nio.charset.StandardCharsets;
29+
import java.util.stream.Stream;
2830
import org.junit.jupiter.api.AfterEach;
2931
import org.junit.jupiter.api.BeforeEach;
3032
import org.junit.jupiter.api.Test;
33+
import org.junit.jupiter.params.ParameterizedTest;
34+
import org.junit.jupiter.params.provider.Arguments;
35+
import org.junit.jupiter.params.provider.MethodSource;
3136
import org.mockito.ArgumentCaptor;
3237
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
3338
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
39+
import software.amazon.awssdk.core.internal.util.Mimetype;
3440
import software.amazon.awssdk.core.sync.RequestBody;
3541
import software.amazon.awssdk.http.AbortableInputStream;
3642
import software.amazon.awssdk.http.ContentStreamProvider;
@@ -43,10 +49,11 @@
4349
import software.amazon.awssdk.services.protocolrestjson.ProtocolRestJsonClient;
4450
import software.amazon.awssdk.services.protocolrestjson.model.StreamingInputOperationRequest;
4551

46-
public class TruncatedRequestStreamTest {
52+
public class StreamingRequestInputStreamLengthAwareTest {
4753
private ProtocolRestJsonClient client;
4854
private SdkHttpClient mockHttpClient;
4955

56+
5057
@BeforeEach
5158
public void setup() throws IOException {
5259
mockHttpClient = mock(SdkHttpClient.class);
@@ -82,8 +89,7 @@ public void streamingRequest_specifiedLengthTruncatesStream_lengthHonored() thro
8289
int bodyLength = 16;
8390

8491
client.streamingInputOperation(StreamingInputOperationRequest.builder().build(),
85-
bodyFromStream(32, bodyLength));
86-
92+
bodyFromInputStream(32, bodyLength));
8793
ArgumentCaptor<HttpExecuteRequest> requestCaptor = ArgumentCaptor.forClass(HttpExecuteRequest.class);
8894

8995
verify(mockHttpClient).prepareRequest(requestCaptor.capture());
@@ -96,9 +102,30 @@ public void streamingRequest_specifiedLengthTruncatesStream_lengthHonored() thro
96102
assertThat(drainStream(streamProvider.newStream())).isEqualTo(bodyLength);
97103
}
98104

99-
private static RequestBody bodyFromStream(int streamLength, int bodyLength) {
105+
106+
public static Stream<Arguments> requestBodies() {
107+
return Stream.of(Arguments.of(bodyFromInputStream(32, 64), 64),
108+
Arguments.of(bodyFromContentProvider(32, 50), 50));
109+
}
110+
111+
@ParameterizedTest
112+
@MethodSource("requestBodies")
113+
public void streamingRequest_streamLessThanContentLength_shouldThrowException(RequestBody body, long contentLength) throws IOException {
114+
assertThatThrownBy(() -> client.streamingInputOperation(StreamingInputOperationRequest.builder().build(),
115+
body)).isInstanceOf(IllegalStateException.class)
116+
.hasMessageContaining("The request content has fewer "
117+
+ "bytes than the specified "
118+
+ "content-length");
119+
}
120+
121+
private static RequestBody bodyFromInputStream(int streamLength, int contentLength) {
122+
InputStream is = new ByteArrayInputStream(new byte[streamLength]);
123+
return RequestBody.fromInputStream(is, contentLength);
124+
}
125+
126+
private static RequestBody bodyFromContentProvider(int streamLength, int contentLength) {
100127
InputStream is = new ByteArrayInputStream(new byte[streamLength]);
101-
return RequestBody.fromInputStream(is, bodyLength);
128+
return RequestBody.fromContentProvider(() -> is, contentLength, Mimetype.MIMETYPE_OCTET_STREAM);
102129
}
103130

104131
private static int drainStream(InputStream is) throws IOException {

0 commit comments

Comments
 (0)