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


362

Это продолжение вопроса Spring MVC @PathVariable становится усеченным

Весенний форум заявляет, что он исправил (версия 3.2) как часть ContentNegotiationManager. см. ссылку ниже
https://jira.springsource.org/browse/SPR-6164
https://jira.springsource.org/browse/SPR-7632

В моем приложении requestParameter с .com усекается.

Может ли кто-нибудь объяснить мне, как использовать эту новую функцию? как это настраивается в XML?

Примечание: весенний форум - # 1 Spring MVC @PathVariable с точкой (.) Усекается

Ответы:


485

Насколько я знаю, эта проблема появляется только для пути переменной в конце запроса сопоставления.

Мы смогли решить эту проблему, определив дополнение регулярных выражений в сопоставлении запросов.

 /somepath/{variable:.+}

1
Спасибо, я думаю, что это исправление доступно ранее (до 3.2V) ?. Однако мне не нравится это исправление; так как он необходим во всех URL-адресах, которые должны обрабатываться в моем приложении ... и о будущей реализации URL-адресов также нужно позаботиться об этом ...
Канагавелу Сугамар

4
вот как я решил проблему весной 3.0.5<!-- Spring Configuration needed to avoid URI using dots to be truncated --> <bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping"> <property name="useDefaultSuffixPattern" value="false" /> </bean>
Фарид

11
@Mariusz, синтаксис такой {variable_name:regular_expression}, поэтому здесь у нас есть переменная с именем variable, значение которой будет сопоставлено с помощью регулярного выражения .+(где .означает «любой символ» и +означает «один или несколько раз»).
Михал Рыбак

4
@StefanHaberl, если вы сопоставляете variableрегулярно, Spring использует свои функции определения суффиксов и обрезает все после точки. Когда вы используете сопоставление с регулярным выражением, эти функции не используются - переменная сопоставляется только с предоставленным вами регулярным выражением.
Михал Рыбак

9
@martin "variable:.+"не работает, когда в переменной больше одной точки. например, размещение электронных писем в конце спокойных путей, таких как /path/abc@server.com.au. Контроллер даже не вызывается, но он работает, когда есть только одна точка /path/abc@server.com. Любая идея, почему и / или обходной путь?
Богемный

242

Spring считает, что все, что находится за последней точкой, является расширением файла, например, .jsonили, .xmlи используют его для получения вашего параметра.

Так что если у вас есть /somepath/{variable}:

  • /somepath/param, /somepath/param.json, /somepath/param.xmlИли /somepath/param.anythingприведет к парам со значениемparam
  • /somepath/param.value.json, /somepath/param.value.xmlили /somepath/param.value.anythingприведет к параметру со значениемparam.value

если вы измените свое отображение на /somepath/{variable:.+}предложенное, любая точка, включая последнюю, будет считаться частью вашего параметра:

  • /somepath/param приведет к параметру со значением param
  • /somepath/param.json приведет к параметру со значением param.json
  • /somepath/param.xml приведет к параметру со значением param.xml
  • /somepath/param.anything приведет к параметру со значением param.anything
  • /somepath/param.value.json приведет к параметру со значением param.value.json
  • ...

Если вас не волнует распознавание расширений, вы можете отключить его, переопределив mvc:annotation-drivenautomagic:

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

Итак, еще раз, если у вас есть /somepath/{variable}:

  • /somepath/param, /somepath/param.json, /somepath/param.xmlИли /somepath/param.anythingприведет к парам со значениемparam
  • /somepath/param.value.json, /somepath/param.value.xmlили /somepath/param.value.anythingприведет к параметру со значениемparam.value

примечание: отличие от конфигурации по умолчанию видно только в том случае, если у вас есть подобное отображение somepath/something.{variable}. см. выпуск проекта 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


Простите, я новичок, так куда вы положите конфиги бобов? а к какой весенней версии это относится?
Всплеск

@Splash: Вы должны определить эти бины в своем «стандартном» файле Spring applicationContext.xml. Это относится как минимум к весне 3.2. Вероятно (по крайней мере, частично) до этого
bmeurant

Это правильный ответ на мой взгляд. Похоже, что параметр «useRegisteredSuffixPatternMatch» был введен именно для проблемы OP.
lrxw

Это была только половина решения для меня. Смотрите ответ Пола Арера.
8bitjunkie

96

Обновление для Spring 4: начиная с 4.0.1 вы можете использовать PathMatchConfigurer(через свой WebMvcConfigurer), например

@Configuration
protected static class AllResources extends WebMvcConfigurerAdapter {

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

}


@Configuration
public class WebConfig implements WebMvcConfigurer {

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

В xml это будет ( https://jira.spring.io/browse/SPR-10163 ):

<mvc:annotation-driven>
    [...]
    <mvc:path-matching registered-suffixes-only="true"/>
</mvc:annotation-driven>

11
это, безусловно, самое чистое решение: отключить вызывающую его функцию, а не взламывать ее. В любом случае, мы не используем эту функцию, поэтому проблема решена - отлично!
Дэвид Лавендер

Куда идет класс AllResources?
irl_irl

1
@ste_irl Добавьте класс Java в тот же пакет, что и ваш основной.
Кометен

5
Используйте matcher.setUseSuffixPatternMatch(false)для полного отключения совпадения суффиксов.
Джан Марко Герарди

Это была только половина решения для меня. Смотрите ответ Пола Арера.
8bitjunkie

87

В дополнение к ответу Мартина Фрея это также можно исправить, добавив косую черту в значение RequestMapping:

/path/{variable}/

Имейте в виду, что это исправление не поддерживает ремонтопригодность. Теперь требуется, чтобы все URI имели косую черту - что может быть неочевидно для пользователей API / новых разработчиков. Поскольку, вероятно, не все параметры могут иметь .в них, это также может создавать периодические ошибки


2
Это даже более чистое решение. Мне пришлось выяснить трудный способ, которым IE устанавливает заголовки принятия в соответствии с суффиксом. Так что я хотел опубликовать на некоторых .doc requestmapping, и я всегда получал загрузку вместо новой HTML-страницы. Этот подход исправил это.
Мартин Фрей

это самое простое решение для меня и решение моей проблемы; во многих случаях регулярное выражение выглядит немного излишним
Riccardo Cossu

7
но он вступает в противоречие с поведением AngularJS по умолчанию для автоматического удаления конечных слешей. Это можно настроить в последних версиях Angular, но это можно отслеживать часами, если вы не знаете, что происходит.
dschulten

1
@dschulten И вы только что сэкономили мне часы отладки, спасибо! Тем не менее, вы должны упомянуть в ответе, что в запросах HTPP потребуется косая черта.
Гофман

1
Это очень опасно! Я, конечно, не рекомендовал бы это, поскольку любой, реализующий API, меньше всего ожидал бы этого. Очень не подлежит ремонту.
sparkyspider

32

В Spring Boot Rest Controller я решил эти проблемы следующим образом:

RestController:

@GetMapping("/statusByEmail/{email:.+}/")
public String statusByEmail(@PathVariable(value = "email") String email){
  //code
}

И От Отдыха Клиента:

Get http://mywebhook.com/statusByEmail/abc.test@gmail.com/

2
Этот ответ зависит от косой черты, чтобы работать.
8bitjunkie

2
работает как шарм (также без косой черты). благодарю вас!
18:13

27

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

значение = { "/username/ndomid:.+}" } не работает

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

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


Это потому, что фигурные скобки оценивают RegEx, и у вас уже есть кое-что вокругid
8bitjunkie

15

/somepath/{variable:.+}работает в requestMappingтеге Java .


Я предпочитаю этот ответ, потому что он не показывает, что не сработало.
Джонниб

Не работает для адресов электронной почты с более чем одной точкой.
8bitjunkie

1
@ 8bitjunkie Sth like "/{code:.+}"работает для многих точек, а не для одного, т.е. 61.12.7он также работает для iek.a.p@o.i.n
пытаясь Hard

13

Вот подход, основанный исключительно на конфигурации 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 MvcConfig extends WebMvcConfigurationSupport{

    @Bean
    public RequestMappingHandlerMapping requestMappingHandlerMapping() {
        RequestMappingHandlerMapping handlerMapping = super.requestMappingHandlerMapping();
        handlerMapping.setUseSuffixPatternMatch(false);
        handlerMapping.setUseTrailingSlashMatch(false);
        return handlerMapping;
    }
}

Спасибо, решил это для меня. Кроме того, это очень чисто и явно. +1
bkis

11

Один из довольно простых способов обойти эту проблему - добавить косую черту ...

например:

использовать:

/somepath/filename.jpg/

вместо:

/somepath/filename.jpg

11

В Spring Boot, регулярное выражение решает проблему, как

@GetMapping("/path/{param1:.+}")

Обратите внимание, что это работает только для одной точки. Это не работает для адресов электронной почты.
8bitjunkie

1
@ 8bitjunkie Sth like "/{code:.+}"работает для многих точек, а не для одного, т.е. 61.12.7он также работает для iek.a.p@o.i.n
пытаясь Hard

1
@ 8bitjunkie Я проверил это с IP-адресом. Это работает очень хорошо. Таким образом, это означает, что он работает для нескольких точек.
Dapper Dan

6

Полное решение, включающее адреса электронной почты в путевых именах для весны 4.2

<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
    content-negotiation-manager="contentNegotiationManager">
    <mvc:path-matching suffix-pattern="false" registered-suffixes-only="true" />
</mvc:annotation-driven>

Добавьте это в application-xml


Upvote - это единственный ответ, который проясняет, что требуются как элементы конфигурации ContentNegotiationManagerFactoryBean, так и contentNegotiationManager
8bitjunkie

5

Если вы используете Spring 3.2.x и <mvc:annotation-driven />создайте это немного BeanPostProcessor:

package spring;

public final class DoNotTruncateMyUrls implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        if (bean instanceof RequestMappingHandlerMapping) {
            ((RequestMappingHandlerMapping)bean).setUseSuffixPatternMatch(false);
        }
        return bean;
    }
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }
}

Затем поместите это в конфигурационный файл MVC xml:

<bean class="spring.DoNotTruncateMyUrls" />

Это связано с ContentNegotiationManager?
Канагавелу Сугамар

Мой код только настраивает RequestMappingHandlerMapping, так что URL-адреса не будут усечены. ContentNegotiationManager - еще один зверь.
Юкка

2
Это старый, но вам действительно не нужно BeanPostProcessorдля этого. Если вы используете, WebMvcConfigurationSupportвы можете переопределить requestMappingHandlerMapping @Beanметод. Если вы используете конфигурацию XML, вы можете просто объявить свой собственный RequestMappingHandlerMappingкомпонент и объявить это свойство.
Сотириос Делиманолис

Большое спасибо, я пробовал разное количество решений для одной и той же проблемы, только это сработало для меня. :-)
Мы Борг,

3

Наконец я нашел решение в Spring Docs :

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

 useSuffixPatternMatching(false), see PathMatchConfigurer

 favorPathExtension(false), see ContentNegotiationConfigurer

Добавление этого к моей WebMvcConfigurerAdapterреализации решило проблему:

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

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

2

Для меня

@GetMapping(path = "/a/{variableName:.+}")

работает, но только если вы также закодировали «точку» в URL-адресе запроса как «% 2E», тогда это работает. Но требует, чтобы все URL были такими ... которые не являются "стандартной" кодировкой, хотя допустимы. По ощущениям что-то вроде ошибки: |

Другой обходной путь, похожий на способ «косой черты», заключается в перемещении переменной, которая будет иметь точку «inline», например:

@GetMapping (path = "/ {variableName} / a")

теперь все точки будут сохранены, без изменений или регулярных выражений.


1

Начиная с версии 5.2.4 (Spring Boot v2.2.6.RELEASE) PathMatchConfigurer.setUseSuffixPatternMatchи ContentNegotiationConfigurer.favorPathExtensionустарели ( https://spring.io/blog/2020/03/24/spring-framework-5-2-5-available-now и https://github.com/spring-projects/spring-framework/issues/24179 ).

Настоящая проблема заключается в том, что клиент запрашивает определенный тип мультимедиа (например, .com), а Spring добавляет все эти типы мультимедиа по умолчанию. В большинстве случаев ваш REST-контроллер будет генерировать только JSON, поэтому он не будет поддерживать запрошенный формат вывода (.com). Чтобы преодолеть эту проблему, у вас должно быть все в порядке, обновив свой контроллер покоя (или определенный метод), чтобы он поддерживал формат 'ouput' ( @RequestMapping(produces = MediaType.ALL_VALUE)) и, конечно, разрешал такие символы, как точка ( {username:.+}).

Пример:

@RequestMapping(value = USERNAME, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
public class UsernameAPI {

    private final UsernameService service;

    @GetMapping(value = "/{username:.+}", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.ALL_VALUE)
    public ResponseEntity isUsernameAlreadyInUse(@PathVariable(value = "username") @Valid @Size(max = 255) String username) {
        log.debug("Check if username already exists");
        if (service.doesUsernameExist(username)) {
            return ResponseEntity.status(HttpStatus.NO_CONTENT).build();
        }
        return ResponseEntity.notFound().build();
    }
}

Spring 5.3 и выше будут соответствовать только зарегистрированным суффиксам (типам носителей).

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.