View Javadoc
1   /*
2    * Copyright 2013–2019 Michael Osipov
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package net.sf.michaelo.tomcat.extras.valves;
17  
18  import java.io.IOException;
19  import java.io.Writer;
20  import java.util.Scanner;
21  
22  import org.apache.catalina.connector.Request;
23  import org.apache.catalina.connector.Response;
24  import org.apache.catalina.util.RequestUtil;
25  import org.apache.catalina.util.ServerInfo;
26  import org.apache.catalina.util.TomcatCSS;
27  import org.apache.catalina.valves.ErrorReportValve;
28  import org.apache.tomcat.util.ExceptionUtils;
29  import org.apache.tomcat.util.res.StringManager;
30  
31  /**
32   * An alternative error report valve with several improvements over the original one.
33   * <p>
34   * The improvements consist of:
35   * <ol>
36   * <li>Language style improvements.</li>
37   * <li>No repetition of messages.</li>
38   * <li>Standard status code reason phrases and descriptions.</li>
39   * </ol>
40   * This work is based off the original {@link ErrorReportValve} which is licensed under Apache
41   * License, Version 2.0.
42   *
43   * @version $Id: EnhancedErrorReportValve.java 123 2019-03-09 22:29:27Z michael-o $
44   */
45  public class EnhancedErrorReportValve extends ErrorReportValve {
46  
47  	@Override
48  	protected void report(Request request, Response response, Throwable throwable) {
49  
50  		int statusCode = response.getStatus();
51  
52  		// Do nothing on a 1xx, 2xx and 3xx status
53  		// Do nothing if anything has been written already
54  		// Do nothing if the response hasn't been explicitly marked as in error
55  		// and that error has not been reported.
56  		if (statusCode < 400 || response.getContentWritten() > 0 || !response.setErrorReported()) {
57  			return;
58  		}
59  		String message = RequestUtil.filter(response.getMessage());
60  		if (message == null) {
61  			if (throwable != null) {
62  				String exceptionMessage = throwable.getMessage();
63  				if (exceptionMessage != null && exceptionMessage.length() > 0)
64  					message = RequestUtil.filter((new Scanner(exceptionMessage)).nextLine());
65  			}
66  			if (message == null)
67  				message = "";
68  		}
69  
70  		// Do nothing if there is no reason phrase for the specified status code and
71  		// no error message provided
72  		String reason = null;
73  		String description = null;
74  		StringManager smClient = StringManager.getManager(
75  				EnhancedErrorReportValve.class.getPackage().getName(), request.getLocales());
76  		response.setLocale(smClient.getLocale());
77  		try {
78  			reason = smClient.getString("http." + statusCode + ".reason");
79  			description = smClient.getString("http." + statusCode + ".desc");
80  		} catch (Throwable t) {
81  			ExceptionUtils.handleThrowable(t);
82  		}
83  		if (reason == null || description == null) {
84  			if (message.isEmpty()) {
85  				return;
86  			} else {
87  				reason = smClient.getString("errorReportValve.unknownReason");
88  				description = smClient.getString("errorReportValve.noDescription");
89  			}
90  		}
91  
92  		StringBuilder sb = new StringBuilder();
93  
94  		sb.append("<!doctype html><html lang=\"");
95  		sb.append(smClient.getLocale().getLanguage()).append("\">");
96  		sb.append("<head>");
97  		sb.append("<title>");
98  		sb.append(smClient.getString("enhancedErrorReportValve.statusHeader",
99  				String.valueOf(statusCode), reason));
100 		sb.append("</title>");
101 		sb.append("<style type=\"text/css\">");
102 		sb.append(TomcatCSS.TOMCAT_CSS);
103 		sb.append("</style>");
104 		sb.append("</head><body>");
105 		sb.append("<h1>");
106 		sb.append(smClient.getString("enhancedErrorReportValve.statusHeader",
107 				String.valueOf(statusCode), reason)).append("</h1>");
108 		if (isShowReport()) {
109 			sb.append("<hr class=\"line\" />");
110 			sb.append("<p><b>");
111 			sb.append(smClient.getString("enhancedErrorReportValve.type"));
112 			sb.append("</b> ");
113 			if (throwable != null)
114 				sb.append(smClient.getString("enhancedErrorReportValve.exceptionReport"));
115 			else
116 				sb.append(smClient.getString("enhancedErrorReportValve.statusReport"));
117 			sb.append("</p>");
118 			if (!message.isEmpty()) {
119 				sb.append("<p><b>");
120 				sb.append(smClient.getString("enhancedErrorReportValve.message"));
121 				sb.append("</b> ");
122 				sb.append(message).append("</p>");
123 			}
124 			sb.append("<p><b>");
125 			sb.append(smClient.getString("enhancedErrorReportValve.description"));
126 			sb.append("</b> ");
127 			sb.append(description);
128 			sb.append("</p>");
129 			if (throwable != null) {
130 				String stackTrace = getPartialServletStackTrace(throwable);
131 				sb.append("<p><b>");
132 				sb.append(smClient.getString("enhancedErrorReportValve.exception"));
133 				sb.append("</b></p><pre>");
134 				sb.append(RequestUtil.filter(stackTrace));
135 				sb.append("</pre>");
136 
137 				int loops = 0;
138 				Throwable rootCause = throwable.getCause();
139 				while (rootCause != null && loops < 10) {
140 					stackTrace = getPartialServletStackTrace(rootCause);
141 					sb.append("<p><b>");
142 					sb.append(smClient.getString("enhancedErrorReportValve.rootCause"));
143 					sb.append("</b></p><pre>");
144 					sb.append(RequestUtil.filter(stackTrace));
145 					sb.append("</pre>");
146 					// In case root cause is somehow heavily nested
147 					rootCause = rootCause.getCause();
148 					loops++;
149 				}
150 
151 				sb.append("<p><b>");
152 				sb.append(smClient.getString("enhancedErrorReportValve.note"));
153 				sb.append("</b> ");
154 				sb.append(smClient.getString("enhancedErrorReportValve.rootCauseInLogs"));
155 				sb.append("</p>");
156 
157 			}
158 			sb.append("<hr class=\"line\" />");
159 		}
160 		if (isShowServerInfo())
161 			sb.append("<h3>").append(ServerInfo.getServerInfo()).append("</h3>");
162 		sb.append("</body></html>");
163 
164 		try {
165 			try {
166 				response.setContentType("text/html");
167 				response.setCharacterEncoding("utf-8");
168 			} catch (Throwable t) {
169 				ExceptionUtils.handleThrowable(t);
170 				if (container.getLogger().isDebugEnabled())
171 					container.getLogger().debug("status.setContentType", t);
172 			}
173 			Writer writer = response.getReporter();
174 			if (writer != null) {
175 				// If writer is null, it's an indication that the response has
176 				// been hard committed already, which should never happen
177 				writer.write(sb.toString());
178 				response.finishResponse();
179 			}
180 		} catch (IOException e) {
181 			// ignore
182 		} catch (IllegalStateException e) {
183 			// ignore
184 		}
185 
186 	}
187 
188 }