Захват и регистрация тела ответа

У меня есть сервлет, который обрабатывает определенные HTTP-запросы и ответы. Я хочу зарегистрировать тело ответа перед отправкой обратно клиенту. Есть ли способ, которым я могу захватить тело ответа, прежде чем он будет отправлен как объект HttpServletResponse из сервлета?

Ответ 1

Если я правильно вас понимаю, вы хотите зарегистрировать ответ тело? Это довольно дорогостоящая задача, но если это деловое требование...

Как показано на рисунке, a Filter является подходящим местом для этого. Вы можете захватить тело ответа, заменив переданный ServletResponse на HttpServletResponseWrapper, который заменяет HttpServletResponse#getWriter() собственной реализацией который копирует тело ответа в некоторый буфер. После продолжения цепи фильтра с замененным ответом просто запишите копию.

Вот пример запуска, как выглядит метод doFilter():

public void doFilter(ServletRequest request, final ServletResponse response, FilterChain chain) throws IOException, ServletException {
    final CopyPrintWriter writer = new CopyPrintWriter(response.getWriter());
    chain.doFilter(request, new HttpServletResponseWrapper((HttpServletResponse) response) {
        @Override public PrintWriter getWriter() {
            return writer;
        }
    });
    logger.log(writer.getCopy());
}

Вот как выглядит CopyPrintWriter:

public class CopyPrintWriter extends PrintWriter {

    private StringBuilder copy = new StringBuilder();

    public CopyPrintWriter(Writer writer) {
        super(writer);
    }

    @Override
    public void write(int c) {
        copy.append((char) c); // It is actually a char, not an int.
        super.write(c);
    }

    @Override
    public void write(char[] chars, int offset, int length) {
        copy.append(chars, offset, length);
        super.write(chars, offset, length);
    }

    @Override
    public void write(String string, int offset, int length) {
        copy.append(string, offset, length);
        super.write(string, offset, length);
    }

    public String getCopy() {
        return copy.toString();
    }

}

Сопоставьте этот фильтр с url-pattern, для которого вы хотите регистрировать ответы. Имейте в виду, что бинарный/статический контент, такой как изображения, CSS, JS файлы и т.д., Не будет регистрироваться таким образом. Вы хотите исключить их, используя достаточно определенное значение url-pattern, например. *.jsp или только на servlet-name рассматриваемого сервлета. Если вы хотите в любом случае записывать бинарный/статический контент (для которого я не вижу никакой пользы), вам нужно также заменить HttpServletResponse#getOutputStream().

Ответ 2

Возможно, вам поможет фильтр сервлетов. Подумайте об этом как аспектно-ориентированное программирование для HTTP.

Ответ 3

Альтернатива ответ BalusC Использование TeeOutputStream для записи в два выходных потока в момент времени.

public void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain chain) throws IOException, ServletException {
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    final PrintStream ps = new PrintStream(baos);

    chain.doFilter(req,new HttpServletResponseWrapper(res) {
         @Override
         public ServletOutputStream getOutputStream() throws IOException {
            return new DelegatingServletOutputStream(new TeeOutputStream(super.getOutputStream(), ps)
            );
         }
         @Override
         public  PrintWriter getWriter() throws IOException {
            return new PrintWriter(new DelegatingServletOutputStream (new TeeOutputStream(super.getOutputStream(), ps))
            );
         }
      });

    //Get Response body calling baos.toString();
}

Ответ 4

Я делаю версию OutputStream без зависимостей сторонних разработчиков, аналогичную с @pdorgambide. Вы можете найти в эту ссылку.