Introduce PathPatternParser for optimized path matching

This commit introduces a PathPatternParser which parses request pattern
strings into PathPattern objects which can then be used to fast
match incoming string paths. The parser and matching supports the syntax
as described in SPR-14544. The code is optimized around the common usages
of request patterns and is designed to create very little transient
garbage when matching.

Issue: SPR-14544
This commit is contained in:
Andy Clement
2016-10-11 15:14:08 -07:00
committed by Brian Clozel
parent 6f029392c7
commit f58ffad939
44 changed files with 3880 additions and 73 deletions

View File

@@ -37,6 +37,7 @@ import org.springframework.web.servlet.handler.MappedInterceptor;
import org.springframework.web.servlet.handler.WebRequestHandlerInterceptorAdapter;
import org.springframework.web.servlet.i18n.LocaleChangeInterceptor;
import org.springframework.web.servlet.theme.ThemeChangeInterceptor;
import org.springframework.web.util.ParsingPathMatcher;
import static org.junit.Assert.*;
@@ -152,7 +153,7 @@ public class InterceptorRegistryTests {
private List<HandlerInterceptor> getInterceptorsForPath(String lookupPath) {
PathMatcher pathMatcher = new AntPathMatcher();
PathMatcher pathMatcher = new ParsingPathMatcher();
List<HandlerInterceptor> result = new ArrayList<>();
for (Object interceptor : this.registry.getInterceptors()) {
if (interceptor instanceof MappedInterceptor) {

View File

@@ -83,6 +83,7 @@ import org.springframework.web.servlet.resource.ResourceUrlProviderExposingInter
import org.springframework.web.servlet.view.BeanNameViewResolver;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
import org.springframework.web.servlet.view.ViewResolverComposite;
import org.springframework.web.util.ParsingPathMatcher;
import org.springframework.web.util.UrlPathHelper;
import static com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES;
@@ -320,7 +321,7 @@ public class WebMvcConfigurationSupportTests {
assertNotNull(urlPathHelper);
assertNotNull(pathMatcher);
assertEquals(AntPathMatcher.class, pathMatcher.getClass());
assertEquals(ParsingPathMatcher.class, pathMatcher.getClass());
}
private ApplicationContext initContext(Class<?>... configClasses) {

View File

@@ -38,6 +38,7 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.context.support.StaticWebApplicationContext;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.util.ParsingPathMatcher;
import org.springframework.web.util.UrlPathHelper;
@@ -244,7 +245,7 @@ public class HandlerMethodMappingTests {
private UrlPathHelper pathHelper = new UrlPathHelper();
private PathMatcher pathMatcher = new AntPathMatcher();
private PathMatcher pathMatcher = new ParsingPathMatcher();
public MyHandlerMethodMapping() {

View File

@@ -81,7 +81,7 @@ public class PathMatchingUrlHandlerMappingTests {
HandlerExecutionChain hec = getHandler(req);
assertTrue("Handler is null", hec != null);
assertTrue("Handler is correct bean", hec.getHandler() == bean);
assertEquals("pathmatchingTest.html", req.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE));
assertEquals("/pathmatchingTest.html", req.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE));
// no match, no forward slash included
req = new MockHttpServletRequest("GET", "welcome.html");
@@ -121,11 +121,6 @@ public class PathMatchingUrlHandlerMappingTests {
hec = getHandler(req);
assertTrue("Handler is correct bean", hec != null && hec.getHandler() == bean);
// this as well, because there's a **/in there as well
req = new MockHttpServletRequest("GET", "/testing/bla.jsp");
hec = getHandler(req);
assertTrue("Handler is correct bean", hec != null && hec.getHandler() == bean);
// should match because exact pattern is there
req = new MockHttpServletRequest("GET", "/administrator/another/bla.xml");
hec = getHandler(req);

View File

@@ -23,11 +23,11 @@ import static org.junit.Assert.*;
import org.springframework.mock.web.test.MockHttpServletRequest;
import org.springframework.mock.web.test.MockHttpServletResponse;
import org.springframework.ui.ModelMap;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.PathMatcher;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.util.ParsingPathMatcher;
/**
* @author Juergen Hoeller
@@ -36,7 +36,7 @@ import org.springframework.web.servlet.ModelAndView;
*/
public class UrlFilenameViewControllerTests {
private PathMatcher pathMatcher = new AntPathMatcher();
private PathMatcher pathMatcher = new ParsingPathMatcher();
@Test

View File

@@ -62,19 +62,21 @@ public class WebContentInterceptorTests {
@Test
public void mappedCacheConfigurationOverridesGlobal() throws Exception {
Properties mappings = new Properties();
mappings.setProperty("**/*handle.vm", "-1");
mappings.setProperty("*/*handle.vm", "-1"); // was **/*handle.vm
WebContentInterceptor interceptor = new WebContentInterceptor();
interceptor.setCacheSeconds(10);
interceptor.setCacheMappings(mappings);
request.setRequestURI("http://localhost:7070/example/adminhandle.vm");
// request.setRequestURI("http://localhost:7070/example/adminhandle.vm");
request.setRequestURI("example/adminhandle.vm");
interceptor.preHandle(request, response, null);
Iterable<String> cacheControlHeaders = response.getHeaders("Cache-Control");
assertThat(cacheControlHeaders, Matchers.emptyIterable());
request.setRequestURI("http://localhost:7070/example/bingo.html");
// request.setRequestURI("http://localhost:7070/example/bingo.html");
request.setRequestURI("example/bingo.html");
interceptor.preHandle(request, response, null);
cacheControlHeaders = response.getHeaders("Cache-Control");
@@ -143,10 +145,11 @@ public class WebContentInterceptorTests {
interceptor.setUseExpiresHeader(true);
interceptor.setAlwaysMustRevalidate(true);
Properties mappings = new Properties();
mappings.setProperty("**/*.cache.html", "10");
mappings.setProperty("*/*.cache.html", "10"); // was **/*.cache.html
interceptor.setCacheMappings(mappings);
request.setRequestURI("http://example.org/foo/page.html");
// request.setRequestURI("http://example.org/foo/page.html");
request.setRequestURI("foo/page.html");
interceptor.preHandle(request, response, null);
Iterable<String> expiresHeaders = response.getHeaders("Expires");
@@ -157,7 +160,8 @@ public class WebContentInterceptorTests {
assertThat(pragmaHeaders, Matchers.contains("no-cache"));
response = new MockHttpServletResponse();
request.setRequestURI("http://example.org/page.cache.html");
// request.setRequestURI("http://example.org/page.cache.html");
request.setRequestURI("foo/page.cache.html");
interceptor.preHandle(request, response, null);
expiresHeaders = response.getHeaders("Expires");

View File

@@ -2364,22 +2364,22 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl
@Controller
static class MyRelativeMethodPathDispatchingController {
@RequestMapping("**/myHandle")
@RequestMapping("*/myHandle") // was **/myHandle
public void myHandle(HttpServletResponse response) throws IOException {
response.getWriter().write("myView");
}
@RequestMapping("/**/*Other")
@RequestMapping("/*/*Other") // was /**/*Other
public void myOtherHandle(HttpServletResponse response) throws IOException {
response.getWriter().write("myOtherView");
}
@RequestMapping("**/myLang")
@RequestMapping("*/myLang") // was **/myLang
public void myLangHandle(HttpServletResponse response) throws IOException {
response.getWriter().write("myLangView");
}
@RequestMapping("/**/surprise")
@RequestMapping("/*/surprise") // was /**/surprise
public void mySurpriseHandle(HttpServletResponse response) throws IOException {
response.getWriter().write("mySurpriseView");
}
@@ -2643,7 +2643,7 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl
@Controller
public static class PathOrderingController {
@RequestMapping(value = {"/dir/myPath1.do", "/**/*.do"})
@RequestMapping(value = {"/dir/myPath1.do", "/*/*.do"})
public void method1(Writer writer) throws IOException {
writer.write("method1");
}

View File

@@ -571,14 +571,14 @@ public class UriTemplateServletAnnotationControllerHandlerMethodTests extends Ab
@RequestMapping("/category")
public static class MultiPathController {
@RequestMapping(value = {"/{category}/page/{page}", "/**/{category}/page/{page}"})
@RequestMapping(value = {"/{category}/page/{page}", "/*/{category}/page/{page}"})
public void category(@PathVariable String category, @PathVariable int page, Writer writer) throws IOException {
writer.write("handle1-");
writer.write("category-" + category);
writer.write("page-" + page);
}
@RequestMapping(value = {"/{category}", "/**/{category}"})
@RequestMapping(value = {"/{category}", "/*/{category}"})
public void category(@PathVariable String category, Writer writer) throws IOException {
writer.write("handle2-");
writer.write("category-" + category);
@@ -598,7 +598,7 @@ public class UriTemplateServletAnnotationControllerHandlerMethodTests extends Ab
}
@Controller
@RequestMapping("/*/menu/**")
@RequestMapping("/*/menu/") // was /*/menu/**
public static class MenuTreeController {
@RequestMapping("type/{var}")