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 import java.util.concurrent.atomic.AtomicInteger;
24
25 import javax.servlet.DispatcherType;
26 import javax.servlet.RequestDispatcher;
27 import javax.servlet.Servlet;
28 import javax.servlet.ServletException;
29 import javax.servlet.UnavailableException;
30 import javax.servlet.http.HttpServletResponse;
31
32 import org.apache.catalina.Container;
33 import org.apache.catalina.Context;
34 import org.apache.catalina.Globals;
35 import org.apache.catalina.LifecycleException;
36 import org.apache.catalina.connector.ClientAbortException;
37 import org.apache.catalina.connector.Request;
38 import org.apache.catalina.connector.Response;
39 import org.apache.catalina.valves.ValveBase;
40 import org.apache.coyote.CloseNowException;
41 import org.apache.tomcat.util.ExceptionUtils;
42 import org.apache.tomcat.util.buf.MessageBytes;
43 import org.apache.tomcat.util.log.SystemLogHandler;
44 import org.apache.tomcat.util.res.StringManager;
45
46 /**
47  * Valve that implements the default basic behavior for the
48  * <code>StandardWrapper</code> container implementation.
49  *
50  * @author Craig R. McClanahan
51  */

52 final class StandardWrapperValve
53     extends ValveBase {
54
55     //------------------------------------------------------ Constructor
56     public StandardWrapperValve() {
57         super(true);
58     }
59
60     // ----------------------------------------------------- Instance Variables
61
62
63     // Some JMX statistics. This valve is associated with a StandardWrapper.
64     // We expose the StandardWrapper as JMX ( j2eeType=Servlet ). The fields
65     // are here for performance.
66     private volatile long processingTime;
67     private volatile long maxTime;
68     private volatile long minTime = Long.MAX_VALUE;
69     private final AtomicInteger requestCount = new AtomicInteger(0);
70     private final AtomicInteger errorCount = new AtomicInteger(0);
71
72
73     /**
74      * The string manager for this package.
75      */

76     private static final StringManager sm =
77         StringManager.getManager(Constants.Package);
78
79
80     // --------------------------------------------------------- Public Methods
81
82
83     /**
84      * Invoke the servlet we are managing, respecting the rules regarding
85      * servlet lifecycle and SingleThreadModel support.
86      *
87      * @param request Request to be processed
88      * @param response Response to be produced
89      *
90      * @exception IOException if an input/output error occurred
91      * @exception ServletException if a servlet error occurred
92      */

93     @Override
94     public final void invoke(Request request, Response response)
95         throws IOException, ServletException {
96
97         // Initialize local variables we may need
98         boolean unavailable = false;
99         Throwable throwable = null;
100         // This should be a Request attribute...
101         long t1=System.currentTimeMillis();
102         requestCount.incrementAndGet();
103         StandardWrapper wrapper = (StandardWrapper) getContainer();
104         Servlet servlet = null;
105         Context context = (Context) wrapper.getParent();
106
107         // Check for the application being marked unavailable
108         if (!context.getState().isAvailable()) {
109             response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE,
110                            sm.getString("standardContext.isUnavailable"));
111             unavailable = true;
112         }
113
114         // Check for the servlet being marked unavailable
115         if (!unavailable && wrapper.isUnavailable()) {
116             container.getLogger().info(sm.getString("standardWrapper.isUnavailable",
117                     wrapper.getName()));
118             long available = wrapper.getAvailable();
119             if ((available > 0L) && (available < Long.MAX_VALUE)) {
120                 response.setDateHeader("Retry-After", available);
121                 response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE,
122                         sm.getString("standardWrapper.isUnavailable",
123                                 wrapper.getName()));
124             } else if (available == Long.MAX_VALUE) {
125                 response.sendError(HttpServletResponse.SC_NOT_FOUND,
126                         sm.getString("standardWrapper.notFound",
127                                 wrapper.getName()));
128             }
129             unavailable = true;
130         }
131
132         // Allocate a servlet instance to process this request
133         try {
134             if (!unavailable) {
135                 servlet = wrapper.allocate();
136             }
137         } catch (UnavailableException e) {
138             container.getLogger().error(
139                     sm.getString("standardWrapper.allocateException",
140                             wrapper.getName()), e);
141             long available = wrapper.getAvailable();
142             if ((available > 0L) && (available < Long.MAX_VALUE)) {
143                 response.setDateHeader("Retry-After", available);
144                 response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE,
145                            sm.getString("standardWrapper.isUnavailable",
146                                         wrapper.getName()));
147             } else if (available == Long.MAX_VALUE) {
148                 response.sendError(HttpServletResponse.SC_NOT_FOUND,
149                            sm.getString("standardWrapper.notFound",
150                                         wrapper.getName()));
151             }
152         } catch (ServletException e) {
153             container.getLogger().error(sm.getString("standardWrapper.allocateException",
154                              wrapper.getName()), StandardWrapper.getRootCause(e));
155             throwable = e;
156             exception(request, response, e);
157         } catch (Throwable e) {
158             ExceptionUtils.handleThrowable(e);
159             container.getLogger().error(sm.getString("standardWrapper.allocateException",
160                              wrapper.getName()), e);
161             throwable = e;
162             exception(request, response, e);
163             servlet = null;
164         }
165
166         MessageBytes requestPathMB = request.getRequestPathMB();
167         DispatcherType dispatcherType = DispatcherType.REQUEST;
168         if (request.getDispatcherType()==DispatcherType.ASYNC) dispatcherType = DispatcherType.ASYNC;
169         request.setAttribute(Globals.DISPATCHER_TYPE_ATTR,dispatcherType);
170         request.setAttribute(Globals.DISPATCHER_REQUEST_PATH_ATTR,
171                 requestPathMB);
172         // Create the filter chain for this request
173         ApplicationFilterChain filterChain =
174                 ApplicationFilterFactory.createFilterChain(request, wrapper, servlet);
175
176         // Call the filter chain for this request
177         // NOTE: This also calls the servlet's service() method
178         Container container = this.container;
179         try {
180             if ((servlet != null) && (filterChain != null)) {
181                 // Swallow output if needed
182                 if (context.getSwallowOutput()) {
183                     try {
184                         SystemLogHandler.startCapture();
185                         if (request.isAsyncDispatching()) {
186                             request.getAsyncContextInternal().doInternalDispatch();
187                         } else {
188                             filterChain.doFilter(request.getRequest(),
189                                     response.getResponse());
190                         }
191                     } finally {
192                         String log = SystemLogHandler.stopCapture();
193                         if (log != null && log.length() > 0) {
194                             context.getLogger().info(log);
195                         }
196                     }
197                 } else {
198                     if (request.isAsyncDispatching()) {
199                         request.getAsyncContextInternal().doInternalDispatch();
200                     } else {
201                         filterChain.doFilter
202                             (request.getRequest(), response.getResponse());
203                     }
204                 }
205
206             }
207         } catch (ClientAbortException | CloseNowException e) {
208             if (container.getLogger().isDebugEnabled()) {
209                 container.getLogger().debug(sm.getString(
210                         "standardWrapper.serviceException", wrapper.getName(),
211                         context.getName()), e);
212             }
213             throwable = e;
214             exception(request, response, e);
215         } catch (IOException e) {
216             container.getLogger().error(sm.getString(
217                     "standardWrapper.serviceException", wrapper.getName(),
218                     context.getName()), e);
219             throwable = e;
220             exception(request, response, e);
221         } catch (UnavailableException e) {
222             container.getLogger().error(sm.getString(
223                     "standardWrapper.serviceException", wrapper.getName(),
224                     context.getName()), e);
225             //            throwable = e;
226             //            exception(request, response, e);
227             wrapper.unavailable(e);
228             long available = wrapper.getAvailable();
229             if ((available > 0L) && (available < Long.MAX_VALUE)) {
230                 response.setDateHeader("Retry-After", available);
231                 response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE,
232                            sm.getString("standardWrapper.isUnavailable",
233                                         wrapper.getName()));
234             } else if (available == Long.MAX_VALUE) {
235                 response.sendError(HttpServletResponse.SC_NOT_FOUND,
236                             sm.getString("standardWrapper.notFound",
237                                         wrapper.getName()));
238             }
239             // Do not save exception in 'throwable', because we
240             // do not want to do exception(request, response, e) processing
241         } catch (ServletException e) {
242             Throwable rootCause = StandardWrapper.getRootCause(e);
243             if (!(rootCause instanceof ClientAbortException)) {
244                 container.getLogger().error(sm.getString(
245                         "standardWrapper.serviceExceptionRoot",
246                         wrapper.getName(), context.getName(), e.getMessage()),
247                         rootCause);
248             }
249             throwable = e;
250             exception(request, response, e);
251         } catch (Throwable e) {
252             ExceptionUtils.handleThrowable(e);
253             container.getLogger().error(sm.getString(
254                     "standardWrapper.serviceException", wrapper.getName(),
255                     context.getName()), e);
256             throwable = e;
257             exception(request, response, e);
258         } finally {
259             // Release the filter chain (if any) for this request
260             if (filterChain != null) {
261                 filterChain.release();
262             }
263
264             // Deallocate the allocated servlet instance
265             try {
266                 if (servlet != null) {
267                     wrapper.deallocate(servlet);
268                 }
269             } catch (Throwable e) {
270                 ExceptionUtils.handleThrowable(e);
271                 container.getLogger().error(sm.getString("standardWrapper.deallocateException",
272                                  wrapper.getName()), e);
273                 if (throwable == null) {
274                     throwable = e;
275                     exception(request, response, e);
276                 }
277             }
278
279             // If this servlet has been marked permanently unavailable,
280             // unload it and release this instance
281             try {
282                 if ((servlet != null) &&
283                     (wrapper.getAvailable() == Long.MAX_VALUE)) {
284                     wrapper.unload();
285                 }
286             } catch (Throwable e) {
287                 ExceptionUtils.handleThrowable(e);
288                 container.getLogger().error(sm.getString("standardWrapper.unloadException",
289                                  wrapper.getName()), e);
290                 if (throwable == null) {
291                     exception(request, response, e);
292                 }
293             }
294             long t2=System.currentTimeMillis();
295
296             long time=t2-t1;
297             processingTime += time;
298             if( time > maxTime) maxTime=time;
299             if( time < minTime) minTime=time;
300         }
301     }
302
303
304     // -------------------------------------------------------- Private Methods
305
306     /**
307      * Handle the specified ServletException encountered while processing
308      * the specified Request to produce the specified Response.  Any
309      * exceptions that occur during generation of the exception report are
310      * logged and swallowed.
311      *
312      * @param request The request being processed
313      * @param response The response being generated
314      * @param exception The exception that occurred (which possibly wraps
315      *  a root cause exception
316      */

317     private void exception(Request request, Response response,
318                            Throwable exception) {
319         request.setAttribute(RequestDispatcher.ERROR_EXCEPTION, exception);
320         response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
321         response.setError();
322     }
323
324     public long getProcessingTime() {
325         return processingTime;
326     }
327
328     public long getMaxTime() {
329         return maxTime;
330     }
331
332     public long getMinTime() {
333         return minTime;
334     }
335
336     public int getRequestCount() {
337         return requestCount.get();
338     }
339
340     public int getErrorCount() {
341         return errorCount.get();
342     }
343
344     public void incrementErrorCount() {
345         errorCount.incrementAndGet();
346     }
347
348     @Override
349     protected void initInternal() throws LifecycleException {
350         // NOOP - Don't register this Valve in JMX
351     }
352 }
353