Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I prevent "Broken pipe" from taking down my tomcat web site?

Tags:

java

tomcat8

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.

like image 440
Steve Swett Avatar asked Mar 03 '16 01:03

Steve Swett


1 Answers

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.

like image 71
Steve Swett Avatar answered Oct 30 '22 23:10

Steve Swett