From 3df902c6cc62770d5ef49f18fac8a3f6e35e7526 Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Tue, 13 Dec 2016 17:56:59 -0500 Subject: [PATCH] Add ServerHttpRequest builder Similar pattern as for ServerWebExchange with a default mutate method on ServerHttpRequest returning a Builder and eventually creating an immutable wrapper. HttpHandlerAdapterSupport uses the builder to set the contextPath. --- .../DefaultServerHttpRequestBuilder.java | 181 ++++++++++++++++++ .../reactive/HttpHandlerAdapterSupport.java | 21 +- .../server/reactive/ServerHttpRequest.java | 66 +++++++ .../HttpHandlerAdapterSupportTests.java | 82 ++++---- 4 files changed, 292 insertions(+), 58 deletions(-) create mode 100644 spring-web/src/main/java/org/springframework/http/server/reactive/DefaultServerHttpRequestBuilder.java diff --git a/spring-web/src/main/java/org/springframework/http/server/reactive/DefaultServerHttpRequestBuilder.java b/spring-web/src/main/java/org/springframework/http/server/reactive/DefaultServerHttpRequestBuilder.java new file mode 100644 index 00000000000..8cafcbf4217 --- /dev/null +++ b/spring-web/src/main/java/org/springframework/http/server/reactive/DefaultServerHttpRequestBuilder.java @@ -0,0 +1,181 @@ +/* + * 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.net.URI; + +import reactor.core.publisher.Flux; + +import org.springframework.core.io.buffer.DataBuffer; +import org.springframework.http.HttpCookie; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.util.Assert; +import org.springframework.util.MultiValueMap; + +/** + * Package private default implementation of {@link ServerHttpRequest.Builder}. + * + * @author Rossen Stoyanchev + * @since 5.0 + */ +class DefaultServerHttpRequestBuilder implements ServerHttpRequest.Builder { + + private final ServerHttpRequest delegate; + + + private HttpMethod httpMethod; + + private URI uri; + + private String contextPath; + + private MultiValueMap queryParams; + + private HttpHeaders headers; + + private MultiValueMap cookies; + + private Flux body; + + + public DefaultServerHttpRequestBuilder(ServerHttpRequest delegate) { + Assert.notNull(delegate, "ServerHttpRequest delegate is required."); + this.delegate = delegate; + } + + + @Override + public ServerHttpRequest.Builder method(HttpMethod httpMethod) { + this.httpMethod = httpMethod; + return this; + } + + @Override + public ServerHttpRequest.Builder uri(URI uri) { + this.uri = uri; + return this; + } + + @Override + public ServerHttpRequest.Builder contextPath(String contextPath) { + this.contextPath = contextPath; + return this; + } + + @Override + public ServerHttpRequest.Builder queryParams(MultiValueMap queryParams) { + this.queryParams = queryParams; + return this; + } + + @Override + public ServerHttpRequest.Builder headers(HttpHeaders headers) { + this.headers = headers; + return this; + } + + @Override + public ServerHttpRequest.Builder cookies(MultiValueMap cookies) { + this.cookies = cookies; + return this; + } + + @Override + public ServerHttpRequest.Builder body(Flux body) { + this.body = body; + return this; + } + + @Override + public ServerHttpRequest build() { + return new MutativeDecorator(this.delegate, this.httpMethod, this.uri, this.contextPath, + this.queryParams, this.headers, this.cookies, this.body); + } + + + /** + * An immutable wrapper of a request returning property overrides -- given + * to the constructor -- or original values otherwise. + */ + private static class MutativeDecorator extends ServerHttpRequestDecorator { + + private final HttpMethod httpMethod; + + private final URI uri; + + private final String contextPath; + + private final MultiValueMap queryParams; + + private final HttpHeaders headers; + + private final MultiValueMap cookies; + + private final Flux body; + + + public MutativeDecorator(ServerHttpRequest delegate, HttpMethod httpMethod, URI uri, + String contextPath, MultiValueMap queryParams, HttpHeaders headers, + MultiValueMap cookies, Flux body) { + + super(delegate); + this.httpMethod = httpMethod; + this.uri = uri; + this.contextPath = contextPath; + this.queryParams = queryParams; + this.headers = headers; + this.cookies = cookies; + this.body = body; + } + + @Override + public HttpMethod getMethod() { + return (this.httpMethod != null ? this.httpMethod : super.getMethod()); + } + + @Override + public URI getURI() { + return (this.uri != null ? this.uri : super.getURI()); + } + + @Override + public String getContextPath() { + return (this.contextPath != null ? this.contextPath : super.getContextPath()); + } + + @Override + public MultiValueMap getQueryParams() { + return (this.queryParams != null ? this.queryParams : super.getQueryParams()); + } + + @Override + public HttpHeaders getHeaders() { + return (this.headers != null ? this.headers : super.getHeaders()); + } + + @Override + public MultiValueMap getCookies() { + return (this.cookies != null ? this.cookies : super.getCookies()); + } + + @Override + public Flux getBody() { + return (this.body != null ? this.body : super.getBody()); + } + } + +} diff --git a/spring-web/src/main/java/org/springframework/http/server/reactive/HttpHandlerAdapterSupport.java b/spring-web/src/main/java/org/springframework/http/server/reactive/HttpHandlerAdapterSupport.java index f601e29ec3b..16c535cc187 100644 --- a/spring-web/src/main/java/org/springframework/http/server/reactive/HttpHandlerAdapterSupport.java +++ b/spring-web/src/main/java/org/springframework/http/server/reactive/HttpHandlerAdapterSupport.java @@ -111,9 +111,11 @@ public abstract class HttpHandlerAdapterSupport { .filter(entry -> path.startsWith(entry.getKey())) .findFirst() .map(entry -> { + // Preserve "native" contextPath from underlying request.. + String contextPath = request.getContextPath() + entry.getKey(); + ServerHttpRequest mutatedRequest = request.mutate().contextPath(contextPath).build(); HttpHandler handler = entry.getValue(); - ServerHttpRequest req = new ContextPathRequestDecorator(request, entry.getKey()); - return handler.handle(req, response); + return handler.handle(mutatedRequest, response); }) .orElseGet(() -> { response.setStatusCode(HttpStatus.NOT_FOUND); @@ -134,19 +136,4 @@ public abstract class HttpHandlerAdapterSupport { } } - private static class ContextPathRequestDecorator extends ServerHttpRequestDecorator { - - private final String contextPath; - - public ContextPathRequestDecorator(ServerHttpRequest delegate, String contextPath) { - super(delegate); - this.contextPath = delegate.getContextPath() + contextPath; - } - - @Override - public String getContextPath() { - return this.contextPath; - } - } - } diff --git a/spring-web/src/main/java/org/springframework/http/server/reactive/ServerHttpRequest.java b/spring-web/src/main/java/org/springframework/http/server/reactive/ServerHttpRequest.java index 297863ad17e..92578a2be62 100644 --- a/spring-web/src/main/java/org/springframework/http/server/reactive/ServerHttpRequest.java +++ b/spring-web/src/main/java/org/springframework/http/server/reactive/ServerHttpRequest.java @@ -16,7 +16,14 @@ package org.springframework.http.server.reactive; +import java.net.URI; + +import reactor.core.publisher.Flux; + +import org.springframework.core.io.buffer.DataBuffer; import org.springframework.http.HttpCookie; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; import org.springframework.http.HttpRequest; import org.springframework.http.ReactiveHttpInputMessage; import org.springframework.util.MultiValueMap; @@ -52,4 +59,63 @@ public interface ServerHttpRequest extends HttpRequest, ReactiveHttpInputMessage */ MultiValueMap getCookies(); + + /** + * Return a builder to mutate properties of this request. The resulting + * new request is an immutable {@link ServerHttpRequestDecorator decorator} + * around the current exchange instance returning mutated values. + */ + default ServerHttpRequest.Builder mutate() { + return new DefaultServerHttpRequestBuilder(this); + } + + + /** + * Builder for mutating properties of a {@link ServerHttpRequest}. + */ + interface Builder { + + /** + * Set the HTTP method. + */ + Builder method(HttpMethod httpMethod); + + /** + * Set the request URI. + */ + Builder uri(URI uri); + + + /** + * Set the contextPath for the request. + */ + Builder contextPath(String contextPath); + + /** + * Set the query params to return. + */ + Builder queryParams(MultiValueMap queryParams); + + /** + * Set the headers to use. + */ + Builder headers(HttpHeaders headers); + + /** + * Set the cookies to use. + */ + Builder cookies(MultiValueMap cookies); + + /** + * Set the body to return. + */ + Builder body(Flux body); + + /** + * Build an immutable wrapper that returning the mutated properties. + */ + ServerHttpRequest build(); + + } + } diff --git a/spring-web/src/test/java/org/springframework/http/server/reactive/HttpHandlerAdapterSupportTests.java b/spring-web/src/test/java/org/springframework/http/server/reactive/HttpHandlerAdapterSupportTests.java index ac41aae0e89..28127b55208 100644 --- a/spring-web/src/test/java/org/springframework/http/server/reactive/HttpHandlerAdapterSupportTests.java +++ b/spring-web/src/test/java/org/springframework/http/server/reactive/HttpHandlerAdapterSupportTests.java @@ -16,6 +16,8 @@ package org.springframework.http.server.reactive; import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; @@ -48,7 +50,7 @@ public class HttpHandlerAdapterSupportTests { private void testInvalidContextPath(String contextPath, String errorMessage) { try { - new TestHttpHandlerAdapter(new TestHttpHandler(contextPath)); + new TestHttpHandlerAdapter(Collections.singletonMap(contextPath, new TestHttpHandler())); fail(); } catch (IllegalArgumentException ex) { @@ -58,35 +60,47 @@ public class HttpHandlerAdapterSupportTests { @Test public void match() throws Exception { - TestHttpHandler handler1 = new TestHttpHandler("/path"); - TestHttpHandler handler2 = new TestHttpHandler("/another/path"); - TestHttpHandler handler3 = new TestHttpHandler("/yet/another/path"); + TestHttpHandler handler1 = new TestHttpHandler(); + TestHttpHandler handler2 = new TestHttpHandler(); + TestHttpHandler handler3 = new TestHttpHandler(); - testPath("/another/path/and/more", handler1, handler2, handler3); + Map map = new HashMap<>(); + map.put("/path", handler1); + map.put("/another/path", handler2); + map.put("/yet/another/path", handler3); - assertInvoked(handler2); + testPath("/another/path/and/more", map); + + assertInvoked(handler2, "/another/path"); assertNotInvoked(handler1, handler3); } @Test public void matchWithContextPathEqualToPath() throws Exception { - TestHttpHandler handler1 = new TestHttpHandler("/path"); - TestHttpHandler handler2 = new TestHttpHandler("/another/path"); - TestHttpHandler handler3 = new TestHttpHandler("/yet/another/path"); + TestHttpHandler handler1 = new TestHttpHandler(); + TestHttpHandler handler2 = new TestHttpHandler(); + TestHttpHandler handler3 = new TestHttpHandler(); + + Map map = new HashMap<>(); + map.put("/path", handler1); + map.put("/another/path", handler2); + map.put("/yet/another/path", handler3); - testPath("/path", handler1, handler2, handler3); + testPath("/path", map); - assertInvoked(handler1); + assertInvoked(handler1, "/path"); assertNotInvoked(handler2, handler3); } @Test public void matchWithNativeContextPath() throws Exception { MockServerHttpRequest request = new MockServerHttpRequest(HttpMethod.GET, "/yet/another/path"); - request.setContextPath("/yet"); + request.setContextPath("/yet"); // contextPath in underlying request - TestHttpHandler handler = new TestHttpHandler("/another/path"); - new TestHttpHandlerAdapter(handler).handle(request); + TestHttpHandler handler = new TestHttpHandler(); + Map map = Collections.singletonMap("/another/path", handler); + + new TestHttpHandlerAdapter(map).handle(request); assertTrue(handler.wasInvoked()); assertEquals("/yet/another/path", handler.getRequest().getContextPath()); @@ -94,24 +108,28 @@ public class HttpHandlerAdapterSupportTests { @Test public void notFound() throws Exception { - TestHttpHandler handler1 = new TestHttpHandler("/path"); - TestHttpHandler handler2 = new TestHttpHandler("/another/path"); + TestHttpHandler handler1 = new TestHttpHandler(); + TestHttpHandler handler2 = new TestHttpHandler(); + + Map map = new HashMap<>(); + map.put("/path", handler1); + map.put("/another/path", handler2); - ServerHttpResponse response = testPath("/yet/another/path", handler1, handler2); + ServerHttpResponse response = testPath("/yet/another/path", map); assertNotInvoked(handler1, handler2); assertEquals(HttpStatus.NOT_FOUND, response.getStatusCode()); } - private ServerHttpResponse testPath(String path, TestHttpHandler... handlers) { - TestHttpHandlerAdapter adapter = new TestHttpHandlerAdapter(handlers); + private ServerHttpResponse testPath(String path, Map handlerMap) { + TestHttpHandlerAdapter adapter = new TestHttpHandlerAdapter(handlerMap); return adapter.handle(path); } - private void assertInvoked(TestHttpHandler handler) { + private void assertInvoked(TestHttpHandler handler, String contextPath) { assertTrue(handler.wasInvoked()); - assertEquals(handler.getContextPath(), handler.getRequest().getContextPath()); + assertEquals(contextPath, handler.getRequest().getContextPath()); } private void assertNotInvoked(TestHttpHandler... handlers) { @@ -123,15 +141,8 @@ public class HttpHandlerAdapterSupportTests { private static class TestHttpHandlerAdapter extends HttpHandlerAdapterSupport { - public TestHttpHandlerAdapter(TestHttpHandler... handlers) { - super(initHandlerMap(handlers)); - } - - - private static Map initHandlerMap(TestHttpHandler... testHandlers) { - Map result = new LinkedHashMap<>(); - Arrays.stream(testHandlers).forEachOrdered(h -> result.put(h.getContextPath(), h)); - return result; + public TestHttpHandlerAdapter(Map handlerMap) { + super(handlerMap); } public ServerHttpResponse handle(String path) { @@ -149,20 +160,9 @@ public class HttpHandlerAdapterSupportTests { @SuppressWarnings("WeakerAccess") private static class TestHttpHandler implements HttpHandler { - private final String contextPath; - private ServerHttpRequest request; - public TestHttpHandler(String contextPath) { - this.contextPath = contextPath; - } - - - public String getContextPath() { - return this.contextPath; - } - public boolean wasInvoked() { return this.request != null; }