Polishing contribution

See gh-37272
This commit is contained in:
Brian Clozel
2026-09-18 17:21:08 +02:00
parent f9f9186478
commit f6e53b76ae
5 changed files with 96 additions and 4 deletions
@@ -221,6 +221,7 @@ public class RouterFunctionMapping extends AbstractHandlerMapping implements Ini
if (matchingPattern != null) {
servletRequest.removeAttribute(RouterFunctions.MATCHING_PATTERN_ATTRIBUTE);
servletRequest.setAttribute(BEST_MATCHING_PATTERN_ATTRIBUTE, matchingPattern.getPatternString());
servletRequest.setAttribute(BEST_MATCHING_PATH_PATTERN_ATTRIBUTE, matchingPattern);
ServerHttpObservationFilter.findObservationContext(request.servletRequest())
.ifPresent(context -> context.setPathPattern(matchingPattern.getPatternString()));
}
@@ -319,7 +319,7 @@ public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping {
pathWithinMapping = UrlPathHelper.defaultInstance.removeSemicolonContent(pathWithinMapping);
PathPattern.PathMatchInfo pathMatchInfo = pattern.matchAndExtract(path);
Map<String, String> uriVariables = (pathMatchInfo != null ? pathMatchInfo.getUriVariables(): null);
return buildPathExposingHandler(handler, pattern.getPatternString(), pathWithinMapping, uriVariables);
return buildPathExposingHandler(handler, pattern, pathWithinMapping, uriVariables);
}
/**
@@ -419,12 +419,14 @@ public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping {
/**
* Build a handler object for the given raw handler, exposing the actual
* handler, the {@link #PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE}, as well as
* handler, the {@link #PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE},
* the {@link #BEST_MATCHING_PATTERN_ATTRIBUTE}, as well as
* the {@link #URI_TEMPLATE_VARIABLES_ATTRIBUTE} before executing the handler.
* <p>The default implementation builds a {@link HandlerExecutionChain}
* with a special interceptor that exposes the path attribute and URI
* template variables
* @param rawHandler the raw handler to expose
* @param bestMatchingPattern the {@linkplain HandlerMapping#BEST_MATCHING_PATTERN_ATTRIBUTE best matching pattern}
* @param pathWithinMapping the path to expose before executing the handler
* @param uriTemplateVariables the URI template variables, can be {@code null} if no variables found
* @return the final handler object
@@ -440,8 +442,33 @@ public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping {
return chain;
}
/**
* Build a handler object for the given raw handler, exposing the actual
* handler, the {@link #PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE},
* the {@link #BEST_MATCHING_PATH_PATTERN_ATTRIBUTE}
* the {@link #BEST_MATCHING_PATTERN_ATTRIBUTE}, as well as
* the {@link #URI_TEMPLATE_VARIABLES_ATTRIBUTE} before executing the handler.
* @param rawHandler the raw handler to expose
* @param bestMatchingPattern the {@linkplain HandlerMapping#BEST_MATCHING_PATH_PATTERN_ATTRIBUTE best matching pattern}
* @param pathWithinMapping the path to expose before executing the handler
* @param uriTemplateVariables the URI template variables, can be {@code null} if no variables found
* @return the final handler object
* @since 7.1
*/
protected Object buildPathExposingHandler(Object rawHandler, PathPattern bestMatchingPattern,
String pathWithinMapping, @Nullable Map<String, String> uriTemplateVariables) {
HandlerExecutionChain chain = new HandlerExecutionChain(rawHandler);
chain.addInterceptor(new PathExposingHandlerInterceptor(bestMatchingPattern, pathWithinMapping));
if (!CollectionUtils.isEmpty(uriTemplateVariables)) {
chain.addInterceptor(new UriTemplateVariablesHandlerInterceptor(uriTemplateVariables));
}
return chain;
}
/**
* Expose the path within the current mapping as request attribute.
* @param bestMatchingPattern the best matching pattern
* @param pathWithinMapping the path within the current mapping
* @param request the request to expose the path to
* @see #PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE
@@ -455,6 +482,22 @@ public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping {
request.setAttribute(PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, pathWithinMapping);
}
/**
* Expose the path within the current mapping as request attribute.
* @param bestMatchingPattern the best matching pattern
* @param pathWithinMapping the path within the current mapping
* @param request the request to expose the path to
* @since 7.1
* @see #PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE
* @see #BEST_MATCHING_PATH_PATTERN_ATTRIBUTE
*/
protected void exposePathWithinMapping(PathPattern bestMatchingPattern, String pathWithinMapping,
HttpServletRequest request) {
request.setAttribute(BEST_MATCHING_PATH_PATTERN_ATTRIBUTE, bestMatchingPattern);
exposePathWithinMapping(bestMatchingPattern.getPatternString(), pathWithinMapping, request);
}
/**
* Expose the URI templates variables as request attribute.
* @param uriTemplateVariables the URI template variables
@@ -495,23 +538,39 @@ public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping {
/**
* Special interceptor for exposing the
* {@link AbstractUrlHandlerMapping#PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE} attribute.
* {@link AbstractUrlHandlerMapping#PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE} and,
* when parsed patterns are in use, the
* {@link AbstractUrlHandlerMapping#BEST_MATCHING_PATH_PATTERN_ATTRIBUTE} attribute.
* @see AbstractUrlHandlerMapping#exposePathWithinMapping
*/
private class PathExposingHandlerInterceptor implements HandlerInterceptor {
private final String bestMatchingPattern;
private final @Nullable PathPattern bestMatchingPathPattern;
private final String pathWithinMapping;
public PathExposingHandlerInterceptor(String bestMatchingPattern, String pathWithinMapping) {
this.bestMatchingPattern = bestMatchingPattern;
this.bestMatchingPathPattern = null;
this.pathWithinMapping = pathWithinMapping;
}
public PathExposingHandlerInterceptor(PathPattern bestMatchingPattern, String pathWithinMapping) {
this.bestMatchingPattern = bestMatchingPattern.getPatternString();
this.bestMatchingPathPattern = bestMatchingPattern;
this.pathWithinMapping = pathWithinMapping;
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
exposePathWithinMapping(this.bestMatchingPattern, this.pathWithinMapping, request);
if (this.bestMatchingPathPattern != null) {
exposePathWithinMapping(this.bestMatchingPathPattern, this.pathWithinMapping, request);
}
else {
exposePathWithinMapping(this.bestMatchingPattern, this.pathWithinMapping, request);
}
request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, handler);
request.setAttribute(INTROSPECT_TYPE_LEVEL_MAPPING, supportsTypeLevelMappings());
return true;
@@ -37,6 +37,7 @@ import org.springframework.web.servlet.function.ServerResponse;
import org.springframework.web.testfixture.servlet.MockHttpServletRequest;
import org.springframework.web.testfixture.servlet.MockHttpServletResponse;
import org.springframework.web.util.ServletRequestPathUtils;
import org.springframework.web.util.pattern.PathPattern;
import org.springframework.web.util.pattern.PathPatternParser;
import static org.assertj.core.api.Assertions.assertThat;
@@ -174,6 +175,9 @@ class RouterFunctionMappingTests {
assertThat(result).isNotNull();
assertThat(request.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE)).isEqualTo("/match");
assertThat(request.getAttribute(HandlerMapping.BEST_MATCHING_PATH_PATTERN_ATTRIBUTE))
.isInstanceOfSatisfying(PathPattern.class,
pattern -> assertThat(pattern.getPatternString()).isEqualTo("/match"));
assertThat(ServerHttpObservationFilter.findObservationContext(request))
.hasValueSatisfying(context -> assertThat(context.getPathPattern()).isEqualTo("/match"));
assertThat(request.getAttribute(HandlerMapping.BEST_MATCHING_HANDLER_ATTRIBUTE)).isEqualTo(handlerFunction);
@@ -42,12 +42,15 @@ import org.springframework.web.servlet.view.RedirectView;
import org.springframework.web.testfixture.servlet.MockHttpServletRequest;
import org.springframework.web.util.UrlPathHelper;
import org.springframework.web.util.WebUtils;
import org.springframework.web.util.pattern.PathPattern;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.junit.jupiter.api.Named.named;
import static org.junit.jupiter.params.provider.Arguments.arguments;
import static org.springframework.web.servlet.HandlerMapping.BEST_MATCHING_HANDLER_ATTRIBUTE;
import static org.springframework.web.servlet.HandlerMapping.BEST_MATCHING_PATH_PATTERN_ATTRIBUTE;
import static org.springframework.web.servlet.HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE;
import static org.springframework.web.servlet.HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE;
import static org.springframework.web.servlet.HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE;
@@ -99,6 +102,30 @@ class SimpleUrlHandlerMappingTests {
assertThat(request.getAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE)).isEqualTo(mainController);
}
@HandlerMappingsTest
void resolveBestMatchingPathPatternAttribute(SimpleUrlHandlerMapping handlerMapping) throws Exception {
StaticApplicationContext applicationContext = new StaticApplicationContext();
applicationContext.registerSingleton("mainController", Object.class);
Object mainController = applicationContext.getBean("mainController");
handlerMapping.setUrlMap(Map.of("/welcome*", "mainController"));
handlerMapping.setApplicationContext(applicationContext);
boolean usePathPatterns = handlerMapping.getPatternParser() != null;
MockHttpServletRequest request = PathPatternsTestUtils.initRequest("GET", "/welcome.x", usePathPatterns);
HandlerExecutionChain chain = getHandler(handlerMapping, request);
assertThat(chain.getHandler()).isSameAs(mainController);
assertThat(request.getAttribute(BEST_MATCHING_PATTERN_ATTRIBUTE)).isEqualTo("/welcome*");
if (usePathPatterns) {
assertThat(request.getAttribute(BEST_MATCHING_PATH_PATTERN_ATTRIBUTE))
.isInstanceOfSatisfying(PathPattern.class,
pattern -> assertThat(pattern.getPatternString()).isEqualTo("/welcome*"));
}
else {
assertThat(request.getAttribute(BEST_MATCHING_PATH_PATTERN_ATTRIBUTE)).isNull();
}
}
@HandlerMappingsTest
void resolvePatternFromMap(SimpleUrlHandlerMapping handlerMapping) throws Exception {
StaticApplicationContext applicationContext = new StaticApplicationContext();
@@ -316,6 +316,7 @@ class RequestMappingInfoHandlerMappingTests {
mapping.handleMatch(info, "/1/2", request);
assertThat(request.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE)).isEqualTo("/{path1}/2");
assertThat(request.getAttribute(HandlerMapping.BEST_MATCHING_PATH_PATTERN_ATTRIBUTE)).isNull();
}
@Test