Cómo leer y copiar el contenido de la secuencia de salida de la respuesta del servlet HTTP para el registro

Creé un filtro en mi servidor web java (appengine en realidad) que registra los parámetros de una solicitud entrante. También me gustaría registrar la respuesta resultante que escribe mi servidor web. Aunque tengo acceso al objeto de respuesta, no estoy seguro de cómo obtener la respuesta de cadena / contenido real.

¿Algunas ideas?

Necesita crear un Filter en el que ServletResponse argumento ServletResponse con una implementación HttpServletResponseWrapper personalizada en la que sobrescriba getOutputStream() y getWriter() para devolver una implementación ServletOutputStream personalizada donde copie los bytes escritos en el resumen base OutputStream#write(int b) método. Luego, en su lugar, HttpServletResponseWrapper el envoltorio personalizado HttpServletResponseWrapper a la FilterChain#doFilter() y finalmente podrá obtener la respuesta copiada después de la llamada.

En otras palabras, el 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. } } 

La costumbre 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]; } } } 

El ServletOutputStream personalizado:

 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(); } } 

La solución BalusC está bien, pero está desactualizada. Spring ahora tiene una característica para él. Todo lo que necesita hacer es usar [ContentCachingResponseWrapper] , que tiene el método public byte[] getContentAsByteArray() .

Sugiero hacer WrapperFactory, que permitirá que sea configurable, ya sea que use ResponseWrapper o ContentCachingResponseWrapper por defecto.

No estoy muy familiarizado con appengine pero necesitas algo Access Log Valve en Tomcat. Su patrón de atributos; un diseño de formato que identifica los diversos campos de información de la solicitud y la respuesta a registrar, o la palabra común o combinada para seleccionar un formato estándar.

Parece que appengine tiene una funcionalidad incorporada para el filtrado de registros .

aplicar un filtro servlet

Si bien la respuesta de BalusC funcionará en la mayoría de los escenarios, debe tener cuidado con la llamada de flush : compromete la respuesta y no es posible escribirla, por ejemplo. a través de los siguientes filtros. Hemos encontrado algunos problemas con un enfoque muy similar en el entorno Websphere donde la respuesta entregada fue solo parcial.

De acuerdo con esta pregunta, el enrojecimiento no se debe invocar en absoluto y debes dejar que se llame internamente.

He resuelto el problema de enjuague utilizando TeeWriter (divide el flujo en 2 flujos) y usando flujos que no son de almacenamiento en búfer en el “flujo ramificado” para fines de registro. No es necesario llamar al flush luego.

 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; } }; } 

Entonces puedes usarlo de esta manera:

 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); } } 

El código se simplifica para brewity.