Java webserver(実際にはappengine))にフィルターを作成し、着信要求のパラメーターを記録します。また、Webサーバーが書き込んだ結果の応答も記録したいと思います。応答オブジェクトにアクセスできますが、実際の文字列/コンテンツ応答を取得する方法がわかりません。
何か案は?
Filter
を作成する必要があります。ここで、ServletResponse
引数をカスタム HttpServletResponseWrapper
実装でラップします。 getOutputStream()
およびgetWriter()
は、カスタム抽象を返します ServletOutputStream
ベースアブストラクトに書き込まれたバイトをコピーする実装 OutputStream#write(int b)
メソッド。次に、ラップされたカスタムHttpServletResponseWrapper
を代わりにFilterChain#doFilter()
呼び出しに渡し、最終的にコピーされた応答を取得できるはずですafter the the call。
つまり、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に機能が追加されました。あなたがする必要があるのは、メソッドpublic byte[] getContentAsByteArray()
を持つ[ContentCachingResponseWrapper]
を使用することだけです。
デフォルトのResponseWrapperとContentCachingResponseWrapperのどちらを使用するかを設定できるようにするWrapperFactoryを作成することをお勧めします。
BalusCの答え はflush
呼び出しに注意する必要があるほとんどのシナリオで機能しますが、応答をコミットし、他の書き込みはできません。次のフィルター経由。配信された応答が部分的なものに過ぎないWebsphere環境で、非常に類似したアプローチでいくつかの問題を発見しました。
この質問 によると、フラッシュはまったく呼び出されるべきではなく、内部的に呼び出されるようにする必要があります。
TeeWriter
(ストリームを2つのストリームに分割する)を使用し、ロギングの目的で「分岐ストリーム」で非バッファリングストリームを使用することで、フラッシュの問題を解決しました。 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);
}
}
コードは簡潔にするために簡略化されています。
私はappengineにあまり精通していませんが、Tomcatで何か Access Log Valve が必要です。その属性pattern;ログに記録する要求と応答からさまざまな情報フィールドを識別するフォーマットレイアウト、または標準形式を選択するためのWordの共通または結合。
Appengineには log filtering の機能が組み込まれているようです。
カスタムのHttpServletResponseWrapperを作成する代わりに、メソッドgetContentAsByteArray()を提供するContentCachingResponseWrapperを使用できます。
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();
}
}