自定义 Tomcat 404错误页面 Tomcat 的错误页面是由 org.apache.catalina.valves.ErrorReportValve 类输出来的。如果想自定义错误页面,不需要修改该类。Servlet 规范声明了相关的API,只需要在每个 web 应用的 web.xml 里定义。可按照错误类型、错误代码配置。例如:
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
version="2.5">
<display-name>Welcome to Tomcat</display-name>
<description>
Welcome to Tomcat
</description>
<error-page>
<error-code>404</error-code>
<location>/errorpages/404.jsp</location>
</error-page>
<error-page>
<exception-type>java.lang.Exception</exception-type>
<location>/errorpages/exception.jsp</location>
</error-page>
</web-app>
注意错误页面必须以“/”开头,这样任何path的404错误页面及exception错误都会映射到这两个文件。然后在本web引用的errorpages下面放置404.jsp, exception.jsp两个文件。
错误页面 404.jsp:
<%@ page contentType="text/html; charset=UTF-8" %>
<%@ page import="java.io.*" %>
<%@ page import="java.util.*" %>
<html>
<header>
<title>404 page</title>
<body>
<pre>
<%
Enumeration<String> attributeNames = request.getAttributeNames();
while (attributeNames.hasMoreElements())
{
String attributeName = attributeNames.nextElement();
Object attribute = request.getAttribute(attributeName);
out.println("request.attribute['" + attributeName + "'] = " + attribute);
}
%>
</pre>
代码中输出了所有的 request 中的变量。从中也可以看到访问哪个文件出错,跳到哪个错误页面了,从而进行更详细、更人性化的错误处理。例如,提示可能的正确网址等等。
例如:访问一个不存在的页面 page_not_exist.html,显示的信息为:
request.attribute['javax.servlet.forward.request_uri'] = /page_not_exists.html
request.attribute['javax.servlet.forward.context_path'] =
request.attribute['javax.servlet.forward.servlet_path'] = /page_not_exists.html
request.attribute['javax.servlet.forward.path_info'] = /errorpages/404.jsp
request.attribute['javax.servlet.error.message'] = /page_not_exists.html
request.attribute['javax.servlet.error.status_code'] = 404
request.attribute['javax.servlet.error.servlet_name'] = default
request.attribute['javax.servlet.error.request_uri'] = /page_not_exists.html
注意,该错误页面必须大于512字节,否则IE将不予显示。因为IE默认只显示大于512字节的错误页面。Firefox中正常显示。可以添加一些其他信息,将页面大小扩充到512字节以上。如果仍不能显示,请检查IE设置,将该选项选中。
异常处理页面 exception.jsp:
<%@ page contentType="text/html; charset=UTF-8" isErrorPage="true" %>
<%@ page import="java.io.*" %>
<html>
<header>
<title>exception page</title>
<body>
<hr/>
<pre>
<%
response.getWriter().println("Exception: " + exception);
if(exception != null)
{
response.getWriter().println("<pre>");
exception.printStackTrace(response.getWriter());
response.getWriter().println("</pre>");
}
response.getWriter().println("<hr/>");
%>
注意isErrorPage熟悉必须为true,才能使用exception对象。exception即捕捉到的异常。此处可以对exception进行处理,比如记录日志、重定向等等。这里把exception trace打印出来了。
500、505 等错误页面的处理类似于404。
----------------------------------------------
还有一种方法是,自定义ErrorReportValve。适合context比较多的情况。自己实现一个ErrorReportValve,就可以适用于所有的404、505、500等错误了。实现后需打成 jar 包,放置到 tomcat 的全局lib里,并配置到tomcat 的server.xml里:
<Host name="localhost" appBase="webapps"
unpackWARs="true" autoDeploy="true" errorReportValveClass="com.helloweenpad.xxxxx.OurCustomizedErrorReportValve"
xmlValidation="false" xmlNamespaceAware="false">
相关 tomcat 的文档为:
errorReportValveClass
Java class name of the error reporting valve which will be used by this Host. The responsability of this valve is to output error reports. Setting this property allows to customize the look of the error pages which will be generated by Tomcat. This class must implement the org.apache.catalina.Valve interface. If none is specified, the value org.apache.catalina.valves.ErrorReportValve will be used by default.
实现方式见 Tomcat 的 org.apache.catalina.valves.ErrorReportValve 类:
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.catalina.valves;
import java.io.IOException;
import java.io.Writer;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletResponse;
import org.apache.catalina.Globals;
import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.Response;
import org.apache.catalina.util.RequestUtil;
import org.apache.catalina.util.ServerInfo;
import org.apache.catalina.util.StringManager;
/**
* <p>Implementation of a Valve that outputs HTML error pages.</p>
*
* <p>This Valve should be attached at the Host level, although it will work
* if attached to a Context.</p>
*
* <p>HTML code from the Cocoon 2 project.</p>
*
* @author Remy Maucherat
* @author Craig R. McClanahan
* @author <a href=" Ken Barozzi</a> Aisa
* @author <a href=" Mazzocchi</a>
* @author Yoav Shapira
* @version $Revision: 543307 $ $Date: 2007-06-01 01:08:24 +0200 (Fri, 01 Jun 2007) $
*/
public class ErrorReportValve
extends ValveBase {
// ----------------------------------------------------- Instance Variables
/**
* The descriptive information related to this implementation.
*/
private static final String info =
"org.apache.catalina.valves.ErrorReportValve/1.0";
/**
* The StringManager for this package.
*/
protected static StringManager sm =
StringManager.getManager(Constants.Package);
// ------------------------- Properties
/**
* Return descriptive information about this Valve implementation.
*/
public String getInfo() {
return (info);
}
// --------------------- Public Methods
/**
* Invoke the next Valve in the sequence. When the invoke returns, check
* the response state, and output an error report is necessary.
*
* @param request The servlet request to be processed
* @param response The servlet response to be created
*
* @exception IOException if an input/output error occurs
* @exception ServletException if a servlet error occurs
*/
public void invoke(Request request, Response response)
throws IOException, ServletException {
// Perform the request
getNext().invoke(request, response);
Throwable throwable =
(Throwable) request.getAttribute(Globals.EXCEPTION_ATTR);
if (response.isCommitted()) {
return;
}
if (throwable != null) {
// The response is an error
response.setError();
// Reset the response (if possible)
try {
response.reset();
} catch (IllegalStateException e) {
;
}
response.sendError
(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
}
response.setSuspended(false);
try {
report(request, response, throwable);
} catch (Throwable tt) {
;
}
}
// ------------------------------------------------------ Protected Methods
/**
* Prints out an error report.
*
* @param request The request being processed
* @param response The response being generated
* @param throwable The exception that occurred (which possibly wraps
* a root cause exception
*/
protected void report(Request request, Response response,
Throwable throwable) {
// Do nothing on non-HTTP responses
int statusCode = response.getStatus();
// Do nothing on a 1xx, 2xx and 3xx status
// Do nothing if anything has been written already
if ((statusCode < 400) || (response.getContentCount() > 0))
return;
String message = RequestUtil.filter(response.getMessage());
if (message == null)
message = "";
// Do nothing if there is no report for the specified status code
String report = null;
try {
report = sm.getString("http." + statusCode, message);
} catch (Throwable t) {
;
}
if (report == null)
return;
StringBuffer sb = new StringBuffer();
sb.append("<html><head><title>");
sb.append(ServerInfo.getServerInfo()).append(" - ");
sb.append(sm.getString("errorReportValve.errorReport"));
sb.append("</title>");
sb.append("<style><!--");
sb.append(org.apache.catalina.util.TomcatCSS.TOMCAT_CSS);
sb.append("--></style> ");
sb.append("</head><body>");
sb.append("<h1>");
sb.append(sm.getString("errorReportValve.statusHeader",
"" + statusCode, message)).append("</h1>");
sb.append("<HR size=\"1\" noshade=\"noshade\">");
sb.append("<p><b>type</b> ");
if (throwable != null) {
sb.append(sm.getString("errorReportValve.exceptionReport"));
} else {
sb.append(sm.getString("errorReportValve.statusReport"));
}
sb.append("</p>");
sb.append("<p><b>");
sb.append(sm.getString("errorReportValve.message"));
sb.append("</b> <u>");
sb.append(message).append("</u></p>");
sb.append("<p><b>");
sb.append(sm.getString("errorReportValve.description"));
sb.append("</b> <u>");
sb.append(report);
sb.append("</u></p>");
if (throwable != null) {
String stackTrace = getPartialServletStackTrace(throwable);
sb.append("<p><b>");
sb.append(sm.getString("errorReportValve.exception"));
sb.append("</b> <pre>");
sb.append(RequestUtil.filter(stackTrace));
sb.append("</pre></p>");
int loops = 0;
Throwable rootCause = throwable.getCause();
while (rootCause != null && (loops < 10)) {
stackTrace = getPartialServletStackTrace(rootCause);
sb.append("<p><b>");
sb.append(sm.getString("errorReportValve.rootCause"));
sb.append("</b> <pre>");
sb.append(RequestUtil.filter(stackTrace));
sb.append("</pre></p>");
// In case root cause is somehow heavily nested
rootCause = rootCause.getCause();
loops++;
}
sb.append("<p><b>");
sb.append(sm.getString("errorReportValve.note"));
sb.append("</b> <u>");
sb.append(sm.getString("errorReportValve.rootCauseInLogs",
ServerInfo.getServerInfo()));
sb.append("</u></p>");
}
sb.append("<HR size=\"1\" noshade=\"noshade\">");
sb.append("<h3>").append(ServerInfo.getServerInfo()).append("</h3>");
sb.append("</body></html>");
try {
try {
response.setContentType("text/html");
response.setCharacterEncoding("utf-8");
} catch (Throwable t) {
if (container.getLogger().isDebugEnabled())
container.getLogger().debug("status.setContentType", t);
}
Writer writer = response.getReporter();
if (writer != null) {
// If writer is null, it's an indication that the response has
// been hard committed already, which should never happen
writer.write(sb.toString());
}
} catch (IOException e) {
;
} catch (IllegalStateException e) {
;
}
}
/**
* Print out a partial servlet stack trace (truncating at the last
* occurrence of javax.servlet.).
*/
protected String getPartialServletStackTrace(Throwable t) {
StringBuffer trace = new StringBuffer();
trace.append(t.toString()).append('\n');
StackTraceElement[] elements = t.getStackTrace();
int pos = elements.length;
for (int i = 0; i < elements.length; i++) {
if ((elements[i].getClassName().startsWith
("org.apache.catalina.core.ApplicationFilterChain"))
&& (elements[i].getMethodName().equals("internalDoFilter"))) {
pos = i;
}
}
for (int i = 0; i < pos; i++) {
if (!(elements[i].getClassName().startsWith
("org.apache.catalina.core."))) {
trace.append('\t').append(elements[i].toString()).append('\n');
}
}
return trace.toString();
}
}