Expose RequestPath in ServerHttpRequest

The new structured getPath() method replaces the existing
getContextPath() + getPathWithinApplication().

Issue: SPR-15648
This commit is contained in:
Rossen Stoyanchev
2017-06-11 21:57:54 -04:00
parent 2d17411ec4
commit 38a12ed4ba
26 changed files with 127 additions and 115 deletions

View File

@@ -41,6 +41,8 @@ public abstract class AbstractServerHttpRequest implements ServerHttpRequest {
private final URI uri;
private final RequestPath path;
private final HttpHeaders headers;
private MultiValueMap<String, String> queryParams;
@@ -51,10 +53,12 @@ public abstract class AbstractServerHttpRequest implements ServerHttpRequest {
/**
* Constructor with the URI and headers for the request.
* @param uri the URI for the request
* @param contextPath the context path for the request
* @param headers the headers for the request
*/
public AbstractServerHttpRequest(URI uri, HttpHeaders headers) {
public AbstractServerHttpRequest(URI uri, String contextPath, HttpHeaders headers) {
this.uri = uri;
this.path = new DefaultRequestPath(uri, contextPath, StandardCharsets.UTF_8);
this.headers = HttpHeaders.readOnlyHttpHeaders(headers);
}
@@ -64,6 +68,11 @@ public abstract class AbstractServerHttpRequest implements ServerHttpRequest {
return this.uri;
}
@Override
public RequestPath getPath() {
return this.path;
}
@Override
public HttpHeaders getHeaders() {
return this.headers;

View File

@@ -16,7 +16,7 @@ import org.springframework.util.Assert;
* <p>This is intended as a coarse-grained mechanism for delegating requests to
* one of several applications -- each represented by an {@code HttpHandler}, with
* the application "context path" (the prefix-based mapping) exposed via
* {@link ServerHttpRequest#getContextPath()}.
* {@link ServerHttpRequest#getPath()}.
*
* @author Rossen Stoyanchev
* @since 5.0
@@ -49,12 +49,12 @@ public class ContextPathCompositeHandler implements HttpHandler {
@Override
public Mono<Void> handle(ServerHttpRequest request, ServerHttpResponse response) {
// Remove underlying context path first (e.g. Servlet container)
String path = request.getPathWithinApplication();
String path = request.getPath().pathWithinApplication().value();
return this.handlerMap.entrySet().stream()
.filter(entry -> path.startsWith(entry.getKey()))
.findFirst()
.map(entry -> {
String contextPath = request.getContextPath() + entry.getKey();
String contextPath = request.getPath().contextPath().value() + entry.getKey();
ServerHttpRequest newRequest = request.mutate().contextPath(contextPath).build();
return entry.getValue().handle(newRequest, response);
})

View File

@@ -58,6 +58,11 @@ class DefaultRequestPath implements RequestPath {
this.pathWithinApplication = initPathWithinApplication(this.fullPath, this.contextPath);
}
DefaultRequestPath(RequestPath requestPath, String contextPath, Charset charset) {
this.fullPath = new DefaultPathSegmentContainer(requestPath.value(), requestPath.pathSegments());
this.contextPath = initContextPath(this.fullPath, contextPath);
this.pathWithinApplication = initPathWithinApplication(this.fullPath, this.contextPath);
}
private static PathSegmentContainer parsePath(String path, Charset charset) {
path = StringUtils.hasText(path) ? path : "";

View File

@@ -18,6 +18,7 @@ package org.springframework.http.server.reactive;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
@@ -79,18 +80,51 @@ class DefaultServerHttpRequestBuilder implements ServerHttpRequest.Builder {
@Override
public ServerHttpRequest build() {
URI uri = null;
if (this.path != null) {
uri = this.delegate.getURI();
try {
uri = new URI(uri.getScheme(), uri.getUserInfo(), uri.getHost(), uri.getPort(),
this.path, uri.getQuery(), uri.getFragment());
}
catch (URISyntaxException ex) {
throw new IllegalStateException("Invalid URI path: \"" + this.path + "\"");
}
URI uriToUse = getUriToUse();
RequestPath path = getRequestPathToUse(uriToUse);
HttpHeaders headers = getHeadersToUse();
return new MutativeDecorator(this.delegate, this.httpMethod, uriToUse, path, headers);
}
@Nullable
private URI getUriToUse() {
if (this.path == null) {
return null;
}
URI uri = this.delegate.getURI();
try {
return new URI(uri.getScheme(), uri.getUserInfo(), uri.getHost(), uri.getPort(),
this.path, uri.getQuery(), uri.getFragment());
}
catch (URISyntaxException ex) {
throw new IllegalStateException("Invalid URI path: \"" + this.path + "\"");
}
}
@Nullable
private RequestPath getRequestPathToUse(@Nullable URI uriToUse) {
if (uriToUse == null && this.contextPath == null) {
return null;
}
else if (uriToUse == null) {
return new DefaultRequestPath(this.delegate.getPath(), this.contextPath, StandardCharsets.UTF_8);
}
else {
return new DefaultRequestPath(uriToUse, this.contextPath, StandardCharsets.UTF_8);
}
}
@Nullable
private HttpHeaders getHeadersToUse() {
if (this.httpHeaders != null) {
HttpHeaders headers = new HttpHeaders();
headers.putAll(this.delegate.getHeaders());
headers.putAll(this.httpHeaders);
return headers;
}
else {
return null;
}
return new MutativeDecorator(this.delegate, this.httpMethod, uri, this.contextPath, this.httpHeaders);
}
@@ -104,25 +138,20 @@ class DefaultServerHttpRequestBuilder implements ServerHttpRequest.Builder {
private final URI uri;
private final String contextPath;
private final RequestPath requestPath;
private final HttpHeaders httpHeaders;
public MutativeDecorator(ServerHttpRequest delegate, HttpMethod httpMethod,
@Nullable URI uri, String contextPath, @Nullable HttpHeaders httpHeaders) {
public MutativeDecorator(ServerHttpRequest delegate, HttpMethod method,
@Nullable URI uri, @Nullable RequestPath requestPath,
@Nullable HttpHeaders httpHeaders) {
super(delegate);
this.httpMethod = httpMethod;
this.httpMethod = method;
this.uri = uri;
this.contextPath = contextPath;
if (httpHeaders != null) {
this.httpHeaders = new HttpHeaders();
this.httpHeaders.putAll(super.getHeaders());
this.httpHeaders.putAll(httpHeaders);
}
else {
this.httpHeaders = null;
}
this.requestPath = requestPath;
this.httpHeaders = httpHeaders;
}
@Override
@@ -136,8 +165,8 @@ class DefaultServerHttpRequestBuilder implements ServerHttpRequest.Builder {
}
@Override
public String getContextPath() {
return (this.contextPath != null ? this.contextPath : super.getContextPath());
public RequestPath getPath() {
return (this.requestPath != null ? this.requestPath : super.getPath());
}
@Override

View File

@@ -49,7 +49,7 @@ public class ReactorServerHttpRequest extends AbstractServerHttpRequest {
public ReactorServerHttpRequest(HttpServerRequest request, NettyDataBufferFactory bufferFactory)
throws URISyntaxException {
super(initUri(request), initHeaders(request));
super(initUri(request), "", initHeaders(request));
Assert.notNull(bufferFactory, "'bufferFactory' must not be null");
this.request = request;
this.bufferFactory = bufferFactory;

View File

@@ -24,7 +24,12 @@ package org.springframework.http.server.reactive;
public interface RequestPath extends PathSegmentContainer {
/**
* The contextPath portion of the request if any.
* Returns the portion of the URL path that represents the application.
* The context path is always at the beginning of the path and starts but
* does not end with "/". It is shared for URLs of the same application.
* <p>The context path may come from the underlying runtime API such as
* when deploying as a WAR to a Servlet container or it may also be assigned
* through the use of {@link ContextPathCompositeHandler} or both.
*/
PathSegmentContainer contextPath();

View File

@@ -24,7 +24,6 @@ import org.springframework.http.HttpRequest;
import org.springframework.http.ReactiveHttpInputMessage;
import org.springframework.lang.Nullable;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
/**
* Represents a reactive server-side HTTP request
@@ -36,35 +35,11 @@ import org.springframework.util.StringUtils;
public interface ServerHttpRequest extends HttpRequest, ReactiveHttpInputMessage {
/**
* Returns the portion of the URL path that represents the application.
* The context path is always at the beginning of the path and starts but
* does not end with "/". It is shared for URLs of the same application.
* <p>The context path may come from the underlying runtime API such as
* when deploying as a WAR to a Servlet container or it may also be assigned
* through the use of {@link ContextPathCompositeHandler} or both.
* <p>The context path is not decoded.
* @return the context path (not decoded) or an empty string
* Returns a structured representation of the request path including the
* context path + path within application portions, path segments with
* encoded and decoded values, and path parameters.
*/
default String getContextPath() {
return "";
}
/**
* Returns the portion of the URL path after the {@link #getContextPath()
* contextPath}. The returned path is not decoded.
* @return the path under the contextPath
*/
default String getPathWithinApplication() {
String path = getURI().getRawPath();
String contextPath = getContextPath();
if (StringUtils.hasText(contextPath)) {
int length = contextPath.length();
return (path.length() > length ? path.substring(length) : "");
}
else {
return path;
}
}
RequestPath getPath();
/**
* Return a read-only map with parsed and decoded query parameter values.
@@ -104,12 +79,13 @@ public interface ServerHttpRequest extends HttpRequest, ReactiveHttpInputMessage
Builder method(HttpMethod httpMethod);
/**
* Set the request URI to return.
* Set the path to use instead of the {@code "rawPath"} of
* {@link ServerHttpRequest#getURI()}.
*/
Builder path(String path);
/**
* Set the contextPath to return.
* Set the contextPath to use.
*/
Builder contextPath(String contextPath);

View File

@@ -68,6 +68,11 @@ public class ServerHttpRequestDecorator implements ServerHttpRequest {
return getDelegate().getURI();
}
@Override
public RequestPath getPath() {
return getDelegate().getPath();
}
@Override
public MultiValueMap<String, String> getQueryParams() {
return getDelegate().getQueryParams();

View File

@@ -72,7 +72,7 @@ public class ServletServerHttpRequest extends AbstractServerHttpRequest {
public ServletServerHttpRequest(HttpServletRequest request, AsyncContext asyncContext,
DataBufferFactory bufferFactory, int bufferSize) throws IOException {
super(initUri(request), initHeaders(request));
super(initUri(request), request.getContextPath(), initHeaders(request));
Assert.notNull(bufferFactory, "'bufferFactory' must not be null");
Assert.isTrue(bufferSize > 0, "'bufferSize' must be higher than 0");
@@ -154,11 +154,6 @@ public class ServletServerHttpRequest extends AbstractServerHttpRequest {
return getServletRequest().getMethod();
}
@Override
public String getContextPath() {
return getServletRequest().getContextPath();
}
@Override
protected MultiValueMap<String, HttpCookie> initCookies() {
MultiValueMap<String, HttpCookie> httpCookies = new LinkedMultiValueMap<>();

View File

@@ -53,7 +53,7 @@ public class UndertowServerHttpRequest extends AbstractServerHttpRequest {
public UndertowServerHttpRequest(HttpServerExchange exchange, DataBufferFactory bufferFactory) {
super(initUri(exchange), initHeaders(exchange));
super(initUri(exchange), "", initHeaders(exchange));
this.exchange = exchange;
this.body = new RequestBodyPublisher(exchange, bufferFactory);
this.body.registerListeners(exchange);

View File

@@ -80,7 +80,7 @@ public class UrlBasedCorsConfigurationSource implements CorsConfigurationSource
@Override
public CorsConfiguration getCorsConfiguration(ServerWebExchange exchange) {
String lookupPath = exchange.getRequest().getPathWithinApplication();
String lookupPath = exchange.getRequest().getPath().pathWithinApplication().value();
for (Map.Entry<String, CorsConfiguration> entry : this.corsConfigurations.entrySet()) {
if (this.pathMatcher.match(entry.getKey(), lookupPath)) {
return entry.getValue();