it-swarm.com.de

Lesen und Kopieren des Inhalts des HTTP-Servlet-Antwortausgabestreams für die Protokollierung

Ich habe in meinem Java) Webserver einen Filter erstellt, der die Parameter einer eingehenden Anfrage protokolliert. Ich möchte auch die resultierende Antwort protokollieren, die mein Webserver schreibt Wenn ich Zugriff auf das Antwortobjekt habe, bin ich nicht sicher, wie ich die eigentliche Zeichenfolge/Inhaltsantwort daraus abrufen kann.

Irgendwelche Ideen?

44
aloo

Sie müssen eine Filter erstellen, in der Sie das ServletResponse-Argument mit einer benutzerdefinierten HttpServletResponseWrapper -Implementierung umschließen, in der Sie die überschreiben getOutputStream() und getWriter(), um eine benutzerdefinierte ServletOutputStream -Implementierung zurückzugeben, bei der Sie die geschriebenen Bytes in die Basiszusammenfassung kopieren OutputStream#write(int b) Methode. Anschließend übergeben Sie den umschlossenen benutzerdefinierten HttpServletResponseWrapper an den Aufruf FilterChain#doFilter(), und Sie sollten schließlich in der Lage sein, die kopierte Antwort zu erhalten after the call.

Mit anderen Worten, das 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.
    }

}

Das benutzerdefinierte 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];
        }
    }

}

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

}
97
BalusC

BalusC-Lösung ist in Ordnung, aber wenig veraltet. Der Frühling hat jetzt ein Feature dafür. Sie müssen nur [ContentCachingResponseWrapper] Mit der Methode public byte[] getContentAsByteArray() verwenden.

Ich schlage vor, WrapperFactory so zu konfigurieren, dass es konfigurierbar ist, ob Standard-ResponseWrapper oder ContentCachingResponseWrapper verwendet werden soll.

9
omilus

Während BalusCs Antwort in den meisten Szenarien funktioniert, müssen Sie beim flush -Aufruf vorsichtig sein - es wird eine Antwort festgeschrieben, und es ist kein anderes Schreiben möglich, z. über folgende Filter. Wir haben einige Probleme mit einem ähnlichen Ansatz in einer Websphere-Umgebung festgestellt, in der die übermittelte Antwort nur teilweise war.

Laut diese Frage sollte der Flush überhaupt nicht aufgerufen werden und Sie sollten ihn intern aufrufen lassen.

Ich habe das Flush-Problem gelöst, indem ich TeeWriter (es teilt den Stream in 2 Streams auf) und nicht-puffernde Streams im "verzweigten Stream" zu Protokollierungszwecken verwende. Es ist nicht nötig, dann flush aufzurufen.

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

Dann können Sie es so benutzen:

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

Der Code ist für die Brauerei vereinfacht.

4
Petr Újezdský

Ich bin nicht ganz vertraut mit Appengine, aber Sie brauchen etwas Access Log Valve in Tomcat. Sein Attribut Muster ; ein Formatierungslayout, das die verschiedenen Informationsfelder aus der zu protokollierenden Anforderung und Antwort oder dem gemeinsamen oder kombinierten Word zur Auswahl eines Standardformats identifiziert.

Es sieht so aus, als ob Appengine Funktionen für Protokollfilterung eingebaut hat.

Servlet-Filter anwenden

Anstatt Custom HttpServletResponseWrapper zu erstellen, können Sie ContentCachingResponseWrapper als Methode getContentAsByteArray () verwenden.

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

        }

    }
2
Shrinivasan