Весна MVC @PathVariable з крапкою (.) Стає усіченою


361

Це продовження питання Spring MVC @PathVariable стає усіченим

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

У моєму запиті програми Параметр з .com врізаний.

Чи може хто-небудь пояснити мені, як користуватися цією новою функцією? як це можна налаштувати в xml?

Примітка: весняний форум №1 весни MVC @PathVariable з крапкою (.) Стає усіченим

Відповіді:


485

Наскільки мені відомо, ця проблема з’являється лише для змінної шляху в кінці карти запитів.

Нам це вдалося вирішити, визначивши адгекс регулярного виразу в картографії запитів.

 /somepath/{variable:.+}

1
Дякую, я думаю, що це виправлення доступне і раніше (до 3,2 В) ?. Однак це виправлення мені не подобається; оскільки вона потрібна взагалі за URL-адресою, яку потрібно обробляти в моїй заяві ... і майбутнім реалізацією URL-адреси також слід подбати про це ...
Kanagavelu Sugumar

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 використовує свої функції виявлення суфіксів і обрізає все після крапки. Коли ви використовуєте збіг у форматі regexp, ці функції не використовуються - змінна відповідає лише регекс-файлу, який ви надаєте.
Michał Rybak

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-drivenавтоматичне:

<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

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

З цієї причини вам все одно доведеться перекрити всю конфігурацію mvc: анотації. Я відкрив квиток на Весну, щоб попросити власну 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/isissue/217


Пробачте, я початківець, тож куди ви ставите конфігурацію квасолі? і до якої весняної версії він застосовується?
Сплеск

@Splash: Ви повинні визначити ці боби у ваших "стандартних" весняних файлах applicationContext.xml. Це стосується як мінімум весни 3.2. Можливо (принаймні частково) раніше
bmeurant

На мій погляд, це правильна відповідь. Здається, що параметр "useRegisteredSuffixPatternMatch" був введений саме для проблеми ОП.
lrxw

Це була лише половина рішення для мене. Дивіться відповідь @Paul Aerer.
8bitjunkie

96

Оновлення для весни 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 в той же пакет, що і ваш основний.
kometen

5
Використовуйте matcher.setUseSuffixPatternMatch(false)для повного вимкнення збігу суфіксів.
Gian Marco Gherardi

Це була лише половина рішення для мене. Дивіться відповідь @Paul Aerer.
8bitjunkie

87

Окрім відповіді Мартіна Фрея, це можна також виправити, додавши проміжну косу рису у значенні RequestMapping:

/path/{variable}/

Майте на увазі, що це виправлення не підтримує ремонтопридатність. Тепер він вимагає, щоб усі URI мали останню косу рису - те, що може не бути очевидним для користувачів API / нових розробників. Оскільки, ймовірно, не всі параметри можуть бути .в них, вони також можуть створювати переривчасті помилки


2
Це навіть більш чисте рішення. Мені довелося з’ясувати важкий спосіб, що IE встановлює заголовки прийому відповідно до суфіксу. Тому я хотів публікувати повідомлення на деяких .doc-картах запитів, і я завжди отримував завантаження замість нової сторінки HTML. Цей підхід зафіксував це.
Мартін Фрей

це найпростіше для мене рішення і вирішило мою проблему; regexp здається трохи надмірним для багатьох випадків
Ріккардо Коссу

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
працює як шарм (також без зачіпки). Дякую тобі!
afe

27

додавання ":. +" працювало для мене, але не поки я не зняв зовнішні фігурні дужки.

value = { "/username/{id:.+}" } не працювало

value = "/username/{id:.+}" працює

Сподіваюся, я комусь допоміг :)


Це тому, що фігурні дужки оцінюють RegEx, а у вас вже є дещоid
8bitjunkie

15

/somepath/{variable:.+}працює в requestMappingтезі Java .


Я віддаю перевагу цій відповіді, тому що вона не показує, що не працювало.
johnnieb

Не працює для електронних адрес з більш ніж однією крапкою.
8bitjunkie

1
@ 8bitjunkie Sth, як "/{code:.+}"працює для багатьох точок, не одна, тобто 61.12.7це також працює, тобтоk.a.p@o.i.n
tryHard

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

У весняному завантаженні регулярний вираз вирішує подібну проблему

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

Зауважте, що це працює лише для однієї крапки. Він не працює для електронних адрес.
8bitjunkie

1
@ 8bitjunkie Sth, як "/{code:.+}"працює для багатьох точок, не одна, тобто 61.12.7це також працює, тобтоk.a.p@o.i.n
tryHard

1
@ 8bitjunkie Я перевірив це з IP-адресою. Це працює дуже добре. Отже, це означає, що він працює для декількох точок.
Даппер Дан

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>

Додайте це до програми-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 config 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/isissue/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();
    }
}

Весна 5.3 і вище відповідатиме лише зареєстрованим суфіксам (типи носіїв).

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.