Spring MVC @PathVariable становится усеченным

У меня есть контроллер, который обеспечивает 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?

15 ответов

Попробуйте регулярное выражение для @RequestMapping аргумент:

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

Это, вероятно, тесно связано с SPR-6164. Вкратце, фреймворк пытается применить некоторые смарты к интерпретации URI, удаляя то, что считает расширениями файлов. Это будет иметь эффект поворота blah2010.08.19-02:25:47 в blah2010.08, так как он думает, что .19-02:25:47 это расширение файла.

Как описано в связанной проблеме, вы можете отключить это поведение, объявив свой собственный DefaultAnnotationHandlerMapping bean-компонент в контексте приложения и установив его useDefaultSuffixPattern собственность на false, Это переопределит поведение по умолчанию и прекратит приставать к вашим данным.

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

<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, вы также можете установить свойство useRegisteredSuffixPatternMatch компонента bean RequestMappingHandlerMapping, чтобы сохранить активацию распознавания суффикса Pattern, но только для зарегистрированного расширения.

Здесь вы определяете только расширения 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 для предоставления пользовательского компонента, но свойство RequestMappingHandlerMapping должно быть изменено на true (по умолчанию false) (см. https://jira.springsource.org/browse/SPR-7632).

По этой причине вам все равно придется переопределить всю конфигурацию mvc: annotation. Я открыл билет в Spring, чтобы попросить пользовательский RequestMappingHandlerMapping: https://jira.springsource.org/browse/SPR-11253. Пожалуйста, проголосуйте, если вы заинтересованы.

При переопределении будьте осторожны, чтобы учесть также переопределение пользовательского управления выполнением. В противном случае все ваши пользовательские сопоставления исключений потерпят неудачу. Вам придется повторно использовать MessageCoverters с 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>

Я реализовал в проекте с открытым исходным кодом Resthub, частью которого я являюсь, набор тестов по этим предметам: см. https://github.com/resthub/resthub-spring-stack/pull/219/files и https://github.com/resthub/resthub-spring-stack/issues/217

Все после последней точки интерпретируется как расширение файла и по умолчанию обрезается.
В свой весенний конфиг xml вы можете добавить DefaultAnnotationHandlerMapping и установить useDefaultSuffixPattern в false (по умолчанию true).

Так что открой свой весенний xml mvc-config.xml (или как это называется) и добавить

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

Теперь ваш @PathVariableblahName (и все остальные тоже) должны содержать полное имя, включая все точки.

РЕДАКТИРОВАТЬ: Вот ссылка на весенний API

Используя правильный класс конфигурации 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);
    }
}

Я также столкнулся с той же проблемой, и установка свойства в false также мне не помогла. Тем не менее, API говорит:

Обратите внимание, что пути, которые включают суффикс ".xxx" или заканчиваются на "/", уже не будут преобразованы с использованием шаблона суффикса по умолчанию в любом случае.

Я попытался добавить "/end" к моему RESTful URL, и проблема исчезла. Мне не нравится решение, но оно сработало.

Кстати, я не знаю, о чем думали дизайнеры Spring, когда добавляли эту "функцию", а затем включали по умолчанию. ИМХО, это надо убрать.

Я решил этот хак

1) Добавлен HttpServletRequest в @PathVariable, как показано ниже

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

2) Получить URL напрямую (на этом уровне без усечения) в запросе

request.getPathInfo() 

Spring MVC @PathVariable с точкой (.) Усекается

Добавление ":.+" работало на меня, но не до тех пор, пока я не удалил внешние фигурные скобки.

value = {"/username/{id:.+}"} не работал

value = "/username/{id:.+}" работает

Надеюсь, я помог кому-то:]

Проблема с расширением файла существует, только если параметр находится в последней части URL. + Изменить

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

в

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

и все будет хорошо снова

Проблема, с которой вы сталкиваетесь, связана с тем, что spring интерпретирует последнюю часть URI после точки (.) Как расширение файла, например.json или.xml. Поэтому, когда spring пытается разрешить переменную path, она просто обрезает остальные данные после того, как встретит точку (.) В конце URI.Примечание: это также происходит, только если вы оставляете переменную path в конце 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) Переопределив стандартную конфигурацию webmvc в Spring

Spring предоставляет способы переопределения конфигураций по умолчанию, которые импортируются с использованием аннотаций @EnableWebMvc. Мы можем настроить конфигурацию Spring MVC, объявив наш собственный компонент DefaultAnnotationHandlerMapping в контексте приложения и установив для его свойства useDefaultSuffixPattern значение false. Пример:

@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

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

Если вы можете отредактировать адрес, на который отправляются запросы, простым исправлением будет добавление косой черты к ним (а также в @RequestMapping значение):

/path/{variable}/

поэтому отображение будет выглядеть так:

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

См. Также Spring MVC @PathVariable с точкой (.) Усекается.

Я только столкнулся с этим, и решения здесь вообще не работали, как я ожидал.

Я предлагаю использовать выражение SpEL и несколько отображений, например

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

Мое предпочтительное решение для предотвращения усечения Spring MVC @PathVariable - добавить косую черту в конце переменной пути.

Например:

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

Итак, запрос будет выглядеть так:

http://localhost:8080/api/email/test@test.com/

Если вы уверены, что ваш текст не будет соответствовать ни одному из расширений по умолчанию, вы можете использовать следующий код:

@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {

    @Override
    public void configurePathMatch(PathMatchConfigurer configurer) {
        configurer.setUseRegisteredSuffixPatternMatch(true);
    }
}

Конфигурационное решение на основе Java для предотвращения усечения (с использованием не устаревшего класса):

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

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

ОБНОВИТЬ:

Я понял, что у меня возникли некоторые проблемы с автоконфигурацией Spring Boot, когда я использовал описанный выше подход (некоторая автоконфигурация не работает).

Вместо этого я начал использовать BeanPostProcessor подход. Казалось, работает лучше.

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);
    }
}

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

Другие вопросы по тегам