自定義 Tomcat 404錯誤處理邏輯,以及錯誤現實(shí)頁(yè)面
Tomcat 的錯誤頁(yè)面是由 org.apache.catalina.valves.ErrorReportValve 類(lèi)輸出來(lái)的。如果想自定義錯誤頁(yè)面,不需要修改該類(lèi)。Servlet 規范聲明了相關(guān)的API,只需要在每個(gè) web 應用的 web.xml 里定義??砂凑斟e誤類(lèi)型、錯誤代碼配置。例如:
<web-app xmlns="
xmlns:xsi="
xsi:schemaLocation="
http://java.sun.com/xml/ns/javaee 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>
注意錯誤頁(yè)面必須以“/”開(kāi)頭,這樣任何path的404錯誤頁(yè)面及exception錯誤都會(huì )映射到這兩個(gè)文件。然后在本web引用的errorpages下面放置404.jsp, exception.jsp兩個(gè)文件。
錯誤頁(yè)面 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 中的變量。從中也可以看到訪(fǎng)問(wèn)哪個(gè)文件出錯,跳到哪個(gè)錯誤頁(yè)面了,從而進(jìn)行更詳細、更人性化的錯誤處理。例如,提示可能的正確網(wǎng)址等等。
例如:訪(fǎng)問(wèn)一個(gè)不存在的頁(yè)面 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
注意,該錯誤頁(yè)面必須大于512字節,否則IE將不予顯示。因為IE默認只顯示大于512字節的錯誤頁(yè)面。Firefox中正常顯示??梢蕴砑右恍┢渌畔?,將頁(yè)面大小擴充到512字節以上。如果仍不能顯示,請檢查IE設置,將該選項選中。
異常處理頁(yè)面 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進(jìn)行處理,比如記錄日志、重定向等等。這里把exception trace打印出來(lái)了。
500、505 等錯誤頁(yè)面的處理類(lèi)似于404。
----------------------------------------------------------------------------------
還有一種方法是,自定義ErrorReportValve。適合context比較多的情況。自己實(shí)現一個(gè)ErrorReportValve,就可以適用于所有的404、505、500等錯誤了。實(shí)現后需打成 jar 包,放置到 tomcat 的全局lib里,并配置到tomcat 的server.xml里:
<Host name="localhost" appBase="webapps"
unpackWARs="true" autoDeploy="true" errorReportValveClass="com.helloweenpad.xxxxx.OurCustomizedErrorReportValve"
xmlValidation="false" xmlNamespaceAware="false">
相關(guān) 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.
實(shí)現方式見(jiàn) Tomcat 的 org.apache.catalina.valves.ErrorReportValve 類(lèi):
/*
* 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 ErrorReportValveextends 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 requestgetNext().invoke(request, response);Throwable throwable =(Throwable) request.getAttribute(Globals.EXCEPTION_ATTR);if (response.isCommitted()) {return;}if (throwable != null) {// The response is an errorresponse.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 responsesint statusCode = response.getStatus();// Do nothing on a 1xx, 2xx and 3xx status// Do nothing if anything has been written alreadyif ((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 codeString 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 nestedrootCause = 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 happenwriter.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();}}