Spring MVC @PathVariable усекается

143

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

фогель
источник

Ответы:

150

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

RequestMapping(method = RequestMethod.GET, value = Routes.BLAH_GET + "/{blahName:.+}")
Earldouglas
источник
1
Спасибо за ответ, это помогло мне решить случай, когда имена пользователей каким-то образом были обрезаны ... (-: Другой вариант с 'useDefaultSuffixPattern' не подходил, потому что мы используем классы пружин @Configuration вместо XML.
evandongen
3
Это работает, но каково значение двоеточия в регулярном выражении?
Ной Йеттер
6
Ной, я давно этим не пользовался, но думаю, двоеточие отделяет регулярное выражение от имени аргумента, к которому его нужно привязать.
Earldouglas 02
3
у нас была аналогичная проблема /item/user@abc.com, все, что было после усечения @, было решено добавлением еще одной косой черты /item/user@abc.com/
Тити Ванса бин Дамхор
59

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

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

Скаффман
источник
3
Включение согласования содержимого на основе расширений по умолчанию кажется таким странным выбором. Сколько систем действительно предоставляют один и тот же ресурс в разных форматах на практике?
Affe
Я попробовал это утром, но переменные пути все еще были усечены.
Фогель
30
+1 за отличный ответ, а также за использование фразы «приставание к вашим данным»
Крис Томпсон
11
Для пользователей Spring 3.1 - если RequestMappingHandlerMappingвместо этого вы используете новый , устанавливаемое свойство useSuffixPatternMatch(также false). @Ted: в связанной проблеме упоминается, что в 3.2 они надеются добавить немного больше контроля, чтобы не было все или ничего.
Ник
2
В Spring 4.2 это немного проще настроить. Мы используем классы конфигурации Java и расширяем те, WebMvcConfigurationSupportкоторые предоставляют простой крючок: public void configurePathMatch(PathMatchConfigurer configurer)- просто переопределите это и настройте путь, соответствующий вашему желанию.
pmckeown
31

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

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

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

При переопределении не забудьте также рассмотреть возможность переопределения настраиваемого управления выполнением. В противном случае все ваши настраиваемые сопоставления исключений завершатся ошибкой. Вам придется повторно использовать 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/files и https: // github.com/resthub/resthub-spring-stack/issues/217

Bmeurant
источник
16

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

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

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

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

EDIT: вот ссылка на весенний api

Янв
источник
Я не пробовал, но другие утверждают, что вам также нужно удалить, <mvc:annotation-driven />если это возможно.
Arjan
7

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

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

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

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

Стив11235
источник
Согласен. Меня недавно это укусило.
llambda 09
7

Используя правильный класс конфигурации 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);
    }
}
Jebeaudet
источник
Это отлично сработало для меня. Запуск на Tomcat Spring версии 4.3.14
Дэйв
4

Я решил этим взломом

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

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

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

request.getPathInfo() 

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

Канагавелу Сугумар
источник
3

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

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

@RequestMapping(method = RequestMethod.GET, 
    value = {Routes.BLAH_GET + "/{blahName:.+}", 
             Routes.BLAH_GET + "/{blahName}/"})
Марк Эллиот
источник
3

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

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

к

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

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

Chrismarx
источник
3

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

/path/{variable}/

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

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

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

Михал Рыбак
источник
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>       
Фарид Алнамрути
источник
3

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

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

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

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

Мартин Чейка
источник
2

Решение для конфигурации на основе 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

Burcakulug
источник
2

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

@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {

    @Override
    public void configurePathMatch(PathMatchConfigurer configurer) {
        configurer.setUseRegisteredSuffixPatternMatch(true);
    }
}
Бассем Реда Зохды
источник
2

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

Например:

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

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

http://localhost:8080/api/email/test@test.com/
Джонни
источник
1

Проблема , с которой вы столкнулись из - за весны интерпретирования последней части Ури после с точки (.) В качестве расширения файла , как .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. Мы можем настроить конфигурацию Spring MVC, объявив наш собственный bean- компонент 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

Анантападманабхан
источник