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.