143 votes

Spring MVC @PathVariable est tronqué

J'ai un contrôleur qui fournit un accès RESTful 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 j'envoie au serveur une variable de chemin contenant des caractères spéciaux, elle est tronquée. 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 pour empêcher Spring de tronquer la @PathVariable ?

0 votes

151voto

James Points 5907

Essayez une expression régulière pour le @RequestMapping argument :

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

1 votes

Merci pour la réponse, cela m'a aidé à résoudre un cas où les noms d'utilisateur ont été coupés d'une manière ou d'une autre (- : L'autre option avec 'useDefaultSuffixPattern' n'était pas une option parce que nous utilisons des classes de printemps @Configuration au lieu de XML.

3 votes

Cela fonctionne, mais quelle est la signification des deux points dans l'expression rationnelle ?

6 votes

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 elle est liée.

59voto

skaffman Points 197885

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

Comme décrit dans la question liée, vous pouvez désactiver ce comportement en déclarant votre propre DefaultAnnotationHandlerMapping dans le contexte de l'application, et en définissant son paramètre useDefaultSuffixPattern à la propriété false . Cela annulera le comportement par défaut, et l'empêchera de maltraiter vos données.

3 votes

Activer par défaut la négociation de contenu basée sur les extensions semble être un choix étrange. Combien de systèmes exposent réellement la même ressource dans différents formats en pratique ?

0 votes

J'ai essayé ça le matin et j'avais toujours des variables de chemin tronquées.

30 votes

+1 pour une excellente réponse et aussi pour avoir utilisé l'expression "molester vos données".

32voto

bmeurant Points 517

Spring considère que tout ce qui se trouve derrière le dernier point est une extension de fichier telle que .json ou .xml et le tronquer pour récupérer votre paramètre.

Donc si vous avez /{blahName} :

  • /param , /param.json , /param.xml ou /param.anything donnera lieu à un paramètre avec la valeur param
  • /param.value.json , /param.value.xml ou /param.value.anything donnera lieu à un paramètre avec la valeur param.value

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

  • /param donnera lieu à un paramètre avec la valeur param
  • /param.json donnera lieu à un paramètre avec la valeur param.json
  • /param.xml donnera lieu à un paramètre avec la valeur param.xml
  • /param.anything donnera lieu à un paramètre avec la valeur param.anything
  • /param.value.json donnera lieu à un paramètre avec la valeur param.value.json
  • ...

Si vous ne vous souciez pas de la reconnaissance des extensions, vous pouvez la désactiver en surchargeant la fonction mvc:annotation-driven automatique :

<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.xml ou /param.anything donnera lieu à un paramètre avec la valeur param
  • /param.value.json , /param.value.xml ou /param.value.anything donnera lieu à un paramètre avec la valeur param.value

Remarque : la différence avec la configuration par défaut n'est visible que si vous disposez d'un mappage tel que /something.{blahName} . Voir 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 conserver la reconnaissance des suffixes activée mais limitée aux extensions enregistrées.

Ici, vous définissez uniquement 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 maintenant une option contentNegotiation pour fournir un bean personnalisé mais la propriété de RequestMappingHandlerMapping doit être changée en true (default false) (cf. https://jira.springsource.org/browse/SPR-7632 ).

Pour cette raison, vous devez toujours surcharger toute la configuration mvc:annotation-driven. J'ai ouvert un ticket à Spring pour demander un RequestMappingHandlerMapping personnalisé : https://jira.springsource.org/browse/SPR-11253 . Veuillez voter si vous êtes intéressé par.

Lors de la substitution, veillez à prendre également en compte la substitution de la gestion des exécutions personnalisées. Sinon, tous vos mappages d'exceptions personnalisés échoueront. Vous devrez réutiliser les messageCoverters avec un list bean :

<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

16voto

Jan Points 1176

Tout ce qui suit le dernier point est interprété comme une extension de fichier et est coupé par défaut.
Dans votre config xml de Spring, vous pouvez ajouter DefaultAnnotationHandlerMapping et mettre useDefaultSuffixPattern à false (la valeur par défaut est true ).

Alors ouvre ton ressort xml mvc-config.xml (ou quel que soit son nom) 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, également) doivent contenir le nom complet, y compris les points.

EDIT : Voici un lien vers l'api de Spring

0 votes

Je n'ai pas essayé, mais d'autres prétendent vous devrez alors également supprimer <mvc:annotation-driven /> le cas échéant.

7voto

Steve11235 Points 198

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

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

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

D'ailleurs, je ne sais pas à quoi pensaient les concepteurs de Spring lorsqu'ils ont ajouté cette "fonctionnalité" et l'ont ensuite activée par défaut. IMHO, elle devrait être supprimée.

0 votes

Je suis d'accord. J'ai récemment été mordu par cela.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X