Spring MVC @PathVariable avec le point (.) Devient tronqué

361

Ceci est la continuation de la question Spring MVC @PathVariable se tronquée

Le forum Spring indique qu'il a corrigé (version 3.2) dans le cadre de ContentNegotiationManager. voir le lien ci-dessous.
https://jira.springsource.org/browse/SPR-6164
https://jira.springsource.org/browse/SPR-7632

Dans ma demande, le paramètre avec .com est tronqué.

Quelqu'un pourrait-il m'expliquer comment utiliser cette nouvelle fonctionnalité? comment est-il configurable au xml?

Remarque: Spring Forum - # 1 Spring MVC @PathVariable avec un point (.) Devient tronqué

Kanagavelu Sugumar
la source

Réponses:

485

Autant que je sache, ce problème n'apparaît que pour la variable de chemin à la fin de la cartographie des demandes.

Nous avons pu résoudre ce problème en définissant l'addon regex dans le mappage des demandes.

 /somepath/{variable:.+}
Martin Frey
la source
1
Merci, je pense que ce correctif était disponible plus tôt (avant 3.2V)?. Cependant, je n'aime pas ce correctif; car il faut du tout l'url qui doit être gérée dans mon application ... et la future implémentation d'URL aussi pour s'en occuper ...
Kanagavelu Sugumar
4
voici comment j'ai résolu le problème au printemps 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, la syntaxe est la suivante {variable_name:regular_expression}, nous avons donc ici une variable nommée variable, dont la valeur sera mise en correspondance en utilisant l'expression régulière .+(où .signifie «n'importe quel caractère» et +signifie «une ou plusieurs fois»).
Michał Rybak
4
@StefanHaberl si vous correspondez variablede manière régulière, Spring utilise ses fonctionnalités de détection de suffixe et tronque tout après le point. Lorsque vous utilisez la correspondance d'expressions régulières, ces fonctionnalités ne sont pas utilisées - la variable ne correspond qu'à l'expression régulière que vous fournissez.
Michał Rybak
9
@martin "variable:.+"ne fonctionne pas lorsqu'il y a plus d'un point dans la variable. par exemple, mettre des e-mails à la fin de chemins reposants comme /path/[email protected]. Le contrôleur n'est même pas appelé, mais cela fonctionne quand il n'y a qu'un seul point /path/[email protected]. Une idée pourquoi et / ou une solution de contournement?
Bohème
242

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

Donc si vous avez /somepath/{variable}:

  • /somepath/param, /somepath/param.json, /somepath/param.xmlOu /somepath/param.anythingse traduira par une valeur avec paramparam
  • /somepath/param.value.json, /somepath/param.value.xmlou /somepath/param.value.anythingentraînera un paramètre avec une valeurparam.value

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

  • /somepath/param se traduira par un paramètre avec une valeur param
  • /somepath/param.json se traduira par un paramètre avec une valeur param.json
  • /somepath/param.xml se traduira par un paramètre avec une valeur param.xml
  • /somepath/param.anything se traduira par un paramètre avec une valeur param.anything
  • /somepath/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 /somepath/{variable}:

  • /somepath/param, /somepath/param.json, /somepath/param.xmlOu /somepath/param.anythingse traduira par une valeur avec paramparam
  • /somepath/param.value.json, /somepath/param.value.xmlou /somepath/param.value.anythingentraînera un paramètre avec une valeurparam.value

remarque: la différence avec la configuration par défaut n'est visible que si vous avez un mappage comme somepath/something.{variable}. 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 garder la reconnaissance suffixPattern activée mais limitée à l'extension enregistrée.

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

Pour cette raison, vous devez toujours remplacer la configuration entièrement 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 vous êtes intercepté.

Lors de la substitution, soyez prudent de considérer également la substitution de la gestion d'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 & https: // github.com/resthub/resthub-spring-stack/issues/217

bmeurant
la source
Pardonnez-moi, je suis un novice, alors où mettez-vous les configurations de haricots? et à quelle version printanière s'applique-t-elle?
Splash
@Splash: Vous devez définir ces beans dans votre ou vos fichiers "standard" Spring applicationContext.xml. Cela s'applique au moins à Spring 3.2. Probablement (au moins partiellement) avant
bmeurant
C'est la bonne réponse à mon avis. Il semble que le paramètre "useRegisteredSuffixPatternMatch" ait été introduit exactement pour le problème OPs.
lrxw
Ce n'était que la moitié de la solution pour moi. Voir la réponse de @Paul Aerer.
8bitjunkie
96

Mise à jour pour Spring 4: depuis 4.0.1, vous pouvez utiliser PathMatchConfigurer(via votre WebMvcConfigurer), par exemple

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

En xml, ce serait ( https://jira.spring.io/browse/SPR-10163 ):

<mvc:annotation-driven>
    [...]
    <mvc:path-matching registered-suffixes-only="true"/>
</mvc:annotation-driven>
Dave Syer
la source
11
c'est de loin la solution la plus propre: désactivez la fonction qui la cause, plutôt que de la pirater. Nous n'utilisons pas cette fonctionnalité de toute façon, donc le problème est résolu - parfait!
David Lavender
Où va la classe AllResources?
irl_irl
1
@ste_irl Ajoutez une classe java dans le même package que votre main.
kometen
5
Utilisez matcher.setUseSuffixPatternMatch(false)pour désactiver complètement la correspondance de suffixe.
Gian Marco Gherardi
Ce n'était que la moitié de la solution pour moi. Voir la réponse de @Paul Aerer.
8bitjunkie
87

En plus de la réponse de Martin Frey, cela peut également être corrigé en ajoutant une barre oblique de fin dans la valeur RequestMapping:

/path/{variable}/

N'oubliez pas que ce correctif ne prend pas en charge la maintenabilité. Il exige maintenant que tous les URI aient une barre oblique finale - quelque chose qui peut ne pas être apparent pour les utilisateurs d'API / les nouveaux développeurs. Comme il est probable que tous les paramètres ne contiennent pas de paramètre ., cela peut également créer des bogues intermittents

Michał Rybak
la source
2
C'est même une solution plus propre. J'ai dû découvrir à la dure comment IE définit les en-têtes d'acceptation en fonction du suffixe. J'ai donc voulu poster sur un mappage de demande .doc et j'ai toujours obtenu un téléchargement au lieu de la nouvelle page html. Cette approche a corrigé cela.
Martin Frey
c'est la solution la plus simple pour moi et a résolu mon problème; regexp semble un peu exagéré dans de nombreux cas
Riccardo Cossu
7
mais il entre en collision avec le comportement par défaut d'AngularJS pour supprimer automatiquement les barres obliques de fin. Cela peut être configuré dans les dernières versions d'Angular mais c'est quelque chose à suivre pendant des heures si vous ne savez pas ce qui se passe.
dschulten
1
@dschulten Et vous venez de m'économiser des heures de débogage, merci! Néanmoins, vous devez mentionner dans la réponse que la barre oblique finale sera requise dans les requêtes HTPP.
Hoffmann
1
C'est très dangereux! Je ne le recommanderais certainement pas, car quiconque implémenterait l'API s'y attendrait le moins. Très non maintenable.
sparkyspider
32

Dans Spring Boot Rest Controller, j'ai résolu ces problèmes en suivant les étapes:

RestController:

@GetMapping("/statusByEmail/{email:.+}/")
public String statusByEmail(@PathVariable(value = "email") String email){
  //code
}

Et depuis Rest Client:

Get http://mywebhook.com/statusByEmail/[email protected]/
GoutamS
la source
2
Cette réponse dépend d'une barre oblique de fin pour fonctionner.
8bitjunkie
2
fonctionne comme un charme (également sans barre oblique). Merci!
afe
27

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

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

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

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

Martin Čejka
la source
C'est parce que les accolades évaluent le RegEx et vous en avez déjà autourid
8bitjunkie
15

/somepath/{variable:.+}fonctionne en requestMappingbalise Java .

amit dahiya
la source
Je préfère cette réponse car elle ne montre pas ce qui n'a pas fonctionné.
johnnieb
Ne fonctionne pas pour les adresses e-mail avec plus d'un point.
8bitjunkie
1
@ 8bitjunkie Sth comme "/{code:.+}"fonctionne pour de nombreux points pas un, c'est- 61.12.7à- dire qu'il fonctionne également pour ie[email protected]
tryHard
13

Voici une approche qui repose uniquement sur la configuration 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;
    }
}
Bruno Carrier
la source
Merci, résolu pour moi. De plus, c'est très propre et explicite. +1
bkis
11

Un moyen assez simple de contourner ce problème consiste à ajouter une barre oblique de fin ...

par exemple:

utilisation :

/somepath/filename.jpg/

au lieu de:

/somepath/filename.jpg
Marcelo C.
la source
11

Dans Spring Boot, l'expression régulière résout le problème comme

@GetMapping("/path/{param1:.+}")
Dapper Dan
la source
Notez que cela ne fonctionne que pour un point. Cela ne fonctionne pas pour les adresses e-mail.
8bitjunkie
1
@ 8bitjunkie Sth comme "/{code:.+}"fonctionne pour de nombreux points pas un, c'est- 61.12.7à- dire qu'il fonctionne également pour ie[email protected]
tryHard
1
@ 8bitjunkie Je l'ai testé avec une adresse IP. Cela fonctionne très bien. Cela signifie donc que cela fonctionne pour plusieurs points.
Dapper Dan
6

La solution complète incluant les adresses e-mail dans les noms de chemin pour le printemps 4.2 est

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

Ajoutez ceci à l'application-xml

Paul Arer
la source
Upvote - c'est la seule réponse ici qui indique clairement que les éléments de configuration ContentNegotiationManagerFactoryBean et contentNegotiationManager sont requis
8bitjunkie
5

Si vous utilisez Spring 3.2.x et <mvc:annotation-driven />créez ce petit 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;
    }
}

Ensuite, mettez cela dans votre XML de configuration MVC:

<bean class="spring.DoNotTruncateMyUrls" />
Jukka
la source
Est-ce lié à ContentNegotiationManager?
Kanagavelu Sugumar
Mon code configure uniquement le RequestMappingHandlerMapping afin que les URL ne soient pas tronquées. ContentNegotiationManager est une autre bête.
Jukka
2
C'est vieux, mais vous n'en avez vraiment pas besoin BeanPostProcessorpour ça. Si vous utilisez, WebMvcConfigurationSupportvous pouvez remplacer la requestMappingHandlerMapping @Beanméthode. Si vous utilisez la configuration XML, vous pouvez simplement déclarer votre propre RequestMappingHandlerMappingbean et déclarer cette propriété.
Sotirios Delimanolis
Merci beaucoup, j'ai essayé un nombre différent de solutions pour le même problème, seule celle-ci a fonctionné pour moi. :-)
Nous sommes Borg
3

Enfin, j'ai trouvé une solution dans Spring Docs :

Pour désactiver complètement l'utilisation des extensions de fichier, vous devez définir les deux éléments suivants:

 useSuffixPatternMatching(false), see PathMatchConfigurer

 favorPathExtension(false), see ContentNegotiationConfigurer

L'ajout de cela à mon WebMvcConfigurerAdapterimplémentation a résolu le problème:

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

@Override
public void configurePathMatch(PathMatchConfigurer matcher) {
    matcher.setUseSuffixPatternMatch(false);
}
luboskrnac
la source
2

Pour moi le

@GetMapping(path = "/a/{variableName:.+}")

fonctionne mais seulement si vous encodez également le "point" dans votre URL de demande en "% 2E" alors cela fonctionne. Mais les URL doivent être toutes ... ce qui n'est pas un encodage "standard", bien que valide. Se sent comme un bug: |

L'autre solution de contournement, similaire à la méthode "barre oblique de fin" consiste à déplacer la variable qui aura le point "en ligne" ex:

@GetMapping (path = "/ {variableName} / a")

maintenant tous les points seront conservés, aucune modification ou regex n'est nécessaire.

rogerdpack
la source
1

Depuis Spring 5.2.4 (Spring Boot v2.2.6.RELEASE) PathMatchConfigurer.setUseSuffixPatternMatchet ContentNegotiationConfigurer.favorPathExtensionsont obsolètes ( https://spring.io/blog/2020/03/24/spring-framework-5-2-5-available-now et https://github.com/spring-projects/spring-framework/issues/24179 ).

Le vrai problème est que le client demande un type de média spécifique (comme .com) et Spring a ajouté tous ces types de média par défaut. Dans la plupart des cas, votre contrôleur REST ne produira que du JSON, il ne prendra donc pas en charge le format de sortie demandé (.com). Pour surmonter ce problème, vous devriez être tout à fait bien en mettant à jour votre contrôleur de repos (ou une méthode spécifique) pour prendre en charge le format 'ouput' ( @RequestMapping(produces = MediaType.ALL_VALUE)) et bien sûr autoriser les caractères comme un point ( {username:.+}).

Exemple:

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

Spring 5.3 et supérieur ne correspondront qu'aux suffixes enregistrés (types de support).

GuyT
la source