Spring MVC @PathVariable getting truncated
Asked Answered
E

16

145

I have a controller that provides RESTful access to information:

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

The problem I am experiencing is that if I hit the server with a path variable with special characters it gets truncated. For example: http://localhost:8080/blah-server/blah/get/blah2010.08.19-02:25:47

The parameter blahName will be blah2010.08

However, the call to request.getRequestURI() contains all the information passed in.

Any idea how to prevent Spring from truncating the @PathVariable?

Encapsulate answered 19/8, 2010 at 21:44 Comment(1)
It seems this has been solved in Spring 3.2-M2: see Allow valid file extension paths for content negotiation to be specified and its documentation.Substantial
S
153

Try a regular expression for the @RequestMapping argument:

RequestMapping(method = RequestMethod.GET, value = Routes.BLAH_GET + "/{blahName:.+}")
Saberio answered 19/8, 2010 at 22:3 Comment(4)
Thanks for the answer, this helped me solve a case where usernames got trimmed somehow .. (-: The other option with 'useDefaultSuffixPattern' was not an option because we're using @Configuration spring classes instead of XML.Lithopone
This works, but what is the significance of the colon in the regex?Fleshy
Noah, I haven't used this in a long time, but I think the colon separates the regular expression from the argument name to bind it to.Saberio
we had a similar problam /item/[email protected], anything after @ was truncated, this was solved by adding another slash /item/[email protected]/Piste
M
61

This is probably closely related to SPR-6164. Briefly, the framework tries to apply some smarts to the URI interpretation, removing what it thinks are file extensions. This would have the effect of turning blah2010.08.19-02:25:47 into blah2010.08, since it thinks the .19-02:25:47 is a file extension.

As described in the linked issue, you can disable this behaviour by declaring your own DefaultAnnotationHandlerMapping bean in the app context, and setting its useDefaultSuffixPattern property to false. This will override the default behaviour, and stop it molesting your data.

Missile answered 19/8, 2010 at 23:4 Comment(6)
Turning extension based content negotiation on by default seems like such a strange choice. How many systems really expose the same resource in different formats in practice?Crispation
I tried this the morning and still had truncated path variables.Encapsulate
+1 for a great answer and also for using the phrase "molesting your data"Mcnamee
this might not be a good solution if you use a .xml/.json suffix on pages and the ContentNegotiatingViewResolverEustace
For Spring 3.1 users - if you're using the new RequestMappingHandlerMapping instead, the property to set is useSuffixPatternMatch (also to false). @Ted: the linked issue mentions that in 3.2 they hope to add a bit more control so it doesn't have to be all-or-nothing.Caviness
In Spring 4.2 this is slightly easier to configure. We use Java config classes and extend the WebMvcConfigurationSupport which provides a simple hook:public void configurePathMatch(PathMatchConfigurer configurer) - just override that and set up the path matching how you like.Witkowski
W
34

Spring considers that anything behind the last dot is a file extension such as .jsonor .xml and truncate it to retrieve your parameter.

So if you have /{blahName}:

  • /param, /param.json, /param.xml or /param.anything will result in a param with value param
  • /param.value.json, /param.value.xml or /param.value.anything will result in a param with value param.value

If you change your mapping to /{blahName:.+} as suggested, any dot, including the last one, will be considered as part of your parameter:

  • /param will result in a param with value param
  • /param.json will result in a param with value param.json
  • /param.xml will result in a param with value param.xml
  • /param.anything will result in a param with value param.anything
  • /param.value.json will result in a param with value param.value.json
  • ...

If you don't care of extension recognition, you can disable it by overriding mvc:annotation-driven automagic:

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

So, again, if you have /{blahName}:

  • /param, /param.json, /param.xml or /param.anything will result in a param with value param
  • /param.value.json, /param.value.xml or /param.value.anything will result in a param with value param.value

Note: the difference from the default config is visible only if you have a mapping like /something.{blahName}. See Resthub project issue.

If you want to keep extension management, since Spring 3.2 you can also set the useRegisteredSuffixPatternMatch property of RequestMappingHandlerMapping bean in order to keep suffixPattern recognition activated but limited to registered extension.

Here you define only json and xml extensions:

<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>

Note that mvc:annotation-driven accepts now a contentNegotiation option to provide a custom bean but the property of RequestMappingHandlerMapping has to be changed to true (default false) (cf. https://jira.springsource.org/browse/SPR-7632).

For that reason, you still have to override all the mvc:annotation-driven configuration. I opened a ticket to Spring to ask for a custom RequestMappingHandlerMapping: https://jira.springsource.org/browse/SPR-11253. Please vote if you are interested in.

While overriding, be careful to consider also custom Execution management overriding. Otherwise, all your custom Exception mappings will fail. You will have to reuse messageCoverters with a list bean:

<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>

I implemented, in the open source project Resthub that I am part of, a set of tests on these subjects: see https://github.com/resthub/resthub-spring-stack/pull/219/files and https://github.com/resthub/resthub-spring-stack/issues/217

Wolfgram answered 23/12, 2013 at 12:51 Comment(0)
L
17

Everything after the last dot is interpreted as file extension and cut off by default.
In your spring config xml you can add DefaultAnnotationHandlerMapping and set useDefaultSuffixPattern to false (default is true).

So open your spring xml mvc-config.xml (or however it is called) and add

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

Now your @PathVariable blahName (and all other, too) should contain the full name including all dots.

EDIT: Here is a link to the spring api

Livorno answered 22/10, 2010 at 15:35 Comment(1)
I have not tried, but others claim you'd then also need to remove <mvc:annotation-driven /> if applicable.Substantial
N
9

Using the correct Java configuration class :

@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);
    }
}
Northeastward answered 5/3, 2015 at 16:59 Comment(1)
This worked great for me. Running on Tomcat Spring version 4.3.14Surveyor
D
8

I also ran into the same issue, and setting the property to false didn't help me either. However, the API says:

Note that paths which include a ".xxx" suffix or end with "/" already will not be transformed using the default suffix pattern in any case.

I tried adding "/end" to my RESTful URL, and the problem went away. I'm not please with the solution, but it did work.

BTW, I don't know what the Spring designers were thinking when they added this "feature" and then turned it on by default. IMHO, it should be removed.

Diary answered 6/4, 2012 at 18:28 Comment(1)
I agree. I was recently bit by this.Catheryncatheter
A
5

I resolved by this hack

1) Added HttpServletRequest in @PathVariable like below

 @PathVariable("requestParam") String requestParam, HttpServletRequest request) throws Exception { 

2) Get the URL directly (At this level no truncation) in the request

request.getPathInfo() 

Spring MVC @PathVariable with dot (.) is getting truncated

Accuracy answered 26/2, 2013 at 10:32 Comment(0)
I
4

adding the ":.+" worked for me, but not until I removed outer curly brackets.

value = {"/username/{id:.+}"} didn't work

value = "/username/{id:.+}" works

Hope I helped someone :]

Infect answered 15/1, 2016 at 11:54 Comment(0)
N
3

I just ran into this and the solutions here didn't generally work as I expected.

I suggest using a SpEL expression and multiple mappings, e.g.

@RequestMapping(method = RequestMethod.GET, 
    value = {Routes.BLAH_GET + "/{blahName:.+}", 
             Routes.BLAH_GET + "/{blahName}/"})
Njord answered 27/7, 2011 at 16:44 Comment(0)
N
3

The file extension problem only exists if the parameter is in the last part of the URL. Change

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

to

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

and all will be well again-

Nietzsche answered 12/7, 2013 at 15:52 Comment(0)
O
3

If you can edit the address that requests are sent to, simple fix would be to add a trailing slash to them (and also in the @RequestMapping value):

/path/{variable}/

so the mapping would look like:

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

See also Spring MVC @PathVariable with dot (.) is getting truncated.

Occasionally answered 28/11, 2013 at 2:9 Comment(0)
C
3
//in your xml dispatcher  add this property to your default annotation mapper bean as follow
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping">
    <property name="alwaysUseFullPath" value="true"></property>
</bean>       
Cyanotype answered 25/3, 2014 at 17:26 Comment(0)
C
3

The problem that you are facing is due to spring interpreting the last part of the uri after the dot (.) as a file extension like .json or .xml . So when spring tries to resolve the path variable it simply truncates the rest of the data after it encounters a dot (.) at the end of the uri. Note: also this happens only if you keep the path variable at the end of the uri.

For example consider 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) {
        // ...  
    }
}

In the above url firstValue = "gallery.df" and secondValue="link" , the last bit after the . gets truncated when the path variable gets interpreted.

So, to prevent this there is two possible ways:

1.) Using a regexp mapping

Use a regex at the end part of mapping

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

By using + , we indicate any value after the dot will also be part of the path variable.

2.) Adding a slash at the end of our @PathVariable

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

This will enclose our second variable protecting it from Spring’s default behavior.

3) By overriding Spring's default webmvc configuration

Spring provides ways to override the default configurations that gets imported by using the annotations @EnableWebMvc.We can customize the Spring MVC configuration by declaring our own DefaultAnnotationHandlerMapping bean in the application context and setting its useDefaultSuffixPattern property to false. Example:

@Configuration
public class CustomWebConfiguration extends WebMvcConfigurationSupport {

    @Bean
    public RequestMappingHandlerMapping 
      requestMappingHandlerMapping() {

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

Keep in mind that overriding this default configuration, affects all urls.

Note : here we are extending the WebMvcConfigurationSupport class to override the default methods. There is one more way to override the deault configurations by implementing the WebMvcConfigurer interface. For more details on this read : https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/servlet/config/annotation/EnableWebMvc.html

Coronach answered 3/4, 2019 at 9:48 Comment(0)
M
2

Java based configuration solution to prevent truncation (using a not-deprecated class):

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

@Configuration
public class PolRepWebConfig extends WebMvcConfigurationSupport {

    @Override
    @Bean
    public RequestMappingHandlerMapping requestMappingHandlerMapping() {
        final RequestMappingHandlerMapping handlerMapping = super
                .requestMappingHandlerMapping();
        // disable the truncation after .
        handlerMapping.setUseSuffixPatternMatch(false);
        // disable the truncation after ;
        handlerMapping.setRemoveSemicolonContent(false);
        return handlerMapping;
    }
}

Source: http://www.javacodegeeks.com/2013/01/spring-mvc-customizing-requestmappinghandlermapping.html

UPDATE:

I realized having some problems with Spring Boot auto-configuration when I used the approach above (some auto-configuration doesn't get effective).

Instead, I started to use the BeanPostProcessor approach. It seemed to be working better.

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;

public class MyBeanPostProcessor implements BeanPostProcessor {
    private static final Logger logger = LoggerFactory
            .getLogger(MyBeanPostProcessor.class);

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName)
            throws BeansException {
        return bean;
    }

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName)
            throws BeansException {
        if (bean instanceof RequestMappingHandlerMapping) {
            setRemoveSemicolonContent((RequestMappingHandlerMapping) bean,
                    beanName);
            setUseSuffixPatternMatch((RequestMappingHandlerMapping) bean,
                    beanName);
        }
        return bean;
    }

    private void setRemoveSemicolonContent(
            RequestMappingHandlerMapping requestMappingHandlerMapping,
            String beanName) {
        logger.info(
                "Setting 'RemoveSemicolonContent' on 'RequestMappingHandlerMapping'-bean to false. Bean name: {}",
                beanName);
        requestMappingHandlerMapping.setRemoveSemicolonContent(false);
    }

    private void setUseSuffixPatternMatch(
            RequestMappingHandlerMapping requestMappingHandlerMapping,
            String beanName) {
        logger.info(
                "Setting 'UseSuffixPatternMatch' on 'RequestMappingHandlerMapping'-bean to false. Bean name: {}",
                beanName);
        requestMappingHandlerMapping.setUseSuffixPatternMatch(false);
    }
}

Inspired from: http://ronaldxq.blogspot.com/2014/10/spring-mvc-setting-alwaysusefullpath-on.html

Mouthpart answered 4/3, 2015 at 21:32 Comment(0)
H
2

if you are sure that your text will not match any of default extensions you can use below code:

@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {

    @Override
    public void configurePathMatch(PathMatchConfigurer configurer) {
        configurer.setUseRegisteredSuffixPatternMatch(true);
    }
}
Helluva answered 29/9, 2015 at 18:23 Comment(0)
L
2

My preferable solution to prevent the Spring MVC @PathVariable to get truncated is to add trailing slash at the end of the path variable.

For example:

@RequestMapping(value ="/email/{email}/")

So, the request will look like:

http://localhost:8080/api/email/[email protected]/
Lorie answered 7/2, 2017 at 10:2 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.