Lendo HttpServletRequest várias vezes na primavera

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.