6

I have a Tomcat 8 web application that uses a 2 MB png image as a "splash/landing page" background. The image is referenced in an external stylesheet.

If I clear my browser cache, directly request the URL of the image, but navigate away from the image before it has loaded, it will completely kill my web application. It fails to provide any more responses for any requests.

I get this error:

org.apache.catalina.connector.ClientAbortException: java.io.IOException: Broken pipe
        at org.apache.catalina.connector.OutputBuffer.realWriteBytes(OutputBuffer.java:396)
        at org.apache.tomcat.util.buf.ByteChunk.append(ByteChunk.java:344)
        at org.apache.catalina.connector.OutputBuffer.writeBytes(OutputBuffer.java:421)
        at org.apache.catalina.connector.OutputBuffer.write(OutputBuffer.java:409)
        at org.apache.catalina.connector.CoyoteOutputStream.write(CoyoteOutputStream.java:97)
        at org.apache.catalina.servlets.DefaultServlet.copy(DefaultServlet.java:1795)
        at org.apache.catalina.servlets.DefaultServlet.serveResource(DefaultServlet.java:919)
        at org.apache.catalina.servlets.DefaultServlet.doGet(DefaultServlet.java:400)
        at javax.servlet.http.HttpServlet.service(HttpServlet.java:621)
        at javax.servlet.http.HttpServlet.service(HttpServlet.java:728)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:303)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
        at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
        at other.JsCssImgResponseHeaderFilter.doFilter(JsCssImgResponseHeaderFilter.java:36)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
        at other.CatchAnyExceptionFilter.doFilter(CatchAnyExceptionFilter.java:39)

I'm trying to catch that exception (or any exception for that matter) and show a friendly ErrorPage.jsp, but I can't redirect to the ErrorPage because the response has already been committed.

I am unable to duplicate this in a test environment, but I can make it break at will in production.

Does anyone know what my options are for solving this kind of problem? Thanks in advance.

BTW, here is what my Filter class's doFilter method looks like:

    public void doFilter(ServletRequest req,
                     ServletResponse res,
                     FilterChain fc) throws IOException, ServletException
{
    HttpServletRequest request = (HttpServletRequest) req;
    HttpServletResponse response = (HttpServletResponse) res;
    HttpSession session = request.getSession();

    try
    {
        fc.doFilter(req, res);

        // Have learned that response headers should not be set until we have determined there won't be an exception.
        // Can't easily redirect or forward (to an error page, for example) if headers have already been committed.

        if (SessionHelper.hasNeedsJsCssImgResponseHeaderFlag(request))
        {
            Calendar cal = Calendar.getInstance();
            cal.add(Calendar.SECOND, JsCssImgResponseHeaderFilter.SECONDS_TO_CACHE);
            response.setHeader("Cache-Control", "PUBLIC, max-age=" + JsCssImgResponseHeaderFilter.SECONDS_TO_CACHE + ", must-revalidate");
            response.setHeader("Expires", DateUtils.getDateInExpiresHeaderFormat(cal.getTime()));
        }
        else if (SessionHelper.hasNeedsDynamicPageResponseHeaderFlag(request))
        {
            response.setHeader("Cache-Control", "no-cache");
            response.setHeader("Expires", "Mon, 25 Nov 2013 00:00:01 GMT");   // in the past
        }
        else if (SessionHelper.hasNeedsRestServicesResponseHeaderFlag(request))
        {
            // CORS - Cross Origin Resource Sharing: allow service requests from other domains -- 
            // such as local development domains hosting AngularJS projects.

            response.setHeader("Access-Control-Allow-Origin", "http://localhost.domain.com:8080");

            response.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS, PUT, DELETE");
            // response.setHeader("Access-Control-Allow-Methods", "*");

            response.setHeader("Access-Control-Allow-Headers", "Content-Type, X-Requested-With");
        }
    }
    catch (Exception e)
    {
        SessionHelper.setErrorPageException(req, session, e);
        final String uri = request.getRequestURI();
        final String playerName = SessionHelper.getLoggedInPlayerName(session);

        String outerLogEntryText = "Player = " + playerName + " - IP = " + request.getRemoteAddr() + 
                " - Mobile = " + SessionHelper.isMobileBrowser(request) +
                " - URI = " + uri + " - Exception MSG = " + e.getMessage();

        LogUtils.writeLogEntry(outerLogEntryText, true, e);
        SessionHelper.setRedirectPage(session, SessionHelper.ERROR_PAGE_PATH);
    }
    finally
    {
        String redirectPage = SessionHelper.getRedirectPage(session);
        SessionHelper.clearRedirectPage(session);

        if (redirectPage != null)
        {
            if (response.isCommitted())
            {
                // Article: http://stackoverflow.com/questions/11305563/cause-of-servlets-response-already-committed
                System.out.println("Response has been committed.  Cannot redirect to: " + redirectPage);
            }
            else
            {
                response.sendRedirect(redirectPage);
            }
        }
    }
}

When the problem occurs, my "Response has been committed..." output statement is indeed written, therefore it is not redirecting to my error page. That's my challenge: how to redirect somewhere when this happens -- or take some better/harmless action.

Steve Swett
  • 89
  • 1
  • 1
  • 5
  • does it matter what browser you use? Chrome v. Firefox v. IE v. Safari? – Scott Sosna Mar 03 '16 at 01:52
  • 1
    what exactly do you mean by navigate away? close the page? switch a tab? go to a different page on the same site? a broken pipe means the connection between the server and the client has been severed before the page finished loading. btw, 2mb image on a site is too bit even if it is the only thing there. – RisingSun Mar 03 '16 at 02:00
  • This exception has been *caught.* That means it hasn't even killed the thread it was thrown in, let alone the entire Tomcat. I suggest that problem lies elsewhere. – user207421 Mar 03 '16 at 02:49
  • Khuderm, by navigate away I mean click a link that goes elsewhere -- like a link on the browser Bookmarks Bar that goes to a different site. Since the original post, I used https://tinypng.com/ to reduce my splash background image size to 539 KB. – Steve Swett Mar 04 '16 at 09:18
  • EJP, yes, the exception is caught. Technically, it probably hasn't killed Tomcat, but my web app stops responding to requests, and the Tomcat Manager web app no longer works. I have to restart the Tomcat service to recover. – Steve Swett Mar 04 '16 at 09:27
  • So far, with the reduction in image file size, I haven't been able to reproduce the problem in production myself, but my Internet download speed (47 Mbps) might be too high to catch it . Over the next few days, I'll see if other users/circumstances cause it to break. Will keep you posted. – Steve Swett Mar 04 '16 at 09:31
  • Another option: If you use apache, nginx or a similar webserver as a reverse proxy you can offload static resources to be loaded directly from the webserver, thus reducing load from your tomcat. – Gerald Schneider Mar 04 '16 at 09:45
  • @SteveSwett So if the exception has been caught, your claim that it has 'brought down the Tomcat site' is incorrect. If you have *another* problem, post it. – user207421 Dec 11 '17 at 08:54

1 Answers1

1

After making a couple changes, the problem hasn't occurred in production. The most impactful change was to reduce the size of my background/splash image. The other change was that I increased the Tomcat Connector connectionTimeout attribute setting 6-fold.

Steve Swett
  • 89
  • 1
  • 1
  • 5