Spring MVC @PathVariable sendo truncado

142

Eu tenho um controlador que fornece acesso RESTful às informações:

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

O problema que estou enfrentando é que, se eu atingir o servidor com uma variável de caminho com caracteres especiais, ela será truncada. Por exemplo: http: // localhost: 8080 / blah-server / blah / get / blah2010.08.19-02: 25: 47

O parâmetro blahName será blah2010.08

No entanto, a chamada para request.getRequestURI () contém todas as informações passadas.

Alguma idéia de como impedir que o Spring trunque o @PathVariable?

phogel
fonte

Respostas:

149

Tente uma expressão regular para o @RequestMappingargumento:

RequestMapping(method = RequestMethod.GET, value = Routes.BLAH_GET + "/{blahName:.+}")
Earldouglas
fonte
1
Obrigado pela resposta, isso me ajudou a resolver um caso em que os nomes de usuários foram cortados de alguma forma .. (-: A outra opção com 'useDefaultSuffixPattern' não era uma opção, porque estamos usando as classes Spring do @Configuration em vez do XML.
evandongen
3
Isso funciona, mas qual é o significado do cólon no regex?
Noah Yetter
6
Noah, eu não uso isso há muito tempo, mas acho que os dois pontos separam a expressão regular do nome do argumento para vinculá-lo.
earldouglas
3
tivemos um problema semelhante /item/[email protected], qualquer coisa após @ foi truncado, isso foi resolvido adicionando outra barra /item/[email protected]/
Titi Wangsa bin Damhore
59

Provavelmente, isso está intimamente relacionado ao SPR-6164 . Resumidamente, a estrutura tenta aplicar alguns conhecimentos à interpretação de URI, removendo o que considera extensões de arquivo. Isso teria o efeito de se transformar blah2010.08.19-02:25:47em blah2010.08, uma vez que pensa que .19-02:25:47é uma extensão de arquivo.

Conforme descrito no problema vinculado, você pode desativar esse comportamento declarando seu próprio DefaultAnnotationHandlerMapping bean no contexto do aplicativo e configurando sua useDefaultSuffixPatternpropriedade para false. Isso substituirá o comportamento padrão e o impedirá de molestar seus dados.

skaffman
fonte
3
Ativar a negociação de conteúdo baseada em extensão por padrão parece uma escolha tão estranha. Quantos sistemas realmente expõem o mesmo recurso em diferentes formatos na prática?
Affe
Tentei isso de manhã e ainda tinha variáveis ​​de caminho truncadas.
Phogel
30
+1 para uma grande resposta e também para usar a frase "molestar seus dados"
Chris Thompson
11
Para usuários do Spring 3.1 - se você estiver usando o novo RequestMappingHandlerMapping, a propriedade a ser configurada é useSuffixPatternMatch(também para false). @ Ted: a questão vinculada menciona que, na versão 3.2, eles esperam adicionar um pouco mais de controle, para que não precise ser tudo ou nada.
Nick
2
No Spring 4.2, isso é um pouco mais fácil de configurar. Usamos as classes de configuração Java e estendemos o WebMvcConfigurationSupportque fornece um gancho simples: public void configurePathMatch(PathMatchConfigurer configurer)- basta substituí-lo e configurar o caminho que você deseja.
pmckeown
31

O Spring considera que qualquer coisa por trás do último ponto é uma extensão de arquivo, como .json ou .xmle truncar-lo para recuperar o seu parâmetro.

Então, se você tem /{blahName}:

  • /param, /param.json, /param.xmlOu /param.anythingirá resultar em uma param com valorparam
  • /param.value.json, /param.value.xmlou/param.value.anythingresultará em um parâmetro com valorparam.value

Se você alterar seu mapeamento para /{blahName:.+} o sugerido, qualquer ponto, incluindo o último, será considerado como parte do seu parâmetro:

  • /param resultará em um parâmetro com valor param
  • /param.json resultará em um parâmetro com valor param.json
  • /param.xml resultará em um parâmetro com valor param.xml
  • /param.anything resultará em um parâmetro com valor param.anything
  • /param.value.json resultará em um parâmetro com valor param.value.json
  • ...

Se você não se importa com o reconhecimento de extensão, pode desativá-lo substituindo mvc:annotation-drivenautomagic:

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

Então, novamente, se você tiver /{blahName}:

  • /param, /param.json, /param.xmlOu /param.anythingirá resultar em uma param com valorparam
  • /param.value.json, /param.value.xmlou /param.value.anythingresultará em um parâmetro com valorparam.value

Nota: a diferença da configuração padrão é visível apenas se você tiver um mapeamento semelhante /something.{blahName}. Consulte Problema do projeto Resthub .

Se você deseja manter o gerenciamento de extensões, desde o Spring 3.2, também é possível definir a propriedade useRegisteredSuffixPatternMatch do bean RequestMappingHandlerMapping para manter o reconhecimento do sufixoPattern ativado, mas limitado à extensão registrada.

Aqui você define apenas extensões 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>

Observe que o mvc: driven-anotation agora aceita uma opção contentNegotiation para fornecer um bean personalizado, mas a propriedade RequestMappingHandlerMapping deve ser alterada para true (padrão false) (consulte https://jira.springsource.org/browse/SPR-7632 )

Por esse motivo, você ainda precisa substituir toda a configuração orientada a anotações do mvc: Abri um ticket para o Spring para solicitar um RequestMappingHandlerMapping personalizado: https://jira.springsource.org/browse/SPR-11253 . Por favor vote se você estiver interessado.

Ao substituir, tome cuidado para considerar também a substituição personalizada do gerenciamento de Execução. Caso contrário, todos os seus mapeamentos de exceção personalizados falharão. Você precisará reutilizar messageCoverters com um 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>

Eu implementei, no projeto de código aberto Resthub do qual faço parte, um conjunto de testes sobre esses assuntos: consulte https://github.com/resthub/resthub-spring-stack/pull/219/files e https: // github.com/resthub/resthub-spring-stack/issues/217

bmeurant
fonte
16

Tudo após o último ponto é interpretado como extensão de arquivo e cortado por padrão.
No seu XML de configuração da primavera, você pode adicionar DefaultAnnotationHandlerMappinge definir useDefaultSuffixPatterncomo false(o padrão é true).

Então abra o seu XML da primavera mvc-config.xml(ou como ele é chamado) e adicione

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

Agora seu @PathVariable blahName(e todos os outros também) deve conter o nome completo, incluindo todos os pontos.

EDIT: Aqui está um link para a API da Primavera

Jan
fonte
Não tentei, mas outros afirmam que você também precisará removê-lo, <mvc:annotation-driven />se aplicável.
Arjan17 /
7

Também encontrei o mesmo problema e definir a propriedade como false também não me ajudou. Contudo, a API diz :

Observe que os caminhos que incluem um sufixo ".xxx" ou terminam com "/" já não serão transformados usando o padrão de sufixo padrão em nenhum caso.

Tentei adicionar "/ end" ao meu URL RESTful e o problema desapareceu. Não estou satisfeito com a solução, mas funcionou.

BTW, não sei o que os designers do Spring estavam pensando quando adicionaram esse "recurso" e o ativaram por padrão. IMHO, deve ser removido.

Steve11235
fonte
Concordo. Eu fui recentemente mordido por isso.
llambda
7

Usando a classe de configuração Java correta:

@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);
    }
}
jebeaudet
fonte
Isso funcionou muito bem para mim. Rodando no Tomcat Spring versão 4.3.14
Dave
4

Eu resolvi por esse hack

1) Adicionado HttpServletRequest em @PathVariable como abaixo

 @PathVariable("requestParam") String requestParam, HttpServletRequest request) throws Exception { 

2) Obtenha o URL diretamente (neste nível, sem truncamento) na solicitação

request.getPathInfo() 

Spring MVC @PathVariable com ponto (.) Está sendo truncado

Kanagavelu Sugumar
fonte
3

Acabei de me deparar com isso e as soluções aqui geralmente não funcionavam como eu esperava.

Sugiro usar uma expressão SpEL e vários mapeamentos, por exemplo

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

O problema de extensão de arquivo existe apenas se o parâmetro estiver na última parte do URL. mudança

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

para

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

e tudo ficará bem de novo

chrismarx
fonte
3

Se você pode editar o endereço para o qual as solicitações são enviadas, a correção simples seria adicionar uma barra final (e também no @RequestMappingvalor):

/path/{variable}/

então o mapeamento ficaria assim:

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

Consulte também Spring MVC @PathVariable com ponto (.) Está sendo truncado .

Michał Rybak
fonte
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>       
Alnamrouti com tarifa
fonte
3

adicionar o ":. +" funcionou para mim, mas não até remover os colchetes externos.

value = {"/username/{id:.+}"} não funcionou

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

Espero ter ajudado alguém:]

Martin Čejka
fonte
2

Solução de configuração baseada em Java para impedir o truncamento (usando uma classe não 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;
    }
}

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

ATUALIZAR:

Percebi que havia alguns problemas com a configuração automática do Spring Boot quando usei a abordagem acima (algumas configurações automáticas não são eficazes).

Em vez disso, comecei a usar a BeanPostProcessorabordagem. Parecia estar funcionando melhor.

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 em: http://ronaldxq.blogspot.com/2014/10/spring-mvc-setting-alwaysusefullpath-on.html

burcakulug
fonte
2

se você tiver certeza de que seu texto não corresponderá a nenhuma das extensões padrão, use o código abaixo:

@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {

    @Override
    public void configurePathMatch(PathMatchConfigurer configurer) {
        configurer.setUseRegisteredSuffixPatternMatch(true);
    }
}
Bassem Reda Zohdy
fonte
1

Minha solução preferível para impedir que o Spring MVC @PathVariable seja truncado é adicionar uma barra final no final da variável de caminho.

Por exemplo:

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

Portanto, a solicitação será semelhante a:

http://localhost:8080/api/email/[email protected]/
Johnny
fonte
1

O problema que você está enfrentando se deve à primavera interpretando a última parte da uri após o ponto (.) Como uma extensão de arquivo como .json ou .xml. Portanto, quando o Spring tenta resolver a variável do caminho, ele simplesmente trunca o restante dos dados depois de encontrar um ponto (.) No final do URI. Nota: isso também acontece apenas se você mantiver a variável path no final do URI.

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

No URL acima firstValue = "gallery.df" e secondValue = "link", o último bit após o. fica truncado quando a variável de caminho é interpretada.

Portanto, para evitar isso, existem duas maneiras possíveis:

1.) Usando um mapeamento regexp

Use uma regex na parte final do mapeamento

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

Usando +, indicamos qualquer valor após o ponto também fazer parte da variável de caminho.

2.) Adicionando uma barra no final do nosso @PathVariable

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

Isso incluirá nossa segunda variável, protegendo-a do comportamento padrão do Spring.

3) Substituindo a configuração padrão do webmvc do Spring

O Spring fornece maneiras de substituir as configurações padrão que são importadas usando as anotações @EnableWebMvc . Podemos personalizar a configuração do Spring MVC declarando nosso próprio bean DefaultAnnotationHandlerMapping no contexto do aplicativo e definindo sua propriedade useDefaultSuffixPattern como false. Exemplo:

@Configuration
public class CustomWebConfiguration extends WebMvcConfigurationSupport {

    @Bean
    public RequestMappingHandlerMapping 
      requestMappingHandlerMapping() {

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

Lembre-se de que a substituição dessa configuração padrão afeta todos os URLs.

Nota: aqui estamos estendendo a classe WebMvcConfigurationSupport para substituir os métodos padrão. Há mais uma maneira de substituir as configurações de deault implementando a interface WebMvcConfigurer. Para obter mais detalhes sobre isso, leia: https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/servlet/config/annotation/EnableWebMvc.html

Ananthapadmanabhan
fonte