Data binding from pathvars and headers in fn handlers

Closes gh-35800
This commit is contained in:
rstoyanchev
2025-11-26 12:30:48 +00:00
parent b29f4ed37e
commit a9a404266c
7 changed files with 30 additions and 12 deletions
@@ -296,7 +296,8 @@ allPartsEvents.windowUntil(PartEvent::isLast)
NOTE: The body contents of the `PartEvent` objects must be completely consumed, relayed, or released to avoid memory leaks.
The following shows how to bind request parameters, including an optional `DataBinder` customization:
The following shows how to bind request parameters, URI variables, or headers via `DataBinder`,
and also shows how to customize the `DataBinder`:
[tabs]
======
@@ -184,7 +184,8 @@ val map = request.params()
----
======
The following shows how to bind request parameters, including an optional `DataBinder` customization:
The following shows how to bind request parameters, URI variables, or headers via `DataBinder`,
and also shows how to customize the `DataBinder`:
[tabs]
======
@@ -61,6 +61,7 @@ import org.springframework.web.reactive.accept.ApiVersionStrategy;
import org.springframework.web.reactive.function.BodyExtractor;
import org.springframework.web.reactive.function.BodyExtractors;
import org.springframework.web.reactive.function.UnsupportedMediaTypeException;
import org.springframework.web.reactive.result.method.annotation.ExtendedWebExchangeDataBinder;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.ServerWebInputException;
import org.springframework.web.server.UnsupportedMediaTypeStatusException;
@@ -244,7 +245,7 @@ class DefaultServerRequest implements ServerRequest {
Assert.notNull(dataBinderCustomizer, "DataBinderCustomizer must not be null");
return Mono.defer(() -> {
WebExchangeDataBinder dataBinder = new WebExchangeDataBinder(null);
WebExchangeDataBinder dataBinder = new ExtendedWebExchangeDataBinder(null);
dataBinder.setTargetType(ResolvableType.forClass(bindType));
dataBinderCustomizer.accept(dataBinder);
@@ -51,6 +51,17 @@ public class ExtendedWebExchangeDataBinder extends WebExchangeDataBinder {
private Predicate<String> headerPredicate = name -> !FILTERED_HEADER_NAMES.contains(name.toLowerCase(Locale.ROOT));
/**
* Create a new instance, with default object name.
* @param target the target object to bind onto (or {@code null} if the
* binder is just used to convert a plain parameter value)
* @since 7.0.2
* @see #DEFAULT_OBJECT_NAME
*/
public ExtendedWebExchangeDataBinder(@Nullable Object target) {
super(target);
}
public ExtendedWebExchangeDataBinder(@Nullable Object target, String objectName) {
super(target, objectName);
}
@@ -30,6 +30,7 @@ import org.springframework.web.testfixture.http.server.reactive.bootstrap.HttpSe
import static org.springframework.web.reactive.function.server.RouterFunctions.route;
/**
* Integration tests with data binding in fn handlers.
* @author Arjen Poutsma
*/
class BindingFunctionIntegrationTests extends AbstractRouterFunctionIntegrationTests {
@@ -65,7 +66,8 @@ class BindingFunctionIntegrationTests extends AbstractRouterFunctionIntegrationT
startServer(httpServer);
Mono<String> result = this.webClient.get()
.uri("/constructor?foo=FOO&bar=BAR")
.uri("/constructor?foo=FOO")
.header("bar", "BAR")
.retrieve()
.bodyToMono(String.class);
@@ -80,7 +82,8 @@ class BindingFunctionIntegrationTests extends AbstractRouterFunctionIntegrationT
startServer(httpServer);
Mono<String> result = this.webClient.get()
.uri("/property?foo=FOO&bar=BAR")
.uri("/property?foo=FOO")
.header("bar", "BAR")
.retrieve()
.bodyToMono(String.class);
@@ -95,7 +98,8 @@ class BindingFunctionIntegrationTests extends AbstractRouterFunctionIntegrationT
startServer(httpServer);
Mono<String> result = this.webClient.get()
.uri("/mixed?foo=FOO&bar=BAR")
.uri("/mixed?foo=FOO")
.header("bar", "BAR")
.retrieve()
.bodyToMono(String.class);
@@ -76,6 +76,7 @@ import org.springframework.web.bind.ServletRequestDataBinder;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.mvc.method.annotation.ExtendedServletRequestDataBinder;
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
import org.springframework.web.util.ServletRequestPathUtils;
import org.springframework.web.util.UriBuilder;
@@ -258,7 +259,7 @@ class DefaultServerRequest implements ServerRequest {
Assert.notNull(bindType, "BindType must not be null");
Assert.notNull(dataBinderCustomizer, "DataBinderCustomizer must not be null");
ServletRequestDataBinder dataBinder = new ServletRequestDataBinder(null);
ServletRequestDataBinder dataBinder = new ExtendedServletRequestDataBinder(null);
dataBinder.setTargetType(ResolvableType.forClass(bindType));
dataBinderCustomizer.accept(dataBinder);
@@ -64,8 +64,7 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
/**
* Tests for {@link DefaultServerRequest}.
*
* Unit tests for {@link DefaultServerRequest}.
* @author Arjen Poutsma
*/
class DefaultServerRequestTests {
@@ -350,7 +349,7 @@ class DefaultServerRequestTests {
void bindToConstructor() throws BindException {
MockHttpServletRequest servletRequest = PathPatternsTestUtils.initRequest("GET", "/", true);
servletRequest.addParameter("foo", "FOO");
servletRequest.addParameter("bar", "BAR");
servletRequest.addHeader("bar", "BAR");
DefaultServerRequest request = new DefaultServerRequest(servletRequest, this.messageConverters);
@@ -363,7 +362,7 @@ class DefaultServerRequestTests {
void bindToProperties() throws BindException {
MockHttpServletRequest servletRequest = PathPatternsTestUtils.initRequest("GET", "/", true);
servletRequest.addParameter("foo", "FOO");
servletRequest.addParameter("bar", "BAR");
servletRequest.addHeader("bar", "BAR");
DefaultServerRequest request = new DefaultServerRequest(servletRequest, this.messageConverters);
@@ -376,7 +375,7 @@ class DefaultServerRequestTests {
void bindToMixed() throws BindException {
MockHttpServletRequest servletRequest = PathPatternsTestUtils.initRequest("GET", "/", true);
servletRequest.addParameter("foo", "FOO");
servletRequest.addParameter("bar", "BAR");
servletRequest.addHeader("bar", "BAR");
DefaultServerRequest request = new DefaultServerRequest(servletRequest, this.messageConverters);