@ -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 . do Filter( request , response ) ;
assertThat ( previousMapping . getInvocation Cou nt ( ) ) . 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 Matchable HandlerMapping {
private static class TestMatchableHandlerMapping extends SimpleUrl HandlerMapping {
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 ) ;
}
}
}