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>
Теперь ваш @PathVariable
blahName
(и все остальные тоже) должны содержать полное имя, включая все точки.
РЕДАКТИРОВАТЬ: Вот ссылка на весенний 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()
Добавление ":.+" работало на меня, но не до тех пор, пока я не удалил внешние фигурные скобки.
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}/")
Я только столкнулся с этим, и решения здесь вообще не работали, как я ожидал.
Я предлагаю использовать выражение 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