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 .