如何读取和复制 HTTP Servlet 响应输出流内容以进行日志记录

发布于 2024-12-27 17:22:00 字数 128 浏览 2 评论 0原文

我在我的java网络服务器(实际上是appengine)中创建了一个过滤器,用于记录传入请求的参数。我还想记录我的网络服务器写入的结果响应。尽管我可以访问响应对象,但我不确定如何从中获取实际的字符串/内容响应。

有什么想法吗?

I've created a filter to in my java webserver (appengine actually) that logs the parameters of an incoming request. I'd also like to log the resulting response that my webserver writes. Although I have access to the response object, I'm not sure how to get the actual string/content response out of it.

Any ideas?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

恋你朝朝暮暮 2025-01-03 17:22:00

您需要创建一个 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();
    }

}

You need to create a Filter wherein you wrap the ServletResponse argument with a custom HttpServletResponseWrapper implementation wherein you override the getOutputStream() and getWriter() to return a custom ServletOutputStream implementation wherein you copy the written byte(s) in the base abstract OutputStream#write(int b) method. Then, you pass the wrapped custom HttpServletResponseWrapper to the FilterChain#doFilter() call instead and finally you should be able to get the copied response after the the call.

In other words, the 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.
    }

}

The custom 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];
        }
    }

}

The custom 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();
    }

}
撩发小公举 2025-01-03 17:22:00

BalusC 解决方案还可以,但有点过时。 Spring 现在有它的功能。您需要做的就是使用 [ContentCachingResponseWrapper],它具有方法 public byte[] getContentAsByteArray()

我建议制作 WrapperFactory ,这将允许使其可配置,无论是使用默认的 ResponseWrapper 还是 ContentCachingResponseWrapper。

BalusC solution is ok, but little outdated. Spring now has feature for it . All you need to do is use [ContentCachingResponseWrapper], which has method public byte[] getContentAsByteArray() .

I Suggest to make WrapperFactory which will allow to make it configurable, whether to use default ResponseWrapper or ContentCachingResponseWrapper.

葬花如无物 2025-01-03 17:22:00

您可以使用 ContentCachingResponseWrapper,而不是创建自定义 HttpServletResponseWrapper,因为它提供方法 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();

        }

    }

Instead of creating Custom HttpServletResponseWrapper.You can use ContentCachingResponseWrapper as it provide method 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();

        }

    }
终弃我 2025-01-03 17:22:00

虽然 BalusC 的答案 在大多数情况下都有效,但你必须小心使用 flush 调用 - 它提交响应并且不可能对其进行其他写入,例如。通过以下过滤器。
我们在 Websphere 环境中发现了一些非常相似的方法的问题,其中所提供的响应只是部分的。

根据 这个问题 根本不应该调用刷新,并且您应该让它在内部调用。

我通过使用 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);
    }
}

为brewity简化了代码。

While BalusC's answer will work in most scenarios you have to be careful with the flush call - it commits response and no other writing to it is possible, eg. via following filters.
We have found some problems with very simmilar approach in Websphere environment where the delivered response was only partial.

According to this question the flush should not be called at all and you should let it be called internally.

I have solved the flush problem by using TeeWriter (it splits stream into 2 streams) and using non-buffering streams in the "branched stream" for logging purpose. It is unneccessary to call the flush then.

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;
        }
    };
}

Then you can use it this way:

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);
    }
}

The code is simplified for brewity.

递刀给你 2025-01-03 17:22:00

我不太熟悉appengine,但你需要一些访问日志阀 在 Tomcat 中。其属性pattern;格式化布局,标识来自要记录的请求和响应的各种信息字段,或者用于选择标准格式的通用或组合词。

appengine 似乎内置了日志过滤功能。

应用 servlet 过滤器

I am not quite familiar with appengine but you need something Access Log Valve in Tomcat. Its attribute pattern ; a formatting layout identifying the various information fields from the request and response to be logged, or the word common or combined to select a standard format.

It looks appengine has built in functionality for log filtering.

apply a servlet filter

物价感观 2025-01-03 17:22:00

如果您只想将响应有效负载作为字符串,我会选择:

final ReadableHttpServletResponse httpResponse = (ReadableHttpServletResponse) response;
final byte[] data = httpResponse.readPayload();
System.out.println(new String(data));

If you just want the response payload as as String, I would go for:

final ReadableHttpServletResponse httpResponse = (ReadableHttpServletResponse) response;
final byte[] data = httpResponse.readPayload();
System.out.println(new String(data));
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文