Creating error page in servlet filter causes error "Writer already obtained"

Viewed 183

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>\r\n" +
                    "<html xmlns=\"http://www.w3.org/1999/xhtml\">\r\n" +
                    "<head>\r\n" +
                    "<script type=\"text/javascript\" src=\"/path/to/jquery/jquery-1.11.1.min.js\"></script>\r\n" +
                    "<title>Error</title>\r\n" +
                    "</head>\r\n" +
                    "<body>\r\n" +
                    "<h1>Error</h1>\r\n" +
                    "</body>\r\n" +
                    "</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?

1 Answers

Option 1 - downgrade JAX-RS to 1.1

Once JAX-RS version is changed back to 1.1 the errors in SystemOut.log will not be shown.

Do the following steps:

  1. Change the JAX-RS version to 1.1 using WAS 9 Admin console. See the detailed instructions at https://www.ibm.com/support/knowledgecenter/SSEQTP_9.0.0/com.ibm.websphere.base.doc/ae/twbs_jaxrs_coexist_adminconsole.html

Option 2 - move chain.doFilter to the end of your doFilter method

   chain.doFilter(req, resp);
}

Option 3 - Remove other usages of PrintWriter or OuputStream

Review application to determine if both PrintWriter and OuputStream were obtained. Modify the failing servlet/JSP to only obtain one or the other.

Related