Java 如何从 ServletFilter 中的 ServletResponse 中获取 HTTP 状态代码?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1302072/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-12 08:00:55  来源:igfitidea点击:

How can I get the HTTP status code out of a ServletResponse in a ServletFilter?

javaservletsservlet-filtershttp-status-codes

提问by Seth Weiner

I'm trying to report on every HTTP status code returned from my webapp. However the status code does not appear to be accessible via the ServletResponse, or even if I cast it to a HttpServletResponse. Is there a way to get access to this value within a ServletFilter?

我正在尝试报告从我的 web 应用程序返回的每个 HTTP 状态代码。但是,状态代码似乎无法通过 ServletResponse 访问,即使我将其转换为 HttpServletResponse。有没有办法在 ServletFilter 中访问这个值?

采纳答案by David Rabinowitz

First, you need to save the status code in an accessible place. The best to wrap the response with your implementation and keep it there:

首先,您需要将状态代码保存在可访问的位置。最好用您的实现包装响应并将其保留在那里:

public class StatusExposingServletResponse extends HttpServletResponseWrapper {

    private int httpStatus;

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

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

    @Override
    public void sendError(int sc, String msg) throws IOException {
        httpStatus = sc;
        super.sendError(sc, msg);
    }


    @Override
    public void setStatus(int sc) {
        httpStatus = sc;
        super.setStatus(sc);
    }

    public int getStatus() {
        return httpStatus;
    }

}

In order to use this wrapper, you need to add a servlet filter, were you can do your reporting:

为了使用这个包装器,你需要添加一个 servlet 过滤器,你可以做你的报告:

public class StatusReportingFilter implements Filter {

    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
        StatusExposingServletResponse response = new StatusExposingServletResponse((HttpServletResponse)res);
        chain.doFilter(req, response);
        int status = response.getStatus();
        // report
    }

    public void init(FilterConfig config) throws ServletException {
        //empty
    }

    public void destroy() {
        // empty
    }

}

回答by Licky Lindsay

Write an HttpServletResponseWrapper and override all the setStatus(), sendError(), and sendRedirect() methods to log everything. Write a Filter that swaps your wrapper in for the response object on every request.

编写一个 HttpServletResponseWrapper 并覆盖所有 setStatus()、sendError() 和 sendRedirect() 方法以记录所有内容。编写一个过滤器,在每个请求中将您的包装器替换为响应对象。

回答by William Rose

One thing missing from David's answer above is that you should also override the other form of sendError:

上面 David 的回答中缺少的一件事是您还应该覆盖另一种形式的 sendError:

@Override
public void sendError(int sc, String msg) throws IOException {
    httpStatus = sc;
    super.sendError(sc, msg);
}

回答by BalusC

Since Servlet 3.0, there's a HttpServletResponse#getStatus().

从 Servlet 3.0 开始,有一个HttpServletResponse#getStatus().

So, if there's room for upgrading, upgrade to Servlet 3.0 (Tomcat 7, Glassfish 3, JBoss AS 6, etc) and you don't need a wrapper.

因此,如果有升级空间,请升级到 Servlet 3.0(Tomcat 7、Glassfish 3、JBoss AS 6 等)并且您不需要包装器。

chain.doFilter(request, response);
int status = ((HttpServletResponse) response).getStatus();

回答by Joel Hockey

Also need to include a wrapper for #sendRedirect, and it would be better to initialize status to '200' rather than '0'

还需要为#sendRedirect 包含一个包装器,并且最好将状态初始化为“200”而不是“0”

private int httpStatus = SC_OK;

...

@Override
public void sendRedirect(String location) throws IOException {
    httpStatus = SC_MOVED_TEMPORARILY;
    super.sendRedirect(location);
}

回答by John Johnson

If you are stuck with an older container then a alternate solution to David Rabinowitz that uses the actual status code (in case it changes after it is set using the wrapper) is:

如果您坚持使用较旧的容器,那么使用实际状态代码的 David Rabinowitz 的替代解决方案(以防它在使用包装器设置后发生更改)是:

public class StatusExposingServletResponse extends HttpServletResponseWrapper {

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

    @Override
    public void sendError(int sc) throws IOException {
        super.sendError(sc);
    }

    @Override
    public void sendError(int sc, String msg) throws IOException {
        super.sendError(sc, msg);
    }

    @Override
    public void setStatus(int sc) {
        super.setStatus(sc);
    }

    public int getStatus() {
        try {
            ServletResponse object = super.getResponse();

            // call the private method 'getResponse'
            Method method1 = object.getClass().getMethod("getResponse");
            Object servletResponse = method1.invoke(object, new Object[] {});

            // call the parents private method 'getResponse'
            Method method2 = servletResponse.getClass().getMethod("getResponse");
            Object parentResponse = method2.invoke(servletResponse, new Object[] {});

            // call the parents private method 'getResponse'
            Method method3 = parentResponse.getClass().getMethod("getStatus");
            int httpStatus = (Integer) method3.invoke(parentResponse, new Object[] {});

            return httpStatus;
        }
        catch (Exception e) {
            e.printStackTrace();
            return HttpServletResponse.SC_ACCEPTED;
        }
    }

    public String getMessage() {
        try {
            ServletResponse object = super.getResponse();

            // call the private method 'getResponse'
            Method method1 = object.getClass().getMethod("getResponse");
            Object servletResponse = method1.invoke(object, new Object[] {});

            // call the parents private method 'getResponse'
            Method method2 = servletResponse.getClass().getMethod("getResponse");
            Object parentResponse = method2.invoke(servletResponse, new Object[] {});

            // call the parents private method 'getResponse'
            Method method3 = parentResponse.getClass().getMethod("getReason");
            String httpStatusMessage = (String) method3.invoke(parentResponse, new Object[] {});

            if (httpStatusMessage == null) {
                int status = getStatus();
                java.lang.reflect.Field[] fields = HttpServletResponse.class.getFields();

                for (java.lang.reflect.Field field : fields) {
                    if (status == field.getInt(servletResponse)) {
                        httpStatusMessage = field.getName();
                        httpStatusMessage = httpStatusMessage.replace("SC_", "");
                        if (!"OK".equals(httpStatusMessage)) {
                            httpStatusMessage = httpStatusMessage.toLowerCase();
                            httpStatusMessage = httpStatusMessage.replace("_", " ");
                            httpStatusMessage = capitalizeFirstLetters(httpStatusMessage);
                        }

                        break;
                    }
                }
            }

            return httpStatusMessage;
        }
        catch (Exception e) {
            e.printStackTrace();
            return "";
        }
    }

    private static String capitalizeFirstLetters(String s) {

        for (int i = 0; i < s.length(); i++) {
            if (i == 0) {
                // Capitalize the first letter of the string.
                s = String.format("%s%s", Character.toUpperCase(s.charAt(0)), s.substring(1));
            }

            if (!Character.isLetterOrDigit(s.charAt(i))) {
                if (i + 1 < s.length()) {
                    s = String.format("%s%s%s", s.subSequence(0, i + 1), 
                            Character.toUpperCase(s.charAt(i + 1)), 
                            s.substring(i + 2));
                }
            }
        }

        return s;

    }

    @Override
    public String toString() {
        return this.getMessage() + " " + this.getStatus();
    }

}

Warning: lots of assumptions of the class hierarchy when using sneaky reflection and introspection to get to private data values.

警告:当使用偷偷摸摸的反射和内省来获取私有数据值时,类层次结构的许多假设。

回答by Grégory Joseph

In addition to David's answer, you'll also want to override the reset method:

除了 David 的回答之外,您还需要覆盖 reset 方法:

@Override
public void reset() {
    super.reset();
    this.httpStatus = SC_OK;
}

... as well as the deprecated setStatus(int, String)

...以及已弃用的 setStatus(int, String)

@Override
public void setStatus(int status, String string) {
    super.setStatus(status, string);
    this.httpStatus = status;
}