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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With