Skip to content

Commit 28c6262

Browse files
committed
Add HttpHeaders argument resolver for HTTP service
Signed-off-by: Yanming Zhou <[email protected]>
1 parent 87f1961 commit 28c6262

File tree

4 files changed

+143
-1
lines changed

4 files changed

+143
-1
lines changed

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -937,6 +937,9 @@ method parameters:
937937
| `HttpMethod`
938938
| Dynamically set the HTTP method for the request, overriding the annotation's `method` attribute
939939

940+
| `HttpHeaders`
941+
| Add request headers. This does not override the annotation's `headers` attribute.
942+
940943
| `@RequestHeader`
941944
| Add a request header or multiple headers. The argument may be a `Map<String, ?>` or
942945
`MultiValueMap<String, ?>` with multiple headers, a `Collection<?>` of values, or an
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/*
2+
* Copyright 2002-2025 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.web.service.invoker;
18+
19+
import org.apache.commons.logging.Log;
20+
import org.apache.commons.logging.LogFactory;
21+
import org.jspecify.annotations.Nullable;
22+
import org.springframework.core.MethodParameter;
23+
import org.springframework.http.HttpHeaders;
24+
import org.springframework.util.Assert;
25+
26+
import java.util.Optional;
27+
28+
/**
29+
* {@link HttpServiceArgumentResolver} that resolves the target
30+
* request's HTTP headers from an {@link HttpHeaders} argument.
31+
*
32+
* @author Yanming Zhou
33+
*/
34+
public class HttpHeadersArgumentResolver implements HttpServiceArgumentResolver {
35+
36+
private static final Log logger = LogFactory.getLog(HttpHeadersArgumentResolver.class);
37+
38+
39+
@Override
40+
public boolean resolve(
41+
@Nullable Object argument, MethodParameter parameter, HttpRequestValues.Builder requestValues) {
42+
43+
parameter = parameter.nestedIfOptional();
44+
45+
if (!parameter.getNestedParameterType().equals(HttpHeaders.class)) {
46+
return false;
47+
}
48+
49+
if (argument instanceof Optional<?> optionalValue) {
50+
argument = optionalValue.orElse(null);
51+
}
52+
53+
if (argument == null) {
54+
Assert.isTrue(parameter.isOptional(), "HttpHeaders is required");
55+
return true;
56+
}
57+
58+
((HttpHeaders) argument).forEach((name, values) -> {
59+
requestValues.addHeader(name, values.toArray(new String[0]));
60+
});
61+
62+
return true;
63+
}
64+
65+
}

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

Lines changed: 3 additions & 1 deletion
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.
@@ -47,6 +47,7 @@
4747
* {@link Builder Builder}.
4848
*
4949
* @author Rossen Stoyanchev
50+
* @author Yanming Zhou
5051
* @since 6.0
5152
* @see org.springframework.web.client.support.RestClientAdapter
5253
* @see org.springframework.web.reactive.function.client.support.WebClientAdapter
@@ -210,6 +211,7 @@ private List<HttpServiceArgumentResolver> initArgumentResolvers() {
210211
resolvers.add(new UrlArgumentResolver());
211212
resolvers.add(new UriBuilderFactoryArgumentResolver());
212213
resolvers.add(new HttpMethodArgumentResolver());
214+
resolvers.add(new HttpHeadersArgumentResolver());
213215

214216
return resolvers;
215217
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
/*
2+
* Copyright 2002-2025 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.web.service.invoker;
18+
19+
import org.junit.jupiter.api.Test;
20+
import org.springframework.http.HttpHeaders;
21+
import org.springframework.web.service.annotation.GetExchange;
22+
import org.springframework.web.service.annotation.HttpExchange;
23+
24+
import static org.assertj.core.api.Assertions.assertThat;
25+
26+
/**
27+
* Tests for {@link HttpHeadersArgumentResolver}.
28+
*
29+
* @author Yanming Zhou
30+
*/
31+
class HttpHeadersArgumentResolverTests {
32+
33+
private final TestExchangeAdapter client = new TestExchangeAdapter();
34+
35+
private final Service service =
36+
HttpServiceProxyFactory.builderFor(this.client).build().createClient(Service.class);
37+
38+
@Test
39+
void headers() {
40+
HttpHeaders headers = new HttpHeaders();
41+
headers.add("foo", "bar");
42+
headers.add("test", "testValue1");
43+
headers.add("test", "testValue2");
44+
this.service.execute(headers);
45+
46+
HttpHeaders actualHeaders = this.client.getRequestValues().getHeaders();
47+
assertThat(actualHeaders.get("foo")).containsOnly("bar");
48+
assertThat(actualHeaders.get("test")).containsExactlyInAnyOrder("testValue1", "testValue2");
49+
}
50+
51+
@Test
52+
void doesNotOverrideAnnotationHeaders() {
53+
HttpHeaders headers = new HttpHeaders();
54+
headers.add("foo", "bar");
55+
this.service.executeWithAnnotationHeaders(headers);
56+
57+
HttpHeaders actualHeaders = this.client.getRequestValues().getHeaders();
58+
assertThat(actualHeaders.get("foo")).containsExactlyInAnyOrder("foo", "bar");
59+
assertThat(actualHeaders.get("bar")).containsOnly("bar");
60+
}
61+
62+
private interface Service {
63+
64+
@GetExchange
65+
void execute(HttpHeaders headers);
66+
67+
@HttpExchange(method = "GET", headers = {"foo=foo", "bar=bar"})
68+
void executeWithAnnotationHeaders(HttpHeaders headers);
69+
70+
}
71+
72+
}

0 commit comments

Comments
 (0)