Skip to content

Commit 00a3589

Browse files
committed
ServerWebExchange provides access to form data
The ServerWebExchange now has a getFormData() method that delegates to FormHttpMessageReader for the parsing and then caches the result so it may be used multiples times during request processing. Issue: SPR-14541
1 parent 81b4ded commit 00a3589

File tree

6 files changed

+190
-117
lines changed

6 files changed

+190
-117
lines changed

spring-web/src/main/java/org/springframework/web/bind/WebExchangeDataBinder.java

+2-29
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,6 @@
2424
import reactor.core.publisher.Mono;
2525

2626
import org.springframework.beans.MutablePropertyValues;
27-
import org.springframework.core.ResolvableType;
28-
import org.springframework.http.MediaType;
29-
import org.springframework.http.codec.HttpMessageReader;
3027
import org.springframework.http.server.reactive.ServerHttpRequest;
3128
import org.springframework.util.LinkedMultiValueMap;
3229
import org.springframework.util.MultiValueMap;
@@ -42,11 +39,6 @@
4239
*/
4340
public class WebExchangeDataBinder extends WebDataBinder {
4441

45-
private static final ResolvableType MULTIVALUE_MAP_TYPE = ResolvableType.forClass(MultiValueMap.class);
46-
47-
48-
private HttpMessageReader<MultiValueMap<String, String>> formReader = null;
49-
5042

5143
/**
5244
* Create a new instance, with default object name.
@@ -69,15 +61,6 @@ public WebExchangeDataBinder(Object target, String objectName) {
6961
}
7062

7163

72-
public void setFormReader(HttpMessageReader<MultiValueMap<String, String>> formReader) {
73-
this.formReader = formReader;
74-
}
75-
76-
public HttpMessageReader<MultiValueMap<String, String>> getFormReader() {
77-
return this.formReader;
78-
}
79-
80-
8164
/**
8265
* Bind the URL query parameters or form data of the body of the given request
8366
* to this binder's target. The request body is parsed if the content-type
@@ -90,7 +73,8 @@ public Mono<Void> bind(ServerWebExchange exchange) {
9073

9174
ServerHttpRequest request = exchange.getRequest();
9275
Mono<MultiValueMap<String, String>> queryParams = Mono.just(request.getQueryParams());
93-
Mono<MultiValueMap<String, String>> formParams = getFormParams(exchange);
76+
Mono<MultiValueMap<String, String>> formParams =
77+
exchange.getFormData().defaultIfEmpty(new LinkedMultiValueMap<>());
9478

9579
return Mono.zip(this::mergeParams, queryParams, formParams)
9680
.map(this::getParamsToBind)
@@ -102,17 +86,6 @@ public Mono<Void> bind(ServerWebExchange exchange) {
10286
});
10387
}
10488

105-
private Mono<MultiValueMap<String, String>> getFormParams(ServerWebExchange exchange) {
106-
ServerHttpRequest request = exchange.getRequest();
107-
MediaType contentType = request.getHeaders().getContentType();
108-
if (this.formReader.canRead(MULTIVALUE_MAP_TYPE, contentType)) {
109-
return this.formReader.readMono(MULTIVALUE_MAP_TYPE, request, Collections.emptyMap());
110-
}
111-
else {
112-
return Mono.just(new LinkedMultiValueMap<>());
113-
}
114-
}
115-
11689
@SuppressWarnings("unchecked")
11790
private MultiValueMap<String, String> mergeParams(Object[] paramMaps) {
11891
MultiValueMap<String, String> result = new LinkedMultiValueMap<>();

spring-web/src/main/java/org/springframework/web/server/DefaultServerWebExchangeMutativeBuilder.java

+20-3
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import org.springframework.http.server.reactive.ServerHttpRequest;
2424
import org.springframework.http.server.reactive.ServerHttpResponse;
2525
import org.springframework.util.Assert;
26+
import org.springframework.util.MultiValueMap;
2627

2728
/**
2829
* Default implementation of
@@ -43,6 +44,8 @@ class DefaultServerWebExchangeMutativeBuilder implements ServerWebExchange.Mutat
4344

4445
private Mono<WebSession> session;
4546

47+
private Mono<MultiValueMap<String, String>> formData;
48+
4649

4750
public DefaultServerWebExchangeMutativeBuilder(ServerWebExchange delegate) {
4851
Assert.notNull(delegate, "'delegate' is required.");
@@ -74,10 +77,16 @@ public ServerWebExchange.MutativeBuilder setSession(Mono<WebSession> session) {
7477
return this;
7578
}
7679

80+
@Override
81+
public ServerWebExchange.MutativeBuilder setFormData(Mono<MultiValueMap<String, String>> formData) {
82+
this.formData = formData;
83+
return this;
84+
}
85+
7786
@Override
7887
public ServerWebExchange build() {
79-
return new MutativeDecorator(this.delegate,
80-
this.request, this.response, this.user, this.session);
88+
return new MutativeDecorator(this.delegate, this.request, this.response,
89+
this.user, this.session, this.formData);
8190
}
8291

8392

@@ -95,16 +104,19 @@ private static class MutativeDecorator extends ServerWebExchangeDecorator {
95104

96105
private final Mono<WebSession> session;
97106

107+
private final Mono<MultiValueMap<String, String>> formData;
108+
98109

99110
public MutativeDecorator(ServerWebExchange delegate,
100111
ServerHttpRequest request, ServerHttpResponse response, Principal user,
101-
Mono<WebSession> session) {
112+
Mono<WebSession> session, Mono<MultiValueMap<String, String>> formData) {
102113

103114
super(delegate);
104115
this.request = request;
105116
this.response = response;
106117
this.user = user;
107118
this.session = session;
119+
this.formData = formData;
108120
}
109121

110122

@@ -128,6 +140,11 @@ public Mono<WebSession> getSession() {
128140
public <T extends Principal> Optional<T> getPrincipal() {
129141
return (this.user != null ? Optional.of((T) this.user) : getDelegate().getPrincipal());
130142
}
143+
144+
@Override
145+
public Mono<MultiValueMap<String, String>> getFormData() {
146+
return (this.formData != null ? this.formData : getDelegate().getFormData());
147+
}
131148
}
132149

133150
}

spring-web/src/main/java/org/springframework/web/server/ServerWebExchange.java

+12
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525

2626
import org.springframework.http.server.reactive.ServerHttpRequest;
2727
import org.springframework.http.server.reactive.ServerHttpResponse;
28+
import org.springframework.util.MultiValueMap;
2829

2930
/**
3031
* Contract for an HTTP request-response interaction. Provides access to the HTTP
@@ -74,6 +75,12 @@ public interface ServerWebExchange {
7475
*/
7576
<T extends Principal> Optional<T> getPrincipal();
7677

78+
/**
79+
* Return the form data from the body of the request or an empty {@code Mono}
80+
* if the Content-Type is not "application/x-www-form-urlencoded".
81+
*/
82+
Mono<MultiValueMap<String, String>> getFormData();
83+
7784
/**
7885
* Returns {@code true} if the one of the {@code checkNotModified} methods
7986
* in this contract were used and they returned true.
@@ -155,6 +162,11 @@ interface MutativeBuilder {
155162
*/
156163
MutativeBuilder setSession(Mono<WebSession> session);
157164

165+
/**
166+
* Set the form data.
167+
*/
168+
MutativeBuilder setFormData(Mono<MultiValueMap<String, String>> formData);
169+
158170
/**
159171
* Build an immutable wrapper that returning the mutated properties.
160172
*/

spring-web/src/main/java/org/springframework/web/server/ServerWebExchangeDecorator.java

+16-10
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import org.springframework.http.server.reactive.ServerHttpRequest;
2626
import org.springframework.http.server.reactive.ServerHttpResponse;
2727
import org.springframework.util.Assert;
28+
import org.springframework.util.MultiValueMap;
2829

2930
/**
3031
* A convenient base class for classes that need to wrap another
@@ -59,52 +60,57 @@ public ServerWebExchange getDelegate() {
5960

6061
@Override
6162
public ServerHttpRequest getRequest() {
62-
return this.getDelegate().getRequest();
63+
return getDelegate().getRequest();
6364
}
6465

6566
@Override
6667
public ServerHttpResponse getResponse() {
67-
return this.getDelegate().getResponse();
68+
return getDelegate().getResponse();
6869
}
6970

7071
@Override
7172
public Map<String, Object> getAttributes() {
72-
return this.getDelegate().getAttributes();
73+
return getDelegate().getAttributes();
7374
}
7475

7576
@Override
7677
public <T> Optional<T> getAttribute(String name) {
77-
return this.getDelegate().getAttribute(name);
78+
return getDelegate().getAttribute(name);
7879
}
7980

8081
@Override
8182
public Mono<WebSession> getSession() {
82-
return this.getDelegate().getSession();
83+
return getDelegate().getSession();
8384
}
8485

8586
@Override
8687
public <T extends Principal> Optional<T> getPrincipal() {
87-
return this.getDelegate().getPrincipal();
88+
return getDelegate().getPrincipal();
89+
}
90+
91+
@Override
92+
public Mono<MultiValueMap<String, String>> getFormData() {
93+
return getDelegate().getFormData();
8894
}
8995

9096
@Override
9197
public boolean isNotModified() {
92-
return this.getDelegate().isNotModified();
98+
return getDelegate().isNotModified();
9399
}
94100

95101
@Override
96102
public boolean checkNotModified(Instant lastModified) {
97-
return this.getDelegate().checkNotModified(lastModified);
103+
return getDelegate().checkNotModified(lastModified);
98104
}
99105

100106
@Override
101107
public boolean checkNotModified(String etag) {
102-
return this.getDelegate().checkNotModified(etag);
108+
return getDelegate().checkNotModified(etag);
103109
}
104110

105111
@Override
106112
public boolean checkNotModified(String etag, Instant lastModified) {
107-
return this.getDelegate().checkNotModified(etag, lastModified);
113+
return getDelegate().checkNotModified(etag, lastModified);
108114
}
109115

110116

spring-web/src/main/java/org/springframework/web/server/adapter/DefaultServerWebExchange.java

+34
Original file line numberDiff line numberDiff line change
@@ -20,19 +20,25 @@
2020
import java.time.Instant;
2121
import java.time.temporal.ChronoUnit;
2222
import java.util.Arrays;
23+
import java.util.Collections;
2324
import java.util.List;
2425
import java.util.Map;
2526
import java.util.Optional;
2627
import java.util.concurrent.ConcurrentHashMap;
2728

2829
import reactor.core.publisher.Mono;
2930

31+
import org.springframework.core.ResolvableType;
3032
import org.springframework.http.HttpHeaders;
3133
import org.springframework.http.HttpMethod;
3234
import org.springframework.http.HttpStatus;
35+
import org.springframework.http.InvalidMediaTypeException;
36+
import org.springframework.http.MediaType;
37+
import org.springframework.http.codec.FormHttpMessageReader;
3338
import org.springframework.http.server.reactive.ServerHttpRequest;
3439
import org.springframework.http.server.reactive.ServerHttpResponse;
3540
import org.springframework.util.Assert;
41+
import org.springframework.util.MultiValueMap;
3642
import org.springframework.util.StringUtils;
3743
import org.springframework.web.server.ServerWebExchange;
3844
import org.springframework.web.server.WebSession;
@@ -48,6 +54,11 @@ public class DefaultServerWebExchange implements ServerWebExchange {
4854

4955
private static final List<HttpMethod> SAFE_METHODS = Arrays.asList(HttpMethod.GET, HttpMethod.HEAD);
5056

57+
private static final FormHttpMessageReader FORM_READER = new FormHttpMessageReader();
58+
59+
private static final ResolvableType MULTIVALUE_TYPE =
60+
ResolvableType.forClassWithGenerics(MultiValueMap.class, String.class, String.class);
61+
5162

5263
private final ServerHttpRequest request;
5364

@@ -57,6 +68,8 @@ public class DefaultServerWebExchange implements ServerWebExchange {
5768

5869
private final Mono<WebSession> sessionMono;
5970

71+
private final Mono<MultiValueMap<String, String>> formDataMono;
72+
6073
private volatile boolean notModified;
6174

6275

@@ -66,9 +79,25 @@ public DefaultServerWebExchange(ServerHttpRequest request, ServerHttpResponse re
6679
Assert.notNull(request, "'request' is required");
6780
Assert.notNull(response, "'response' is required");
6881
Assert.notNull(response, "'sessionManager' is required");
82+
Assert.notNull(response, "'formReader' is required");
6983
this.request = request;
7084
this.response = response;
7185
this.sessionMono = sessionManager.getSession(this).cache();
86+
this.formDataMono = initFormData(request);
87+
}
88+
89+
private static Mono<MultiValueMap<String, String>> initFormData(ServerHttpRequest request) {
90+
MediaType contentType;
91+
try {
92+
contentType = request.getHeaders().getContentType();
93+
if (MediaType.APPLICATION_FORM_URLENCODED.isCompatibleWith(contentType)) {
94+
return FORM_READER.readMono(MULTIVALUE_TYPE, request, Collections.emptyMap()).cache();
95+
}
96+
}
97+
catch (InvalidMediaTypeException ex) {
98+
// Ignore
99+
}
100+
return Mono.empty();
72101
}
73102

74103

@@ -110,6 +139,11 @@ public <T extends Principal> Optional<T> getPrincipal() {
110139
return Optional.empty();
111140
}
112141

142+
@Override
143+
public Mono<MultiValueMap<String, String>> getFormData() {
144+
return this.formDataMono;
145+
}
146+
113147
@Override
114148
public boolean isNotModified() {
115149
return this.notModified;

0 commit comments

Comments
 (0)