diff --git a/spring-web-reactive/src/main/java/org/springframework/http/server/reactive/AbstractServerHttpRequest.java b/spring-web-reactive/src/main/java/org/springframework/http/server/reactive/AbstractServerHttpRequest.java index f9301eab6d..b88a3e3b7c 100644 --- a/spring-web-reactive/src/main/java/org/springframework/http/server/reactive/AbstractServerHttpRequest.java +++ b/spring-web-reactive/src/main/java/org/springframework/http/server/reactive/AbstractServerHttpRequest.java @@ -17,14 +17,15 @@ package org.springframework.http.server.reactive; import java.net.URI; import java.net.URISyntaxException; -import java.util.List; -import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import org.springframework.http.HttpCookie; import org.springframework.http.HttpHeaders; -import org.springframework.util.LinkedCaseInsensitiveMap; +import org.springframework.util.CollectionUtils; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; +import org.springframework.util.StringUtils; /** * Common base class for {@link ServerHttpRequest} implementations. @@ -33,8 +34,13 @@ import org.springframework.util.MultiValueMap; */ public abstract class AbstractServerHttpRequest implements ServerHttpRequest { + private static final Pattern QUERY_PATTERN = Pattern.compile("([^&=]+)(=?)([^&]+)?"); + + private URI uri; + private MultiValueMap queryParams; + private HttpHeaders headers; private MultiValueMap cookies; @@ -60,6 +66,30 @@ public abstract class AbstractServerHttpRequest implements ServerHttpRequest { */ protected abstract URI initUri() throws URISyntaxException; + @Override + public MultiValueMap getQueryParams() { + if (this.queryParams == null) { + this.queryParams = CollectionUtils.unmodifiableMultiValueMap(initQueryParams()); + } + return this.queryParams; + } + + protected MultiValueMap initQueryParams() { + MultiValueMap queryParams = new LinkedMultiValueMap<>(); + String query = getURI().getRawQuery(); + if (query != null) { + Matcher matcher = QUERY_PATTERN.matcher(query); + while (matcher.find()) { + String name = matcher.group(1); + String eq = matcher.group(2); + String value = matcher.group(3); + value = (value != null ? value : (StringUtils.hasLength(eq) ? "" : null)); + queryParams.add(name, value); + } + } + return queryParams; + } + @Override public HttpHeaders getHeaders() { if (this.headers == null) { @@ -79,7 +109,7 @@ public abstract class AbstractServerHttpRequest implements ServerHttpRequest { @Override public MultiValueMap getCookies() { if (this.cookies == null) { - this.cookies = new LinkedMultiValueMap(); + this.cookies = new LinkedMultiValueMap<>(); initCookies(this.cookies); } return this.cookies; diff --git a/spring-web-reactive/src/main/java/org/springframework/http/server/reactive/ServerHttpRequest.java b/spring-web-reactive/src/main/java/org/springframework/http/server/reactive/ServerHttpRequest.java index 695e944b0a..377721d566 100644 --- a/spring-web-reactive/src/main/java/org/springframework/http/server/reactive/ServerHttpRequest.java +++ b/spring-web-reactive/src/main/java/org/springframework/http/server/reactive/ServerHttpRequest.java @@ -16,9 +16,6 @@ package org.springframework.http.server.reactive; -import java.util.List; -import java.util.Map; - import org.springframework.http.HttpCookie; import org.springframework.http.HttpRequest; import org.springframework.http.ReactiveHttpInputMessage; @@ -31,6 +28,11 @@ import org.springframework.util.MultiValueMap; */ public interface ServerHttpRequest extends HttpRequest, ReactiveHttpInputMessage { + /** + * Return a read-only map with parsed and decoded query parameter values. + */ + MultiValueMap getQueryParams(); + /** * Return a read-only map of cookies sent by the client. */ diff --git a/spring-web-reactive/src/test/java/org/springframework/http/server/reactive/MockServerHttpRequest.java b/spring-web-reactive/src/test/java/org/springframework/http/server/reactive/MockServerHttpRequest.java index 7678536a38..3f7864144b 100644 --- a/spring-web-reactive/src/test/java/org/springframework/http/server/reactive/MockServerHttpRequest.java +++ b/spring-web-reactive/src/test/java/org/springframework/http/server/reactive/MockServerHttpRequest.java @@ -36,6 +36,8 @@ public class MockServerHttpRequest implements ServerHttpRequest { private URI uri; + private MultiValueMap queryParams = new LinkedMultiValueMap<>(); + private HttpHeaders headers = new HttpHeaders(); private MultiValueMap cookies = new LinkedMultiValueMap<>(); @@ -79,6 +81,11 @@ public class MockServerHttpRequest implements ServerHttpRequest { return this.headers; } + @Override + public MultiValueMap getQueryParams() { + return this.queryParams; + } + @Override public MultiValueMap getCookies() { return this.cookies; diff --git a/spring-web-reactive/src/test/java/org/springframework/http/server/reactive/ServerHttpRequestTests.java b/spring-web-reactive/src/test/java/org/springframework/http/server/reactive/ServerHttpRequestTests.java new file mode 100644 index 0000000000..6de90e0cff --- /dev/null +++ b/spring-web-reactive/src/test/java/org/springframework/http/server/reactive/ServerHttpRequestTests.java @@ -0,0 +1,79 @@ +/* + * Copyright 2002-2016 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.http.server.reactive; + +import java.util.Arrays; +import java.util.Collections; +import javax.servlet.http.HttpServletRequest; + +import org.junit.Test; +import reactor.core.publisher.Flux; + +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.util.MultiValueMap; + +import static org.junit.Assert.assertEquals; + +/** + * Unit tests for {@link AbstractServerHttpRequest}. + * + * @author Rossen Stoyanchev + */ +public class ServerHttpRequestTests { + + + @Test + public void queryParamsNone() throws Exception { + MultiValueMap params = createHttpRequest("/path").getQueryParams(); + assertEquals(0, params.size()); + } + + @Test + public void queryParams() throws Exception { + MultiValueMap params = createHttpRequest("/path?a=A&b=B").getQueryParams(); + assertEquals(2, params.size()); + assertEquals(Collections.singletonList("A"), params.get("a")); + assertEquals(Collections.singletonList("B"), params.get("b")); + } + + @Test + public void queryParamsWithMulitpleValues() throws Exception { + MultiValueMap params = createHttpRequest("/path?a=1&a=2").getQueryParams(); + assertEquals(1, params.size()); + assertEquals(Arrays.asList("1", "2"), params.get("a")); + } + + @Test + public void queryParamsWithEmptyValue() throws Exception { + MultiValueMap params = createHttpRequest("/path?a=").getQueryParams(); + assertEquals(1, params.size()); + assertEquals(Collections.singletonList(""), params.get("a")); + } + + @Test + public void queryParamsWithNoValue() throws Exception { + MultiValueMap params = createHttpRequest("/path?a").getQueryParams(); + assertEquals(1, params.size()); + assertEquals(Collections.singletonList(null), params.get("a")); + } + + + private ServerHttpRequest createHttpRequest(String path) { + HttpServletRequest servletRequest = new MockHttpServletRequest("GET", path); + return new ServletServerHttpRequest(servletRequest, Flux.empty()); + } + +}