如何捕获基于Google Web Toolkit的旧应用的所有HTTP请求与响应?
Hey there! Let's work through how to capture all incoming requests and outgoing responses for your old GWT app running on Tomcat 9. It sounds like your initial Tomcat-level attempt didn't pan out, so let's break down reliable, actionable methods tailored to your setup.
1. Custom Tomcat Valve (Global/Application-Specific)
Tomcat Valves are designed to intercept requests and responses at the server or application level—this is probably what you tried before, but maybe the configuration or implementation was off. Here's how to get it right:
Step 1: Create a Custom Valve Class
Extend ValveBase and override the invoke method to log request and response details. Make sure to handle both request metadata and content, plus response status and output.
import org.apache.catalina.connector.Request; import org.apache.catalina.connector.Response; import org.apache.catalina.valves.ValveBase; import javax.servlet.ServletException; import java.io.IOException; import java.nio.charset.StandardCharsets; public class RequestResponseCaptureValve extends ValveBase { @Override public void invoke(Request request, Response response) throws IOException, ServletException { // Capture request details String method = request.getMethod(); String url = request.getRequestURI(); String queryString = request.getQueryString(); String requestBody = new String(request.getInputStream().readAllBytes(), StandardCharsets.UTF_8); System.out.printf("Incoming Request: %s %s?%s%nBody:%n%s%n", method, url, queryString, requestBody); // Let the request proceed to the app getNext().invoke(request, response); // Capture response details after processing int statusCode = response.getStatus(); String responseBody = new String(response.getContentByteArray(), StandardCharsets.UTF_8); System.out.printf("Outgoing Response: Status %d%nBody:%n%s%n", statusCode, responseBody); } }
Step 2: Deploy & Configure the Valve
- Compile this class into a JAR and place it in
Tomcat_HOME/lib. - To apply it only to your GWT app, edit the app's
META-INF/context.xmland add:<Valve className="com.yourpackage.RequestResponseCaptureValve" /> - For global Tomcat-wide capture, add the same
<Valve>tag inside the<Host>element inTomcat_HOME/conf/server.xml.
Note: If you're using Tomcat 9, make sure your Valve class is compatible with Servlet 4.0 APIs, and avoid blocking the request/response flow—always call
getNext().invoke()to pass control along.
2. Servlet Filter (Application-Only, No Tomcat Global Changes)
If you don't want to modify Tomcat's global config, a Servlet Filter is perfect—it runs within your GWT app's context and intercepts all requests/responses targeted at it.
Step 1: Create a Response Wrapper to Capture Output
Since the default HttpServletResponse doesn't let you read the response body after it's written, you'll need a wrapper to capture it:
import javax.servlet.ServletOutputStream; import javax.servlet.WriteListener; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponseWrapper; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.PrintWriter; public class CapturableResponseWrapper extends HttpServletResponseWrapper { private final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); private PrintWriter printWriter; public CapturableResponseWrapper(HttpServletResponse response) { super(response); } @Override public ServletOutputStream getOutputStream() throws IOException { return new ServletOutputStream() { @Override public boolean isReady() { return true; } @Override public void setWriteListener(WriteListener writeListener) { // Not needed for basic capture } @Override public void write(int b) throws IOException { byteArrayOutputStream.write(b); getResponse().getOutputStream().write(b); } }; } @Override public PrintWriter getWriter() throws IOException { if (printWriter == null) { printWriter = new PrintWriter(byteArrayOutputStream); } return printWriter; } public String getResponseBody() { if (printWriter != null) { printWriter.flush(); } return byteArrayOutputStream.toString(); } }
Step 2: Create the Filter Class
import javax.servlet.*; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.nio.charset.StandardCharsets; public class RequestResponseCaptureFilter implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { // Initialize if needed } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest httpRequest = (HttpServletRequest) request; CapturableResponseWrapper wrappedResponse = new CapturableResponseWrapper((HttpServletResponse) response); // Capture request details String method = httpRequest.getMethod(); String url = httpRequest.getRequestURI(); String queryString = httpRequest.getQueryString(); String requestBody = new String(httpRequest.getInputStream().readAllBytes(), StandardCharsets.UTF_8); System.out.printf("Filter: Incoming Request - %s %s?%s%nBody:%n%s%n", method, url, queryString, requestBody); // Pass request to the app chain.doFilter(httpRequest, wrappedResponse); // Capture response details int statusCode = wrappedResponse.getStatus(); String responseBody = wrappedResponse.getResponseBody(); System.out.printf("Filter: Outgoing Response - Status %d%nBody:%n%s%n", statusCode, responseBody); } @Override public void destroy() { // Cleanup if needed } }
Step 3: Register the Filter in web.xml
Add this to your GWT app's WEB-INF/web.xml to map the filter to all requests:
<filter> <filter-name>RequestResponseCaptureFilter</filter-name> <filter-class>com.yourpackage.RequestResponseCaptureFilter</filter-class> </filter> <filter-mapping> <filter-name>RequestResponseCaptureFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
3. GWT RPC-Specific Capture (If Focused on RPC Calls)
If most of your app's traffic is GWT RPC requests (the core of GWT's client-server communication), you can extend the GWT RemoteServiceServlet to log RPC payloads directly:
import com.google.gwt.user.server.rpc.RemoteServiceServlet; import javax.servlet.http.HttpServletRequest; import java.io.BufferedReader; import java.io.IOException; public class LoggingRemoteServiceServlet extends RemoteServiceServlet { @Override public String processCall(String payload) throws IOException { HttpServletRequest request = getThreadLocalRequest(); // Log incoming RPC request System.out.printf("RPC Request from %s:%n%s%n", request.getRemoteAddr(), payload); // Process the RPC call as usual String responsePayload = super.processCall(payload); // Log outgoing RPC response System.out.printf("RPC Response:%n%s%n", responsePayload); return responsePayload; } // Optional: Override processPost to log raw request body if needed @Override protected void processPost(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { BufferedReader reader = request.getReader(); StringBuilder requestBody = new StringBuilder(); String line; while ((line = reader.readLine()) != null) { requestBody.append(line); } System.out.printf("Raw RPC Request Body:%n%s%n", requestBody); super.processPost(request, response); } }
Then update your GWT service servlets in web.xml to use this subclass instead of the default RemoteServiceServlet.
Why Your Initial Tomcat Attempt Might Have Failed
Common pitfalls include:
- Incorrect Valve placement: If you added it to the global
server.xmlbut your app is deployed in a separate context, it might not have applied. - Not capturing response content: Tomcat's default Valve doesn't capture response bodies by default—you need to explicitly read the response's byte array or use a wrapper.
- Classpath issues: Your Valve JAR wasn't placed in
Tomcat_HOME/lib, so Tomcat couldn't load it.
Pick the method that fits your needs: use the Valve for global capture, the Filter for app-specific changes without touching Tomcat config, or the RPC servlet extension if you only care about GWT's RPC traffic.
内容的提问来源于stack exchange,提问作者CR Sardar

