Skip to content

Commit 536eb46

Browse files
authored
feat(s3): add expectContinueEnabled to S3Configuration (#6774)
* feat(s3): add disableExpect100ContinueForPuts to S3Configuration * changed disableExpect100ContinueForPuts to expectContinueEnabled after api surface review discussions * fixed sonarquebe issue
1 parent ca05520 commit 536eb46

6 files changed

Lines changed: 488 additions & 138 deletions

File tree

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"type": "feature",
3+
"category": "Amazon S3",
4+
"contributor": "",
5+
"description": "Added `expectContinueEnabled` to `S3Configuration` to control the `Expect: 100-continue` header on PutObject and UploadPart requests. When set to `false`, the SDK stops adding the header. For Apache HTTP client users, to have `ApacheHttpClient.builder().expectContinueEnabled()` fully control the header, set `expectContinueEnabled(false)` on `S3Configuration`."
6+
}

services/s3/src/main/java/software/amazon/awssdk/services/s3/S3Configuration.java

Lines changed: 69 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
import software.amazon.awssdk.services.s3.model.GetObjectRequest;
3636
import software.amazon.awssdk.services.s3.model.PutBucketAccelerateConfigurationRequest;
3737
import software.amazon.awssdk.services.s3.model.PutObjectRequest;
38+
import software.amazon.awssdk.services.s3.model.UploadPartRequest;
3839
import software.amazon.awssdk.services.s3.presigner.S3Presigner;
3940
import software.amazon.awssdk.utils.builder.CopyableBuilder;
4041
import software.amazon.awssdk.utils.builder.ToCopyableBuilder;
@@ -70,11 +71,18 @@ public final class S3Configuration implements ServiceConfiguration, ToCopyableBu
7071
*/
7172
private static final boolean DEFAULT_CHUNKED_ENCODING_ENABLED = true;
7273

74+
/**
75+
* By default, the SDK sends the {@code Expect: 100-continue} header for {@link PutObjectRequest}
76+
* and {@link UploadPartRequest}.
77+
*/
78+
private static final boolean DEFAULT_EXPECT_CONTINUE_ENABLED = true;
79+
7380
private final FieldWithDefault<Boolean> pathStyleAccessEnabled;
7481
private final FieldWithDefault<Boolean> accelerateModeEnabled;
7582
private final FieldWithDefault<Boolean> dualstackEnabled;
7683
private final FieldWithDefault<Boolean> checksumValidationEnabled;
7784
private final FieldWithDefault<Boolean> chunkedEncodingEnabled;
85+
private final FieldWithDefault<Boolean> expectContinueEnabled;
7886
private final Boolean useArnRegionEnabled;
7987
private final Boolean multiRegionEnabled;
8088
private final FieldWithDefault<Supplier<ProfileFile>> profileFile;
@@ -87,6 +95,8 @@ private S3Configuration(DefaultS3ServiceConfigurationBuilder builder) {
8795
this.checksumValidationEnabled = FieldWithDefault.create(builder.checksumValidationEnabled,
8896
DEFAULT_CHECKSUM_VALIDATION_ENABLED);
8997
this.chunkedEncodingEnabled = FieldWithDefault.create(builder.chunkedEncodingEnabled, DEFAULT_CHUNKED_ENCODING_ENABLED);
98+
this.expectContinueEnabled = FieldWithDefault.create(builder.expectContinueEnabled,
99+
DEFAULT_EXPECT_CONTINUE_ENABLED);
90100
this.profileFile = FieldWithDefault.create(builder.profileFile, ProfileFile::defaultProfileFile);
91101
this.profileName = FieldWithDefault.create(builder.profileName,
92102
ProfileFileSystemSetting.AWS_PROFILE.getStringValueOrThrow());
@@ -216,6 +226,26 @@ public boolean chunkedEncodingEnabled() {
216226
return chunkedEncodingEnabled.value();
217227
}
218228

229+
/**
230+
* Returns whether the S3 SDK client's explicit setting of the {@code Expect: 100-continue} header is enabled for
231+
* {@link PutObjectRequest} and {@link UploadPartRequest}. This controls whether the SDK adds the header during
232+
* request interceptor processing.
233+
* <p>
234+
* By default, the SDK sends the {@code Expect: 100-continue} header for these operations, allowing the server to
235+
* reject the request before the client sends the full payload. Setting this to {@code false} disables this behavior.
236+
* <p>
237+
* <b>Note:</b> When using the Apache HTTP client, the Apache client also independently adds the
238+
* {@code Expect: 100-continue} header by default via its own {@code expectContinueEnabled} setting. To fully
239+
* suppress the header on the wire, you must also disable it on the Apache HTTP client builder using
240+
* {@code ApacheHttpClient.builder().expectContinueEnabled(false)}.
241+
*
242+
* @return True if the Expect: 100-continue header is enabled.
243+
* @see S3Configuration.Builder#expectContinueEnabled(Boolean)
244+
*/
245+
public boolean expectContinueEnabled() {
246+
return expectContinueEnabled.value();
247+
}
248+
219249
/**
220250
* Returns whether the client is allowed to make cross-region calls when an S3 Access Point ARN has a different
221251
* region to the one configured on the client.
@@ -246,6 +276,7 @@ public Builder toBuilder() {
246276
.pathStyleAccessEnabled(pathStyleAccessEnabled.valueOrNullIfDefault())
247277
.checksumValidationEnabled(checksumValidationEnabled.valueOrNullIfDefault())
248278
.chunkedEncodingEnabled(chunkedEncodingEnabled.valueOrNullIfDefault())
279+
.expectContinueEnabled(expectContinueEnabled.valueOrNullIfDefault())
249280
.useArnRegionEnabled(useArnRegionEnabled)
250281
.profileFile(profileFile.valueOrNullIfDefault())
251282
.profileName(profileName.valueOrNullIfDefault());
@@ -355,6 +386,26 @@ public interface Builder extends CopyableBuilder<Builder, S3Configuration> {
355386
*/
356387
Builder chunkedEncodingEnabled(Boolean chunkedEncodingEnabled);
357388

389+
Boolean expectContinueEnabled();
390+
391+
/**
392+
* Option to enable or disable the S3 SDK client's explicit setting of the {@code Expect: 100-continue} header
393+
* for {@link PutObjectRequest} and {@link UploadPartRequest}.
394+
* <p>
395+
* By default, the SDK sends the {@code Expect: 100-continue} header for these operations, allowing the server to
396+
* reject the request before the client sends the full payload. Setting this to {@code false} disables this behavior.
397+
* <p>
398+
* <b>Note:</b> When using the Apache HTTP client, the Apache client also independently adds the
399+
* {@code Expect: 100-continue} header by default via its own {@code expectContinueEnabled} setting. To fully
400+
* suppress the header on the wire, you must also disable it on the Apache HTTP client builder using
401+
* {@code ApacheHttpClient.builder().expectContinueEnabled(false)}.
402+
* <p>
403+
* Enabled by default (i.e., the header is sent).
404+
*
405+
* @see S3Configuration#expectContinueEnabled()
406+
*/
407+
Builder expectContinueEnabled(Boolean expectContinueEnabled);
408+
358409
Boolean useArnRegionEnabled();
359410

360411
/**
@@ -423,6 +474,7 @@ static final class DefaultS3ServiceConfigurationBuilder implements Builder {
423474
private Boolean pathStyleAccessEnabled;
424475
private Boolean checksumValidationEnabled;
425476
private Boolean chunkedEncodingEnabled;
477+
private Boolean expectContinueEnabled;
426478
private Boolean useArnRegionEnabled;
427479
private Boolean multiRegionEnabled;
428480
private Supplier<ProfileFile> profileFile;
@@ -500,14 +552,29 @@ public Builder chunkedEncodingEnabled(Boolean chunkedEncodingEnabled) {
500552
}
501553

502554
@Override
503-
public Boolean useArnRegionEnabled() {
504-
return useArnRegionEnabled;
555+
public Boolean expectContinueEnabled() {
556+
return expectContinueEnabled;
505557
}
506558

507559
public void setChunkedEncodingEnabled(Boolean chunkedEncodingEnabled) {
508560
chunkedEncodingEnabled(chunkedEncodingEnabled);
509561
}
510562

563+
@Override
564+
public Builder expectContinueEnabled(Boolean expectContinueEnabled) {
565+
this.expectContinueEnabled = expectContinueEnabled;
566+
return this;
567+
}
568+
569+
public void setExpectContinueEnabled(Boolean expectContinueEnabled) {
570+
expectContinueEnabled(expectContinueEnabled);
571+
}
572+
573+
@Override
574+
public Boolean useArnRegionEnabled() {
575+
return useArnRegionEnabled;
576+
}
577+
511578
@Override
512579
public Builder useArnRegionEnabled(Boolean useArnRegionEnabled) {
513580
this.useArnRegionEnabled = useArnRegionEnabled;

services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/handlers/StreamingRequestInterceptor.java

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,19 @@
1717

1818
import java.util.Optional;
1919
import software.amazon.awssdk.annotations.SdkInternalApi;
20+
import software.amazon.awssdk.core.ServiceConfiguration;
2021
import software.amazon.awssdk.core.interceptor.Context;
2122
import software.amazon.awssdk.core.interceptor.ExecutionAttributes;
2223
import software.amazon.awssdk.core.interceptor.ExecutionInterceptor;
24+
import software.amazon.awssdk.core.interceptor.SdkExecutionAttribute;
2325
import software.amazon.awssdk.http.SdkHttpRequest;
26+
import software.amazon.awssdk.services.s3.S3Configuration;
2427
import software.amazon.awssdk.services.s3.model.PutObjectRequest;
2528
import software.amazon.awssdk.services.s3.model.UploadPartRequest;
2629

2730
/**
28-
* Interceptor to add an 'Expect: 100-continue' header to the HTTP Request if it represents a PUT Object request.
31+
* Interceptor to add an 'Expect: 100-continue' header to the HTTP Request if it represents a PUT Object or Upload Part
32+
* request. This behavior can be disabled via {@link S3Configuration#expectContinueEnabled()}.
2933
*/
3034
@SdkInternalApi
3135
//TODO: This should be generalized for all streaming requests
@@ -37,24 +41,38 @@ public final class StreamingRequestInterceptor implements ExecutionInterceptor {
3741
@Override
3842
public SdkHttpRequest modifyHttpRequest(Context.ModifyHttpRequest context,
3943
ExecutionAttributes executionAttributes) {
40-
if (shouldAddExpectContinueHeader(context)) {
44+
if (shouldAddExpectContinueHeader(context, executionAttributes)) {
4145
return context.httpRequest().toBuilder().putHeader("Expect", "100-continue").build();
4246
}
4347
return context.httpRequest();
4448
}
4549

46-
private boolean shouldAddExpectContinueHeader(Context.ModifyHttpRequest context) {
50+
private boolean shouldAddExpectContinueHeader(Context.ModifyHttpRequest context,
51+
ExecutionAttributes executionAttributes) {
4752
// Only applies to streaming operations
4853
if (!(context.request() instanceof PutObjectRequest
4954
|| context.request() instanceof UploadPartRequest)) {
5055
return false;
5156
}
57+
58+
if (isExpect100ContinueDisabled(executionAttributes)) {
59+
return false;
60+
}
61+
5262
return getContentLengthHeader(context.httpRequest())
5363
.map(Long::parseLong)
5464
.map(length -> length != 0L)
5565
.orElse(true);
5666
}
5767

68+
private boolean isExpect100ContinueDisabled(ExecutionAttributes executionAttributes) {
69+
ServiceConfiguration serviceConfig = executionAttributes.getAttribute(SdkExecutionAttribute.SERVICE_CONFIG);
70+
if (serviceConfig instanceof S3Configuration) {
71+
return !((S3Configuration) serviceConfig).expectContinueEnabled();
72+
}
73+
return false;
74+
}
75+
5876
/**
5977
* Retrieves content length header value.
6078
* Checks x-amz-decoded-content-length first, then falls back to Content-Length.

services/s3/src/test/java/software/amazon/awssdk/services/s3/S3ConfigurationTest.java

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -39,13 +39,14 @@ public void clearSystemProperty() {
3939
@Test
4040
public void createConfiguration_minimal() {
4141
S3Configuration config = S3Configuration.builder().build();
42-
assertThat(config.accelerateModeEnabled()).isEqualTo(false);
43-
assertThat(config.checksumValidationEnabled()).isEqualTo(true);
44-
assertThat(config.chunkedEncodingEnabled()).isEqualTo(true);
45-
assertThat(config.dualstackEnabled()).isEqualTo(false);
46-
assertThat(config.multiRegionEnabled()).isEqualTo(true);
47-
assertThat(config.pathStyleAccessEnabled()).isEqualTo(false);
48-
assertThat(config.useArnRegionEnabled()).isEqualTo(false);
42+
assertThat(config.accelerateModeEnabled()).isFalse();
43+
assertThat(config.checksumValidationEnabled()).isTrue();
44+
assertThat(config.chunkedEncodingEnabled()).isTrue();
45+
assertThat(config.dualstackEnabled()).isFalse();
46+
assertThat(config.multiRegionEnabled()).isTrue();
47+
assertThat(config.pathStyleAccessEnabled()).isFalse();
48+
assertThat(config.useArnRegionEnabled()).isFalse();
49+
assertThat(config.expectContinueEnabled()).isTrue();
4950
}
5051

5152
@Test

0 commit comments

Comments
 (0)