Spring MVC @PathVariable dengan dot (.) Semakin terpotong

361

Ini adalah kelanjutan dari pertanyaan Spring MVC @PathVariable yang terpotong

Forum musim semi menyatakan bahwa ia telah memperbaiki (versi 3.2) sebagai bagian dari ContentNegotiationManager. lihat tautan di bawah ini.
https://jira.springsource.org/browse/SPR-6164
https://jira.springsource.org/browse/SPR-7632

Dalam aplikasi saya requestParameter dengan .com terpotong.

Adakah yang bisa menjelaskan kepada saya bagaimana cara menggunakan fitur baru ini? bagaimana ini dapat dikonfigurasi di xml?

Catatan: spring forum- # 1 Spring MVC @PathVariable dengan dot (.) Semakin terpotong

Kanagavelu Sugumar
sumber

Jawaban:

485

Sejauh yang saya tahu masalah ini hanya muncul untuk pathvariable di akhir pemetaan permintaan.

Kami dapat menyelesaikannya dengan mendefinisikan addex regex di pemetaan permintaan.

 /somepath/{variable:.+}
Martin Frey
sumber
1
Terima kasih, saya pikir perbaikan ini tersedia sebelumnya juga (sebelum 3.2V)? Namun saya tidak suka perbaikan ini; karena diperlukan di semua url yang harus ditangani dalam aplikasi saya ... dan implementasi URL di masa depan juga harus diurus ini ...
Kanagavelu Sugumar
4
di sini adalah bagaimana saya memecahkan masalah di musim semi 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, sintaksnya adalah {variable_name:regular_expression}, jadi di sini kita memiliki variabel bernama variable, nilai mana yang akan dicocokkan menggunakan regex .+(di mana .berarti 'karakter apa pun' dan +berarti 'satu atau lebih kali').
Michał Rybak
4
@StefanHaberl jika Anda cocok variabledengan cara biasa, Spring menggunakan fitur deteksi sufiks dan memotong semuanya setelah titik. Saat Anda menggunakan pencocokan regexp, fitur-fitur itu tidak digunakan - variabel hanya cocok dengan regexp yang Anda berikan.
Michał Rybak
9
@martin "variable:.+"tidak berfungsi ketika ada lebih dari satu titik di variabel. misalnya meletakkan email di akhir jalur yang sepi seperti /path/[email protected]. Kontroler itu bahkan tidak dipanggil, tetapi berfungsi ketika hanya ada satu titik /path/[email protected]. Adakah yang tahu mengapa dan / atau solusinya?
Bohemian
242

Spring menganggap bahwa apa pun di belakang titik terakhir adalah ekstensi file seperti .jsonatau .xmldan buatkan itu untuk mengambil parameter Anda.

Jadi, jika Anda memiliki /somepath/{variable}:

  • /somepath/param, /somepath/param.json, /somepath/param.xmlAtau /somepath/param.anythingakan menghasilkan param dengan nilaiparam
  • /somepath/param.value.json, /somepath/param.value.xmlatau /somepath/param.value.anythingakan menghasilkan param dengan nilaiparam.value

jika Anda mengubah pemetaan menjadi /somepath/{variable:.+}seperti yang disarankan, sembarang titik, termasuk yang terakhir akan dianggap sebagai bagian dari parameter Anda:

  • /somepath/param akan menghasilkan param dengan nilai param
  • /somepath/param.json akan menghasilkan param dengan nilai param.json
  • /somepath/param.xml akan menghasilkan param dengan nilai param.xml
  • /somepath/param.anything akan menghasilkan param dengan nilai param.anything
  • /somepath/param.value.json akan menghasilkan param dengan nilai param.value.json
  • ...

Jika Anda tidak peduli dengan pengenalan ekstensi, Anda dapat menonaktifkannya dengan menimpa 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>

Jadi, sekali lagi, jika Anda memiliki /somepath/{variable}:

  • /somepath/param, /somepath/param.json, /somepath/param.xmlAtau /somepath/param.anythingakan menghasilkan param dengan nilaiparam
  • /somepath/param.value.json, /somepath/param.value.xmlatau /somepath/param.value.anythingakan menghasilkan param dengan nilaiparam.value

Catatan: perbedaan dari konfigurasi default hanya terlihat jika Anda memiliki pemetaan seperti somepath/something.{variable}. lihat masalah proyek Resthub

jika Anda ingin mempertahankan manajemen ekstensi, sejak Spring 3.2 Anda juga dapat mengatur properti useRegisteredSuffixPatternMatch dari RequestMappingHandlerMapping bean untuk menjaga suffixPengenalan pola makan diaktifkan tetapi terbatas pada ekstensi yang terdaftar.

Di sini Anda hanya mendefinisikan ekstensi json dan 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>

Perhatikan bahwa mvc: annotation-driven menerima sekarang opsi contentNegotiation untuk menyediakan kacang kustom tetapi properti dari RequestMappingHandlerMapping harus diubah menjadi true (default false) (lih. Https://jira.springsource.org/browse/SPR-7632 ).

Untuk alasan itu, Anda masih harus mengganti semua konfigurasi mvc: annotation-driven. Saya membuka tiket ke Spring untuk meminta RequestMappingHandlerMapping kustom: https://jira.springsource.org/browse/SPR-11253 . Berikan suara jika Anda diinteret masuk.

Sementara mengesampingkan, berhati-hatilah untuk mempertimbangkan juga menimpa manajemen Eksekusi kustom. Jika tidak, semua pemetaan Pengecualian khusus Anda akan gagal. Anda harus menggunakan kembali messageCoverters dengan daftar kacang:

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

Saya menerapkan, dalam proyek open source Resthub yang menjadi bagian saya, serangkaian tes pada subjek ini: lihat https://github.com/resthub/resthub-spring-stack/pull/219/files & https: // github.com/resthub/resthub-spring-stack/issues/217

bmeurant
sumber
Maafkan saya, saya seorang pemula, jadi di mana Anda meletakkan kacang konfigurasi? dan apa versi musim semi yang berlaku untuknya?
Splash
@Splash: Anda harus mendefinisikan kacang ini ke dalam file "application" Spring applicationContext.xml Anda. Ini berlaku untuk Spring 3.2 setidaknya. Mungkin (setidaknya sebagian) sebelum
bmeurant
Ini jawaban yang benar menurut saya. Tampaknya parameter "useRegisteredSuffixPatternMatch" diperkenalkan persis untuk masalah OP.
lrxw
Ini hanya setengah dari solusi bagi saya. Lihat jawaban @Paul Aerer.
8bitjunkie
96

Pembaruan untuk Spring 4: sejak 4.0.1 Anda dapat menggunakan PathMatchConfigurer(via Anda WebMvcConfigurer), mis

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

Dalam xml, itu akan menjadi ( https://jira.spring.io/browse/SPR-10163 ):

<mvc:annotation-driven>
    [...]
    <mvc:path-matching registered-suffixes-only="true"/>
</mvc:annotation-driven>
Dave Syer
sumber
11
sejauh ini ini adalah solusi terbersih: matikan fitur yang menyebabkannya, daripada meretasnya. Kami tidak menggunakan fitur ini, jadi masalah terpecahkan - sempurna!
David Lavender
Kemana kelas AllResources pergi?
irl_irl
1
@ste_irl Tambahkan kelas java dalam paket yang sama dengan utama Anda.
kometen
5
Gunakan matcher.setUseSuffixPatternMatch(false)untuk sepenuhnya menonaktifkan kecocokan sufiks.
Gian Marco Gherardi
Ini hanya setengah dari solusi bagi saya. Lihat jawaban @Paul Aerer.
8bitjunkie
87

Selain jawaban Martin Frey, ini juga dapat diperbaiki dengan menambahkan garis miring pada nilai RequestMapping:

/path/{variable}/

Perlu diingat bahwa perbaikan ini tidak mendukung perawatan. Sekarang mengharuskan semua URI untuk memiliki garis miring - sesuatu yang mungkin tidak terlihat oleh pengguna API / pengembang baru. Karena kemungkinan tidak semua parameter memiliki .di dalamnya, itu juga dapat membuat bug berselang

Michał Rybak
sumber
2
Itu bahkan solusi yang lebih bersih. Saya harus mencari tahu cara yang sulit yaitu pengaturan IE menerima header sesuai dengan akhiran. Jadi saya ingin memposting beberapa pemetaan permintaan .doc dan saya selalu mendapat unduhan alih-alih halaman html baru. Pendekatan ini memperbaiki itu.
Martin Frey
ini adalah solusi paling sederhana untuk saya dan menyelesaikan masalah saya; regexp tampaknya sedikit berlebihan untuk banyak kasus
Riccardo Cossu
7
tetapi bertabrakan dengan perilaku default AngularJS untuk menghapus garis miring secara otomatis. Itu dapat dikonfigurasikan dalam rilis Angular terbaru tetapi itu adalah sesuatu untuk dilacak berjam-jam jika Anda tidak tahu apa yang sedang terjadi.
dschulten
1
@dschulten Dan Anda baru saja menyelamatkan saya dari debugging, terima kasih! Namun demikian Anda harus menyebutkan dalam jawaban bahwa garis miring akan diperlukan dalam permintaan HTPP.
Hoffmann
1
Ini sangat berbahaya! Saya pasti tidak akan merekomendasikan hal ini karena siapa pun yang mengimplementasikan API tidak akan mengharapkannya. Sangat tidak bisa dirawat.
sparkyspider
32

Di Spring Boot Rest Controller, saya telah menyelesaikan ini dengan Langkah-langkah berikut:

RestController:

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

Dan Dari Istirahat Klien:

Get http://mywebhook.com/statusByEmail/[email protected]/
Goutam
sumber
2
Jawaban ini tergantung pada garis miring untuk bekerja.
8bitjunkie
2
bekerja seperti pesona (juga tanpa garis miring). Terima kasih!
afe
27

menambahkan ":. +" bekerja untuk saya, tetapi tidak sampai saya menghapus kurung keriting luar.

value = { "/username/{id:.+}" } tidak berfungsi

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

Semoga saya membantu seseorang :)

Martin Čejka
sumber
Itu karena kurung keriting mengevaluasi RegEx dan Anda sudah memiliki sekitarid
8bitjunkie
15

/somepath/{variable:.+}bekerja di requestMappingtag Java .

amit dahiya
sumber
Saya lebih suka jawaban ini karena tidak menunjukkan apa yang tidak berhasil.
johnnieb
Tidak berfungsi untuk alamat email dengan lebih dari satu titik.
8bitjunkie
1
@ 8bitjunkie Sth suka "/{code:.+}"bekerja untuk banyak titik bukan satu yaitu 61.12.7juga berfungsi untuk yaitu[email protected]
mencobaHard
13

Berikut ini pendekatan yang sepenuhnya bergantung pada konfigurasi 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
sumber
Terima kasih, selesaikan untuk saya. Juga, sangat bersih dan eksplisit. +1
bkis
11

Salah satu cara yang cukup mudah untuk mengatasi masalah ini adalah menambahkan garis miring ...

misalnya:

gunakan:

/somepath/filename.jpg/

dari pada:

/somepath/filename.jpg
Marcelo C.
sumber
11

Di Spring Boot, Ekspresi Reguler menyelesaikan masalah seperti

@GetMapping("/path/{param1:.+}")
Dapper Dan
sumber
Perhatikan bahwa ini hanya berfungsi untuk satu titik. Itu tidak berfungsi untuk alamat email.
8bitjunkie
1
@ 8bitjunkie Sth suka "/{code:.+}"bekerja untuk banyak titik bukan satu yaitu 61.12.7juga berfungsi untuk yaitu[email protected]
mencobaHard
1
@ 8bitjunkie Saya telah mengujinya dengan alamat IP. Ini bekerja dengan sangat baik. Jadi, itu berarti ia berfungsi untuk banyak titik.
Dapper Dan
6

Solusi lengkap termasuk alamat email dalam nama jalur untuk pegas 4.2 adalah

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

Tambahkan ini ke aplikasi-xml

Paul Arer
sumber
Upvote - ini adalah satu-satunya jawaban di sini yang menjelaskan bahwa ContentNegotiationManagerFactoryBean dan contentNegotiationManager diperlukan untuk konfigurasi
8bitjunkie
5

Jika Anda menggunakan Spring 3.2.x dan <mvc:annotation-driven />, buat ini sedikit 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;
    }
}

Kemudian taruh ini di xml konfigurasi MVC Anda:

<bean class="spring.DoNotTruncateMyUrls" />
Jukka
sumber
Apakah ini terkait dengan ContentNegotiationManager?
Kanagavelu Sugumar
Kode saya hanya mengonfigurasi RequestMappingHandlerMapping sehingga URL tidak akan terpotong. Manajer ContentNegotiation adalah binatang buas lainnya.
Jukka
2
Ini sudah tua, tetapi Anda benar-benar tidak perlu BeanPostProcessoruntuk ini. Jika Anda menggunakan, WebMvcConfigurationSupportAnda dapat mengganti requestMappingHandlerMapping @Beanmetode. Jika Anda menggunakan konfigurasi XML, Anda bisa mendeklarasikan RequestMappingHandlerMappingkacang Anda sendiri dan mendeklarasikan properti itu.
Sotirios Delimanolis
Terima kasih banyak, saya mencoba berbagai solusi untuk masalah yang sama, hanya yang ini yang berhasil untuk saya. :-)
Kami adalah Borg
3

Akhirnya saya menemukan solusi di Spring Docs :

Untuk sepenuhnya menonaktifkan penggunaan ekstensi file, Anda harus menetapkan kedua berikut ini:

 useSuffixPatternMatching(false), see PathMatchConfigurer

 favorPathExtension(false), see ContentNegotiationConfigurer

Menambahkan ini ke WebMvcConfigurerAdapterimplementasi saya memecahkan masalah:

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

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

Bagi saya itu

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

tidak berfungsi tetapi hanya jika Anda juga menyandikan "titik" di url permintaan Anda sebagai "% 2E" maka itu berfungsi. Tetapi mengharuskan URL untuk semua itu ... yang bukan pengkodean "standar", meskipun valid. Terasa seperti bug: |

Pekerjaan lain di sekitar, mirip dengan cara "trailing slash" adalah dengan memindahkan variabel yang akan memiliki titik "inline" ex:

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

sekarang semua titik akan dilestarikan, tidak ada modifikasi atau diperlukan regex.

rogerdpack
sumber
1

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

Masalah sebenarnya adalah bahwa klien meminta jenis media tertentu (seperti .com) dan Spring menambahkan semua jenis media secara default. Dalam kebanyakan kasus, kontroler REST Anda hanya akan menghasilkan JSON sehingga tidak akan mendukung format output yang diminta (.com). Untuk mengatasi masalah ini, Anda harus baik-baik saja dengan memperbarui pengontrol istirahat Anda (atau metode tertentu) untuk mendukung format 'ouput' ( @RequestMapping(produces = MediaType.ALL_VALUE)) dan tentu saja mengizinkan karakter seperti titik ( {username:.+}).

Contoh:

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

Pegas 5.3 dan di atasnya hanya akan cocok dengan sufiks yang terdaftar (tipe media).

GuyT
sumber