Skip to content

Commit

Permalink
Respect context path in WebMvc.fn & WebFlux.fn
Browse files Browse the repository at this point in the history
This commit makes several changes in both WebMvc.fn as well as
WebFlux.fn.

 - ServerRequest now exposes a RequestPath through requestPath(), and
   pathContainer() has been deprecated.

 - The PathPredicate and PathResourceLookupFunction now respects this
   RequestPath's pathInApplication() in their path-related
   functionality.

 - When nesting, the PathPredicate now appends the matched part of the
   path to the current context path, instead of removing the matched
   part (which was done previously). This has the same result: the
   matched part is gone, but now the full path stays the same.

Closes gh-25270
  • Loading branch information
poutsma committed Sep 3, 2020
1 parent 88249b2 commit d550d34
Show file tree
Hide file tree
Showing 14 changed files with 122 additions and 137 deletions.
Expand Up @@ -41,7 +41,6 @@
import org.springframework.http.MediaType;
import org.springframework.http.codec.HttpMessageReader;
import org.springframework.http.codec.multipart.Part;
import org.springframework.http.server.PathContainer;
import org.springframework.http.server.RequestPath;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.lang.Nullable;
Expand Down Expand Up @@ -69,7 +68,7 @@ public final class MockServerRequest implements ServerRequest {

private final URI uri;

private final RequestPath pathContainer;
private final RequestPath requestPath;

private final MockHeaders headers;

Expand All @@ -88,7 +87,7 @@ public final class MockServerRequest implements ServerRequest {
private final WebSession session;

@Nullable
private Principal principal;
private final Principal principal;

@Nullable
private final InetSocketAddress remoteAddress;
Expand All @@ -111,7 +110,7 @@ private MockServerRequest(HttpMethod method, URI uri, String contextPath, MockHe

this.method = method;
this.uri = uri;
this.pathContainer = RequestPath.parse(uri, contextPath);
this.requestPath = RequestPath.parse(uri, contextPath);
this.headers = headers;
this.cookies = cookies;
this.body = body;
Expand Down Expand Up @@ -148,8 +147,8 @@ public UriBuilder uriBuilder() {
}

@Override
public PathContainer pathContainer() {
return this.pathContainer;
public RequestPath requestPath() {
return this.requestPath;
}

@Override
Expand Down
Expand Up @@ -249,7 +249,7 @@ else if (!hasLength(pathContainer)) {
@Nullable
public PathRemainingMatchInfo matchStartOfPath(PathContainer pathContainer) {
if (this.head == null) {
return new PathRemainingMatchInfo(pathContainer);
return new PathRemainingMatchInfo(EMPTY_PATH, pathContainer);
}
else if (!hasLength(pathContainer)) {
return null;
Expand All @@ -262,15 +262,17 @@ else if (!hasLength(pathContainer)) {
return null;
}
else {
PathRemainingMatchInfo info;
PathContainer pathMatched;
PathContainer pathRemaining;
if (matchingContext.remainingPathIndex == pathContainer.elements().size()) {
info = new PathRemainingMatchInfo(EMPTY_PATH, matchingContext.getPathMatchResult());
pathMatched = pathContainer;
pathRemaining = EMPTY_PATH;
}
else {
info = new PathRemainingMatchInfo(pathContainer.subPath(matchingContext.remainingPathIndex),
matchingContext.getPathMatchResult());
pathMatched = pathContainer.subPath(0, matchingContext.remainingPathIndex);
pathRemaining = pathContainer.subPath(matchingContext.remainingPathIndex);
}
return info;
return new PathRemainingMatchInfo(pathMatched, pathRemaining, matchingContext.getPathMatchResult());
}
}

Expand Down Expand Up @@ -592,20 +594,32 @@ public String toString() {
*/
public static class PathRemainingMatchInfo {

private final PathContainer pathMatched;

private final PathContainer pathRemaining;

private final PathMatchInfo pathMatchInfo;


PathRemainingMatchInfo(PathContainer pathRemaining) {
this(pathRemaining, PathMatchInfo.EMPTY);
PathRemainingMatchInfo(PathContainer pathMatched, PathContainer pathRemaining) {
this(pathMatched, pathRemaining, PathMatchInfo.EMPTY);
}

PathRemainingMatchInfo(PathContainer pathRemaining, PathMatchInfo pathMatchInfo) {
PathRemainingMatchInfo(PathContainer pathMatched, PathContainer pathRemaining,
PathMatchInfo pathMatchInfo) {
this.pathRemaining = pathRemaining;
this.pathMatched = pathMatched;
this.pathMatchInfo = pathMatchInfo;
}

/**
* Return the part of a path that was matched by a pattern.
* @since 5.3
*/
public PathContainer getPathMatched() {
return this.pathMatched;
}

/**
* Return the part of a path that was not matched by a pattern.
*/
Expand Down
Expand Up @@ -557,18 +557,21 @@ public void pathRemainingEnhancements_spr15419() {
pp = parse("/{this}/{one}/{here}");
pri = getPathRemaining(pp, "/foo/bar/goo/boo");
assertThat(pri.getPathRemaining().value()).isEqualTo("/boo");
assertThat(pri.getPathMatched().value()).isEqualTo("/foo/bar/goo");
assertThat(pri.getUriVariables().get("this")).isEqualTo("foo");
assertThat(pri.getUriVariables().get("one")).isEqualTo("bar");
assertThat(pri.getUriVariables().get("here")).isEqualTo("goo");

pp = parse("/aaa/{foo}");
pri = getPathRemaining(pp, "/aaa/bbb");
assertThat(pri.getPathRemaining().value()).isEqualTo("");
assertThat(pri.getPathMatched().value()).isEqualTo("/aaa/bbb");
assertThat(pri.getUriVariables().get("foo")).isEqualTo("bbb");

pp = parse("/aaa/bbb");
pri = getPathRemaining(pp, "/aaa/bbb");
assertThat(pri.getPathRemaining().value()).isEqualTo("");
assertThat(pri.getPathMatched().value()).isEqualTo("/aaa/bbb");
assertThat(pri.getUriVariables().size()).isEqualTo(0);

pp = parse("/*/{foo}/b*");
Expand Down
Expand Up @@ -43,7 +43,7 @@
import org.springframework.http.MediaType;
import org.springframework.http.codec.HttpMessageReader;
import org.springframework.http.codec.multipart.Part;
import org.springframework.http.server.PathContainer;
import org.springframework.http.server.RequestPath;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.lang.Nullable;
Expand Down Expand Up @@ -123,7 +123,7 @@ public UriBuilder uriBuilder() {
}

@Override
public PathContainer pathContainer() {
public RequestPath requestPath() {
return request().getPath();
}

Expand Down
Expand Up @@ -56,7 +56,7 @@ public PathResourceLookupFunction(String pattern, Resource location) {

@Override
public Mono<Resource> apply(ServerRequest request) {
PathContainer pathContainer = request.pathContainer();
PathContainer pathContainer = request.requestPath().pathWithinApplication();
if (!this.pattern.matches(pathContainer)) {
return Mono.empty();
}
Expand Down
Expand Up @@ -19,7 +19,6 @@
import java.net.InetSocketAddress;
import java.net.URI;
import java.security.Principal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.EnumSet;
Expand Down Expand Up @@ -47,6 +46,7 @@
import org.springframework.http.codec.HttpMessageReader;
import org.springframework.http.codec.multipart.Part;
import org.springframework.http.server.PathContainer;
import org.springframework.http.server.RequestPath;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
Expand Down Expand Up @@ -492,7 +492,7 @@ public PathPatternPredicate(PathPattern pattern) {

@Override
public boolean test(ServerRequest request) {
PathContainer pathContainer = request.pathContainer();
PathContainer pathContainer = request.requestPath().pathWithinApplication();
PathPattern.PathMatchInfo info = this.pattern.matchAndExtract(pathContainer);
traceMatch("Pattern", this.pattern.getPatternString(), request.path(), info != null);
if (info != null) {
Expand All @@ -518,7 +518,7 @@ private static void mergeAttributes(ServerRequest request, Map<String, String> v

@Override
public Optional<ServerRequest> nest(ServerRequest request) {
return Optional.ofNullable(this.pattern.matchStartOfPath(request.pathContainer()))
return Optional.ofNullable(this.pattern.matchStartOfPath(request.requestPath().pathWithinApplication()))
.map(info -> new SubPathServerRequestWrapper(request, info, this.pattern));
}

Expand Down Expand Up @@ -926,17 +926,27 @@ private static class SubPathServerRequestWrapper implements ServerRequest {

private final ServerRequest request;

private final PathContainer pathContainer;
private final RequestPath requestPath;

private final Map<String, Object> attributes;

public SubPathServerRequestWrapper(ServerRequest request,
PathPattern.PathRemainingMatchInfo info, PathPattern pattern) {
this.request = request;
this.pathContainer = new SubPathContainer(info.getPathRemaining());
this.requestPath = requestPath(request.requestPath(), info);
this.attributes = mergeAttributes(request, info.getUriVariables(), pattern);
}

private static RequestPath requestPath(RequestPath original, PathPattern.PathRemainingMatchInfo info) {
StringBuilder contextPath = new StringBuilder(original.contextPath().value());
contextPath.append(info.getPathMatched().value());
int length = contextPath.length();
if (length > 0 && contextPath.charAt(length - 1) == '/') {
contextPath.setLength(length - 1);
}
return original.modifyContextPath(contextPath.toString());
}

private static Map<String, Object> mergeAttributes(ServerRequest request,
Map<String, String> pathVariables, PathPattern pattern) {
Map<String, Object> result = new ConcurrentHashMap<>(request.attributes());
Expand Down Expand Up @@ -972,13 +982,8 @@ public UriBuilder uriBuilder() {
}

@Override
public String path() {
return this.pathContainer.value();
}

@Override
public PathContainer pathContainer() {
return this.pathContainer;
public RequestPath requestPath() {
return this.requestPath;
}

@Override
Expand Down Expand Up @@ -1089,46 +1094,6 @@ public String toString() {
return method() + " " + path();
}

private static class SubPathContainer implements PathContainer {

private static final PathContainer.Separator SEPARATOR = () -> "/";


private final String value;

private final List<Element> elements;

public SubPathContainer(PathContainer original) {
this.value = prefixWithSlash(original.value());
this.elements = prependWithSeparator(original.elements());
}

private static String prefixWithSlash(String path) {
if (!path.startsWith("/")) {
path = "/" + path;
}
return path;
}

private static List<Element> prependWithSeparator(List<Element> elements) {
List<Element> result = new ArrayList<>(elements);
if (result.isEmpty() || !(result.get(0) instanceof Separator)) {
result.add(0, SEPARATOR);
}
return Collections.unmodifiableList(result);
}


@Override
public String value() {
return this.value;
}

@Override
public List<Element> elements() {
return this.elements;
}
}
}

}
Expand Up @@ -42,6 +42,7 @@
import org.springframework.http.codec.json.Jackson2CodecSupport;
import org.springframework.http.codec.multipart.Part;
import org.springframework.http.server.PathContainer;
import org.springframework.http.server.RequestPath;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
Expand Down Expand Up @@ -100,14 +101,24 @@ default HttpMethod method() {
* Get the request path.
*/
default String path() {
return uri().getRawPath();
return requestPath().pathWithinApplication().value();
}

/**
* Get the request path as a {@code PathContainer}.
* @deprecated as of 5.3, in favor on {@link #requestPath()}
*/
@Deprecated
default PathContainer pathContainer() {
return PathContainer.parsePath(path());
return requestPath();
}

/**
* Get the request path as a {@code PathContainer}.
* @since 5.3
*/
default RequestPath requestPath() {
return exchange().getRequest().getPath();
}

/**
Expand Down
Expand Up @@ -38,6 +38,7 @@
import org.springframework.http.codec.HttpMessageReader;
import org.springframework.http.codec.multipart.Part;
import org.springframework.http.server.PathContainer;
import org.springframework.http.server.RequestPath;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.util.Assert;
import org.springframework.util.MultiValueMap;
Expand Down Expand Up @@ -104,10 +105,16 @@ public String path() {
}

@Override
@Deprecated
public PathContainer pathContainer() {
return this.delegate.pathContainer();
}

@Override
public RequestPath requestPath() {
return this.delegate.requestPath();
}

@Override
public Headers headers() {
return this.delegate.headers();
Expand Down
Expand Up @@ -29,7 +29,6 @@
import org.springframework.web.testfixture.server.MockServerWebExchange;
import org.springframework.web.util.pattern.PathPatternParser;

import static java.util.Collections.emptyList;
import static org.assertj.core.api.Assertions.assertThat;

/**
Expand Down Expand Up @@ -141,7 +140,7 @@ public void path() {
URI uri = URI.create("https://localhost/path");
RequestPredicate predicate = RequestPredicates.path("/p*");
MockServerHttpRequest mockRequest = MockServerHttpRequest.get(uri.toString()).build();
ServerRequest request = new DefaultServerRequest(MockServerWebExchange.from(mockRequest), emptyList());
ServerRequest request = new DefaultServerRequest(MockServerWebExchange.from(mockRequest), Collections.emptyList());
assertThat(predicate.test(request)).isTrue();

mockRequest = MockServerHttpRequest.head("https://example.com").build();
Expand Down Expand Up @@ -178,6 +177,15 @@ public void pathPredicates() {
assertThat(predicate.test(request)).isTrue();
}

@Test
public void pathWithContext() {
RequestPredicate predicate = RequestPredicates.path("/p*");
MockServerHttpRequest mockRequest = MockServerHttpRequest.get("https://localhost/context/path")
.contextPath("/context").build();
ServerRequest request = new DefaultServerRequest(MockServerWebExchange.from(mockRequest), Collections.emptyList());
assertThat(predicate.test(request)).isTrue();
}

@Test
public void headers() {
String name = "MyHeader";
Expand Down

0 comments on commit d550d34

Please sign in to comment.