在 servlet 过滤器中创建错误页面会导致错误“已获得写入器”

Posted

技术标签:

【中文标题】在 servlet 过滤器中创建错误页面会导致错误“已获得写入器”【英文标题】:Creating error page in servlet filter causes error "Writer already obtained" 【发布时间】:2021-07-03 14:12:53 【问题描述】:

我正在为众多 JSF 1.x 和 2.x 应用程序创建一个自定义框架(类似于门户)。为此,我创建了一个 servlet 过滤器,它使用框架菜单、面包屑、注销等“丰富”应用程序 html。在该过滤器中,我读取应用程序的 HTML,对其进行修改并将其写入输出流。到目前为止,一切都很好,但现在我在创建自定义错误页面时遇到了问题。

我尝试读取响应状态代码并根据该代码创建输出 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);
    

如果状态码等于 200(else 子句),这段代码没有任何问题,但是当它不等于 200(我触发 404 错误)时,会出现以下错误:

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

我真的不明白为什么会出现这个错误。两种情况之间的唯一区别是在两种情况下都有效的 HTML 内容。有什么帮助吗?

使用 Websphere 应用服务器 8.5.5.18。

编辑:我尝试调用reset(),然后再次设置标题和状态代码,但是reset() 调用导致IllegalStateException - 如javadoc 中所述,显然响应已经提交。据我了解,ServletOutputStreamflush() 方法可能会导致响应被提交,但我没有在任何地方调用它。我还添加了一些日志以查看是否确实提交了响应。在这两种情况下(状态 200 和 404)@​​987654328@ 返回 true。这是否意味着在调用 doFilter 之前提交了响应?

【问题讨论】:

【参考方案1】:

选项 1 - 降级 JAX-RS to 1.1

一旦 JAX-RS 版本改回 1.1,SystemOut.log 中的错误将不会显示。

执行以下步骤:

    使用 WAS 9 管理控制台将 JAX-RS 版本更改为 1.1。请参阅详细说明 https://www.ibm.com/support/knowledgecenter/SSEQTP_9.0.0/com.ibm.websphere.base.doc/ae/twbs_jaxrs_coexist_adminconsole.html

选项 2 - 将 chain.doFilter 移动到 doFilter method 的末尾

   chain.doFilter(req, resp);

选项 3 - 删除 PrintWriter or OuputStream 的其他用法

查看应用程序以确定是否同时获得了 PrintWriter 和 OuputStream。修改失败的 servlet/JSP 以仅获取其中一个。

【讨论】:

尝试了选项 2,但我还有另一个问题。如果我在res.getContentType() 之后移动chain.doFilter(),则内容类型的计算结果为null。是否有其他方法可以检查内容类型是否为 text/html? 关于选项 1 - 我使用的是 WAS 8.5.5.18,我相信 JAX-RS 版本 1.1 是默认版本。此外,我什至在 WAS 8.5.5.18 管理控制台中都找不到“默认 JAX-RS 设置”链接(此选项在 WAS 9 管理控制台中确实可用)。

以上是关于在 servlet 过滤器中创建错误页面会导致错误“已获得写入器”的主要内容,如果未能解决你的问题,请参考以下文章

除了明显的错误配置问题之外,还有啥可能导致 Servlet 过滤器的 ClassNotFoundException?

如何在 MySQL 中创建序列?

在 Lumen 中创建自定义错误页面

在 cPanel 中创建自定义 PHP 错误页面,而不将它们从引发错误的页面重定向

如何在 Spring Boot 中创建自定义错误页面

如何在 Symfony 4 中创建登录页面而不会出现“InvalidConfigurationException”错误?