Spring MVC @PathVariable est tronqué

142

J'ai un contrôleur qui fournit un accès REST aux informations:

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

Le problème que je rencontre est que si je frappe le serveur avec une variable de chemin avec des caractères spéciaux, il est tronqué. Par exemple: http: // localhost: 8080 / blah-server / blah / get / blah2010.08.19-02: 25: 47

Le paramètre blahName sera blah2010.08

Cependant, l'appel à request.getRequestURI () contient toutes les informations transmises.

Une idée comment empêcher Spring de tronquer la @PathVariable?

phogel
la source
Il semble que cela ait été résolu dans Spring 3.2-M2: voir Autoriser les chemins d'extension de fichier valides pour la négociation de contenu à spécifier et sa documentation .
Arjan

Réponses:

149

Essayez une expression régulière pour l' @RequestMappingargument:

RequestMapping(method = RequestMethod.GET, value = Routes.BLAH_GET + "/{blahName:.+}")
Earldouglas
la source
1
Merci pour la réponse, cela m'a aidé à résoudre un cas où les noms d' utilisateurs se sont parés en quelque sorte .. (-: L'autre option avec « useDefaultSuffixPattern » était pas une option parce que nous utilisons les classes de printemps de @Configuration au lieu de XML.
evandongen
3
Cela fonctionne, mais quelle est la signification du côlon dans l'expression régulière?
Noah Yetter
6
Noah, je ne l'ai pas utilisé depuis longtemps, mais je pense que les deux-points séparent l'expression régulière du nom de l'argument auquel la lier.
earldouglas
3
nous avons eu un problème similaire /item/[email protected], quoi que ce soit après @ a été tronqué, cela a été résolu en ajoutant un autre slash /item/[email protected]/
Titi Wangsa bin Damhore
59

Ceci est probablement étroitement lié au SPR-6164 . En bref, le framework essaie d'appliquer une certaine intelligence à l'interprétation URI, en supprimant ce qu'il pense être des extensions de fichier. Cela aurait pour effet de transformer blah2010.08.19-02:25:47en blah2010.08, car il pense que l' .19-02:25:47est une extension de fichier.

Comme décrit dans le problème lié, vous pouvez désactiver ce comportement en déclarant votre propre DefaultAnnotationHandlerMappingbean dans le contexte de l'application et en définissant sa useDefaultSuffixPatternpropriété sur false. Cela remplacera le comportement par défaut et l'empêchera d'agresser vos données.

skaffman
la source
3
Activer la négociation de contenu basée sur les extensions par défaut semble être un choix si étrange. Combien de systèmes exposent réellement la même ressource dans différents formats dans la pratique?
Affe
J'ai essayé cela le matin et j'avais toujours des variables de chemin tronquées.
phogel
30
+1 pour une excellente réponse et aussi pour avoir utilisé l'expression «abuser de vos données»
Chris Thompson
11
Pour les utilisateurs de Spring 3.1 - si vous utilisez le nouveau à la RequestMappingHandlerMappingplace, la propriété à définir est useSuffixPatternMatch(également à false). @Ted: le problème lié mentionne que dans la version 3.2, ils espèrent ajouter un peu plus de contrôle afin que ce ne soit pas nécessairement tout ou rien.
Nick
2
Au printemps 4.2, c'est légèrement plus facile à configurer. Nous utilisons des classes de configuration Java et étendons le WebMvcConfigurationSupportqui fournit un crochet simple: public void configurePathMatch(PathMatchConfigurer configurer)- remplacez simplement cela et configurez le chemin correspondant à votre goût.
pmckeown
31

Spring considère que tout ce qui se trouve derrière le dernier point est une extension de fichier telle que .jsonou .xmlet la tronque pour récupérer votre paramètre.

Donc si vous avez /{blahName}:

  • /param, /param.json, /param.xmlOu /param.anythingse traduira par une valeur avec paramparam
  • /param.value.json, /param.value.xmlou /param.value.anythingaboutira à un paramètre avec une valeurparam.value

Si vous modifiez votre mappage /{blahName:.+}comme suggéré, tout point, y compris le dernier, sera considéré comme faisant partie de votre paramètre:

  • /param se traduira par un paramètre avec une valeur param
  • /param.json se traduira par un paramètre avec une valeur param.json
  • /param.xml se traduira par un paramètre avec une valeur param.xml
  • /param.anything se traduira par un paramètre avec une valeur param.anything
  • /param.value.json se traduira par un paramètre avec une valeur param.value.json
  • ...

Si vous ne vous souciez pas de la reconnaissance des extensions, vous pouvez la désactiver en mvc:annotation-drivenremplaçant automagic:

<bean id="handlerMapping"
      class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping">
    <property name="contentNegotiationManager" ref="contentNegotiationManager"/>
    <property name="useSuffixPatternMatch" value="false"/>
</bean>

Donc, encore une fois, si vous avez /{blahName}:

  • /param, /param.json, /param.xmlOu /param.anythingse traduira par une valeur avec paramparam
  • /param.value.json, /param.value.xmlou /param.value.anythingaboutira à un paramètre avec une valeurparam.value

Remarque: la différence par rapport à la configuration par défaut n'est visible que si vous avez un mappage comme /something.{blahName} . Voir le problème du projet Resthub .

Si vous souhaitez conserver la gestion des extensions, depuis Spring 3.2, vous pouvez également définir la propriété useRegisteredSuffixPatternMatch du bean RequestMappingHandlerMapping afin de garder la reconnaissance suffixPattern activée mais limitée à l'extension enregistrée.

Ici, vous ne définissez que les extensions json et 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>

Notez que mvc: annotation-driven accepte désormais une option contentNegotiation pour fournir un bean personnalisé mais la propriété de RequestMappingHandlerMapping doit être changée en true (par défaut false) (cf. https://jira.springsource.org/browse/SPR-7632 ).

Pour cette raison, vous devez toujours remplacer toute la configuration mvc: basée sur les annotations. J'ai ouvert un ticket pour Spring pour demander un RequestMappingHandlerMapping personnalisé: https://jira.springsource.org/browse/SPR-11253 . Veuillez voter si cela vous intéresse.

Lors du remplacement, veillez à prendre en compte également le remplacement de la gestion de l'exécution personnalisée. Sinon, tous vos mappages d'exceptions personnalisés échoueront. Vous devrez réutiliser messageCoverters avec un 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>

J'ai implémenté, dans le projet open source Resthub dont je fais partie, un ensemble de tests sur ces sujets: voir https://github.com/resthub/resthub-spring-stack/pull/219/files et https: // github.com/resthub/resthub-spring-stack/issues/217

bmeurant
la source
16

Tout ce qui se trouve après le dernier point est interprété comme une extension de fichier et coupé par défaut.
Dans votre fichier XML de configuration de printemps, vous pouvez ajouter DefaultAnnotationHandlerMappinget définir useDefaultSuffixPatternsur false(la valeur par défaut est true).

Alors ouvrez votre spring xml mvc-config.xml(ou comment on l'appelle) et ajoutez

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

Maintenant, votre @PathVariable blahName(et tous les autres aussi) doivent contenir le nom complet, y compris tous les points.

EDIT: Voici un lien vers l'API de printemps

Jan
la source
Je n'ai pas essayé, mais d' autres affirment que vous devrez également supprimer le <mvc:annotation-driven />cas échéant.
Arjan
7

J'ai également rencontré le même problème et la définition de la propriété sur false ne m'a pas non plus aidé. Cependant, l'API dit :

Notez que les chemins qui incluent déjà un suffixe ".xxx" ou se terminent par "/" ne seront en aucun cas transformés en utilisant le modèle de suffixe par défaut.

J'ai essayé d'ajouter "/ end" à mon URL RESTful et le problème a disparu. Je ne suis pas satisfait de la solution, mais cela a fonctionné.

BTW, je ne sais pas ce que les concepteurs de Spring pensaient quand ils ont ajouté cette "fonctionnalité" et l'ont ensuite activée par défaut. IMHO, il devrait être supprimé.

Steve11235
la source
Je suis d'accord. J'étais récemment mordu par ça.
llambda
7

Utilisation de la classe de configuration Java correcte:

@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
la source
Cela a très bien fonctionné pour moi. Fonctionnement sur Tomcat Spring version 4.3.14
Dave
4

J'ai résolu par ce hack

1) Ajout de HttpServletRequest dans @PathVariable comme ci-dessous

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

2) Obtenez l'URL directement (à ce niveau pas de troncature) dans la requête

request.getPathInfo() 

Spring MVC @PathVariable avec point (.) Est tronqué

Kanagavelu Sugumar
la source
3

Je viens de rencontrer ceci et les solutions ici ne fonctionnaient généralement pas comme je m'y attendais.

Je suggère d'utiliser une expression SpEL et plusieurs mappages, par exemple

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

Le problème d'extension de fichier n'existe que si le paramètre se trouve dans la dernière partie de l'URL. Changement

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

à

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

et tout ira bien à nouveau

chrismarx
la source
3

Si vous pouvez modifier l'adresse à laquelle les demandes sont envoyées, une solution simple serait de leur ajouter une barre oblique de fin (et également dans la @RequestMappingvaleur):

/path/{variable}/

donc le mappage ressemblerait à:

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

Voir aussi Spring MVC @PathVariable avec un point (.) Est tronqué .

Michał Rybak
la source
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>       
Tarif Alnamrouti
la source
3

l'ajout du ":. +" a fonctionné pour moi, mais pas avant d'avoir supprimé les accolades extérieures.

value = {"/username/{id:.+}"} n'a pas fonctionné

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

J'espère que j'ai aidé quelqu'un:]

Martin Čejka
la source
2

Solution de configuration basée sur Java pour éviter la troncature (en utilisant une classe non obsolète):

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

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

METTRE À JOUR:

J'ai réalisé que j'avais des problèmes avec la configuration automatique de Spring Boot lorsque j'ai utilisé l'approche ci-dessus (certaines configurations automatiques ne sont pas efficaces).

Au lieu de cela, j'ai commencé à utiliser l' BeanPostProcessorapproche. Cela semblait mieux fonctionner.

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

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

burcakulug
la source
2

si vous êtes sûr que votre texte ne correspondra à aucune des extensions par défaut, vous pouvez utiliser le code ci-dessous:

@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {

    @Override
    public void configurePathMatch(PathMatchConfigurer configurer) {
        configurer.setUseRegisteredSuffixPatternMatch(true);
    }
}
Bassem Reda Zohdy
la source
1

Ma solution préférable pour éviter que Spring MVC @PathVariable ne soit tronqué est d'ajouter une barre oblique à la fin de la variable de chemin.

Par exemple:

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

Ainsi, la demande ressemblera à:

http://localhost:8080/api/email/[email protected]/
Johnny
la source
1

Le problème auquel vous êtes confronté est dû au fait que Spring interprète la dernière partie de l'URI après le point (.) Comme une extension de fichier comme .json ou .xml. Ainsi, lorsque spring essaie de résoudre la variable de chemin, il tronque simplement le reste des données après avoir rencontré un point (.) À la fin de l'URI. Remarque: cela ne se produit également que si vous conservez la variable path à la fin de l'URI.

Par exemple, considérez 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) {
        // ...  
    }
}

Dans l'url ci-dessus firstValue = "gallery.df" et secondValue = "link", le dernier bit après le. est tronqué lorsque la variable de chemin est interprétée.

Donc, pour éviter cela, il y a deux façons possibles:

1.) Utilisation d'un mappage d'expression régulière

Utilisez une expression régulière à la fin du mappage

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

En utilisant +, nous indiquons que toute valeur après le point fera également partie de la variable de chemin.

2.) Ajout d'une barre oblique à la fin de notre @PathVariable

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

Cela englobera notre deuxième variable la protégeant du comportement par défaut de Spring.

3) En remplaçant la configuration webmvc par défaut de Spring

Spring fournit des moyens de remplacer les configurations par défaut importées à l'aide des annotations @EnableWebMvc . Nous pouvons personnaliser la configuration de Spring MVC en déclarant notre propre bean DefaultAnnotationHandlerMapping dans le contexte de l'application et en définissant sa propriété useDefaultSuffixPattern sur false. Exemple:

@Configuration
public class CustomWebConfiguration extends WebMvcConfigurationSupport {

    @Bean
    public RequestMappingHandlerMapping 
      requestMappingHandlerMapping() {

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

Gardez à l'esprit que le remplacement de cette configuration par défaut affecte toutes les URL.

Remarque: ici, nous étendons la classe WebMvcConfigurationSupport pour remplacer les méthodes par défaut. Il existe un autre moyen de remplacer les configurations désactivées en implémentant l'interface WebMvcConfigurer. Pour plus de détails à ce sujet, lisez: https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/servlet/config/annotation/EnableWebMvc.html

Ananthapadmanabhan
la source