Respect context path in WebMvc.fn & WebFlux.fn
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
This commit is contained in:
parent
88249b2d9a
commit
d550d344d5
|
|
@ -41,7 +41,6 @@ import org.springframework.http.HttpRange;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.http.codec.HttpMessageReader;
|
import org.springframework.http.codec.HttpMessageReader;
|
||||||
import org.springframework.http.codec.multipart.Part;
|
import org.springframework.http.codec.multipart.Part;
|
||||||
import org.springframework.http.server.PathContainer;
|
|
||||||
import org.springframework.http.server.RequestPath;
|
import org.springframework.http.server.RequestPath;
|
||||||
import org.springframework.http.server.reactive.ServerHttpRequest;
|
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||||
import org.springframework.lang.Nullable;
|
import org.springframework.lang.Nullable;
|
||||||
|
|
@ -69,7 +68,7 @@ public final class MockServerRequest implements ServerRequest {
|
||||||
|
|
||||||
private final URI uri;
|
private final URI uri;
|
||||||
|
|
||||||
private final RequestPath pathContainer;
|
private final RequestPath requestPath;
|
||||||
|
|
||||||
private final MockHeaders headers;
|
private final MockHeaders headers;
|
||||||
|
|
||||||
|
|
@ -88,7 +87,7 @@ public final class MockServerRequest implements ServerRequest {
|
||||||
private final WebSession session;
|
private final WebSession session;
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private Principal principal;
|
private final Principal principal;
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private final InetSocketAddress remoteAddress;
|
private final InetSocketAddress remoteAddress;
|
||||||
|
|
@ -111,7 +110,7 @@ public final class MockServerRequest implements ServerRequest {
|
||||||
|
|
||||||
this.method = method;
|
this.method = method;
|
||||||
this.uri = uri;
|
this.uri = uri;
|
||||||
this.pathContainer = RequestPath.parse(uri, contextPath);
|
this.requestPath = RequestPath.parse(uri, contextPath);
|
||||||
this.headers = headers;
|
this.headers = headers;
|
||||||
this.cookies = cookies;
|
this.cookies = cookies;
|
||||||
this.body = body;
|
this.body = body;
|
||||||
|
|
@ -148,8 +147,8 @@ public final class MockServerRequest implements ServerRequest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public PathContainer pathContainer() {
|
public RequestPath requestPath() {
|
||||||
return this.pathContainer;
|
return this.requestPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
||||||
|
|
@ -249,7 +249,7 @@ public class PathPattern implements Comparable<PathPattern> {
|
||||||
@Nullable
|
@Nullable
|
||||||
public PathRemainingMatchInfo matchStartOfPath(PathContainer pathContainer) {
|
public PathRemainingMatchInfo matchStartOfPath(PathContainer pathContainer) {
|
||||||
if (this.head == null) {
|
if (this.head == null) {
|
||||||
return new PathRemainingMatchInfo(pathContainer);
|
return new PathRemainingMatchInfo(EMPTY_PATH, pathContainer);
|
||||||
}
|
}
|
||||||
else if (!hasLength(pathContainer)) {
|
else if (!hasLength(pathContainer)) {
|
||||||
return null;
|
return null;
|
||||||
|
|
@ -262,15 +262,17 @@ public class PathPattern implements Comparable<PathPattern> {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
PathRemainingMatchInfo info;
|
PathContainer pathMatched;
|
||||||
|
PathContainer pathRemaining;
|
||||||
if (matchingContext.remainingPathIndex == pathContainer.elements().size()) {
|
if (matchingContext.remainingPathIndex == pathContainer.elements().size()) {
|
||||||
info = new PathRemainingMatchInfo(EMPTY_PATH, matchingContext.getPathMatchResult());
|
pathMatched = pathContainer;
|
||||||
|
pathRemaining = EMPTY_PATH;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
info = new PathRemainingMatchInfo(pathContainer.subPath(matchingContext.remainingPathIndex),
|
pathMatched = pathContainer.subPath(0, matchingContext.remainingPathIndex);
|
||||||
matchingContext.getPathMatchResult());
|
pathRemaining = pathContainer.subPath(matchingContext.remainingPathIndex);
|
||||||
}
|
}
|
||||||
return info;
|
return new PathRemainingMatchInfo(pathMatched, pathRemaining, matchingContext.getPathMatchResult());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -592,20 +594,32 @@ public class PathPattern implements Comparable<PathPattern> {
|
||||||
*/
|
*/
|
||||||
public static class PathRemainingMatchInfo {
|
public static class PathRemainingMatchInfo {
|
||||||
|
|
||||||
|
private final PathContainer pathMatched;
|
||||||
|
|
||||||
private final PathContainer pathRemaining;
|
private final PathContainer pathRemaining;
|
||||||
|
|
||||||
private final PathMatchInfo pathMatchInfo;
|
private final PathMatchInfo pathMatchInfo;
|
||||||
|
|
||||||
|
|
||||||
PathRemainingMatchInfo(PathContainer pathRemaining) {
|
PathRemainingMatchInfo(PathContainer pathMatched, PathContainer pathRemaining) {
|
||||||
this(pathRemaining, PathMatchInfo.EMPTY);
|
this(pathMatched, pathRemaining, PathMatchInfo.EMPTY);
|
||||||
}
|
}
|
||||||
|
|
||||||
PathRemainingMatchInfo(PathContainer pathRemaining, PathMatchInfo pathMatchInfo) {
|
PathRemainingMatchInfo(PathContainer pathMatched, PathContainer pathRemaining,
|
||||||
|
PathMatchInfo pathMatchInfo) {
|
||||||
this.pathRemaining = pathRemaining;
|
this.pathRemaining = pathRemaining;
|
||||||
|
this.pathMatched = pathMatched;
|
||||||
this.pathMatchInfo = pathMatchInfo;
|
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.
|
* Return the part of a path that was not matched by a pattern.
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -557,6 +557,7 @@ public class PathPatternTests {
|
||||||
pp = parse("/{this}/{one}/{here}");
|
pp = parse("/{this}/{one}/{here}");
|
||||||
pri = getPathRemaining(pp, "/foo/bar/goo/boo");
|
pri = getPathRemaining(pp, "/foo/bar/goo/boo");
|
||||||
assertThat(pri.getPathRemaining().value()).isEqualTo("/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("this")).isEqualTo("foo");
|
||||||
assertThat(pri.getUriVariables().get("one")).isEqualTo("bar");
|
assertThat(pri.getUriVariables().get("one")).isEqualTo("bar");
|
||||||
assertThat(pri.getUriVariables().get("here")).isEqualTo("goo");
|
assertThat(pri.getUriVariables().get("here")).isEqualTo("goo");
|
||||||
|
|
@ -564,11 +565,13 @@ public class PathPatternTests {
|
||||||
pp = parse("/aaa/{foo}");
|
pp = parse("/aaa/{foo}");
|
||||||
pri = getPathRemaining(pp, "/aaa/bbb");
|
pri = getPathRemaining(pp, "/aaa/bbb");
|
||||||
assertThat(pri.getPathRemaining().value()).isEqualTo("");
|
assertThat(pri.getPathRemaining().value()).isEqualTo("");
|
||||||
|
assertThat(pri.getPathMatched().value()).isEqualTo("/aaa/bbb");
|
||||||
assertThat(pri.getUriVariables().get("foo")).isEqualTo("bbb");
|
assertThat(pri.getUriVariables().get("foo")).isEqualTo("bbb");
|
||||||
|
|
||||||
pp = parse("/aaa/bbb");
|
pp = parse("/aaa/bbb");
|
||||||
pri = getPathRemaining(pp, "/aaa/bbb");
|
pri = getPathRemaining(pp, "/aaa/bbb");
|
||||||
assertThat(pri.getPathRemaining().value()).isEqualTo("");
|
assertThat(pri.getPathRemaining().value()).isEqualTo("");
|
||||||
|
assertThat(pri.getPathMatched().value()).isEqualTo("/aaa/bbb");
|
||||||
assertThat(pri.getUriVariables().size()).isEqualTo(0);
|
assertThat(pri.getUriVariables().size()).isEqualTo(0);
|
||||||
|
|
||||||
pp = parse("/*/{foo}/b*");
|
pp = parse("/*/{foo}/b*");
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@ import org.springframework.http.HttpRange;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.http.codec.HttpMessageReader;
|
import org.springframework.http.codec.HttpMessageReader;
|
||||||
import org.springframework.http.codec.multipart.Part;
|
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.ServerHttpRequest;
|
||||||
import org.springframework.http.server.reactive.ServerHttpResponse;
|
import org.springframework.http.server.reactive.ServerHttpResponse;
|
||||||
import org.springframework.lang.Nullable;
|
import org.springframework.lang.Nullable;
|
||||||
|
|
@ -123,7 +123,7 @@ class DefaultServerRequest implements ServerRequest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public PathContainer pathContainer() {
|
public RequestPath requestPath() {
|
||||||
return request().getPath();
|
return request().getPath();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -56,7 +56,7 @@ class PathResourceLookupFunction implements Function<ServerRequest, Mono<Resourc
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Mono<Resource> apply(ServerRequest request) {
|
public Mono<Resource> apply(ServerRequest request) {
|
||||||
PathContainer pathContainer = request.pathContainer();
|
PathContainer pathContainer = request.requestPath().pathWithinApplication();
|
||||||
if (!this.pattern.matches(pathContainer)) {
|
if (!this.pattern.matches(pathContainer)) {
|
||||||
return Mono.empty();
|
return Mono.empty();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,6 @@ package org.springframework.web.reactive.function.server;
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.security.Principal;
|
import java.security.Principal;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.EnumSet;
|
import java.util.EnumSet;
|
||||||
|
|
@ -47,6 +46,7 @@ import org.springframework.http.MediaType;
|
||||||
import org.springframework.http.codec.HttpMessageReader;
|
import org.springframework.http.codec.HttpMessageReader;
|
||||||
import org.springframework.http.codec.multipart.Part;
|
import org.springframework.http.codec.multipart.Part;
|
||||||
import org.springframework.http.server.PathContainer;
|
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.ServerHttpRequest;
|
||||||
import org.springframework.lang.NonNull;
|
import org.springframework.lang.NonNull;
|
||||||
import org.springframework.lang.Nullable;
|
import org.springframework.lang.Nullable;
|
||||||
|
|
@ -492,7 +492,7 @@ public abstract class RequestPredicates {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean test(ServerRequest request) {
|
public boolean test(ServerRequest request) {
|
||||||
PathContainer pathContainer = request.pathContainer();
|
PathContainer pathContainer = request.requestPath().pathWithinApplication();
|
||||||
PathPattern.PathMatchInfo info = this.pattern.matchAndExtract(pathContainer);
|
PathPattern.PathMatchInfo info = this.pattern.matchAndExtract(pathContainer);
|
||||||
traceMatch("Pattern", this.pattern.getPatternString(), request.path(), info != null);
|
traceMatch("Pattern", this.pattern.getPatternString(), request.path(), info != null);
|
||||||
if (info != null) {
|
if (info != null) {
|
||||||
|
|
@ -518,7 +518,7 @@ public abstract class RequestPredicates {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Optional<ServerRequest> nest(ServerRequest request) {
|
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));
|
.map(info -> new SubPathServerRequestWrapper(request, info, this.pattern));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -926,17 +926,27 @@ public abstract class RequestPredicates {
|
||||||
|
|
||||||
private final ServerRequest request;
|
private final ServerRequest request;
|
||||||
|
|
||||||
private final PathContainer pathContainer;
|
private final RequestPath requestPath;
|
||||||
|
|
||||||
private final Map<String, Object> attributes;
|
private final Map<String, Object> attributes;
|
||||||
|
|
||||||
public SubPathServerRequestWrapper(ServerRequest request,
|
public SubPathServerRequestWrapper(ServerRequest request,
|
||||||
PathPattern.PathRemainingMatchInfo info, PathPattern pattern) {
|
PathPattern.PathRemainingMatchInfo info, PathPattern pattern) {
|
||||||
this.request = request;
|
this.request = request;
|
||||||
this.pathContainer = new SubPathContainer(info.getPathRemaining());
|
this.requestPath = requestPath(request.requestPath(), info);
|
||||||
this.attributes = mergeAttributes(request, info.getUriVariables(), pattern);
|
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,
|
private static Map<String, Object> mergeAttributes(ServerRequest request,
|
||||||
Map<String, String> pathVariables, PathPattern pattern) {
|
Map<String, String> pathVariables, PathPattern pattern) {
|
||||||
Map<String, Object> result = new ConcurrentHashMap<>(request.attributes());
|
Map<String, Object> result = new ConcurrentHashMap<>(request.attributes());
|
||||||
|
|
@ -972,13 +982,8 @@ public abstract class RequestPredicates {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String path() {
|
public RequestPath requestPath() {
|
||||||
return this.pathContainer.value();
|
return this.requestPath;
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public PathContainer pathContainer() {
|
|
||||||
return this.pathContainer;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -1089,46 +1094,6 @@ public abstract class RequestPredicates {
|
||||||
return method() + " " + path();
|
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,7 @@ import org.springframework.http.codec.HttpMessageReader;
|
||||||
import org.springframework.http.codec.json.Jackson2CodecSupport;
|
import org.springframework.http.codec.json.Jackson2CodecSupport;
|
||||||
import org.springframework.http.codec.multipart.Part;
|
import org.springframework.http.codec.multipart.Part;
|
||||||
import org.springframework.http.server.PathContainer;
|
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.ServerHttpRequest;
|
||||||
import org.springframework.lang.Nullable;
|
import org.springframework.lang.Nullable;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
|
|
@ -100,14 +101,24 @@ public interface ServerRequest {
|
||||||
* Get the request path.
|
* Get the request path.
|
||||||
*/
|
*/
|
||||||
default String path() {
|
default String path() {
|
||||||
return uri().getRawPath();
|
return requestPath().pathWithinApplication().value();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the request path as a {@code PathContainer}.
|
* Get the request path as a {@code PathContainer}.
|
||||||
|
* @deprecated as of 5.3, in favor on {@link #requestPath()}
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
default PathContainer pathContainer() {
|
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();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,7 @@ import org.springframework.http.MediaType;
|
||||||
import org.springframework.http.codec.HttpMessageReader;
|
import org.springframework.http.codec.HttpMessageReader;
|
||||||
import org.springframework.http.codec.multipart.Part;
|
import org.springframework.http.codec.multipart.Part;
|
||||||
import org.springframework.http.server.PathContainer;
|
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.ServerHttpRequest;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
import org.springframework.util.MultiValueMap;
|
import org.springframework.util.MultiValueMap;
|
||||||
|
|
@ -104,10 +105,16 @@ public class ServerRequestWrapper implements ServerRequest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@Deprecated
|
||||||
public PathContainer pathContainer() {
|
public PathContainer pathContainer() {
|
||||||
return this.delegate.pathContainer();
|
return this.delegate.pathContainer();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RequestPath requestPath() {
|
||||||
|
return this.delegate.requestPath();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Headers headers() {
|
public Headers headers() {
|
||||||
return this.delegate.headers();
|
return this.delegate.headers();
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,6 @@ import org.springframework.web.testfixture.http.server.reactive.MockServerHttpRe
|
||||||
import org.springframework.web.testfixture.server.MockServerWebExchange;
|
import org.springframework.web.testfixture.server.MockServerWebExchange;
|
||||||
import org.springframework.web.util.pattern.PathPatternParser;
|
import org.springframework.web.util.pattern.PathPatternParser;
|
||||||
|
|
||||||
import static java.util.Collections.emptyList;
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -141,7 +140,7 @@ public class RequestPredicatesTests {
|
||||||
URI uri = URI.create("https://localhost/path");
|
URI uri = URI.create("https://localhost/path");
|
||||||
RequestPredicate predicate = RequestPredicates.path("/p*");
|
RequestPredicate predicate = RequestPredicates.path("/p*");
|
||||||
MockServerHttpRequest mockRequest = MockServerHttpRequest.get(uri.toString()).build();
|
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();
|
assertThat(predicate.test(request)).isTrue();
|
||||||
|
|
||||||
mockRequest = MockServerHttpRequest.head("https://example.com").build();
|
mockRequest = MockServerHttpRequest.head("https://example.com").build();
|
||||||
|
|
@ -178,6 +177,15 @@ public class RequestPredicatesTests {
|
||||||
assertThat(predicate.test(request)).isTrue();
|
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
|
@Test
|
||||||
public void headers() {
|
public void headers() {
|
||||||
String name = "MyHeader";
|
String name = "MyHeader";
|
||||||
|
|
|
||||||
|
|
@ -51,7 +51,6 @@ import org.springframework.http.HttpRange;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.http.converter.GenericHttpMessageConverter;
|
import org.springframework.http.converter.GenericHttpMessageConverter;
|
||||||
import org.springframework.http.converter.HttpMessageConverter;
|
import org.springframework.http.converter.HttpMessageConverter;
|
||||||
import org.springframework.http.server.PathContainer;
|
|
||||||
import org.springframework.http.server.RequestPath;
|
import org.springframework.http.server.RequestPath;
|
||||||
import org.springframework.http.server.ServletServerHttpRequest;
|
import org.springframework.http.server.ServletServerHttpRequest;
|
||||||
import org.springframework.lang.Nullable;
|
import org.springframework.lang.Nullable;
|
||||||
|
|
@ -132,13 +131,8 @@ class DefaultServerRequest implements ServerRequest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String path() {
|
public RequestPath requestPath() {
|
||||||
return pathContainer().value();
|
return this.requestPath;
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public PathContainer pathContainer() {
|
|
||||||
return this.requestPath.pathWithinApplication();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,7 @@ class PathResourceLookupFunction implements Function<ServerRequest, Optional<Res
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Optional<Resource> apply(ServerRequest request) {
|
public Optional<Resource> apply(ServerRequest request) {
|
||||||
PathContainer pathContainer = request.pathContainer();
|
PathContainer pathContainer = request.requestPath().pathWithinApplication();
|
||||||
if (!this.pattern.matches(pathContainer)) {
|
if (!this.pattern.matches(pathContainer)) {
|
||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,6 @@ import java.net.InetSocketAddress;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.security.Principal;
|
import java.security.Principal;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.EnumSet;
|
import java.util.EnumSet;
|
||||||
|
|
@ -51,6 +50,7 @@ import org.springframework.http.HttpMethod;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.http.converter.HttpMessageConverter;
|
import org.springframework.http.converter.HttpMessageConverter;
|
||||||
import org.springframework.http.server.PathContainer;
|
import org.springframework.http.server.PathContainer;
|
||||||
|
import org.springframework.http.server.RequestPath;
|
||||||
import org.springframework.lang.NonNull;
|
import org.springframework.lang.NonNull;
|
||||||
import org.springframework.lang.Nullable;
|
import org.springframework.lang.Nullable;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
|
|
@ -490,7 +490,7 @@ public abstract class RequestPredicates {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean test(ServerRequest request) {
|
public boolean test(ServerRequest request) {
|
||||||
PathContainer pathContainer = request.pathContainer();
|
PathContainer pathContainer = request.requestPath().pathWithinApplication();
|
||||||
PathPattern.PathMatchInfo info = this.pattern.matchAndExtract(pathContainer);
|
PathPattern.PathMatchInfo info = this.pattern.matchAndExtract(pathContainer);
|
||||||
traceMatch("Pattern", this.pattern.getPatternString(), request.path(), info != null);
|
traceMatch("Pattern", this.pattern.getPatternString(), request.path(), info != null);
|
||||||
if (info != null) {
|
if (info != null) {
|
||||||
|
|
@ -516,7 +516,7 @@ public abstract class RequestPredicates {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Optional<ServerRequest> nest(ServerRequest request) {
|
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));
|
.map(info -> new SubPathServerRequestWrapper(request, info, this.pattern));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -924,17 +924,27 @@ public abstract class RequestPredicates {
|
||||||
|
|
||||||
private final ServerRequest request;
|
private final ServerRequest request;
|
||||||
|
|
||||||
private final PathContainer pathContainer;
|
private RequestPath requestPath;
|
||||||
|
|
||||||
private final Map<String, Object> attributes;
|
private final Map<String, Object> attributes;
|
||||||
|
|
||||||
public SubPathServerRequestWrapper(ServerRequest request,
|
public SubPathServerRequestWrapper(ServerRequest request,
|
||||||
PathPattern.PathRemainingMatchInfo info, PathPattern pattern) {
|
PathPattern.PathRemainingMatchInfo info, PathPattern pattern) {
|
||||||
this.request = request;
|
this.request = request;
|
||||||
this.pathContainer = new SubPathContainer(info.getPathRemaining());
|
this.requestPath = requestPath(request.requestPath(), info);
|
||||||
this.attributes = mergeAttributes(request, info.getUriVariables(), pattern);
|
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,
|
private static Map<String, Object> mergeAttributes(ServerRequest request,
|
||||||
Map<String, String> pathVariables, PathPattern pattern) {
|
Map<String, String> pathVariables, PathPattern pattern) {
|
||||||
Map<String, Object> result = new ConcurrentHashMap<>(request.attributes());
|
Map<String, Object> result = new ConcurrentHashMap<>(request.attributes());
|
||||||
|
|
@ -970,13 +980,8 @@ public abstract class RequestPredicates {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String path() {
|
public RequestPath requestPath() {
|
||||||
return this.pathContainer.value();
|
return this.requestPath;
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public PathContainer pathContainer() {
|
|
||||||
return this.pathContainer;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -1084,46 +1089,6 @@ public abstract class RequestPredicates {
|
||||||
return method() + " " + path();
|
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -43,10 +43,12 @@ import org.springframework.http.HttpRange;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.http.converter.HttpMessageConverter;
|
import org.springframework.http.converter.HttpMessageConverter;
|
||||||
import org.springframework.http.server.PathContainer;
|
import org.springframework.http.server.PathContainer;
|
||||||
|
import org.springframework.http.server.RequestPath;
|
||||||
import org.springframework.lang.Nullable;
|
import org.springframework.lang.Nullable;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
import org.springframework.util.CollectionUtils;
|
import org.springframework.util.CollectionUtils;
|
||||||
import org.springframework.util.MultiValueMap;
|
import org.springframework.util.MultiValueMap;
|
||||||
|
import org.springframework.web.util.ServletRequestPathUtils;
|
||||||
import org.springframework.web.util.UriBuilder;
|
import org.springframework.web.util.UriBuilder;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -92,14 +94,24 @@ public interface ServerRequest {
|
||||||
* Get the request path.
|
* Get the request path.
|
||||||
*/
|
*/
|
||||||
default String path() {
|
default String path() {
|
||||||
return uri().getRawPath();
|
return requestPath().pathWithinApplication().value();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the request path as a {@code PathContainer}.
|
* Get the request path as a {@code PathContainer}.
|
||||||
|
* @deprecated as of 5.3, in favor on {@link #requestPath()}
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
default PathContainer pathContainer() {
|
default PathContainer pathContainer() {
|
||||||
return PathContainer.parsePath(path());
|
return requestPath();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the request path as a {@code PathContainer}.
|
||||||
|
* @since 5.3
|
||||||
|
*/
|
||||||
|
default RequestPath requestPath() {
|
||||||
|
return ServletRequestPathUtils.getParsedRequestPath(servletRequest());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -148,6 +148,13 @@ class RequestPredicatesTests {
|
||||||
assertThat(predicate.test(initRequest("GET", "/path"))).isTrue();
|
assertThat(predicate.test(initRequest("GET", "/path"))).isTrue();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void pathWithContext() {
|
||||||
|
RequestPredicate predicate = RequestPredicates.path("/p*");
|
||||||
|
ServerRequest request = initRequest("GET", "/context/path",
|
||||||
|
servletRequest -> servletRequest.setContextPath("/context"));
|
||||||
|
assertThat(predicate.test(request)).isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void headers() {
|
void headers() {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue