| /* |
| * 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.core; |
| |
| |
| import java.io.IOException; |
| import java.util.concurrent.atomic.LongAdder; |
| |
| import jakarta.servlet.DispatcherType; |
| import jakarta.servlet.RequestDispatcher; |
| import jakarta.servlet.Servlet; |
| import jakarta.servlet.ServletException; |
| import jakarta.servlet.UnavailableException; |
| import jakarta.servlet.http.HttpServletResponse; |
| |
| import org.apache.catalina.Container; |
| import org.apache.catalina.Context; |
| import org.apache.catalina.Globals; |
| import org.apache.catalina.LifecycleException; |
| import org.apache.catalina.connector.Request; |
| import org.apache.catalina.connector.Response; |
| import org.apache.catalina.valves.ValveBase; |
| import org.apache.coyote.BadRequestException; |
| import org.apache.coyote.CloseNowException; |
| import org.apache.tomcat.util.ExceptionUtils; |
| import org.apache.tomcat.util.buf.MessageBytes; |
| import org.apache.tomcat.util.http.InvalidParameterException; |
| import org.apache.tomcat.util.log.SystemLogHandler; |
| import org.apache.tomcat.util.res.StringManager; |
| |
| /** |
| * Valve that implements the default basic behavior for the <code>StandardWrapper</code> container implementation. |
| */ |
| final class StandardWrapperValve extends ValveBase { |
| |
| private static final StringManager sm = StringManager.getManager(StandardWrapperValve.class); |
| |
| |
| // ------------------------------------------------------ Constructor |
| |
| StandardWrapperValve() { |
| super(true); |
| } |
| |
| |
| // ----------------------------------------------------- Instance Variables |
| |
| // Some JMX statistics. This valve is associated with a StandardWrapper. |
| // We expose the StandardWrapper as JMX ( j2eeType=Servlet ). The fields |
| // are here for performance. |
| private final LongAdder processingTime = new LongAdder(); |
| private volatile long maxTime; |
| private volatile long minTime = Long.MAX_VALUE; |
| private final LongAdder requestCount = new LongAdder(); |
| private final LongAdder errorCount = new LongAdder(); |
| |
| |
| // --------------------------------------------------------- Public Methods |
| |
| /** |
| * Invoke the servlet we are managing, respecting the rules regarding servlet lifecycle support. |
| * |
| * @param request Request to be processed |
| * @param response Response to be produced |
| * |
| * @exception IOException if an input/output error occurred |
| * @exception ServletException if a servlet error occurred |
| */ |
| @Override |
| public void invoke(Request request, Response response) throws IOException, ServletException { |
| |
| // Initialize local variables we may need |
| boolean unavailable = false; |
| Throwable throwable = null; |
| // This should be a Request attribute... |
| long t1 = System.currentTimeMillis(); |
| requestCount.increment(); |
| StandardWrapper wrapper = (StandardWrapper) getContainer(); |
| Servlet servlet = null; |
| Context context = (Context) wrapper.getParent(); |
| |
| // Check for the application being marked unavailable |
| if (!context.getState().isAvailable()) { |
| response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE, |
| sm.getString("standardContext.isUnavailable")); |
| unavailable = true; |
| } |
| |
| // Check for the servlet being marked unavailable |
| if (!unavailable && wrapper.isUnavailable()) { |
| container.getLogger().info(sm.getString("standardWrapper.isUnavailable", wrapper.getName())); |
| checkWrapperAvailable(response, wrapper); |
| unavailable = true; |
| } |
| |
| // Allocate a servlet instance to process this request |
| try { |
| if (!unavailable) { |
| servlet = wrapper.allocate(); |
| } |
| } catch (UnavailableException e) { |
| container.getLogger().error(sm.getString("standardWrapper.allocateException", wrapper.getName()), e); |
| checkWrapperAvailable(response, wrapper); |
| } catch (ServletException e) { |
| container.getLogger().error(sm.getString("standardWrapper.allocateException", wrapper.getName()), |
| StandardWrapper.getRootCause(e)); |
| throwable = e; |
| exception(request, response, e); |
| } catch (Throwable t) { |
| ExceptionUtils.handleThrowable(t); |
| container.getLogger().error(sm.getString("standardWrapper.allocateException", wrapper.getName()), t); |
| throwable = t; |
| exception(request, response, t); |
| // servlet = null; is set here |
| } |
| |
| MessageBytes requestPathMB = request.getRequestPathMB(); |
| DispatcherType dispatcherType = DispatcherType.REQUEST; |
| if (request.getDispatcherType() == DispatcherType.ASYNC) { |
| dispatcherType = DispatcherType.ASYNC; |
| } |
| request.setAttribute(Globals.DISPATCHER_TYPE_ATTR, dispatcherType); |
| request.setAttribute(Globals.DISPATCHER_REQUEST_PATH_ATTR, requestPathMB); |
| // Create the filter chain for this request |
| ApplicationFilterChain filterChain = ApplicationFilterFactory.createFilterChain(request, wrapper, servlet); |
| |
| // Call the filter chain for this request |
| // NOTE: This also calls the servlet's service() method |
| Container container = this.container; |
| try { |
| if ((servlet != null) && (filterChain != null)) { |
| // Swallow output if needed |
| if (context.getSwallowOutput()) { |
| try { |
| SystemLogHandler.startCapture(); |
| if (request.isAsyncDispatching()) { |
| request.getAsyncContextInternal().doInternalDispatch(); |
| } else { |
| filterChain.doFilter(request.getRequest(), response.getResponse()); |
| } |
| } finally { |
| String log = SystemLogHandler.stopCapture(); |
| if (log != null && !log.isEmpty()) { |
| context.getLogger().info(log); |
| } |
| } |
| } else { |
| if (request.isAsyncDispatching()) { |
| request.getAsyncContextInternal().doInternalDispatch(); |
| } else { |
| filterChain.doFilter(request.getRequest(), response.getResponse()); |
| } |
| } |
| |
| } |
| } catch (BadRequestException e) { |
| if (container.getLogger().isDebugEnabled()) { |
| container.getLogger().debug( |
| sm.getString("standardWrapper.serviceException", wrapper.getName(), context.getName()), e); |
| } |
| throwable = e; |
| exception(request, response, e, HttpServletResponse.SC_BAD_REQUEST); |
| } catch (CloseNowException e) { |
| if (container.getLogger().isDebugEnabled()) { |
| container.getLogger().debug( |
| sm.getString("standardWrapper.serviceException", wrapper.getName(), context.getName()), e); |
| } |
| throwable = e; |
| exception(request, response, e); |
| } catch (IOException ioe) { |
| container.getLogger() |
| .error(sm.getString("standardWrapper.serviceException", wrapper.getName(), context.getName()), ioe); |
| throwable = ioe; |
| exception(request, response, ioe); |
| } catch (UnavailableException e) { |
| container.getLogger() |
| .error(sm.getString("standardWrapper.serviceException", wrapper.getName(), context.getName()), e); |
| wrapper.unavailable(e); |
| checkWrapperAvailable(response, wrapper); |
| // Do not save exception in 'throwable', because we |
| // do not want to do exception(request, response, e) processing |
| } catch (ServletException e) { |
| Throwable rootCause = StandardWrapper.getRootCause(e); |
| if (!(rootCause instanceof BadRequestException)) { |
| container.getLogger().error(sm.getString("standardWrapper.serviceExceptionRoot", wrapper.getName(), |
| context.getName(), e.getMessage()), rootCause); |
| } |
| throwable = e; |
| exception(request, response, e); |
| } catch (InvalidParameterException e) { |
| if (container.getLogger().isDebugEnabled()) { |
| container.getLogger().debug( |
| sm.getString("standardWrapper.serviceException", wrapper.getName(), context.getName()), e); |
| } |
| throwable = e; |
| exception(request, response, e, e.getErrorCode()); |
| } catch (Throwable t) { |
| ExceptionUtils.handleThrowable(t); |
| container.getLogger() |
| .error(sm.getString("standardWrapper.serviceException", wrapper.getName(), context.getName()), t); |
| throwable = t; |
| exception(request, response, t); |
| } finally { |
| // Release the filter chain (if any) for this request |
| if (filterChain != null) { |
| filterChain.release(); |
| } |
| |
| // Deallocate the allocated servlet instance |
| try { |
| if (servlet != null) { |
| wrapper.deallocate(servlet); |
| } |
| } catch (Throwable t) { |
| ExceptionUtils.handleThrowable(t); |
| container.getLogger().error(sm.getString("standardWrapper.deallocateException", wrapper.getName()), t); |
| if (throwable == null) { |
| throwable = t; |
| exception(request, response, t); |
| } |
| } |
| |
| // If this servlet has been marked permanently unavailable, |
| // unload it and release this instance |
| try { |
| if ((servlet != null) && (wrapper.getAvailable() == Long.MAX_VALUE)) { |
| wrapper.unload(); |
| } |
| } catch (Throwable t) { |
| ExceptionUtils.handleThrowable(t); |
| container.getLogger().error(sm.getString("standardWrapper.unloadException", wrapper.getName()), t); |
| if (throwable == null) { |
| exception(request, response, t); |
| } |
| } |
| long t2 = System.currentTimeMillis(); |
| |
| long time = t2 - t1; |
| processingTime.add(time); |
| if (time > maxTime) { |
| maxTime = time; |
| } |
| if (time < minTime) { |
| minTime = time; |
| } |
| } |
| } |
| |
| private void checkWrapperAvailable(Response response, StandardWrapper wrapper) throws IOException { |
| long available = wrapper.getAvailable(); |
| if ((available > 0L) && (available < Long.MAX_VALUE)) { |
| response.setDateHeader("Retry-After", available); |
| response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE, |
| sm.getString("standardWrapper.isUnavailable", wrapper.getName())); |
| } else if (available == Long.MAX_VALUE) { |
| response.sendError(HttpServletResponse.SC_NOT_FOUND, |
| sm.getString("standardWrapper.notFound", wrapper.getName())); |
| } |
| } |
| |
| |
| // -------------------------------------------------------- Private Methods |
| |
| /** |
| * Handle the specified ServletException encountered while processing the specified Request to produce the specified |
| * Response. Any exceptions that occur during generation of the exception report are logged and swallowed. |
| * |
| * @param request The request being processed |
| * @param response The response being generated |
| * @param exception The exception that occurred (which possibly wraps a root cause exception |
| */ |
| private void exception(Request request, Response response, Throwable exception) { |
| if (exception.getCause() instanceof InvalidParameterException) { |
| exception(request, response, exception, ((InvalidParameterException) exception.getCause()).getErrorCode()); |
| } else { |
| exception(request, response, exception, HttpServletResponse.SC_INTERNAL_SERVER_ERROR); |
| } |
| } |
| |
| private void exception(Request request, Response response, Throwable exception, int errorCode) { |
| request.setAttribute(RequestDispatcher.ERROR_EXCEPTION, exception); |
| response.setStatus(errorCode); |
| response.setError(); |
| } |
| |
| public long getProcessingTime() { |
| return processingTime.sum(); |
| } |
| |
| public long getMaxTime() { |
| return maxTime; |
| } |
| |
| public long getMinTime() { |
| return minTime; |
| } |
| |
| /** |
| * Returns the number of requests processed by the associated wrapper. |
| * |
| * @return the number of requests processed by the associated wrapper. |
| */ |
| public long getRequestCount() { |
| return requestCount.sum(); |
| } |
| |
| /** |
| * Returns the number of requests processed by the associated wrapper that resulted in an error. |
| * |
| * @return the number of requests processed by the associated wrapper that resulted in an error. |
| */ |
| public long getErrorCount() { |
| return errorCount.sum(); |
| } |
| |
| public void incrementErrorCount() { |
| errorCount.increment(); |
| } |
| |
| @Override |
| protected void initInternal() throws LifecycleException { |
| // NOOP - Don't register this Valve in JMX |
| } |
| } |