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

@ -56,8 +56,6 @@ public class MockServerHttpRequest extends AbstractServerHttpRequest {
private final HttpMethod httpMethod; private final HttpMethod httpMethod;
private final String contextPath;
private final MultiValueMap<String, HttpCookie> cookies; private final MultiValueMap<String, HttpCookie> cookies;
private final InetSocketAddress remoteAddress; private final InetSocketAddress remoteAddress;
@ -70,9 +68,8 @@ public class MockServerHttpRequest extends AbstractServerHttpRequest {
InetSocketAddress remoteAddress, InetSocketAddress remoteAddress,
Publisher<? extends DataBuffer> body) { Publisher<? extends DataBuffer> body) {
super(uri, headers); super(uri, contextPath, headers);
this.httpMethod = httpMethod; this.httpMethod = httpMethod;
this.contextPath = contextPath;
this.cookies = cookies; this.cookies = cookies;
this.remoteAddress = remoteAddress; this.remoteAddress = remoteAddress;
this.body = Flux.from(body); this.body = Flux.from(body);
@ -89,11 +86,6 @@ public class MockServerHttpRequest extends AbstractServerHttpRequest {
return this.httpMethod.name(); return this.httpMethod.name();
} }
@Override
public String getContextPath() {
return this.contextPath;
}
@Override @Override
public InetSocketAddress getRemoteAddress() { public InetSocketAddress getRemoteAddress() {
return this.remoteAddress; return this.remoteAddress;

View File

@ -41,6 +41,8 @@ public abstract class AbstractServerHttpRequest implements ServerHttpRequest {
private final URI uri; private final URI uri;
private final RequestPath path;
private final HttpHeaders headers; private final HttpHeaders headers;
private MultiValueMap<String, String> queryParams; private MultiValueMap<String, String> queryParams;
@ -51,10 +53,12 @@ public abstract class AbstractServerHttpRequest implements ServerHttpRequest {
/** /**
* Constructor with the URI and headers for the request. * Constructor with the URI and headers for the request.
* @param uri the URI 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 * @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.uri = uri;
this.path = new DefaultRequestPath(uri, contextPath, StandardCharsets.UTF_8);
this.headers = HttpHeaders.readOnlyHttpHeaders(headers); this.headers = HttpHeaders.readOnlyHttpHeaders(headers);
} }
@ -64,6 +68,11 @@ public abstract class AbstractServerHttpRequest implements ServerHttpRequest {
return this.uri; return this.uri;
} }
@Override
public RequestPath getPath() {
return this.path;
}
@Override @Override
public HttpHeaders getHeaders() { public HttpHeaders getHeaders() {
return this.headers; 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 * <p>This is intended as a coarse-grained mechanism for delegating requests to
* one of several applications -- each represented by an {@code HttpHandler}, with * one of several applications -- each represented by an {@code HttpHandler}, with
* the application "context path" (the prefix-based mapping) exposed via * the application "context path" (the prefix-based mapping) exposed via
* {@link ServerHttpRequest#getContextPath()}. * {@link ServerHttpRequest#getPath()}.
* *
* @author Rossen Stoyanchev * @author Rossen Stoyanchev
* @since 5.0 * @since 5.0
@ -49,12 +49,12 @@ public class ContextPathCompositeHandler implements HttpHandler {
@Override @Override
public Mono<Void> handle(ServerHttpRequest request, ServerHttpResponse response) { public Mono<Void> handle(ServerHttpRequest request, ServerHttpResponse response) {
// Remove underlying context path first (e.g. Servlet container) // Remove underlying context path first (e.g. Servlet container)
String path = request.getPathWithinApplication(); String path = request.getPath().pathWithinApplication().value();
return this.handlerMap.entrySet().stream() return this.handlerMap.entrySet().stream()
.filter(entry -> path.startsWith(entry.getKey())) .filter(entry -> path.startsWith(entry.getKey()))
.findFirst() .findFirst()
.map(entry -> { .map(entry -> {
String contextPath = request.getContextPath() + entry.getKey(); String contextPath = request.getPath().contextPath().value() + entry.getKey();
ServerHttpRequest newRequest = request.mutate().contextPath(contextPath).build(); ServerHttpRequest newRequest = request.mutate().contextPath(contextPath).build();
return entry.getValue().handle(newRequest, response); return entry.getValue().handle(newRequest, response);
}) })

View File

@ -58,6 +58,11 @@ class DefaultRequestPath implements RequestPath {
this.pathWithinApplication = initPathWithinApplication(this.fullPath, this.contextPath); 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) { private static PathSegmentContainer parsePath(String path, Charset charset) {
path = StringUtils.hasText(path) ? path : ""; path = StringUtils.hasText(path) ? path : "";

View File

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

View File

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

View File

@ -24,7 +24,12 @@ package org.springframework.http.server.reactive;
public interface RequestPath extends PathSegmentContainer { 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(); PathSegmentContainer contextPath();

View File

@ -24,7 +24,6 @@ import org.springframework.http.HttpRequest;
import org.springframework.http.ReactiveHttpInputMessage; import org.springframework.http.ReactiveHttpInputMessage;
import org.springframework.lang.Nullable; import org.springframework.lang.Nullable;
import org.springframework.util.MultiValueMap; import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
/** /**
* Represents a reactive server-side HTTP request * Represents a reactive server-side HTTP request
@ -36,35 +35,11 @@ import org.springframework.util.StringUtils;
public interface ServerHttpRequest extends HttpRequest, ReactiveHttpInputMessage { public interface ServerHttpRequest extends HttpRequest, ReactiveHttpInputMessage {
/** /**
* Returns the portion of the URL path that represents the application. * Returns a structured representation of the request path including the
* The context path is always at the beginning of the path and starts but * context path + path within application portions, path segments with
* does not end with "/". It is shared for URLs of the same application. * encoded and decoded values, and path parameters.
* <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
*/ */
default String getContextPath() { RequestPath getPath();
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;
}
}
/** /**
* Return a read-only map with parsed and decoded query parameter values. * 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); 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); Builder path(String path);
/** /**
* Set the contextPath to return. * Set the contextPath to use.
*/ */
Builder contextPath(String contextPath); Builder contextPath(String contextPath);

View File

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

View File

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

View File

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

View File

@ -80,7 +80,7 @@ public class UrlBasedCorsConfigurationSource implements CorsConfigurationSource
@Override @Override
public CorsConfiguration getCorsConfiguration(ServerWebExchange exchange) { 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()) { for (Map.Entry<String, CorsConfiguration> entry : this.corsConfigurations.entrySet()) {
if (this.pathMatcher.match(entry.getKey(), lookupPath)) { if (this.pathMatcher.match(entry.getKey(), lookupPath)) {
return entry.getValue(); return entry.getValue();

View File

@ -105,7 +105,7 @@ public class ContextPathCompositeHandlerTests {
new ContextPathCompositeHandler(map).handle(request, new MockServerHttpResponse()); new ContextPathCompositeHandler(map).handle(request, new MockServerHttpResponse());
assertTrue(handler.wasInvoked()); assertTrue(handler.wasInvoked());
assertEquals("/yet/another/path", handler.getRequest().getContextPath()); assertEquals("/yet/another/path", handler.getRequest().getPath().contextPath().value());
} }
@Test @Test
@ -133,7 +133,7 @@ public class ContextPathCompositeHandlerTests {
private void assertInvoked(TestHttpHandler handler, String contextPath) { private void assertInvoked(TestHttpHandler handler, String contextPath) {
assertTrue(handler.wasInvoked()); assertTrue(handler.wasInvoked());
assertEquals(contextPath, handler.getRequest().getContextPath()); assertEquals(contextPath, handler.getRequest().getPath().contextPath().value());
} }
private void assertNotInvoked(TestHttpHandler... handlers) { private void assertNotInvoked(TestHttpHandler... handlers) {

View File

@ -56,7 +56,7 @@ public class RxNettyServerHttpRequest extends AbstractServerHttpRequest {
NettyDataBufferFactory dataBufferFactory, InetSocketAddress remoteAddress) NettyDataBufferFactory dataBufferFactory, InetSocketAddress remoteAddress)
throws URISyntaxException { throws URISyntaxException {
super(initUri(request, remoteAddress), initHeaders(request)); super(initUri(request, remoteAddress), "", initHeaders(request));
this.request = request; this.request = request;
Assert.notNull(dataBufferFactory, "NettyDataBufferFactory must not be null"); Assert.notNull(dataBufferFactory, "NettyDataBufferFactory must not be null");

View File

@ -56,8 +56,6 @@ public class MockServerHttpRequest extends AbstractServerHttpRequest {
private final HttpMethod httpMethod; private final HttpMethod httpMethod;
private final String contextPath;
private final MultiValueMap<String, HttpCookie> cookies; private final MultiValueMap<String, HttpCookie> cookies;
private final InetSocketAddress remoteAddress; private final InetSocketAddress remoteAddress;
@ -70,9 +68,8 @@ public class MockServerHttpRequest extends AbstractServerHttpRequest {
InetSocketAddress remoteAddress, InetSocketAddress remoteAddress,
Publisher<? extends DataBuffer> body) { Publisher<? extends DataBuffer> body) {
super(uri, headers); super(uri, contextPath, headers);
this.httpMethod = httpMethod; this.httpMethod = httpMethod;
this.contextPath = (contextPath != null ? contextPath : "");
this.cookies = cookies; this.cookies = cookies;
this.remoteAddress = remoteAddress; this.remoteAddress = remoteAddress;
this.body = Flux.from(body); this.body = Flux.from(body);
@ -89,11 +86,6 @@ public class MockServerHttpRequest extends AbstractServerHttpRequest {
return this.httpMethod.name(); return this.httpMethod.name();
} }
@Override
public String getContextPath() {
return this.contextPath;
}
@Override @Override
public InetSocketAddress getRemoteAddress() { public InetSocketAddress getRemoteAddress() {
return this.remoteAddress; return this.remoteAddress;

View File

@ -100,7 +100,7 @@ public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping {
@Override @Override
public Mono<Object> getHandlerInternal(ServerWebExchange exchange) { public Mono<Object> getHandlerInternal(ServerWebExchange exchange) {
String lookupPath = exchange.getRequest().getPathWithinApplication(); String lookupPath = exchange.getRequest().getPath().pathWithinApplication().value();
Object handler; Object handler;
try { try {
handler = lookupHandler(lookupPath, exchange); handler = lookupHandler(lookupPath, exchange);

View File

@ -166,7 +166,7 @@ public class ResourceUrlProvider implements ApplicationListener<ContextRefreshed
private int getLookupPathIndex(ServerWebExchange exchange) { private int getLookupPathIndex(ServerWebExchange exchange) {
ServerHttpRequest request = exchange.getRequest(); ServerHttpRequest request = exchange.getRequest();
String requestPath = request.getURI().getPath(); String requestPath = request.getURI().getPath();
String lookupPath = exchange.getRequest().getPathWithinApplication(); String lookupPath = exchange.getRequest().getPath().pathWithinApplication().value();
return requestPath.indexOf(lookupPath); return requestPath.indexOf(lookupPath);
} }

View File

@ -186,7 +186,7 @@ public final class PatternsRequestCondition extends AbstractRequestCondition<Pat
return this; return this;
} }
String lookupPath = exchange.getRequest().getPathWithinApplication(); String lookupPath = exchange.getRequest().getPath().pathWithinApplication().value();
List<String> matches = getMatchingPatterns(lookupPath); List<String> matches = getMatchingPatterns(lookupPath);
return matches.isEmpty() ? null : return matches.isEmpty() ? null :
@ -243,7 +243,7 @@ public final class PatternsRequestCondition extends AbstractRequestCondition<Pat
*/ */
@Override @Override
public int compareTo(PatternsRequestCondition other, ServerWebExchange exchange) { public int compareTo(PatternsRequestCondition other, ServerWebExchange exchange) {
String lookupPath = exchange.getRequest().getPathWithinApplication(); String lookupPath = exchange.getRequest().getPath().pathWithinApplication().value();
Comparator<String> patternComparator = this.pathMatcher.getPatternComparator(lookupPath); Comparator<String> patternComparator = this.pathMatcher.getPatternComparator(lookupPath);
Iterator<String> iterator = this.patterns.iterator(); Iterator<String> iterator = this.patterns.iterator();
Iterator<String> iteratorOther = other.patterns.iterator(); Iterator<String> iteratorOther = other.patterns.iterator();

View File

@ -257,7 +257,7 @@ public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMap
*/ */
@Override @Override
public Mono<HandlerMethod> getHandlerInternal(ServerWebExchange exchange) { public Mono<HandlerMethod> getHandlerInternal(ServerWebExchange exchange) {
String lookupPath = exchange.getRequest().getPathWithinApplication(); String lookupPath = exchange.getRequest().getPath().pathWithinApplication().value();
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug("Looking up handler method for path " + lookupPath); logger.debug("Looking up handler method for path " + lookupPath);
} }

View File

@ -202,9 +202,11 @@ public class RedirectView extends AbstractUrlBasedView {
String url = getUrl(); String url = getUrl();
Assert.state(url != null, "'url' not set"); Assert.state(url != null, "'url' not set");
ServerHttpRequest request = exchange.getRequest();
StringBuilder targetUrl = new StringBuilder(); StringBuilder targetUrl = new StringBuilder();
if (isContextRelative() && url.startsWith("/")) { if (isContextRelative() && url.startsWith("/")) {
targetUrl.append(exchange.getRequest().getContextPath()); targetUrl.append(request.getPath().contextPath().value());
} }
targetUrl.append(url); targetUrl.append(url);
@ -214,7 +216,7 @@ public class RedirectView extends AbstractUrlBasedView {
} }
if (isPropagateQuery()) { if (isPropagateQuery()) {
targetUrl = appendCurrentRequestQuery(targetUrl.toString(), exchange.getRequest()); targetUrl = appendCurrentRequestQuery(targetUrl.toString(), request);
} }
String result = targetUrl.toString(); String result = targetUrl.toString();

View File

@ -28,6 +28,7 @@ import org.springframework.context.NoSuchMessageException;
import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.lang.Nullable; import org.springframework.lang.Nullable;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.validation.BindException; import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult; import org.springframework.validation.BindingResult;
import org.springframework.validation.Errors; import org.springframework.validation.Errors;
@ -180,10 +181,10 @@ public class RequestContext {
/** /**
* Return the context path of the current web application. This is * Return the context path of the current web application. This is
* useful for building links to other resources within the application. * useful for building links to other resources within the application.
* <p>Delegates to {@link ServerHttpRequest#getContextPath()}. * <p>Delegates to {@link ServerHttpRequest#getPath()}.
*/ */
public String getContextPath() { public String getContextPath() {
return this.exchange.getRequest().getContextPath(); return this.exchange.getRequest().getPath().contextPath().value();
} }
/** /**
@ -193,7 +194,7 @@ public class RequestContext {
* absolute path also URL-encoded accordingly * absolute path also URL-encoded accordingly
*/ */
public String getContextUrl(String relativeUrl) { public String getContextUrl(String relativeUrl) {
String url = getContextPath() + relativeUrl; String url = StringUtils.applyRelativePath(getContextPath() + "/", relativeUrl);
return getExchange().getResponse().encodeUrl(url); return getExchange().getResponse().encodeUrl(url);
} }
@ -208,7 +209,7 @@ public class RequestContext {
* absolute path also URL-encoded accordingly * absolute path also URL-encoded accordingly
*/ */
public String getContextUrl(String relativeUrl, Map<String, ?> params) { public String getContextUrl(String relativeUrl, Map<String, ?> params) {
String url = getContextPath() + relativeUrl; String url = StringUtils.applyRelativePath(getContextPath() + "/", relativeUrl);
UriTemplate template = new UriTemplate(url); UriTemplate template = new UriTemplate(url);
url = template.expand(params).toASCIIString(); url = template.expand(params).toASCIIString();
return getExchange().getResponse().encodeUrl(url); return getExchange().getResponse().encodeUrl(url);

View File

@ -258,7 +258,7 @@ public class ViewResolutionResultHandler extends HandlerResultHandlerSupport
* Use the request path the leading and trailing slash stripped. * Use the request path the leading and trailing slash stripped.
*/ */
private String getDefaultViewName(ServerWebExchange exchange) { private String getDefaultViewName(ServerWebExchange exchange) {
String path = exchange.getRequest().getPathWithinApplication(); String path = exchange.getRequest().getPath().pathWithinApplication().value();
if (path.startsWith("/")) { if (path.startsWith("/")) {
path = path.substring(1); path = path.substring(1);
} }

View File

@ -213,7 +213,7 @@ public class RequestMappingInfoHandlerMappingTests {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public void handleMatchUriTemplateVariables() throws Exception { public void handleMatchUriTemplateVariables() throws Exception {
ServerWebExchange exchange = get("/1/2").toExchange(); ServerWebExchange exchange = get("/1/2").toExchange();
String lookupPath = exchange.getRequest().getPathWithinApplication(); String lookupPath = exchange.getRequest().getPath().pathWithinApplication().value();
RequestMappingInfo key = paths("/{path1}/{path2}").build(); RequestMappingInfo key = paths("/{path1}/{path2}").build();
this.handlerMapping.handleMatch(key, lookupPath, exchange); this.handlerMapping.handleMatch(key, lookupPath, exchange);
@ -232,7 +232,7 @@ public class RequestMappingInfoHandlerMappingTests {
URI url = URI.create("/group/a%2Fb"); URI url = URI.create("/group/a%2Fb");
ServerWebExchange exchange = method(HttpMethod.GET, url).toExchange(); ServerWebExchange exchange = method(HttpMethod.GET, url).toExchange();
String lookupPath = exchange.getRequest().getPathWithinApplication(); String lookupPath = exchange.getRequest().getPath().pathWithinApplication().value();
this.handlerMapping.handleMatch(key, lookupPath, exchange); this.handlerMapping.handleMatch(key, lookupPath, exchange);
String name = HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE; String name = HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE;
@ -248,7 +248,7 @@ public class RequestMappingInfoHandlerMappingTests {
public void handleMatchBestMatchingPatternAttribute() throws Exception { public void handleMatchBestMatchingPatternAttribute() throws Exception {
RequestMappingInfo key = paths("/{path1}/2", "/**").build(); RequestMappingInfo key = paths("/{path1}/2", "/**").build();
ServerWebExchange exchange = get("/1/2").toExchange(); ServerWebExchange exchange = get("/1/2").toExchange();
String lookupPath = exchange.getRequest().getPathWithinApplication(); String lookupPath = exchange.getRequest().getPath().pathWithinApplication().value();
this.handlerMapping.handleMatch(key, lookupPath, exchange); this.handlerMapping.handleMatch(key, lookupPath, exchange);
assertEquals("/{path1}/2", exchange.getAttributes().get(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE)); assertEquals("/{path1}/2", exchange.getAttributes().get(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE));
@ -258,7 +258,7 @@ public class RequestMappingInfoHandlerMappingTests {
public void handleMatchBestMatchingPatternAttributeNoPatternsDefined() throws Exception { public void handleMatchBestMatchingPatternAttributeNoPatternsDefined() throws Exception {
RequestMappingInfo key = paths().build(); RequestMappingInfo key = paths().build();
ServerWebExchange exchange = get("/1/2").toExchange(); ServerWebExchange exchange = get("/1/2").toExchange();
String lookupPath = exchange.getRequest().getPathWithinApplication(); String lookupPath = exchange.getRequest().getPath().pathWithinApplication().value();
this.handlerMapping.handleMatch(key, lookupPath, exchange); this.handlerMapping.handleMatch(key, lookupPath, exchange);
assertEquals("/1/2", exchange.getAttributes().get(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE)); assertEquals("/1/2", exchange.getAttributes().get(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE));
@ -367,7 +367,7 @@ public class RequestMappingInfoHandlerMappingTests {
private void handleMatch(ServerWebExchange exchange, String pattern) { private void handleMatch(ServerWebExchange exchange, String pattern) {
RequestMappingInfo info = paths(pattern).build(); RequestMappingInfo info = paths(pattern).build();
String lookupPath = exchange.getRequest().getPathWithinApplication(); String lookupPath = exchange.getRequest().getPath().pathWithinApplication().value();
this.handlerMapping.handleMatch(info, lookupPath, exchange); this.handlerMapping.handleMatch(info, lookupPath, exchange);
} }

View File

@ -117,7 +117,7 @@ public class ContextPathIntegrationTests {
@GetMapping("/test") @GetMapping("/test")
public String handle(ServerHttpRequest request) { public String handle(ServerHttpRequest request) {
return "Tested in " + request.getContextPath(); return "Tested in " + request.getPath().contextPath().value();
} }
} }

View File

@ -47,7 +47,7 @@ public class RedirectViewTests {
@Before @Before
public void setup() { public void setup() {
this.exchange = MockServerHttpRequest.get("/").contextPath("/context").toExchange(); this.exchange = MockServerHttpRequest.get("/context/path").contextPath("/context").toExchange();
} }

View File

@ -34,7 +34,8 @@ import static org.junit.Assert.assertEquals;
*/ */
public class RequestContextTests { public class RequestContextTests {
private final MockServerWebExchange exchange = MockServerHttpRequest.get("/").contextPath("foo/").toExchange(); private final MockServerWebExchange exchange = MockServerHttpRequest.get("/foo/path")
.contextPath("/foo").toExchange();
private GenericApplicationContext applicationContext; private GenericApplicationContext applicationContext;
@ -50,7 +51,7 @@ public class RequestContextTests {
@Test @Test
public void testGetContextUrl() throws Exception { public void testGetContextUrl() throws Exception {
RequestContext context = new RequestContext(this.exchange, this.model, this.applicationContext); RequestContext context = new RequestContext(this.exchange, this.model, this.applicationContext);
assertEquals("foo/bar", context.getContextUrl("bar")); assertEquals("/foo/bar", context.getContextUrl("bar"));
} }
@Test @Test
@ -59,7 +60,7 @@ public class RequestContextTests {
Map<String, Object> map = new HashMap<>(); Map<String, Object> map = new HashMap<>();
map.put("foo", "bar"); map.put("foo", "bar");
map.put("spam", "bucket"); map.put("spam", "bucket");
assertEquals("foo/bar?spam=bucket", context.getContextUrl("{foo}?spam={spam}", map)); assertEquals("/foo/bar?spam=bucket", context.getContextUrl("{foo}?spam={spam}", map));
} }
@Test @Test
@ -68,7 +69,7 @@ public class RequestContextTests {
Map<String, Object> map = new HashMap<>(); Map<String, Object> map = new HashMap<>();
map.put("foo", "bar baz"); map.put("foo", "bar baz");
map.put("spam", "&bucket="); map.put("spam", "&bucket=");
assertEquals("foo/bar%20baz?spam=%26bucket%3D", context.getContextUrl("{foo}?spam={spam}", map)); assertEquals("/foo/bar%20baz?spam=%26bucket%3D", context.getContextUrl("{foo}?spam={spam}", map));
} }
} }