Membaca HttpServletRequest Beberapa Kali di Musim Semi

1. Perkenalan

Dalam tutorial ini, kita akan belajar cara membaca isi dari HttpServletRequest beberapa kali menggunakan Spring.

HttpServletRequest adalah antarmuka yang mengekspos metode getInputStream () untuk membaca isi. Secara default, data dari InputStream ini hanya dapat dibaca sekali .

2. Ketergantungan Maven

Hal pertama yang kita perlukan adalah dependensi spring-webmvc dan javax.servlet yang sesuai :

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

Juga, karena kita menggunakan tipe konten application / json , ketergantungan jackson-databind diperlukan:

 com.fasterxml.jackson.core jackson-databind 2.10.0 

Spring menggunakan pustaka ini untuk mengonversi ke dan dari JSON.

3. Spring's ContentCachingRequestWrapper

Spring menyediakan kelas ContentCachingRequestWrapper . Kelas ini menyediakan metode getContentAsByteArray () untuk membaca isi beberapa kali .

Kelas ini memiliki batasan, meskipun: Kita tidak dapat membaca body beberapa kali menggunakan metode getInputStream () dan getReader () .

Kelas ini meng-cache badan permintaan dengan menggunakan InputStream . Jika kita membaca InputStream di salah satu filter, filter berikutnya di rantai filter tidak dapat membacanya lagi. Karena batasan ini, kelas ini tidak cocok untuk semua situasi.

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

4. Memperluas HttpServletRequest

Mari buat kelas baru - CachedBodyHttpServletRequest - yang memperluas HttpServletRequestWrapper . Dengan cara ini, kita tidak perlu menimpa semua metode abstrak dari antarmuka HttpServletRequest .

Kelas HttpServletRequestWrapper memiliki dua metode abstrak getInputStream () dan getReader () . Kami akan mengganti kedua metode ini dan membuat konstruktor baru.

4.1. Pembuatnya

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

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 bisa membaca badannya beberapa kali.

4.2. getInputStream ()

Selanjutnya, mari kita ganti metode getInputStream () . Kami akan menggunakan metode ini untuk membaca tubuh mentah dan mengubahnya menjadi objek.

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

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

4.3. getReader ()

Kemudian, kami akan mengganti metode getReader () . Metode ini mengembalikan objek BufferedReader :

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

5. Implementasi ServletInputStream

Mari buat kelas - CachedBodyServletInputStream - yang akan mengimplementasikan ServletInputStream . Di kelas ini, kita akan membuat konstruktor baru serta mengganti metode isFinished () , isReady () dan read () .

5.1. Pembuatnya

Pertama, mari buat konstruktor baru yang menggunakan array byte.

Di dalamnya, kita akan membuat instance ByteArrayInputStream baru menggunakan array byte itu. Setelah itu, kami akan menetapkannya ke variabel global cachedBodyInputStream:

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

5.2. Baca()

Then, we'll override the read() method. In this method, we'll call ByteArrayInputStream#read:

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

5.3. isFinished()

Then, we'll override the isFinished() method. This method indicates whether InputStream has more data to read or not. It returns true when zero bytes available to read:

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

5.4. isReady()

Similarly, we'll override the isReady() method. This method indicates whether InputStream is ready for reading or not.

Since we've already copied InputStream in a byte array, we'll return true to indicate that it's always available:

@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 implementasi baru dari kelas HttpServletRequestWrapper . Kami mengganti metode getInputStream () untuk mengembalikan objek kelas ServletInputStream .

Terakhir, kami membuat filter baru untuk meneruskan objek pembungkus permintaan ke rantai filter. Jadi, kami dapat membaca permintaan tersebut beberapa kali.

Kode sumber lengkap dari contoh-contoh tersebut dapat ditemukan di GitHub.