Turn off use of path extensions by default

Closes gh-23915
This commit is contained in:
Rossen Stoyanchev 2020-05-05 08:04:40 +01:00
parent 153690e717
commit 147b8fb755
9 changed files with 161 additions and 194 deletions

View File

@ -50,18 +50,18 @@ import org.springframework.web.context.ServletContextAware;
* <th>Enabled Or Not</th> * <th>Enabled Or Not</th>
* </tr> * </tr>
* <tr> * <tr>
* <td>{@link #setFavorPathExtension favorPathExtension}</td>
* <td>true</td>
* <td>{@link PathExtensionContentNegotiationStrategy}</td>
* <td>Enabled</td>
* </tr>
* <tr>
* <td>{@link #setFavorParameter favorParameter}</td> * <td>{@link #setFavorParameter favorParameter}</td>
* <td>false</td> * <td>false</td>
* <td>{@link ParameterContentNegotiationStrategy}</td> * <td>{@link ParameterContentNegotiationStrategy}</td>
* <td>Off</td> * <td>Off</td>
* </tr> * </tr>
* <tr> * <tr>
* <td>{@link #setFavorPathExtension favorPathExtension}</td>
* <td>false (as of 5.3)</td>
* <td>{@link PathExtensionContentNegotiationStrategy}</td>
* <td>Off</td>
* </tr>
* <tr>
* <td>{@link #setIgnoreAcceptHeader ignoreAcceptHeader}</td> * <td>{@link #setIgnoreAcceptHeader ignoreAcceptHeader}</td>
* <td>false</td> * <td>false</td>
* <td>{@link HeaderContentNegotiationStrategy}</td> * <td>{@link HeaderContentNegotiationStrategy}</td>
@ -104,11 +104,11 @@ public class ContentNegotiationManagerFactoryBean
private List<ContentNegotiationStrategy> strategies; private List<ContentNegotiationStrategy> strategies;
private boolean favorPathExtension = true;
private boolean favorParameter = false; private boolean favorParameter = false;
private boolean ignoreAcceptHeader = false; private String parameterName = "format";
private boolean favorPathExtension = true;
private Map<String, MediaType> mediaTypes = new HashMap<>(); private Map<String, MediaType> mediaTypes = new HashMap<>();
@ -117,7 +117,7 @@ public class ContentNegotiationManagerFactoryBean
@Nullable @Nullable
private Boolean useRegisteredExtensionsOnly; private Boolean useRegisteredExtensionsOnly;
private String parameterName = "format"; private boolean ignoreAcceptHeader = false;
@Nullable @Nullable
private ContentNegotiationStrategy defaultNegotiationStrategy; private ContentNegotiationStrategy defaultNegotiationStrategy;
@ -141,17 +141,35 @@ public class ContentNegotiationManagerFactoryBean
this.strategies = (strategies != null ? new ArrayList<>(strategies) : null); this.strategies = (strategies != null ? new ArrayList<>(strategies) : null);
} }
/**
* Whether a request parameter ("format" by default) should be used to
* determine the requested media type. For this option to work you must
* register {@link #setMediaTypes media type mappings}.
* <p>By default this is set to {@code false}.
* @see #setParameterName
*/
public void setFavorParameter(boolean favorParameter) {
this.favorParameter = favorParameter;
}
/**
* Set the query parameter name to use when {@link #setFavorParameter} is on.
* <p>The default parameter name is {@code "format"}.
*/
public void setParameterName(String parameterName) {
Assert.notNull(parameterName, "parameterName is required");
this.parameterName = parameterName;
}
/** /**
* Whether the path extension in the URL path should be used to determine * Whether the path extension in the URL path should be used to determine
* the requested media type. * the requested media type.
* <p>By default this is set to {@code true} in which case a request * <p>By default this is set to {@code false} in which case path extensions
* for {@code /hotels.pdf} will be interpreted as a request for * have no impact on content negotiation.
* {@code "application/pdf"} regardless of the 'Accept' header.
* @deprecated as of 5.2.4. See class-level note on the deprecation of path * @deprecated as of 5.2.4. See class-level note on the deprecation of path
* extension config options. As there is no replacement for this method, * extension config options. As there is no replacement for this method,
* for the time being it's necessary to continue using it in order to set it * in 5.2.x it is necessary to set it to {@code false}. In 5.3 {@code false}
* to {@code false}. In 5.3 when {@code false} becomes the default, use of * becomes the default, and use of this property is longer be necessary.
* this property will no longer be necessary.
*/ */
@Deprecated @Deprecated
public void setFavorPathExtension(boolean favorPathExtension) { public void setFavorPathExtension(boolean favorPathExtension) {
@ -224,8 +242,8 @@ public class ContentNegotiationManagerFactoryBean
/** /**
* Indicate whether to use the Java Activation Framework as a fallback option * Indicate whether to use the Java Activation Framework as a fallback option
* to map from file extensions to media types. * to map from file extensions to media types.
* @deprecated as of 5.0, in favor of {@link #setUseRegisteredExtensionsOnly(boolean)}, which * @deprecated as of 5.0, in favor of {@link #setUseRegisteredExtensionsOnly(boolean)},
* has reverse behavior. * which has reverse behavior.
*/ */
@Deprecated @Deprecated
public void setUseJaf(boolean useJaf) { public void setUseJaf(boolean useJaf) {
@ -247,26 +265,6 @@ public class ContentNegotiationManagerFactoryBean
return (this.useRegisteredExtensionsOnly != null && this.useRegisteredExtensionsOnly); return (this.useRegisteredExtensionsOnly != null && this.useRegisteredExtensionsOnly);
} }
/**
* Whether a request parameter ("format" by default) should be used to
* determine the requested media type. For this option to work you must
* register {@link #setMediaTypes media type mappings}.
* <p>By default this is set to {@code false}.
* @see #setParameterName
*/
public void setFavorParameter(boolean favorParameter) {
this.favorParameter = favorParameter;
}
/**
* Set the query parameter name to use when {@link #setFavorParameter} is on.
* <p>The default parameter name is {@code "format"}.
*/
public void setParameterName(String parameterName) {
Assert.notNull(parameterName, "parameterName is required");
this.parameterName = parameterName;
}
/** /**
* Whether to disable checking the 'Accept' request header. * Whether to disable checking the 'Accept' request header.
* <p>By default this value is set to {@code false}. * <p>By default this value is set to {@code false}.

View File

@ -49,19 +49,19 @@ import org.springframework.web.accept.ParameterContentNegotiationStrategy;
* <th>Enabled Or Not</th> * <th>Enabled Or Not</th>
* </tr> * </tr>
* <tr> * <tr>
* <td>{@link #favorPathExtension}</td>
* <td>true</td>
* <td>{@link org.springframework.web.accept.PathExtensionContentNegotiationStrategy
* PathExtensionContentNegotiationStrategy}</td>
* <td>Enabled</td>
* </tr>
* <tr>
* <td>{@link #favorParameter}</td> * <td>{@link #favorParameter}</td>
* <td>false</td> * <td>false</td>
* <td>{@link ParameterContentNegotiationStrategy}</td> * <td>{@link ParameterContentNegotiationStrategy}</td>
* <td>Off</td> * <td>Off</td>
* </tr> * </tr>
* <tr> * <tr>
* <td>{@link #favorPathExtension}</td>
* <td>false (as of 5.3)</td>
* <td>{@link org.springframework.web.accept.PathExtensionContentNegotiationStrategy
* PathExtensionContentNegotiationStrategy}</td>
* <td>Off</td>
* </tr>
* <tr>
* <td>{@link #ignoreAcceptHeader}</td> * <td>{@link #ignoreAcceptHeader}</td>
* <td>false</td> * <td>false</td>
* <td>{@link HeaderContentNegotiationStrategy}</td> * <td>{@link HeaderContentNegotiationStrategy}</td>
@ -123,18 +123,34 @@ public class ContentNegotiationConfigurer {
this.factory.setStrategies(strategies); this.factory.setStrategies(strategies);
} }
/**
* Whether a request parameter ("format" by default) should be used to
* determine the requested media type. For this option to work you must
* register {@link #mediaType(String, MediaType) media type mappings}.
* <p>By default this is set to {@code false}.
* @see #parameterName(String)
*/
public ContentNegotiationConfigurer favorParameter(boolean favorParameter) {
this.factory.setFavorParameter(favorParameter);
return this;
}
/**
* Set the query parameter name to use when {@link #favorParameter} is on.
* <p>The default parameter name is {@code "format"}.
*/
public ContentNegotiationConfigurer parameterName(String parameterName) {
this.factory.setParameterName(parameterName);
return this;
}
/** /**
* Whether the path extension in the URL path should be used to determine * Whether the path extension in the URL path should be used to determine
* the requested media type. * the requested media type.
* <p>By default this is set to {@code true} in which case a request * <p>By default this is set to {@code false} in which case path extensions
* for {@code /hotels.pdf} will be interpreted as a request for * have no impact on content negotiation.
* {@code "application/pdf"} regardless of the 'Accept' header. * @deprecated as of 5.2.4. See deprecation note on
* @deprecated as of 5.2.4. See class-level note in * {@link ContentNegotiationManagerFactoryBean#setFavorPathExtension(boolean)}.
* {@link ContentNegotiationManagerFactoryBean} on the deprecation of path
* extension config options. As there is no replacement for this method,
* for the time being it's necessary to continue using it in order to set it
* to {@code false}. In 5.3 when {@code false} becomes the default, use of
* this property will no longer be necessary.
*/ */
@Deprecated @Deprecated
public ContentNegotiationConfigurer favorPathExtension(boolean favorPathExtension) { public ContentNegotiationConfigurer favorPathExtension(boolean favorPathExtension) {
@ -190,9 +206,8 @@ public class ContentNegotiationConfigurer {
* to any media type. Setting this to {@code false} will result in an * to any media type. Setting this to {@code false} will result in an
* {@code HttpMediaTypeNotAcceptableException} if there is no match. * {@code HttpMediaTypeNotAcceptableException} if there is no match.
* <p>By default this is set to {@code true}. * <p>By default this is set to {@code true}.
* @deprecated as of 5.2.4. See class-level note in * @deprecated as of 5.2.4. See deprecation note on
* {@link ContentNegotiationManagerFactoryBean} on the deprecation of path * {@link ContentNegotiationManagerFactoryBean#setIgnoreUnknownPathExtensions(boolean)}.
* extension config options.
*/ */
@Deprecated @Deprecated
public ContentNegotiationConfigurer ignoreUnknownPathExtensions(boolean ignore) { public ContentNegotiationConfigurer ignoreUnknownPathExtensions(boolean ignore) {
@ -224,27 +239,6 @@ public class ContentNegotiationConfigurer {
return this; return this;
} }
/**
* Whether a request parameter ("format" by default) should be used to
* determine the requested media type. For this option to work you must
* register {@link #mediaType(String, MediaType) media type mappings}.
* <p>By default this is set to {@code false}.
* @see #parameterName(String)
*/
public ContentNegotiationConfigurer favorParameter(boolean favorParameter) {
this.factory.setFavorParameter(favorParameter);
return this;
}
/**
* Set the query parameter name to use when {@link #favorParameter} is on.
* <p>The default parameter name is {@code "format"}.
*/
public ContentNegotiationConfigurer parameterName(String parameterName) {
this.factory.setParameterName(parameterName);
return this;
}
/** /**
* Whether to disable checking the 'Accept' request header. * Whether to disable checking the 'Accept' request header.
* <p>By default this value is set to {@code false}. * <p>By default this value is set to {@code false}.

View File

@ -69,9 +69,9 @@ public class PathMatchConfigurer {
* @see #registeredSuffixPatternMatch * @see #registeredSuffixPatternMatch
* @deprecated as of 5.2.4. See class-level note in * @deprecated as of 5.2.4. See class-level note in
* {@link RequestMappingHandlerMapping} on the deprecation of path extension * {@link RequestMappingHandlerMapping} on the deprecation of path extension
* config options. As there is no replacement for this method, for the time * config options. As there is no replacement for this method, in 5.2.x it is
* being it's necessary to set it to {@code false}. In 5.3 when {@code false} * necessary to set it to {@code false}. In 5.3 {@code false} becomes the
* becomes the default, use of this property will no longer be necessary. * default, and use of this property is longer be necessary.
*/ */
@Deprecated @Deprecated
public PathMatchConfigurer setUseSuffixPatternMatch(Boolean suffixPatternMatch) { public PathMatchConfigurer setUseSuffixPatternMatch(Boolean suffixPatternMatch) {
@ -150,9 +150,8 @@ public class PathMatchConfigurer {
/** /**
* Whether to use registered suffixes for pattern matching. * Whether to use registered suffixes for pattern matching.
* @deprecated as of 5.2.4. See class-level note in * @deprecated as of 5.2.4, see deprecation note on
* {@link RequestMappingHandlerMapping} on the deprecation of path extension * {@link #setUseSuffixPatternMatch(Boolean)}.
* config options.
*/ */
@Nullable @Nullable
@Deprecated @Deprecated
@ -162,9 +161,8 @@ public class PathMatchConfigurer {
/** /**
* Whether to use registered suffixes for pattern matching. * Whether to use registered suffixes for pattern matching.
* @deprecated as of 5.2.4. See class-level note in * @deprecated as of 5.2.4, see deprecation note on
* {@link RequestMappingHandlerMapping} on the deprecation of path extension * {@link #setUseRegisteredSuffixPatternMatch(Boolean)}.
* config options.
*/ */
@Nullable @Nullable
@Deprecated @Deprecated

View File

@ -543,7 +543,7 @@ public final class RequestMappingInfo implements RequestCondition<RequestMapping
private boolean trailingSlashMatch = true; private boolean trailingSlashMatch = true;
private boolean suffixPatternMatch = true; private boolean suffixPatternMatch = false;
private boolean registeredSuffixPatternMatch = false; private boolean registeredSuffixPatternMatch = false;
@ -600,11 +600,10 @@ public final class RequestMappingInfo implements RequestCondition<RequestMapping
/** /**
* Set whether to apply suffix pattern matching in PatternsRequestCondition. * Set whether to apply suffix pattern matching in PatternsRequestCondition.
* <p>By default this is set to 'true'. * <p>By default this is set to 'false'.
* @see #setRegisteredSuffixPatternMatch(boolean) * @see #setRegisteredSuffixPatternMatch(boolean)
* @deprecated as of 5.2.4. See class-level note in * @deprecated as of 5.2.4. See deprecation note on
* {@link RequestMappingHandlerMapping} on the deprecation of path * {@link RequestMappingHandlerMapping#setUseSuffixPatternMatch(boolean)}.
* extension config options.
*/ */
@Deprecated @Deprecated
public void setSuffixPatternMatch(boolean suffixPatternMatch) { public void setSuffixPatternMatch(boolean suffixPatternMatch) {
@ -613,9 +612,8 @@ public final class RequestMappingInfo implements RequestCondition<RequestMapping
/** /**
* Return whether to apply suffix pattern matching in PatternsRequestCondition. * Return whether to apply suffix pattern matching in PatternsRequestCondition.
* @deprecated as of 5.2.4. See class-level note in * @deprecated as of 5.2.4. See deprecation note on
* {@link RequestMappingHandlerMapping} on the deprecation of path * {@link RequestMappingHandlerMapping#setUseSuffixPatternMatch(boolean)}.
* extension config options.
*/ */
@Deprecated @Deprecated
public boolean useSuffixPatternMatch() { public boolean useSuffixPatternMatch() {
@ -630,8 +628,7 @@ public final class RequestMappingInfo implements RequestCondition<RequestMapping
* obtain the registered file extensions. * obtain the registered file extensions.
* @deprecated as of 5.2.4. See class-level note in * @deprecated as of 5.2.4. See class-level note in
* {@link RequestMappingHandlerMapping} on the deprecation of path * {@link RequestMappingHandlerMapping} on the deprecation of path
* extension config options; note also that in 5.3 the default for this * extension config options.
* property switches from {@code false} to {@code true}.
*/ */
@Deprecated @Deprecated
public void setRegisteredSuffixPatternMatch(boolean registeredSuffixPatternMatch) { public void setRegisteredSuffixPatternMatch(boolean registeredSuffixPatternMatch) {

View File

@ -61,7 +61,7 @@ import org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMappi
* <p><strong>Deprecation Note:</strong></p> In 5.2.4, * <p><strong>Deprecation Note:</strong></p> In 5.2.4,
* {@link #setUseSuffixPatternMatch(boolean) useSuffixPatternMatch} and * {@link #setUseSuffixPatternMatch(boolean) useSuffixPatternMatch} and
* {@link #setUseRegisteredSuffixPatternMatch(boolean) useRegisteredSuffixPatternMatch} * {@link #setUseRegisteredSuffixPatternMatch(boolean) useRegisteredSuffixPatternMatch}
* are deprecated in order to discourage use of path extensions for request * were deprecated in order to discourage use of path extensions for request
* mapping and for content negotiation (with similar deprecations in * mapping and for content negotiation (with similar deprecations in
* {@link ContentNegotiationManager}). For further context, please read issue * {@link ContentNegotiationManager}). For further context, please read issue
* <a href="https://github.com/spring-projects/spring-framework/issues/24179">#24719</a>. * <a href="https://github.com/spring-projects/spring-framework/issues/24179">#24719</a>.
@ -74,7 +74,7 @@ import org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMappi
public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMapping public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMapping
implements MatchableHandlerMapping, EmbeddedValueResolverAware { implements MatchableHandlerMapping, EmbeddedValueResolverAware {
private boolean useSuffixPatternMatch = true; private boolean useSuffixPatternMatch = false;
private boolean useRegisteredSuffixPatternMatch = false; private boolean useRegisteredSuffixPatternMatch = false;
@ -93,14 +93,13 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi
/** /**
* Whether to use suffix pattern match (".*") when matching patterns to * Whether to use suffix pattern match (".*") when matching patterns to
* requests. If enabled a method mapped to "/users" also matches to "/users.*". * requests. If enabled a method mapped to "/users" also matches to "/users.*".
* <p>The default value is {@code true}. * <p>By default value this is set to {@code false}.
* <p>Also see {@link #setUseRegisteredSuffixPatternMatch(boolean)} for * <p>Also see {@link #setUseRegisteredSuffixPatternMatch(boolean)} for
* more fine-grained control over specific suffixes to allow. * more fine-grained control over specific suffixes to allow.
* @deprecated as of 5.2.4. See class level comment about deprecation of * @deprecated as of 5.2.4. See class level note on the deprecation of
* path extension config options. As there is no replacement for this method, * path extension config options. As there is no replacement for this method,
* for the time being it's necessary to set it to {@code false}. In 5.3 * in 5.2.x it is necessary to set it to {@code false}. In 5.3 {@code false}
* when {@code false} becomes the default, use of this property will no * becomes the default, and use of this property is longer be necessary.
* longer be necessary.
*/ */
@Deprecated @Deprecated
public void setUseSuffixPatternMatch(boolean useSuffixPatternMatch) { public void setUseSuffixPatternMatch(boolean useSuffixPatternMatch) {
@ -113,7 +112,7 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi
* is generally recommended to reduce ambiguity and to avoid issues such as * is generally recommended to reduce ambiguity and to avoid issues such as
* when a "." appears in the path for other reasons. * when a "." appears in the path for other reasons.
* <p>By default this is set to "false". * <p>By default this is set to "false".
* @deprecated as of 5.2.4. See class level comment about deprecation of * @deprecated as of 5.2.4. See class level note on the deprecation of
* path extension config options. * path extension config options.
*/ */
@Deprecated @Deprecated
@ -191,8 +190,8 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi
/** /**
* Whether to use registered suffixes for pattern matching. * Whether to use registered suffixes for pattern matching.
* @deprecated as of 5.2.4. See class-level note on the deprecation of path * @deprecated as of 5.2.4. See deprecation notice on
* extension config options. * {@link #setUseSuffixPatternMatch(boolean)}.
*/ */
@Deprecated @Deprecated
public boolean useSuffixPatternMatch() { public boolean useSuffixPatternMatch() {
@ -201,8 +200,8 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi
/** /**
* Whether to use registered suffixes for pattern matching. * Whether to use registered suffixes for pattern matching.
* @deprecated as of 5.2.4. See class-level note on the deprecation of path * @deprecated as of 5.2.4. See deprecation notice on
* extension config options. * {@link #setUseRegisteredSuffixPatternMatch(boolean)}.
*/ */
@Deprecated @Deprecated
public boolean useRegisteredSuffixPatternMatch() { public boolean useRegisteredSuffixPatternMatch() {

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2019 the original author or authors. * Copyright 2002-2020 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -22,7 +22,6 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; import java.lang.annotation.Target;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.security.Principal; import java.security.Principal;
import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import java.util.Map; import java.util.Map;
@ -72,7 +71,7 @@ public class RequestMappingHandlerMappingTests {
@Test @Test
public void useRegisteredSuffixPatternMatch() { public void useRegisteredSuffixPatternMatch() {
assertThat(this.handlerMapping.useSuffixPatternMatch()).isTrue(); assertThat(this.handlerMapping.useSuffixPatternMatch()).isFalse();
assertThat(this.handlerMapping.useRegisteredSuffixPatternMatch()).isFalse(); assertThat(this.handlerMapping.useRegisteredSuffixPatternMatch()).isFalse();
Map<String, MediaType> fileExtensions = Collections.singletonMap("json", MediaType.APPLICATION_JSON); Map<String, MediaType> fileExtensions = Collections.singletonMap("json", MediaType.APPLICATION_JSON);
@ -85,7 +84,7 @@ public class RequestMappingHandlerMappingTests {
assertThat(this.handlerMapping.useSuffixPatternMatch()).isTrue(); assertThat(this.handlerMapping.useSuffixPatternMatch()).isTrue();
assertThat(this.handlerMapping.useRegisteredSuffixPatternMatch()).isTrue(); assertThat(this.handlerMapping.useRegisteredSuffixPatternMatch()).isTrue();
assertThat(this.handlerMapping.getFileExtensions()).isEqualTo(Arrays.asList("json")); assertThat(this.handlerMapping.getFileExtensions()).isEqualTo(Collections.singletonList("json"));
} }
@Test @Test
@ -117,9 +116,6 @@ public class RequestMappingHandlerMappingTests {
@Test @Test
public void useSuffixPatternMatch() { public void useSuffixPatternMatch() {
assertThat(this.handlerMapping.useSuffixPatternMatch()).isTrue();
this.handlerMapping.setUseSuffixPatternMatch(false);
assertThat(this.handlerMapping.useSuffixPatternMatch()).isFalse(); assertThat(this.handlerMapping.useSuffixPatternMatch()).isFalse();
this.handlerMapping.setUseRegisteredSuffixPatternMatch(false); this.handlerMapping.setUseRegisteredSuffixPatternMatch(false);

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2019 the original author or authors. * Copyright 2002-2020 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -469,22 +469,26 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl
@Test @Test
public void adaptedHandleMethods() throws Exception { public void adaptedHandleMethods() throws Exception {
doTestAdaptedHandleMethods(MyAdaptedController.class); initServlet(wac -> {
RootBeanDefinition mappingDef = new RootBeanDefinition(RequestMappingHandlerMapping.class);
mappingDef.getPropertyValues().add("useSuffixPatternMatch", true);
wac.registerBeanDefinition("handlerMapping", mappingDef);
}, MyAdaptedController.class);
doTestAdaptedHandleMethods();
} }
@Test @Test
public void adaptedHandleMethods2() throws Exception { public void adaptedHandleMethods2() throws Exception {
doTestAdaptedHandleMethods(MyAdaptedController2.class); initServletWithControllers(MyAdaptedController2.class);
} }
@Test @Test
public void adaptedHandleMethods3() throws Exception { public void adaptedHandleMethods3() throws Exception {
doTestAdaptedHandleMethods(MyAdaptedController3.class); initServletWithControllers(MyAdaptedController3.class);
doTestAdaptedHandleMethods();
} }
private void doTestAdaptedHandleMethods(final Class<?> controllerClass) throws Exception { private void doTestAdaptedHandleMethods() throws Exception {
initServletWithControllers(controllerClass);
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/myPath1.do"); MockHttpServletRequest request = new MockHttpServletRequest("GET", "/myPath1.do");
MockHttpServletResponse response = new MockHttpServletResponse(); MockHttpServletResponse response = new MockHttpServletResponse();
request.addParameter("param1", "value1"); request.addParameter("param1", "value1");
@ -727,8 +731,11 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl
@Test @Test
public void relativePathDispatchingController() throws Exception { public void relativePathDispatchingController() throws Exception {
initServletWithControllers(MyRelativePathDispatchingController.class); initServlet(wac -> {
getServlet().init(new MockServletConfig()); RootBeanDefinition mappingDef = new RootBeanDefinition(RequestMappingHandlerMapping.class);
mappingDef.getPropertyValues().add("useSuffixPatternMatch", true);
wac.registerBeanDefinition("handlerMapping", mappingDef);
}, MyRelativePathDispatchingController.class);
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/myApp/myHandle"); MockHttpServletRequest request = new MockHttpServletRequest("GET", "/myApp/myHandle");
MockHttpServletResponse response = new MockHttpServletResponse(); MockHttpServletResponse response = new MockHttpServletResponse();
@ -753,8 +760,11 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl
@Test @Test
public void relativeMethodPathDispatchingController() throws Exception { public void relativeMethodPathDispatchingController() throws Exception {
initServletWithControllers(MyRelativeMethodPathDispatchingController.class); initServlet(wac -> {
getServlet().init(new MockServletConfig()); RootBeanDefinition mappingDef = new RootBeanDefinition(RequestMappingHandlerMapping.class);
mappingDef.getPropertyValues().add("useSuffixPatternMatch", true);
wac.registerBeanDefinition("handlerMapping", mappingDef);
}, MyRelativeMethodPathDispatchingController.class);
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/myApp/myHandle"); MockHttpServletRequest request = new MockHttpServletRequest("GET", "/myApp/myHandle");
MockHttpServletResponse response = new MockHttpServletResponse(); MockHttpServletResponse response = new MockHttpServletResponse();
@ -1674,8 +1684,13 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl
@Test @Test
public void responseBodyAsHtml() throws Exception { public void responseBodyAsHtml() throws Exception {
initServlet(wac -> { initServlet(wac -> {
RootBeanDefinition mappingDef = new RootBeanDefinition(RequestMappingHandlerMapping.class);
mappingDef.getPropertyValues().add("useSuffixPatternMatch", true);
wac.registerBeanDefinition("handlerMapping", mappingDef);
ContentNegotiationManagerFactoryBean factoryBean = new ContentNegotiationManagerFactoryBean(); ContentNegotiationManagerFactoryBean factoryBean = new ContentNegotiationManagerFactoryBean();
factoryBean.afterPropertiesSet(); factoryBean.afterPropertiesSet();
RootBeanDefinition adapterDef = new RootBeanDefinition(RequestMappingHandlerAdapter.class); RootBeanDefinition adapterDef = new RootBeanDefinition(RequestMappingHandlerAdapter.class);
adapterDef.getPropertyValues().add("contentNegotiationManager", factoryBean.getObject()); adapterDef.getPropertyValues().add("contentNegotiationManager", factoryBean.getObject());
wac.registerBeanDefinition("handlerAdapter", adapterDef); wac.registerBeanDefinition("handlerAdapter", adapterDef);
@ -1720,8 +1735,13 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl
@Test @Test
public void responseBodyAsHtmlWithProducesCondition() throws Exception { public void responseBodyAsHtmlWithProducesCondition() throws Exception {
initServlet(wac -> { initServlet(wac -> {
RootBeanDefinition mappingDef = new RootBeanDefinition(RequestMappingHandlerMapping.class);
mappingDef.getPropertyValues().add("useSuffixPatternMatch", true);
wac.registerBeanDefinition("handlerMapping", mappingDef);
ContentNegotiationManagerFactoryBean factoryBean = new ContentNegotiationManagerFactoryBean(); ContentNegotiationManagerFactoryBean factoryBean = new ContentNegotiationManagerFactoryBean();
factoryBean.afterPropertiesSet(); factoryBean.afterPropertiesSet();
RootBeanDefinition adapterDef = new RootBeanDefinition(RequestMappingHandlerAdapter.class); RootBeanDefinition adapterDef = new RootBeanDefinition(RequestMappingHandlerAdapter.class);
adapterDef.getPropertyValues().add("contentNegotiationManager", factoryBean.getObject()); adapterDef.getPropertyValues().add("contentNegotiationManager", factoryBean.getObject());
wac.registerBeanDefinition("handlerAdapter", adapterDef); wac.registerBeanDefinition("handlerAdapter", adapterDef);

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2019 the original author or authors. * Copyright 2002-2020 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -34,7 +34,6 @@ import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.propertyeditors.CustomDateEditor; import org.springframework.beans.propertyeditors.CustomDateEditor;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
import org.springframework.util.MultiValueMap; import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.WebDataBinder; import org.springframework.web.bind.WebDataBinder;
@ -44,7 +43,6 @@ import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.context.WebApplicationContext; import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.GenericWebApplicationContext;
import org.springframework.web.servlet.View; import org.springframework.web.servlet.View;
import org.springframework.web.servlet.ViewResolver; import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.view.AbstractView; import org.springframework.web.servlet.view.AbstractView;
@ -87,14 +85,10 @@ public class UriTemplateServletAnnotationControllerHandlerMethodTests extends Ab
pathVars.put("booking", 21); pathVars.put("booking", 21);
pathVars.put("other", "other"); pathVars.put("other", "other");
WebApplicationContext wac = WebApplicationContext wac = initServlet(context -> {
initServlet(new ApplicationContextInitializer<GenericWebApplicationContext>() {
@Override
public void initialize(GenericWebApplicationContext context) {
RootBeanDefinition beanDef = new RootBeanDefinition(ModelValidatingViewResolver.class); RootBeanDefinition beanDef = new RootBeanDefinition(ModelValidatingViewResolver.class);
beanDef.getConstructorArgumentValues().addGenericArgumentValue(pathVars); beanDef.getConstructorArgumentValues().addGenericArgumentValue(pathVars);
context.registerBeanDefinition("viewResolver", beanDef); context.registerBeanDefinition("viewResolver", beanDef);
}
}, ViewRenderingController.class); }, ViewRenderingController.class);
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/hotels/42;q=1,2/bookings/21-other;q=3;r=R"); MockHttpServletRequest request = new MockHttpServletRequest("GET", "/hotels/42;q=1,2/bookings/21-other;q=3;r=R");
@ -143,22 +137,21 @@ public class UriTemplateServletAnnotationControllerHandlerMethodTests extends Ab
MockHttpServletResponse response = new MockHttpServletResponse(); MockHttpServletResponse response = new MockHttpServletResponse();
getServlet().service(request, response); getServlet().service(request, response);
assertThat(response.getContentAsString()).isEqualTo("test-42-21"); assertThat(response.getContentAsString()).isEqualTo("test-42-21");
request = new MockHttpServletRequest("GET", "/hotels/42/bookings/21.html");
response = new MockHttpServletResponse();
getServlet().service(request, response);
assertThat(response.getContentAsString()).isEqualTo("test-42-21");
} }
@Test @Test
public void extension() throws Exception { public void extension() throws Exception {
initServletWithControllers(SimpleUriTemplateController.class); initServlet(wac -> {
RootBeanDefinition mappingDef = new RootBeanDefinition(RequestMappingHandlerMapping.class);
mappingDef.getPropertyValues().add("useSuffixPatternMatch", true);
mappingDef.getPropertyValues().add("removeSemicolonContent", "false");
wac.registerBeanDefinition("handlerMapping", mappingDef);
}, SimpleUriTemplateController.class);
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/42;jsessionid=c0o7fszeb1;q=24.xml"); MockHttpServletRequest request = new MockHttpServletRequest("GET", "/42;jsessionid=c0o7fszeb1;q=24.xml");
MockHttpServletResponse response = new MockHttpServletResponse(); MockHttpServletResponse response = new MockHttpServletResponse();
getServlet().service(request, response); getServlet().service(request, response);
assertThat(response.getContentAsString()).isEqualTo("test-42-24"); assertThat(response.getContentAsString()).isEqualTo("test-42-24");
} }
@Test @Test
@ -264,11 +257,6 @@ public class UriTemplateServletAnnotationControllerHandlerMethodTests extends Ab
MockHttpServletResponse response = new MockHttpServletResponse(); MockHttpServletResponse response = new MockHttpServletResponse();
getServlet().service(request, response); getServlet().service(request, response);
assertThat(response.getContentAsString()).isEqualTo("handle4-page-5"); assertThat(response.getContentAsString()).isEqualTo("handle4-page-5");
request = new MockHttpServletRequest("GET", "/category/page/5.html");
response = new MockHttpServletResponse();
getServlet().service(request, response);
assertThat(response.getContentAsString()).isEqualTo("handle4-page-5");
} }
@Test @Test
@ -282,10 +270,7 @@ public class UriTemplateServletAnnotationControllerHandlerMethodTests extends Ab
assertThat(response.getContentAsString()).isEqualTo("test-42-;q=1;q=2-[1, 2]"); assertThat(response.getContentAsString()).isEqualTo("test-42-;q=1;q=2-[1, 2]");
} }
/* @Test // gh-11306
* See SPR-6640
*/
@Test
public void menuTree() throws Exception { public void menuTree() throws Exception {
initServletWithControllers(MenuTreeController.class); initServletWithControllers(MenuTreeController.class);
@ -295,10 +280,7 @@ public class UriTemplateServletAnnotationControllerHandlerMethodTests extends Ab
assertThat(response.getContentAsString()).isEqualTo("M5"); assertThat(response.getContentAsString()).isEqualTo("M5");
} }
/* @Test // gh-11542
* See SPR-6876
*/
@Test
public void variableNames() throws Exception { public void variableNames() throws Exception {
initServletWithControllers(VariableNamesController.class); initServletWithControllers(VariableNamesController.class);
@ -313,12 +295,13 @@ public class UriTemplateServletAnnotationControllerHandlerMethodTests extends Ab
assertThat(response.getContentAsString()).isEqualTo("bar-bar"); assertThat(response.getContentAsString()).isEqualTo("bar-bar");
} }
/* @Test // gh-13187
* See SPR-8543
*/
@Test
public void variableNamesWithUrlExtension() throws Exception { public void variableNamesWithUrlExtension() throws Exception {
initServletWithControllers(VariableNamesController.class); initServlet(wac -> {
RootBeanDefinition mappingDef = new RootBeanDefinition(RequestMappingHandlerMapping.class);
mappingDef.getPropertyValues().add("useSuffixPatternMatch", true);
wac.registerBeanDefinition("handlerMapping", mappingDef);
}, VariableNamesController.class);
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/test/foo.json"); MockHttpServletRequest request = new MockHttpServletRequest("GET", "/test/foo.json");
MockHttpServletResponse response = new MockHttpServletResponse(); MockHttpServletResponse response = new MockHttpServletResponse();
@ -326,10 +309,7 @@ public class UriTemplateServletAnnotationControllerHandlerMethodTests extends Ab
assertThat(response.getContentAsString()).isEqualTo("foo-foo"); assertThat(response.getContentAsString()).isEqualTo("foo-foo");
} }
/* @Test // gh-11643
* See SPR-6978
*/
@Test
public void doIt() throws Exception { public void doIt() throws Exception {
initServletWithControllers(Spr6978Controller.class); initServletWithControllers(Spr6978Controller.class);

View File

@ -1683,11 +1683,11 @@ See <<mvc-config-path-matching>> in the configuration section.
[[mvc-ann-requestmapping-suffix-pattern-match]] [[mvc-ann-requestmapping-suffix-pattern-match]]
==== Suffix Match ==== Suffix Match
By default, Spring MVC performs `.{asterisk}` suffix pattern matching so that a Starting in 5.3, by default Spring MVC no longer performs `.{asterisk}` suffix pattern
controller mapped to `/person` is also implicitly mapped to `/person.{asterisk}`. matching where a controller mapped to `/person` is also implicitly mapped to
The file extension is then used to interpret the requested content type to use for `/person.{asterisk}`. As a consequence path extensions are no longer used to interpret
the response (that is, instead of the `Accept` header) -- for example, `/person.pdf`, the requested content type for the response -- for example, `/person.pdf`, `/person.xml`,
`/person.xml`, and others. and so on.
Using file extensions in this way was necessary when browsers used to send `Accept` headers Using file extensions in this way was necessary when browsers used to send `Accept` headers
that were hard to interpret consistently. At present, that is no longer a necessity and that were hard to interpret consistently. At present, that is no longer a necessity and
@ -1698,28 +1698,16 @@ It can cause ambiguity when overlain with the use of URI variables, path paramet
URI encoding. Reasoning about URL-based authorization URI encoding. Reasoning about URL-based authorization
and security (see next section for more details) also become more difficult. and security (see next section for more details) also become more difficult.
To completely disable the use of file extensions, you must set both of the following: To completely disable the use of path extensions in versions prior to 5.3, set the following:
* `useSuffixPatternMatching(false)`, see <<mvc-config-path-matching, PathMatchConfigurer>> * `useSuffixPatternMatching(false)`, see <<mvc-config-path-matching, PathMatchConfigurer>>
* `favorPathExtension(false)`, see <<mvc-config-content-negotiation, ContentNegotiationConfigurer>> * `favorPathExtension(false)`, see <<mvc-config-content-negotiation, ContentNegotiationConfigurer>>
URL-based content negotiation can still be useful (for example, when typing a URL in a Having a way to request content types other than through the `"Accept"` header can still
browser). To enable that, we recommend a query parameter-based strategy to avoid most of be useful, e.g. when typing a URL in a browser. A safe alternative to path extensions is
the issues that come with file extensions. Alternatively, if you must use file extensions, consider to use the query parameter strategy. If you must use file extensions, consider restricting
restricting them to a list of explicitly registered extensions through the them to a list of explicitly registered extensions through the `mediaTypes` property of
`mediaTypes` property of <<mvc-config-content-negotiation,ContentNegotiationConfigurer>>. <<mvc-config-content-negotiation,ContentNegotiationConfigurer>>.
[NOTE]
====
Starting in 5.2.4, path extension related options for request mapping in
{api-spring-framework}/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMapping.java[RequestMappingHandlerMapping]
and for content negotiation in
{api-spring-framework}/org.springframework.web.accept/ContentNegotiationManagerFactoryBean.java[ContentNegotiationManagerFactoryBean]
are deprecated. See Spring Framework issue
https://github.com/spring-projects/spring-framework/issues/24179[#24179] and related
issues for further plans.
====
[[mvc-ann-requestmapping-rfd]] [[mvc-ann-requestmapping-rfd]]
@ -5851,7 +5839,6 @@ The following example shows how to customize path matching in Java configuration
public void configurePathMatch(PathMatchConfigurer configurer) { public void configurePathMatch(PathMatchConfigurer configurer) {
configurer configurer
.setUseTrailingSlashMatch(false) .setUseTrailingSlashMatch(false)
.setUseRegisteredSuffixPatternMatch(true)
.setPathMatcher(antPathMatcher()) .setPathMatcher(antPathMatcher())
.setUrlPathHelper(urlPathHelper()) .setUrlPathHelper(urlPathHelper())
.addPathPrefix("/api", HandlerTypePredicate.forAnnotation(RestController.class)); .addPathPrefix("/api", HandlerTypePredicate.forAnnotation(RestController.class));
@ -5879,7 +5866,6 @@ The following example shows how to customize path matching in Java configuration
configurer configurer
.setUseSuffixPatternMatch(true) .setUseSuffixPatternMatch(true)
.setUseTrailingSlashMatch(false) .setUseTrailingSlashMatch(false)
.setUseRegisteredSuffixPatternMatch(true)
.setPathMatcher(antPathMatcher()) .setPathMatcher(antPathMatcher())
.setUrlPathHelper(urlPathHelper()) .setUrlPathHelper(urlPathHelper())
.addPathPrefix("/api", HandlerTypePredicate.forAnnotation(RestController::class.java)) .addPathPrefix("/api", HandlerTypePredicate.forAnnotation(RestController::class.java))
@ -5904,7 +5890,6 @@ The following example shows how to achieve the same configuration in XML:
<mvc:annotation-driven> <mvc:annotation-driven>
<mvc:path-matching <mvc:path-matching
trailing-slash="false" trailing-slash="false"
registered-suffixes-only="true"
path-helper="pathHelper" path-helper="pathHelper"
path-matcher="pathMatcher"/> path-matcher="pathMatcher"/>
</mvc:annotation-driven> </mvc:annotation-driven>