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.
This commit is contained in:
@@ -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<String, String> queryParams;
|
||||||
|
|
||||||
|
private HttpHeaders headers;
|
||||||
|
|
||||||
|
private MultiValueMap<String, HttpCookie> cookies;
|
||||||
|
|
||||||
|
private Flux<DataBuffer> 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<String, String> queryParams) {
|
||||||
|
this.queryParams = queryParams;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ServerHttpRequest.Builder headers(HttpHeaders headers) {
|
||||||
|
this.headers = headers;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ServerHttpRequest.Builder cookies(MultiValueMap<String, HttpCookie> cookies) {
|
||||||
|
this.cookies = cookies;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ServerHttpRequest.Builder body(Flux<DataBuffer> 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<String, String> queryParams;
|
||||||
|
|
||||||
|
private final HttpHeaders headers;
|
||||||
|
|
||||||
|
private final MultiValueMap<String, HttpCookie> cookies;
|
||||||
|
|
||||||
|
private final Flux<DataBuffer> body;
|
||||||
|
|
||||||
|
|
||||||
|
public MutativeDecorator(ServerHttpRequest delegate, HttpMethod httpMethod, URI uri,
|
||||||
|
String contextPath, MultiValueMap<String, String> queryParams, HttpHeaders headers,
|
||||||
|
MultiValueMap<String, HttpCookie> cookies, Flux<DataBuffer> 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<String, String> getQueryParams() {
|
||||||
|
return (this.queryParams != null ? this.queryParams : super.getQueryParams());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public HttpHeaders getHeaders() {
|
||||||
|
return (this.headers != null ? this.headers : super.getHeaders());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MultiValueMap<String, HttpCookie> getCookies() {
|
||||||
|
return (this.cookies != null ? this.cookies : super.getCookies());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Flux<DataBuffer> getBody() {
|
||||||
|
return (this.body != null ? this.body : super.getBody());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -111,9 +111,11 @@ public abstract class HttpHandlerAdapterSupport {
|
|||||||
.filter(entry -> path.startsWith(entry.getKey()))
|
.filter(entry -> path.startsWith(entry.getKey()))
|
||||||
.findFirst()
|
.findFirst()
|
||||||
.map(entry -> {
|
.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();
|
HttpHandler handler = entry.getValue();
|
||||||
ServerHttpRequest req = new ContextPathRequestDecorator(request, entry.getKey());
|
return handler.handle(mutatedRequest, response);
|
||||||
return handler.handle(req, response);
|
|
||||||
})
|
})
|
||||||
.orElseGet(() -> {
|
.orElseGet(() -> {
|
||||||
response.setStatusCode(HttpStatus.NOT_FOUND);
|
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,14 @@
|
|||||||
|
|
||||||
package org.springframework.http.server.reactive;
|
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.HttpCookie;
|
||||||
|
import org.springframework.http.HttpHeaders;
|
||||||
|
import org.springframework.http.HttpMethod;
|
||||||
import org.springframework.http.HttpRequest;
|
import org.springframework.http.HttpRequest;
|
||||||
import org.springframework.http.ReactiveHttpInputMessage;
|
import org.springframework.http.ReactiveHttpInputMessage;
|
||||||
import org.springframework.util.MultiValueMap;
|
import org.springframework.util.MultiValueMap;
|
||||||
@@ -52,4 +59,63 @@ public interface ServerHttpRequest extends HttpRequest, ReactiveHttpInputMessage
|
|||||||
*/
|
*/
|
||||||
MultiValueMap<String, HttpCookie> getCookies();
|
MultiValueMap<String, HttpCookie> 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<String, String> queryParams);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the headers to use.
|
||||||
|
*/
|
||||||
|
Builder headers(HttpHeaders headers);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the cookies to use.
|
||||||
|
*/
|
||||||
|
Builder cookies(MultiValueMap<String, HttpCookie> cookies);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the body to return.
|
||||||
|
*/
|
||||||
|
Builder body(Flux<DataBuffer> body);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build an immutable wrapper that returning the mutated properties.
|
||||||
|
*/
|
||||||
|
ServerHttpRequest build();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,8 @@
|
|||||||
package org.springframework.http.server.reactive;
|
package org.springframework.http.server.reactive;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
@@ -48,7 +50,7 @@ public class HttpHandlerAdapterSupportTests {
|
|||||||
|
|
||||||
private void testInvalidContextPath(String contextPath, String errorMessage) {
|
private void testInvalidContextPath(String contextPath, String errorMessage) {
|
||||||
try {
|
try {
|
||||||
new TestHttpHandlerAdapter(new TestHttpHandler(contextPath));
|
new TestHttpHandlerAdapter(Collections.singletonMap(contextPath, new TestHttpHandler()));
|
||||||
fail();
|
fail();
|
||||||
}
|
}
|
||||||
catch (IllegalArgumentException ex) {
|
catch (IllegalArgumentException ex) {
|
||||||
@@ -58,35 +60,47 @@ public class HttpHandlerAdapterSupportTests {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void match() throws Exception {
|
public void match() throws Exception {
|
||||||
TestHttpHandler handler1 = new TestHttpHandler("/path");
|
TestHttpHandler handler1 = new TestHttpHandler();
|
||||||
TestHttpHandler handler2 = new TestHttpHandler("/another/path");
|
TestHttpHandler handler2 = new TestHttpHandler();
|
||||||
TestHttpHandler handler3 = new TestHttpHandler("/yet/another/path");
|
TestHttpHandler handler3 = new TestHttpHandler();
|
||||||
|
|
||||||
testPath("/another/path/and/more", handler1, handler2, handler3);
|
Map<String, HttpHandler> 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);
|
assertNotInvoked(handler1, handler3);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void matchWithContextPathEqualToPath() throws Exception {
|
public void matchWithContextPathEqualToPath() throws Exception {
|
||||||
TestHttpHandler handler1 = new TestHttpHandler("/path");
|
TestHttpHandler handler1 = new TestHttpHandler();
|
||||||
TestHttpHandler handler2 = new TestHttpHandler("/another/path");
|
TestHttpHandler handler2 = new TestHttpHandler();
|
||||||
TestHttpHandler handler3 = new TestHttpHandler("/yet/another/path");
|
TestHttpHandler handler3 = new TestHttpHandler();
|
||||||
|
|
||||||
testPath("/path", handler1, handler2, handler3);
|
Map<String, HttpHandler> map = new HashMap<>();
|
||||||
|
map.put("/path", handler1);
|
||||||
|
map.put("/another/path", handler2);
|
||||||
|
map.put("/yet/another/path", handler3);
|
||||||
|
|
||||||
assertInvoked(handler1);
|
testPath("/path", map);
|
||||||
|
|
||||||
|
assertInvoked(handler1, "/path");
|
||||||
assertNotInvoked(handler2, handler3);
|
assertNotInvoked(handler2, handler3);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void matchWithNativeContextPath() throws Exception {
|
public void matchWithNativeContextPath() throws Exception {
|
||||||
MockServerHttpRequest request = new MockServerHttpRequest(HttpMethod.GET, "/yet/another/path");
|
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");
|
TestHttpHandler handler = new TestHttpHandler();
|
||||||
new TestHttpHandlerAdapter(handler).handle(request);
|
Map<String, HttpHandler> map = Collections.singletonMap("/another/path", handler);
|
||||||
|
|
||||||
|
new TestHttpHandlerAdapter(map).handle(request);
|
||||||
|
|
||||||
assertTrue(handler.wasInvoked());
|
assertTrue(handler.wasInvoked());
|
||||||
assertEquals("/yet/another/path", handler.getRequest().getContextPath());
|
assertEquals("/yet/another/path", handler.getRequest().getContextPath());
|
||||||
@@ -94,24 +108,28 @@ public class HttpHandlerAdapterSupportTests {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void notFound() throws Exception {
|
public void notFound() throws Exception {
|
||||||
TestHttpHandler handler1 = new TestHttpHandler("/path");
|
TestHttpHandler handler1 = new TestHttpHandler();
|
||||||
TestHttpHandler handler2 = new TestHttpHandler("/another/path");
|
TestHttpHandler handler2 = new TestHttpHandler();
|
||||||
|
|
||||||
ServerHttpResponse response = testPath("/yet/another/path", handler1, handler2);
|
Map<String, HttpHandler> map = new HashMap<>();
|
||||||
|
map.put("/path", handler1);
|
||||||
|
map.put("/another/path", handler2);
|
||||||
|
|
||||||
|
ServerHttpResponse response = testPath("/yet/another/path", map);
|
||||||
|
|
||||||
assertNotInvoked(handler1, handler2);
|
assertNotInvoked(handler1, handler2);
|
||||||
assertEquals(HttpStatus.NOT_FOUND, response.getStatusCode());
|
assertEquals(HttpStatus.NOT_FOUND, response.getStatusCode());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private ServerHttpResponse testPath(String path, TestHttpHandler... handlers) {
|
private ServerHttpResponse testPath(String path, Map<String, HttpHandler> handlerMap) {
|
||||||
TestHttpHandlerAdapter adapter = new TestHttpHandlerAdapter(handlers);
|
TestHttpHandlerAdapter adapter = new TestHttpHandlerAdapter(handlerMap);
|
||||||
return adapter.handle(path);
|
return adapter.handle(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void assertInvoked(TestHttpHandler handler) {
|
private void assertInvoked(TestHttpHandler handler, String contextPath) {
|
||||||
assertTrue(handler.wasInvoked());
|
assertTrue(handler.wasInvoked());
|
||||||
assertEquals(handler.getContextPath(), handler.getRequest().getContextPath());
|
assertEquals(contextPath, handler.getRequest().getContextPath());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void assertNotInvoked(TestHttpHandler... handlers) {
|
private void assertNotInvoked(TestHttpHandler... handlers) {
|
||||||
@@ -123,15 +141,8 @@ public class HttpHandlerAdapterSupportTests {
|
|||||||
private static class TestHttpHandlerAdapter extends HttpHandlerAdapterSupport {
|
private static class TestHttpHandlerAdapter extends HttpHandlerAdapterSupport {
|
||||||
|
|
||||||
|
|
||||||
public TestHttpHandlerAdapter(TestHttpHandler... handlers) {
|
public TestHttpHandlerAdapter(Map<String, HttpHandler> handlerMap) {
|
||||||
super(initHandlerMap(handlers));
|
super(handlerMap);
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private static Map<String, HttpHandler> initHandlerMap(TestHttpHandler... testHandlers) {
|
|
||||||
Map<String, HttpHandler> result = new LinkedHashMap<>();
|
|
||||||
Arrays.stream(testHandlers).forEachOrdered(h -> result.put(h.getContextPath(), h));
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public ServerHttpResponse handle(String path) {
|
public ServerHttpResponse handle(String path) {
|
||||||
@@ -149,20 +160,9 @@ public class HttpHandlerAdapterSupportTests {
|
|||||||
@SuppressWarnings("WeakerAccess")
|
@SuppressWarnings("WeakerAccess")
|
||||||
private static class TestHttpHandler implements HttpHandler {
|
private static class TestHttpHandler implements HttpHandler {
|
||||||
|
|
||||||
private final String contextPath;
|
|
||||||
|
|
||||||
private ServerHttpRequest request;
|
private ServerHttpRequest request;
|
||||||
|
|
||||||
|
|
||||||
public TestHttpHandler(String contextPath) {
|
|
||||||
this.contextPath = contextPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public String getContextPath() {
|
|
||||||
return this.contextPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean wasInvoked() {
|
public boolean wasInvoked() {
|
||||||
return this.request != null;
|
return this.request != null;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user