我已经创建了一个过滤器(filter)在我的Java Web服务器中(实际上是AppEngine),用于记录传入请求(request)的参数。我还想记录我的Web服务器编写的响应(response)结果。虽然我可以访问响应对象,但不确定如何从中获取实际的字符串/内容响应。
你有什么想法吗?
你有什么想法吗?
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();
}
}
BalusC的解决方案还可以,但有点过时。现在Spring已经有相关功能了。你只需要使用[ContentCachingResponseWrapper]
即可,它有一个方法public byte[] getContentAsByteArray()
。
我建议创建一个WrapperFactory,允许配置默认的ResponseWrapper或ContentCachingResponseWrapper。
你可以使用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();
}
}
虽然BalusC的答案在大多数情况下都有效,但你必须小心flush
调用-它提交响应并且不能通过以下过滤器进行其他写入。
我们在Websphere环境中发现了一些非常相似的问题,其中交付的响应仅是部分响应。
根据这个问题,根本不应该调用flush,应该让它在内部被调用。
我通过使用TeeWriter
(将流分成两个流)并在“分支流”中使用非缓冲流来解决了刷新问题,以进行日志记录。然后就不需要调用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);
}
}
代码为简洁起见而简化。
final ReadableHttpServletResponse httpResponse = (ReadableHttpServletResponse) response;
final byte[] data = httpResponse.readPayload();
System.out.println(new String(data));
response.getWriter().write(yourResponseString)
吗?还是有其他方法?你是否想要记录错误呢?换句话说,当你使用response.sendError(yourError)
时,你是否想要记录响应日志? - Dave