Membaca HttpServletMemohon Berkali-kali pada Musim Bunga

1. Pengenalan

Dalam tutorial ini, kita akan belajar membaca badan dari HttpServletRequest berkali-kali menggunakan Spring.

HttpServletRequest adalah antara muka yang memperlihatkan kaedah getInputStream () untuk membaca isi. Secara lalai, data dari InputStream ini dapat dibaca sekali sahaja .

2. Pergantungan Maven

Perkara pertama yang kita perlukan adalah pergantungan spring-webmvc dan javax.servlet yang sesuai :

 org.springframework spring-webmvc 5.2.0.RELEASE   javax.servlet javax.servlet-api 4.0.1  

Juga, kerana kami menggunakan aplikasi / jenis kandungan json , diperlukan ketergantungan jackson-databaseind :

 com.fasterxml.jackson.core jackson-databind 2.10.0 

Spring menggunakan perpustakaan ini untuk menukar ke dan dari JSON.

3. ContentCachingRequestWrapper Spring

Spring menyediakan kelas ContentCachingRequestWrapper . Kelas ini menyediakan kaedah, getContentAsByteArray () untuk membaca badan beberapa kali .

Kelas ini mempunyai batasan, walaupun: Kami tidak dapat membaca isi badan beberapa kali menggunakan kaedah getInputStream () dan getReader () .

Kelas ini menyimpan isi permintaan dengan menggunakan InputStream . Sekiranya kita membaca InputStream di salah satu penapis, maka penapis berikutnya di rantai penapis tidak dapat membacanya lagi. Kerana had ini, kelas ini tidak sesuai dalam semua keadaan.

Untuk mengatasi batasan ini, mari kita lihat penyelesaian yang lebih umum.

4. Memperluas HttpServletRequest

Mari buat kelas baru - CachedBodyHttpServletRequest - yang meluaskan HttpServletRequestWrapper . Dengan cara ini, kita tidak perlu mengganti semua kaedah abstrak antara muka HttpServletRequest .

Kelas HttpServletRequestWrapper mempunyai dua kaedah abstrak getInputStream () dan getReader () . Kami akan mengatasi kedua-dua kaedah ini dan membuat konstruktor baru.

4.1. Pembina

Pertama, mari buat konstruktor. Di dalamnya, kita akan membaca isi dari InputStream sebenar dan menyimpannya dalam objek bait [] :

public class CachedBodyHttpServletRequest extends HttpServletRequestWrapper { private byte[] cachedBody; public CachedBodyHttpServletRequest(HttpServletRequest request) throws IOException { super(request); InputStream requestInputStream = request.getInputStream(); this.cachedBody = StreamUtils.copyToByteArray(requestInputStream); } }

Hasilnya, kita akan dapat membaca isi badan beberapa kali.

4.2. getInputStream ()

Seterusnya, mari kita ganti kaedah getInputStream () . Kami akan menggunakan kaedah ini untuk membaca badan mentah dan mengubahnya menjadi objek.

Dalam kaedah ini, kami akan membuat dan mengembalikan objek baru dari kelas CachedBodyServletInputStream (pelaksanaan ServletInputStream) :

@Override public ServletInputStream getInputStream() throws IOException { return new CachedBodyServletInputStream(this.cachedBody); }

4.3. getReader ()

Kemudian, kami akan mengatasi kaedah getReader () . Kaedah ini mengembalikan objek BufferedReader :

@Override public BufferedReader getReader() throws IOException { ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(this.cachedBody); return new BufferedReader(new InputStreamReader(byteArrayInputStream)); }

5. Pelaksanaan ServletInputStream

Mari buat kelas - CachedBodyServletInputStream - yang akan melaksanakan ServletInputStream . Di kelas ini, kita akan membuat konstruktor baru serta mengatasi kaedah isFinished () , isReady () dan read () .

5.1. Pembina

Pertama, mari buat konstruktor baru yang menggunakan tatasusunan bait.

Di dalamnya, kami akan membuat contoh ByteArrayInputStream baru menggunakan susunan bait itu. Selepas itu, kami akan menetapkannya ke pembolehubah global cachedBodyInputStream:

public class CachedBodyServletInputStream extends ServletInputStream { private InputStream cachedBodyInputStream; public CachedBodyServletInputStream(byte[] cachedBody) { this.cachedBodyInputStream = new ByteArrayInputStream(cachedBody); } }

5.2. baca ()

Kemudian, kami akan mengatasi membaca () kaedah . Dalam kaedah ini, kita akan memanggil ByteArrayInputStream # baca:

@Override public int read() throws IOException { return cachedBodyInputStream.read(); }

5.3. sudah tamat()

Kemudian, kami akan mengatasi kaedah isFinished () . Kaedah ini menunjukkan sama ada InputStream mempunyai lebih banyak data untuk dibaca atau tidak. Ia kembali benar apabila sifar bait tersedia untuk dibaca:

@Override public boolean isFinished() { return cachedBody.available() == 0; }

5.4. sudah bersedia()

Begitu juga, kita akan mengatasi kaedah isReady () . Kaedah ini menunjukkan sama ada InputStream sudah siap untuk dibaca atau tidak.

Oleh kerana kami telah menyalin InputStream dalam susunan bait, kami akan kembali benar untuk menunjukkan bahawa ia selalu tersedia:

@Override public boolean isReady() { return true; }

6. The Filter

Finally, let's create a new filter to make use of the CachedBodyHttpServletRequest class. Here we'll extend Spring's OncePerRequestFilter class. This class has an abstract method doFilterInternal().

In this method, we'll create an object of the CachedBodyHttpServletRequest class from the actual request object:

CachedBodyHttpServletRequest cachedBodyHttpServletRequest = new CachedBodyHttpServletRequest(request);

Then we'll pass this new request wrapper object to the filter chain. So, all the subsequent calls to the getInputStream() method will invoke the overridden method:

filterChain.doFilter(cachedContentHttpServletRequest, response);

7. Conclusion

In this tutorial, we quickly walked through the ContentCachingRequestWrapper class. We also saw its limitations.

Kemudian, kami membuat pelaksanaan baru dari kelas HttpServletRequestWrapper . Kami mengatasi kaedah getInputStream () untuk mengembalikan objek kelas ServletInputStream .

Akhirnya, kami membuat penapis baru untuk meneruskan objek pembungkus permintaan ke rantai penapis. Oleh itu, kami dapat membaca permintaan itu berkali-kali.

Kod sumber lengkap contoh boleh didapati di GitHub.