Skip to content

Commit c563179

Browse files
committed
Add formData() and multipartData() to ServerRequest
Issue: SPR-16551
1 parent 1b83f12 commit c563179

File tree

7 files changed

+160
-6
lines changed

7 files changed

+160
-6
lines changed

spring-test/src/main/java/org/springframework/mock/web/reactive/function/server/MockServerRequest.java

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2017 the original author or authors.
2+
* Copyright 2002-2018 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.
@@ -40,6 +40,7 @@
4040
import org.springframework.http.HttpRange;
4141
import org.springframework.http.HttpRequest;
4242
import org.springframework.http.MediaType;
43+
import org.springframework.http.codec.multipart.Part;
4344
import org.springframework.http.server.PathContainer;
4445
import org.springframework.http.server.RequestPath;
4546
import org.springframework.http.server.reactive.ServerHttpRequest;
@@ -209,6 +210,21 @@ public Mono<? extends Principal> principal() {
209210
return Mono.justOrEmpty(this.principal);
210211
}
211212

213+
214+
@Override
215+
@SuppressWarnings("unchecked")
216+
public Mono<MultiValueMap<String, String>> formData() {
217+
Assert.state(this.body != null, "No body");
218+
return (Mono<MultiValueMap<String, String>>) this.body;
219+
}
220+
221+
@Override
222+
@SuppressWarnings("unchecked")
223+
public Mono<MultiValueMap<String, Part>> multipartData() {
224+
Assert.state(this.body != null, "No body");
225+
return (Mono<MultiValueMap<String, Part>>) this.body;
226+
}
227+
212228
public static Builder builder() {
213229
return new BuilderImpl();
214230
}

spring-webflux/src/main/java/org/springframework/web/reactive/function/server/DefaultServerRequest.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2017 the original author or authors.
2+
* Copyright 2002-2018 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.
@@ -39,6 +39,7 @@
3939
import org.springframework.http.HttpRequest;
4040
import org.springframework.http.MediaType;
4141
import org.springframework.http.codec.HttpMessageReader;
42+
import org.springframework.http.codec.multipart.Part;
4243
import org.springframework.http.server.PathContainer;
4344
import org.springframework.http.server.reactive.ServerHttpRequest;
4445
import org.springframework.http.server.reactive.ServerHttpResponse;
@@ -190,6 +191,16 @@ public Mono<? extends Principal> principal() {
190191
return this.exchange.getPrincipal();
191192
}
192193

194+
@Override
195+
public Mono<MultiValueMap<String, String>> formData() {
196+
return this.exchange.getFormData();
197+
}
198+
199+
@Override
200+
public Mono<MultiValueMap<String, Part>> multipartData() {
201+
return this.exchange.getMultipartData();
202+
}
203+
193204
private ServerHttpRequest request() {
194205
return this.exchange.getRequest();
195206
}

spring-webflux/src/main/java/org/springframework/web/reactive/function/server/RequestPredicates.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2017 the original author or authors.
2+
* Copyright 2002-2018 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.
@@ -39,6 +39,7 @@
3939
import org.springframework.http.HttpCookie;
4040
import org.springframework.http.HttpMethod;
4141
import org.springframework.http.MediaType;
42+
import org.springframework.http.codec.multipart.Part;
4243
import org.springframework.http.server.PathContainer;
4344
import org.springframework.http.server.reactive.ServerHttpRequest;
4445
import org.springframework.lang.Nullable;
@@ -582,6 +583,16 @@ public Mono<? extends Principal> principal() {
582583
return this.request.principal();
583584
}
584585

586+
@Override
587+
public Mono<MultiValueMap<String, String>> formData() {
588+
return this.request.formData();
589+
}
590+
591+
@Override
592+
public Mono<MultiValueMap<String, Part>> multipartData() {
593+
return this.request.multipartData();
594+
}
595+
585596
@Override
586597
public String toString() {
587598
return method() + " " + path();

spring-webflux/src/main/java/org/springframework/web/reactive/function/server/ServerRequest.java

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2017 the original author or authors.
2+
* Copyright 2002-2018 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.
@@ -37,6 +37,7 @@
3737
import org.springframework.http.MediaType;
3838
import org.springframework.http.codec.HttpMessageReader;
3939
import org.springframework.http.codec.json.Jackson2CodecSupport;
40+
import org.springframework.http.codec.multipart.Part;
4041
import org.springframework.http.server.PathContainer;
4142
import org.springframework.http.server.reactive.ServerHttpRequest;
4243
import org.springframework.lang.Nullable;
@@ -244,6 +245,27 @@ default String pathVariable(String name) {
244245
*/
245246
Mono<? extends Principal> principal();
246247

248+
/**
249+
* Return the form data from the body of the request if the Content-Type is
250+
* {@code "application/x-www-form-urlencoded"} or an empty map otherwise.
251+
*
252+
* <p><strong>Note:</strong> calling this method causes the request body to
253+
* be read and parsed in full and the resulting {@code MultiValueMap} is
254+
* cached so that this method is safe to call more than once.
255+
*/
256+
Mono<MultiValueMap<String, String>> formData();
257+
258+
/**
259+
* Return the parts of a multipart request if the Content-Type is
260+
* {@code "multipart/form-data"} or an empty map otherwise.
261+
*
262+
* <p><strong>Note:</strong> calling this method causes the request body to
263+
* be read and parsed in full and the resulting {@code MultiValueMap} is
264+
* cached so that this method is safe to call more than once.
265+
*/
266+
Mono<MultiValueMap<String, Part>> multipartData();
267+
268+
247269

248270
/**
249271
* Create a new {@code ServerRequest} based on the given {@code ServerWebExchange} and

spring-webflux/src/main/java/org/springframework/web/reactive/function/server/support/ServerRequestWrapper.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
import org.springframework.http.HttpMethod;
3636
import org.springframework.http.HttpRange;
3737
import org.springframework.http.MediaType;
38+
import org.springframework.http.codec.multipart.Part;
3839
import org.springframework.http.server.PathContainer;
3940
import org.springframework.http.server.reactive.ServerHttpRequest;
4041
import org.springframework.util.Assert;
@@ -185,6 +186,16 @@ public Mono<? extends Principal> principal() {
185186
return this.delegate.principal();
186187
}
187188

189+
@Override
190+
public Mono<MultiValueMap<String, String>> formData() {
191+
return this.delegate.formData();
192+
}
193+
194+
@Override
195+
public Mono<MultiValueMap<String, Part>> multipartData() {
196+
return this.delegate.multipartData();
197+
}
198+
188199
/**
189200
* Implementation of the {@code Headers} interface that can be subclassed
190201
* to adapt the headers in a

spring-webflux/src/test/java/org/springframework/web/reactive/function/server/DefaultServerRequestTests.java

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,13 +44,15 @@
4444
import org.springframework.http.MediaType;
4545
import org.springframework.http.codec.DecoderHttpMessageReader;
4646
import org.springframework.http.codec.HttpMessageReader;
47+
import org.springframework.http.codec.multipart.FormFieldPart;
48+
import org.springframework.http.codec.multipart.Part;
4749
import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest;
4850
import org.springframework.mock.web.test.server.MockServerWebExchange;
4951
import org.springframework.util.LinkedMultiValueMap;
5052
import org.springframework.util.MultiValueMap;
5153
import org.springframework.web.server.UnsupportedMediaTypeStatusException;
5254

53-
import static org.junit.Assert.assertEquals;
55+
import static org.junit.Assert.*;
5456
import static org.springframework.web.reactive.function.BodyExtractors.toMono;
5557

5658
/**
@@ -336,4 +338,70 @@ public void bodyUnacceptable() throws Exception {
336338
.verify();
337339
}
338340

341+
@Test
342+
public void formData() throws Exception {
343+
DefaultDataBufferFactory factory = new DefaultDataBufferFactory();
344+
DefaultDataBuffer dataBuffer =
345+
factory.wrap(ByteBuffer.wrap("foo=bar&baz=qux".getBytes(StandardCharsets.UTF_8)));
346+
Flux<DataBuffer> body = Flux.just(dataBuffer);
347+
348+
HttpHeaders httpHeaders = new HttpHeaders();
349+
httpHeaders.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
350+
MockServerHttpRequest mockRequest = MockServerHttpRequest
351+
.method(HttpMethod.GET, "http://example.com")
352+
.headers(httpHeaders)
353+
.body(body);
354+
DefaultServerRequest request = new DefaultServerRequest(MockServerWebExchange.from(mockRequest), Collections.emptyList());
355+
356+
Mono<MultiValueMap<String, String>> resultData = request.formData();
357+
StepVerifier.create(resultData)
358+
.consumeNextWith(formData -> {
359+
assertEquals(2, formData.size());
360+
assertEquals("bar", formData.getFirst("foo"));
361+
assertEquals("qux", formData.getFirst("baz"));
362+
})
363+
.verifyComplete();
364+
}
365+
366+
@Test
367+
public void multipartData() throws Exception {
368+
String data = "--12345\r\n" +
369+
"Content-Disposition: form-data; name=\"foo\"\r\n" +
370+
"\r\n" +
371+
"bar\r\n" +
372+
"--12345\r\n" +
373+
"Content-Disposition: form-data; name=\"baz\"\r\n" +
374+
"\r\n" +
375+
"qux\r\n" +
376+
"--12345--\r\n";
377+
DefaultDataBufferFactory factory = new DefaultDataBufferFactory();
378+
DefaultDataBuffer dataBuffer =
379+
factory.wrap(ByteBuffer.wrap(data.getBytes(StandardCharsets.UTF_8)));
380+
Flux<DataBuffer> body = Flux.just(dataBuffer);
381+
382+
HttpHeaders httpHeaders = new HttpHeaders();
383+
httpHeaders.set(HttpHeaders.CONTENT_TYPE, "multipart/form-data; boundary=12345");
384+
MockServerHttpRequest mockRequest = MockServerHttpRequest
385+
.method(HttpMethod.GET, "http://example.com")
386+
.headers(httpHeaders)
387+
.body(body);
388+
DefaultServerRequest request = new DefaultServerRequest(MockServerWebExchange.from(mockRequest), Collections.emptyList());
389+
390+
Mono<MultiValueMap<String, Part>> resultData = request.multipartData();
391+
StepVerifier.create(resultData)
392+
.consumeNextWith(formData -> {
393+
assertEquals(2, formData.size());
394+
395+
Part part = formData.getFirst("foo");
396+
assertTrue(part instanceof FormFieldPart);
397+
FormFieldPart formFieldPart = (FormFieldPart) part;
398+
assertEquals("bar", formFieldPart.value());
399+
400+
part = formData.getFirst("baz");
401+
assertTrue(part instanceof FormFieldPart);
402+
formFieldPart = (FormFieldPart) part;
403+
assertEquals("qux", formFieldPart.value());
404+
})
405+
.verifyComplete();
406+
}
339407
}

spring-webflux/src/test/java/org/springframework/web/reactive/function/server/MockServerRequest.java

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2017 the original author or authors.
2+
* Copyright 2002-2018 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.
@@ -40,6 +40,7 @@
4040
import org.springframework.http.HttpRange;
4141
import org.springframework.http.HttpRequest;
4242
import org.springframework.http.MediaType;
43+
import org.springframework.http.codec.multipart.Part;
4344
import org.springframework.http.server.PathContainer;
4445
import org.springframework.http.server.RequestPath;
4546
import org.springframework.http.server.reactive.ServerHttpRequest;
@@ -208,6 +209,20 @@ public Mono<? extends Principal> principal() {
208209
return Mono.justOrEmpty(this.principal);
209210
}
210211

212+
@Override
213+
@SuppressWarnings("unchecked")
214+
public Mono<MultiValueMap<String, String>> formData() {
215+
Assert.state(this.body != null, "No body");
216+
return (Mono<MultiValueMap<String, String>>) this.body;
217+
}
218+
219+
@Override
220+
@SuppressWarnings("unchecked")
221+
public Mono<MultiValueMap<String, Part>> multipartData() {
222+
Assert.state(this.body != null, "No body");
223+
return (Mono<MultiValueMap<String, Part>>) this.body;
224+
}
225+
211226
public static Builder builder() {
212227
return new BuilderImpl();
213228
}

0 commit comments

Comments
 (0)