Lendo HttpServletRequest várias vezes na primavera
1. Introdução
Neste tutorial, aprenderemos como ler o corpo deHttpServletRequest várias vezes usando Spring.
HttpServletRequest é uma interface que expõe o métodogetInputStream() para ler o corpo. Por padrão,the data from this InputStream can be read only once.
2. Dependências do Maven
A primeira coisa de que precisaremos são as dependênciasspring-webmvcejavax.servlet apropriadas:
org.springframework
spring-webmvc
5.2.0.RELEASE
javax.servlet
javax.servlet-api
4.0.1
Além disso, como estamos usando o tipo de conteúdoapplication/json, a dependênciajackson-databind é necessária:
com.fasterxml.jackson.core
jackson-databind
2.10.0
O Spring usa essa biblioteca para converter de e para JSON.
3. ContentCachingRequestWrapper da primavera
O Spring fornece uma classeContentCachingRequestWrapper. Esta classe fornece um método,getContentAsByteArray() para ler o corpo várias vezes.
Esta classe tem uma limitação, embora:We can’t read the body multiple times using the getInputStream() and getReader() methods.
Essa classe armazena em cache o corpo da solicitação consumindoInputStream. Se lermosInputStream em um dos filtros, então outros filtros subsequentes na cadeia de filtros não poderão mais lê-lo. Por causa dessa limitação, essa classe não é adequada em todas as situações.
Para superar essa limitação, vamos agora dar uma olhada em uma solução de uso geral.
4. EstendendoHttpServletRequest
Vamos criar umnew class – CachedBodyHttpServletRequest – which extends HttpServletRequestWrapper. Desta forma, não precisamos substituir todos os métodos abstratos da interfaceHttpServletRequest.
A classeHttpServletRequestWrapper tem dois métodos abstratosgetInputStream()egetReader(). Iremos substituir esses dois métodos e criar um novo construtor.
4.1. O Construtor
Primeiro, vamos criar um construtor. Dentro dele, vamos ler o corpo doInputStreame real e armazená-lo em um objetobyte[]:
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);
}
}
Como resultado, seremos capazes de ler o corpo várias vezes.
4.2. getInputStream()
Em seguida, vamos substituir o métodogetInputStream(). Usaremos este método para ler o corpo bruto e convertê-lo em um objeto.
Neste método, vamoscreate and return a new object of CachedBodyServletInputStream class (uma implementação deServletInputStream):
@Override
public ServletInputStream getInputStream() throws IOException {
return new CachedBodyServletInputStream(this.cachedBody);
}
4.3. getReader()
Então, vamos substituir o métodogetReader(). Este método retorna um objetoBufferedReader:
@Override
public BufferedReader getReader() throws IOException {
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(this.cachedBody);
return new BufferedReader(new InputStreamReader(byteArrayInputStream));
}
5. Implementação deServletInputStream
Vamos criar umclass – CachedBodyServletInputStream – which will implement ServletInputStream. Nesta aula, vamos criar um novo construtor, bem como substituir os métodosisFinished(),isReady()eread().
5.1. O Construtor
Primeiro, vamos criar um novo construtor que leva uma matriz de bytes.
Dentro dele, vamos criar umnew ByteArrayInputStream instance using that byte array.. Depois disso, vamos atribuí-lo à variável globalcachedBodyInputStream:
public class CachedBodyServletInputStream extends ServletInputStream {
private InputStream cachedBodyInputStream;
public CachedBodyServletInputStream(byte[] cachedBody) {
this.cachedBodyInputStream = new ByteArrayInputStream(cachedBody);
}
}
5.2. read()
Então, vamos substituir o métodoread().. Neste método, vamos chamarByteArrayInputStream#read:
@Override
public int read() throws IOException {
return cachedBodyInputStream.read();
}
5.3. isFinished()
Então, vamos substituir o métodoisFinished(). Este método indica seInputStream tem mais dados para ler ou não. Ele retornatrue quando zero bytes disponíveis para leitura:
@Override
public boolean isFinished() {
return cachedBody.available() == 0;
}
5.4. isReady()
Da mesma forma, vamos substituir o métodoisReady(). Este método indica seInputStream está pronto para leitura ou não.
Como já copiamosInputStream em uma matriz de bytes, retornaremostrue para indicar que está sempre disponível:
@Override
public boolean isReady() {
return true;
}
6. O filtro
Finalmente, vamos criar um novo filtro para fazer uso da classeCachedBodyHttpServletRequest. Aqui vamos estender a aula deOncePerRequestFilter de Spring. Esta classe possui um método abstratodoFilterInternal().
Neste método, vamoscreate an object of the CachedBodyHttpServletRequest class from the actual request object:
CachedBodyHttpServletRequest cachedBodyHttpServletRequest =
new CachedBodyHttpServletRequest(request);
Então vamospass this new request wrapper object to the filter chain. Portanto, todas as chamadas subsequentes ao métodogetInputStream () invocarão o método substituído:
filterChain.doFilter(cachedContentHttpServletRequest, response);
7. Conclusão
Neste tutorial, percorremos rapidamente a classeContentCachingRequestWrapper. Também vimos suas limitações.
Em seguida, criamos uma nova implementação da classeHttpServletRequestWrapper. Substituímos o métodogetInputStream() para retornar um objeto deServletInputStream class.
Por fim, criamos um novo filtro para passar o objeto wrapper de solicitação para a cadeia de filtros. Assim, conseguimos ler a solicitação várias vezes.
O código-fonte completo dos exemplos pode ser encontradoover on GitHub.