Skip to content

Commit 6873427

Browse files
committed
Merge branch '6.2.x'
2 parents 3b1d14a + 384d274 commit 6873427

File tree

6 files changed

+59
-64
lines changed

6 files changed

+59
-64
lines changed

framework-docs/modules/ROOT/pages/integration/rest-clients.adoc

+4-4
Original file line numberDiff line numberDiff line change
@@ -938,10 +938,10 @@ method parameters:
938938
| Dynamically set the HTTP method for the request, overriding the annotation's `method` attribute
939939

940940
| `@RequestHeader`
941-
| Add a request header or multiple headers. The argument may be a `Map<String, ?>` or
942-
`MultiValueMap<String, ?>` with multiple headers, a `Collection<?>` of values, or an
943-
individual value. Type conversion is supported for non-String values. This overrides
944-
the annotation's `headers` attribute.
941+
| Add a request header or multiple headers. The argument may be a single value,
942+
a `Collection<?>` of values, `Map<String, ?>`,`MultiValueMap<String, ?>`.
943+
Type conversion is supported for non-String values. Header values are added and
944+
do not override already added header values.
945945

946946
| `@PathVariable`
947947
| Add a variable for expand a placeholder in the request URL. The argument may be a

framework-docs/modules/ROOT/pages/web/webflux/controller/ann-requestmapping.adoc

+1-6
Original file line numberDiff line numberDiff line change
@@ -605,9 +605,4 @@ For method parameters and returns values, generally, `@HttpExchange` supports a
605605
subset of the method parameters that `@RequestMapping` does. Notably, it excludes any
606606
server-side specific parameter types. For details, see the list for
607607
xref:integration/rest-clients.adoc#rest-http-interface-method-parameters[@HttpExchange] and
608-
xref:web/webflux/controller/ann-methods/arguments.adoc[@RequestMapping].
609-
610-
`@HttpExchange` also supports a `headers()` parameter which accepts `"name=value"`-like
611-
pairs like in `@RequestMapping(headers={})` on the client side. On the server side,
612-
this extends to the full syntax that
613-
xref:#webflux-ann-requestmapping-params-and-headers[`@RequestMapping`] supports.
608+
xref:web/webflux/controller/ann-methods/arguments.adoc[@RequestMapping].

spring-web/src/main/java/org/springframework/web/service/invoker/HttpServiceMethod.java

+35-49
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@
2222
import java.util.ArrayList;
2323
import java.util.List;
2424
import java.util.Optional;
25-
import java.util.Set;
2625
import java.util.function.Function;
2726
import java.util.function.Supplier;
2827

@@ -159,7 +158,7 @@ private void applyArguments(HttpRequestValues.Builder requestValues, @Nullable O
159158
private record HttpRequestValuesInitializer(
160159
@Nullable HttpMethod httpMethod, @Nullable String url,
161160
@Nullable MediaType contentType, @Nullable List<MediaType> acceptMediaTypes,
162-
@Nullable MultiValueMap<String, String> otherHeaders,
161+
MultiValueMap<String, String> headers,
163162
Supplier<HttpRequestValues.Builder> requestValuesSupplier) {
164163

165164
public HttpRequestValues.Builder initializeRequestValuesBuilder() {
@@ -176,16 +175,8 @@ public HttpRequestValues.Builder initializeRequestValuesBuilder() {
176175
if (this.acceptMediaTypes != null) {
177176
requestValues.setAccept(this.acceptMediaTypes);
178177
}
179-
if (this.otherHeaders != null) {
180-
this.otherHeaders.forEach((name, values) -> {
181-
if (values.size() == 1) {
182-
requestValues.addHeader(name, values.get(0));
183-
}
184-
else {
185-
requestValues.addHeader(name, values.toArray(new String[0]));
186-
}
187-
});
188-
}
178+
this.headers.forEach((name, values) ->
179+
values.forEach(value -> requestValues.addHeader(name, value)));
189180
return requestValues;
190181
}
191182

@@ -216,10 +207,10 @@ public static HttpRequestValuesInitializer create(
216207
String url = initUrl(typeAnnotation, methodAnnotation, embeddedValueResolver);
217208
MediaType contentType = initContentType(typeAnnotation, methodAnnotation);
218209
List<MediaType> acceptableMediaTypes = initAccept(typeAnnotation, methodAnnotation);
219-
MultiValueMap<String, String> headers = initHeaders(typeAnnotation, methodAnnotation,
220-
embeddedValueResolver);
221-
return new HttpRequestValuesInitializer(httpMethod, url, contentType,
222-
acceptableMediaTypes, headers, requestValuesSupplier);
210+
MultiValueMap<String, String> headers = initHeaders(typeAnnotation, methodAnnotation, embeddedValueResolver);
211+
212+
return new HttpRequestValuesInitializer(
213+
httpMethod, url, contentType, acceptableMediaTypes, headers, requestValuesSupplier);
223214
}
224215

225216
private static @Nullable HttpMethod initHttpMethod(@Nullable HttpExchange typeAnnotation, HttpExchange methodAnnotation) {
@@ -291,47 +282,42 @@ public static HttpRequestValuesInitializer create(
291282
return null;
292283
}
293284

294-
private static MultiValueMap<String, String> parseHeaders(String[] headersArray,
285+
private static MultiValueMap<String, String> initHeaders(
286+
@Nullable HttpExchange typeAnnotation, HttpExchange methodAnnotation,
295287
@Nullable StringValueResolver embeddedValueResolver) {
288+
296289
MultiValueMap<String, String> headers = new LinkedMultiValueMap<>();
297-
for (String h: headersArray) {
298-
String[] headerPair = StringUtils.split(h, "=");
299-
if (headerPair != null) {
300-
String headerName = headerPair[0].trim();
301-
List<String> headerValues = new ArrayList<>();
302-
Set<String> parsedValues = StringUtils.commaDelimitedListToSet(headerPair[1]);
303-
for (String headerValue : parsedValues) {
304-
if (embeddedValueResolver != null) {
305-
headerValue = embeddedValueResolver.resolveStringValue(headerValue);
306-
}
307-
if (headerValue != null) {
308-
headerValue = headerValue.trim();
309-
headerValues.add(headerValue);
310-
}
311-
}
312-
if (!headerValues.isEmpty()) {
313-
headers.addAll(headerName, headerValues);
314-
}
315-
}
290+
if (typeAnnotation != null) {
291+
addHeaders(typeAnnotation.headers(), embeddedValueResolver, headers);
316292
}
293+
addHeaders(methodAnnotation.headers(), embeddedValueResolver, headers);
317294
return headers;
318295
}
319296

320-
private static @Nullable MultiValueMap<String, String> initHeaders(@Nullable HttpExchange typeAnnotation, HttpExchange methodAnnotation,
321-
@Nullable StringValueResolver embeddedValueResolver) {
322-
MultiValueMap<String, String> methodLevelHeaders = parseHeaders(methodAnnotation.headers(),
323-
embeddedValueResolver);
324-
if (!ObjectUtils.isEmpty(methodLevelHeaders)) {
325-
return methodLevelHeaders;
326-
}
297+
private static void addHeaders(
298+
String[] rawValues, @Nullable StringValueResolver embeddedValueResolver,
299+
MultiValueMap<String, String> outputHeaders) {
327300

328-
MultiValueMap<String, String> typeLevelHeaders = (typeAnnotation != null ?
329-
parseHeaders(typeAnnotation.headers(), embeddedValueResolver) : null);
330-
if (!ObjectUtils.isEmpty(typeLevelHeaders)) {
331-
return typeLevelHeaders;
301+
for (String rawValue: rawValues) {
302+
String[] pair = StringUtils.split(rawValue, "=");
303+
if (pair == null) {
304+
continue;
305+
}
306+
String name = pair[0].trim();
307+
List<String> values = new ArrayList<>();
308+
for (String value : StringUtils.commaDelimitedListToSet(pair[1])) {
309+
if (embeddedValueResolver != null) {
310+
value = embeddedValueResolver.resolveStringValue(value);
311+
}
312+
if (value != null) {
313+
value = value.trim();
314+
values.add(value);
315+
}
316+
}
317+
if (!values.isEmpty()) {
318+
outputHeaders.addAll(name, values);
319+
}
332320
}
333-
334-
return null;
335321
}
336322

337323
private static List<AnnotationDescriptor> getAnnotationDescriptors(AnnotatedElement element) {

spring-web/src/test/java/org/springframework/web/service/invoker/HttpServiceMethodTests.java

+5-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2024 the original author or authors.
2+
* Copyright 2002-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -348,8 +348,10 @@ private interface MethodLevelAnnotatedService {
348348
@PostExchange(url = "/url", contentType = APPLICATION_JSON_VALUE, accept = APPLICATION_JSON_VALUE)
349349
void performPost();
350350

351-
@HttpExchange(contentType = APPLICATION_JSON_VALUE, headers = {"CustomHeader=a,b, c",
352-
"Content-Type=" + APPLICATION_NDJSON_VALUE}, method = "GET")
351+
@HttpExchange(
352+
method = "GET",
353+
contentType = APPLICATION_JSON_VALUE,
354+
headers = {"CustomHeader=a,b, c", "Content-Type=" + APPLICATION_NDJSON_VALUE})
353355
void performGetWithHeaders();
354356

355357
}

spring-web/src/test/java/org/springframework/web/service/invoker/RequestHeaderArgumentResolverTests.java

+12-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2024 the original author or authors.
2+
* Copyright 2002-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -23,6 +23,7 @@
2323
import org.springframework.util.ObjectUtils;
2424
import org.springframework.web.bind.annotation.RequestHeader;
2525
import org.springframework.web.service.annotation.GetExchange;
26+
import org.springframework.web.service.annotation.HttpExchange;
2627

2728
import static org.assertj.core.api.Assertions.assertThat;
2829

@@ -32,6 +33,7 @@
3233
*
3334
* @author Olga Maciaszek-Sharma
3435
* @author Rossen Stoyanchev
36+
* @author Yanming Zhou
3537
*/
3638
class RequestHeaderArgumentResolverTests {
3739

@@ -49,6 +51,12 @@ void header() {
4951
assertRequestHeaders("id", "test");
5052
}
5153

54+
@Test
55+
void doesNotOverrideAnnotationHeaders() {
56+
this.service.executeWithAnnotationHeaders("2");
57+
assertRequestHeaders("myHeader", "1", "2");
58+
}
59+
5260
private void assertRequestHeaders(String key, String... values) {
5361
List<String> actualValues = this.client.getRequestValues().getHeaders().get(key);
5462
if (ObjectUtils.isEmpty(values)) {
@@ -65,6 +73,9 @@ private interface Service {
6573
@GetExchange
6674
void execute(@RequestHeader String id);
6775

76+
@HttpExchange(method = "GET", headers = "myHeader=1")
77+
void executeWithAnnotationHeaders(@RequestHeader String myHeader);
78+
6879
}
6980

7081
}

spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestMappingHandlerMappingTests.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -430,7 +430,8 @@ public void defaultValuesExchange() {}
430430
@PostExchange(url = "/custom", contentType = "application/json", accept = "text/plain;charset=UTF-8")
431431
public void customValuesExchange(){}
432432

433-
@HttpExchange(method="GET", url = "/headers",
433+
@HttpExchange(
434+
method="GET", url = "/headers",
434435
headers = {"h1=hv1", "!h2", "Accept=application/ignored"})
435436
public String customHeadersExchange() {
436437
return "info";

0 commit comments

Comments
 (0)