Недавно я столкнулся с требованием, в котором мне нужно дважды прочитать тело HttpServletRequest, а затем снова передать запрос в цепочку фильтров для нормального потока приложения. Затем я создал приведенный ниже класс Java, который можно использовать внутри фильтра сервлета для перехвата запроса, чтения содержимого тела запроса, а затем снова передать запрос в цепочку фильтров сервлета для дальнейшей обработки.
Мы должны знать, что по умолчанию тело HTTP-запроса может быть прочитано только один раз. Если мы прочитаем тело в фильтре, целевой сервлет не сможет перечитать его, и это также вызовет IllegalStateException.
1. Пользовательский HttpServletRequestWrapper
Это исходный код пользовательской реализации класса HttpServletRequestWrapper. Обратите внимание, что я использую спецификацию Servlets 2.5, поскольку мне пришлось работать над некоторыми устаревшими приложениями. Не стесняйтесь изменять реализацию в соответствии с последней спецификацией сервлета.
В целом, класс-обертка извлекает тело запроса и сохраняет его в переменной экземпляра body. Мы можем читать атрибут body столько раз, сколько захотим, не получая никаких ошибок.
Этот класс можно рассматривать как пример двойного чтения InputStream, но это не так.
import java.io.BufferedReader;import java.io.ByteArrayInputStream;import java.io.IOException;import java.io.InputStream;import java.io.InputStreamReader;import javax.servlet.ServletInputStream;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletRequestWrapper;public class RequestWrapper extends HttpServletRequestWrapper {private final String body;public RequestWrapper(HttpServletRequest request) throws IOException{//So that other request method behave just like beforesuper(request);StringBuilder stringBuilder = new StringBuilder();BufferedReader bufferedReader = null;try {InputStream inputStream = request.getInputStream();if(inputStream != null) {bufferedReader = new BufferedReader(new InputStreamReader(inputStream));char[] charBuffer = new char[128];int bytesRead = -1;while((bytesRead = bufferedReader.read(charBuffer)) > 0) {stringBuilder.append(charBuffer, 0, bytesRead);}} else {stringBuilder.append("");}} catch(IOException ex) {throw ex;} finally {if(bufferedReader != null) {try {bufferedReader.close();} catch(IOException ex) {throw ex;}}}//Store request pody content in 'body' variablebody = stringBuilder.toString();}@Overridepublic ServletInputStream getInputStream() throws IOException {final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body.getBytes());ServletInputStream servletInputStream = new ServletInputStream() {public int read() throws IOException {return byteArrayInputStream.read();}};return servletInputStream;}@Overridepublic BufferedReader getReader() throws IOException {return new BufferedReader(new InputStreamReader(this.getInputStream()));}//Use this method to read the request body N timespublic String getBody() {return this.body;}}
2. Использование HttpServletRequestWrapper
Используйте класс-обертку для изменения параметров запроса в фильтре сервлета. Это поможет сервлету дважды прочитать тело запроса.
Чтобы использовать этот класс, мы должны сначала добавить отображение фильтра сервлета в web.xml. Мы будем использовать класс-оболочку внутри этого фильтра.
<filter><filter-name>cacheFilter</filter-name><filter-class>com.howtodoinjava.filter.RESTCacheFilter</filter-class></filter><filter-mapping><filter-name>cacheFilter</filter-name><url-pattern>/*</url-pattern></filter-mapping>
Приведенное выше сопоставление фильтра будет вызывать фильтр при всех запросах, поскольку мы сопоставили его с подстановочным знаком /*.
3. Чтение тела запроса несколько раз
В фильтре сервлета мы можем прочитать тело HTTP-запроса N раз, а затем передать его в цепочку фильтров, и это будет работать нормально.
Это небольшой класс утилит и он может быть не нужен в большинстве случаев. Но когда он понадобится – вы это узнаете.
public class CacheFilter implements Filter {private static final Logger LOGGER = LoggerFactory.getLogger(CacheFilter.class);private static final String INDEX_DIR = "c:/temp/cache";private FilterConfig filterConfig = null;public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)throws IOException, ServletException {request = new RequestWrapper((HttpServletRequest) request);//Read request.getBody() as many time you needchain.doFilter(request, response);}public void init(FilterConfig filterConfiguration) throws ServletException {this.filterConfig = filterConfiguration;}public void destroy() {this.filterConfig = null;}}
4. Заключение
Используя приведенный выше HttpServletRequestWrapper, вы можете прочитать тело HTTP-запроса, а затем сервлет все еще может прочитать его позже. По сути, содержимое тела запроса кэшируется внутри объекта-обертки, поэтому оно может быть N раз в течение всего жизненного цикла запроса.