如何在不指定servlet位置的情况下谎言错误代码?

时间:2014-10-25 15:12:55

标签: java tomcat http-status-codes

在Tomcat中,我们可以在Web应用程序的web.xml中指定类似以下内容的片段来定义自定义错误处理程序:

<error-page>
    <error-code>500</error-code>
    <location>/SomeServlet</location>
</error-page>

并且还可以使用一个处理程序覆盖所有错误代码的内置页面:

<error-page>
    <location>/SomeServlet</location>
</error-page>

正如我们所看到的,当我们这样做时,总会有一个自定义处理程序;在它上面的片段/SomeServlet

但是我想如果我只是想与另一个现有的内置处理程序讨论特定的错误代码,例如,谎言HTTP 404HTTP 401,并使用HTTP 401的内置处理程序进行响应。

我怎么能这样做或甚至可能?如果没有,那么使用Tomcat的内置库来解决错误代码的最佳做法是什么?

5 个答案:

答案 0 :(得分:1)

实施您自己的HttpServletResponse并使用它。通过这种方式,您可以为一个代码设置<error-code>,并在HTTP响应中为另一个代码返回响应。

另一个方法是实现自己的Servlet Filter,但这会对性能产生重大影响。

使用google作为示例如何实现它。

答案 1 :(得分:1)

这个看起来很有趣,所以我抓了一个。

可悲的是,我无法通过配置找到一个好方法。但是,我确实提出了一种方法,允许您在web.xml中设置单个过滤器,并通过该过滤器的init参数“重新指定”所有HTTP状态代码。

需要注意的是,这个答案与其他答案的不同之处在于我确实实现了两者一个过滤器和一个HttpServletResponseWrapper,因为我想捕获所有极端情况,包括响应的位置{{ 3}}在过滤器接收响应之前。此提交可以在各种地方进行,例如调用committed的客户端代码,框架等

无论如何......你的web.xml片段看起来像是:

<filter>
    <filter-name>HttpStatusCodeFilter</filter-name>
    <filter-class>net.stackoverflow.HttpStatusCodeConverter</filter-class>
    <init-param>
        <param-name>502</param-name>
        <param-value>403</param-value>
    </init-param>
    <init-param>
        <param-name>405</param-name>
        <param-value>403</param-value>
    </init-param>
</filter>
<filter-mapping>
    <filter-name>HttpStatusCodeFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

和您的Servlet过滤器(以及内部HttpServletResponseWrapper类)如下:

public class HttpStatusCodeConverter implements Filter {

    private static final Logger logger
            = Logger.getLogger(HttpStatusCodeConverter.class.getName());
    private Map<Integer, Integer> errorMap;

    @Override
    public void doFilter(ServletRequest request, ServletResponse response,
            FilterChain chain)
            throws IOException, ServletException {

        if (response instanceof HttpServletResponse) {
            AntiCommitResponseWrapper hr = new AntiCommitResponseWrapper(
                    (HttpServletResponse) response);

            //pre-filter check for error codes, if code exists, no need to continue
            if (!hasFilterCode(hr)) {
                //no error yet, progress through filters
                try {
                    chain.doFilter(request, hr);
                } catch (Throwable t) {
                    if (!hasFilterCode(hr)) {
                        //exception from a filter but no code was set, set it now
                        hr.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
                    }
                }
            }

            //do post-filter check for error codes
            if (hasFilterCode(hr)) {
                hr.sendError(errorMap.get(hr.getStatus()));
            }

            hr.complete();
        }

    }

    private boolean hasFilterCode(HttpServletResponse hr) {
        return errorMap != null && errorMap.containsKey(hr.getStatus());
    }

    @Override
    public void destroy() {
    }

    @Override
    public void init(FilterConfig filterConfig) {
        errorMap = new HashMap<>();
        Enumeration<String> paramNames = filterConfig.getInitParameterNames();
        while (paramNames.hasMoreElements()) {
            String name = paramNames.nextElement();
            try {
                errorMap.put(Integer.valueOf(name),
                        Integer.valueOf(filterConfig.getInitParameter(name)));
            } catch (NumberFormatException ex) {
                logger.log(Level.WARNING, "Invalid HTTP status code mapping "
                        + "''{0}''->''{1}''.", 
                        new Object[]{name, filterConfig.getInitParameter(name)});
            }
        }
    }

    private class AntiCommitResponseWrapper extends HttpServletResponseWrapper {

        private int status = SC_OK;
        private String statusMsg;
        private String redirectLocation;
        private final ByteArrayOutputStream buffer = new ByteArrayOutputStream();

        public AntiCommitResponseWrapper(HttpServletResponse response) {
            super(response);
        }

        @Override
        public int getStatus() {
            return status;
        }

        @Override
        public void setStatus(int sc) {
            this.status = sc;
        }

        @Override
        public void sendRedirect(String location) throws IOException {
            this.status = SC_FOUND;
            this.redirectLocation = location;
        }

        @Override
        public void sendError(int sc) throws IOException {
            this.status = sc;
        }

        @Override
        public void sendError(int sc, String msg) throws IOException {
            this.status = sc;
            this.statusMsg = msg;
        }

        @Override
        public void resetBuffer() {
            buffer.reset();
        }

        @Override
        public void reset() {
            buffer.reset();
            status = SC_OK;
            statusMsg = null;
            super.reset();
        }

        @Override
        public void flushBuffer() throws IOException {
        }

        @Override
        public void setBufferSize(int size) {
        }

        @Override
        public ServletOutputStream getOutputStream() throws IOException {
            return new ServletOutputStream() {

                @Override
                public boolean isReady() {
                    return true;
                }

                @Override
                public void setWriteListener(WriteListener writeListener) {
                    try {
                        writeListener.onWritePossible();
                    } catch (IOException ex) {
                    }
                }

                @Override
                public void write(int i) throws IOException {
                    buffer.write(i);
                }
            };
        }

        /**
         * Send everything to the client.
         */
        private void complete() throws IOException {
            if (status != SC_OK) {
                super.sendError(status, statusMsg);
            } else if (status == SC_FOUND) {
                super.sendRedirect(redirectLocation);
            } else {
                super.setStatus(status);
                try (OutputStream out = super.getOutputStream()) {
                    out.write(buffer.toByteArray());
                }
            }

        }
    }
}

最后,为了进行测试,我最终创建了HttpStatusCodeConverter作为Servlet 3.0的HttpServletResponse#sendError,如果您感兴趣,可以在web-fragment上使用它。您可以分叉项目,添加自己的过滤器参数,然后将其放到类路径中......或者将其添加到CP中,并通过在web.xml中添加自己的配置来覆盖Web片段过滤器参数。 / p>

答案 2 :(得分:1)

所以你想在不编写任何代码的情况下更改响应代码?

尝试使用url-rewrite

首先,将url-rewrite Filter映射到任意URL(如/fake-responses/404)。然后,创建一个匹配/fake-responses/404的重写规则,并将响应状态代码设置为401(或其他)。调用目标URL /fake-responses/401可能更有意义,因为这将是返回给客户端的内容。

答案 3 :(得分:0)

如果您使用Spring,答案肯定就在那里,而不是在Tomcat中。尽管如此,这是一个很大的问题。我无法看到Tomcat能够做到这样的事情。然而,这并不是零代码。

  

通常,处理Web请求时抛出的任何未处理的异常都会导致服务器返回HTTP 500响应。但是,您自己编写的任何异常都可以使用@ResponseStatus注释(它支持HTTP规范定义的所有HTTP状态代码)进行注释。当从控制器方法抛出带注释的异常,而不在其他地方处理时,它将自动导致使用指定的状态代码返回相应的HTTP响应。

答案 4 :(得分:-1)

直接转发错误页面而不转发错误处理程序。也许这可以为您提供:

<error-code>500</error-code>
<location>/resources/errorPage404.jsp</location>