Spring MVC @PathVariable mit Punkt (.) Wird abgeschnitten

361

Dies ist die Fortsetzung der Frage ob Spring MVC @PathVariable abgeschnitten wird

Das Frühlingsforum gibt an, dass es als Teil von ContentNegotiationManager behoben wurde (Version 3.2). siehe den folgenden Link.
https://jira.springsource.org/browse/SPR-6164
https://jira.springsource.org/browse/SPR-7632

In meiner Anwendung wird requestParameter with .com abgeschnitten.

Kann mir jemand erklären, wie man diese neue Funktion benutzt? Wie ist es bei XML konfigurierbar?

Hinweis: Spring Forum - # 1 Spring MVC @PathVariable mit Punkt (.) Wird abgeschnitten

Kanagavelu Sugumar
quelle

Antworten:

485

Soweit ich weiß, tritt dieses Problem nur für die Pfadvariable am Ende der Anforderungszuordnung auf.

Wir konnten dies lösen, indem wir das Regex-Addon im Requestmapping definierten.

 /somepath/{variable:.+}
Martin Frey
quelle
1
Danke, ich denke, dieser Fix ist auch früher verfügbar (vor 3.2V). Allerdings mag ich dieses Update nicht. da es überhaupt die URL benötigt, die in meiner Anwendung behandelt werden muss ... und zukünftige URL-Implementierung auch, um dies zu
erledigen
4
Hier ist, wie ich das Problem im Frühjahr 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, die Syntax ist {variable_name:regular_expression}, also haben wir hier eine Variable namens variable, deren Wert mit Regex abgeglichen wird .+(wobei ."beliebiges Zeichen" und +"einmal oder mehrmals" bedeutet).
Michał Rybak
4
@StefanHaberl Wenn Sie variableregelmäßig übereinstimmen , verwendet Spring seine Suffix-Erkennungsfunktionen und schneidet alles nach Punkt ab. Wenn Sie den regulären Ausdruck verwenden, werden diese Funktionen nicht verwendet. Die Variable wird nur mit dem von Ihnen bereitgestellten regulären Ausdruck abgeglichen.
Michał Rybak
9
@martin "variable:.+"funktioniert nicht, wenn die Variable mehr als einen Punkt enthält. zB E-Mails am Ende von erholsamen Pfaden wie setzen /path/[email protected]. Der Controller wird nicht einmal aufgerufen, funktioniert aber, wenn nur ein Punkt vorhanden ist /path/[email protected]. Irgendeine Idee warum und / oder eine Problemumgehung?
Bohemian
242

Spring ist der Ansicht, dass alles hinter dem letzten Punkt eine Dateierweiterung wie .jsonoder ist.xml und trucate es, um Ihren Parameter abzurufen.

Also, wenn Sie haben /somepath/{variable}:

  • /somepath/param, /somepath/param.json, /somepath/param.xmlOder /somepath/param.anythingwird in einem param mit Wert führenparam
  • /somepath/param.value.json, /somepath/param.value.xmloder /somepath/param.value.anythingführt zu einem Parameter mit Wertparam.value

Wenn Sie Ihre Zuordnung /somepath/{variable:.+}wie vorgeschlagen ändern , wird jeder Punkt, einschließlich des letzten, als Teil Ihres Parameters betrachtet:

  • /somepath/param führt zu einem Parameter mit Wert param
  • /somepath/param.json führt zu einem Parameter mit Wert param.json
  • /somepath/param.xml führt zu einem Parameter mit Wert param.xml
  • /somepath/param.anything führt zu einem Parameter mit Wert param.anything
  • /somepath/param.value.json führt zu einem Parameter mit Wert param.value.json
  • ...

Wenn Sie die Erweiterungserkennung nicht interessieren, können Sie sie deaktivieren, indem Sie mvc:annotation-drivenautomagic überschreiben :

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

Also noch einmal, wenn Sie haben /somepath/{variable}:

  • /somepath/param, /somepath/param.json, /somepath/param.xmlOder /somepath/param.anythingwird in einem param mit Wert führenparam
  • /somepath/param.value.json, /somepath/param.value.xmloder /somepath/param.value.anythingführt zu einem Parameter mit Wertparam.value

Hinweis: Der Unterschied zur Standardkonfiguration ist nur sichtbar, wenn Sie eine Zuordnung wie haben somepath/something.{variable}. Siehe Problem mit dem Resthub-Projekt

Wenn Sie die Erweiterungsverwaltung beibehalten möchten, können Sie seit Spring 3.2 auch die useRegisteredSuffixPatternMatch-Eigenschaft der RequestMappingHandlerMapping-Bean festlegen, um die SuffixPattern-Erkennung aktiviert, aber auf die registrierte Erweiterung beschränkt zu halten.

Hier definieren Sie nur JSON- und XML-Erweiterungen:

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

Beachten Sie, dass mvc: annotation-powered jetzt eine contentNegotiation-Option zum Bereitstellen einer benutzerdefinierten Bean akzeptiert, die Eigenschaft von RequestMappingHandlerMapping jedoch in true geändert werden muss (Standardwert false) (vgl. Https://jira.springsource.org/browse/SPR-7632) ).

Aus diesem Grund müssen Sie immer noch die all mvc: annotation-gesteuerte Konfiguration überschreiben. Ich habe ein Ticket für Spring geöffnet, um nach einem benutzerdefinierten RequestMappingHandlerMapping zu fragen: https://jira.springsource.org/browse/SPR-11253 . Bitte stimmen Sie ab, wenn Sie interessiert sind.

Achten Sie beim Überschreiben darauf, auch das Überschreiben der benutzerdefinierten Ausführungsverwaltung zu berücksichtigen. Andernfalls schlagen alle Ihre benutzerdefinierten Ausnahmezuordnungen fehl. Sie müssen messageCoverters mit einer List Bean wiederverwenden:

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

Ich habe in dem Open-Source-Projekt Resthub , an dem ich beteiligt bin, eine Reihe von Tests zu diesen Themen implementiert: siehe https://github.com/resthub/resthub-spring-stack/pull/219/files & https: // github.com/resthub/resthub-spring-stack/issues/217

bmeurant
quelle
Verzeih mir, ich bin ein Neuling. Wo legst du die Bohnenkonfigurationen hin? und für welche Frühlingsversion gilt es?
Splash
@Splash: Sie müssen diese Beans in Ihren "Standard" Spring applicationContext.xml-Dateien definieren. Dies gilt mindestens für Spring 3.2. Wahrscheinlich (zumindest teilweise) vor
bmeurant
Dies ist meiner Meinung nach die richtige Antwort. Es scheint, dass der Parameter "useRegisteredSuffixPatternMatch" genau für das OP-Problem eingeführt wurde.
lrxw
Dies war nur die halbe Lösung für mich. Siehe die Antwort von @Paul Aerer.
8bitjunkie
96

Update für Spring 4: Seit 4.0.1 können Sie PathMatchConfigurer(über Ihre WebMvcConfigurer) z

@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 wäre es ( https://jira.spring.io/browse/SPR-10163 ):

<mvc:annotation-driven>
    [...]
    <mvc:path-matching registered-suffixes-only="true"/>
</mvc:annotation-driven>
Dave Syer
quelle
11
Dies ist bei weitem die sauberste Lösung: Schalten Sie die Funktion aus, die sie verursacht, anstatt sie zu hacken. Wir verwenden diese Funktion sowieso nicht, also Problem gelöst - perfekt!
David Lavender
Wohin geht die AllResources-Klasse?
irl_irl
1
@ste_irl Fügen Sie eine Java-Klasse im selben Paket wie Ihre Hauptklasse hinzu.
kometen
5
Verwenden Sie matcher.setUseSuffixPatternMatch(false)diese Option, um die Suffixübereinstimmung vollständig zu deaktivieren.
Gian Marco Gherardi
Dies war nur die halbe Lösung für mich. Siehe die Antwort von @Paul Aerer.
8bitjunkie
87

Zusätzlich zu Martin Freys Antwort kann dies auch durch Hinzufügen eines abschließenden Schrägstrichs im RequestMapping-Wert behoben werden:

/path/{variable}/

Beachten Sie, dass dieses Update die Wartbarkeit nicht unterstützt. Jetzt müssen alle URIs einen abschließenden Schrägstrich haben - etwas, das für API-Benutzer / neue Entwickler möglicherweise nicht ersichtlich ist. Da wahrscheinlich nicht alle Parameter ein enthalten ., kann es auch zu zeitweiligen Fehlern kommen

Michał Rybak
quelle
2
Das ist sogar eine sauberere Lösung. Ich musste herausfinden, auf welche harte Weise der IE Accept-Header gemäß dem Suffix setzt. Also wollte ich auf einem .doc Requestmapping posten und bekam immer einen Download anstelle der neuen HTML-Seite. Dieser Ansatz hat das behoben.
Martin Frey
Dies ist die einfachste Lösung für mich, um mein Problem zu lösen. Regexp scheint in vielen Fällen ein bisschen übertrieben zu sein
Riccardo Cossu
7
Es kollidiert jedoch mit dem Standardverhalten von AngularJS, um nachfolgende Schrägstriche automatisch zu entfernen. Das kann in den neuesten Angular-Versionen konfiguriert werden, aber es ist stundenlang zu verfolgen, wenn Sie nicht wissen, was los ist.
dschulten
1
@dschulten Und du hast mir gerade Stunden beim Debuggen erspart, danke! Trotzdem sollten Sie in der Antwort erwähnen, dass der abschließende Schrägstrich in den HTPP-Anforderungen erforderlich ist.
Hoffmann
1
Das ist sehr gefährlich! Ich würde es sicherlich nicht empfehlen, da jeder, der die API implementiert, es am wenigsten erwarten würde. Sehr nicht wartbar.
Sparkyspider
32

In Spring Boot Rest Controller habe ich diese durch die folgenden Schritte behoben:

RestController:

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

Und vom Rest Client:

Get http://mywebhook.com/statusByEmail/[email protected]/
GoutamS
quelle
2
Diese Antwort hängt von einem abschließenden Schrägstrich ab, um zu funktionieren.
8bitjunkie
2
funktioniert wie ein Zauber (auch ohne nachgestellten Schrägstrich). Vielen Dank!
Am
27

Das Hinzufügen des ":. +" funktionierte für mich, aber erst, als ich die äußeren geschweiften Klammern entfernte.

value = { "/username/{id:.+}" } hat nicht funktioniert

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

Hoffe ich habe jemandem geholfen :)

Martin Čejka
quelle
Das liegt daran, dass die geschweiften Klammern den RegEx auswerten und Sie bereits einige habenid
8bitjunkie
15

/somepath/{variable:.+}funktioniert in Java- requestMappingTag.

amit dahiya
quelle
Ich bevorzuge diese Antwort, weil sie nicht zeigt, was nicht funktioniert hat.
Johnnieb
Funktioniert nicht für E-Mail-Adressen mit mehr als einem Punkt.
8bitjunkie
1
@ 8bitjunkie Sth wie "/{code:.+}" funktioniert für viele Punkte nicht einer, dh 61.12.7es funktioniert auch für dh[email protected]
versuchenHard
13

Hier ist ein Ansatz, der sich ausschließlich auf die Java-Konfiguration stützt:

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
quelle
Danke, habe es für mich gelöst. Es ist auch sehr sauber und explizit. +1
bkis
11

Eine ziemlich einfache Möglichkeit, dieses Problem zu umgehen, besteht darin, einen abschließenden Schrägstrich anzuhängen ...

z.B:

verwenden :

/somepath/filename.jpg/

anstatt:

/somepath/filename.jpg
Marcelo C.
quelle
11

In Spring Boot löst der reguläre Ausdruck das Problem wie folgt

@GetMapping("/path/{param1:.+}")
Dapper Dan
quelle
Beachten Sie, dass dies nur für einen Punkt funktioniert. Es funktioniert nicht für E-Mail-Adressen.
8bitjunkie
1
@ 8bitjunkie Sth wie "/{code:.+}"funktioniert für viele Punkte nicht einer, dh 61.12.7es funktioniert auch für dh[email protected]
versuchenHard
1
@ 8bitjunkie Ich habe es mit IP-Adresse getestet. Es funktioniert sehr gut. Das heißt, es funktioniert für mehrere Punkte.
Dapper Dan
6

Die vollständige Lösung mit E-Mail-Adressen in Pfadnamen für Frühjahr 4.2 lautet

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

Fügen Sie dies der Anwendungs-XML hinzu

Paul Arer
quelle
Upvote - dies ist die einzige Antwort hier, die klar macht, dass sowohl die Konfigurationselemente ContentNegotiationManagerFactoryBean als auch contentNegotiationManager erforderlich sind
8bitjunkie
5

Wenn Sie Spring 3.2.x und verwenden <mvc:annotation-driven />, erstellen Sie Folgendes 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;
    }
}

Fügen Sie dies dann in Ihre MVC-Konfigurations-XML ein:

<bean class="spring.DoNotTruncateMyUrls" />
Jukka
quelle
Bezieht es sich auf ContentNegotiationManager?
Kanagavelu Sugumar
Mein Code konfiguriert nur das RequestMappingHandlerMapping so, dass URLs nicht abgeschnitten werden. ContentNegotiationManager ist ein weiteres Biest.
Jukka
2
Das ist alt, aber dafür brauchst du wirklich keine BeanPostProcessor. Wenn Sie verwenden WebMvcConfigurationSupport, können Sie die requestMappingHandlerMapping @BeanMethode überschreiben . Wenn Sie die XML-Konfiguration verwenden, können Sie einfach Ihre eigene RequestMappingHandlerMappingBean deklarieren und diese Eigenschaft deklarieren.
Sotirios Delimanolis
Vielen Dank, ich habe eine andere Anzahl von Lösungen für das gleiche Problem ausprobiert, nur diese hat für mich funktioniert. :-)
Wir sind Borg
3

Endlich habe ich in Spring Docs eine Lösung gefunden :

Um die Verwendung von Dateierweiterungen vollständig zu deaktivieren, müssen Sie beide der folgenden Optionen festlegen:

 useSuffixPatternMatching(false), see PathMatchConfigurer

 favorPathExtension(false), see ContentNegotiationConfigurer

Das Hinzufügen zu meiner WebMvcConfigurerAdapterImplementierung löste das Problem:

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

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

Für mich die

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

funktioniert aber nur, wenn Sie auch den "Punkt" in Ihrer Anforderungs-URL als "% 2E" codieren, dann funktioniert es. Es müssen jedoch alle URLs so sein, dass ... was keine "Standard" -Codierung ist, obwohl sie gültig ist. Fühlt sich wie ein Bug an: |

Die andere Problemumgehung, ähnlich wie beim "abschließenden Schrägstrich", besteht darin, die Variable zu verschieben, die den Punkt "inline" hat, z.

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

Jetzt bleiben alle Punkte erhalten, es sind keine Änderungen oder Regex erforderlich.

Rogerdpack
quelle
1

Ab Spring 5.2.4 (Spring Boot v2.2.6.RELEASE) PathMatchConfigurer.setUseSuffixPatternMatchund ContentNegotiationConfigurer.favorPathExtensionveraltet ( https://spring.io/blog/2020/03/24/spring-framework-5-2-5-available-now and https://github.com/spring-projects/spring-framework/issues/24179 ).

Das eigentliche Problem besteht darin, dass der Client einen bestimmten Medientyp (z. B. .com) anfordert und Spring standardmäßig alle diese Medientypen hinzufügt. In den meisten Fällen erzeugt Ihr REST-Controller nur JSON, sodass das angeforderte Ausgabeformat (.com) nicht unterstützt wird. Um dieses Problem zu lösen, sollten Sie alle gut sein, indem Sie Ihren Rest-Controller (oder eine bestimmte Methode) aktualisieren, um das 'ouput'-Format ( @RequestMapping(produces = MediaType.ALL_VALUE) zu unterstützen) und natürlich Zeichen wie einen Punkt zulassen ({username:.+} ) .

Beispiel:

@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 und höher stimmt nur mit registrierten Suffixen (Medientypen) überein.

GuyT
quelle