diff --git a/hadoop-tools/hadoop-aws/src/main/java/org/apache/hadoop/fs/s3a/Constants.java b/hadoop-tools/hadoop-aws/src/main/java/org/apache/hadoop/fs/s3a/Constants.java
index 271e9302a3d74d..26877c9558b921 100644
--- a/hadoop-tools/hadoop-aws/src/main/java/org/apache/hadoop/fs/s3a/Constants.java
+++ b/hadoop-tools/hadoop-aws/src/main/java/org/apache/hadoop/fs/s3a/Constants.java
@@ -1353,16 +1353,21 @@ private Constants() {
*/
public static final String CUSTOM_HEADERS_POSTFIX = ".custom.headers";
+ /** Custom per-request headers postfix.
+ * value: {@value}
+ */
+ public static final String CUSTOM_PER_REQUEST_HEADERS_POSTFIX = ".request";
+
/**
* List of custom headers to be set on the service client.
* Multiple parameters can be used to specify custom headers.
*
* Usage:
- * fs.s3a.client.s3.custom.headers - Headers to add on all the S3 requests.
- * fs.s3a.client.sts.custom.headers - Headers to add on all the STS requests.
+ * fs.s3a.client.s3.custom.headers - Headers to add to all S3 requests.
+ * fs.s3a.client.sts.custom.headers - Headers to add to all STS requests.
*
* Examples:
- * CustomHeader {@literal ->} 'Header1:Value1'
+ * CustomHeader {@literal ->} 'Header1=Value1'
* CustomHeaders {@literal ->} 'Header1=Value1;Value2,Header2=Value1'
*
*/
@@ -1374,6 +1379,31 @@ private Constants() {
FS_S3A_CLIENT_PREFIX + AWS_SERVICE_IDENTIFIER_S3.toLowerCase(Locale.ROOT)
+ CUSTOM_HEADERS_POSTFIX;
+ /**
+ * List of custom per-request-type headers to be set on the service client.
+ * Multiple parameters can be used to specify custom headers.
+ *
+ * Usage:
+ * fs.s3a.client.s3.custom.headers.request.REQUEST - Headers to add to all S3 REQUEST requests.
+ * fs.s3a.client.sts.custom.headers.request.REQUEST - Headers to add to all STS REQUEST requests.
+ *
+ * Note: REQUEST refers to the AWS S3 request name.
+ * See subclasses of S3Request
+ * and subclasses of StsRequest
+ * for all existing requests.
+ *
+ * Examples:
+ * fs.s3a.client.s3.custom.headers.request.DeleteObjectRequest
+ * CustomHeader {@literal ->} 'Header1=Value1'
+ * CustomHeaders {@literal ->} 'Header1=Value1;Value2,Header2=Value1'
+ *
+ */
+ public static final String CUSTOM_REQUEST_HEADERS_STS_PREFIX =
+ CUSTOM_HEADERS_STS + CUSTOM_PER_REQUEST_HEADERS_POSTFIX + ".";
+
+ public static final String CUSTOM_REQUEST_HEADERS_S3_PREFIX =
+ CUSTOM_HEADERS_S3 + CUSTOM_PER_REQUEST_HEADERS_POSTFIX + ".";
+
/**
* How long to wait for the thread pool to terminate when cleaning up.
* Value: {@value} seconds.
diff --git a/hadoop-tools/hadoop-aws/src/main/java/org/apache/hadoop/fs/s3a/impl/AWSClientConfig.java b/hadoop-tools/hadoop-aws/src/main/java/org/apache/hadoop/fs/s3a/impl/AWSClientConfig.java
index 2627b90037c45f..59613a14f7159c 100644
--- a/hadoop-tools/hadoop-aws/src/main/java/org/apache/hadoop/fs/s3a/impl/AWSClientConfig.java
+++ b/hadoop-tools/hadoop-aws/src/main/java/org/apache/hadoop/fs/s3a/impl/AWSClientConfig.java
@@ -23,9 +23,12 @@
import java.net.URISyntaxException;
import java.time.Duration;
import java.util.Arrays;
+import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
import java.util.stream.Collectors;
import org.slf4j.Logger;
@@ -82,6 +85,8 @@
import static org.apache.hadoop.fs.s3a.Constants.USER_AGENT_PREFIX;
import static org.apache.hadoop.fs.s3a.Constants.CUSTOM_HEADERS_S3;
import static org.apache.hadoop.fs.s3a.Constants.CUSTOM_HEADERS_STS;
+import static org.apache.hadoop.fs.s3a.Constants.CUSTOM_REQUEST_HEADERS_S3_PREFIX;
+import static org.apache.hadoop.fs.s3a.Constants.CUSTOM_REQUEST_HEADERS_STS_PREFIX;
import static org.apache.hadoop.fs.s3a.impl.ConfigurationHelper.enforceMinimumDuration;
import static org.apache.hadoop.fs.s3a.impl.ConfigurationHelper.getDuration;
import static org.apache.hadoop.util.Preconditions.checkArgument;
@@ -420,6 +425,32 @@ private static void initSigner(Configuration conf,
}
}
+ /**
+ * Parses header configuration at the given key. Calls apply callback for headers with
+ * non-empty header values list. Calls ignore callback for headers with empty header values list.
+ *
+ * @param conf hadoop configuration
+ * @param configKey configuration key
+ * @param apply apply callback
+ * @param ignore ignore callback
+ */
+ private static void applyHeaders(Configuration conf, String configKey,
+ BiConsumer> apply, Consumer ignore) {
+ Map awsClientCustomHeadersMap =
+ S3AUtils.getTrimmedStringCollectionSplitByEquals(conf, configKey);
+ awsClientCustomHeadersMap.forEach((header, valueString) -> {
+ List headerValues = Arrays.stream(valueString.split(";"))
+ .map(String::trim)
+ .filter(v -> !v.isEmpty())
+ .collect(Collectors.toList());
+ if (!headerValues.isEmpty()) {
+ apply.accept(header, headerValues);
+ } else {
+ ignore.accept(header);
+ }
+ });
+ }
+
/**
* Initialize custom request headers for AWS clients.
* @param conf hadoop configuration
@@ -429,32 +460,44 @@ private static void initSigner(Configuration conf,
private static void initRequestHeaders(Configuration conf,
ClientOverrideConfiguration.Builder clientConfig, String awsServiceIdentifier) {
String configKey = null;
+ String configKeyPrefix = null;
switch (awsServiceIdentifier) {
case AWS_SERVICE_IDENTIFIER_S3:
configKey = CUSTOM_HEADERS_S3;
+ configKeyPrefix = CUSTOM_REQUEST_HEADERS_S3_PREFIX;
break;
case AWS_SERVICE_IDENTIFIER_STS:
configKey = CUSTOM_HEADERS_STS;
+ configKeyPrefix = CUSTOM_REQUEST_HEADERS_STS_PREFIX;
break;
default:
// No known service.
}
if (configKey != null) {
- Map awsClientCustomHeadersMap =
- S3AUtils.getTrimmedStringCollectionSplitByEquals(conf, configKey);
- awsClientCustomHeadersMap.forEach((header, valueString) -> {
- List headerValues = Arrays.stream(valueString.split(";"))
- .map(String::trim)
- .filter(v -> !v.isEmpty())
- .collect(Collectors.toList());
- if (!headerValues.isEmpty()) {
- clientConfig.putHeader(header, headerValues);
- } else {
- LOG.warn("Ignoring header '{}' for {} client because no values were provided",
- header, awsServiceIdentifier);
- }
- });
+ // headers for all requests are provided via clientConfig.putHeader
+ applyHeaders(conf, configKey, clientConfig::putHeader,
+ (header) -> LOG.warn("Ignoring header '{}' for {} client because no values were provided",
+ header, awsServiceIdentifier));
LOG.debug("headers for {} client = {}", awsServiceIdentifier, clientConfig.headers());
+
+ // per-request headers are provided via AddRequestHeaderInterceptor
+ String keyPrefix = configKeyPrefix;
+ Map>> requestHeaders = new HashMap<>();
+ Map requestHeaderConfs = conf.getPropsWithPrefix(keyPrefix);
+ requestHeaderConfs.keySet().forEach((request) ->
+ applyHeaders(conf, keyPrefix + request,
+ (header, headerValues) ->
+ requestHeaders.computeIfAbsent(request, c -> new HashMap<>()).put(header, headerValues),
+ (header) -> LOG.warn("Ignoring {} request header '{}' for {} client because " +
+ "no values were provided", request, header, awsServiceIdentifier)
+ )
+ );
+ if (!requestHeaders.isEmpty()) {
+ clientConfig.addExecutionInterceptor(new AddRequestHeaderInterceptor(requestHeaders));
+ requestHeaders.forEach((request, headers) ->
+ LOG.debug("{} request headers for {} client = {}", request, awsServiceIdentifier, headers)
+ );
+ }
}
}
diff --git a/hadoop-tools/hadoop-aws/src/main/java/org/apache/hadoop/fs/s3a/impl/AddRequestHeaderInterceptor.java b/hadoop-tools/hadoop-aws/src/main/java/org/apache/hadoop/fs/s3a/impl/AddRequestHeaderInterceptor.java
new file mode 100644
index 00000000000000..3bfa69484b1733
--- /dev/null
+++ b/hadoop-tools/hadoop-aws/src/main/java/org/apache/hadoop/fs/s3a/impl/AddRequestHeaderInterceptor.java
@@ -0,0 +1,62 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hadoop.fs.s3a.impl;
+
+import software.amazon.awssdk.awscore.AwsRequest;
+import software.amazon.awssdk.awscore.AwsRequestOverrideConfiguration;
+import software.amazon.awssdk.core.SdkRequest;
+import software.amazon.awssdk.core.interceptor.Context;
+import software.amazon.awssdk.core.interceptor.ExecutionAttributes;
+import software.amazon.awssdk.core.interceptor.ExecutionInterceptor;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.function.Consumer;
+
+public class AddRequestHeaderInterceptor implements ExecutionInterceptor {
+ private final Map> appliers = new HashMap<>();
+
+ public AddRequestHeaderInterceptor(Map>> requestHeaders) {
+ requestHeaders.forEach((request, headers) -> appliers
+ .put(request.toLowerCase(Locale.ROOT), (b) -> headers.forEach(b::putHeader))
+ );
+ }
+
+ public SdkRequest modifyRequest(Context.ModifyRequest context, ExecutionAttributes executionAttributes) {
+ assert context.request() instanceof AwsRequest;
+
+ AwsRequest request = (AwsRequest) context.request();
+ String requestName = request.getClass().getSimpleName().toLowerCase(Locale.ROOT);
+ Consumer applier = appliers.get(requestName);
+
+ if (applier != null) {
+ AwsRequestOverrideConfiguration overrideConfiguration =
+ request.overrideConfiguration()
+ .map(AwsRequestOverrideConfiguration::toBuilder)
+ .orElseGet(AwsRequestOverrideConfiguration::builder)
+ .applyMutation(applier)
+ .build();
+ return request.toBuilder().overrideConfiguration(overrideConfiguration).build();
+ } else {
+ return request;
+ }
+ }
+}
diff --git a/hadoop-tools/hadoop-aws/src/site/markdown/tools/hadoop-aws/index.md b/hadoop-tools/hadoop-aws/src/site/markdown/tools/hadoop-aws/index.md
index 88fcd2db7fbf48..8d899602b99d71 100644
--- a/hadoop-tools/hadoop-aws/src/site/markdown/tools/hadoop-aws/index.md
+++ b/hadoop-tools/hadoop-aws/src/site/markdown/tools/hadoop-aws/index.md
@@ -951,10 +951,14 @@ The switch to turn S3A auditing on or off.
### Configuring Custom Headers for AWS Service Clients
-You can set custom headers for S3 and STS requests. These headers are set on client level, and will be sent for all requests made to these services.
+You can set custom headers for S3 and STS requests. Headers can be set on client level and request type level.
+Client level headers are sent for all requests made through the client. Request type level headers are sent for
+requests of the respective type only.
+
+#### Client Level Headers
**Configuration Properties:**
-- `fs.s3a.client.s3.custom.headers`: Custom headers for S3 service requests.
+- `fs.s3a.client.s3.custom.headers`: Sets custom headers for S3 service requests.
- `fs.s3a.client.sts.custom.headers`: Sets custom headers for all requests to AWS STS.
**Header Format:**
@@ -973,6 +977,33 @@ Custom headers should be specified as key-value pairs, separated by `=`. Multipl
```
+#### Request-type Level Headers
+
+**Configuration Properties:**
+- `fs.s3a.client.s3.custom.headers.request.REQUEST`: Sets custom headers for S3 service requests of type `REQUEST`.
+- `fs.s3a.client.sts.custom.headers.request.REQUEST`: Sets custom headers for all requests to AWS STS of type `REQUEST`.
+
+Note: `REQUEST` refers to the AWS S3 and STS request name. These request type names are case-insensitive.
+See subclasses of S3Request
+and subclasses of StsRequest
+for all existing requests.
+
+**Header Format:**
+Custom headers should be specified as key-value pairs, separated by `=`. Multiple values for a single header can be separated by `;`. Multiple headers can be separated by `,`.
+
+
+```xml
+
+ fs.s3a.client.s3.custom.headers.request.ListObjectsV2Request
+ Header1=Value1
+
+
+
+ fs.s3a.client.sts.custom.headers.request.deleteobjectrequest
+ Header1=Value1;Value2,Header2=Value1
+
+```
+
## Retry and Recovery
The S3A client makes a best-effort attempt at recovering from network failures;
diff --git a/hadoop-tools/hadoop-aws/src/test/java/org/apache/hadoop/fs/s3a/impl/TestAwsClientConfig.java b/hadoop-tools/hadoop-aws/src/test/java/org/apache/hadoop/fs/s3a/impl/TestAwsClientConfig.java
index a0437a81817919..bd82146b6f73b9 100644
--- a/hadoop-tools/hadoop-aws/src/test/java/org/apache/hadoop/fs/s3a/impl/TestAwsClientConfig.java
+++ b/hadoop-tools/hadoop-aws/src/test/java/org/apache/hadoop/fs/s3a/impl/TestAwsClientConfig.java
@@ -21,6 +21,8 @@
import java.io.IOException;
import java.time.Duration;
import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
@@ -30,6 +32,15 @@
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.test.AbstractHadoopTestBase;
import org.apache.hadoop.util.Lists;
+import software.amazon.awssdk.core.SdkRequest;
+import software.amazon.awssdk.core.interceptor.Context;
+import software.amazon.awssdk.core.interceptor.InterceptorContext;
+import software.amazon.awssdk.services.s3.model.CreateBucketRequest;
+import software.amazon.awssdk.services.s3.model.DeleteObjectRequest;
+import software.amazon.awssdk.services.s3.model.ListObjectsV2Request;
+import software.amazon.awssdk.services.sts.model.AssumeRoleRequest;
+import software.amazon.awssdk.services.sts.model.DecodeAuthorizationMessageRequest;
+import software.amazon.awssdk.services.sts.model.GetSessionTokenRequest;
import static org.apache.hadoop.fs.s3a.Constants.AWS_SERVICE_IDENTIFIER_S3;
import static org.apache.hadoop.fs.s3a.Constants.AWS_SERVICE_IDENTIFIER_STS;
@@ -39,6 +50,8 @@
import static org.apache.hadoop.fs.s3a.Constants.CONNECTION_TTL;
import static org.apache.hadoop.fs.s3a.Constants.CUSTOM_HEADERS_S3;
import static org.apache.hadoop.fs.s3a.Constants.CUSTOM_HEADERS_STS;
+import static org.apache.hadoop.fs.s3a.Constants.CUSTOM_REQUEST_HEADERS_S3_PREFIX;
+import static org.apache.hadoop.fs.s3a.Constants.CUSTOM_REQUEST_HEADERS_STS_PREFIX;
import static org.apache.hadoop.fs.s3a.Constants.DEFAULT_CONNECTION_ACQUISITION_TIMEOUT_DURATION;
import static org.apache.hadoop.fs.s3a.Constants.DEFAULT_CONNECTION_IDLE_TIME_DURATION;
import static org.apache.hadoop.fs.s3a.Constants.DEFAULT_CONNECTION_KEEPALIVE;
@@ -217,15 +230,26 @@ private void setOptionsToValue(String value, Configuration conf, String... keys)
public void testInitRequestHeadersForSTS() throws IOException {
final Configuration conf = new Configuration();
conf.set(CUSTOM_HEADERS_STS, "header1=value1;value2,header2=value3");
+ conf.set(CUSTOM_REQUEST_HEADERS_STS_PREFIX + "GetSessionTokenRequest", "header3=value4;value5,header4=value6");
+ conf.set(CUSTOM_REQUEST_HEADERS_STS_PREFIX + "assumerolerequest", "header5=value7");
assertThat(conf.get(CUSTOM_HEADERS_S3))
.describedAs("Custom client headers for s3 %s", CUSTOM_HEADERS_S3)
.isNull();
+ assertThat(conf.getPropsWithPrefix(CUSTOM_REQUEST_HEADERS_S3_PREFIX))
+ .describedAs("Custom per-request client headers for s3 %s", CUSTOM_REQUEST_HEADERS_S3_PREFIX)
+ .isEmpty();
assertThat(createClientConfigBuilder(conf, AWS_SERVICE_IDENTIFIER_S3)
.headers().size())
.describedAs("Count of S3 client headers")
.isEqualTo(0);
+ assertThat(createClientConfigBuilder(conf, AWS_SERVICE_IDENTIFIER_S3)
+ .executionInterceptors().stream()
+ .filter(ei -> ei instanceof AddRequestHeaderInterceptor)
+ .count())
+ .describedAs("Count of request header interceptors of S3 client")
+ .isEqualTo(0);
assertThat(createClientConfigBuilder(conf, AWS_SERVICE_IDENTIFIER_STS)
.headers().size())
@@ -241,6 +265,55 @@ public void testInitRequestHeadersForSTS() throws IOException {
.headers().get("header2"))
.describedAs("STS client 'header2' header value")
.isEqualTo(Lists.newArrayList("value3"));
+
+ List interceptors =
+ createClientConfigBuilder(conf, AWS_SERVICE_IDENTIFIER_STS)
+ .executionInterceptors().stream()
+ .filter(ei -> ei instanceof AddRequestHeaderInterceptor)
+ .map(ie -> (AddRequestHeaderInterceptor) ie)
+ .toList();
+
+ assertThat(interceptors.size())
+ .describedAs("Count of request header interceptors of STS client")
+ .isEqualTo(1);
+
+ AddRequestHeaderInterceptor interceptor = interceptors.get(0);
+
+ SdkRequest request = DecodeAuthorizationMessageRequest.builder().build();
+ Context.ModifyRequest modifyRequest = InterceptorContext.builder().request(request).build();
+ SdkRequest modifiedRequest = interceptor.modifyRequest(modifyRequest, null);
+ assertThat(modifiedRequest.overrideConfiguration().isPresent())
+ .describedAs("STS list request has override configuration")
+ .isFalse();
+
+ SdkRequest getRequest = GetSessionTokenRequest.builder().build();
+ Context.ModifyRequest modifyGetRequest = InterceptorContext.builder().request(getRequest).build();
+ SdkRequest modifiedGetRequest = interceptor.modifyRequest(modifyGetRequest, null);
+ assertThat(modifiedGetRequest.overrideConfiguration().isPresent())
+ .describedAs("STS list request has override configuration")
+ .isTrue();
+ assertThat(modifiedGetRequest.overrideConfiguration().get().headers().keySet())
+ .describedAs("STS client request headers")
+ .isEqualTo(new HashSet<>(Lists.newArrayList("header3", "header4")));
+ assertThat(modifiedGetRequest.overrideConfiguration().get().headers().get("header3"))
+ .describedAs("STS client request 'header3' header value")
+ .isEqualTo(Lists.newArrayList("value4", "value5"));
+ assertThat(modifiedGetRequest.overrideConfiguration().get().headers().get("header4"))
+ .describedAs("STS client request 'header4' header value")
+ .isEqualTo(Lists.newArrayList("value6"));
+
+ SdkRequest assumeRequest = AssumeRoleRequest.builder().build();
+ Context.ModifyRequest modifyAssumeRequest = InterceptorContext.builder().request(assumeRequest).build();
+ SdkRequest modifiedAssumeRequest = interceptor.modifyRequest(modifyAssumeRequest, null);
+ assertThat(modifiedAssumeRequest.overrideConfiguration().isPresent())
+ .describedAs("STS delete request has override configuration")
+ .isTrue();
+ assertThat(modifiedAssumeRequest.overrideConfiguration().get().headers().keySet())
+ .describedAs("STS client request headers")
+ .isEqualTo(new HashSet<>(Lists.newArrayList("header5")));
+ assertThat(modifiedAssumeRequest.overrideConfiguration().get().headers().get("header5"))
+ .describedAs("STS client request 'header3' header value")
+ .isEqualTo(Lists.newArrayList("value7"));
}
/**
@@ -251,15 +324,26 @@ public void testInitRequestHeadersForSTS() throws IOException {
public void testInitRequestHeadersForS3() throws IOException {
final Configuration conf = new Configuration();
conf.set(CUSTOM_HEADERS_S3, "header1=value1;value2,header2=value3");
+ conf.set(CUSTOM_REQUEST_HEADERS_S3_PREFIX + "ListObjectsV2Request", "header3=value4;value5,header4=value6");
+ conf.set(CUSTOM_REQUEST_HEADERS_S3_PREFIX + "deleteobjectrequest", "header5=value7");
assertThat(conf.get(CUSTOM_HEADERS_STS))
.describedAs("Custom client headers for STS %s", CUSTOM_HEADERS_STS)
.isNull();
+ assertThat(conf.getPropsWithPrefix(CUSTOM_REQUEST_HEADERS_STS_PREFIX))
+ .describedAs("Custom per-request client headers for STS %s", CUSTOM_REQUEST_HEADERS_STS_PREFIX)
+ .isEmpty();
assertThat(createClientConfigBuilder(conf, AWS_SERVICE_IDENTIFIER_STS)
.headers().size())
.describedAs("Count of STS client headers")
.isEqualTo(0);
+ assertThat(createClientConfigBuilder(conf, AWS_SERVICE_IDENTIFIER_STS)
+ .executionInterceptors().stream()
+ .filter(ei -> ei instanceof AddRequestHeaderInterceptor)
+ .count())
+ .describedAs("Count of request header interceptors of STS client")
+ .isEqualTo(0);
assertThat(createClientConfigBuilder(conf, AWS_SERVICE_IDENTIFIER_S3)
.headers().size())
@@ -275,6 +359,55 @@ public void testInitRequestHeadersForS3() throws IOException {
.headers().get("header2"))
.describedAs("S3 client 'header2' header value")
.isEqualTo(Lists.newArrayList("value3"));
+
+ List interceptors =
+ createClientConfigBuilder(conf, AWS_SERVICE_IDENTIFIER_S3)
+ .executionInterceptors().stream()
+ .filter(ei -> ei instanceof AddRequestHeaderInterceptor)
+ .map(ie -> (AddRequestHeaderInterceptor) ie)
+ .toList();
+
+ assertThat(interceptors.size())
+ .describedAs("Count of request header interceptors of S3 client")
+ .isEqualTo(1);
+
+ AddRequestHeaderInterceptor interceptor = interceptors.get(0);
+
+ SdkRequest request = CreateBucketRequest.builder().build();
+ Context.ModifyRequest modifyRequest = InterceptorContext.builder().request(request).build();
+ SdkRequest modifiedRequest = interceptor.modifyRequest(modifyRequest, null);
+ assertThat(modifiedRequest.overrideConfiguration().isPresent())
+ .describedAs("S3 list request has override configuration")
+ .isFalse();
+
+ SdkRequest listRequest = ListObjectsV2Request.builder().build();
+ Context.ModifyRequest modifyListRequest = InterceptorContext.builder().request(listRequest).build();
+ SdkRequest modifiedListRequest = interceptor.modifyRequest(modifyListRequest, null);
+ assertThat(modifiedListRequest.overrideConfiguration().isPresent())
+ .describedAs("S3 list request has override configuration")
+ .isTrue();
+ assertThat(modifiedListRequest.overrideConfiguration().get().headers().keySet())
+ .describedAs("S3 client request headers")
+ .isEqualTo(new HashSet<>(Lists.newArrayList("header3", "header4")));
+ assertThat(modifiedListRequest.overrideConfiguration().get().headers().get("header3"))
+ .describedAs("S3 client request 'header3' header value")
+ .isEqualTo(Lists.newArrayList("value4", "value5"));
+ assertThat(modifiedListRequest.overrideConfiguration().get().headers().get("header4"))
+ .describedAs("S3 client request 'header4' header value")
+ .isEqualTo(Lists.newArrayList("value6"));
+
+ SdkRequest deleteRequest = DeleteObjectRequest.builder().build();
+ Context.ModifyRequest modifyDeleteRequest = InterceptorContext.builder().request(deleteRequest).build();
+ SdkRequest modifiedDeleteRequest = interceptor.modifyRequest(modifyDeleteRequest, null);
+ assertThat(modifiedDeleteRequest.overrideConfiguration().isPresent())
+ .describedAs("S3 delete request has override configuration")
+ .isTrue();
+ assertThat(modifiedDeleteRequest.overrideConfiguration().get().headers().keySet())
+ .describedAs("S3 client request headers")
+ .isEqualTo(new HashSet<>(Lists.newArrayList("header5")));
+ assertThat(modifiedDeleteRequest.overrideConfiguration().get().headers().get("header5"))
+ .describedAs("S3 client request 'header3' header value")
+ .isEqualTo(Lists.newArrayList("value7"));
}
/**