Spring MVC @PathVariable se trunca


142

Tengo un controlador que proporciona acceso RESTful a la información:

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

El problema que estoy experimentando es que si golpeo el servidor con una variable de ruta con caracteres especiales, se trunca. Por ejemplo: http: // localhost: 8080 / blah-server / blah / get / blah2010.08.19-02: 25: 47

El parámetro blahName será blah2010.08

Sin embargo, la llamada a request.getRequestURI () contiene toda la información que se pasa.

¿Alguna idea de cómo evitar que Spring trunca el @PathVariable?


Respuestas:


149

Pruebe con una expresión regular para el @RequestMappingargumento:

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

1
Gracias por la respuesta, esto me ayudó a resolver un caso en el que los nombres de usuario se recortaron de alguna manera ... (-: La otra opción con 'useDefaultSuffixPattern' no era una opción porque estamos usando clases de primavera @Configuration en lugar de XML.
evandongen

3
Esto funciona, pero ¿cuál es el significado del colon en la expresión regular?
Noah Yetter

66
Noah, no he usado esto en mucho tiempo, pero creo que los dos puntos separan la expresión regular del nombre del argumento para vincularlo.
Earldouglas

3
tuvimos un problam similar /item/user@abc.com, cualquier cosa después de que @ se truncara, esto se resolvió agregando otra barra /item/user@abc.com/
Titi Wangsa bin Damhore el

59

Esto probablemente esté estrechamente relacionado con SPR-6164 . Brevemente, el marco intenta aplicar algunos conocimientos a la interpretación de URI, eliminando lo que cree que son extensiones de archivo. Esto tendría el efecto de convertirse blah2010.08.19-02:25:47en blah2010.08, ya que cree que .19-02:25:47es una extensión de archivo.

Como se describe en el problema vinculado, puede deshabilitar este comportamiento declarando su propio DefaultAnnotationHandlerMappingbean en el contexto de la aplicación y estableciendo su useDefaultSuffixPatternpropiedad en false. Esto anulará el comportamiento predeterminado y evitará que moleste sus datos.


3
Activar la negociación de contenido basada en extensiones de forma predeterminada parece una elección tan extraña. ¿Cuántos sistemas realmente exponen el mismo recurso en diferentes formatos en la práctica?
Afe

Intenté esto la mañana y todavía tenía variables de ruta truncadas.
phogel

30
+1 para una gran respuesta y también para usar la frase "molestando sus datos"
Chris Thompson

11
Para los usuarios de Spring 3.1: si está utilizando el nuevo RequestMappingHandlerMapping, la propiedad para establecer es useSuffixPatternMatch(también para false). @Ted: el problema vinculado menciona que en 3.2 esperan agregar un poco más de control para que no tenga que ser todo o nada.
Nick

2
En Spring 4.2 esto es un poco más fácil de configurar. Usamos clases de configuración de Java y ampliamos lo WebMvcConfigurationSupportque proporciona un enlace simple: public void configurePathMatch(PathMatchConfigurer configurer)simplemente anule eso y configure la ruta que coincida como desee.
pmckeown

31

Spring considera que cualquier cosa detrás del último punto es una extensión de archivo como .jsono .xmly truncarlo para recuperar su parámetro.

Entonces si tienes /{blahName}:

  • /param, /param.json, /param.xmlO /param.anythingdará lugar a un parámetro con el valorparam
  • /param.value.json, /param.value.xmlo /param.value.anythingdará como resultado un parámetro con valorparam.value

Si cambia la asignación a /{blahName:.+}lo sugerido, cualquier punto, incluido el último, se considerará parte de su parámetro:

  • /param resultará en un parámetro con valor param
  • /param.json resultará en un parámetro con valor param.json
  • /param.xml resultará en un parámetro con valor param.xml
  • /param.anything resultará en un parámetro con valor param.anything
  • /param.value.json resultará en un parámetro con valor param.value.json
  • ...

Si no le importa el reconocimiento de extensiones, puede deshabilitarlo anulando el mvc:annotation-drivenautómata:

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

Entonces, de nuevo, si tienes /{blahName}:

  • /param, /param.json, /param.xmlO /param.anythingdará lugar a un parámetro con el valorparam
  • /param.value.json, /param.value.xmlo /param.value.anythingdará como resultado un parámetro con valorparam.value

Nota: la diferencia con la configuración predeterminada es visible solo si tiene una asignación como /something.{blahName}. Ver problema del proyecto Resthub .

Si desea mantener la administración de extensiones, desde Spring 3.2 también puede establecer la propiedad useRegisteredSuffixPatternMatch del bean RequestMappingHandlerMapping para mantener activado el reconocimiento de sufijoPattern pero limitado a la extensión registrada.

Aquí solo define las extensiones json y 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>

Tenga en cuenta que mvc: impulsado por anotaciones acepta ahora una opción contentNegotiation para proporcionar un bean personalizado, pero la propiedad de RequestMappingHandlerMapping debe cambiarse a verdadero (valor predeterminado falso) (cf. https://jira.springsource.org/browse/SPR-7632 )

Por esa razón, aún debe anular toda la configuración mvc: basada en anotaciones. Abrí un boleto a Spring para solicitar un RequestMappingHandlerMapping personalizado: https://jira.springsource.org/browse/SPR-11253 . Por favor vote si le interesa.

Al anular, tenga cuidado de considerar también la anulación de la gestión de ejecución personalizada. De lo contrario, todas sus asignaciones de excepción personalizadas fallarán. Deberá reutilizar messageCoverters con un bean de lista:

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

Implementé, en el proyecto de código abierto Resthub del que formo parte, un conjunto de pruebas sobre estos temas: consulte https://github.com/resthub/resthub-spring-stack/pull/219/files y https: // github.com/resthub/resthub-spring-stack/issues/217


16

Todo después del último punto se interpreta como extensión de archivo y se corta de manera predeterminada.
En su spring config xml puede agregar DefaultAnnotationHandlerMappingy configurar useDefaultSuffixPatterna false(el valor predeterminado es true).

Entonces abra su spring xml mvc-config.xml(o como se llame) y agregue

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

Ahora tu @PathVariable blahName(y todos los demás también) debe contener el nombre completo, incluidos todos los puntos.

EDITAR: Aquí hay un enlace a la API de primavera


No lo he intentado, pero otros afirman que también deberías eliminarlo <mvc:annotation-driven />si corresponde.
Arjan

7

También me encontré con el mismo problema, y ​​establecer la propiedad en falso tampoco me ayudó. Sin embargo, la API dice :

Tenga en cuenta que las rutas que incluyen un sufijo ".xxx" o terminan con "/" ya no se transformarán utilizando el patrón de sufijo predeterminado en ningún caso.

Intenté agregar "/ end" a mi URL RESTful, y el problema desapareció. No estoy satisfecho con la solución, pero funcionó.

Por cierto, no sé qué pensaban los diseñadores de Spring cuando agregaron esta "característica" y luego la activaron de manera predeterminada. En mi humilde opinión, debe ser eliminado.


Estoy de acuerdo. Recientemente fui mordido por esto.
llambda

7

Usando la clase de configuración correcta de Java:

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

Esto funciono muy bien para mi. Se ejecuta en Tomcat Spring versión 4.3.14
Dave


3

Me encontré con esto y las soluciones aquí generalmente no funcionaron como esperaba.

Sugiero usar una expresión SpEL y múltiples asignaciones, por ejemplo

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

3

El problema de extensión de archivo solo existe si el parámetro está en la última parte de la URL. Cambio

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

a

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

y todo estará bien otra vez


3

Si puede editar la dirección a la que se envían las solicitudes, una solución simple sería agregarles una barra diagonal (y también en el @RequestMappingvalor):

/path/{variable}/

entonces el mapeo se vería así:

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

Consulte también Spring MVC @PathVariable con punto (.) Se está truncando .


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

agregar el ":. +" funcionó para mí, pero no hasta que eliminé los corchetes externos.

value = {"/username/{id:.+}"} no funcionó

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

Espero haber ayudado a alguien:]


2

Solución de configuración basada en Java para evitar el truncamiento (utilizando una clase no obsoleta):

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

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

ACTUALIZAR:

Me di cuenta de que tenía algunos problemas con la configuración automática de Spring Boot cuando utilicé el enfoque anterior (algunas configuraciones automáticas no son efectivas).

En cambio, comencé a usar el BeanPostProcessorenfoque. Parecía estar funcionando mejor.

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

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


2

si está seguro de que su texto no coincidirá con ninguna de las extensiones predeterminadas, puede usar el siguiente código:

@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {

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

1

Mi solución preferible para evitar que Spring MVC @PathVariable se trunca es agregar una barra diagonal final al final de la variable de ruta.

Por ejemplo:

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

Entonces, la solicitud se verá así:

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

1

El problema que enfrenta se debe a que Spring interpreta la última parte de la uri después del punto (.) Como una extensión de archivo como .json o .xml. Entonces, cuando spring intenta resolver la variable de ruta, simplemente trunca el resto de los datos después de encontrar un punto (.) Al final de la uri. Nota: esto también sucede solo si mantiene la variable de ruta al final de la uri.

Por ejemplo, considere 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) {
        // ...  
    }
}

En la url anterior firstValue = "gallery.df" y secondValue = "link", el último bit después de. se trunca cuando se interpreta la variable de ruta.

Entonces, para evitar esto, hay dos formas posibles:

1.) Usando un mapeo regexp

Use una expresión regular al final de la asignación

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

Al usar +, indicamos que cualquier valor después del punto también será parte de la variable de ruta.

2.) Agregar una barra al final de nuestro @PathVariable

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

Esto incluirá nuestra segunda variable protegiéndola del comportamiento predeterminado de Spring.

3) Al anular la configuración predeterminada de Spring webmvc

Spring proporciona formas de anular las configuraciones predeterminadas que se importan utilizando las anotaciones @EnableWebMvc. Podemos personalizar la configuración de Spring MVC declarando nuestro propio bean DefaultAnnotationHandlerMapping en el contexto de la aplicación y estableciendo su propiedad useDefaultSuffixPattern en falso. Ejemplo:

@Configuration
public class CustomWebConfiguration extends WebMvcConfigurationSupport {

    @Bean
    public RequestMappingHandlerMapping 
      requestMappingHandlerMapping() {

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

Tenga en cuenta que anular esta configuración predeterminada afecta a todas las URL.

Nota: aquí estamos ampliando la clase WebMvcConfigurationSupport para anular los métodos predeterminados. Hay una forma más de anular las configuraciones predeterminadas mediante la implementación de la interfaz WebMvcConfigurer. Para obtener más detalles sobre esto, lea: https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/servlet/config/annotation/EnableWebMvc.html

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.