|
|
|
@ -23,7 +23,10 @@ import java.util.Collections;
@@ -23,7 +23,10 @@ import java.util.Collections;
|
|
|
|
|
import java.util.List; |
|
|
|
|
|
|
|
|
|
import jakarta.servlet.Filter; |
|
|
|
|
import jakarta.servlet.FilterChain; |
|
|
|
|
import jakarta.servlet.ServletException; |
|
|
|
|
import jakarta.servlet.ServletRequest; |
|
|
|
|
import jakarta.servlet.ServletResponse; |
|
|
|
|
import jakarta.servlet.http.HttpServlet; |
|
|
|
|
import jakarta.servlet.http.HttpServletRequest; |
|
|
|
|
import jakarta.servlet.http.HttpServletResponse; |
|
|
|
@ -42,12 +45,14 @@ import org.springframework.web.context.support.AnnotationConfigWebApplicationCon
@@ -42,12 +45,14 @@ import org.springframework.web.context.support.AnnotationConfigWebApplicationCon
|
|
|
|
|
import org.springframework.web.context.support.GenericWebApplicationContext; |
|
|
|
|
import org.springframework.web.context.support.StaticWebApplicationContext; |
|
|
|
|
import org.springframework.web.cors.CorsConfiguration; |
|
|
|
|
import org.springframework.web.cors.CorsConfigurationSource; |
|
|
|
|
import org.springframework.web.servlet.HandlerExecutionChain; |
|
|
|
|
import org.springframework.web.servlet.HandlerMapping; |
|
|
|
|
import org.springframework.web.servlet.function.RouterFunction; |
|
|
|
|
import org.springframework.web.servlet.function.RouterFunctions; |
|
|
|
|
import org.springframework.web.servlet.function.ServerResponse; |
|
|
|
|
import org.springframework.web.servlet.function.support.RouterFunctionMapping; |
|
|
|
|
import org.springframework.web.servlet.handler.HandlerMappingIntrospector.CachedResult; |
|
|
|
|
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; |
|
|
|
|
import org.springframework.web.testfixture.servlet.MockFilterChain; |
|
|
|
|
import org.springframework.web.testfixture.servlet.MockHttpServletRequest; |
|
|
|
@ -202,68 +207,67 @@ public class HandlerMappingIntrospectorTests {
@@ -202,68 +207,67 @@ public class HandlerMappingIntrospectorTests {
|
|
|
|
|
|
|
|
|
|
@Test |
|
|
|
|
void cacheFilter() throws Exception { |
|
|
|
|
testCacheFilter(new MockHttpServletRequest()); |
|
|
|
|
} |
|
|
|
|
CorsConfiguration corsConfig = new CorsConfiguration(); |
|
|
|
|
TestMatchableHandlerMapping mapping = new TestMatchableHandlerMapping(); |
|
|
|
|
mapping.registerHandler("/test", new TestHandler(corsConfig)); |
|
|
|
|
|
|
|
|
|
@Test |
|
|
|
|
void cacheFilterRestoresPreviousValues() throws Exception { |
|
|
|
|
TestMatchableHandlerMapping previousMapping = new TestMatchableHandlerMapping(); |
|
|
|
|
CorsConfiguration previousCorsConfig = new CorsConfiguration(); |
|
|
|
|
HandlerMappingIntrospector introspector = initIntrospector(mapping); |
|
|
|
|
|
|
|
|
|
MockHttpServletRequest request = new MockHttpServletRequest(); |
|
|
|
|
request.setAttribute(HandlerMappingIntrospector.MAPPING_ATTRIBUTE, previousMapping); |
|
|
|
|
request.setAttribute(HandlerMappingIntrospector.CORS_CONFIG_ATTRIBUTE, previousCorsConfig); |
|
|
|
|
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/test"); |
|
|
|
|
MockHttpServletResponse response = new MockHttpServletResponse(); |
|
|
|
|
|
|
|
|
|
MockFilterChain filterChain = new MockFilterChain( |
|
|
|
|
new TestServlet(), new CacheResultFilter(introspector), new AuthFilter(introspector, corsConfig)); |
|
|
|
|
|
|
|
|
|
testCacheFilter(request); |
|
|
|
|
filterChain.doFilter(request, response); |
|
|
|
|
|
|
|
|
|
assertThat(previousMapping.getInvocationCount()).isEqualTo(0); |
|
|
|
|
assertThat(request.getAttribute(HandlerMappingIntrospector.MAPPING_ATTRIBUTE)).isSameAs(previousMapping); |
|
|
|
|
assertThat(request.getAttribute(HandlerMappingIntrospector.CORS_CONFIG_ATTRIBUTE)).isSameAs(previousCorsConfig); |
|
|
|
|
assertThat(response.getContentAsString()).isEqualTo("Success"); |
|
|
|
|
assertThat(mapping.getInvocationCount()).isEqualTo(1); |
|
|
|
|
assertThat(mapping.getMatchCount()).isEqualTo(1); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private void testCacheFilter(MockHttpServletRequest request) throws IOException, ServletException { |
|
|
|
|
TestMatchableHandlerMapping mapping = new TestMatchableHandlerMapping(); |
|
|
|
|
StaticWebApplicationContext context = new StaticWebApplicationContext(); |
|
|
|
|
context.registerBean(TestMatchableHandlerMapping.class, () -> mapping); |
|
|
|
|
context.refresh(); |
|
|
|
|
@Test |
|
|
|
|
void cacheFilterWithNestedDispatch() throws Exception { |
|
|
|
|
CorsConfiguration corsConfig1 = new CorsConfiguration(); |
|
|
|
|
CorsConfiguration corsConfig2 = new CorsConfiguration(); |
|
|
|
|
|
|
|
|
|
HandlerMappingIntrospector introspector = initIntrospector(context); |
|
|
|
|
MockHttpServletResponse response = new MockHttpServletResponse(); |
|
|
|
|
TestMatchableHandlerMapping mapping1 = new TestMatchableHandlerMapping(); |
|
|
|
|
TestMatchableHandlerMapping mapping2 = new TestMatchableHandlerMapping(); |
|
|
|
|
|
|
|
|
|
Filter filter = (req, res, chain) -> { |
|
|
|
|
try { |
|
|
|
|
for (int i = 0; i < 10; i++) { |
|
|
|
|
introspector.getMatchableHandlerMapping((HttpServletRequest) req); |
|
|
|
|
introspector.getCorsConfiguration((HttpServletRequest) req); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
catch (Exception ex) { |
|
|
|
|
throw new IllegalStateException(ex); |
|
|
|
|
} |
|
|
|
|
chain.doFilter(req, res); |
|
|
|
|
}; |
|
|
|
|
mapping1.registerHandler("/1", new TestHandler(corsConfig1)); |
|
|
|
|
mapping2.registerHandler("/2", new TestHandler(corsConfig2)); |
|
|
|
|
|
|
|
|
|
HttpServlet servlet = new HttpServlet() { |
|
|
|
|
HandlerMappingIntrospector introspector = initIntrospector(mapping1, mapping2); |
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
|
protected void service(HttpServletRequest req, HttpServletResponse res) { |
|
|
|
|
try { |
|
|
|
|
res.getWriter().print("Success"); |
|
|
|
|
} |
|
|
|
|
catch (Exception ex) { |
|
|
|
|
throw new IllegalStateException(ex); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
}; |
|
|
|
|
MockFilterChain filterChain = new MockFilterChain( |
|
|
|
|
new TestServlet(), |
|
|
|
|
new CacheResultFilter(introspector), |
|
|
|
|
new AuthFilter(introspector, corsConfig1), |
|
|
|
|
(req, res, chain) -> chain.doFilter(new MockHttpServletRequest("GET", "/2"), res), |
|
|
|
|
new CacheResultFilter(introspector), |
|
|
|
|
new AuthFilter(introspector, corsConfig2)); |
|
|
|
|
|
|
|
|
|
new MockFilterChain(servlet, introspector.createCacheFilter(), filter) |
|
|
|
|
.doFilter(request, response); |
|
|
|
|
MockHttpServletResponse response = new MockHttpServletResponse(); |
|
|
|
|
filterChain.doFilter(new MockHttpServletRequest("GET", "/1"), response); |
|
|
|
|
|
|
|
|
|
assertThat(response.getContentAsString()).isEqualTo("Success"); |
|
|
|
|
assertThat(mapping.getInvocationCount()).isEqualTo(1); |
|
|
|
|
assertThat(mapping1.getInvocationCount()).isEqualTo(2); |
|
|
|
|
assertThat(mapping2.getInvocationCount()).isEqualTo(1); |
|
|
|
|
assertThat(mapping1.getMatchCount()).isEqualTo(1); |
|
|
|
|
assertThat(mapping2.getMatchCount()).isEqualTo(1); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private HandlerMappingIntrospector initIntrospector(WebApplicationContext context) { |
|
|
|
|
private HandlerMappingIntrospector initIntrospector(TestMatchableHandlerMapping... mappings) { |
|
|
|
|
StaticWebApplicationContext context = new StaticWebApplicationContext(); |
|
|
|
|
int index = 0; |
|
|
|
|
for (TestMatchableHandlerMapping mapping : mappings) { |
|
|
|
|
context.registerBean("mapping" + index++, TestMatchableHandlerMapping.class, () -> mapping); |
|
|
|
|
} |
|
|
|
|
context.refresh(); |
|
|
|
|
return initIntrospector(context); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private static HandlerMappingIntrospector initIntrospector(WebApplicationContext context) { |
|
|
|
|
HandlerMappingIntrospector introspector = new HandlerMappingIntrospector(); |
|
|
|
|
introspector.setApplicationContext(context); |
|
|
|
|
introspector.afterPropertiesSet(); |
|
|
|
@ -327,23 +331,111 @@ public class HandlerMappingIntrospectorTests {
@@ -327,23 +331,111 @@ public class HandlerMappingIntrospectorTests {
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private static class TestMatchableHandlerMapping implements MatchableHandlerMapping { |
|
|
|
|
private static class TestMatchableHandlerMapping extends SimpleUrlHandlerMapping { |
|
|
|
|
|
|
|
|
|
private int invocationCount; |
|
|
|
|
|
|
|
|
|
private int matchCount; |
|
|
|
|
|
|
|
|
|
public int getInvocationCount() { |
|
|
|
|
return this.invocationCount; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public int getMatchCount() { |
|
|
|
|
return this.matchCount; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
|
public HandlerExecutionChain getHandler(HttpServletRequest request) { |
|
|
|
|
protected Object getHandlerInternal(HttpServletRequest request) throws Exception { |
|
|
|
|
this.invocationCount++; |
|
|
|
|
return new HandlerExecutionChain(new Object()); |
|
|
|
|
Object handler = super.getHandlerInternal(request); |
|
|
|
|
if (handler != null) { |
|
|
|
|
this.matchCount++; |
|
|
|
|
} |
|
|
|
|
return handler; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private static class TestHandler implements CorsConfigurationSource { |
|
|
|
|
|
|
|
|
|
private final CorsConfiguration corsConfig; |
|
|
|
|
|
|
|
|
|
private TestHandler(CorsConfiguration corsConfig) { |
|
|
|
|
this.corsConfig = corsConfig; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
|
public RequestMatchResult match(HttpServletRequest request, String pattern) { |
|
|
|
|
throw new UnsupportedOperationException(); |
|
|
|
|
public CorsConfiguration getCorsConfiguration(HttpServletRequest request) { |
|
|
|
|
return this.corsConfig; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private static class CacheResultFilter implements Filter { |
|
|
|
|
|
|
|
|
|
private final HandlerMappingIntrospector introspector; |
|
|
|
|
|
|
|
|
|
private CacheResultFilter(HandlerMappingIntrospector introspector) { |
|
|
|
|
this.introspector = introspector; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
|
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) |
|
|
|
|
throws ServletException { |
|
|
|
|
|
|
|
|
|
CachedResult previousValue = this.introspector.setCache((HttpServletRequest) req); |
|
|
|
|
try { |
|
|
|
|
chain.doFilter(req, res); |
|
|
|
|
} |
|
|
|
|
catch (Exception ex) { |
|
|
|
|
throw new ServletException("HandlerMapping introspection failed", ex); |
|
|
|
|
} |
|
|
|
|
finally { |
|
|
|
|
this.introspector.resetCache(req, previousValue); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private static class AuthFilter implements Filter { |
|
|
|
|
|
|
|
|
|
private final HandlerMappingIntrospector introspector; |
|
|
|
|
|
|
|
|
|
private final CorsConfiguration corsConfig; |
|
|
|
|
|
|
|
|
|
private AuthFilter(HandlerMappingIntrospector introspector, CorsConfiguration corsConfig) { |
|
|
|
|
this.introspector = introspector; |
|
|
|
|
this.corsConfig = corsConfig; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
|
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { |
|
|
|
|
try { |
|
|
|
|
for (int i = 0; i < 10; i++) { |
|
|
|
|
HttpServletRequest httpRequest = (HttpServletRequest) req; |
|
|
|
|
assertThat(introspector.getMatchableHandlerMapping(httpRequest)).isNotNull(); |
|
|
|
|
assertThat(introspector.getCorsConfiguration(httpRequest)).isSameAs(corsConfig); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
catch (Exception ex) { |
|
|
|
|
throw new IllegalStateException(ex); |
|
|
|
|
} |
|
|
|
|
chain.doFilter(req, res); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private static class TestServlet extends HttpServlet { |
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
|
protected void service(HttpServletRequest req, HttpServletResponse res) { |
|
|
|
|
try { |
|
|
|
|
res.getWriter().print("Success"); |
|
|
|
|
} |
|
|
|
|
catch (Exception ex) { |
|
|
|
|
throw new IllegalStateException(ex); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|