【发布时间】:2011-03-15 14:36:51
【问题描述】:
我有一个处理某些 HTTP 请求和响应的 servlet。我想在发送回客户端之前记录响应正文。有什么方法可以在响应正文作为 HttpServletResponse 对象从 servlet 发送之前捕获它?
【问题讨论】:
我有一个处理某些 HTTP 请求和响应的 servlet。我想在发送回客户端之前记录响应正文。有什么方法可以在响应正文作为 HttpServletResponse 对象从 servlet 发送之前捕获它?
【问题讨论】:
BalusC answer 的替代品 使用 TeeOutputStream 一次写入两个输出流。
public void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain chain) throws IOException, ServletException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
final PrintStream ps = new PrintStream(baos);
chain.doFilter(req,new HttpServletResponseWrapper(res) {
@Override
public ServletOutputStream getOutputStream() throws IOException {
return new DelegatingServletOutputStream(new TeeOutputStream(super.getOutputStream(), ps)
);
}
@Override
public PrintWriter getWriter() throws IOException {
return new PrintWriter(new DelegatingServletOutputStream (new TeeOutputStream(super.getOutputStream(), ps))
);
}
});
//Get Response body calling baos.toString();
}
【讨论】:
TeeOutputStream 和 DelegatingServletOutputStream 来自哪些库?
如果我理解正确,您想记录响应body吗?这是一项相当昂贵的任务,但如果这是业务需求......
正如@duffymo 指出的那样,Filter 是一个合适的地方。您可以通过将传入的ServletResponse 替换为HttpServletResponseWrapper 实现来捕获响应主体,该实现将HttpServletResponse#getWriter() 替换为将响应主体复制到某个缓冲区中的自己的实现。使用替换后的响应继续过滤器链后,只需记录副本即可。
这是一个启动示例,doFilter() 方法的外观如下:
public void doFilter(ServletRequest request, final ServletResponse response, FilterChain chain) throws IOException, ServletException {
final CopyPrintWriter writer = new CopyPrintWriter(response.getWriter());
chain.doFilter(request, new HttpServletResponseWrapper((HttpServletResponse) response) {
@Override public PrintWriter getWriter() {
return writer;
}
});
logger.log(writer.getCopy());
}
CopyPrintWriter 的外观如下:
public class CopyPrintWriter extends PrintWriter {
private StringBuilder copy = new StringBuilder();
public CopyPrintWriter(Writer writer) {
super(writer);
}
@Override
public void write(int c) {
copy.append((char) c); // It is actually a char, not an int.
super.write(c);
}
@Override
public void write(char[] chars, int offset, int length) {
copy.append(chars, offset, length);
super.write(chars, offset, length);
}
@Override
public void write(String string, int offset, int length) {
copy.append(string, offset, length);
super.write(string, offset, length);
}
public String getCopy() {
return copy.toString();
}
}
将此过滤器映射到您要为其记录响应的url-pattern。请记住,图像、CSS、JS 文件等二进制/静态内容不会以这种方式记录。您想通过使用足够具体的url-pattern 来排除它们,例如*.jsp 或只是在相关 servlet 的 servlet-name 上。如果您仍然想记录二进制/静态内容(对此我看不到任何好处),那么您也需要以同样的方式替换 HttpServletResponse#getOutputStream()。
【讨论】:
CopyPrintWriter 时,您已经通过调用getWriter 消耗了响应输出流,因此包装对象不再一致,导致IllegalStateException。跨度>
@page pageEncoding,JSP 将设置它。但是,这个过滤器在 JSP 有机会设置它之前就已经获取了编写器,现在设置编码为时已晚。
也许servlet filter 可以帮助您。将其视为面向方面的 HTTP 编程。
【讨论】:
PrintWriter的提示