Skip to content

Commit f0d9dfe

Browse files
committed
Support for putHeaders on Request override config
1 parent 229d4a9 commit f0d9dfe

File tree

2 files changed

+108
-16
lines changed

2 files changed

+108
-16
lines changed

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

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ public ApplyUserAgentStage(HttpClientDependencies dependencies) {
6868
public SdkHttpFullRequest.Builder execute(SdkHttpFullRequest.Builder request,
6969
RequestExecutionContext context) throws Exception {
7070

71-
if (hasUserAgentInAdditionalHeaders()) {
71+
if (hasUserAgentInAdditionalHeaders() || hasUserAgentInRequestHeaders(context)) {
7272
return request;
7373
}
7474
String headerValue = finalizeUserAgent(context);
@@ -87,6 +87,18 @@ private boolean hasUserAgentInAdditionalHeaders() {
8787
return additionalHeaders.containsKey(HEADER_USER_AGENT);
8888
}
8989

90+
/**
91+
* Checks if User-Agent header is present in request-level headers.
92+
* We skip adding user-agent in the ApplyUserAgentStage if user has set "User-Agent" header at request level
93+
*/
94+
private boolean hasUserAgentInRequestHeaders(RequestExecutionContext context) {
95+
Map<String, List<String>> requestHeaders = context.requestConfig().headers();
96+
if (requestHeaders == null) {
97+
return false;
98+
}
99+
return requestHeaders.containsKey(HEADER_USER_AGENT);
100+
}
101+
90102
/**
91103
* The final value sent in the user agent header consists of
92104
* <ol>

test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/useragent/CustomUserAgentHeaderTest.java

Lines changed: 95 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,6 @@ private static Stream<Arguments> customUserAgentValues() {
6363
);
6464
}
6565

66-
// ========== Default Behavior Tests ==========
67-
6866
private static Stream<Arguments> customUserAgentListValues() {
6967
return Stream.of(
7068
Arguments.of(Arrays.asList("Agent1")),
@@ -73,13 +71,13 @@ private static Stream<Arguments> customUserAgentListValues() {
7371
);
7472
}
7573

76-
// ========== Custom User-Agent Preservation Tests ==========
77-
7874
@BeforeEach
7975
void setUp() {
8076
interceptor = new CapturingInterceptor();
8177
}
8278

79+
// ========== Default Behavior Tests ==========
80+
8381
@Test
8482
void executeRequest_withoutCustomUserAgent_shouldAddSdkDefaultUserAgent() {
8583
RestJsonEndpointProvidersClient client = defaultClientBuilder().build();
@@ -88,7 +86,7 @@ void executeRequest_withoutCustomUserAgent_shouldAddSdkDefaultUserAgent() {
8886
assertUserAgentContains(SDK_USER_AGENT_PREFIX);
8987
}
9088

91-
// ========== API Name Handling Tests ==========
89+
// ========== Custom User-Agent Preservation Tests ==========
9290

9391
@ParameterizedTest(name = "Custom User-Agent ''{0}'' should be preserved without SDK prefix")
9492
@MethodSource("customUserAgentValues")
@@ -112,6 +110,69 @@ void executeRequest_withCustomUserAgentList_shouldPreserveAllValues(List<String>
112110
assertThat(userAgentList).isEqualTo(customUserAgentList);
113111
}
114112

113+
// ========== Request-Level User-Agent Tests ==========
114+
115+
@ParameterizedTest(name = "Request-level User-Agent ''{0}'' should be preserved")
116+
@MethodSource("customUserAgentValues")
117+
void executeRequest_withRequestLevelCustomUserAgent_shouldPreserveAndNotOverwrite(String customUserAgent) {
118+
RestJsonEndpointProvidersClient client = defaultClientBuilder().build();
119+
120+
assertThatThrownBy(() -> client.allTypes(r -> r
121+
.overrideConfiguration(o -> o.putHeader(USER_AGENT_HEADER, customUserAgent))))
122+
.hasMessageContaining(INTERCEPTOR_STOP_MESSAGE);
123+
124+
String userAgent = getCapturedUserAgent();
125+
assertThat(userAgent)
126+
.isEqualTo(customUserAgent)
127+
.doesNotContain(SDK_USER_AGENT_PREFIX);
128+
}
129+
130+
@ParameterizedTest(name = "Request-level User-Agent list {0} should be preserved")
131+
@MethodSource("customUserAgentListValues")
132+
void executeRequest_withRequestLevelCustomUserAgentList_shouldPreserveAllValues(List<String> customUserAgentList) {
133+
RestJsonEndpointProvidersClient client = defaultClientBuilder().build();
134+
135+
assertThatThrownBy(() -> client.allTypes(r -> r
136+
.overrideConfiguration(o -> o.putHeader(USER_AGENT_HEADER, customUserAgentList))))
137+
.hasMessageContaining(INTERCEPTOR_STOP_MESSAGE);
138+
139+
List<String> userAgentList = getCapturedUserAgentList();
140+
assertThat(userAgentList).isEqualTo(customUserAgentList);
141+
}
142+
143+
@Test
144+
void executeRequest_withRequestLevelCustomUserAgentAndApiName_shouldNotAppendApiName() {
145+
String customUserAgent = "CustomUserAgentHeaderValue";
146+
RestJsonEndpointProvidersClient client = defaultClientBuilder().build();
147+
148+
assertThatThrownBy(() -> client.allTypes(r -> r
149+
.overrideConfiguration(o -> o
150+
.addApiName(api -> api.name(TEST_API_NAME).version(TEST_API_VERSION))
151+
.putHeader(USER_AGENT_HEADER, customUserAgent))))
152+
.hasMessageContaining(INTERCEPTOR_STOP_MESSAGE);
153+
154+
String userAgent = getCapturedUserAgent();
155+
assertThat(userAgent)
156+
.isEqualTo(customUserAgent)
157+
.doesNotContain(TEST_API_NAME);
158+
}
159+
160+
@ParameterizedTest(name = "Request-level User-Agent list {0} with API name should not append API name")
161+
@MethodSource("customUserAgentListValues")
162+
void executeRequest_withRequestLevelCustomUserAgentListAndApiName_shouldNotAppendApiName(List<String> customUserAgentList) {
163+
RestJsonEndpointProvidersClient client = defaultClientBuilder().build();
164+
165+
assertThatThrownBy(() -> client.allTypes(r -> r
166+
.overrideConfiguration(o -> o
167+
.addApiName(api -> api.name(TEST_API_NAME).version(TEST_API_VERSION))
168+
.putHeader(USER_AGENT_HEADER, customUserAgentList))))
169+
.hasMessageContaining(INTERCEPTOR_STOP_MESSAGE);
170+
171+
List<String> userAgentList = getCapturedUserAgentList();
172+
assertThat(userAgentList).isEqualTo(customUserAgentList);
173+
assertThat(String.join(" ", userAgentList)).doesNotContain(TEST_API_NAME);
174+
}
175+
115176
// ========== Header via Interceptors ==========
116177

117178
@Test
@@ -133,7 +194,7 @@ public SdkHttpRequest modifyHttpRequest(Context.ModifyHttpRequest context,
133194
assertUserAgentContains(SDK_USER_AGENT_PREFIX);
134195
}
135196

136-
// ========== Edge Case Tests ==========
197+
// ========== API Name Handling Tests ==========
137198

138199
@Test
139200
void executeRequest_withCustomUserAgentAndApiName_shouldNotAppendApiName() {
@@ -155,12 +216,8 @@ void executeRequest_withoutCustomUserAgentAndWithApiName_shouldAppendApiName() {
155216
assertUserAgentContains(TEST_API_NAME + "/" + TEST_API_VERSION);
156217
}
157218

158-
/**
159-
* Verifies that null User-Agent list throws NullPointerException.
160-
*
161-
* <p>This ensures the SDK fails fast with clear error rather than allowing
162-
* invalid configuration.
163-
*/
219+
// ========== Edge Case Tests ==========
220+
164221
@Test
165222
void buildClient_withNullListUserAgent_shouldThrowNullPointerException() {
166223
assertThatThrownBy(() -> clientWithCustomUserAgentList(null))
@@ -195,17 +252,40 @@ void executeRequest_withNullStringUserAgent_shouldStoreAsSdkUserAgent() {
195252
.hasSize(1)
196253
.allSatisfy(ua -> {
197254
assertThat(ua).isNull();
198-
199255
});
200256
}
201257

258+
@Test
259+
void executeRequest_withRequestLevelEmptyCustomUserAgent_shouldStoreEmptyUserAgent() {
260+
RestJsonEndpointProvidersClient client = defaultClientBuilder().build();
261+
262+
assertThatThrownBy(() -> client.allTypes(r -> r
263+
.overrideConfiguration(o -> o.putHeader(USER_AGENT_HEADER, ""))))
264+
.hasMessageContaining(INTERCEPTOR_STOP_MESSAGE);
265+
266+
assertUserAgentContains("");
267+
}
268+
269+
@Test
270+
void executeRequest_withRequestLevelEmptyListUserAgent_shouldResultInNoUserAgent() {
271+
RestJsonEndpointProvidersClient client = defaultClientBuilder().build();
272+
273+
assertThatThrownBy(() -> client.allTypes(r -> r
274+
.overrideConfiguration(o -> o.putHeader(USER_AGENT_HEADER, Collections.emptyList()))))
275+
.hasMessageContaining(INTERCEPTOR_STOP_MESSAGE);
276+
277+
List<String> userAgentList = getCapturedUserAgentList();
278+
assertThat(userAgentList).isNull();
279+
}
280+
281+
// ========== Helper Methods ==========
282+
202283
private void assertUserAgentContains(String expected) {
203284
assertThat(getCapturedUserAgent()).contains(expected);
204285
}
205286

206287
private void executeRequestExpectingInterception(RestJsonEndpointProvidersClient client) {
207-
assertThatThrownBy(() -> client.allTypes(r -> {
208-
}))
288+
assertThatThrownBy(() -> client.allTypes(r -> {}))
209289
.hasMessageContaining(INTERCEPTOR_STOP_MESSAGE);
210290
}
211291

0 commit comments

Comments
 (0)