Spring MVC @PathVariable zostaje obcięty

142

Posiadam kontroler zapewniający RESTful dostęp do informacji:

@RequestMapping(method = RequestMethod.GET, value = Routes.BLAH_GET + "/{blahName}")
public ModelAndView getBlah(@PathVariable String blahName, HttpServletRequest request,
                            HttpServletResponse response) {

Problem, którego doświadczam, polega na tym, że jeśli trafię na serwer ze zmienną ścieżki ze znakami specjalnymi, zostanie ona obcięta. Na przykład: http: // localhost: 8080 / blah-server / blah / get / blah2010.08.19-02: 25: 47

Parametr blahName będzie miał wartość blah2010.08

Jednak wywołanie request.getRequestURI () zawiera wszystkie przekazane informacje.

Masz pomysł, jak uniemożliwić Springowi obcięcie zmiennej @PathVariable?

phogel
źródło
Wygląda na to, że zostało to rozwiązane w Spring 3.2-M2: zobacz Zezwalanie na prawidłowe ścieżki rozszerzeń plików do określania negocjacji treści i jego dokumentację .
Arjan

Odpowiedzi:

149

Wypróbuj wyrażenie regularne dla @RequestMappingargumentu:

RequestMapping(method = RequestMethod.GET, value = Routes.BLAH_GET + "/{blahName:.+}")
earldouglas
źródło
1
Dzięki za odpowiedź, pomogło mi to rozwiązać przypadek, w którym nazwy użytkowników zostały w jakiś sposób przycięte ... (-: Inna opcja z 'useDefaultSuffixPattern' nie była opcją, ponieważ używamy klas wiosennych @Configuration zamiast XML.
evandongen
3
To działa, ale jakie jest znaczenie okrężnicy w wyrażeniu regularnym?
Noah Yetter
6
Noah, nie używałem tego od dawna, ale myślę, że dwukropek oddziela wyrażenie regularne od nazwy argumentu, z którym chcesz je powiązać.
earldouglas
3
mieliśmy podobny problem /item/[email protected], wszystko po obcięciu @ zostało rozwiązane przez dodanie kolejnego ukośnika /item/[email protected]/
Titi Wangsa bin Damhore
59

Jest to prawdopodobnie blisko spokrewnione z SPR-6164 . Krótko mówiąc, framework próbuje zastosować pewne sprytne rozwiązania do interpretacji URI, usuwając to, co uważa za rozszerzenia plików. Spowodowałoby to przekształcenie się blah2010.08.19-02:25:47w blah2010.08, ponieważ uważa, że .19-02:25:47jest to rozszerzenie pliku.

Jak opisano w powiązanym problemie, możesz wyłączyć to zachowanie, deklarując własny DefaultAnnotationHandlerMappingkomponent bean w kontekście aplikacji i ustawiając jego useDefaultSuffixPatternwłaściwość na false. Spowoduje to zastąpienie domyślnego zachowania i powstrzymanie go przed molestowaniem danych.

skaffman
źródło
3
Domyślne włączenie negocjacji treści opartych na rozszerzeniach wydaje się dziwnym wyborem. Ile systemów w rzeczywistości udostępnia ten sam zasób w różnych formatach w praktyce?
Affe
Próbowałem tego rano i nadal miałem skrócone zmienne ścieżki.
phogel
30
+1 za świetną odpowiedź, a także za użycie wyrażenia „molestowanie danych”
Chris Thompson,
11
Dla użytkowników Spring 3.1 - jeśli RequestMappingHandlerMappingzamiast tego używasz nowego , właściwość do ustawienia to useSuffixPatternMatch(również do false). @Ted: powiązany problem wspomina, że ​​w 3.2 mają nadzieję na dodanie nieco większej kontroli, więc nie musi to być wszystko albo nic.
Nick
2
W Spring 4.2 jest to nieco łatwiejsze do skonfigurowania. Używamy klas konfiguracyjnych Java i rozszerzamy te, WebMvcConfigurationSupportktóre zapewniają prosty punkt zaczepienia: public void configurePathMatch(PathMatchConfigurer configurer)- po prostu nadpisuj to i ustaw ścieżkę tak, jak chcesz.
pmckeown
31

Spring uważa, że ​​wszystko, co znajduje się za ostatnią kropką, to rozszerzenie pliku, takie jak .jsonlub.xml i skraca je, aby pobrać parametr.

Więc jeśli masz /{blahName}:

  • /param, /param.json, /param.xmlLub /param.anythingspowoduje param z wartościąparam
  • /param.value.json, /param.value.xmlLub /param.value.anythingspowoduje param z wartościąparam.value

Jeśli zmienisz mapowanie na /{blahName:.+}zgodnie z sugestią, każda kropka, w tym ostatnia, będzie traktowana jako część parametru:

  • /param da parametr o wartości param
  • /param.json da parametr o wartości param.json
  • /param.xml da parametr o wartości param.xml
  • /param.anything da parametr o wartości param.anything
  • /param.value.json da parametr o 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 znowu, jeśli masz /{blahName}:

  • /param, /param.json, /param.xmlLub /param.anythingspowoduje param z wartościąparam
  • /param.value.json, /param.value.xmlLub /param.value.anythingspowoduje param z wartościąparam.value

Uwaga: różnica w stosunku do domyślnej konfiguracji jest widoczna tylko wtedy, gdy masz mapowanie takie jak /something.{blahName}. Zobacz problem z projektem Resthub .

Jeśli chcesz zachować zarządzanie rozszerzeniami, od Spring 3.2 możesz również ustawić właściwość useRegisteredSuffixPatternMatch komponentu bean RequestMappingHandlerMapping, aby funkcja rozpoznawania sufiksów była aktywna, ale ograniczona 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: annotation-based 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 nadpisać całą konfigurację mvc: opartą na adnotacjach. Otworzyłem zgłoszenie do Springa, aby poprosić o niestandardowe RequestMappingHandlerMapping: https://jira.springsource.org/browse/SPR-11253 . Prosimy o głosowanie, jeśli jesteś zainteresowany.

Podczas zastępowania należy uważać, aby rozważyć również zastąpienie niestandardowego zarządzania wykonaniem. W przeciwnym razie wszystkie niestandardowe mapowania wyjątków zakończą się niepowodzeniem. Będziesz musiał ponownie użyć wiadomości Coverters z fasolą listy:

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

Zaimplementowałem w otwartym projekcie Resthub , którego jestem częścią, zestaw testów na te tematy: patrz https://github.com/resthub/resthub-spring-stack/pull/219/files i https: // github.com/resthub/resthub-spring-stack/issues/217

bmeurant
źródło
16

Wszystko po ostatniej kropce jest interpretowane jako rozszerzenie pliku i domyślnie obcięte.
W swoim spring config xml możesz dodać DefaultAnnotationHandlerMappingi ustawić useDefaultSuffixPatternna false(domyślnie true).

Więc otwórz swój spring xml mvc-config.xml(lub jak to się nazywa) i dodaj

<bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping">
    <property name="useDefaultSuffixPattern" value="false" />
</bean>

Teraz Twój @PathVariable blahName(i wszystkie inne) powinien zawierać pełne imię i nazwisko, w tym wszystkie kropki.

EDYCJA: Oto link do wiosennego interfejsu API

Jan
źródło
Nie próbowałem, ale inni twierdzą , że w razie potrzeby musiałbyś również usunąć <mvc:annotation-driven />.
Arjan
7

Napotkałem również ten sam problem, a ustawienie właściwości na false też mi nie pomogło. Jednak API mówi :

Zwróć uwagę, że ścieżki zawierające przyrostek „.xxx” lub kończące się znakiem „/” nie zostaną w żadnym przypadku przekształcone przy użyciu domyślnego wzorca sufiksu.

Próbowałem dodać „/ end” do mojego RESTful URL i problem zniknął. Nie jestem zadowolony z rozwiązania, ale zadziałało.

Przy okazji, nie wiem, co myśleli projektanci Spring, kiedy dodali tę „funkcję”, a potem włączyli ją domyślnie. IMHO, należy go usunąć.

Steve11235
źródło
Zgadzam się. Niedawno mnie to ugryzło.
llambda
7

Korzystanie z poprawnej klasy konfiguracyjnej Java:

@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter
{

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

    @Override
    public void configurePathMatch(PathMatchConfigurer configurer)
    {
        configurer.setUseSuffixPatternMatch(false);
    }
}
jebeaudet
źródło
To działało świetnie dla mnie. Działa na Tomcat Spring w wersji 4.3.14
Dave
4

Rozwiązałem przez ten hack

1) Dodano HttpServletRequest w @PathVariable, jak poniżej

 @PathVariable("requestParam") String requestParam, HttpServletRequest request) throws Exception { 

2) Uzyskaj adres URL bezpośrednio (na tym poziomie bez obcinania) w żądaniu

request.getPathInfo() 

Spring MVC @PathVariable z kropką (.) Jest obcinany

Kanagavelu Sugumar
źródło
3

Właśnie wpadłem na to, a rozwiązania tutaj generalnie nie działały tak, jak się spodziewałem.

Proponuję użyć wyrażenia SpEL i wielu mapowań np

@RequestMapping(method = RequestMethod.GET, 
    value = {Routes.BLAH_GET + "/{blahName:.+}", 
             Routes.BLAH_GET + "/{blahName}/"})
Mark Elliot
źródło
3

Problem z rozszerzeniem pliku istnieje tylko wtedy, gdy parametr znajduje się w ostatniej części adresu URL. Zmiana

@RequestMapping(method = RequestMethod.GET, value = Routes.BLAH_GET + "/{blahName}")

do

@RequestMapping(
   method = RequestMethod.GET, value = Routes.BLAH_GET + "/{blahName}/safe")

i wszystko będzie dobrze znowu-

chrismarx
źródło
3

Jeśli możesz edytować adres, na który wysyłane są żądania, prostą poprawką byłoby dodanie do nich końcowego ukośnika (a także @RequestMappingwartości):

/path/{variable}/

więc mapowanie wyglądałoby następująco:

RequestMapping(method = RequestMethod.GET, value = Routes.BLAH_GET + "/{blahName}/")

Zobacz także Spring MVC @PathVariable z kropką (.) Jest obcięta .

Michał Rybak
źródło
3
//in your xml dispatcher  add this property to your default annotation mapper bean as follow
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping">
    <property name="alwaysUseFullPath" value="true"></property>
</bean>       
Fareed Alnamrouti
źródło
3

dodanie „:. +” zadziałało, ale nie do momentu usunięcia zewnętrznych nawiasów klamrowych.

value = {"/username/{id:.+}"} nie zadziałało

value = "/username/{id:.+}" Pracuje

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

Martin Čejka
źródło
2

Rozwiązanie konfiguracyjne oparte na języku Java, aby zapobiec obcięciu (przy użyciu klasy, która nie jest przestarzała):

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 PolRepWebConfig extends WebMvcConfigurationSupport {

    @Override
    @Bean
    public RequestMappingHandlerMapping requestMappingHandlerMapping() {
        final RequestMappingHandlerMapping handlerMapping = super
                .requestMappingHandlerMapping();
        // disable the truncation after .
        handlerMapping.setUseSuffixPatternMatch(false);
        // disable the truncation after ;
        handlerMapping.setRemoveSemicolonContent(false);
        return handlerMapping;
    }
}

Źródło: http://www.javacodegeeks.com/2013/01/spring-mvc-customizing-requestmappinghandlermapping.html

AKTUALIZACJA:

Zdałem sobie sprawę, że mam pewne problemy z automatyczną konfiguracją Spring Boot, gdy zastosowałem powyższe podejście (niektóre autokonfiguracje nie działają).

Zamiast tego zacząłem stosować to BeanPostProcessorpodejście. Wydawało się, że działa lepiej.

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;

public class MyBeanPostProcessor implements BeanPostProcessor {
    private static final Logger logger = LoggerFactory
            .getLogger(MyBeanPostProcessor.class);

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName)
            throws BeansException {
        return bean;
    }

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName)
            throws BeansException {
        if (bean instanceof RequestMappingHandlerMapping) {
            setRemoveSemicolonContent((RequestMappingHandlerMapping) bean,
                    beanName);
            setUseSuffixPatternMatch((RequestMappingHandlerMapping) bean,
                    beanName);
        }
        return bean;
    }

    private void setRemoveSemicolonContent(
            RequestMappingHandlerMapping requestMappingHandlerMapping,
            String beanName) {
        logger.info(
                "Setting 'RemoveSemicolonContent' on 'RequestMappingHandlerMapping'-bean to false. Bean name: {}",
                beanName);
        requestMappingHandlerMapping.setRemoveSemicolonContent(false);
    }

    private void setUseSuffixPatternMatch(
            RequestMappingHandlerMapping requestMappingHandlerMapping,
            String beanName) {
        logger.info(
                "Setting 'UseSuffixPatternMatch' on 'RequestMappingHandlerMapping'-bean to false. Bean name: {}",
                beanName);
        requestMappingHandlerMapping.setUseSuffixPatternMatch(false);
    }
}

Zainspirowany: http://ronaldxq.blogspot.com/2014/10/spring-mvc-setting-alwaysusefullpath-on.html

burcakulug
źródło
2

jeśli masz pewność, że Twój tekst nie będzie pasował do żadnego z domyślnych rozszerzeń, możesz użyć poniższego kodu:

@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {

    @Override
    public void configurePathMatch(PathMatchConfigurer configurer) {
        configurer.setUseRegisteredSuffixPatternMatch(true);
    }
}
Bassem Reda Zohdy
źródło
1

Moim preferowanym rozwiązaniem, aby zapobiec obcięciu Spring MVC @PathVariable, jest dodanie końcowego ukośnika na końcu zmiennej ścieżki.

Na przykład:

@RequestMapping(value ="/email/{email}/")

Tak więc żądanie będzie wyglądać następująco:

http://localhost:8080/api/email/[email protected]/
Jasio
źródło
1

Problem, że masz do czynienia ze względu na wiosnę interpretacji ostatnią częścią URI po tej kropki (.) Jako rozszerzenia pliku jak .json lub .xml. Więc gdy spring próbuje rozwiązać zmienną ścieżki, po prostu obcina resztę danych po napotkaniu kropki (.) Na końcu uri. Uwaga: dzieje się tak również tylko wtedy, gdy zachowasz zmienną ścieżki na końcu uri.

Na przykład rozważmy uri: https: //localhost/example/gallery.df/link.ar

@RestController
public class CustomController {
    @GetMapping("/example/{firstValue}/{secondValue}")
    public void example(@PathVariable("firstValue") String firstValue,
      @PathVariable("secondValue") String secondValue) {
        // ...  
    }
}

W powyższym adresie URL firstValue = "gallery.df" i secondValue = "link", ostatni bit po. zostaje obcięty, gdy zmienna ścieżki jest interpretowana.

Aby temu zapobiec, istnieją dwa możliwe sposoby:

1.) Korzystanie z mapowania regexp

Użyj wyrażenia regularnego na końcu mapowania

@GetMapping("/example/{firstValue}/{secondValue:.+}")   
public void example(
  @PathVariable("firstValue") String firstValue,
  @PathVariable("secondValue") String secondValue) {
    //...
}

Używając +, wskazujemy, że każda wartość po kropce będzie również częścią zmiennej ścieżki.

2.) Dodanie ukośnika na końcu naszej @PathVariable

@GetMapping("/example/{firstValue}/{secondValue}/")
public void example(
  @PathVariable("firstValue") String firstValue,
  @PathVariable("secondValue") String secondValue) {
    //...
}

To otoczy naszą drugą zmienną chroniącą ją przed domyślnym zachowaniem Springa.

3) Zastępując domyślną konfigurację Webmvc Springa

Spring zapewnia sposoby nadpisania domyślnych konfiguracji, które są importowane za pomocą adnotacji @EnableWebMvc. Możemy dostosować konfigurację Spring MVC, deklarując naszą własną komponent bean DefaultAnnotationHandlerMapping w kontekście aplikacji i ustawiając jej właściwość useDefaultSuffixPattern na wartość false. Przykład:

@Configuration
public class CustomWebConfiguration extends WebMvcConfigurationSupport {

    @Bean
    public RequestMappingHandlerMapping 
      requestMappingHandlerMapping() {

        RequestMappingHandlerMapping handlerMapping
          = super.requestMappingHandlerMapping();
        handlerMapping.setUseSuffixPatternMatch(false);
        return handlerMapping;
    }
}

Pamiętaj, że zastąpienie tej domyślnej konfiguracji ma wpływ na wszystkie adresy URL.

Uwaga: tutaj rozszerzamy klasę WebMvcConfigurationSupport, aby zastąpić metody domyślne. Istnieje jeszcze jeden sposób na zastąpienie domyślnych konfiguracji poprzez implementację interfejsu WebMvcConfigurer. Aby uzyskać więcej informacji na ten temat, przeczytaj: https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/servlet/config/annotation/EnableWebMvc.html

Ananthapadmanabhan
źródło