RouterFunction honors PathPatternParser in config

This commit introduces a way to change the PathPatternParser used in
PathPredicates, by way of a ChangePathPatternParserVisitor. This
visitor is used by both WebFluxConfigurationSupport and
WebMvcConfigurationSupport to make sure the configured parser is used.

Closes gh-23236
This commit is contained in:
Arjen Poutsma
2020-05-01 14:00:16 +02:00
parent 671d4519b2
commit e9d9de5f99
12 changed files with 454 additions and 32 deletions

View File

@@ -128,9 +128,18 @@ public class WebFluxConfigurationSupport implements ApplicationContextAware {
RequestMappingHandlerMapping mapping = createRequestMappingHandlerMapping();
mapping.setOrder(0);
mapping.setContentTypeResolver(contentTypeResolver);
mapping.setCorsConfigurations(getCorsConfigurations());
PathMatchConfigurer configurer = getPathMatchConfigurer();
configureAbstractHandlerMapping(mapping, configurer);
Map<String, Predicate<Class<?>>> pathPrefixes = configurer.getPathPrefixes();
if (pathPrefixes != null) {
mapping.setPathPrefixes(pathPrefixes);
}
return mapping;
}
private void configureAbstractHandlerMapping(AbstractHandlerMapping mapping, PathMatchConfigurer configurer) {
mapping.setCorsConfigurations(getCorsConfigurations());
Boolean useTrailingSlashMatch = configurer.isUseTrailingSlashMatch();
if (useTrailingSlashMatch != null) {
mapping.setUseTrailingSlashMatch(useTrailingSlashMatch);
@@ -139,12 +148,6 @@ public class WebFluxConfigurationSupport implements ApplicationContextAware {
if (useCaseSensitiveMatch != null) {
mapping.setUseCaseSensitiveMatch(useCaseSensitiveMatch);
}
Map<String, Predicate<Class<?>>> pathPrefixes = configurer.getPathPrefixes();
if (pathPrefixes != null) {
mapping.setPathPrefixes(pathPrefixes);
}
return mapping;
}
/**
@@ -210,8 +213,7 @@ public class WebFluxConfigurationSupport implements ApplicationContextAware {
RouterFunctionMapping mapping = createRouterFunctionMapping();
mapping.setOrder(-1); // go before RequestMappingHandlerMapping
mapping.setMessageReaders(serverCodecConfigurer.getReaders());
mapping.setCorsConfigurations(getCorsConfigurations());
configureAbstractHandlerMapping(mapping, getPathMatchConfigurer());
return mapping;
}

View File

@@ -0,0 +1,82 @@
/*
* Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.reactive.function.server;
import java.util.function.Function;
import reactor.core.publisher.Mono;
import org.springframework.core.io.Resource;
import org.springframework.util.Assert;
import org.springframework.web.util.pattern.PathPatternParser;
/**
* Implementation of {@link RouterFunctions.Visitor} that changes the
* {@link PathPatternParser} on path-related request predicates
* (i.e. {@code RequestPredicates.PathPatternPredicate}.
*
* @author Arjen Poutsma
* @since 5.3
*/
class ChangePathPatternParserVisitor implements RouterFunctions.Visitor {
private final PathPatternParser parser;
public ChangePathPatternParserVisitor(PathPatternParser parser) {
Assert.notNull(parser, "Parser must not be null");
this.parser = parser;
}
@Override
public void startNested(RequestPredicate predicate) {
changeParser(predicate);
}
@Override
public void endNested(RequestPredicate predicate) {
}
@Override
public void route(RequestPredicate predicate, HandlerFunction<?> handlerFunction) {
changeParser(predicate);
}
@Override
public void resources(Function<ServerRequest, Mono<Resource>> lookupFunction) {
}
@Override
public void unknown(RouterFunction<?> routerFunction) {
}
private void changeParser(RequestPredicate predicate) {
if (predicate instanceof Target) {
Target target = (Target) predicate;
target.changeParser(this.parser);
}
}
/**
* Interface implemented by predicates that can change the parser.
*/
public interface Target {
void changeParser(PathPatternParser parser);
}
}

View File

@@ -441,7 +441,6 @@ public abstract class RequestPredicates {
public HttpMethodPredicate(HttpMethod... httpMethods) {
Assert.notEmpty(httpMethods, "HttpMethods must not be empty");
this.httpMethods = EnumSet.copyOf(Arrays.asList(httpMethods));
}
@@ -465,7 +464,6 @@ public abstract class RequestPredicates {
}
}
@Override
public void accept(Visitor visitor) {
visitor.method(Collections.unmodifiableSet(this.httpMethods));
@@ -483,9 +481,9 @@ public abstract class RequestPredicates {
}
private static class PathPatternPredicate implements RequestPredicate {
private static class PathPatternPredicate implements RequestPredicate, ChangePathPatternParserVisitor.Target {
private final PathPattern pattern;
private PathPattern pattern;
public PathPatternPredicate(PathPattern pattern) {
Assert.notNull(pattern, "'pattern' must not be null");
@@ -529,10 +527,17 @@ public abstract class RequestPredicates {
visitor.path(this.pattern.getPatternString());
}
@Override
public void changeParser(PathPatternParser parser) {
String patternString = this.pattern.getPatternString();
this.pattern = parser.parse(patternString);
}
@Override
public String toString() {
return this.pattern.getPatternString();
}
}
@@ -750,7 +755,7 @@ public abstract class RequestPredicates {
* {@link RequestPredicate} for where both {@code left} and {@code right} predicates
* must match.
*/
static class AndRequestPredicate implements RequestPredicate {
static class AndRequestPredicate implements RequestPredicate, ChangePathPatternParserVisitor.Target {
private final RequestPredicate left;
@@ -788,6 +793,16 @@ public abstract class RequestPredicates {
visitor.endAnd();
}
@Override
public void changeParser(PathPatternParser parser) {
if (this.left instanceof ChangePathPatternParserVisitor.Target) {
((ChangePathPatternParserVisitor.Target) left).changeParser(parser);
}
if (this.right instanceof ChangePathPatternParserVisitor.Target) {
((ChangePathPatternParserVisitor.Target) right).changeParser(parser);
}
}
@Override
public String toString() {
return String.format("(%s && %s)", this.left, this.right);
@@ -797,7 +812,8 @@ public abstract class RequestPredicates {
/**
* {@link RequestPredicate} that negates a delegate predicate.
*/
static class NegateRequestPredicate implements RequestPredicate {
static class NegateRequestPredicate implements RequestPredicate, ChangePathPatternParserVisitor.Target {
private final RequestPredicate delegate;
public NegateRequestPredicate(RequestPredicate delegate) {
@@ -822,6 +838,13 @@ public abstract class RequestPredicates {
visitor.endNegate();
}
@Override
public void changeParser(PathPatternParser parser) {
if (this.delegate instanceof ChangePathPatternParserVisitor.Target) {
((ChangePathPatternParserVisitor.Target) delegate).changeParser(parser);
}
}
@Override
public String toString() {
return "!" + this.delegate.toString();
@@ -832,7 +855,7 @@ public abstract class RequestPredicates {
* {@link RequestPredicate} where either {@code left} or {@code right} predicates
* may match.
*/
static class OrRequestPredicate implements RequestPredicate {
static class OrRequestPredicate implements RequestPredicate, ChangePathPatternParserVisitor.Target {
private final RequestPredicate left;
@@ -882,6 +905,15 @@ public abstract class RequestPredicates {
visitor.endOr();
}
@Override
public void changeParser(PathPatternParser parser) {
if (this.left instanceof ChangePathPatternParserVisitor.Target) {
((ChangePathPatternParserVisitor.Target) left).changeParser(parser);
}
if (this.right instanceof ChangePathPatternParserVisitor.Target) {
((ChangePathPatternParserVisitor.Target) right).changeParser(parser);
}
}
@Override
public String toString() {

View File

@@ -37,6 +37,7 @@ import org.springframework.web.reactive.result.view.ViewResolver;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebHandler;
import org.springframework.web.server.adapter.WebHttpHandlerBuilder;
import org.springframework.web.util.pattern.PathPatternParser;
/**
* <strong>Central entry point to Spring's functional web framework.</strong>
@@ -255,6 +256,26 @@ public abstract class RouterFunctions {
return new RouterFunctionWebHandler(strategies, routerFunction);
}
/**
* Changes the {@link PathPatternParser} on the given {@linkplain RouterFunction router function}. This method
* can be used to change the {@code PathPatternParser} properties from the defaults, for instance to change
* {@linkplain PathPatternParser#setCaseSensitive(boolean) case sensitivity}.
* @param routerFunction the router function to change the parser in
* @param parser the parser to change to.
* @param <T> the type of response returned by the handler function
* @return the change router function
*/
public static <T extends ServerResponse> RouterFunction<T> changeParser(RouterFunction<T> routerFunction,
PathPatternParser parser) {
Assert.notNull(routerFunction, "RouterFunction must not be null");
Assert.notNull(parser, "Parser must not be null");
ChangePathPatternParserVisitor visitor = new ChangePathPatternParserVisitor(parser);
routerFunction.accept(visitor);
return routerFunction;
}
/**
* Represents a discoverable builder for router functions.
* Obtained via {@link RouterFunctions#route()}.
@@ -895,6 +916,7 @@ public abstract class RouterFunctions {
public void accept(Visitor visitor) {
visitor.route(this.predicate, this.handlerFunction);
}
}
@@ -938,6 +960,7 @@ public abstract class RouterFunctions {
this.routerFunction.accept(visitor);
visitor.endNested(this.predicate);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -103,6 +103,10 @@ public class RouterFunctionMapping extends AbstractHandlerMapping implements Ini
if (this.routerFunction == null) {
initRouterFunctions();
}
if (this.routerFunction != null) {
RouterFunctions.changeParser(this.routerFunction, getPathPatternParser());
}
}
/**

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -23,6 +23,7 @@ import reactor.test.StepVerifier;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.web.reactive.function.server.HandlerFunction;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.RouterFunctions;
import org.springframework.web.reactive.function.server.ServerResponse;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.testfixture.http.server.reactive.MockServerHttpRequest;
@@ -67,4 +68,26 @@ public class RouterFunctionMappingTests {
.verify();
}
@Test
public void changeParser() throws Exception {
HandlerFunction<ServerResponse> handlerFunction = request -> ServerResponse.ok().build();
RouterFunction<ServerResponse> routerFunction = RouterFunctions.route()
.GET("/foo", handlerFunction)
.POST("/bar", handlerFunction)
.build();
RouterFunctionMapping mapping = new RouterFunctionMapping(routerFunction);
mapping.setMessageReaders(this.codecConfigurer.getReaders());
mapping.setUseCaseSensitiveMatch(false);
mapping.afterPropertiesSet();
ServerWebExchange exchange = MockServerWebExchange.from(MockServerHttpRequest.get("https://example.com/FOO"));
Mono<Object> result = mapping.getHandler(exchange);
StepVerifier.create(result)
.expectNext(handlerFunction)
.verifyComplete();
}
}