Как читать и копировать содержимое потока вывода ответа сервлета HTTP для ведения журнала

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

Есть идеи?

6 ответов

Решение

Вам нужно создать 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();
    }

}

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

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

Вместо создания 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();

        }

    }

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

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

Я решил проблему сброса с помощью 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);
    }
}

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

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

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

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

Если вам просто нужна полезная нагрузка в виде String, я бы выбрал:

      final ReadableHttpServletResponse httpResponse = (ReadableHttpServletResponse) response;
final byte[] data = httpResponse.readPayload();
System.out.println(new String(data));
Другие вопросы по тегам