Рубрики
Без рубрики

Чтение HttpServletRequest несколько раз весной

Узнайте, как прочитать HttpServletRequest Spring несколько раз

Автор оригинала: baeldung.

1. введение

В этом уроке мы узнаем, как прочитать тело из HttpServletRequest несколько раз с помощью Spring.

HttpServletRequest – это интерфейс, который предоставляет метод getInputStream() для чтения тела. По умолчанию данные из этого входного потока могут быть прочитаны только один раз .

2. Зависимости Maven

Первое, что нам понадобится,-это соответствующие зависимости spring-webmvc и javax.servlet :


    org.springframework
    spring-webmvc
    5.2.0.RELEASE


    javax.servlet
    javax.servlet-api
    4.0.1

Кроме того, поскольку мы используем тип содержимого application/json , требуется зависимость jackson-databind :


    com.fasterxml.jackson.core
    jackson-databind
    2.10.0

Spring использует эту библиотеку для преобразования в JSON и из него.

3. Оболочка запроса кэширования содержимого Spring

Spring предоставляет Оболочку запроса кэширования содержимого класс. Этот класс предоставляет метод getContentAsByteArray() для многократного чтения тела .

Однако у этого класса есть ограничение: Мы не можем прочитать тело несколько раз, используя методы getInputStream() и getReader () .

Этот класс кэширует тело запроса, используя InputStream . Если мы прочитаем InputStream в одном из фильтров, то другие последующие фильтры в цепочке фильтров больше не смогут его прочитать. Из-за этого ограничения этот класс подходит не во всех ситуациях.

Чтобы преодолеть это ограничение, давайте теперь рассмотрим более универсальное решение.

4. Расширение HttpServletRequest

Давайте создадим новый класс – CachedBodyHttpServletRequest – , который расширяет HttpServletRequestWrapper . Таким образом, нам не нужно переопределять все абстрактные методы интерфейса HttpServletRequest |.

Класс HttpServletRequestWrapper имеет два абстрактных метода getInputStream() и getReader() . Мы переопределим оба этих метода и создадим новый конструктор.

4.1. Конструктор

Во-первых, давайте создадим конструктор. Внутри него мы прочитаем тело из фактического InputStream и сохраним его в byte[] object:

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);
    }
}

В результате мы сможем прочитать тело несколько раз.

4.2. getInputStream()

Затем давайте переопределим метод getInputStream () . Мы будем использовать этот метод для чтения необработанного тела и преобразования его в объект.

В этом методе мы создадим и вернем новый объект CachedBody ServletInputStream class (реализация ServletInputStream) :

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

4.3. getReader()

Затем мы переопределим метод getReader () . Этот метод возвращает объект BufferedReader :

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

5. Реализация ServletInputStream

Давайте создадим класс – CachedBodyServletInputStream – который будет реализовывать ServletInputStream . В этом классе мы создадим новый конструктор, а также переопределим методы is Finished() , is Ready() и read () .

5.1. Конструктор

Во-первых, давайте создадим новый конструктор, который принимает массив байтов.

Внутри него мы создадим экземпляр new ByteArrayInputStream , используя этот массив байтов. После этого мы назначим его глобальной переменной cachedBody InputStream:

public class CachedBodyServletInputStream extends ServletInputStream {

    private InputStream cachedBodyInputStream;

    public CachedBodyServletInputStream(byte[] cachedBody) {
        this.cachedBodyInputStream = new ByteArrayInputStream(cachedBody);
    }
}

5.2. прочитайте()

Затем мы переопределим метод read () //. В этом методе мы вызовем ByteArrayInputStream#read:

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

5.3. Завершено()

Затем мы переопределим метод isFinished () . Этот метод указывает, имеет ли InputStream больше данных для чтения или нет. Он возвращает true , когда нулевые байты доступны для чтения:

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

5.4. isReady()

Аналогично, мы переопределим метод isReady () . Этот метод указывает, готов ли InputStream к чтению или нет.

Поскольку мы уже скопировали InputStream в массив байтов, мы вернем true , чтобы указать, что он всегда доступен:

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

6. Фильтр

Наконец, давайте создадим новый фильтр для использования класса CachedBody HttpServletRequest . Здесь мы расширим класс Spring OncePerRequestFilter . Этот класс имеет абстрактный метод doFilterInternal() .

В этом методе мы создадим объект класса CachedBody HttpServletRequest из фактического объекта запроса :

CachedBodyHttpServletRequest cachedBodyHttpServletRequest =
  new CachedBodyHttpServletRequest(request);

Затем мы передадим этот новый объект-оболочку запроса в цепочку фильтров . Таким образом, все последующие вызовы метода getInputStream () будут вызывать переопределенный метод:

filterChain.doFilter(cachedContentHttpServletRequest, response);

7. Заключение

В этом уроке мы быстро прошлись по классу Оболочка запроса кэширования содержимого|/. Мы также видели его ограничения.

Затем мы создали новую реализацию класса HttpServletRequestWrapper|/. Мы переопределили метод getInputStream () , чтобы вернуть объект класса ServletInputStream .

Наконец, мы создали новый фильтр для передачи объекта-оболочки запроса в цепочку фильтров. Таким образом, мы смогли прочитать запрос несколько раз.

Полный исходный код примеров можно найти на GitHub .