【发布时间】:2010-11-21 02:02:55
【问题描述】:
我正在尝试报告从我的 web 应用返回的每个 HTTP 状态代码。但是,状态代码似乎无法通过 ServletResponse 访问,或者即使我将其转换为 HttpServletResponse。有没有办法在 ServletFilter 中访问这个值?
【问题讨论】:
标签: java servlets servlet-filters http-status-codes
我正在尝试报告从我的 web 应用返回的每个 HTTP 状态代码。但是,状态代码似乎无法通过 ServletResponse 访问,或者即使我将其转换为 HttpServletResponse。有没有办法在 ServletFilter 中访问这个值?
【问题讨论】:
标签: java servlets servlet-filters http-status-codes
首先,您需要将状态代码保存在可访问的位置。最好将响应与您的实现一起包装并保留:
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;
}
}
为了使用这个包装器,你需要添加一个 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
}
}
【讨论】:
HttpServletRepsone.getStatus() - 请参阅 BalusC 的回答:stackoverflow.com/a/4305235/280244
从 Servlet 3.0 开始,有一个 HttpServletResponse#getStatus()。
因此,如果有升级空间,请升级到 Servlet 3.0(Tomcat 7、Glassfish 3、JBoss AS 6 等),并且不需要包装器。
chain.doFilter(request, response);
int status = ((HttpServletResponse) response).getStatus();
【讨论】:
还需要为#sendRedirect 包含一个包装器,最好将状态初始化为'200'而不是'0'
private int httpStatus = SC_OK;
...
@Override
public void sendRedirect(String location) throws IOException {
httpStatus = SC_MOVED_TEMPORARILY;
super.sendRedirect(location);
}
【讨论】:
0
上面大卫的回答中缺少的一件事是您还应该覆盖另一种形式的 sendError:
@Override
public void sendError(int sc, String msg) throws IOException {
httpStatus = sc;
super.sendError(sc, msg);
}
【讨论】:
除了大卫的回答之外,您还需要覆盖重置方法:
@Override
public void reset() {
super.reset();
this.httpStatus = SC_OK;
}
... 以及已弃用的 setStatus(int, String)
@Override
public void setStatus(int status, String string) {
super.setStatus(status, string);
this.httpStatus = status;
}
【讨论】:
编写一个 HttpServletResponseWrapper 并覆盖所有 setStatus()、sendError() 和 sendRedirect() 方法以记录所有内容。编写一个过滤器,将您的包装器换成每个请求的响应对象。
【讨论】:
如果您被旧容器卡住,那么使用实际状态代码的 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();
}
}
警告:在使用偷偷摸摸的反射和自省来获取私有数据值时,对类层次结构有很多假设。
【讨论】: