Как прочитать и скопировать содержимое потока вывода ответа сервлета HTTP для ведения журнала - PullRequest
41 голосов
/ 20 января 2012

Я создал фильтр на своем веб-сервере java (собственно appengine), который регистрирует параметры входящего запроса.Я также хотел бы записать полученный ответ, который пишет мой веб-сервер.Несмотря на то, что у меня есть доступ к объекту ответа, я не уверен, как получить из него фактическую строку / ответ содержимого.

Есть идеи?

Ответы [ 5 ]

93 голосов
/ 23 января 2012

Вам необходимо создать Filter, в котором вы заключаете аргумент ServletResponse с пользовательской реализацией HttpServletResponseWrapper, в которой вы переопределяете getOutputStream() и getWriter() на вернуть пользовательскую реализацию ServletOutputStream, в которой вы копируете записанный байт (ы) в базовый реферат OutputStream#write(int b) метод. Затем вместо этого вы передаете упакованный пользовательский HttpServletResponseWrapper вызову FilterChain#doFilter() и, наконец, вы сможете получить скопированный ответ после вызова.

Другими словами, Filter:

@WebFilter("/*")
public class ResponseLogger implements Filter {

    @Override
    public void init(FilterConfig config) throws ServletException {
        // NOOP.
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException {
        if (response.getCharacterEncoding() == null) {
            response.setCharacterEncoding("UTF-8"); // Or whatever default. UTF-8 is good for World Domination.
        }

        HttpServletResponseCopier responseCopier = new HttpServletResponseCopier((HttpServletResponse) response);

        try {
            chain.doFilter(request, responseCopier);
            responseCopier.flushBuffer();
        } finally {
            byte[] copy = responseCopier.getCopy();
            System.out.println(new String(copy, response.getCharacterEncoding())); // Do your logging job here. This is just a basic example.
        }
    }

    @Override
    public void destroy() {
        // NOOP.
    }

}

Кастом HttpServletResponseWrapper:

public class HttpServletResponseCopier extends HttpServletResponseWrapper {

    private ServletOutputStream outputStream;
    private PrintWriter writer;
    private ServletOutputStreamCopier copier;

    public HttpServletResponseCopier(HttpServletResponse response) throws IOException {
        super(response);
    }

    @Override
    public ServletOutputStream getOutputStream() throws IOException {
        if (writer != null) {
            throw new IllegalStateException("getWriter() has already been called on this response.");
        }

        if (outputStream == null) {
            outputStream = getResponse().getOutputStream();
            copier = new ServletOutputStreamCopier(outputStream);
        }

        return copier;
    }

    @Override
    public PrintWriter getWriter() throws IOException {
        if (outputStream != null) {
            throw new IllegalStateException("getOutputStream() has already been called on this response.");
        }

        if (writer == null) {
            copier = new ServletOutputStreamCopier(getResponse().getOutputStream());
            writer = new PrintWriter(new OutputStreamWriter(copier, getResponse().getCharacterEncoding()), true);
        }

        return writer;
    }

    @Override
    public void flushBuffer() throws IOException {
        if (writer != null) {
            writer.flush();
        } else if (outputStream != null) {
            copier.flush();
        }
    }

    public byte[] getCopy() {
        if (copier != null) {
            return copier.getCopy();
        } else {
            return new byte[0];
        }
    }

}

Кастом ServletOutputStream:

public class ServletOutputStreamCopier extends ServletOutputStream {

    private OutputStream outputStream;
    private ByteArrayOutputStream copy;

    public ServletOutputStreamCopier(OutputStream outputStream) {
        this.outputStream = outputStream;
        this.copy = new ByteArrayOutputStream(1024);
    }

    @Override
    public void write(int b) throws IOException {
        outputStream.write(b);
        copy.write(b);
    }

    public byte[] getCopy() {
        return copy.toByteArray();
    }

}
9 голосов
/ 14 апреля 2017

Решение BalusC в порядке, но немного устарело. У весны теперь есть особенность для этого. Все, что вам нужно сделать, это использовать [ContentCachingResponseWrapper], который имеет метод public byte[] getContentAsByteArray().

Предлагаю сделать WrapperFactory, который позволит сделать его настраиваемым, использовать ли по умолчанию ResponseWrapper или ContentCachingResponseWrapper.

4 голосов
/ 18 октября 2016

Хотя ответ BalusC будет работать в большинстве сценариев, вы должны быть осторожны с вызовом flush - он передает ответ, и никакая другая запись в него невозможна, например. через следующие фильтры. Мы обнаружили некоторые проблемы с очень схожим подходом в среде Вебсферы, где полученный ответ был только частичным.

Согласно этому вопросу не следует вызывать флеш, и вы должны разрешить его вызывать изнутри.

Я решил проблему сброса, используя TeeWriter (он разбивает поток на 2 потока) и используя небуферизованные потоки в «разветвленном потоке» для целей регистрации. Тогда нет необходимости вызывать flush.

private HttpServletResponse wrapResponseForLogging(HttpServletResponse response, final Writer branchedWriter) {
    return new HttpServletResponseWrapper(response) {
        PrintWriter writer;

        @Override
        public synchronized PrintWriter getWriter() throws IOException {
            if (writer == null) {
                writer = new PrintWriter(new TeeWriter(super.getWriter(), branchedWriter));
            }
            return writer;
        }
    };
}

Тогда вы можете использовать это следующим образом:

protected void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException {
    //...
    StringBuilderWriter branchedWriter = new org.apache.commons.io.output.StringBuilderWriter();
    try {
        chain.doFilter(request, wrapResponseForLogging(response, branchedWriter));
    } finally {
        log.trace("Response: " + branchedWriter);
    }
}

Код упрощен для пивоварения.

3 голосов
/ 23 января 2012

Я не совсем знаком с appengine, но вам нужно что-то Access Log Valve в Tomcat. Его атрибут шаблон ; форматирование, определяющее различные информационные поля из запрошенного запроса и ответа или слова общего или объединенного для выбора стандартного формата.

Похоже, appengine имеет встроенную функциональность для фильтрации журналов .

применить фильтр сервлетов

0 голосов
/ 04 июля 2019

Вместо создания Custom HttpServletResponseWrapper. Вы можете использовать ContentCachingResponseWrapper, поскольку он предоставляет метод getContentAsByteArray ().

public void doFilterInternal(HttpServletRequest servletRequest, HttpServletResponse servletResponse,
            FilterChain filterChain) throws IOException, ServletException {

        HttpServletRequest request = servletRequest;
        HttpServletResponse response = servletResponse;
        ContentCachingRequestWrapper requestWrapper = new ContentCachingRequestWrapper(request);
        ContentCachingResponseWrapper responseWrapper =new ContentCachingResponseWrapper(response);
        try {
            super.doFilterInternal(requestWrapper, responseWrapper, filterChain);

        } finally {

            byte[] responseArray=responseWrapper.getContentAsByteArray();
            String responseStr=new String(responseArray,responseWrapper.getCharacterEncoding());
            System.out.println("string"+responseStr);       
            /*It is important to copy cached reponse body back to response stream
            to see response */
            responseWrapper.copyBodyToResponse();

        }

    }
...