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