如何读取和复制HTTP servlet响应输出stream内容以进行logging
我创build了一个filter,在我的Java Web服务器(实际上是appengine),logging传入请求的参数。 我也想logging我的networking服务器写入的结果。 尽pipe我有权访问响应对象,但我不确定如何从中获取实际的string/内容响应。
有任何想法吗?
您需要创build一个Filter
其中包装ServletResponse
参数与自定义HttpServletResponseWrapper
实现,其中您重写getOutputStream()
和getWriter()
以返回一个自定义ServletOutputStream
实现,其中您复制基本抽象OutputStream#write(int b)
的写入的字节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解决scheme是可以的,但没有什么过时的。 spring现在有它的function。 所有你需要做的就是使用[ContentCachingResponseWrapper]
,它具有方法public byte[] getContentAsByteArray()
。
我build议使WrapperFactory将允许使其可configuration,无论是使用默认的ResponseWrapper或ContentCachingResponseWrapper。
我不是很熟悉appengine,但是你需要一些Tomcat中的Access Log Valve 。 其属性模式 ; 格式化布局,其识别来自要logging的请求和响应的各种信息字段,或者公共或组合以select标准格式的字。
它看起来appengine内置了日志过滤function 。
应用一个servletfilter
虽然BalusC的答案将在大多数情况下工作,你必须小心flush
调用 – 它提交响应,没有其他的写作是可能的,例如。 通过以下filter。 在Websphere环境中,我们已经发现了一些非常类似的问题,在这个环境下,所提供的响应只是部分的。
根据这个问题 ,冲洗不应该被调用,你应该让它在内部调用。
我已经通过使用TeeWriter
(它将stream分成两个stream)和在“分支stream”中使用非缓冲stream来解决冲洗问题,以用于logging目的。 然后调用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。