diff --git a/spring-test/src/main/java/org/springframework/mock/web/reactive/function/server/MockServerRequest.java b/spring-test/src/main/java/org/springframework/mock/web/reactive/function/server/MockServerRequest.java index 4adca3e3084..8499476861e 100644 --- a/spring-test/src/main/java/org/springframework/mock/web/reactive/function/server/MockServerRequest.java +++ b/spring-test/src/main/java/org/springframework/mock/web/reactive/function/server/MockServerRequest.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -40,6 +40,7 @@ import org.springframework.http.HttpMethod; import org.springframework.http.HttpRange; import org.springframework.http.HttpRequest; import org.springframework.http.MediaType; +import org.springframework.http.codec.multipart.Part; import org.springframework.http.server.PathContainer; import org.springframework.http.server.RequestPath; import org.springframework.http.server.reactive.ServerHttpRequest; @@ -209,6 +210,21 @@ public class MockServerRequest implements ServerRequest { return Mono.justOrEmpty(this.principal); } + + @Override + @SuppressWarnings("unchecked") + public Mono> formData() { + Assert.state(this.body != null, "No body"); + return (Mono>) this.body; + } + + @Override + @SuppressWarnings("unchecked") + public Mono> multipartData() { + Assert.state(this.body != null, "No body"); + return (Mono>) this.body; + } + public static Builder builder() { return new BuilderImpl(); } diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/DefaultServerRequest.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/DefaultServerRequest.java index 1f770e9b4a5..a8c115b4fb9 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/DefaultServerRequest.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/DefaultServerRequest.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -39,6 +39,7 @@ import org.springframework.http.HttpRange; import org.springframework.http.HttpRequest; import org.springframework.http.MediaType; import org.springframework.http.codec.HttpMessageReader; +import org.springframework.http.codec.multipart.Part; import org.springframework.http.server.PathContainer; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.http.server.reactive.ServerHttpResponse; @@ -190,6 +191,16 @@ class DefaultServerRequest implements ServerRequest { return this.exchange.getPrincipal(); } + @Override + public Mono> formData() { + return this.exchange.getFormData(); + } + + @Override + public Mono> multipartData() { + return this.exchange.getMultipartData(); + } + private ServerHttpRequest request() { return this.exchange.getRequest(); } diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/RequestPredicates.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/RequestPredicates.java index 1da5a793ec0..654990265ad 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/RequestPredicates.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/RequestPredicates.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -39,6 +39,7 @@ import org.springframework.core.ParameterizedTypeReference; import org.springframework.http.HttpCookie; import org.springframework.http.HttpMethod; import org.springframework.http.MediaType; +import org.springframework.http.codec.multipart.Part; import org.springframework.http.server.PathContainer; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.lang.Nullable; @@ -582,6 +583,16 @@ public abstract class RequestPredicates { return this.request.principal(); } + @Override + public Mono> formData() { + return this.request.formData(); + } + + @Override + public Mono> multipartData() { + return this.request.multipartData(); + } + @Override public String toString() { return method() + " " + path(); diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/ServerRequest.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/ServerRequest.java index 594dc845282..9ee7d8cb2d3 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/ServerRequest.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/ServerRequest.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -37,6 +37,7 @@ import org.springframework.http.HttpRange; import org.springframework.http.MediaType; import org.springframework.http.codec.HttpMessageReader; import org.springframework.http.codec.json.Jackson2CodecSupport; +import org.springframework.http.codec.multipart.Part; import org.springframework.http.server.PathContainer; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.lang.Nullable; @@ -244,6 +245,27 @@ public interface ServerRequest { */ Mono principal(); + /** + * Return the form data from the body of the request if the Content-Type is + * {@code "application/x-www-form-urlencoded"} or an empty map otherwise. + * + *

Note: calling this method causes the request body to + * be read and parsed in full and the resulting {@code MultiValueMap} is + * cached so that this method is safe to call more than once. + */ + Mono> formData(); + + /** + * Return the parts of a multipart request if the Content-Type is + * {@code "multipart/form-data"} or an empty map otherwise. + * + *

Note: calling this method causes the request body to + * be read and parsed in full and the resulting {@code MultiValueMap} is + * cached so that this method is safe to call more than once. + */ + Mono> multipartData(); + + /** * Create a new {@code ServerRequest} based on the given {@code ServerWebExchange} and diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/support/ServerRequestWrapper.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/support/ServerRequestWrapper.java index 86176860705..dde16b7dd18 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/support/ServerRequestWrapper.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/support/ServerRequestWrapper.java @@ -35,6 +35,7 @@ import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.HttpRange; import org.springframework.http.MediaType; +import org.springframework.http.codec.multipart.Part; import org.springframework.http.server.PathContainer; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.util.Assert; @@ -185,6 +186,16 @@ public class ServerRequestWrapper implements ServerRequest { return this.delegate.principal(); } + @Override + public Mono> formData() { + return this.delegate.formData(); + } + + @Override + public Mono> multipartData() { + return this.delegate.multipartData(); + } + /** * Implementation of the {@code Headers} interface that can be subclassed * to adapt the headers in a diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/function/server/DefaultServerRequestTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/function/server/DefaultServerRequestTests.java index 2551621bdd9..74d7ea8fb99 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/function/server/DefaultServerRequestTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/function/server/DefaultServerRequestTests.java @@ -44,13 +44,15 @@ import org.springframework.http.HttpRange; import org.springframework.http.MediaType; import org.springframework.http.codec.DecoderHttpMessageReader; import org.springframework.http.codec.HttpMessageReader; +import org.springframework.http.codec.multipart.FormFieldPart; +import org.springframework.http.codec.multipart.Part; import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest; import org.springframework.mock.web.test.server.MockServerWebExchange; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.web.server.UnsupportedMediaTypeStatusException; -import static org.junit.Assert.assertEquals; +import static org.junit.Assert.*; import static org.springframework.web.reactive.function.BodyExtractors.toMono; /** @@ -336,4 +338,70 @@ public class DefaultServerRequestTests { .verify(); } + @Test + public void formData() throws Exception { + DefaultDataBufferFactory factory = new DefaultDataBufferFactory(); + DefaultDataBuffer dataBuffer = + factory.wrap(ByteBuffer.wrap("foo=bar&baz=qux".getBytes(StandardCharsets.UTF_8))); + Flux body = Flux.just(dataBuffer); + + HttpHeaders httpHeaders = new HttpHeaders(); + httpHeaders.setContentType(MediaType.APPLICATION_FORM_URLENCODED); + MockServerHttpRequest mockRequest = MockServerHttpRequest + .method(HttpMethod.GET, "http://example.com") + .headers(httpHeaders) + .body(body); + DefaultServerRequest request = new DefaultServerRequest(MockServerWebExchange.from(mockRequest), Collections.emptyList()); + + Mono> resultData = request.formData(); + StepVerifier.create(resultData) + .consumeNextWith(formData -> { + assertEquals(2, formData.size()); + assertEquals("bar", formData.getFirst("foo")); + assertEquals("qux", formData.getFirst("baz")); + }) + .verifyComplete(); + } + + @Test + public void multipartData() throws Exception { + String data = "--12345\r\n" + + "Content-Disposition: form-data; name=\"foo\"\r\n" + + "\r\n" + + "bar\r\n" + + "--12345\r\n" + + "Content-Disposition: form-data; name=\"baz\"\r\n" + + "\r\n" + + "qux\r\n" + + "--12345--\r\n"; + DefaultDataBufferFactory factory = new DefaultDataBufferFactory(); + DefaultDataBuffer dataBuffer = + factory.wrap(ByteBuffer.wrap(data.getBytes(StandardCharsets.UTF_8))); + Flux body = Flux.just(dataBuffer); + + HttpHeaders httpHeaders = new HttpHeaders(); + httpHeaders.set(HttpHeaders.CONTENT_TYPE, "multipart/form-data; boundary=12345"); + MockServerHttpRequest mockRequest = MockServerHttpRequest + .method(HttpMethod.GET, "http://example.com") + .headers(httpHeaders) + .body(body); + DefaultServerRequest request = new DefaultServerRequest(MockServerWebExchange.from(mockRequest), Collections.emptyList()); + + Mono> resultData = request.multipartData(); + StepVerifier.create(resultData) + .consumeNextWith(formData -> { + assertEquals(2, formData.size()); + + Part part = formData.getFirst("foo"); + assertTrue(part instanceof FormFieldPart); + FormFieldPart formFieldPart = (FormFieldPart) part; + assertEquals("bar", formFieldPart.value()); + + part = formData.getFirst("baz"); + assertTrue(part instanceof FormFieldPart); + formFieldPart = (FormFieldPart) part; + assertEquals("qux", formFieldPart.value()); + }) + .verifyComplete(); + } } diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/function/server/MockServerRequest.java b/spring-webflux/src/test/java/org/springframework/web/reactive/function/server/MockServerRequest.java index f97257ba888..ccdd2db1800 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/function/server/MockServerRequest.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/function/server/MockServerRequest.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -40,6 +40,7 @@ import org.springframework.http.HttpMethod; import org.springframework.http.HttpRange; import org.springframework.http.HttpRequest; import org.springframework.http.MediaType; +import org.springframework.http.codec.multipart.Part; import org.springframework.http.server.PathContainer; import org.springframework.http.server.RequestPath; import org.springframework.http.server.reactive.ServerHttpRequest; @@ -208,6 +209,20 @@ public class MockServerRequest implements ServerRequest { return Mono.justOrEmpty(this.principal); } + @Override + @SuppressWarnings("unchecked") + public Mono> formData() { + Assert.state(this.body != null, "No body"); + return (Mono>) this.body; + } + + @Override + @SuppressWarnings("unchecked") + public Mono> multipartData() { + Assert.state(this.body != null, "No body"); + return (Mono>) this.body; + } + public static Builder builder() { return new BuilderImpl(); }