Leggere HttpServletRequest più volte in primavera

1. Introduzione

In questo tutorial impareremo come leggere più volte il corpo di HttpServletRequest usando Spring.

HttpServletRequest è un'interfaccia che espone il metodo getInputStream () per leggere il corpo. Per impostazione predefinita, i dati di questo InputStream possono essere letti solo una volta .

2. Dipendenze di Maven

La prima cosa di cui avremo bisogno sono le dipendenze spring-webmvc e javax.servlet appropriate :

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

Inoltre, poiché stiamo utilizzando il tipo di contenuto application / json , è richiesta la dipendenza jackson-databind :

 com.fasterxml.jackson.core jackson-databind 2.10.0 

Spring utilizza questa libreria per convertire in e da JSON.

3. ContentCachingRequestWrapper di Spring

Spring fornisce una classe ContentCachingRequestWrapper . Questa classe fornisce un metodo, getContentAsByteArray () per leggere il corpo più volte .

Questa classe ha una limitazione, però: non possiamo leggere il corpo più volte usando i metodi getInputStream () e getReader () .

Questa classe memorizza nella cache il corpo della richiesta utilizzando InputStream . Se leggiamo InputStream in uno dei filtri, gli altri filtri successivi nella catena di filtri non possono più leggerlo. A causa di questa limitazione, questa classe non è adatta in tutte le situazioni.

Per superare questa limitazione, diamo ora un'occhiata a una soluzione più generica.

4. Estensione di HttpServletRequest

Creiamo una nuova classe - CachedBodyHttpServletRequest - che estende HttpServletRequestWrapper . In questo modo, non è necessario sovrascrivere tutti i metodi astratti dell'interfaccia HttpServletRequest .

La classe HttpServletRequestWrapper ha due metodi astratti getInputStream () e getReader () . Sostituiremo entrambi questi metodi e creeremo un nuovo costruttore.

4.1. Il Costruttore

Per prima cosa, creiamo un costruttore. Al suo interno, leggeremo il corpo dall'effettivo InputStream e lo memorizzeremo in un oggetto 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); } }

Di conseguenza, saremo in grado di leggere il corpo più volte.

4.2. getInputStream ()

Successivamente, sovrascriviamo il metodo getInputStream () . Useremo questo metodo per leggere il corpo grezzo e convertirlo in un oggetto.

In questo metodo, creeremo e restituiremo un nuovo oggetto della classe CachedBodyServletInputStream (un'implementazione di ServletInputStream) :

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

4.3. getReader ()

Quindi sovrascriveremo il metodo getReader () . Questo metodo restituisce un oggetto BufferedReader :

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

5. Implementazione di ServletInputStream

Creiamo una classe - CachedBodyServletInputStream - che implementerà ServletInputStream . In questa classe, creeremo un nuovo costruttore e sovrascriveremo i metodi isFinished () , isReady () e read () .

5.1. Il Costruttore

Per prima cosa, creiamo un nuovo costruttore che accetta un array di byte.

Al suo interno, creeremo una nuova istanza ByteArrayInputStream usando quell'array di byte. Dopodiché, lo assegneremo alla variabile globale cachedBodyInputStream:

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

5.2. leggere()

Quindi sovrascriveremo il metodo read () . In questo metodo, chiameremo ByteArrayInputStream # read:

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

5.3. è finito()

Quindi, sovrascriveremo il metodo isFinished () . Questo metodo indica se InputStream ha più dati da leggere o meno. Restituisce vero quando zero byte disponibili per la lettura:

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

5.4. è pronta()

Allo stesso modo, sovrascriveremo il metodo isReady () . Questo metodo indica se InputStream è pronto per la lettura o meno.

Poiché abbiamo già copiato InputStream in un array di byte, restituiremo true per indicare che è sempre disponibile:

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

Quindi, abbiamo creato una nuova implementazione della classe HttpServletRequestWrapper . Abbiamo sovrascritto il metodo getInputStream () per restituire un oggetto della classe ServletInputStream .

Infine, abbiamo creato un nuovo filtro per passare l'oggetto wrapper della richiesta alla catena di filtri. Quindi, siamo stati in grado di leggere la richiesta più volte.

Il codice sorgente completo degli esempi può essere trovato su GitHub.