-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1 All,
On 2/20/2009 12:49 PM, Christopher Schultz wrote: > So, I'm resuming my efforts to complete the response side of the > equation. I'll post more code when it's available. Okay, I've got my filter written. A reminder of the requirements: - - Capture request as seen by the application - - Capture response emitted by the /application/ Note that the last requirement does not include standard headers such as "Date" and the default status code. Only messages actually generated by the application are captured. For those interested in seeing some techniques, these items are covered by the code: - - how to wrap a servlet input stream and reader - - how to make a copy of streaming data - - how to figure out which request parameters are GET versus POST - - how to wrap a servlet output stream and writer Again, comments and questions are welcome. Enjoy, - -chris -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.4.9 (MingW32) Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org iEYEARECAAYFAkmsYg4ACgkQ9CaO5/Lv0PBtCwCgj9apHnSGHkQ5Dm0ZLkBQdISs jm0AnR1CDifx2SKdKcMrtBQr7vsYOnbz =gHd2 -----END PGP SIGNATURE-----
// // License: // Copyright (c) 2009 Christopher Schultz // Free to use for any purpose for no fee. No guarantees. Credits and shout-outs are appreciated. // package net.christopherschultz.http; import java.io.IOException; import java.io.PrintStream; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; // RequestWrapper import java.io.InputStream; import java.io.OutputStream; import java.io.BufferedReader; import java.io.ByteArrayOutputStream; import java.io.Writer; import java.io.StringWriter; import java.io.BufferedReader; import java.io.UnsupportedEncodingException; import java.nio.CharBuffer; import java.util.Enumeration; import java.util.Iterator; import java.util.Map; import java.util.HashMap; import java.net.URLEncoder; import javax.servlet.http.HttpServletRequestWrapper; import javax.servlet.ServletInputStream; import javax.servlet.ServletOutputStream; // ResponseWrapper import java.io.PrintWriter; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import javax.servlet.http.HttpServletResponseWrapper; import java.util.Locale; /** * HttpConversationRecorderFilter * * Dumps the complete HTTP request to stdout after the request has * been processed. * * This should be the first filter in your filter chain so you can * capture all of the request body. * * @author Chris Schultz (ch...@christopherschultz.net) * @version 2009-02-19 */ public class HttpConversationRecorderFilter implements Filter { private static final String DEFAULT_CHARSET = "ISO-8859-1"; private static final String DEFAULT_URI_CHARSET = "UTF-8"; private String _defaultCharset = DEFAULT_CHARSET; private String _uriCharset = DEFAULT_URI_CHARSET; public void init(FilterConfig config) { String charset = config.getInitParameter("default-charset"); if(null != charset) _defaultCharset = charset; charset = config.getInitParameter("default-uri-charset"); if(null != charset) _uriCharset = charset; } public void destroy() { } public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { if(request instanceof HttpServletRequest && response instanceof HttpServletResponse) { Throwable ex = null; HttpRequestRecorderWrapper req; HttpResponseRecorderWrapper rsp; req = wrapRequest((HttpServletRequest)request); rsp = wrapResponse((HttpServletResponse)response); try { chain.doFilter(req, rsp); } catch (Exception e) { // Save the exception for later. We still want to record // the request and possibly part of the response. // // Don't worry about catching java.lang.Error. ex = e; } // TODO: Write the request to a file? req.dumpRequest(System.out); req.free(); rsp.dumpResponse(System.out); rsp.free(); // Re-throw any exception that might have occurred if(null != ex) { if(ex instanceof ServletException) throw (ServletException)ex; if(ex instanceof IOException) throw (IOException)ex; if(ex instanceof RuntimeException) throw (RuntimeException)ex; if(ex instanceof Error) throw (Error)ex; // Should never get here throw new ServletException("Unexpected exception", ex); } } else { chain.doFilter(request, response); } } private HttpRequestRecorderWrapper wrapRequest(HttpServletRequest request) { return new HttpRequestRecorderWrapper(request); } private HttpResponseRecorderWrapper wrapResponse(HttpServletResponse response) { return new HttpResponseRecorderWrapper(response); } private class HttpRequestRecorderWrapper extends HttpServletRequestWrapper { private ServletTeeInputStream _inputStream; private ByteArrayOutputStream _inputStreamBuffer; private BufferedTeeReader _inputReader; private StringWriter _inputReaderBuffer; private boolean _usingReader; public HttpRequestRecorderWrapper(HttpServletRequest request) { super(request); } public boolean usingReader() { return _usingReader; } public BufferedReader getReader() throws IOException { if(null == _inputReader) { BufferedReader in = super.getReader(); _inputReaderBuffer = new StringWriter(); _inputReader = new BufferedTeeReader(in, _inputReaderBuffer, true); _usingReader = true; } return _inputReader; } public ServletInputStream getInputStream() throws IOException { if(null == _inputStream) { ServletInputStream in = super.getInputStream(); _inputStreamBuffer = new ByteArrayOutputStream(); _inputStream = new ServletTeeInputStream(in, _inputStreamBuffer, true); } return _inputStream; } public byte[] getRequestBody() { if(usingReader()) { String charset = getCharacterEncoding(); if(null == charset) charset = _defaultCharset; try { return _inputReaderBuffer.toString().getBytes(charset); } catch (UnsupportedEncodingException uee) { throw new InternalError("Unrecognized character set: " + charset); } } else { return _inputStreamBuffer.toByteArray(); } } public void free() { _inputStream = null; _inputReader = null; if(null != _inputStreamBuffer) { _inputStreamBuffer.reset(); // empty buffer _inputStreamBuffer = null; } if(null != _inputReaderBuffer) { _inputReaderBuffer.getBuffer().setLength(0); // empty buffer _inputReaderBuffer = null; } } public void dumpRequest(PrintStream out) throws IOException { out.println("======> dumping HTTP request <============="); // // Wow, the first line of an HTTP request is tough to reconstruct // out.print(getMethod()); out.print(" "); out.print(getRequestURI()); String query = getQueryString(); if(null != query) { out.print("?"); out.print(query); } String protocol = getProtocol(); if(null != protocol) { out.print(" "); out.print(protocol); } out.println(); // // Now, dump all the headers // for(Enumeration i=getHeaderNames(); i.hasMoreElements(); ) { String headerName = (String)i.nextElement(); out.print(headerName); out.print(": "); for(Enumeration j = getHeaders(headerName); j.hasMoreElements(); ) { out.print((String)j.nextElement()); if(j.hasMoreElements()) out.print(","); } out.println(); } // Print the end-of-header newline out.println(); // // If Content-Type is application/x-www-form-urlencoded // then we have to reconstruct the parameters from the parameter // map. // // Trigger the fetching of request parameters getParameter(""); // See Servlet Specification SRV.3.1.1 for details. if("POST".equals(getMethod()) && "application/x-www-form-urlencoded".equals(getContentType())) { // In this case, the servlet container is supposed to // consume the request body and parse it as POST form data. // // We will need to reconstruct the POST string using all // parameters read plus the query string to figure out which // came from where. String postQueryString = RequestUtils.getPostQueryString(this, _uriCharset); out.print(postQueryString); } else { // // Consume any remaining request body // if(usingReader()) { int leftover = 0; // Stealthily avoid reading from a closed reader if(!_inputReader.isClosed()) { try { while(-1 != _inputReader.read()) leftover++; _inputReader.close(); } catch (IOException ioe) { System.err.println("WARN: reader already closed?"); } } if(0 < leftover) System.out.println("====> Filter read " + leftover + " unread characters from the request reader"); } else { int leftover = 0; // Force the InputStream to be created if it hasn't already // been created. For instance, if the servlet never // requested /either/ an InputStream /or/ a Reader, // neither of these objects would ever have been created. getInputStream(); // Stealthily avoid reading from a closed reader if(!_inputStream.isClosed()) { try { while(-1 != _inputStream.read()) leftover++; _inputStream.close(); } catch (IOException ioe) { System.err.println("WARN: stream already closed? " + ioe); } } if(0 < leftover) System.out.println("====> Filter read " + leftover + " unread bytes from the request stream"); } // // Dump the request body // byte[] body = getRequestBody(); if(null != body && 0 < body.length) out.write(body); } out.flush(); } } private class HttpResponseRecorderWrapper extends HttpServletResponseWrapper { private ServletTeeOutputStream _outputStream; private ByteArrayOutputStream _outputStreamBuffer; private PrintTeeWriter _outputWriter; private StringWriter _outputWriterBuffer; private boolean _usingWriter; private int _httpStatusCode = -1; private LinkedHashMap _headers = new LinkedHashMap(); public HttpResponseRecorderWrapper(HttpServletResponse response) { super(response); } public boolean usingWriter() { return _usingWriter; } // Have to snoop the methods that set the response status public void setStatus(int sc) { super.setStatus(sc); _httpStatusCode = sc; } public void setStatus(int sc, String message) { super.setStatus(sc, message); _httpStatusCode = sc; } public void sendError(int sc) throws IOException { super.sendError(sc); _httpStatusCode = sc; } public void sendError(int sc, String message) throws IOException { super.sendError(sc, message); _httpStatusCode = sc; } public int getStatus() { return _httpStatusCode; } // Have to snoop the methods that set headers // TODO: Implement other addHeader and setHeader methods public void addHeader(String name, String value) { List values = (List)_headers.get(name); if(null == values) { values = new ArrayList(); _headers.put(name, values); } values.add(value); } public void setHeader(String name, String value) { super.setHeader(name, value); _headers.remove(name); // Remove first to preserve ordering ArrayList values = new ArrayList(); values.add(value); _headers.put(name, values); } public ServletOutputStream getOutputStream() throws IOException { if(null == _outputStream) { ServletOutputStream out = super.getOutputStream(); _outputStreamBuffer = new ByteArrayOutputStream(); // TODO: use autoFlush on this PrintStream? _outputStream = new ServletTeeOutputStream(out, new PrintStream(_outputStreamBuffer), true); } return _outputStream; } public PrintWriter getWriter() throws IOException { if(null == _outputWriter) { PrintWriter out = super.getWriter(); _outputWriterBuffer = new StringWriter(); // TODO: use autoFlush on this PrintWriter? _outputWriter = new PrintTeeWriter(out, new PrintWriter(_outputWriterBuffer), true); _usingWriter = true; } return _outputWriter; } public byte[] getResponseBody() { if(usingWriter()) { String charset = getCharacterEncoding(); if(null == charset) charset = _defaultCharset; try { return _outputWriterBuffer.toString().getBytes(charset); } catch (UnsupportedEncodingException uee) { throw new InternalError("Unrecognized character set: " + charset); } } else { return _outputStreamBuffer.toByteArray(); } } public void free() { _outputStream = null; _outputWriter = null; if(null != _outputStreamBuffer) { _outputStreamBuffer.reset(); // empty buffer _outputStreamBuffer = null; } if(null != _outputWriter) { _outputWriterBuffer.getBuffer().setLength(0); // empty buffer _outputWriterBuffer = null; } } public void dumpResponse(PrintStream out) throws IOException { out.println("======> dumping HTTP response <============="); out.print("Status: "); out.println(getStatus()); for(Iterator i=_headers.entrySet().iterator(); i.hasNext(); ) { Map.Entry entry = (Map.Entry)i.next(); String name = (String)entry.getKey(); out.print(name); out.print(": "); for(Iterator j = ((List)entry.getValue()).iterator(); j.hasNext(); ) { out.print((String)j.next()); if(j.hasNext()) out.print(", "); } out.println(); } // // Dump the response body // byte[] body = getResponseBody(); if(null != body && 0 < body.length) out.write(body); } } private static class ServletTeeInputStream extends ServletInputStream { private InputStream _in; private OutputStream _out; private boolean _close; private boolean _isClosed = false; public ServletTeeInputStream(InputStream in, OutputStream out, boolean close) { _in = in; _out = out; _close = close; } public int available() throws IOException { return _in.available(); } public void close() throws IOException { try { _in.close(); } finally { if(_close) _out.close(); } _isClosed = true; } public void mark(int readlimit) { _in.mark(readlimit); } // TODO: return false? public boolean markSupported() { return _in.markSupported(); } public void reset() throws IOException { _in.reset(); } public int read() throws IOException { int ch = _in.read(); if(-1 != ch) _out.write(ch); return ch; } public int read(byte[] b) throws IOException { return this.read(b, 0, b.length); } public int read(byte[] b, int off, int len) throws IOException { int n = _in.read(b,off,len); if(-1 != n) _out.write(b, off, n); return n; } public long skip(long want) throws IOException { long total = 0; byte[] buffer = new byte[1024]; while(0 < want) { int n; int wantThisTime = (1024 < want ? 1024 : (int)want); if(-1 != (n = _in.read(buffer, 0, wantThisTime))) { _out.write(buffer, 0, n); total += n; want -= n; } } return total; } public boolean isClosed() { return _isClosed; } } public static class ServletTeeOutputStream extends ServletOutputStream { private ServletOutputStream _wrapped; private PrintStream _branch; private boolean _closeBranch; private boolean _isClosed = false; public ServletTeeOutputStream(ServletOutputStream wrapped, PrintStream branch, boolean closeBranch) { _wrapped = wrapped; _branch = branch; _closeBranch = closeBranch; } // OutputStream methods public void write(int ch) throws IOException { _wrapped.write(ch); _branch.write(ch); } public void write(byte[] buffer) throws IOException { _wrapped.write(buffer); _branch.write(buffer); } public void write(byte[] buffer, int off, int len) throws IOException { _wrapped.write(buffer, off, len); _branch.write(buffer, off, len); } public void flush() throws IOException { _wrapped.flush(); _branch.flush(); } public void close() throws IOException { try { _wrapped.close(); } finally { if(_closeBranch) _branch.close(); } } // ServletOutputStream methods public void println() throws IOException { _wrapped.println(); _branch.println(); } public void println(String string) throws IOException { _wrapped.println(string); _branch.println(string); } public void println(boolean boolean0) throws IOException { _wrapped.println(boolean0); _branch.println(boolean0); } public void println(char char0) throws IOException { _wrapped.println(char0); _branch.println(char0); } public void println(int int0) throws IOException { _wrapped.println(int0); _branch.println(int0); } public void println(long long0) throws IOException { _wrapped.println(long0); _branch.println(long0); } public void println(float float0) throws IOException { _wrapped.println(float0); _branch.println(float0); } public void println(double double0) throws IOException { _wrapped.println(double0); _branch.println(double0); } public void print(String string) throws IOException { _wrapped.print(string); _branch.print(string); } public void print(boolean boolean0) throws IOException { _wrapped.print(boolean0); _branch.print(boolean0); } public void print(char char0) throws IOException { _wrapped.print(char0); _branch.print(char0); } public void print(int int0) throws IOException { _wrapped.print(int0); _branch.print(int0); } public void print(long long0) throws IOException { _wrapped.print(long0); _branch.print(long0); } public void print(float float0) throws IOException { _wrapped.print(float0); _branch.print(float0); } public void print(double double0) throws IOException { _wrapped.print(double0); _branch.print(double0); } } public static class BufferedTeeReader extends BufferedReader { private Writer _out; private boolean _close; private boolean _isClosed = false; // TODO: Allow this to be overridden private String _lineSeparator = System.getProperty("line.separator"); public BufferedTeeReader(BufferedReader in, Writer out, boolean close) { super(in); _out = out; _close = close; } public int read() throws IOException { int c = super.read(); if(-1 != c) _out.write(c); return c; } public int read(char[] buffer) throws IOException { // Calling super.read(buffer) results in // this.read(buffer, 0, buffer.length) being called, // which will double the amount of output generated. // int n = super.read(buffer); // // if(-1 != n) // _out.write(buffer, 0, n); // // return n; return this.read(buffer, 0, buffer.length); } public int read(char[] buffer, int off, int len) throws IOException { int n = super.read(buffer, off, len); if(-1 != n) _out.write(buffer, off, n); return n; } public int read(CharBuffer buffer) throws IOException { int n = super.read(buffer); if(-1 != n) _out.append(buffer, 0, n); return n; } public String readLine() throws IOException { String line = super.readLine(); if(null != line) { _out.write(line); _out.write(_lineSeparator); } return line; } public long skip(long want) throws IOException { long total = 0; char[] buffer = new char[1024]; while(0 < want) { int n; int wantThisTime = (1024 < want ? 1024 : (int)want); if(-1 != (n = super.read(buffer, 0, wantThisTime))) { _out.write(buffer, 0, n); total += n; want -= n; } } return total; } public boolean ready() throws IOException { return super.ready(); } // TODO: return false? public boolean markSupported() { return super.markSupported(); } public void mark(int readAheadLimit) throws IOException { super.mark(readAheadLimit); } public void reset() throws IOException { super.reset(); } public void close() throws IOException { try { super.close(); } finally { if(_close) _out.close(); } _isClosed = true; } public boolean isClosed() { return _isClosed; } } private class PrintTeeWriter extends PrintWriter implements Appendable { private PrintWriter _branch; private boolean _closeBranch; public PrintTeeWriter(PrintWriter out, PrintWriter branch, boolean closeBranch) { super(out); _branch = branch; _closeBranch = closeBranch; } public void println() { super.println(); } public void println(boolean boolean0) { super.println(boolean0); _branch.println(boolean0); } public void println(char char0) { super.println(char0); _branch.println(char0); } public void println(int int0) { super.println(int0); _branch.println(int0); } public void println(long long0) { super.println(long0); _branch.println(long0); } public void println(float float0) { super.println(float0); _branch.println(float0); } public void println(double double0) { super.println(double0); _branch.println(double0); } public void println(char[] chars) { super.println(chars); _branch.println(chars); } public void println(String string) { super.println(string); _branch.println(string); } public void println(Object object) { super.println(object); _branch.println(object); } public void write(int int0) { super.write(int0); _branch.write(int0); } public void write(char[] chars, int int0, int int1) { super.write(chars, int0, int1); _branch.write(chars, int0, int1); } public void write(String string, int int0, int int1) { super.write(string, int0, int1); _branch.write(string, int0, int1); } public void print(boolean boolean0) { super.print(boolean0); _branch.print(boolean0); } public void print(char char0) { super.print(char0); _branch.print(char0); } public void print(int int0) { super.print(int0); _branch.print(int0); } public void print(long long0) { super.print(long0); _branch.print(long0); } public void print(float float0) { super.print(float0); _branch.print(float0); } public void print(double double0) { super.print(double0); _branch.print(double0); } public void flush() { super.flush(); _branch.flush(); } // skipping private void java.io.PrintWriter.newLine() public void close() { try { super.close(); } finally { if(_closeBranch) _branch.close(); } } public boolean checkError() { boolean error = super.checkError(); _branch.checkError(); // Call this even though it's worthless? return error; } protected void setError() { super.setError(); // can't call _branch.setError() :( // _branch.setError(); } } public static class RequestUtils { public static String getPostQueryString(HttpServletRequest req) { try { return getPostQueryString(req, "UTF-8"); } catch (UnsupportedEncodingException uee) { throw new InternalError("UTF-8 charset is suddenly unsupported"); } } public static String getPostQueryString(HttpServletRequest req, String uriCharset) throws UnsupportedEncodingException { if(!("POST".equals(req.getMethod()) && "application/x-www-form-urlencoded".equals(req.getContentType()))) throw new IllegalArgumentException("Request must be both POST and application/x-www-urlencoded"); StringBuffer sb = new StringBuffer(); Map queryParameterCounts = countQueryParameters(req.getQueryString()); boolean first = true; for(Iterator i=req.getParameterMap().entrySet().iterator(); i.hasNext(); ) { Map.Entry entry = (Map.Entry)i.next(); String paramName = (String)entry.getKey(); String[] values = (String[])entry.getValue(); int skip = 0; Integer queryCount = (Integer)queryParameterCounts .get(paramName); if(null != queryCount) skip += queryCount.intValue(); if(values.length > skip) { for(int j=skip; j<values.length; ++j) { if(first) first = false; else sb.append('&'); sb.append(URLEncoder.encode(paramName, uriCharset)) .append('=') .append(URLEncoder.encode(values[j], uriCharset)); } } } return sb.toString(); } private static Map countQueryParameters(String query) { HashMap counts = new HashMap(); if(null != query) { String[] paramNames = query.split("=[^&]*(&)?"); for(int i=0; i<paramNames.length; ++i) { Integer count = (Integer)counts.get(paramNames[i]); if(null == count) count = new Integer(1); else count = new Integer(count.intValue() + 1); counts.put(paramNames[i], count); } } return counts; } } }
--------------------------------------------------------------------- To unsubscribe, e-mail: users-unsubscr...@tomcat.apache.org For additional commands, e-mail: users-h...@tomcat.apache.org