#StackBounty: #servlets #servlet-filters #custom-error-pages Creating error page in servlet filter causes error "Writer already ob…

Bounty: 100

I’m creating a custom framework (something like portal) for numerous JSF 1.x and 2.x applications. For that purpose I created a servlet filter that "enrich" application HTML with framework menu, breadcrumb, logout, etc. In that filter I read app’s HTML, modify it and write it to an output stream. So far everything worked great but now I’m having problem with creating a custom error page.

I tried to read a response status code and based on that code, I’m creating output HTML:

public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
    HttpServletRequest req = (HttpServletRequest) req;
    HttpServletResponse res = (HttpServletResponse) resp;
    StringServletResponseWrapper responseWrapper = new StringServletResponseWrapper(res);
    // Invoke resource, accumulating output in the wrapper.
    chain.doFilter(req, responseWrapper);
    String contentType = res.getContentType();
    byte[] data;
    if (contentType.contains("text/html")) {
        String html = null;
        int statusCode = res.getStatus();
        LOG.debug("status: {}, committed: {}", statusCode, res.isCommitted());

        if (statusCode != 200) {
            html = "<!DOCTYPE html>rn" +
                    "<html xmlns="http://www.w3.org/1999/xhtml">rn" +
                    "<head>rn" +
                    "<script type="text/javascript" src="/path/to/jquery/jquery-1.11.1.min.js"></script>rn" +
                    "<title>Error</title>rn" +
                    "</head>rn" +
                    "<body>rn" +
                    "<h1>Error</h1>rn" +
                    "</body>rn" +
                    "</html>";
            Collection<String> headerNames = res.getHeaderNames();
            Map<String, String> headerMap = new HashMap<String, String>();
            for (String header : headerNames) {
                headerMap.put(header, res.getHeader(header));
            }
            res.reset();
            for (Map.Entry<String,String> entry : headerMap.entrySet()) {
                res.setHeader(entry.getKey(), entry.getValue());
            }
            res.setStatus(statusCode);
            response.setContentType("text/html");
        } else {
            html = responseWrapper.getCaptureAsString();
        }

        if (ObjectUtils.isNotEmpty(html)) {
            // do some modification
            String modifiedResponse = doModification(html);
            data = modifiedResponse.getBytes("UTF-8");
            response.setContentLength(data.length);
            response.getOutputStream().write(data); // this line causes error
        }
    } else {
        data = responseWrapper.getCaptureAsBytes();
        response.setContentLength(data.length);
        response.getOutputStream().write(data);
    }
}

This code works without any problem if status code equals 200 (else clause), but when it’s not equal to 200 (I triggered 404 error), the following error occures:

com.ibm.ws.webcontainer.webapp.WebApp logServletError SRVE0293E: [Servlet Error]-[Faces Servlet]: java.lang.IllegalStateException: SRVE0209E: Writer already obtained

I don’t really understand why does this error appear. The only difference between two cases is HTML content which is valid in both cases. Any help?

Using Websphere Application Server 8.5.5.18.

EDIT: I’ve tried to call reset() and then set headers and status code again, but that reset() call causes an IllegalStateException – as stated in javadoc, apparently response has already been committed. As far as I understand, flush() method of ServletOutputStream could cause response to be committed, but I’m not calling it anywhere. I’ve also added some log to see if response really is committed. In both cases (status 200 and 404) response.isCommitted() returns true. Does that mean that response is committed before doFilter is called?


Get this bounty!!!

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.