如何读取和复制 HTTP servlet 响应输出流内容以进行日志记录
Posted
技术标签:
【中文标题】如何读取和复制 HTTP servlet 响应输出流内容以进行日志记录【英文标题】:How to read and copy the HTTP servlet response output stream content for logging 【发布时间】:2012-02-14 12:36:23 【问题描述】:我在我的 Java 网络服务器(实际上是 appengine)中创建了一个过滤器,用于记录传入请求的参数。我还想记录我的网络服务器写入的结果响应。虽然我可以访问响应对象,但我不确定如何从中获取实际的字符串/内容响应。
有什么想法吗?
【问题讨论】:
你是如何写你的回复的?response.getWriter().write(yourResponseString)
???或者你正在做一些不同的事情?你也想写错误吗? (换句话说,你想在你做response.sendError(yourError)
时记录响应吗??)
也许这个java.sun.com/blueprints/corej2eepatterns/Patterns/…和这个docstore.mik.ua/orelly/xml/jxslt/ch08_04.htm可能会给你一个提示
@Dave 只是使用你提到的 response.getWriter().write(yourResponseString) ,这就是我想要捕获的旧输出。
使用 TeeOutputStream 一次写入两个输出流:***.com/a/28305057/1203628.
【参考方案1】:
您需要创建一个Filter
,其中您使用自定义HttpServletResponseWrapper
实现包装ServletResponse
参数,其中您覆盖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();
【讨论】:
HttpServletResponseCopier
的构造函数名称不正确,我无法编辑它,因为编辑应该超过 6 个字符,我不想更改答案的任何其他内容。跨度>
想知道为什么获取响应正文如此复杂。它应该类似于 response.getContent()。这背后一定有一些坚实的理由:)
@ant:这会占用内存,并且通常对 webapp 本身不感兴趣。
@ant:设置一个请求属性即可。
如果是Spring,从4.1.3版本开始,还有ContentCachingResponseWrapper。【参考方案2】:
BalusC 解决方案还可以,但有点过时了。 Spring 现在有它的功能。您需要做的就是使用[ContentCachingResponseWrapper]
,它有方法public byte[] getContentAsByteArray()
。
我建议让 WrapperFactory 允许使其可配置,无论是使用默认的 ResponseWrapper 还是 ContentCachingResponseWrapper。
【讨论】:
你如何“使用”它?从玩了一下,看起来你用 ContentCachingResponseWrapper 替换了 HttpServletResponseCopier ——对吗?【参考方案3】:而不是创建自定义 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 的上述解决方案对我不起作用,但这个解决方案有效 虽然需要春天【参考方案4】:虽然BalusC's answer 在大多数情况下都可以工作,但您必须小心flush
调用 - 它会提交响应,并且不可能对其进行其他写入,例如。通过以下过滤器。
我们在 Websphere 环境中发现了一些非常相似的方法存在的问题,其中交付的响应只是部分的。
根据this question,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);
代码被简化了。
【讨论】:
【参考方案5】:我对 appengine 不太熟悉,但您需要 Tomcat 中的 Access Log Valve。它的属性 pattern ;一种格式布局,用于标识要记录的请求和响应中的各种信息字段,或者用于选择标准格式的常用词或组合词。
看起来 appengine 已经为log filtering 内置了功能。
apply a servlet filter
【讨论】:
【参考方案6】:如果您只想将响应负载作为字符串,我会选择:
final ReadableHttpServletResponse httpResponse = (ReadableHttpServletResponse) response;
final byte[] data = httpResponse.readPayload();
System.out.println(new String(data));
【讨论】:
以上是关于如何读取和复制 HTTP servlet 响应输出流内容以进行日志记录的主要内容,如果未能解决你的问题,请参考以下文章