捕获并记录响应正文

Jon*_*ony 29 java servlets

我有一个servlet来处理某些HTTP请求和响应.我想在发送回客户端之前记录响应正文.有没有什么办法可以在HttpServletResponse从servlet 作为对象发送之前捕获响应主体?

Bal*_*usC 29

如果我理解正确,您想要记录响应正文吗?这是一项相当昂贵的任务,但如果这是业务需求......

正如@duffymo指出的那样,一个Filter适合这个的地方.您可以通过将传入替换ServletResponseHttpServletResponseWrapper实现来捕获响应主体,该实现替换了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());
}
Run Code Online (Sandbox Code Playgroud)

这是如何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();
    }

}
Run Code Online (Sandbox Code Playgroud)

将此过滤器映射到url-pattern您要为其记录响应的过滤器.请记住,二进制/静态内容(如图像,CSS,JS文件等)不会以这种方式记录.您希望通过使用足够具体的内容来排除它们url-pattern,例如,*.jsp或仅仅使用相关servlet-name的servlet.如果你想记录二进制/静态内容(我没有看到任何好处),那么你也需要以HttpServletResponse#getOutputStream()同样的方式替换.

  • @BalusC当你实例化`CopyPrintWriter`时,你已经通过调用`getWriter`来使用响应输出流,结果包装器对象不再一致,导致`IllegalStateException`. (9认同)
  • 解决了,请查看https://gist.github.com/4400492以获取工作示例.对我来说,问题是扩展PrintWriter而不是ServletOutputStream. (3认同)
  • @Muhammad:如果指定了“ @page pageEncoding”,则JSP将对其进行设置。但是,此过滤器早在JSP有机会对其进行设置之前就已经吸引了编写者,现在设置编码已经为时已晚。 (2认同)
  • spring boot 1.5.6 有一个运行时错误:java.lang.IllegalStateException: getWriter() has been called for this response (2认同)

pdo*_*ide 13

BalusC的替代方案回答 使用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();
}
Run Code Online (Sandbox Code Playgroud)

  • 为了后代的利益......哪些库是`TeeOutputStream`和`DelegatingServletOutputStream`来自哪里? (3认同)