diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractUrlHandlerMapping.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractUrlHandlerMapping.java index 9fc500c959..7a9af15f8c 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractUrlHandlerMapping.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractUrlHandlerMapping.java @@ -72,6 +72,14 @@ public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping i private final Map pathPatternHandlerMap = new LinkedHashMap<>(); + /** + * Handler for "/*", to be checked after all other handlers. + * Effectively similar to {@link #getDefaultHandler}, but processed at our level, + * within {@link #getHandlerInternal} where the request is available. + * @see org.springframework.web.socket.server.support.WebSocketHandlerMapping + */ + private @Nullable Object wildcardHandler; + @Override public void setPatternParser(@Nullable PathPatternParser patternParser) { @@ -166,9 +174,9 @@ public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping i } else if (urlPath.equals("/*")) { if (logger.isTraceEnabled()) { - logger.trace("Default mapping to " + getHandlerDescription(handler)); + logger.trace("Wildcard mapping to " + getHandlerDescription(handler)); } - setDefaultHandler(resolvedHandler); + this.wildcardHandler = resolvedHandler; } else { this.handlerMap.put(urlPath, resolvedHandler); @@ -197,9 +205,9 @@ public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping i } else if (urlPath.equals("/*")) { if (logger.isTraceEnabled()) { - logger.trace("Removing default mapping: " + getDefaultHandler()); + logger.trace("Removing wildcard mapping: " + getDefaultHandler()); } - setDefaultHandler(null); + this.wildcardHandler = null; } else { Object mappedHandler = this.handlerMap.get(urlPath); @@ -248,6 +256,9 @@ public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping i if (StringUtils.matchesCharacter(lookupPath, '/')) { rawHandler = getRootHandler(); } + if (rawHandler == null) { + rawHandler = this.wildcardHandler; + } if (rawHandler == null) { rawHandler = getDefaultHandler(); } diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/handler/AbstractUrlHandlerMappingTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/handler/AbstractUrlHandlerMappingTests.java index c4d0fdcaa7..ee84dc3dd5 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/handler/AbstractUrlHandlerMappingTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/handler/AbstractUrlHandlerMappingTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 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 org.jspecify.annotations.Nullable; import org.junit.jupiter.api.Test; import org.springframework.context.support.StaticApplicationContext; +import org.springframework.web.testfixture.servlet.MockHttpServletRequest; import static java.util.Map.entry; import static org.assertj.core.api.Assertions.assertThat; @@ -45,10 +46,11 @@ class AbstractUrlHandlerMappingTests { } @Test - void registerDefaultHandler() { - TestController defaultHandler = new TestController(); - mapping.registerHandler("/*", defaultHandler); - assertThat(mapping).satisfies(hasMappings(null, defaultHandler, Map.of())); + void registerWildcardHandler() throws Exception { + TestController handler = new TestController(); + mapping.registerHandler("/*", handler); + assertThat(mapping).satisfies(hasMappings(null, null, Map.of())); + assertThat(getHandlerForPath("/abc")).isNotNull(); } @Test @@ -70,43 +72,41 @@ class AbstractUrlHandlerMappingTests { @Test void unregisterRootHandler() { TestController rootHandler = new TestController(); - TestController defaultHandler = new TestController(); TestController specificHandler = new TestController(); mapping.registerHandler("/", rootHandler); - mapping.registerHandler("/*", defaultHandler); mapping.registerHandler("/test", specificHandler); - assertThat(mapping).satisfies(hasMappings(rootHandler, defaultHandler, Map.of("/test", specificHandler))); + assertThat(mapping).satisfies(hasMappings(rootHandler, null, Map.of("/test", specificHandler))); mapping.unregisterHandler("/"); - assertThat(mapping).satisfies(hasMappings(null, defaultHandler, Map.of("/test", specificHandler))); + assertThat(mapping).satisfies(hasMappings(null, null, Map.of("/test", specificHandler))); } @Test - void unregisterDefaultHandler() { - TestController rootHandler = new TestController(); - TestController defaultHandler = new TestController(); + void unregisterDefaultHandler() throws Exception { + TestController wildcardHandler = new TestController(); TestController specificHandler = new TestController(); - mapping.registerHandler("/", rootHandler); - mapping.registerHandler("/*", defaultHandler); + mapping.registerHandler("/*", wildcardHandler); mapping.registerHandler("/test", specificHandler); - assertThat(mapping).satisfies(hasMappings(rootHandler, defaultHandler, Map.of("/test", specificHandler))); + assertThat(mapping).satisfies(hasMappings(null, null, Map.of("/test", specificHandler))); + assertThat(getHandlerForPath("/abc")).isNotNull(); mapping.unregisterHandler("/*"); - assertThat(mapping).satisfies(hasMappings(rootHandler, null, Map.of("/test", specificHandler))); + assertThat(mapping).satisfies(hasMappings(null, null, Map.of("/test", specificHandler))); + assertThat(getHandlerForPath("/abc")).isNull(); } @Test void unregisterSpecificHandler() { TestController rootHandler = new TestController(); - TestController defaultHandler = new TestController(); + TestController wildcardHandler = new TestController(); TestController specificHandler = new TestController(); mapping.registerHandler("/", rootHandler); - mapping.registerHandler("/*", defaultHandler); + mapping.registerHandler("/*", wildcardHandler); mapping.registerHandler("/test", specificHandler); - assertThat(mapping).satisfies(hasMappings(rootHandler, defaultHandler, Map.of("/test", specificHandler))); + assertThat(mapping).satisfies(hasMappings(rootHandler, null, Map.of("/test", specificHandler))); mapping.unregisterHandler("/test"); - assertThat(mapping).satisfies(hasMappings(rootHandler, defaultHandler, Map.of())); + assertThat(mapping).satisfies(hasMappings(rootHandler, null, Map.of())); } @Test @@ -129,8 +129,9 @@ class AbstractUrlHandlerMappingTests { } - private Consumer hasMappings(@Nullable Object rootHandler, - @Nullable Object defaultHandler, Map handlerMap) { + private Consumer hasMappings( + @Nullable Object rootHandler, @Nullable Object defaultHandler, Map handlerMap) { + return actual -> { assertThat(actual.getRootHandler()).isEqualTo(rootHandler); assertThat(actual.getDefaultHandler()).isEqualTo(defaultHandler); @@ -138,6 +139,12 @@ class AbstractUrlHandlerMappingTests { }; } + private Object getHandlerForPath(String path) throws Exception { + MockHttpServletRequest request = new MockHttpServletRequest("GET", path); + return mapping.getHandlerInternal(request); + } + + private static class TestController {} } diff --git a/spring-websocket/src/test/java/org/springframework/web/socket/server/support/WebSocketHandlerMappingTests.java b/spring-websocket/src/test/java/org/springframework/web/socket/server/support/WebSocketHandlerMappingTests.java index d7d76f68fb..b3e0e0fef5 100644 --- a/spring-websocket/src/test/java/org/springframework/web/socket/server/support/WebSocketHandlerMappingTests.java +++ b/spring-websocket/src/test/java/org/springframework/web/socket/server/support/WebSocketHandlerMappingTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 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. @@ -16,7 +16,7 @@ package org.springframework.web.socket.server.support; -import java.util.Collections; +import java.util.Map; import org.junit.jupiter.api.Test; @@ -40,7 +40,7 @@ class WebSocketHandlerMappingTests { HttpRequestHandler handler = new WebSocketHttpRequestHandler(mock()); WebSocketHandlerMapping mapping = new WebSocketHandlerMapping(); - mapping.setUrlMap(Collections.singletonMap("/path", handler)); + mapping.setUrlMap(Map.of("/path", handler)); mapping.setApplicationContext(new StaticWebApplicationContext()); MockHttpServletRequest request = new MockHttpServletRequest("GET", "/path"); @@ -66,4 +66,21 @@ class WebSocketHandlerMappingTests { assertThat(chain).isNull(); } + @Test // gh-34503 + void defaultHandler() throws Exception { + HttpRequestHandler handler = new WebSocketHttpRequestHandler(mock()); + + WebSocketHandlerMapping mapping = new WebSocketHandlerMapping(); + mapping.setUrlMap(Map.of("/*", handler)); + mapping.setApplicationContext(new StaticWebApplicationContext()); + + assertThat(mapping.getDefaultHandler()).isNull(); + + MockHttpServletRequest request = new MockHttpServletRequest("GET", "/path"); + HandlerExecutionChain chain = mapping.getHandler(request); + + assertThat(chain).isNotNull(); + assertThat(chain.getHandler()).isSameAs(handler); + } + }