Spring MVC @PathVariable viene troncato


142

Ho un controller che fornisce l'accesso RESTful alle informazioni:

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

Il problema che sto riscontrando è che se colpisco il server con una variabile di percorso con caratteri speciali, viene troncato. Ad esempio: http: // localhost: 8080 / blah-server / blah / get / blah2010.08.19-02: 25: 47

Il parametro blahName sarà blah2010.08

Tuttavia, la chiamata a request.getRequestURI () contiene tutte le informazioni passate.

Qualche idea su come impedire a Spring di troncare @PathVariable?


Risposte:


149

Prova un'espressione regolare per l' @RequestMappingargomento:

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

1
Grazie per la risposta, questo mi ha aiutato a risolvere un caso in cui i nomi utente sono stati tagliati in qualche modo .. (-: L'altra opzione con 'useDefaultSuffixPattern' non era un'opzione perché stiamo usando le classi di primavera @Configuration anziché XML.
evandongen

3
Funziona, ma qual è il significato dei due punti nella regex?
Noah Yetter,

6
Noah, non lo uso da molto tempo, ma penso che i due punti separino l'espressione regolare dal nome dell'argomento a cui associarlo.
Earldouglas,

3
abbiamo avuto un problam simile /item/user@abc.com, qualsiasi cosa dopo che @ è stato troncato, questo è stato risolto aggiungendo un'altra barra /item/user@abc.com/
Titi Wangsa bin Damhore,

59

Ciò è probabilmente strettamente correlato a SPR-6164 . In breve, il framework cerca di applicare alcune intelligenze all'interpretazione dell'URI, rimuovendo quelle che pensa siano estensioni di file. Ciò avrebbe l'effetto di trasformarsi blah2010.08.19-02:25:47in blah2010.08, poiché pensa che .19-02:25:47sia un'estensione di file.

Come descritto nel problema collegato, è possibile disabilitare questo comportamento dichiarando il proprio DefaultAnnotationHandlerMappingbean nel contesto dell'app e impostando la sua useDefaultSuffixPatternproprietà su false. Ciò sovrascriverà il comportamento predefinito e lo impedirà di molestare i tuoi dati.


3
L'attivazione della negoziazione del contenuto basata sull'estensione per impostazione predefinita sembra una scelta così strana. Quanti sistemi espongono davvero la stessa risorsa in diversi formati in pratica?
Affe

L'ho provato la mattina e avevo ancora troncato le variabili del percorso.
phogel,

30
+1 per un'ottima risposta e anche per l'uso della frase "molestare i tuoi dati"
Chris Thompson,

11
Per gli utenti di Spring 3.1: se si utilizza RequestMappingHandlerMappinginvece il nuovo , la proprietà da impostare è useSuffixPatternMatch(anche a false). @Ted: il problema collegato menziona che in 3.2 sperano di aggiungere un po 'più di controllo in modo che non debba essere tutto o niente.
Nick,

2
Nella primavera 4.2 questo è leggermente più facile da configurare. Usiamo le classi di configurazione Java ed estendiamo il WebMvcConfigurationSupportche fornisce un semplice hook: public void configurePathMatch(PathMatchConfigurer configurer)- basta ignorarlo e impostare il percorso corrispondente a proprio piacimento.
pmckeown

31

Spring ritiene che qualsiasi cosa dietro all'ultimo punto sia un'estensione di file come .jsono.xml e la troncano per recuperare il parametro.

Quindi se hai /{blahName}:

  • /param, /param.json, /param.xmlO /param.anythingsi tradurrà in un param con un valoreparam
  • /param.value.json, /param.value.xmlo /param.value.anythingrisulterà in un parametro con valoreparam.value

Se modifichi la mappatura /{blahName:.+}come suggerito, qualsiasi punto, incluso l'ultimo, verrà considerato come parte del tuo parametro:

  • /param si tradurrà in un parametro con valore param
  • /param.json si tradurrà in un parametro con valore param.json
  • /param.xml si tradurrà in un parametro con valore param.xml
  • /param.anything si tradurrà in un parametro con valore param.anything
  • /param.value.json si tradurrà in un parametro con valore param.value.json
  • ...

Se non ti interessa il riconoscimento delle estensioni, puoi disabilitarlo sovrascrivendo 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>

Quindi, ancora una volta, se hai /{blahName}:

  • /param, /param.json, /param.xmlO /param.anythingsi tradurrà in un param con un valoreparam
  • /param.value.json, /param.value.xmlo /param.value.anythingrisulterà in un parametro con valoreparam.value

Nota: la differenza dalla configurazione predefinita è visibile solo se si ha una mappatura simile /something.{blahName}. Vedi il problema del progetto Resthub .

Se si desidera mantenere la gestione delle estensioni, dalla primavera 3.2 è inoltre possibile impostare la proprietà useRegisteredSuffixPatternMatch del bean RequestMappingHandlerMapping per mantenere il riconoscimento suffixPattern attivato ma limitato all'estensione registrata.

Qui definisci solo le estensioni json e 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>

Nota che mvc: annotation-driven ora accetta un'opzione contentNegotiation per fornire un bean personalizzato ma la proprietà di RequestMappingHandlerMapping deve essere cambiata in true (default false) (cfr. Https://jira.springsource.org/browse/SPR-7632 ).

Per questo motivo, devi ancora sovrascrivere tutta la configurazione guidata da mvc: annotation. Ho aperto un biglietto per Spring per chiedere un RequestMappingHandlerMapping personalizzato: https://jira.springsource.org/browse/SPR-11253 . Si prega di votare se si è interessati.

Durante l'override, fare attenzione a considerare anche l'override della gestione dell'esecuzione personalizzata. In caso contrario, tutte le mappature delle eccezioni personalizzate avranno esito negativo. Dovrai riutilizzare messageCoverters con un bean di elenco:

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

Ho implementato, nel progetto open source Resthub di cui faccio parte, una serie di test su questi argomenti: vedi https://github.com/resthub/resthub-spring-stack/pull/219/files e https: // github.com/resthub/resthub-spring-stack/issues/217


16

Tutto ciò che segue l'ultimo punto viene interpretato come estensione del file e tagliato per impostazione predefinita.
Nella tua xml primavera di configurazione è possibile aggiungere DefaultAnnotationHandlerMappinge set useDefaultSuffixPatternper false(il default è true).

Quindi apri il tuo spring xml mvc-config.xml(o comunque si chiama) e aggiungi

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

Ora il tuo @PathVariable blahName(e anche tutti gli altri) dovrebbe contenere il nome completo, compresi tutti i punti.

EDIT: ecco un link all'api primavera


Non ho provato, ma altri sostengono che dovresti rimuovere anche <mvc:annotation-driven />se applicabile.
Arjan,

7

Ho anche riscontrato lo stesso problema e l'impostazione della proprietà su false non mi ha aiutato neanche. Tuttavia, l'API dice :

Si noti che i percorsi che includono un suffisso ".xxx" o che terminano già con "/" non verranno comunque trasformati utilizzando il modello di suffisso predefinito.

Ho provato ad aggiungere "/ end" al mio URL RESTful e il problema è andato via. Non sono soddisfatto della soluzione, ma ha funzionato.

A proposito, non so cosa stessero pensando i designer Spring quando hanno aggiunto questa "funzione" e poi l'hanno attivata per impostazione predefinita. IMHO, dovrebbe essere rimosso.


Sono d'accordo. Sono stato recentemente morso da questo.
llambda,

7

Utilizzando la classe di configurazione Java corretta:

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

Questo ha funzionato alla grande per me. In esecuzione su Tomcat Spring versione 4.3.14
Dave,


3

Mi sono appena imbattuto in questo e le soluzioni qui non funzionavano generalmente come mi aspettavo.

Suggerisco di usare un'espressione SpEL e mappature multiple, ad es

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

3

Il problema con l'estensione del file esiste solo se il parametro si trova nell'ultima parte dell'URL. Modificare

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

per

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

e tutto andrà di nuovo bene-


3

Se è possibile modificare l'indirizzo a cui vengono inviate le richieste, una soluzione semplice sarebbe quella di aggiungere una barra finale (e anche nel @RequestMappingvalore):

/path/{variable}/

quindi la mappatura sarebbe simile a:

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

Vedi anche Spring MVC @PathVariable con punto (.) Viene troncato .


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>       

3

aggiungendo ":. +" ha funzionato per me, ma non fino a quando non ho rimosso le parentesi graffe esterne.

value = {"/username/{id:.+}"} non ha funzionato

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

Spero di aver aiutato qualcuno:]


2

Soluzione di configurazione basata su Java per impedire il troncamento (utilizzando una classe non deprecata):

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

Fonte: http://www.javacodegeeks.com/2013/01/spring-mvc-customizing-requestmappinghandlermapping.html

AGGIORNARE:

Mi sono reso conto di avere alcuni problemi con la configurazione automatica di Spring Boot quando ho usato l'approccio sopra (alcune configurazioni automatiche non sono efficaci).

Invece, ho iniziato ad usare l' BeanPostProcessorapproccio. Sembrava funzionare meglio.

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

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


2

se sei sicuro che il tuo testo non corrisponderà a nessuna delle estensioni predefinite puoi usare il codice seguente:

@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {

    @Override
    public void configurePathMatch(PathMatchConfigurer configurer) {
        configurer.setUseRegisteredSuffixPatternMatch(true);
    }
}

1

La mia soluzione preferibile per evitare che Spring MVC @PathVariable venga troncata è aggiungere una barra finale alla fine della variabile path.

Per esempio:

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

Quindi, la richiesta sarà simile a:

http://localhost:8080/api/email/test@test.com/

1

Il problema che stai affrontando è dovuto alla primavera che interpreta l' ultima parte dell'uri dopo il punto (.) Come un'estensione di file come .json o .xml. Quindi, quando spring tenta di risolvere la variabile path, tronca semplicemente il resto dei dati dopo aver incontrato un punto (.) Alla fine dell'uri. Nota: anche questo accade solo se si mantiene la variabile path alla fine dell'uri.

Ad esempio, considera 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) {
        // ...  
    }
}

Nell'URL sopra, firstValue = "gallery.df" e secondValue = "link", l'ultimo bit dopo il. viene troncato quando viene interpretata la variabile path.

Quindi, per evitare ciò, ci sono due modi possibili:

1.) Utilizzo di una mappatura regexp

Usa una regex nella parte finale della mappatura

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

Usando +, indichiamo qualsiasi valore dopo che il punto farà anche parte della variabile path.

2.) Aggiunta di una barra alla fine del nostro @PathVariable

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

Questo racchiuderà la nostra seconda variabile proteggendola dal comportamento predefinito di Spring.

3) Sostituendo la configurazione webmvc predefinita di Spring

Spring fornisce modi per sovrascrivere le configurazioni predefinite che vengono importate utilizzando le annotazioni @EnableWebMvc. Possiamo personalizzare la configurazione Spring MVC dichiarando il nostro bean DefaultAnnotationHandlerMapping nel contesto dell'applicazione e impostando la proprietà useDefaultSuffixPattern su false. Esempio:

@Configuration
public class CustomWebConfiguration extends WebMvcConfigurationSupport {

    @Bean
    public RequestMappingHandlerMapping 
      requestMappingHandlerMapping() {

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

Tieni presente che l'override di questa configurazione predefinita influisce su tutti gli URL.

Nota: qui stiamo estendendo la classe WebMvcConfigurationSupport per sovrascrivere i metodi predefiniti. Esiste un altro modo per ignorare le configurazioni non elaborate implementando l'interfaccia WebMvcConfigurer. Per maggiori dettagli su questa lettura: https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/servlet/config/annotation/EnableWebMvc.html

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.