Spring MVC @PathVariable截断问题

145

我有一个控制器,它提供对信息的RESTful访问:

@RequestMapping(method = RequestMethod.GET, value = Routes.BLAH_GET + "/{blahName}")
public ModelAndView getBlah(@PathVariable String blahName, HttpServletRequest request,
                            HttpServletResponse response) {

我的问题是,如果我使用包含特殊字符的路径变量请求服务器,它会被截断。例如:http://localhost:8080/blah-server/blah/get/blah2010.08.19-02:25:47

参数 blahName 将会是 blah2010.08。

然而,对 request.getRequestURI() 的调用包含了传递的所有信息。

有什么办法可以防止 Spring 截断 @PathVariable 吗?


似乎在Spring 3.2-M2中已经解决了这个问题:请参见允许指定内容协商的有效文件扩展路径及其文档 - Arjan
16个回答

153

尝试使用正则表达式作为@RequestMapping参数:

RequestMapping(method = RequestMethod.GET, value = Routes.BLAH_GET + "/{blahName:.+}")

1
谢谢你的回答,这帮助我解决了一个用户名被截断的问题.. (-: 使用'useDefaultSuffixPattern'的另一个选项不可行,因为我们正在使用@Configuration Spring类,而不是XML。 - evandongen
3
这个正则表达式有效,但是冒号在正则表达式中有什么意义? - Noah Yetter
6
诺亚,我很久没有使用这个了,但我认为冒号将正则表达式与参数名称分隔开来,以便将其绑定。 - earldouglas
3
我们遇到了类似的问题,/item/user@abc.com 后面的内容被截断了,通过添加另一个斜杠 /item/user@abc.com/,问题得以解决。 - Titi Wangsa bin Damhore

61

这可能与SPR-6164密切相关。简单地说,该框架尝试对URI解释应用一些智能技术,删除它认为是文件扩展名的部分。这将导致blah2010.08.19-02:25:47变成blah2010.08,因为它认为.19-02:25:47是文件扩展名。

如链接的问题描述,您可以在应用程序上下文中声明自己的DefaultAnnotationHandlerMapping bean,并将其useDefaultSuffixPattern属性设置为false来禁用此行为。这将覆盖默认行为,并停止它篡改您的数据。


3
默认启用基于扩展名的内容协商似乎是一个奇怪的选择。实际上有多少系统会以不同的格式公开相同的资源? - Affe
今天早上我尝试了这个方法,但仍然存在截断的路径变量。 - phogel
31
感谢你给出了一份精彩的回答,也因为使用了“molesting your data”这个词组而给你点赞。 - Chris Thompson
如果您在页面上使用.xml/.json后缀和ContentNegotiatingViewResolver,则这可能不是一个好的解决方案。 - Ted Pennings
11
对于使用Spring 3.1的用户-如果您正在使用新的RequestMappingHandlerMapping,则要设置的属性是useSuffixPatternMatch(也为false)。 @Ted:链接的问题提到,在3.2中,他们希望增加一些控制,使它不必是全有或全无。 - Nick
2
在Spring 4.2中,这个配置稍微容易一些。我们使用Java配置类并扩展WebMvcConfigurationSupport,它提供了一个简单的钩子:public void configurePathMatch(PathMatchConfigurer configurer) - 只需覆盖它并设置路径匹配方式即可。 - pmckeown

33

Spring认为最后一个点后面的任何内容都是文件扩展名,例如.json.xml,并将其截断以检索您的参数。

因此,如果您有/{blahName}

  • /param/param.json/param.xml/param.anything将导致参数值为param
  • /param.value.json/param.value.xml/param.value.anything将导致参数值为param.value

如果按照建议更改映射为/{blahName:.+},则包括最后一个点在内的任何点都将被视为参数的一部分:

  • /param会导致一个值为param的参数
  • /param.json会导致一个值为param.json的参数
  • /param.xml会导致一个值为param.xml的参数
  • /param.anything会导致一个值为param.anything的参数
  • /param.value.json会导致一个值为param.value.json的参数
  • ...

如果您不关心扩展名识别,可以通过覆盖mvc:annotation-driven自动化来禁用它:

<bean id="handlerMapping"
      class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping">
    <property name="contentNegotiationManager" ref="contentNegotiationManager"/>
    <property name="useSuffixPatternMatch" value="false"/>
</bean>

所以,如果你有/{blahName}
  • /param/param.json/param.xml/param.anything将得到一个值为param的参数
  • /param.value.json/param.value.xml/param.value.anything将得到一个值为param.value的参数
注意:与默认配置的区别仅在您有像/something.{blahName}这样的映射时才可见。请参见Resthub项目问题
如果您想保留扩展名管理,自Spring 3.2以来,您还可以设置RequestMappingHandlerMapping bean的useRegisteredSuffixPatternMatch属性,以保持suffixPattern识别激活但限于已注册的扩展名。
在这里,您只定义了json和xml扩展名:
<bean id="handlerMapping"
      class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping">
    <property name="contentNegotiationManager" ref="contentNegotiationManager"/>
    <property name="useRegisteredSuffixPatternMatch" value="true"/>
</bean>

<bean id="contentNegotiationManager" class="org.springframework.web.accept.ContentNegotiationManagerFactoryBean">
    <property name="favorPathExtension" value="false"/>
    <property name="favorParameter" value="true"/>
    <property name="mediaTypes">
        <value>
            json=application/json
            xml=application/xml
        </value>
    </property>
</bean>

请注意,mvc:annotation-driven现在接受contentNegotiation选项以提供自定义bean,但RequestMappingHandlerMapping的属性必须更改为true(默认为false)(参见https://jira.springsource.org/browse/SPR-7632)。
因此,您仍然需要覆盖所有mvc:annotation-driven配置。我向Spring开了一个票,要求一个自定义的RequestMappingHandlerMapping:https://jira.springsource.org/browse/SPR-11253。如果您感兴趣,请投票。
在覆盖时,请注意考虑自定义执行管理的覆盖。否则,所有自定义异常映射都将失败。您将不得不使用列表bean重新使用messageCoverters:
<bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean" />
<bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean" />

<util:list id="messageConverters">
    <bean class="your.custom.message.converter.IfAny"></bean>
    <bean class="org.springframework.http.converter.ByteArrayHttpMessageConverter"></bean>
    <bean class="org.springframework.http.converter.StringHttpMessageConverter"></bean>
    <bean class="org.springframework.http.converter.ResourceHttpMessageConverter"></bean>
    <bean class="org.springframework.http.converter.xml.SourceHttpMessageConverter"></bean>
    <bean class="org.springframework.http.converter.xml.XmlAwareFormHttpMessageConverter"></bean>
    <bean class="org.springframework.http.converter.xml.Jaxb2RootElementHttpMessageConverter"></bean>
    <bean class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter"></bean>
</util:list>

<bean name="exceptionHandlerExceptionResolver"
      class="org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver">
    <property name="order" value="0"/>
    <property name="messageConverters" ref="messageConverters"/>
</bean>

<bean name="handlerAdapter"
      class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
    <property name="webBindingInitializer">
        <bean class="org.springframework.web.bind.support.ConfigurableWebBindingInitializer">
            <property name="conversionService" ref="conversionService" />
            <property name="validator" ref="validator" />
        </bean>
    </property>
    <property name="messageConverters" ref="messageConverters"/>
</bean>

<bean id="handlerMapping"
      class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping">
</bean>

我在我所参与的开源项目Resthub中实现了一组关于这些主题的测试:请查看https://github.com/resthub/resthub-spring-stack/pull/219/fileshttps://github.com/resthub/resthub-spring-stack/issues/217


17

最后一个点号后面的所有内容默认被解释为文件扩展名并被截断。
在您的Spring配置XML中,您可以添加DefaultAnnotationHandlerMapping并将useDefaultSuffixPattern设置为false(默认值为true)。

因此,请打开您的Spring XML mvc-config.xml(或者它叫什么名字),然后添加以下内容:

<bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping">
    <property name="useDefaultSuffixPattern" value="false" />
</bean>

现在你的@PathVariable blahName(以及所有其他变量)应该包含完整的名称,包括所有点号。
编辑:这里是Spring API链接

我自己没有尝试过,但其他人声称如果适用的话,你还需要删除<mvc:annotation-driven /> - Arjan

9

使用正确的Java配置类:

@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter
{

    @Override
    public void configureContentNegotiation(ContentNegotiationConfigurer configurer)
    {
        configurer.favorPathExtension(false);
    }

    @Override
    public void configurePathMatch(PathMatchConfigurer configurer)
    {
        configurer.setUseSuffixPatternMatch(false);
    }
}

这对我来说非常有效。运行在Tomcat Spring 4.3.14版本上。 - Dave

8
我也遇到了同样的问题,将属性设置为false也没有帮助。不过,API中说道:

请注意,包含".xxx"后缀或以"/"结尾的路径在任何情况下都不会使用默认后缀模式进行转换。

我试着在我的RESTful URL中加入"/end",问题解决了。虽然这个解决方案并不理想,但确实可行。
顺便说一句,我不知道Spring的设计人员在添加这个“特性”并默认开启它时在想什么。在我看来,它应该被移除。

我同意。最近我也被这个问题困扰过。 - gx0r

5
我通过以下方法解决了这个问题:
1)在@PathVariable中添加HttpServletRequest,如下所示:
 @PathVariable("requestParam") String requestParam, HttpServletRequest request) throws Exception { 

2) 在请求中直接获取URL(在此级别上不截断)

request.getPathInfo() 

Spring MVC中@PathVariable包含点(.)时被截断的问题


4

在我的情况下,添加":.+"是有效的,但是必须删除外部大括号。

value = {"/username/{id:.+}"} 不起作用

value = "/username/{id:.+}"可以正常工作

希望我能帮到你 :]


3

我刚刚遇到了这个问题,这里的解决方案并没有像我预期的那样起作用。

我建议使用SpEL表达式和多个映射,例如:

@RequestMapping(method = RequestMethod.GET, 
    value = {Routes.BLAH_GET + "/{blahName:.+}", 
             Routes.BLAH_GET + "/{blahName}/"})

3
您面临的问题是由于 Spring 将 URI 中点 (.)后面的最后一部分解释为类似 .json 或 .xml 的文件扩展名。所以当 Spring 尝试解析路径变量时,它会在遇到 URI 末尾的点 (.) 后截断其余数据。 注意: 仅当您将路径变量保留在 URI 末尾时才会发生这种情况。
例如,考虑 URI: https://localhost/example/gallery.df/link.ar
@RestController
public class CustomController {
    @GetMapping("/example/{firstValue}/{secondValue}")
    public void example(@PathVariable("firstValue") String firstValue,
      @PathVariable("secondValue") String secondValue) {
        // ...  
    }
}

在上述的URL中,firstValue = "gallery.df",secondValue = "link",当解释路径变量时,点号后面的部分会被截断。
因此,有两种可能的方法来防止这种情况发生:
1. 使用正则表达式映射
在映射的结尾部分使用正则表达式。
@GetMapping("/example/{firstValue}/{secondValue:.+}")   
public void example(
  @PathVariable("firstValue") String firstValue,
  @PathVariable("secondValue") String secondValue) {
    //...
}

使用 + 号表示点之后的所有值都将成为路径变量的一部分。

2.) 在 @PathVariable 后面添加斜杠

@GetMapping("/example/{firstValue}/{secondValue}/")
public void example(
  @PathVariable("firstValue") String firstValue,
  @PathVariable("secondValue") String secondValue) {
    //...
}

这将保护我们的第二个变量不受Spring的默认行为影响。

3) 通过覆盖Spring的默认webmvc配置

Spring提供了一种通过使用注解@EnableWebMvc来覆盖导入的默认配置的方式。我们可以通过在应用程序上下文中声明自己的DefaultAnnotationHandlerMapping bean并将其useDefaultSuffixPattern属性设置为false来自定义Spring MVC配置。 示例:

@Configuration
public class CustomWebConfiguration extends WebMvcConfigurationSupport {

    @Bean
    public RequestMappingHandlerMapping 
      requestMappingHandlerMapping() {

        RequestMappingHandlerMapping handlerMapping
          = super.requestMappingHandlerMapping();
        handlerMapping.setUseSuffixPatternMatch(false);
        return handlerMapping;
    }
}

请注意,覆盖此默认配置会影响所有的URL。
值得注意的是:我们在扩展WebMvcConfigurationSupport类以覆盖默认方法。还有一种方式可以通过实现WebMvcConfigurer接口来重写默认配置。
更多细节,请参阅:https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/servlet/config/annotation/EnableWebMvc.html

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接