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

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:.+}

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/abc@server.com.au. Kontroler nawet nie zostaje wywołany, ale działa, gdy jest tylko jedna kropka /path/abc@server.com. 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


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>

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


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/abc.test@gmail.com/

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


To dlatego, że nawiasy klamrowe oceniają RegEx, a ty już masz jakieśid
8bitjunkie

15

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


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.k.a.p@o.i.n
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;
    }
}

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

11

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

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

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.k.a.p@o.i.n
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


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

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

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.


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

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.