68

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?

Etienne Neveu
  • 12,604
  • 9
  • 36
  • 59
Seth Weiner
  • 2,999
  • 5
  • 26
  • 14

7 Answers7

93

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:

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
    }

}
David Rabinowitz
  • 29,904
  • 14
  • 93
  • 125
  • 9
    in case somebody does not read until the end of page, watch out Joel's comment below to also set default status=200 and to also override sendRedirect(..) – manuel aldana Oct 19 '12 at 15:56
  • 1
    This was supremely helpful for an older version of Tomcat that's on Servlet spec 2.4. Thank you! – user3621633 Mar 01 '16 at 20:13
  • response.sendRedirect() is giving illegalStateExcpetion. I have overridded sendRedirect also as Joel's comment – Prasanna Kumar H A Apr 11 '16 at 08:47
  • There is `HttpServletRepsone.getStatus()` since Servlet 3.0 - see BalusC`s answer: http://stackoverflow.com/a/4305235/280244 – Ralph Nov 21 '16 at 20:35
70

Since Servlet 3.0, there's a 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.

chain.doFilter(request, response);
int status = ((HttpServletResponse) response).getStatus();
BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555
17

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

private int httpStatus = SC_OK;

...

@Override
public void sendRedirect(String location) throws IOException {
    httpStatus = SC_MOVED_TEMPORARILY;
    super.sendRedirect(location);
}
orique
  • 1,295
  • 1
  • 27
  • 36
Joel Hockey
  • 231
  • 3
  • 4
  • I can see situations where your filter mapping placement can impact whether your overriding code gets triggered. For example, a successive filter may not wrap your response but rather replace it. Besides those scenarios, can the status code be set on the response without invoking setStatus, sendError or sendRedirect variants? Is that why you've initialized status to 200? – 1in9ui5t Dec 04 '12 at 16:50
  • @Joel Can you please mention why to use SC_OK here? I have faced the same issue where the responseWrapper setStatus() is not invoked and httpStatus remains `0` – Piyush Upadhyay Dec 04 '19 at 11:04
12

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

@Override
public void sendError(int sc, String msg) throws IOException {
    httpStatus = sc;
    super.sendError(sc, msg);
}
William Rose
  • 971
  • 1
  • 6
  • 13
8

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

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

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

@Override
public void setStatus(int status, String string) {
    super.setStatus(status, string);
    this.httpStatus = status;
}
orique
  • 1,295
  • 1
  • 27
  • 36
Grégory Joseph
  • 1,549
  • 16
  • 14
6

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.

Licky Lindsay
  • 1,048
  • 6
  • 10
0

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:

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.

John Johnson
  • 99
  • 1
  • 1