1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18
19 package org.apache.catalina.core;
20
21
22 import java.io.IOException;
23
24 import javax.servlet.RequestDispatcher;
25 import javax.servlet.ServletContext;
26 import javax.servlet.ServletException;
27 import javax.servlet.http.HttpServletResponse;
28
29 import org.apache.catalina.CometEvent;
30 import org.apache.catalina.Context;
31 import org.apache.catalina.Globals;
32 import org.apache.catalina.Wrapper;
33 import org.apache.catalina.connector.ClientAbortException;
34 import org.apache.catalina.connector.Request;
35 import org.apache.catalina.connector.Response;
36 import org.apache.catalina.deploy.ErrorPage;
37 import org.apache.catalina.util.StringManager;
38 import org.apache.catalina.valves.ValveBase;
39 import org.apache.juli.logging.Log;
40 import org.apache.juli.logging.LogFactory;
41
42
43 /**
44 * Valve that implements the default basic behavior for the
45 * <code>StandardHost</code> container implementation.
46 * <p>
47 * <b>USAGE CONSTRAINT</b>: This implementation is likely to be useful only
48 * when processing HTTP requests.
49 *
50 * @author Craig R. McClanahan
51 * @author Remy Maucherat
52 * @version $Revision: 781484 $ $Date: 2009-06-03 19:23:03 +0200 (Wed, 03 Jun 2009) $
53 */
54
55 final class StandardHostValve
56 extends ValveBase {
57
58
59 private static Log log = LogFactory.getLog(StandardHostValve.class);
60
61 // ----------------------------------------------------- Instance Variables
62
63
64 /**
65 * The descriptive information related to this implementation.
66 */
67 private static final String info =
68 "org.apache.catalina.core.StandardHostValve/1.0";
69
70
71 /**
72 * The string manager for this package.
73 */
74 private static final StringManager sm =
75 StringManager.getManager(Constants.Package);
76
77
78 // ------------------------------------------------------------- Properties
79
80
81 /**
82 * Return descriptive information about this Valve implementation.
83 */
84 public String getInfo() {
85
86 return (info);
87
88 }
89
90
91 // --------------------------------------------------------- Public Methods
92
93
94 /**
95 * Select the appropriate child Context to process this request,
96 * based on the specified request URI. If no matching Context can
97 * be found, return an appropriate HTTP error.
98 *
99 * @param request Request to be processed
100 * @param response Response to be produced
101 * @param valveContext Valve context used to forward to the next Valve
102 *
103 * @exception IOException if an input/output error occurred
104 * @exception ServletException if a servlet error occurred
105 */
106 public final void invoke(Request request, Response response)
107 throws IOException, ServletException {
108
109 // Select the Context to be used for this Request
110 Context context = request.getContext();
111 if (context == null) {
112 response.sendError
113 (HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
114 sm.getString("standardHost.noContext"));
115 return;
116 }
117
118 // Bind the context CL to the current thread
119 if( context.getLoader() != null ) {
120 // Not started - it should check for availability first
121 // This should eventually move to Engine, it's generic.
122 Thread.currentThread().setContextClassLoader
123 (context.getLoader().getClassLoader());
124 }
125
126 // Ask this Context to process this request
127 context.getPipeline().getFirst().invoke(request, response);
128
129 // Access a session (if present) to update last accessed time, based on a
130 // strict interpretation of the specification
131 if (Globals.STRICT_SERVLET_COMPLIANCE) {
132 request.getSession(false);
133 }
134
135 // Error page processing
136 response.setSuspended(false);
137
138 Throwable t = (Throwable) request.getAttribute(Globals.EXCEPTION_ATTR);
139
140 if (t != null) {
141 throwable(request, response, t);
142 } else {
143 status(request, response);
144 }
145
146 // Restore the context classloader
147 Thread.currentThread().setContextClassLoader
148 (StandardHostValve.class.getClassLoader());
149
150 }
151
152
153 /**
154 * Process Comet event.
155 *
156 * @param request Request to be processed
157 * @param response Response to be produced
158 * @param valveContext Valve context used to forward to the next Valve
159 *
160 * @exception IOException if an input/output error occurred
161 * @exception ServletException if a servlet error occurred
162 */
163 public final void event(Request request, Response response, CometEvent event)
164 throws IOException, ServletException {
165
166 // Select the Context to be used for this Request
167 Context context = request.getContext();
168
169 // Bind the context CL to the current thread
170 if( context.getLoader() != null ) {
171 // Not started - it should check for availability first
172 // This should eventually move to Engine, it's generic.
173 Thread.currentThread().setContextClassLoader
174 (context.getLoader().getClassLoader());
175 }
176
177 // Ask this Context to process this request
178 context.getPipeline().getFirst().event(request, response, event);
179
180 // Access a session (if present) to update last accessed time, based on a
181 // strict interpretation of the specification
182 if (Globals.STRICT_SERVLET_COMPLIANCE) {
183 request.getSession(false);
184 }
185
186 // Error page processing
187 response.setSuspended(false);
188
189 Throwable t = (Throwable) request.getAttribute(Globals.EXCEPTION_ATTR);
190
191 if (t != null) {
192 throwable(request, response, t);
193 } else {
194 status(request, response);
195 }
196
197 // Restore the context classloader
198 Thread.currentThread().setContextClassLoader
199 (StandardHostValve.class.getClassLoader());
200
201 }
202
203
204 // ------------------------------------------------------ Protected Methods
205
206
207 /**
208 * Handle the specified Throwable encountered while processing
209 * the specified Request to produce the specified Response. Any
210 * exceptions that occur during generation of the exception report are
211 * logged and swallowed.
212 *
213 * @param request The request being processed
214 * @param response The response being generated
215 * @param throwable The exception that occurred (which possibly wraps
216 * a root cause exception
217 */
218 protected void throwable(Request request, Response response,
219 Throwable throwable) {
220 Context context = request.getContext();
221 if (context == null)
222 return;
223
224 Throwable realError = throwable;
225
226 if (realError instanceof ServletException) {
227 realError = ((ServletException) realError).getRootCause();
228 if (realError == null) {
229 realError = throwable;
230 }
231 }
232
233 // If this is an aborted request from a client just log it and return
234 if (realError instanceof ClientAbortException ) {
235 if (log.isDebugEnabled()) {
236 log.debug
237 (sm.getString("standardHost.clientAbort",
238 realError.getCause().getMessage()));
239 }
240 return;
241 }
242
243 ErrorPage errorPage = findErrorPage(context, throwable);
244 if ((errorPage == null) && (realError != throwable)) {
245 errorPage = findErrorPage(context, realError);
246 }
247
248 if (errorPage != null) {
249 response.setAppCommitted(false);
250 request.setAttribute
251 (ApplicationFilterFactory.DISPATCHER_REQUEST_PATH_ATTR,
252 errorPage.getLocation());
253 request.setAttribute(ApplicationFilterFactory.DISPATCHER_TYPE_ATTR,
254 new Integer(ApplicationFilterFactory.ERROR));
255 request.setAttribute
256 (Globals.STATUS_CODE_ATTR,
257 new Integer(HttpServletResponse.SC_INTERNAL_SERVER_ERROR));
258 request.setAttribute(Globals.ERROR_MESSAGE_ATTR,
259 throwable.getMessage());
260 request.setAttribute(Globals.EXCEPTION_ATTR,
261 realError);
262 Wrapper wrapper = request.getWrapper();
263 if (wrapper != null)
264 request.setAttribute(Globals.SERVLET_NAME_ATTR,
265 wrapper.getName());
266 request.setAttribute(Globals.EXCEPTION_PAGE_ATTR,
267 request.getRequestURI());
268 request.setAttribute(Globals.EXCEPTION_TYPE_ATTR,
269 realError.getClass());
270 if (custom(request, response, errorPage)) {
271 try {
272 response.flushBuffer();
273 } catch (IOException e) {
274 container.getLogger().warn("Exception Processing " + errorPage, e);
275 }
276 }
277 } else {
278 // A custom error-page has not been defined for the exception
279 // that was thrown during request processing. Check if an
280 // error-page for error code 500 was specified and if so,
281 // send that page back as the response.
282 response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
283 // The response is an error
284 response.setError();
285
286 status(request, response);
287 }
288
289
290 }
291
292
293 /**
294 * Handle the HTTP status code (and corresponding message) generated
295 * while processing the specified Request to produce the specified
296 * Response. Any exceptions that occur during generation of the error
297 * report are logged and swallowed.
298 *
299 * @param request The request being processed
300 * @param response The response being generated
301 */
302 protected void status(Request request, Response response) {
303
304 int statusCode = response.getStatus();
305
306 // Handle a custom error page for this status code
307 Context context = request.getContext();
308 if (context == null)
309 return;
310
311 /* Only look for error pages when isError() is set.
312 * isError() is set when response.sendError() is invoked. This
313 * allows custom error pages without relying on default from
314 * web.xml.
315 */
316 if (!response.isError())
317 return;
318
319 ErrorPage errorPage = context.findErrorPage(statusCode);
320 if (errorPage != null) {
321 response.setAppCommitted(false);
322 request.setAttribute(Globals.STATUS_CODE_ATTR,
323 new Integer(statusCode));
324
325 String message = response.getMessage();
326 if (message == null)
327 message = "";
328 request.setAttribute(Globals.ERROR_MESSAGE_ATTR, message);
329 request.setAttribute
330 (ApplicationFilterFactory.DISPATCHER_REQUEST_PATH_ATTR,
331 errorPage.getLocation());
332 request.setAttribute(ApplicationFilterFactory.DISPATCHER_TYPE_ATTR,
333 new Integer(ApplicationFilterFactory.ERROR));
334
335
336 Wrapper wrapper = request.getWrapper();
337 if (wrapper != null)
338 request.setAttribute(Globals.SERVLET_NAME_ATTR,
339 wrapper.getName());
340 request.setAttribute(Globals.EXCEPTION_PAGE_ATTR,
341 request.getRequestURI());
342 if (custom(request, response, errorPage)) {
343 try {
344 response.flushBuffer();
345 } catch (ClientAbortException e) {
346 // Ignore
347 } catch (IOException e) {
348 container.getLogger().warn("Exception Processing " + errorPage, e);
349 }
350 }
351 }
352
353 }
354
355
356 /**
357 * Find and return the ErrorPage instance for the specified exception's
358 * class, or an ErrorPage instance for the closest superclass for which
359 * there is such a definition. If no associated ErrorPage instance is
360 * found, return <code>null</code>.
361 *
362 * @param context The Context in which to search
363 * @param exception The exception for which to find an ErrorPage
364 */
365 protected static ErrorPage findErrorPage
366 (Context context, Throwable exception) {
367
368 if (exception == null)
369 return (null);
370 Class<?> clazz = exception.getClass();
371 String name = clazz.getName();
372 while (!Object.class.equals(clazz)) {
373 ErrorPage errorPage = context.findErrorPage(name);
374 if (errorPage != null)
375 return (errorPage);
376 clazz = clazz.getSuperclass();
377 if (clazz == null)
378 break;
379 name = clazz.getName();
380 }
381 return (null);
382
383 }
384
385
386 /**
387 * Handle an HTTP status code or Java exception by forwarding control
388 * to the location included in the specified errorPage object. It is
389 * assumed that the caller has already recorded any request attributes
390 * that are to be forwarded to this page. Return <code>true</code> if
391 * we successfully utilized the specified error page location, or
392 * <code>false</code> if the default error report should be rendered.
393 *
394 * @param request The request being processed
395 * @param response The response being generated
396 * @param errorPage The errorPage directive we are obeying
397 */
398 protected boolean custom(Request request, Response response,
399 ErrorPage errorPage) {
400
401 if (container.getLogger().isDebugEnabled())
402 container.getLogger().debug("Processing " + errorPage);
403
404 request.setPathInfo(errorPage.getLocation());
405
406 try {
407 // Reset the response (keeping the real error code and message)
408 response.resetBuffer(true);
409
410 // Forward control to the specified location
411 ServletContext servletContext =
412 request.getContext().getServletContext();
413 RequestDispatcher rd =
414 servletContext.getRequestDispatcher(errorPage.getLocation());
415 rd.forward(request.getRequest(), response.getResponse());
416
417 // If we forward, the response is suspended again
418 response.setSuspended(false);
419
420 // Indicate that we have successfully processed this custom page
421 return (true);
422
423 } catch (Throwable t) {
424
425 // Report our failure to process this custom page
426 container.getLogger().error("Exception Processing " + errorPage, t);
427 return (false);
428
429 }
430
431 }
432
433
434 }