Spring MVC @PathVariable con punto (.) Viene troncato


361

Questa è la continuazione della domanda Spring MVC @PathVariable viene troncata

Il forum di primavera afferma che è stato corretto (versione 3.2) come parte di ContentNegotiationManager. vedi il link qui sotto.
https://jira.springsource.org/browse/SPR-6164
https://jira.springsource.org/browse/SPR-7632

Nella mia richiesta dell'applicazione, Parametro con .com viene troncato.

Qualcuno potrebbe spiegarmi come utilizzare questa nuova funzionalità? come è configurabile su xml?

Nota: spring forum- # 1 Spring MVC @PathVariable con punto (.) Viene troncato

Risposte:


485

Per quanto ne so questo problema appare solo per il pathvariable alla fine della richiesta.

Siamo stati in grado di risolverlo definendo il componente aggiuntivo regex nella richiesta.

 /somepath/{variable:.+}

1
Grazie, penso che questa correzione sia disponibile anche prima (prima di 3.2 V) ?. Tuttavia non mi piace questa correzione; dal momento che è necessario a tutti gli URL che devono essere gestiti nella mia applicazione ... e anche la futura implementazione degli URL deve essere curata da questo ...
Kanagavelu Sugumar

4
ecco come ho risolto il problema nella primavera del 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 sintassi è {variable_name:regular_expression}, quindi qui abbiamo la variabile denominata variable, il cui valore verrà abbinato usando regex .+(dove .significa "qualsiasi carattere" e +significa "una o più volte").
Michał Rybak,

4
@StefanHaberl se esegui una corrispondenza variablein modo regolare, Spring utilizza le sue funzioni di rilevamento del suffisso e tronca tutto dopo il punto. Quando si utilizza la corrispondenza regexp, tali funzionalità non vengono utilizzate: la variabile viene associata solo alla regexp fornita.
Michał Rybak,

9
@ Martin "variable:.+" non funziona quando c'è più di un punto nella variabile. ad esempio mettendo e-mail alla fine di percorsi riposanti come /path/abc@server.com.au. Il controller non viene nemmeno chiamato, ma funziona quando c'è solo un punto /path/abc@server.com. Qualche idea sul perché e / o una soluzione alternativa?
Boemo

242

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

Quindi se hai /somepath/{variable}:

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

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

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

Se non ti interessa il riconoscimento delle estensioni, puoi disabilitarlo sostituendo mvc:annotation-driven automagic:

<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 /somepath/{variable}:

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

nota: la differenza dalla configurazione predefinita è visibile solo se si ha una mappatura simile somepath/something.{variable} . 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>

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

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

Durante l'override, sii attento 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 & https: // github.com/resthub/resthub-spring-stack/issues/217


Perdonami, sono un principiante, quindi dove metti i config di fagioli? e a quale versione di primavera si applica?
Splash,

@Splash: è necessario definire questi bean nei file "standard" applicationContext.xml di Spring. Questo vale almeno per la primavera 3.2. Probabilmente (almeno in parte) prima
bmeurant,

Questa è la risposta corretta secondo me. Sembra che il parametro "useRegisteredSuffixPatternMatch" sia stato introdotto esattamente per il problema dei PO.
lrxw,

Questa era solo la metà della soluzione per me. Vedi la risposta di @Paul Aerer.
8bitjunkie,

96

Aggiornamento per Spring 4: dalla 4.0.1 è possibile utilizzare PathMatchConfigurer(tramite il proprio WebMvcConfigurer), ad es

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

In xml, sarebbe ( https://jira.spring.io/browse/SPR-10163 ):

<mvc:annotation-driven>
    [...]
    <mvc:path-matching registered-suffixes-only="true"/>
</mvc:annotation-driven>

11
questa è di gran lunga la soluzione più pulita: disattiva la funzione che la causa, piuttosto che hackerarla. Comunque non stiamo usando questa funzione, quindi il problema è stato risolto: perfetto!
David Lavender,

Dove va la classe AllResources?
irl_irl,

1
@ste_irl Aggiungi una classe java nello stesso pacchetto del tuo main.
kometen,

5
Utilizzare matcher.setUseSuffixPatternMatch(false)per disabilitare completamente la corrispondenza dei suffissi.
Gian Marco Gherardi,

Questa era solo la metà della soluzione per me. Vedi la risposta di @Paul Aerer.
8bitjunkie,

87

Oltre alla risposta di Martin Frey, questo può essere risolto aggiungendo una barra finale nel valore RequestMapping:

/path/{variable}/

Tieni presente che questa correzione non supporta la manutenibilità. Ora richiede che tutti gli URI abbiano una barra finale - qualcosa che potrebbe non essere evidente agli utenti API / ai nuovi sviluppatori. Dal momento che è probabile che non tutti i parametri possano contenere ., potrebbe anche creare bug intermittenti


2
Questa è anche una soluzione più pulita. Ho dovuto scoprire nel modo più duro che IE sta impostando accettare le intestazioni in base al suffisso. Quindi volevo postare su alcune richieste di doc. Doc e ho sempre avuto un download invece della nuova pagina HTML. Questo approccio lo ha risolto.
Martin Frey,

questa è la soluzione più semplice per me e ho risolto il mio problema; regexp sembra un po 'eccessivo per molti casi
Riccardo Cossu,

7
ma si scontra con il comportamento predefinito di AngularJS per rimuovere automaticamente le barre finali. Ciò può essere configurato nelle ultime versioni angolari, ma è qualcosa da tenere traccia per ore se non sai cosa sta succedendo.
dschulten,

1
@dschulten E mi hai appena salvato ore di debug, grazie! Tuttavia, dovresti menzionare nella risposta che la barra finale sarà richiesta nelle richieste HTPP.
Hoffmann,

1
Questo è molto pericoloso! Certamente non lo consiglierei poiché nessuno implementerebbe l'API meno se lo aspetterebbe. Molto non mantenibile.
sparkyspider

32

In Spring Boot Rest Controller, ho risolto questi procedendo come segue:

RestController:

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

E dal resto del cliente:

Get http://mywebhook.com/statusByEmail/abc.test@gmail.com/

2
Questa risposta dipende da una barra finale per funzionare.
8bitjunkie,

2
funziona come un incantesimo (anche senza barra). grazie!
AFE

27

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:.+}" funziona

Spero di aver aiutato qualcuno :)


Questo perché le parentesi graffe valutano il RegEx e ne hai già un po 'in giroid
8bitjunkie,

15

/somepath/{variable:.+}funziona nel requestMappingtag Java .


Preferisco questa risposta perché non mostra ciò che non ha funzionato.
johnnieb,

Non funziona con indirizzi e-mail con più di un punto.
8bitjunkie,

1
@ 8bitjunkie Sth come "/{code:.+}"funziona per molti punti, non uno, vale 61.12.7a dire , funziona anche per esempiok.a.p@o.i.n
provare Hard

13

Ecco un approccio che si basa esclusivamente sulla configurazione 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;
    }
}

Grazie, risolto per me. Inoltre, è molto pulito ed esplicito. +1
bkis

11

Un modo abbastanza semplice per aggirare questo problema è aggiungere una barra finale ...

per esempio:

uso :

/somepath/filename.jpg/

invece di:

/somepath/filename.jpg

11

In Spring Boot, l'espressione regolare risolve il problema come

@GetMapping("/path/{param1:.+}")

Nota che funziona solo per un punto. Non funziona per gli indirizzi email.
8bitjunkie,

1
@ 8bitjunkie Sth come "/{code:.+}"funziona per molti punti, non uno, vale 61.12.7a dire , funziona anche per esempiok.a.p@o.i.n
provare Hard

1
@ 8bitjunkie L'ho provato con l'indirizzo IP. Funziona molto bene Quindi, ciò significa che funziona per più punti.
Dapper Dan,

6

La soluzione completa che include gli indirizzi e-mail nei nomi dei percorsi per la primavera 4.2 è

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

Aggiungi questo a application-xml


Upvote: questa è l'unica risposta che chiarisce che sono richiesti sia gli elementi di configurazione ContentNegotiationManagerFactoryBean sia contentNegotiationManager
8bitjunkie,

5

Se stai usando Spring 3.2.xe <mvc:annotation-driven />, crea questo piccolo 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;
    }
}

Quindi inseriscilo nel tuo xml di configurazione MVC:

<bean class="spring.DoNotTruncateMyUrls" />

È correlato a ContentNegotiationManager?
Kanagavelu Sugumar

Il mio codice configura solo RequestMappingHandlerMapping in modo che gli URL non vengano troncati. ContentNegotiationManager è un'altra bestia.
Jukka,

2
Questo è vecchio, ma non hai davvero bisogno BeanPostProcessordi questo. Se lo usi WebMvcConfigurationSupportpuoi sovrascrivere il requestMappingHandlerMapping @Beanmetodo. Se usi la configurazione XML, puoi semplicemente dichiarare il tuo RequestMappingHandlerMappingbean e dichiarare quella proprietà.
Sotirios Delimanolis,

Grazie mille, ho provato un numero diverso di soluzioni per lo stesso problema, solo questo ha funzionato per me. :-)
Siamo a Borg il

3

Finalmente ho trovato una soluzione in Spring Docs :

Per disabilitare completamente l'uso delle estensioni di file, è necessario impostare entrambe le seguenti:

 useSuffixPatternMatching(false), see PathMatchConfigurer

 favorPathExtension(false), see ContentNegotiationConfigurer

L'aggiunta di questo alla mia WebMvcConfigurerAdapterimplementazione ha risolto il problema:

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

@Override
public void configurePathMatch(PathMatchConfigurer matcher) {
    matcher.setUseSuffixPatternMatch(false);
}

2

Per me il

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

funziona ma solo se codifichi anche il "punto" nell'URL della tua richiesta come "% 2E", allora funziona. Ma richiede che gli URL siano tutti ... che non è una codifica "standard", sebbene valida. Sembra qualcosa di un bug: |

L'altro aggirare, simile al modo "barra finale" è quello di spostare la variabile che avrà il punto "inline" es:

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

ora tutti i punti saranno conservati, non sono necessarie modifiche o regex.


1

A partire da Spring 5.2.4 (Spring Boot v2.2.6.RELEASE) PathMatchConfigurer.setUseSuffixPatternMatche ContentNegotiationConfigurer.favorPathExtensionsono stati deprecati ( https://spring.io/blog/2020/03/24/spring-framework-5-2-5-available-now e https://github.com/spring-projects/spring-framework/issues/24179 ).

Il vero problema è che il client richiede un tipo di supporto specifico (come .com) e Spring ha aggiunto tutti questi tipi di supporto per impostazione predefinita. Nella maggior parte dei casi il controller REST produrrà solo JSON, quindi non supporterà il formato di output richiesto (.com). Per ovviare a questo problema dovresti essere bravo aggiornando il tuo controller di riposo (o metodo specifico) per supportare il formato "ouput" ( @RequestMapping(produces = MediaType.ALL_VALUE)) e, naturalmente, consentire caratteri come un punto ({username:.+} ).

Esempio:

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

La primavera 5.3 e successive corrisponderanno solo ai suffissi registrati (tipi di media).

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.