Skip to content

Commit 2819565

Browse files
committed
Add an Optional getter for the contentLength of RequestBody, also add a new fromContentProvider method allowing contentLength to be null.
1 parent 379ce26 commit 2819565

File tree

6 files changed

+88
-7
lines changed

6 files changed

+88
-7
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"category": "AWS SDK for Java v2",
3+
"contributor": "",
4+
"type": "feature",
5+
"description": "Add a new Optional getter for the content length of RequestBody, also add a new fromContentProvider method allowing contentLength to be null."
6+
}

core/sdk-core/src/main/java/software/amazon/awssdk/core/runtime/transform/StreamingRequestMarshaller.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717

1818
import static software.amazon.awssdk.http.Header.CONTENT_TYPE;
1919

20-
import java.util.Optional;
2120
import software.amazon.awssdk.annotations.SdkProtectedApi;
2221
import software.amazon.awssdk.core.internal.transform.AbstractStreamingRequestMarshaller;
2322
import software.amazon.awssdk.core.sync.RequestBody;
@@ -56,7 +55,7 @@ public SdkHttpFullRequest marshall(T in) {
5655
// Currently, SDK always require content length in RequestBody. So we always
5756
// send Content-Length header for sync APIs
5857
// This change will be useful if SDK relaxes the content-length requirement in RequestBody
59-
addHeaders(marshalled, Optional.of(requestBody.contentLength()), requiresLength, transferEncoding, useHttp2);
58+
addHeaders(marshalled, requestBody.optionalContentLength(), requiresLength, transferEncoding, useHttp2);
6059

6160
return marshalled.build();
6261
}

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

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

1818
import static software.amazon.awssdk.utils.FunctionalUtils.invokeSafely;
19+
import static software.amazon.awssdk.utils.Validate.isNotNegative;
1920
import static software.amazon.awssdk.utils.Validate.paramNotNull;
2021
import static software.amazon.awssdk.utils.Validate.validState;
2122

@@ -28,6 +29,7 @@
2829
import java.nio.file.Files;
2930
import java.nio.file.Path;
3031
import java.util.Arrays;
32+
import java.util.Optional;
3133
import software.amazon.awssdk.annotations.SdkPublicApi;
3234
import software.amazon.awssdk.core.internal.sync.FileContentStreamProvider;
3335
import software.amazon.awssdk.core.internal.util.Mimetype;
@@ -46,14 +48,13 @@ public final class RequestBody {
4648

4749
// TODO Handle stream management (progress listener, orig input stream tracking, etc
4850
private final ContentStreamProvider contentStreamProvider;
49-
private final long contentLength;
51+
private final Long contentLength;
5052
private final String contentType;
5153

52-
private RequestBody(ContentStreamProvider contentStreamProvider, long contentLength, String contentType) {
54+
private RequestBody(ContentStreamProvider contentStreamProvider, Long contentLength, String contentType) {
5355
this.contentStreamProvider = paramNotNull(contentStreamProvider, "contentStreamProvider");
54-
this.contentLength = contentLength;
56+
this.contentLength = contentLength != null ? isNotNegative(contentLength, "Content-length") : null;
5557
this.contentType = paramNotNull(contentType, "contentType");
56-
validState(contentLength >= 0, "Content length must be greater than or equal to zero");
5758
}
5859

5960
/**
@@ -64,12 +65,23 @@ public ContentStreamProvider contentStreamProvider() {
6465
}
6566

6667
/**
68+
* @deprecated by {@link #optionalContentLength()}
6769
* @return Content length of {@link RequestBody}.
6870
*/
71+
@Deprecated
6972
public long contentLength() {
73+
validState(this.contentLength != null,
74+
"Content length is invalid, please use optionalContentLength() for your case.");
7075
return contentLength;
7176
}
7277

78+
/**
79+
* @return Optional object of content length of {@link RequestBody}.
80+
*/
81+
public Optional<Long> optionalContentLength() {
82+
return Optional.ofNullable(contentLength);
83+
}
84+
7385
/**
7486
* @return Content type of {@link RequestBody}.
7587
*/
@@ -108,7 +120,8 @@ public static RequestBody fromFile(File file) {
108120
* could tamper with the sending of the request.
109121
* <p>
110122
* To support resetting via {@link ContentStreamProvider}, this uses {@link InputStream#reset()} and uses a read limit of
111-
* 128 KiB. If you need more control, use {@link #fromContentProvider(ContentStreamProvider, long, String)}.
123+
* 128 KiB. If you need more control, use {@link #fromContentProvider(ContentStreamProvider, long, String)} or
124+
* {@link #fromContentProvider(ContentStreamProvider, String)}.
112125
*
113126
* @param inputStream Input stream to send to the service. The stream will not be closed by the SDK.
114127
* @param contentLength Content length of data in input stream.
@@ -205,6 +218,18 @@ public static RequestBody fromContentProvider(ContentStreamProvider provider, lo
205218
return new RequestBody(provider, contentLength, mimeType);
206219
}
207220

221+
/**
222+
* Creates a {@link RequestBody} from the given {@link ContentStreamProvider}.
223+
*
224+
* @param provider The content provider.
225+
* @param mimeType The MIME type of the content.
226+
*
227+
* @return The created {@code RequestBody}.
228+
*/
229+
public static RequestBody fromContentProvider(ContentStreamProvider provider, String mimeType) {
230+
return new RequestBody(provider, null, mimeType);
231+
}
232+
208233
/**
209234
* Creates a {@link RequestBody} using the specified bytes (without copying).
210235
*/

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,11 @@
1616
package software.amazon.awssdk.core.sync;
1717

1818
import static org.assertj.core.api.Assertions.assertThat;
19+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
1920

2021
import com.google.common.jimfs.Configuration;
2122
import com.google.common.jimfs.Jimfs;
23+
import java.io.ByteArrayInputStream;
2224
import java.io.File;
2325
import java.io.FileWriter;
2426
import java.io.IOException;
@@ -104,6 +106,15 @@ public void emptyBytesConstructorHasCorrectContentType() {
104106
assertThat(requestBody.contentType()).isEqualTo(Mimetype.MIMETYPE_OCTET_STREAM);
105107
}
106108

109+
@Test
110+
public void contentProviderConstuctorWithNullContentLength_NoContentLength() {
111+
byte[] bytes = new byte[0];
112+
RequestBody requestBody = RequestBody.fromContentProvider(() -> new ByteArrayInputStream(bytes),
113+
Mimetype.MIMETYPE_OCTET_STREAM);
114+
assertThat(requestBody.optionalContentLength().isPresent()).isFalse();
115+
assertThatThrownBy(() -> requestBody.contentLength()).isInstanceOf(IllegalStateException.class);
116+
}
117+
107118
@Test
108119
public void remainingByteBufferConstructorOnlyRemainingBytesCopied() throws IOException {
109120
ByteBuffer bb = ByteBuffer.allocate(4);
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# Decision Log for SDK V2 RequestBody Content-Length
2+
3+
## Log Entry Template
4+
5+
**Source:** (Meeting/aside/pair programming discussion/daily standup) to (discuss/implement) X
6+
7+
**Attendees:** Anna-Karin, Dongie, Nico, John, Zoe
8+
9+
**Closed Decisions:**
10+
11+
1. Question? Decision. Justification.
12+
13+
**Open Decisions:**
14+
15+
1. (Old/Reopened/New) Question?
16+
17+
## 4/8/2021
18+
19+
**Source:** Meeting to discuss how to address the deprecated content-length getter after adding a new `Optional<Long>` getter in sync RequestBody: https://github.com/aws/aws-sdk-java-v2/blob/master/core/sdk-core/src/main/java/software/amazon/awssdk/core/sync/RequestBody.java#L66-L71
20+
21+
**Attendees:** Anna-Karin, Dongie, Nico, John, Zoe
22+
23+
**Closed Decisions:**
24+
25+
1. Should we replace the old content-length getter with a new optional getter? No, because that's going to be a breaking change, adding a Deprecated annotation is more acceptable.
26+
2. Should we use a negative value to denote the null content-length? Yes, because a negative value can be distinguished from normal zero content-length.
27+
3. Should we throw an exception in the deprecated getter when the content-length is negative? Yes, because with an exception the customers using this deprecated method would be notified that this method is deprecated and start to use the `Optional<Long>` getter. On the other hand, since the content-length was always greater or equal to zero before this change, throwing an exception here wouldn't break any existed customers.
28+
29+
**Open Decisions:**
30+
31+
None

utils/src/main/java/software/amazon/awssdk/utils/Validate.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -628,6 +628,15 @@ public static int isNotNegative(int num, String fieldName) {
628628
return num;
629629
}
630630

631+
public static long isNotNegative(long num, String fieldName) {
632+
633+
if (num < 0) {
634+
throw new IllegalArgumentException(String.format("%s must not be negative", fieldName));
635+
}
636+
637+
return num;
638+
}
639+
631640
/**
632641
* Asserts that the given duration is positive (non-negative and non-zero).
633642
*

0 commit comments

Comments
 (0)