Skip to content

Commit 2f98f89

Browse files
committed
feat(s3): add disableExpect100ContinueForPuts to S3Configuration
1 parent 1ac242b commit 2f98f89

6 files changed

Lines changed: 478 additions & 131 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 `disableExpect100ContinueForPuts` to `S3Configuration` to control the `Expect: 100-continue` header on PutObject and UploadPart requests. When set to `true`, the SDK stops adding the header. For Apache HTTP client users, to have `ApacheHttpClient.builder().expectContinueEnabled()` fully control the header, set `disableExpect100ContinueForPuts(true)` 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,17 @@ public final class S3Configuration implements ServiceConfiguration, ToCopyableBu
7071
*/
7172
private static final boolean DEFAULT_CHUNKED_ENCODING_ENABLED = true;
7273

74+
/**
75+
* By default, Expect: 100-continue is sent for {@link PutObjectRequest} and {@link UploadPartRequest}.
76+
*/
77+
private static final boolean DEFAULT_DISABLE_EXPECT_100_CONTINUE_FOR_PUTS = false;
78+
7379
private final FieldWithDefault<Boolean> pathStyleAccessEnabled;
7480
private final FieldWithDefault<Boolean> accelerateModeEnabled;
7581
private final FieldWithDefault<Boolean> dualstackEnabled;
7682
private final FieldWithDefault<Boolean> checksumValidationEnabled;
7783
private final FieldWithDefault<Boolean> chunkedEncodingEnabled;
84+
private final FieldWithDefault<Boolean> disableExpect100ContinueForPuts;
7885
private final Boolean useArnRegionEnabled;
7986
private final Boolean multiRegionEnabled;
8087
private final FieldWithDefault<Supplier<ProfileFile>> profileFile;
@@ -87,6 +94,8 @@ private S3Configuration(DefaultS3ServiceConfigurationBuilder builder) {
8794
this.checksumValidationEnabled = FieldWithDefault.create(builder.checksumValidationEnabled,
8895
DEFAULT_CHECKSUM_VALIDATION_ENABLED);
8996
this.chunkedEncodingEnabled = FieldWithDefault.create(builder.chunkedEncodingEnabled, DEFAULT_CHUNKED_ENCODING_ENABLED);
97+
this.disableExpect100ContinueForPuts =
98+
FieldWithDefault.create(builder.disableExpect100ContinueForPuts, DEFAULT_DISABLE_EXPECT_100_CONTINUE_FOR_PUTS);
9099
this.profileFile = FieldWithDefault.create(builder.profileFile, ProfileFile::defaultProfileFile);
91100
this.profileName = FieldWithDefault.create(builder.profileName,
92101
ProfileFileSystemSetting.AWS_PROFILE.getStringValueOrThrow());
@@ -216,6 +225,26 @@ public boolean chunkedEncodingEnabled() {
216225
return chunkedEncodingEnabled.value();
217226
}
218227

228+
/**
229+
* Returns whether the S3 SDK client's explicit setting of the {@code Expect: 100-continue} header is disabled for
230+
* {@link PutObjectRequest} and {@link UploadPartRequest}. This controls whether the SDK adds the header during
231+
* request interceptor processing.
232+
* <p>
233+
* By default, the SDK sends the {@code Expect: 100-continue} header for these operations, allowing the server to
234+
* reject the request before the client sends the full payload. Setting this to {@code true} disables this behavior.
235+
* <p>
236+
* <b>Note:</b> When using the Apache HTTP client, the Apache client also independently adds the
237+
* {@code Expect: 100-continue} header by default via its own {@code expectContinueEnabled} setting. To fully
238+
* suppress the header on the wire, you must also disable it on the Apache HTTP client builder using
239+
* {@code ApacheHttpClient.builder().expectContinueEnabled(false)}.
240+
*
241+
* @return True if the Expect: 100-continue header is disabled for put operations.
242+
* @see S3Configuration.Builder#disableExpect100ContinueForPuts(Boolean)
243+
*/
244+
public boolean disableExpect100ContinueForPuts() {
245+
return disableExpect100ContinueForPuts.value();
246+
}
247+
219248
/**
220249
* Returns whether the client is allowed to make cross-region calls when an S3 Access Point ARN has a different
221250
* region to the one configured on the client.
@@ -246,6 +275,7 @@ public Builder toBuilder() {
246275
.pathStyleAccessEnabled(pathStyleAccessEnabled.valueOrNullIfDefault())
247276
.checksumValidationEnabled(checksumValidationEnabled.valueOrNullIfDefault())
248277
.chunkedEncodingEnabled(chunkedEncodingEnabled.valueOrNullIfDefault())
278+
.disableExpect100ContinueForPuts(disableExpect100ContinueForPuts.valueOrNullIfDefault())
249279
.useArnRegionEnabled(useArnRegionEnabled)
250280
.profileFile(profileFile.valueOrNullIfDefault())
251281
.profileName(profileName.valueOrNullIfDefault());
@@ -355,6 +385,27 @@ public interface Builder extends CopyableBuilder<Builder, S3Configuration> {
355385
*/
356386
Builder chunkedEncodingEnabled(Boolean chunkedEncodingEnabled);
357387

388+
Boolean disableExpect100ContinueForPuts();
389+
390+
/**
391+
* Option to disable the S3 SDK client's explicit setting of the {@code Expect: 100-continue} header for
392+
* {@link PutObjectRequest} and {@link UploadPartRequest}. This controls whether the SDK adds the header
393+
* during request interceptor processing.
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 true} 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+
* Disabled by default (i.e., the header is sent).
404+
*
405+
* @see S3Configuration#disableExpect100ContinueForPuts()
406+
*/
407+
Builder disableExpect100ContinueForPuts(Boolean disableExpect100ContinueForPuts);
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 disableExpect100ContinueForPuts;
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 disableExpect100ContinueForPuts() {
556+
return disableExpect100ContinueForPuts;
505557
}
506558

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

563+
@Override
564+
public Builder disableExpect100ContinueForPuts(Boolean disableExpect100ContinueForPuts) {
565+
this.disableExpect100ContinueForPuts = disableExpect100ContinueForPuts;
566+
return this;
567+
}
568+
569+
public void setDisableExpect100ContinueForPuts(Boolean disableExpect100ContinueForPuts) {
570+
disableExpect100ContinueForPuts(disableExpect100ContinueForPuts);
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#disableExpect100ContinueForPuts()}.
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).disableExpect100ContinueForPuts();
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: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ public void createConfiguration_minimal() {
4646
assertThat(config.multiRegionEnabled()).isEqualTo(true);
4747
assertThat(config.pathStyleAccessEnabled()).isEqualTo(false);
4848
assertThat(config.useArnRegionEnabled()).isEqualTo(false);
49+
assertThat(config.disableExpect100ContinueForPuts()).isEqualTo(false);
4950
}
5051

5152
@Test

0 commit comments

Comments
 (0)