Linux下Apache Tomcat 8实现浏览器关闭时通过CGI执行Shell清理脚本
Got it, let's tackle this problem head-on—since you can't modify the target HTML (it's constantly changing) but need to trigger that cleanup shell script via CGI when a browser window closes, here are three practical, workable approaches tailored to your Tomcat 8 setup:
1. Detect Connection Closures with a Tomcat Servlet Filter
When a browser window closes, the underlying HTTP connection is usually terminated (though some browsers keep connections alive briefly for pooling). You can use a Tomcat Filter to listen for these connection events and trigger your cleanup script directly.
Steps to Implement:
- Create a Java Filter class and deploy it to your Tomcat webapp (the same one hosting your CGI scripts):
import javax.servlet.*; import javax.servlet.annotation.WebFilter; import javax.servlet.http.HttpServletRequest; import java.io.IOException; @WebFilter("/*") // Match all requests, or narrow to your target page path public class ConnectionCloseDetector implements Filter { @Override public void init(FilterConfig config) throws ServletException {} @Override public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletRequest httpReq = (HttpServletRequest) req; AsyncContext asyncCtx = httpReq.startAsync(); // Add a listener to catch connection completion/closure asyncCtx.addListener(new AsyncListener() { @Override public void onComplete(AsyncEvent event) throws IOException { // Trigger cleanup when the connection finishes (includes browser close) runCleanupScript(); } @Override public void onTimeout(AsyncEvent event) throws IOException {} @Override public void onError(AsyncEvent event) throws IOException {} @Override public void onStartAsync(AsyncEvent event) throws IOException {} }); chain.doFilter(req, res); } private void runCleanupScript() { try { // Replace with your actual CGI cleanup script path Process process = Runtime.getRuntime().exec("/var/lib/tomcat8/webapps/ROOT/cgi-bin/cleanup.sh"); process.waitFor(); } catch (Exception e) { // Log errors instead of printing to console in production e.printStackTrace(); } } @Override public void destroy() {} }
- Caveats: Some browsers may hold connections open in a pool, so there might be a small delay before the trigger fires. Add logic to track user sessions (via
HttpSession) to avoid running the script multiple times for the same user.
2. Use a Reverse Proxy (e.g., Nginx) to Detect Client Disconnects
If your Tomcat sits behind a reverse proxy like Nginx, you can leverage Nginx's ability to detect when a client drops the connection, then trigger your CGI script directly.
Example Nginx Config Snippet:
location /your-target-page { proxy_pass http://localhost:8080/your-target-page; proxy_http_version 1.1; proxy_set_header Connection ""; proxy_read_timeout 30s; # Trigger cleanup when the client disconnects post_action @run-cleanup; } location @run-cleanup { internal; # Only accessible internally, not via direct requests # Forward to your Tomcat CGI endpoint proxy_pass http://localhost:8080/cgi-bin/cleanup.sh; }
- Notes: Ensure your Nginx version supports
post_action(most modern versions do). Also, make sure Nginx has permissions to forward requests to Tomcat, and Tomcat's CGI handler is configured correctly.
3. WebSocket Connection Closure (Most Reliable)
WebSockets maintain a persistent connection, and the server gets an immediate onclose event when the browser window closes. Since you can't modify the target HTML, use a Filter to inject a tiny WebSocket connection script into the page's response automatically.
Step 1: Deploy a WebSocket Endpoint
import javax.websocket.*; import javax.websocket.server.ServerEndpoint; @ServerEndpoint("/ws-cleanup") public class CleanupWebSocket { @OnOpen public void onOpen(Session session) { // Optional: Track sessions to avoid duplicate cleanup runs } @OnClose public void onClose(Session session) { // Runs immediately when the browser closes the WebSocket runCleanupScript(); } private void runCleanupScript() { try { Process process = Runtime.getRuntime().exec("/var/lib/tomcat8/webapps/ROOT/cgi-bin/cleanup.sh"); process.waitFor(); } catch (Exception e) { e.printStackTrace(); } } }
Step 2: Inject WebSocket Code via Filter
import javax.servlet.*; import javax.servlet.annotation.WebFilter; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; @WebFilter("/your-target-page") // Target only your dynamic page public class WebSocketInjectorFilter implements Filter { @Override public void init(FilterConfig config) throws ServletException {} @Override public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletResponse httpRes = (HttpServletResponse) res; PrintWriter out = httpRes.getWriter(); // Let Tomcat serve the original page first chain.doFilter(req, res); // Inject WebSocket connection script at the end of the page out.println("<script>"); out.println("const ws = new WebSocket(`ws://${window.location.host}/ws-cleanup`);"); out.println("ws.onclose = () => {}; // No client-side action needed"); out.println("</script>"); out.flush(); } @Override public void destroy() {} }
- Why this works: The Filter modifies the page's response on the fly, so you don't need to touch the original HTML. WebSocket closure events are far more reliable than HTTP connection checks for detecting browser window closes.
General Tips
- Avoid duplicate runs: Use user session IDs (from
HttpSessionor WebSocketSession) to mark when cleanup has already been executed for a user. - Permissions: Ensure the Tomcat system user (e.g.,
tomcat) has execute permissions on your cleanup script:chmod +x /path/to/cleanup.sh. - Testing: Test with multiple browsers—some handle connection closures differently (e.g., Chrome vs. Firefox).
内容的提问来源于stack exchange,提问作者Sanjay

