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


361

Esta é a continuação da pergunta Spring MVC @PathVariable ficando truncada

O fórum do Spring declara que foi corrigido (versão 3.2) como parte do ContentNegotiationManager. veja o link abaixo.
https://jira.springsource.org/browse/SPR-6164
https://jira.springsource.org/browse/SPR-7632

No meu pedido requestParameter com .com está truncado.

Alguém poderia me explicar como usar esse novo recurso? como é configurável no xml?

Nota: spring forum- # 1 Spring MVC @PathVariable com ponto (.) Está sendo truncado

Respostas:


485

Tanto quanto eu sei, esse problema aparece apenas para o pathvariable no final do requestmapping.

Conseguimos resolver isso definindo o complemento regex no requestmapping.

 /somepath/{variable:.+}

11
Obrigado, acho que esta correção também está disponível anteriormente (antes de 3.2V) ?. No entanto, eu não gosto dessa correção; uma vez que é necessária em toda a url que tem de ser tratado na minha candidatura ... e implementação futura URL também deve ser tomado cuidado de esta ...
Kanagavelu Sugumar

4
aqui está como eu resolvi o problema na 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, a sintaxe é {variable_name:regular_expression}, então aqui temos a variável nomeada variable, cujo valor será correspondido usando regex .+(onde .significa 'qualquer caractere' e +significa 'uma ou mais vezes').
Michał Rybak

4
@StefanHaberl, se você corresponder variableregularmente, o Spring usará os recursos de detecção de sufixo e truncará tudo após o ponto. Quando você usa a correspondência de regexp, esses recursos não são usados ​​- a variável é correspondida apenas à regexp que você fornece.
Michał Rybak

9
O @martin "variable:.+"não funciona quando há mais de um ponto na variável. por exemplo, colocar e-mails no final de caminhos tranqüilos como /path/abc@server.com.au. O controlador nem é chamado, mas funciona quando há apenas um ponto /path/abc@server.com. Alguma idéia do porquê e / ou uma solução alternativa?
Bohemian

242

O Spring considera que qualquer coisa por trás do último ponto é uma extensão de arquivo como .jsonou .xmle o trucate para recuperar seu parâmetro.

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

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

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

  • /somepath/param resultará em um parâmetro com valor param
  • /somepath/param.json resultará em um parâmetro com valor param.json
  • /somepath/param.xml resultará em um parâmetro com valor param.xml
  • /somepath/param.anything resultará em um parâmetro com valor param.anything
  • /somepath/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 /somepath/{variable}:

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

nota: a diferença da configuração padrão é visível somente se você tiver um mapeamento semelhante somepath/something.{variable}. 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 de 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: annotation-driven 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 a configuração toda 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 & https: // github.com/resthub/resthub-spring-stack/issues/217


Perdoe-me, eu sou um novato, então onde você coloca as configurações de bean? e a que versão de primavera se aplica?
respingo

@ Splash: Você deve definir esses beans nos arquivos applicationContext.xml "padrão" do Spring. Isso se aplica ao Spring 3.2 pelo menos. Provavelmente (pelo menos parcialmente) antes
bmeurant

Esta é a resposta correta na minha opinião. Parece que o parâmetro "useRegisteredSuffixPatternMatch" foi introduzido exatamente para o problema dos OPs.
Lrxw 19/04

Esta foi apenas metade da solução para mim. Veja a resposta de @Paul Aerer.
8bjunkie 26/07/2018

96

Atualização para o Spring 4: desde a versão 4.0.1, você pode usar PathMatchConfigurer(via sua WebMvcConfigurer), por exemplo

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

Em xml, seria ( https://jira.spring.io/browse/SPR-10163 ):

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

11
essa é de longe a solução mais limpa: desative o recurso que o causa, em vez de invadir. De qualquer forma, não estamos usando esse recurso, por isso o problema foi resolvido - perfeito!
David Lavender

Para onde vai a classe AllResources?
irl_irl

11
@ste_irl Adicione uma classe java no mesmo pacote que o seu principal.
Kometen

5
Use matcher.setUseSuffixPatternMatch(false)para desativar completamente a correspondência de sufixo.
Gian Marco Gherardi

Esta foi apenas metade da solução para mim. Veja a resposta de @Paul Aerer.
8bjunkie 26/07/2018

87

Além da resposta de Martin Frey, isso também pode ser corrigido adicionando uma barra no valor RequestMapping:

/path/{variable}/

Lembre-se de que essa correção não oferece suporte à manutenção. Agora, exige que todos os URIs tenham uma barra final - algo que pode não ser aparente para os usuários / novos desenvolvedores da API. Como é provável que nem todos os parâmetros tenham um .neles, também pode criar erros intermitentes


2
Isso é ainda uma solução mais limpa. Eu tive que descobrir da maneira mais difícil que o IE está configurando aceitar cabeçalhos de acordo com o sufixo. Então, eu queria postar em alguns .doc requestmapping e eu sempre recebia um download em vez da nova página html. Essa abordagem corrigiu isso.
Martin Frey

esta é a solução mais simples para mim e resolvi meu problema; regexp parece um pouco de um exagero para muitos casos
Riccardo Cossu

7
mas ele colide com o comportamento padrão do AngularJS para remover as barras finais automaticamente. Isso pode ser configurado nas versões mais recentes do Angular, mas é algo a ser rastreado por horas se você não souber o que está acontecendo.
dschulten 01/09/14

11
@dschulten E você me salvou horas de depuração, obrigado! No entanto, você deve mencionar na resposta que a barra final será necessária nas solicitações HTPP.
Hoffmann #

11
Isso é muito perigoso! Eu certamente não recomendaria, pois qualquer pessoa que implemente a API menos esperaria. Muito insustentável.
Sparkyspider

32

No Spring Boot Rest Controller, eu os resolvi seguindo as etapas:

RestController:

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

E do cliente em repouso:

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

2
Esta resposta depende de uma barra final para funcionar.
8bitjunkie

2
funciona como um encanto (também sem barra). obrigado!
afe

27

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

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

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

Espero ter ajudado alguém :)


Isso porque as chaves avaliar a RegEx e você já tem algum em tornoid
8bitjunkie

15

/somepath/{variable:.+}funciona na requestMappingtag Java .


Prefiro essa resposta porque ela não mostra o que não funcionou.
johnnieb

Não funciona para endereços de email com mais de um ponto.
8bitjunkie

11
@ 8bitjunkie Sth como "/{code:.+}"obras para muitos pontos e não um ou seja, 61.12.7ele também funciona para o IEk.a.p@o.i.n
tryingHard

13

Aqui está uma abordagem que se baseia puramente na configuração 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;
    }
}

Obrigado, resolveu para mim. Além disso, é muito limpo e explícito. +1
bkis 08/07/19

11

Uma maneira bem fácil de solucionar esse problema é anexar uma barra final ...

por exemplo:

usar :

/somepath/filename.jpg/

ao invés de:

/somepath/filename.jpg

11

No Spring Boot, a expressão Regular resolve o problema como

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

Observe que isso funciona apenas para um ponto. Não funciona para endereços de email.
8bitjunkie

11
@ 8bitjunkie Sth como "/{code:.+}"obras para muitos pontos e não um ou seja, 61.12.7ele também funciona para o IEk.a.p@o.i.n
tryingHard

11
@ 8bitjunkie Eu testei com endereço IP. Funciona muito bem. Então, isso significa que funciona para vários pontos.
Dapper Dan

6

A solução completa, incluindo endereços de e-mail nos nomes dos caminhos para o 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>

Adicione isso ao application-xml


Upvote - esta é a única resposta aqui o que torna claro que ambos os itens de configuração ContentNegotiationManagerFactoryBean e contentNegotiationManager são necessários
8bitjunkie

5

Se você estiver usando o Spring 3.2.xe <mvc:annotation-driven />crie este pequeno 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;
    }
}

Em seguida, coloque isso no seu xml de configuração do MVC:

<bean class="spring.DoNotTruncateMyUrls" />

Está relacionado ao ContentNegotiationManager?
Kanagavelu Sugumar

Meu código apenas configura o RequestMappingHandlerMapping para que os URLs não sejam truncados. O ContentNegotiationManager é outro animal.
Jukka 31/05

2
Isso é antigo, mas você realmente não precisa de um BeanPostProcessorpara isso. Se você usar, WebMvcConfigurationSupportpoderá substituir o requestMappingHandlerMapping @Beanmétodo. Se você usa a configuração XML, basta declarar seu próprio RequestMappingHandlerMappingbean e declarar essa propriedade.
Sotirios Delimanolis

Muito obrigado, tentei um número diferente de soluções para o mesmo problema, apenas esta funcionou para mim. :-)
Estamos Borg

3

Finalmente encontrei a solução no Spring Docs :

Para desativar completamente o uso de extensões de arquivo, você deve definir o seguinte:

 useSuffixPatternMatching(false), see PathMatchConfigurer

 favorPathExtension(false), see ContentNegotiationConfigurer

Adicionar isso à minha WebMvcConfigurerAdapterimplementação resolveu o problema:

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

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

2

Para mim o

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

funciona, mas somente se você também codificar o "ponto" no URL da sua solicitação como "% 2E", funciona. Mas exige que URLs sejam tudo isso ... que não é uma codificação "padrão", embora válida. Parece um bug: |

A outra solução alternativa, semelhante à maneira "trailing slash", é mover a variável que terá o ponto "inline" ex:

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

agora todos os pontos serão preservados, nenhuma modificação ou regex é necessária.


1

A partir do Spring 5.2.4 (Spring Boot v2.2.6.RELEASE) PathMatchConfigurer.setUseSuffixPatternMatche ContentNegotiationConfigurer.favorPathExtensionfoi preterido ( https://spring.io/blog/2020/03/24/spring-framework-5-2-5-available-now e https://github.com/spring-projects/spring-framework/issues/24179 ).

O verdadeiro problema é que o cliente solicita um tipo de mídia específico (como .com) e o Spring adicionou todos esses tipos de mídia por padrão. Na maioria dos casos, seu controlador REST produzirá apenas JSON, portanto, não suportará o formato de saída solicitado (.com). Para superar esse problema, você deve ser bom atualizando seu controlador de descanso (ou método específico) para suportar o formato 'ouput' ( @RequestMapping(produces = MediaType.ALL_VALUE)) e, é claro, permitir caracteres como um ponto ( {username:.+}).

Exemplo:

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

O Spring 5.3 e superior corresponderão apenas a sufixos registrados (tipos de mídia).

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.