Merge branch '6.2.x'
This commit is contained in:
@@ -938,10 +938,10 @@ method parameters:
|
||||
| Dynamically set the HTTP method for the request, overriding the annotation's `method` attribute
|
||||
|
||||
| `@RequestHeader`
|
||||
| Add a request header or multiple headers. The argument may be a `Map<String, ?>` or
|
||||
`MultiValueMap<String, ?>` with multiple headers, a `Collection<?>` of values, or an
|
||||
individual value. Type conversion is supported for non-String values. This overrides
|
||||
the annotation's `headers` attribute.
|
||||
| Add a request header or multiple headers. The argument may be a single value,
|
||||
a `Collection<?>` of values, `Map<String, ?>`,`MultiValueMap<String, ?>`.
|
||||
Type conversion is supported for non-String values. Header values are added and
|
||||
do not override already added header values.
|
||||
|
||||
| `@PathVariable`
|
||||
| Add a variable for expand a placeholder in the request URL. The argument may be a
|
||||
|
||||
@@ -605,9 +605,4 @@ For method parameters and returns values, generally, `@HttpExchange` supports a
|
||||
subset of the method parameters that `@RequestMapping` does. Notably, it excludes any
|
||||
server-side specific parameter types. For details, see the list for
|
||||
xref:integration/rest-clients.adoc#rest-http-interface-method-parameters[@HttpExchange] and
|
||||
xref:web/webflux/controller/ann-methods/arguments.adoc[@RequestMapping].
|
||||
|
||||
`@HttpExchange` also supports a `headers()` parameter which accepts `"name=value"`-like
|
||||
pairs like in `@RequestMapping(headers={})` on the client side. On the server side,
|
||||
this extends to the full syntax that
|
||||
xref:#webflux-ann-requestmapping-params-and-headers[`@RequestMapping`] supports.
|
||||
xref:web/webflux/controller/ann-methods/arguments.adoc[@RequestMapping].
|
||||
@@ -22,7 +22,6 @@ import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
@@ -159,7 +158,7 @@ final class HttpServiceMethod {
|
||||
private record HttpRequestValuesInitializer(
|
||||
@Nullable HttpMethod httpMethod, @Nullable String url,
|
||||
@Nullable MediaType contentType, @Nullable List<MediaType> acceptMediaTypes,
|
||||
@Nullable MultiValueMap<String, String> otherHeaders,
|
||||
MultiValueMap<String, String> headers,
|
||||
Supplier<HttpRequestValues.Builder> requestValuesSupplier) {
|
||||
|
||||
public HttpRequestValues.Builder initializeRequestValuesBuilder() {
|
||||
@@ -176,16 +175,8 @@ final class HttpServiceMethod {
|
||||
if (this.acceptMediaTypes != null) {
|
||||
requestValues.setAccept(this.acceptMediaTypes);
|
||||
}
|
||||
if (this.otherHeaders != null) {
|
||||
this.otherHeaders.forEach((name, values) -> {
|
||||
if (values.size() == 1) {
|
||||
requestValues.addHeader(name, values.get(0));
|
||||
}
|
||||
else {
|
||||
requestValues.addHeader(name, values.toArray(new String[0]));
|
||||
}
|
||||
});
|
||||
}
|
||||
this.headers.forEach((name, values) ->
|
||||
values.forEach(value -> requestValues.addHeader(name, value)));
|
||||
return requestValues;
|
||||
}
|
||||
|
||||
@@ -216,10 +207,10 @@ final class HttpServiceMethod {
|
||||
String url = initUrl(typeAnnotation, methodAnnotation, embeddedValueResolver);
|
||||
MediaType contentType = initContentType(typeAnnotation, methodAnnotation);
|
||||
List<MediaType> acceptableMediaTypes = initAccept(typeAnnotation, methodAnnotation);
|
||||
MultiValueMap<String, String> headers = initHeaders(typeAnnotation, methodAnnotation,
|
||||
embeddedValueResolver);
|
||||
return new HttpRequestValuesInitializer(httpMethod, url, contentType,
|
||||
acceptableMediaTypes, headers, requestValuesSupplier);
|
||||
MultiValueMap<String, String> headers = initHeaders(typeAnnotation, methodAnnotation, embeddedValueResolver);
|
||||
|
||||
return new HttpRequestValuesInitializer(
|
||||
httpMethod, url, contentType, acceptableMediaTypes, headers, requestValuesSupplier);
|
||||
}
|
||||
|
||||
private static @Nullable HttpMethod initHttpMethod(@Nullable HttpExchange typeAnnotation, HttpExchange methodAnnotation) {
|
||||
@@ -291,47 +282,42 @@ final class HttpServiceMethod {
|
||||
return null;
|
||||
}
|
||||
|
||||
private static MultiValueMap<String, String> parseHeaders(String[] headersArray,
|
||||
private static MultiValueMap<String, String> initHeaders(
|
||||
@Nullable HttpExchange typeAnnotation, HttpExchange methodAnnotation,
|
||||
@Nullable StringValueResolver embeddedValueResolver) {
|
||||
|
||||
MultiValueMap<String, String> headers = new LinkedMultiValueMap<>();
|
||||
for (String h: headersArray) {
|
||||
String[] headerPair = StringUtils.split(h, "=");
|
||||
if (headerPair != null) {
|
||||
String headerName = headerPair[0].trim();
|
||||
List<String> headerValues = new ArrayList<>();
|
||||
Set<String> parsedValues = StringUtils.commaDelimitedListToSet(headerPair[1]);
|
||||
for (String headerValue : parsedValues) {
|
||||
if (embeddedValueResolver != null) {
|
||||
headerValue = embeddedValueResolver.resolveStringValue(headerValue);
|
||||
}
|
||||
if (headerValue != null) {
|
||||
headerValue = headerValue.trim();
|
||||
headerValues.add(headerValue);
|
||||
}
|
||||
}
|
||||
if (!headerValues.isEmpty()) {
|
||||
headers.addAll(headerName, headerValues);
|
||||
}
|
||||
}
|
||||
if (typeAnnotation != null) {
|
||||
addHeaders(typeAnnotation.headers(), embeddedValueResolver, headers);
|
||||
}
|
||||
addHeaders(methodAnnotation.headers(), embeddedValueResolver, headers);
|
||||
return headers;
|
||||
}
|
||||
|
||||
private static @Nullable MultiValueMap<String, String> initHeaders(@Nullable HttpExchange typeAnnotation, HttpExchange methodAnnotation,
|
||||
@Nullable StringValueResolver embeddedValueResolver) {
|
||||
MultiValueMap<String, String> methodLevelHeaders = parseHeaders(methodAnnotation.headers(),
|
||||
embeddedValueResolver);
|
||||
if (!ObjectUtils.isEmpty(methodLevelHeaders)) {
|
||||
return methodLevelHeaders;
|
||||
}
|
||||
private static void addHeaders(
|
||||
String[] rawValues, @Nullable StringValueResolver embeddedValueResolver,
|
||||
MultiValueMap<String, String> outputHeaders) {
|
||||
|
||||
MultiValueMap<String, String> typeLevelHeaders = (typeAnnotation != null ?
|
||||
parseHeaders(typeAnnotation.headers(), embeddedValueResolver) : null);
|
||||
if (!ObjectUtils.isEmpty(typeLevelHeaders)) {
|
||||
return typeLevelHeaders;
|
||||
for (String rawValue: rawValues) {
|
||||
String[] pair = StringUtils.split(rawValue, "=");
|
||||
if (pair == null) {
|
||||
continue;
|
||||
}
|
||||
String name = pair[0].trim();
|
||||
List<String> values = new ArrayList<>();
|
||||
for (String value : StringUtils.commaDelimitedListToSet(pair[1])) {
|
||||
if (embeddedValueResolver != null) {
|
||||
value = embeddedValueResolver.resolveStringValue(value);
|
||||
}
|
||||
if (value != null) {
|
||||
value = value.trim();
|
||||
values.add(value);
|
||||
}
|
||||
}
|
||||
if (!values.isEmpty()) {
|
||||
outputHeaders.addAll(name, values);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static List<AnnotationDescriptor> getAnnotationDescriptors(AnnotatedElement element) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -348,8 +348,10 @@ class HttpServiceMethodTests {
|
||||
@PostExchange(url = "/url", contentType = APPLICATION_JSON_VALUE, accept = APPLICATION_JSON_VALUE)
|
||||
void performPost();
|
||||
|
||||
@HttpExchange(contentType = APPLICATION_JSON_VALUE, headers = {"CustomHeader=a,b, c",
|
||||
"Content-Type=" + APPLICATION_NDJSON_VALUE}, method = "GET")
|
||||
@HttpExchange(
|
||||
method = "GET",
|
||||
contentType = APPLICATION_JSON_VALUE,
|
||||
headers = {"CustomHeader=a,b, c", "Content-Type=" + APPLICATION_NDJSON_VALUE})
|
||||
void performGetWithHeaders();
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -23,6 +23,7 @@ import org.junit.jupiter.api.Test;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.web.bind.annotation.RequestHeader;
|
||||
import org.springframework.web.service.annotation.GetExchange;
|
||||
import org.springframework.web.service.annotation.HttpExchange;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@@ -32,6 +33,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
*
|
||||
* @author Olga Maciaszek-Sharma
|
||||
* @author Rossen Stoyanchev
|
||||
* @author Yanming Zhou
|
||||
*/
|
||||
class RequestHeaderArgumentResolverTests {
|
||||
|
||||
@@ -49,6 +51,12 @@ class RequestHeaderArgumentResolverTests {
|
||||
assertRequestHeaders("id", "test");
|
||||
}
|
||||
|
||||
@Test
|
||||
void doesNotOverrideAnnotationHeaders() {
|
||||
this.service.executeWithAnnotationHeaders("2");
|
||||
assertRequestHeaders("myHeader", "1", "2");
|
||||
}
|
||||
|
||||
private void assertRequestHeaders(String key, String... values) {
|
||||
List<String> actualValues = this.client.getRequestValues().getHeaders().get(key);
|
||||
if (ObjectUtils.isEmpty(values)) {
|
||||
@@ -65,6 +73,9 @@ class RequestHeaderArgumentResolverTests {
|
||||
@GetExchange
|
||||
void execute(@RequestHeader String id);
|
||||
|
||||
@HttpExchange(method = "GET", headers = "myHeader=1")
|
||||
void executeWithAnnotationHeaders(@RequestHeader String myHeader);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -430,7 +430,8 @@ class RequestMappingHandlerMappingTests {
|
||||
@PostExchange(url = "/custom", contentType = "application/json", accept = "text/plain;charset=UTF-8")
|
||||
public void customValuesExchange(){}
|
||||
|
||||
@HttpExchange(method="GET", url = "/headers",
|
||||
@HttpExchange(
|
||||
method="GET", url = "/headers",
|
||||
headers = {"h1=hv1", "!h2", "Accept=application/ignored"})
|
||||
public String customHeadersExchange() {
|
||||
return "info";
|
||||
|
||||
Reference in New Issue
Block a user