Skip to content

Commit 8aeced9

Browse files
committed
Support header filtering in web data binding
Closes gh-34039
1 parent 70c326e commit 8aeced9

File tree

4 files changed

+121
-4
lines changed

4 files changed

+121
-4
lines changed

spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/ExtendedWebExchangeDataBinder.java

+34-1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818

1919
import java.util.List;
2020
import java.util.Map;
21+
import java.util.Set;
22+
import java.util.function.Predicate;
2123

2224
import reactor.core.publisher.Mono;
2325

@@ -41,12 +43,40 @@
4143
*/
4244
public class ExtendedWebExchangeDataBinder extends WebExchangeDataBinder {
4345

46+
private static final Set<String> FILTERED_HEADER_NAMES = Set.of("Priority");
47+
48+
49+
private Predicate<String> headerPredicate = name -> !FILTERED_HEADER_NAMES.contains(name);
50+
4451

4552
public ExtendedWebExchangeDataBinder(@Nullable Object target, String objectName) {
4653
super(target, objectName);
4754
}
4855

4956

57+
/**
58+
* Add a Predicate that filters the header names to use for data binding.
59+
* Multiple predicates are combined with {@code AND}.
60+
* @param headerPredicate the predicate to add
61+
* @since 6.2.1
62+
*/
63+
public void addHeaderPredicate(Predicate<String> headerPredicate) {
64+
this.headerPredicate = this.headerPredicate.and(headerPredicate);
65+
}
66+
67+
/**
68+
* Set the Predicate that filters the header names to use for data binding.
69+
* <p>Note that this method resets any previous predicates that may have been
70+
* set, including headers excluded by default such as the RFC 9218 defined
71+
* "Priority" header.
72+
* @param headerPredicate the predicate to add
73+
* @since 6.2.1
74+
*/
75+
public void setHeaderPredicate(Predicate<String> headerPredicate) {
76+
this.headerPredicate = headerPredicate;
77+
}
78+
79+
5080
@Override
5181
public Mono<Map<String, Object>> getValuesToBind(ServerWebExchange exchange) {
5282
return super.getValuesToBind(exchange).doOnNext(map -> {
@@ -56,10 +86,13 @@ public Mono<Map<String, Object>> getValuesToBind(ServerWebExchange exchange) {
5686
}
5787
HttpHeaders headers = exchange.getRequest().getHeaders();
5888
for (Map.Entry<String, List<String>> entry : headers.entrySet()) {
89+
String name = entry.getKey();
90+
if (!this.headerPredicate.test(entry.getKey())) {
91+
continue;
92+
}
5993
List<String> values = entry.getValue();
6094
if (!CollectionUtils.isEmpty(values)) {
6195
// For constructor args with @BindParam mapped to the actual header name
62-
String name = entry.getKey();
6396
addValueIfNotPresent(map, "Header", name, (values.size() == 1 ? values.get(0) : values));
6497
// Also adapt to Java conventions for setters
6598
name = StringUtils.uncapitalize(entry.getKey().replace("-", ""));

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

+18
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,24 @@ void bindUriVarsAndHeadersAddedConditionally() throws Exception {
202202
assertThat(target.getAge()).isEqualTo(25);
203203
}
204204

205+
@Test
206+
void headerPredicate() throws Exception {
207+
MockServerHttpRequest request = MockServerHttpRequest.get("/path")
208+
.header("Priority", "u1")
209+
.header("Some-Int-Array", "1")
210+
.header("Another-Int-Array", "1")
211+
.build();
212+
213+
MockServerWebExchange exchange = MockServerWebExchange.from(request);
214+
215+
BindingContext context = createBindingContext("initBinderWithAttributeName", WebDataBinder.class);
216+
ExtendedWebExchangeDataBinder binder = (ExtendedWebExchangeDataBinder) context.createDataBinder(exchange, null, "", null);
217+
binder.addHeaderPredicate(name -> !name.equalsIgnoreCase("Another-Int-Array"));
218+
219+
Map<String, Object> map = binder.getValuesToBind(exchange).block();
220+
assertThat(map).containsExactlyInAnyOrderEntriesOf(Map.of("someIntArray", "1", "Some-Int-Array", "1"));
221+
}
222+
205223
private BindingContext createBindingContext(String methodName, Class<?>... parameterTypes) throws Exception {
206224
Object handler = new InitBinderHandler();
207225
Method method = handler.getClass().getMethod(methodName, parameterTypes);

spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ExtendedServletRequestDataBinder.java

+38-3
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,14 @@
2121
import java.util.List;
2222
import java.util.Map;
2323
import java.util.Set;
24+
import java.util.function.Predicate;
2425

2526
import jakarta.servlet.ServletRequest;
2627
import jakarta.servlet.http.HttpServletRequest;
2728

2829
import org.springframework.beans.MutablePropertyValues;
2930
import org.springframework.lang.Nullable;
31+
import org.springframework.util.StringUtils;
3032
import org.springframework.web.bind.ServletRequestDataBinder;
3133
import org.springframework.web.bind.WebDataBinder;
3234
import org.springframework.web.servlet.HandlerMapping;
@@ -51,6 +53,12 @@
5153
*/
5254
public class ExtendedServletRequestDataBinder extends ServletRequestDataBinder {
5355

56+
private static final Set<String> FILTERED_HEADER_NAMES = Set.of("Priority");
57+
58+
59+
private Predicate<String> headerPredicate = name -> !FILTERED_HEADER_NAMES.contains(name);
60+
61+
5462
/**
5563
* Create a new instance, with default object name.
5664
* @param target the target object to bind onto (or {@code null}
@@ -73,6 +81,29 @@ public ExtendedServletRequestDataBinder(@Nullable Object target, String objectNa
7381
}
7482

7583

84+
/**
85+
* Add a Predicate that filters the header names to use for data binding.
86+
* Multiple predicates are combined with {@code AND}.
87+
* @param headerPredicate the predicate to add
88+
* @since 6.2.1
89+
*/
90+
public void addHeaderPredicate(Predicate<String> headerPredicate) {
91+
this.headerPredicate = this.headerPredicate.and(headerPredicate);
92+
}
93+
94+
/**
95+
* Set the Predicate that filters the header names to use for data binding.
96+
* <p>Note that this method resets any previous predicates that may have been
97+
* set, including headers excluded by default such as the RFC 9218 defined
98+
* "Priority" header.
99+
* @param headerPredicate the predicate to add
100+
* @since 6.2.1
101+
*/
102+
public void setHeaderPredicate(Predicate<String> headerPredicate) {
103+
this.headerPredicate = headerPredicate;
104+
}
105+
106+
76107
@Override
77108
protected ServletRequestValueResolver createValueResolver(ServletRequest request) {
78109
return new ExtendedServletRequestValueResolver(request, this);
@@ -93,7 +124,7 @@ protected void addBindValues(MutablePropertyValues mpvs, ServletRequest request)
93124
String name = names.nextElement();
94125
Object value = getHeaderValue(httpRequest, name);
95126
if (value != null) {
96-
name = name.replace("-", "");
127+
name = StringUtils.uncapitalize(name.replace("-", ""));
97128
addValueIfNotPresent(mpvs, "Header", name, value);
98129
}
99130
}
@@ -118,7 +149,11 @@ private static void addValueIfNotPresent(MutablePropertyValues mpvs, String labe
118149
}
119150

120151
@Nullable
121-
private static Object getHeaderValue(HttpServletRequest request, String name) {
152+
private Object getHeaderValue(HttpServletRequest request, String name) {
153+
if (!this.headerPredicate.test(name)) {
154+
return null;
155+
}
156+
122157
Enumeration<String> valuesEnum = request.getHeaders(name);
123158
if (!valuesEnum.hasMoreElements()) {
124159
return null;
@@ -141,7 +176,7 @@ private static Object getHeaderValue(HttpServletRequest request, String name) {
141176
/**
142177
* Resolver of values that looks up URI path variables.
143178
*/
144-
private static class ExtendedServletRequestValueResolver extends ServletRequestValueResolver {
179+
private class ExtendedServletRequestValueResolver extends ServletRequestValueResolver {
145180

146181
ExtendedServletRequestValueResolver(ServletRequest request, WebDataBinder dataBinder) {
147182
super(request, dataBinder);

spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ExtendedServletRequestDataBinderTests.java

+31
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,11 @@
1818

1919
import java.util.Map;
2020

21+
import jakarta.servlet.ServletRequest;
2122
import org.junit.jupiter.api.BeforeEach;
2223
import org.junit.jupiter.api.Test;
2324

25+
import org.springframework.beans.MutablePropertyValues;
2426
import org.springframework.beans.testfixture.beans.TestBean;
2527
import org.springframework.core.ResolvableType;
2628
import org.springframework.web.bind.ServletRequestDataBinder;
@@ -102,6 +104,22 @@ void uriVarsAndHeadersAddedConditionally() {
102104
assertThat(target.getAge()).isEqualTo(25);
103105
}
104106

107+
@Test
108+
void headerPredicate() {
109+
TestBinder binder = new TestBinder();
110+
binder.addHeaderPredicate(name -> !name.equalsIgnoreCase("Another-Int-Array"));
111+
112+
MutablePropertyValues mpvs = new MutablePropertyValues();
113+
request.addHeader("Priority", "u1");
114+
request.addHeader("Some-Int-Array", "1");
115+
request.addHeader("Another-Int-Array", "1");
116+
117+
binder.addBindValues(mpvs, request);
118+
119+
assertThat(mpvs.size()).isEqualTo(1);
120+
assertThat(mpvs.get("someIntArray")).isEqualTo("1");
121+
}
122+
105123
@Test
106124
void noUriTemplateVars() {
107125
TestBean target = new TestBean();
@@ -116,4 +134,17 @@ void noUriTemplateVars() {
116134
private record DataBean(String name, int age, @BindParam("Some-Int-Array") Integer[] someIntArray) {
117135
}
118136

137+
138+
private static class TestBinder extends ExtendedServletRequestDataBinder {
139+
140+
public TestBinder() {
141+
super(null);
142+
}
143+
144+
@Override
145+
public void addBindValues(MutablePropertyValues mpvs, ServletRequest request) {
146+
super.addBindValues(mpvs, request);
147+
}
148+
}
149+
119150
}

0 commit comments

Comments
 (0)