Add option to configure PathPatternParser
As of Spring Framework 5.3, it is now possible to use `PathPatternParser` to parse and match request mapping path patterns, as an alternative to the current default `AntPathMatcher`. This new implementation has been used for a while in Spring WebFlux and it’s been designed for consistency and performance. This commit introduces a new configuration property for opting-in this new variant: `spring.mvc.pathmatch.matching-strategy=path_pattern_parser` The default option is still `ant_path_matcher` for now, but we might change the default in future versions since Spring Framework considers it the best choice for modern applications. There are several behavior differences with this new variant: * double wildcards `"**"` are rejected when used in the middle patterns, this is only allowed as the last matching segment in a pattern. * it is incompatible with some path matching options, like suffix-pattern, registered-suffix-pattern or configuring a Servlet prefix on the `DispatcherServlet` (`spring.mvc.servlet.path=/test`) This commit introduces two `FailureAnalyzer` implementations to guide developers when facing those issues. Closes gh-21694pull/22302/head
parent
2d91a096db
commit
0f264b68e8
@ -0,0 +1,43 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2012-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.boot.context.properties;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exception thrown when the application has configured an incompatible set of
|
||||||
|
* {@link ConfigurationProperties} keys.
|
||||||
|
*
|
||||||
|
* @author Brian Clozel
|
||||||
|
* @since 2.4.0
|
||||||
|
*/
|
||||||
|
public class IncompatibleConfigurationException extends RuntimeException {
|
||||||
|
|
||||||
|
private final List<String> incompatibleKeys;
|
||||||
|
|
||||||
|
public IncompatibleConfigurationException(String... incompatibleKeys) {
|
||||||
|
super("The following configuration properties have incompatible values: " + Arrays.toString(incompatibleKeys));
|
||||||
|
this.incompatibleKeys = Arrays.asList(incompatibleKeys);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Collection<String> getIncompatibleKeys() {
|
||||||
|
return this.incompatibleKeys;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,40 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2012-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.boot.diagnostics.analyzer;
|
||||||
|
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import org.springframework.boot.context.properties.IncompatibleConfigurationException;
|
||||||
|
import org.springframework.boot.diagnostics.AbstractFailureAnalyzer;
|
||||||
|
import org.springframework.boot.diagnostics.FailureAnalysis;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A {@code FailureAnalyzer} that performs analysis of failures caused by a
|
||||||
|
* {@code IncompatibleConfigurationException}.
|
||||||
|
*
|
||||||
|
* @author Brian Clozel
|
||||||
|
*/
|
||||||
|
class IncompatibleConfigurationFailureAnalyzer extends AbstractFailureAnalyzer<IncompatibleConfigurationException> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected FailureAnalysis analyze(Throwable rootFailure, IncompatibleConfigurationException cause) {
|
||||||
|
String action = String.format("Review the docs for %s and change the configured values.",
|
||||||
|
cause.getIncompatibleKeys().stream().collect(Collectors.joining(", ")));
|
||||||
|
return new FailureAnalysis(cause.getMessage(), action, cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,39 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2012-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.boot.diagnostics.analyzer;
|
||||||
|
|
||||||
|
import org.springframework.boot.diagnostics.AbstractFailureAnalyzer;
|
||||||
|
import org.springframework.boot.diagnostics.FailureAnalysis;
|
||||||
|
import org.springframework.web.util.pattern.PatternParseException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A {@code FailureAnalyzer} that performs analysis of failures caused by a
|
||||||
|
* {@code PatternParseException}.
|
||||||
|
*
|
||||||
|
* @author Brian Clozel
|
||||||
|
*/
|
||||||
|
class PatternParseFailureAnalyzer extends AbstractFailureAnalyzer<PatternParseException> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected FailureAnalysis analyze(Throwable rootFailure, PatternParseException cause) {
|
||||||
|
return new FailureAnalysis("Invalid mapping pattern detected: " + cause.toDetailedString(),
|
||||||
|
"Fix this pattern in your application or switch to the legacy parser implementation with "
|
||||||
|
+ "`spring.mvc.pathpattern.matching-strategy=ant_path_matcher`.",
|
||||||
|
cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,47 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2012-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.boot.diagnostics.analyzer;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import org.springframework.boot.context.properties.IncompatibleConfigurationException;
|
||||||
|
import org.springframework.boot.diagnostics.FailureAnalysis;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for {@link IncompatibleConfigurationFailureAnalyzer}
|
||||||
|
*
|
||||||
|
* @author Brian Clozel
|
||||||
|
*/
|
||||||
|
class IncompatibleConfigurationFailureAnalyzerTests {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void incompatibleConfigurationListsKeys() {
|
||||||
|
FailureAnalysis failureAnalysis = performAnalysis("spring.first.key", "spring.second.key");
|
||||||
|
assertThat(failureAnalysis.getDescription()).contains(
|
||||||
|
"The following configuration properties have incompatible values: [spring.first.key, spring.second.key]");
|
||||||
|
assertThat(failureAnalysis.getAction())
|
||||||
|
.contains("Review the docs for spring.first.key, spring.second.key and change the configured values.");
|
||||||
|
}
|
||||||
|
|
||||||
|
private FailureAnalysis performAnalysis(String... keys) {
|
||||||
|
IncompatibleConfigurationException failure = new IncompatibleConfigurationException(keys);
|
||||||
|
return new IncompatibleConfigurationFailureAnalyzer().analyze(failure);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,61 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2012-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.boot.diagnostics.analyzer;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import org.springframework.boot.diagnostics.FailureAnalysis;
|
||||||
|
import org.springframework.web.util.pattern.PathPatternParser;
|
||||||
|
import org.springframework.web.util.pattern.PatternParseException;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for {@link PatternParseFailureAnalyzer}
|
||||||
|
*
|
||||||
|
* @author Brian Clozel
|
||||||
|
*/
|
||||||
|
class PatternParseFailureAnalyzerTests {
|
||||||
|
|
||||||
|
private PathPatternParser parser = new PathPatternParser();
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void patternParseFailureQuotesPattern() {
|
||||||
|
FailureAnalysis failureAnalysis = performAnalysis("/spring/**/framework");
|
||||||
|
assertThat(failureAnalysis.getDescription()).contains("Invalid mapping pattern detected: /spring/**/framework");
|
||||||
|
assertThat(failureAnalysis.getAction())
|
||||||
|
.contains("Fix this pattern in your application or switch to the legacy parser"
|
||||||
|
+ " implementation with `spring.mvc.pathpattern.matching-strategy=ant_path_matcher`.");
|
||||||
|
}
|
||||||
|
|
||||||
|
private FailureAnalysis performAnalysis(String pattern) {
|
||||||
|
PatternParseException failure = createFailure(pattern);
|
||||||
|
assertThat(failure).isNotNull();
|
||||||
|
return new PatternParseFailureAnalyzer().analyze(failure);
|
||||||
|
}
|
||||||
|
|
||||||
|
PatternParseException createFailure(String pattern) {
|
||||||
|
try {
|
||||||
|
this.parser.parse(pattern);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
catch (PatternParseException ex) {
|
||||||
|
return ex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue