Spring MVC @PathVariable with dot (.) Jest obcinany

361

To jest kontynuacja pytania Spring MVC @PathVariable zostanie obcięta

Wiosenne forum stwierdza, że ​​zostało naprawione (wersja 3.2) jako część ContentNegotiationManager. patrz poniższy link.
https://jira.springsource.org/browse/SPR-6164
https://jira.springsource.org/browse/SPR-7632

W mojej aplikacji requestParameter with .com jest obcięty.

Czy ktoś mógłby mi wyjaśnić, jak korzystać z tej nowej funkcji? jak można to skonfigurować w xml?

Uwaga: wiosenne forum - # 1 Spring MVC @PathVariable with dot (.) Jest obcinane

Kanagavelu Sugumar
źródło

Odpowiedzi:

485

O ile mi wiadomo, ten problem pojawia się tylko dla zmiennej ścieżki na końcu mapowania żądań.

Byliśmy w stanie to rozwiązać, definiując dodatek regex w mapowaniu żądań.

 /somepath/{variable:.+}
Martin Frey
źródło
1
Dzięki, myślę, że ta poprawka jest dostępna również wcześniej (przed 3.2 V) ?. Jednak nie podoba mi się ta poprawka; ponieważ jest potrzebny na wszystkich adresach URL, które muszą być obsługiwane w mojej aplikacji ... i przyszłym wdrożeniom adresów URL również należy się tym
zająć
4
oto jak rozwiązałem problem wiosną 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>
Farid
11
@Mariusz, składnia jest {variable_name:regular_expression}, więc tutaj mamy zmienną o nazwie variable, której wartość zostanie dopasowana za pomocą wyrażenia regularnego .+(gdzie .oznacza „dowolny znak” i +„raz lub więcej razy”).
Michał Rybak,
4
@StefanHaberl, jeśli dopasowujesz variableregularnie, Spring używa funkcji wykrywania sufiksów i obcina wszystko po kropce. Gdy używasz dopasowania wyrażenia regularnego, te funkcje nie są używane - zmienna jest dopasowywana tylko do wyrażenia regularnego, które udostępniasz.
Michał Rybak
9
@martin "variable:.+"nie działa, gdy w zmiennej występuje więcej niż jedna kropka. np. umieszczanie wiadomości e-mail na końcu spokojnych ścieżek, takich jak /path/[email protected]. Kontroler nawet nie zostaje wywołany, ale działa, gdy jest tylko jedna kropka /path/[email protected]. Masz pomysł, dlaczego i / lub obejście?
Czeski
242

Spring uważa, że ​​cokolwiek za ostatnią kropką jest rozszerzeniem pliku, takim jak .jsonlub.xml i obciąć je, aby pobrać parametr.

Więc jeśli masz /somepath/{variable}:

  • /somepath/param, /somepath/param.json, /somepath/param.xmlLub /somepath/param.anythingspowoduje param z wartościąparam
  • /somepath/param.value.json, /somepath/param.value.xmllub /somepath/param.value.anythingspowoduje parametr o wartościparam.value

jeśli zmienisz mapowanie na /somepath/{variable:.+}sugerowane, dowolna kropka, w tym ostatnia, będzie brana pod uwagę jako część parametru:

  • /somepath/param spowoduje parametr z wartością param
  • /somepath/param.json spowoduje parametr z wartością param.json
  • /somepath/param.xml spowoduje parametr z wartością param.xml
  • /somepath/param.anything spowoduje parametr z wartością param.anything
  • /somepath/param.value.json spowoduje parametr z wartością param.value.json
  • ...

Jeśli nie zależy Ci na rozpoznawaniu rozszerzeń, możesz je wyłączyć, zastępując 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>

Więc ponownie, jeśli masz /somepath/{variable}:

  • /somepath/param, /somepath/param.json, /somepath/param.xmlLub /somepath/param.anythingspowoduje param z wartościąparam
  • /somepath/param.value.json, /somepath/param.value.xmllub /somepath/param.value.anythingspowoduje parametr o wartościparam.value

Uwaga: różnica w stosunku do domyślnej konfiguracji jest widoczna tylko wtedy, gdy masz podobne mapowanie somepath/something.{variable}. patrz problem z projektem Resthub

jeśli chcesz zachować zarządzanie rozszerzeniami, od wersji 3.2 możesz również ustawić właściwość useRegisteredSuffixPatternMatch komponentu Bean RequestMappingHandlerMapping, aby aktywować rozpoznawanie sufiksuPattern, ale ograniczone do zarejestrowanego rozszerzenia.

Tutaj definiujesz tylko rozszerzenia json i 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>

Zauważ, że mvc: oparty na adnotacjach akceptuje teraz opcję contentNegotiation w celu zapewnienia niestandardowego komponentu bean, ale właściwość RequestMappingHandlerMapping musi zostać zmieniona na true (domyślnie false) (por. Https://jira.springsource.org/browse/SPR-7632 ).

Z tego powodu nadal musisz przesłonić konfigurację opartą na wszystkich mvc: adnotacjach. Otworzyłem bilet do Spring z prośbą o niestandardowe RequestMappingHandlerMapping: https://jira.springsource.org/browse/SPR-11253 . Głosuj, jeśli jesteś zainteresowany.

Podczas nadpisywania pamiętaj o rozważeniu nadpisania niestandardowego zarządzania wykonywaniem. W przeciwnym razie wszystkie niestandardowe odwzorowania wyjątków zakończą się niepowodzeniem. Będziesz musiał ponownie użyć programu MessageCoverters z komponentem bean list:

<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>

Wdrożyłem, w projekcie Resthub projektu open source , którego jestem częścią, zestaw testów na te tematy: patrz https://github.com/resthub/resthub-spring-stack/pull/219/files & https: // github.com/resthub/resthub-spring-stack/issues/217

bmeurant
źródło
Wybacz, że jestem nowicjuszem, więc gdzie umieszczasz konfiguracje fasoli? i jakiej wersji wiosennej dotyczy?
Splash
@Splash: Musisz zdefiniować te ziarna w swoich „standardowych” plikach Spring applicationContext.xml. Dotyczy to przynajmniej wiosny 3.2. Prawdopodobnie (przynajmniej częściowo) przedtem
bmeurant
To jest poprawna odpowiedź moim zdaniem. Wygląda na to, że parametr „useRegisteredSuffixPatternMatch” został wprowadzony właśnie dla problemu OP.
lrxw 19.04.17
To była dla mnie tylko połowa rozwiązania. Zobacz odpowiedź @Paul Aerer.
8bitjunkie
96

Aktualizacja do wiosny 4: od 4.0.1 możesz używać PathMatchConfigurer(za pośrednictwem swojego WebMvcConfigurer), np

@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);
   }
}

W xml byłoby to ( https://jira.spring.io/browse/SPR-10163 ):

<mvc:annotation-driven>
    [...]
    <mvc:path-matching registered-suffixes-only="true"/>
</mvc:annotation-driven>
Dave Syer
źródło
11
jest to zdecydowanie najczystsze rozwiązanie: wyłącz funkcję, która ją powoduje, zamiast hakować. I tak nie korzystamy z tej funkcji, więc problem został rozwiązany - idealnie!
David Lavender
Gdzie idzie klasa AllResources?
irl_irl
1
@ste_irl Dodaj klasę Java w tym samym pakiecie, co główny.
kometen
5
Użyj, matcher.setUseSuffixPatternMatch(false)aby całkowicie wyłączyć dopasowanie sufiksu.
Gian Marco Gherardi
To była dla mnie tylko połowa rozwiązania. Zobacz odpowiedź @Paul Aerer.
8bitjunkie
87

Oprócz odpowiedzi Martina Freya można to naprawić, dodając ukośnik końcowy w wartości RequestMapping:

/path/{variable}/

Należy pamiętać, że ta poprawka nie obsługuje możliwości konserwacji. Teraz wymaga, aby wszystkie identyfikatory URI miały końcowy ukośnik - coś, co może nie być widoczne dla użytkowników interfejsu API / nowych programistów. Ponieważ prawdopodobnie nie wszystkie parametry mogą .w nich zawierać , może również powodować sporadyczne błędy

Michał Rybak
źródło
2
To nawet czystsze rozwiązanie. Musiałem dowiedzieć się, w jaki sposób IE ustawia akceptuj nagłówki zgodnie z sufiksem. Więc chciałem pisać na mapowaniu żądań .doc i zawsze otrzymałem plik do pobrania zamiast nowej strony HTML. To podejście naprawiło to.
Martin Frey
to jest dla mnie najprostsze rozwiązanie i rozwiązanie mojego problemu; regexp wydaje się przesadą w wielu przypadkach
Riccardo Cossu
7
ale koliduje z domyślnym zachowaniem AngularJS w celu automatycznego usuwania końcowych ukośników. Można to skonfigurować w najnowszych wydaniach Angular, ale jest to coś, co można śledzić godzinami, jeśli nie wiesz, co się dzieje.
dschulten
1
@dschulten I właśnie zaoszczędziłeś mi godzin debugowania, dzięki! Niemniej jednak powinieneś wspomnieć w odpowiedzi, że końcowy ukośnik będzie wymagany w żądaniach HTPP.
Hoffmann
1
To jest bardzo niebezpieczne! Z pewnością nie poleciłbym tego, ponieważ każdy, kto implementuje API, najmniej tego oczekiwałby. Bardzo nie do utrzymania.
sparkyspider
32

W Spring Boot Rest Controller rozwiązałem je, wykonując następujące kroki:

RestController:

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

I od klienta Rest:

Get http://mywebhook.com/statusByEmail/[email protected]/
GoutamS
źródło
2
Ta odpowiedź zależy od końcowego ukośnika.
8bitjunkie
2
działa jak urok (również bez końcowego ukośnika). Dziękuję Ci!
AFE
27

dodanie „:. +” zadziałało dla mnie, ale dopiero po usunięciu zewnętrznych nawiasów klamrowych.

wartość = { "/username/{id:.+}" } nie działa

wartość = "/username/{id:.+}" działa

Mam nadzieję, że komuś pomogłem :)

Martin Čejka
źródło
To dlatego, że nawiasy klamrowe oceniają RegEx, a ty już masz jakieśid
8bitjunkie
15

/somepath/{variable:.+}działa w requestMappingznaczniku Java .

amit dahiya
źródło
Wolę tę odpowiedź, ponieważ nie pokazuje, co nie działało.
johnnieb
Nie działa w przypadku adresów e-mail zawierających więcej niż jedną kropkę.
8bitjunkie
1
@ 8bitjunkie Sth jak "/{code:.+}"działa dla wielu kropek, a nie jedna, tj 61.12.7. Działa również na np.[email protected]
próbowanie
13

Oto podejście oparte wyłącznie na konfiguracji 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;
    }
}
Bruno Carrier
źródło
Dzięki, rozwiązałeś to dla mnie. Ponadto jest bardzo czysty i wyraźny. +1
bkis
11

Jednym dość łatwym sposobem obejścia tego problemu jest dodanie ukośnika końcowego ...

na przykład:

posługiwać się :

/somepath/filename.jpg/

zamiast:

/somepath/filename.jpg
Marcelo C.
źródło
11

W Spring Boot wyrażenie regularne rozwiązuje problem jak

@GetMapping("/path/{param1:.+}")
Dapper Dan
źródło
Pamiętaj, że działa to tylko dla jednej kropki. To nie działa na adresy e-mail.
8bitjunkie
1
@ 8bitjunkie Sth jak "/{code:.+}"działa dla wielu kropek, nie jedna, tj 61.12.7. Działa również dla np.[email protected]
próbując
1
@ 8bitjunkie Przetestowałem to z adresem IP. Działa bardzo dobrze. Oznacza to, że działa dla wielu kropek.
Dapper Dan
6

Kompletne rozwiązanie obejmujące adresy e-mail w nazwach ścieżek na wiosnę 4.2 to

<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>

Dodaj to do xml aplikacji

Paul Arer
źródło
Upvote - jest to jedyna odpowiedź, która wyjaśnia, że wymagane są zarówno elementy konfiguracji ContentNegotiationManagerFactoryBean
8bitjunkie
5

Jeśli używasz wersji Spring 3.2.xi <mvc:annotation-driven />, utwórz to 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;
    }
}

Następnie umieść to w pliku XML konfiguracji MVC:

<bean class="spring.DoNotTruncateMyUrls" />
Jukka
źródło
Czy jest to związane z ContentNegotiationManager?
Kanagavelu Sugumar
Mój kod konfiguruje tylko RequestMappingHandlerMapping, aby adresy URL nie były obcinane. ContentNegotiationManager to kolejna bestia.
Jukka
2
To jest stare, ale tak naprawdę nie potrzebujesz BeanPostProcessordo tego. Jeśli używasz WebMvcConfigurationSupport, możesz zastąpić requestMappingHandlerMapping @Beanmetodę. Jeśli używasz konfiguracji XML, możesz po prostu zadeklarować własną RequestMappingHandlerMappingfasolę i zadeklarować tę właściwość.
Sotirios Delimanolis
Dziękuję bardzo, wypróbowałem inną liczbę rozwiązań tego samego problemu, tylko ten działał dla mnie. :-)
Jesteśmy Borg
3

Wreszcie znalazłem rozwiązanie w Spring Docs :

Aby całkowicie wyłączyć korzystanie z rozszerzeń plików, musisz ustawić oba następujące elementy:

 useSuffixPatternMatching(false), see PathMatchConfigurer

 favorPathExtension(false), see ContentNegotiationConfigurer

Dodanie tego do mojej WebMvcConfigurerAdapterimplementacji rozwiązało problem:

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

@Override
public void configurePathMatch(PathMatchConfigurer matcher) {
    matcher.setUseSuffixPatternMatch(false);
}
luboskrnac
źródło
2

Dla mnie

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

działa, ale tylko jeśli zakodujesz również „kropkę” w adresie URL żądania jako „% 2E”, to działa. Wymaga jednak, aby wszystkie adresy URL były…, co nie jest „standardowym” kodowaniem, chociaż jest prawidłowe. Czuje się jak błąd: |

Drugi sposób obejścia, podobny do „końcowego ukośnika”, to przesunięcie zmiennej, która będzie miała kropkę „inline”, np .:

@GetMapping (ścieżka = "/ {nazwa_zmiennej} / a")

teraz wszystkie kropki zostaną zachowane, nie będą potrzebne żadne modyfikacje ani wyrażenia regularne.

rogerdpack
źródło
1

Od wiosny 5.2.4 (Spring Boot v2.2.6.RELEASE) PathMatchConfigurer.setUseSuffixPatternMatchi ContentNegotiationConfigurer.favorPathExtensionzostały wycofane ( https://spring.io/blog/2020/03/24/spring-framework-5-2-5-available-now i https://github.com/spring-projects/spring-framework/issues/24179 ).

Prawdziwy problem polega na tym, że klient żąda określonego typu nośnika (np. .Com), a Spring domyślnie dodał wszystkie te typy nośników. W większości przypadków kontroler REST będzie produkował tylko JSON, więc nie będzie obsługiwał żądanego formatu wyjściowego (.com). Aby rozwiązać ten problem, powinieneś być dobry, aktualizując kontroler spoczynkowy (lub określoną metodę) w celu obsługi formatu „ouput” ​​( @RequestMapping(produces = MediaType.ALL_VALUE)) i oczywiście zezwalaj na znaki takie jak kropka ({username:.+} ).

Przykład:

@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();
    }
}

Wiosna 5.3 i nowsze będą pasować tylko do zarejestrowanych sufiksów (typy mediów).

GuyT
źródło