Spring MVC @PathVariable con punto (.) Se está truncando

361

Esta es la continuación de la pregunta Spring MVC @PathVariable truncada

El foro de Spring afirma que se ha solucionado (versión 3.2) como parte de ContentNegotiationManager. ver el siguiente enlace.
https://jira.springsource.org/browse/SPR-6164
https://jira.springsource.org/browse/SPR-7632

En mi solicitud requestParameter con .com está truncado.

¿Alguien podría explicarme cómo usar esta nueva función? ¿Cómo es configurable en xml?

Nota: Spring forum- # 1 Spring MVC @PathVariable con punto (.) Se está truncando

Kanagavelu Sugumar
fuente

Respuestas:

485

Hasta donde sé, este problema aparece solo para la variable de ruta al final de la asignación de solicitudes.

Pudimos resolver eso definiendo el complemento regex en el mapa de solicitud.

 /somepath/{variable:.+}
Martin Frey
fuente
1
Gracias, creo que esta solución también está disponible antes (antes de 3.2V). Sin embargo, no me gusta esta solución; ya que se necesita en absoluto la url que tiene que ser manejado en mi solicitud ... realización URL futuro también para ser atendido de esta ...
Kanagavelu Sugumar
44
así es como resolví el problema en la primavera 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 sintaxis es {variable_name:regular_expression}, por lo que aquí tenemos una variable llamada variable, cuyo valor se combinará usando regex .+(donde .significa 'cualquier carácter' y +significa 'una o más veces').
Michał Rybak
44
@StefanHaberl si coincide variablede manera regular, Spring usa sus funciones de detección de sufijos y trunca todo después del punto. Cuando utiliza la coincidencia de expresiones regulares, esas características no se utilizan: la variable solo se corresponde con la expresión regular que proporciona.
Michał Rybak
99
@martin "variable:.+"no funciona cuando hay más de un punto en la variable. por ejemplo, poner correos electrónicos al final de rutas relajantes como /path/[email protected]. El controlador ni siquiera recibe una llamada, pero funciona cuando solo hay un punto /path/[email protected]. ¿Alguna idea de por qué y / o una solución alternativa?
Bohemio
242

Spring considera que todo lo que está detrás del último punto es una extensión de archivo como .jsono .xmly lo truca para recuperar su parámetro.

Entonces si tienes /somepath/{variable}:

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

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

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

Si no le importa el reconocimiento de extensión, 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 /somepath/{variable}:

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

nota: la diferencia con la configuración predeterminada es visible solo si tiene un mapeo como somepath/something.{variable}. 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í define solo 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 está interesado.

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. Tendrá que 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 & https: // github.com/resthub/resthub-spring-stack/issues/217

bmeurant
fuente
Perdóname, soy un novato, entonces, ¿dónde pones las configuraciones de bean? ¿Y a qué versión de primavera se aplica?
Splash
@Splash: debe definir estos beans en sus archivos "estándar" Spring applicationContext.xml. Esto se aplica al Spring 3.2 al menos. Probablemente (al menos parcialmente) antes
bmeurant
Esta es la respuesta correcta en mi opinión. Parece que el parámetro "useRegisteredSuffixPatternMatch" se introdujo exactamente para el problema de los OP.
lrxw
Esto fue solo la mitad de la solución para mí. Ver la respuesta de @Paul Aerer.
8bitjunkie
96

Actualización para Spring 4: desde 4.0.1 puede usar PathMatchConfigurer(a través de su WebMvcConfigurer), por ejemplo

@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, sería ( https://jira.spring.io/browse/SPR-10163 ):

<mvc:annotation-driven>
    [...]
    <mvc:path-matching registered-suffixes-only="true"/>
</mvc:annotation-driven>
Dave Syer
fuente
11
esta es, con mucho, la solución más limpia: apague la función que la causa, en lugar de piratearla. No estamos utilizando esta función de todos modos, así que el problema está resuelto, ¡perfecto!
David Lavender
¿A dónde va la clase AllResources?
irl_irl
1
@ste_irl Agregue una clase java en el mismo paquete que su main.
kometen
55
Utilícelo matcher.setUseSuffixPatternMatch(false)para deshabilitar completamente la coincidencia de sufijo.
Gian Marco Gherardi
Esto fue solo la mitad de la solución para mí. Ver la respuesta de @Paul Aerer.
8bitjunkie
87

Además de la respuesta de Martin Frey, esto también se puede solucionar agregando una barra diagonal al valor de RequestMapping:

/path/{variable}/

Tenga en cuenta que esta solución no admite la mantenibilidad. Ahora requiere que todos los URI tengan una barra diagonal final, algo que puede no ser evidente para los usuarios de API / nuevos desarrolladores. Debido a que es probable que no todos los parámetros tengan un elemento ., también puede crear errores intermitentes

Michał Rybak
fuente
2
Eso es incluso una solución más limpia. Tuve que descubrir de la manera difícil que IE está configurando aceptar encabezados de acuerdo con el sufijo. Así que quería publicar en algunos mapas de solicitudes .doc y siempre recibí una descarga en lugar de la nueva página html. Este enfoque solucionó eso.
Martin Frey
Esta es la solución más simple para mí y resolvió mi problema; regexp parece un poco exagerado para muchos casos
Riccardo Cossu
77
pero colisiona con el comportamiento predeterminado de AngularJS para eliminar las barras inclinadas automáticamente. Eso se puede configurar en las últimas versiones de Angular, pero es algo que se debe rastrear durante horas si no sabe lo que está sucediendo.
dschulten
1
@dschulten Y me acabas de ahorrar horas de depuración, ¡gracias! Sin embargo, debe mencionar en la respuesta que se requerirá la barra diagonal final en las solicitudes de HTPP.
Hoffmann
1
¡Esto es muy peligroso! Ciertamente no lo recomendaría, ya que cualquiera que implemente la API menos lo esperaría. Muy no mantenible.
sparkyspider
32

En Spring Boot Rest Controller, los resolví siguiendo estos pasos:

RestController:

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

Y desde Rest Client:

Get http://mywebhook.com/statusByEmail/[email protected]/
GoutamS
fuente
2
Esta respuesta depende de una barra inclinada final para poder funcionar.
8bitjunkie
2
funciona como un encanto (también sin barra inclinada). ¡gracias!
afe
27

agregar el ":. +" funcionó para mí, pero no hasta que eliminé las llaves exteriores.

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

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

Espero haber ayudado a alguien :)

Martin Čejka
fuente
Esto se debe a que los corchetes evalúan el RegEx y ya tienes algunosid
8bitjunkie
15

/somepath/{variable:.+}Funciona en la requestMappingetiqueta Java .

amit dahiya
fuente
Prefiero esta respuesta porque no muestra lo que no funcionó.
johnnieb
No funciona para direcciones de correo electrónico con más de un punto.
8bitjunkie
1
@ 8bitjunkie Sth como "/{code:.+}"funciona para muchos puntos, no uno, es decir 61.12.7, también funciona para, por ejemplo[email protected]
tryHard
13

Aquí hay un enfoque que se basa exclusivamente en la configuración de 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
fuente
Gracias, lo resolvió por mí. Además, es muy limpio y explícito. +1
bkis
11

Una forma bastante fácil de solucionar este problema es agregar una barra diagonal final ...

p.ej:

utilizar :

/somepath/filename.jpg/

en vez de:

/somepath/filename.jpg
Marcelo C.
fuente
11

En Spring Boot, la expresión regular resuelve el problema como

@GetMapping("/path/{param1:.+}")
Dapper Dan
fuente
Tenga en cuenta que esto solo funciona para un punto. No funciona para direcciones de correo electrónico.
8bitjunkie
1
@ 8bitjunkie Sth como "/{code:.+}"funciona para muchos puntos, no uno, es decir 61.12.7, también funciona para, por ejemplo[email protected]
tryHard
1
@ 8bitjunkie Lo he probado con la dirección IP. Funciona muy bien Entonces, eso significa que funciona para múltiples puntos.
Dapper Dan
6

La solución completa que incluye direcciones de correo electrónico en los nombres de ruta para spring 4.2 es

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

Agregue esto a la aplicación-xml

Paul Arer
fuente
Voto a favor: esta es la única respuesta aquí que deja en claro que tanto los elementos de configuración ContentNegotiationManagerFactoryBean como contentNegotiationManager son obligatorios
8bitjunkie
5

Si está utilizando Spring 3.2.xy <mvc:annotation-driven />cree este pequeño 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;
    }
}

Luego ponga esto en su MVC config xml:

<bean class="spring.DoNotTruncateMyUrls" />
Jukka
fuente
¿Está relacionado con ContentNegotiationManager?
Kanagavelu Sugumar
Mi código solo configura RequestMappingHandlerMapping para que las URL no se trunqueen. ContentNegotiationManager es otra bestia.
Jukka
2
Esto es viejo, pero realmente no necesitas un BeanPostProcessorpara esto. Si lo usa WebMvcConfigurationSupport, puede anular el requestMappingHandlerMapping @Beanmétodo. Si usa la configuración XML, puede declarar su propio RequestMappingHandlerMappingbean y declarar esa propiedad.
Sotirios Delimanolis
Muchas gracias, probé una cantidad diferente de soluciones para el mismo problema, solo esta funcionó para mí. :-)
Somos Borg
3

Finalmente encontré solución en Spring Docs :

Para deshabilitar completamente el uso de extensiones de archivo, debe configurar lo siguiente:

 useSuffixPatternMatching(false), see PathMatchConfigurer

 favorPathExtension(false), see ContentNegotiationConfigurer

Agregar esto a mi WebMvcConfigurerAdapterimplementación resolvió el problema:

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

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

Para mi el

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

funciona, pero solo si también codifica el "punto" en su url de solicitud como "% 2E", entonces funciona. Pero requiere que todas las URL sean eso ... lo cual no es una codificación "estándar", aunque es válida. Se siente como un error: |

La otra solución, similar a la forma de "barra inclinada final" es mover la variable que tendrá el punto "en línea" ej:

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

ahora todos los puntos se conservarán, no se necesitan modificaciones ni expresiones regulares.

rogerdpack
fuente
1

A partir de Spring 5.2.4 (Spring Boot v2.2.6.RELEASE) PathMatchConfigurer.setUseSuffixPatternMatchy ContentNegotiationConfigurer.favorPathExtensionhan quedado en desuso ( https://spring.io/blog/2020/03/24/spring-framework-5-2-5-available-now and https://github.com/spring-projects/spring-framework/issues/24179 ).

El verdadero problema es que el cliente solicita un tipo de medio específico (como .com) y Spring agregó todos esos tipos de medios de forma predeterminada. En la mayoría de los casos, su controlador REST solo producirá JSON, por lo que no admitirá el formato de salida solicitado (.com). Para superar este problema, debería ser bueno actualizando su controlador de descanso (o método específico) para admitir el formato de 'salida' ( @RequestMapping(produces = MediaType.ALL_VALUE)) y, por supuesto, permitir caracteres como un punto ( {username:.+}).

Ejemplo:

@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 y superior solo coincidirá con sufijos registrados (tipos de medios).

GuyT
fuente