From b5086c43c60b7b97a6eb8ef1390e6fe3ad8a7830 Mon Sep 17 00:00:00 2001 From: remm Date: Wed, 13 Sep 2006 00:55:15 +0000 Subject: [PATCH] - Redo Comet processing using events. - Add Valve.event (most regular valves do not do anything, but standard valves need to call the next pipeline). - Add a new Filter interface that filters can implement to intercept the events (I didn't test it yet). git-svn-id: https://svn.apache.org/repos/asf/tomcat/tc6.0.x/trunk@442778 13f79535-47bb-0310-9956-ffa450edef68 --- java/org/apache/catalina/CometEvent.java | 7 +- java/org/apache/catalina/CometFilter.java | 36 ++++ java/org/apache/catalina/CometFilterChain.java | 35 ++++ java/org/apache/catalina/CometProcessor.java | 100 +---------- java/org/apache/catalina/Valve.java | 15 ++ .../apache/catalina/connector/CometEventImpl.java | 112 ++++++++++++ .../apache/catalina/connector/CoyoteAdapter.java | 58 +++--- java/org/apache/catalina/connector/Request.java | 47 ++++- java/org/apache/catalina/connector/Response.java | 8 +- .../catalina/core/ApplicationFilterChain.java | 189 +++++++++++++++++++- .../catalina/core/ApplicationFilterFactory.java | 40 ++++- java/org/apache/catalina/core/StandardContext.java | 6 +- .../apache/catalina/core/StandardContextValve.java | 85 ++++++++- .../apache/catalina/core/StandardEngineValve.java | 20 +++ .../apache/catalina/core/StandardHostValve.java | 52 ++++++ .../apache/catalina/core/StandardWrapperValve.java | 197 ++++++++++++++++++++- .../org/apache/catalina/servlets/CometServlet.java | 101 ----------- java/org/apache/catalina/valves/ValveBase.java | 21 +++ 18 files changed, 859 insertions(+), 270 deletions(-) create mode 100644 java/org/apache/catalina/CometFilter.java create mode 100644 java/org/apache/catalina/CometFilterChain.java create mode 100644 java/org/apache/catalina/connector/CometEventImpl.java delete mode 100644 java/org/apache/catalina/servlets/CometServlet.java diff --git a/java/org/apache/catalina/CometEvent.java b/java/org/apache/catalina/CometEvent.java index 643f764a1..1e022cdb0 100644 --- a/java/org/apache/catalina/CometEvent.java +++ b/java/org/apache/catalina/CometEvent.java @@ -41,7 +41,8 @@ public interface CometEvent { * it is possible to use the response object to write data on the open connection. * Note that the response object and depedent OutputStream and Writer are still * not synchronized, so when they are accessed by multiple threads, - * synchronization is mandatory. + * synchronization is mandatory. After processing the initial event, the request + * is considered to be committed. * READ - This indicates that input data is available, and that one read can be made * without blocking. The available and ready methods of the InputStream or * Reader may be used to determine if there is a risk of blocking: the servlet @@ -61,7 +62,7 @@ public interface CometEvent { * been processed, the request and response objects, as well as all their dependent * objects will be recycled and used to process other requests. */ - enum EventType {BEGIN, READ, END, ERROR} + public enum EventType {BEGIN, READ, END, ERROR} /** @@ -73,7 +74,7 @@ public interface CometEvent { * SERVER_SHUTDOWN - the server is shutting down (sub type of END) * SESSION_END - the servlet ended the session (sub type of END) */ - enum EventSubType { TIMEOUT, CLIENT_DISCONNECT, IOEXCEPTION, WEBAPP_RELOAD, SERVER_SHUTDOWN, SESSION_END } + public enum EventSubType { TIMEOUT, CLIENT_DISCONNECT, IOEXCEPTION, WEBAPP_RELOAD, SERVER_SHUTDOWN, SESSION_END } /** diff --git a/java/org/apache/catalina/CometFilter.java b/java/org/apache/catalina/CometFilter.java new file mode 100644 index 000000000..62458a237 --- /dev/null +++ b/java/org/apache/catalina/CometFilter.java @@ -0,0 +1,36 @@ +/* + * Copyright 2006 The Apache Software Foundation. + * + * Licensed 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; + +import java.io.IOException; + +import javax.servlet.FilterChain; +import javax.servlet.ServletException; + +/** + * The CometFilter interface. + * + * @author Filip Hanik + */ +public interface CometFilter { + + + public void doFilterEvent(CometEvent event, CometFilterChain chain) throws IOException, ServletException; + + +} diff --git a/java/org/apache/catalina/CometFilterChain.java b/java/org/apache/catalina/CometFilterChain.java new file mode 100644 index 000000000..5e365538d --- /dev/null +++ b/java/org/apache/catalina/CometFilterChain.java @@ -0,0 +1,35 @@ +/* + * Copyright 2006 The Apache Software Foundation. + * + * Licensed 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; + +import java.io.IOException; + +import javax.servlet.ServletException; + +/** + * The CometFilter interface. + * + * @author Filip Hanik + */ +public interface CometFilterChain { + + + public void doFilterEvent(CometEvent event) throws IOException, ServletException; + + +} diff --git a/java/org/apache/catalina/CometProcessor.java b/java/org/apache/catalina/CometProcessor.java index b329fdf95..3979c0835 100644 --- a/java/org/apache/catalina/CometProcessor.java +++ b/java/org/apache/catalina/CometProcessor.java @@ -20,8 +20,6 @@ package org.apache.catalina; import java.io.IOException; import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; /** * This interface should be implemented by servlets which would like to handle @@ -31,101 +29,13 @@ import javax.servlet.http.HttpServletResponse; public interface CometProcessor { /** - * Begin will be called by the main service method of the servlet at the beginning - * of the processing of the connection. It can be used to initialize any relevant - * fields using the request and response objects. Between the end of the execution - * of this method, and the beginning of the execution of the end or error methods, - * it is possible to use the response object to write data on the open connection. - * Note that the response object and depedent OutputStream and Writer are still - * not synchronized, so when they are accessed by multiple threads, - * synchronization is mandatory. + * Process the given Comet event. * - * @param request The HTTP servlet request instance, which can be accessed - * asynchronously at any time until the end or error methods are called - * @param response The HTTP servlet response instance, which can be accessed - * asynchronously at any time until the end or error methods are called - * @throws IOException An IOException may be thrown to indicate an IO error - * @throws ServletException An exception has occurred, as specified by the root - * cause + * @param event The Comet event that will be processed + * @throws IOException + * @throws ServletException */ - public void begin(HttpServletRequest request, HttpServletResponse response) + public void event(CometEvent event) throws IOException, ServletException; - /** - * End may be called to end the processing of the request. Fields that have - * been initialized in the begin method should be reset. After this method has - * been called, the request and response objects, as well as all their dependent - * objects will be recycled and used to process other requests. - * - * @param request The HTTP servlet request instance - * @param response The HTTP servlet response instance - * @throws IOException An IOException may be thrown to indicate an IO error - * @throws ServletException An exception has occurred, as specified by the root - * cause - */ - public void end(HttpServletRequest request, HttpServletResponse response) - throws IOException, ServletException; - - /** - * Error will be called by the container in the case where an IO exception - * or a similar unrecoverable error occurs on the connection. Fields that have - * been initialized in the begin method should be reset. After this method has - * been called, the request and response objects, as well as all their dependent - * objects will be recycled and used to process other requests. - * - * @param request The HTTP servlet request instance - * @param response The HTTP servlet response instance - * @throws IOException An IOException may be thrown to indicate an IO error - * @throws ServletException An exception has occurred, as specified by the root - * cause - */ - public void error(HttpServletRequest request, HttpServletResponse response) - throws IOException, ServletException; - - /** - * This indicates that input data is available, and that one read can be made - * without blocking. The available and ready methods of the InputStream or - * Reader may be used to determine if there is a risk of blocking: the servlet - * should read while data is reported available, and can make one additional read - * without blocking. When encountering a read error or an EOF, the servlet MUST - * report it by either returning false or throwing an exception such as an - * IOException. This will cause the error method to be invoked, and the connection - * will be closed. It is not allowed to attempt reading data from the request object - * outside of the execution of this method. - * - * @param request The HTTP servlet request instance - * @param response The HTTP servlet response instance - * @throws IOException An IOException may be thrown to indicate an IO error, - * or that the EOF has been reached on the connection - * @throws ServletException An exception has occurred, as specified by the root - * cause - * @return false if the read attempt returned an EOF; alternately, it is also - * valid to throw an IOException - */ - public boolean read(HttpServletRequest request, HttpServletResponse response) - throws IOException, ServletException; - - /** - * Sets the timeout for this Comet connection. Please NOTE, that the implementation - * of a per connection timeout is OPTIONAL and MAY NOT be implemented.
- * This method sets the timeout in milliseconds of idle time on the connection. - * The timeout is reset every time data is received from the connection or data is flushed - * using response.flushBuffer(). If a timeout occurs, the - * error(HttpServletRequest, HttpServletResponse) method is invoked. The - * web application SHOULD NOT attempt to reuse the request and response objects after a timeout - * as the error(HttpServletRequest, HttpServletResponse) method indicates.
- * This method should not be called asynchronously, as that will have no effect. - * @param request The HTTP servlet request instance - * @param response The HTTP servlet response instance - * @param timeout The timeout in milliseconds for this connection, must be a positive value, larger than 0 - * @throws IOException An IOException may be thrown to indicate an IO error, - * or that the EOF has been reached on the connection - * @throws ServletException An exception has occurred, as specified by the root - * cause - * @throws UnsupportedOperationException if per connection timeout is not supported, either at all or at this phase - * of the invocation. - */ - public void setTimeout(HttpServletRequest request, HttpServletResponse response, int timeout) - throws IOException, ServletException, UnsupportedOperationException; - } diff --git a/java/org/apache/catalina/Valve.java b/java/org/apache/catalina/Valve.java index b988ac4d0..bb0f25f23 100644 --- a/java/org/apache/catalina/Valve.java +++ b/java/org/apache/catalina/Valve.java @@ -128,5 +128,20 @@ public interface Valve { public void invoke(Request request, Response response) throws IOException, ServletException; + + /** + * Process a Comet event. + * + * @param request The servlet request to be processed + * @param response The servlet response to be created + * + * @exception IOException if an input/output error occurs, or is thrown + * by a subsequently invoked Valve, Filter, or Servlet + * @exception ServletException if a servlet error occurs, or is thrown + * by a subsequently invoked Valve, Filter, or Servlet + */ + public void event(Request request, Response response, CometEvent event) + throws IOException, ServletException; + } diff --git a/java/org/apache/catalina/connector/CometEventImpl.java b/java/org/apache/catalina/connector/CometEventImpl.java new file mode 100644 index 000000000..6673e88e5 --- /dev/null +++ b/java/org/apache/catalina/connector/CometEventImpl.java @@ -0,0 +1,112 @@ +/* + * Copyright 2006 The Apache Software Foundation. + * + * Licensed 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.connector; + +import java.io.IOException; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.catalina.CometEvent; + +public class CometEventImpl implements CometEvent { + + + public CometEventImpl(Request request, Response response) { + this.request = request; + this.response = response; + } + + + // ----------------------------------------------------- Instance Variables + + + /** + * Associated request. + */ + protected Request request = null; + + + /** + * Associated response. + */ + protected Response response = null; + + + /** + * Event type. + */ + protected EventType eventType = EventType.BEGIN; + + + /** + * Event sub type. + */ + protected EventSubType eventSubType = null; + + + // --------------------------------------------------------- Public Methods + + /** + * Clear the event. + */ + public void clear() { + request = null; + response = null; + } + + public void setEventType(EventType eventType) { + this.eventType = eventType; + } + + public void serEventSubType(EventSubType eventSubType) { + this.eventSubType = eventSubType; + } + + public void close() throws IOException { + request.setComet(false); + response.finishResponse(); + } + + public EventSubType getEventSubType() { + return eventSubType; + } + + public EventType getEventType() { + return eventType; + } + + public HttpServletRequest getHttpServletRequest() { + return request.getRequest(); + } + + public HttpServletResponse getHttpServletResponse() { + return response.getResponse(); + } + + public void setTimeout(int timeout) throws IOException, ServletException, + UnsupportedOperationException { + if (request.getAttribute("org.apache.tomcat.comet.timeout.support") == Boolean.TRUE) { + request.setAttribute("org.apache.tomcat.comet.timeout", new Integer(timeout)); + } else { + throw new UnsupportedOperationException(); + } + } + +} diff --git a/java/org/apache/catalina/connector/CoyoteAdapter.java b/java/org/apache/catalina/connector/CoyoteAdapter.java index 70e202b1f..7e12b47d1 100644 --- a/java/org/apache/catalina/connector/CoyoteAdapter.java +++ b/java/org/apache/catalina/connector/CoyoteAdapter.java @@ -21,6 +21,7 @@ import java.io.IOException; import javax.servlet.Servlet; +import org.apache.catalina.CometEvent; import org.apache.catalina.CometProcessor; import org.apache.catalina.Context; import org.apache.catalina.Globals; @@ -119,29 +120,16 @@ public class CoyoteAdapter if (request.getWrapper() != null) { - CometProcessor servlet = null; - - // Bind the context CL to the current thread - if (request.getContext().getLoader() != null ) { - Thread.currentThread().setContextClassLoader - (request.getContext().getLoader().getClassLoader()); - } - try { - servlet = (CometProcessor) request.getWrapper().allocate(); if (error) { - servlet.error(request.getRequest(), response.getResponse()); + request.getEvent().setEventType(CometEvent.EventType.ERROR); } else { - if (!servlet.read(request.getRequest(), response.getResponse())) { - error = true; - try { - servlet.error(request.getRequest(), response.getResponse()); - } catch (Throwable th) { - log.error(sm.getString("coyoteAdapter.service"), th); - } - } + request.getEvent().setEventType(CometEvent.EventType.READ); } - if (response.isClosed()) { + // Calling the container + connector.getContainer().getPipeline().getFirst().event(request, response, request.getEvent()); + + if (response.isClosed() || !request.isComet()) { res.action(ActionCode.ACTION_COMET_END, null); } return (!error); @@ -150,24 +138,14 @@ public class CoyoteAdapter log.error(sm.getString("coyoteAdapter.service"), t); } error = true; - try { - servlet.error(request.getRequest(), response.getResponse()); - } catch (Throwable th) { - log.error(sm.getString("coyoteAdapter.service"), th); - } + // FIXME: Since there's likely some structures kept in the servlet or elsewhere, + // a cleanup event of some sort could be needed ? return false; } finally { - // Restore the context classloader - Thread.currentThread().setContextClassLoader - (CoyoteAdapter.class.getClassLoader()); - try { - request.getWrapper().deallocate((Servlet) servlet); - } catch (Exception e) { - log.error(sm.getString("coyoteAdapter.service"), e); - } // Recycle the wrapper request and response - if (error || response.isClosed()) { + if (error || response.isClosed() || !request.isComet()) { request.recycle(); + request.setFilterChain(null); response.recycle(); } } @@ -223,11 +201,15 @@ public class CoyoteAdapter // Calling the container connector.getContainer().getPipeline().getFirst().invoke(request, response); - if (request.getWrapper().getServlet() instanceof CometProcessor - && !response.isClosed() - && req.getAttribute("org.apache.tomcat.comet.support") == Boolean.TRUE) { - comet = true; - res.action(ActionCode.ACTION_COMET_BEGIN, null); + if (request.isComet()) { + if (!response.isClosed()) { + comet = true; + res.action(ActionCode.ACTION_COMET_BEGIN, null); + } else { + // Clear the filter chain, as otherwise it will not be reset elsewhere + // since this is a Comet request + request.setFilterChain(null); + } } } diff --git a/java/org/apache/catalina/connector/Request.java b/java/org/apache/catalina/connector/Request.java index 184179031..a7349c8a2 100644 --- a/java/org/apache/catalina/connector/Request.java +++ b/java/org/apache/catalina/connector/Request.java @@ -195,8 +195,20 @@ public class Request */ protected String authType = null; + + /** + * Associated event. + */ + protected CometEventImpl event = null; + /** + * Comet state + */ + protected boolean comet = false; + + + /** * The current dispatcher type. */ protected Object dispatcherType = null; @@ -373,6 +385,12 @@ public class Request dispatcherType = null; requestDispatcherPath = null; + comet = false; + if (event != null) { + event.clear(); + event = null; + } + authType = null; inputBuffer.recycle(); usingInputStream = false; @@ -2193,6 +2211,34 @@ public class Request } + /** + * Get the event associated with the request. + * @return + */ + public CometEventImpl getEvent() { + if (event == null) { + event = new CometEventImpl(this, response); + } + return event; + } + + + /** + * Return true if the current request is handling Comet traffic. + */ + public boolean isComet() { + return comet; + } + + + /** + * Set comet state. + */ + public void setComet(boolean comet) { + this.comet = comet; + } + + // ------------------------------------------------------ Protected Methods @@ -2546,5 +2592,4 @@ public class Request } - } diff --git a/java/org/apache/catalina/connector/Response.java b/java/org/apache/catalina/connector/Response.java index 9b9c836dc..e643b1d92 100644 --- a/java/org/apache/catalina/connector/Response.java +++ b/java/org/apache/catalina/connector/Response.java @@ -481,13 +481,7 @@ public class Response public void finishResponse() throws IOException { // Writing leftover bytes - try { - outputBuffer.close(); - } catch(IOException e) { - ; - } catch(Throwable t) { - t.printStackTrace(); - } + outputBuffer.close(); } diff --git a/java/org/apache/catalina/core/ApplicationFilterChain.java b/java/org/apache/catalina/core/ApplicationFilterChain.java index 6297dfb4c..33e9a89a6 100644 --- a/java/org/apache/catalina/core/ApplicationFilterChain.java +++ b/java/org/apache/catalina/core/ApplicationFilterChain.java @@ -31,6 +31,10 @@ import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import org.apache.catalina.CometEvent; +import org.apache.catalina.CometFilter; +import org.apache.catalina.CometFilterChain; +import org.apache.catalina.CometProcessor; import org.apache.catalina.InstanceEvent; import org.apache.catalina.security.SecurityUtil; import org.apache.catalina.util.InstanceSupport; @@ -47,7 +51,7 @@ import org.apache.catalina.util.StringManager; * @version $Revision: 303523 $ $Date: 2004-11-22 17:35:18 +0100 (lun., 22 nov. 2004) $ */ -final class ApplicationFilterChain implements FilterChain { +final class ApplicationFilterChain implements FilterChain, CometFilterChain { // -------------------------------------------------------------- Constants @@ -114,7 +118,7 @@ final class ApplicationFilterChain implements FilterChain { /** * Static class array used when the SecurityManager is turned on and - * doFilterdoFilter is invoked. */ private static Class[] classType = new Class[]{ServletRequest.class, ServletResponse.class, @@ -122,12 +126,26 @@ final class ApplicationFilterChain implements FilterChain { /** * Static class array used when the SecurityManager is turned on and - * serviceservice is invoked. */ private static Class[] classTypeUsedInService = new Class[]{ ServletRequest.class, ServletResponse.class}; + /** + * Static class array used when the SecurityManager is turned on and + * doFilterEvent is invoked. + */ + private static Class[] cometClassType = + new Class[]{ CometEvent.class, CometFilterChain.class}; + + /** + * Static class array used when the SecurityManager is turned on and + * event is invoked. + */ + private static Class[] classTypeUsedInEvent = + new Class[] { CometEvent.class }; + // ---------------------------------------------------- FilterChain Methods @@ -279,6 +297,163 @@ final class ApplicationFilterChain implements FilterChain { } + /** + * Invoke the next filter in this chain, passing the specified request + * and response. If there are no more filters in this chain, invoke + * the service() method of the servlet itself. + * + * @param request The servlet request we are processing + * @param response The servlet response we are creating + * + * @exception IOException if an input/output error occurs + * @exception ServletException if a servlet exception occurs + */ + public void doFilterEvent(CometEvent event) + throws IOException, ServletException { + + if( System.getSecurityManager() != null ) { + final CometEvent ev = event; + try { + java.security.AccessController.doPrivileged( + new java.security.PrivilegedExceptionAction() { + public Object run() + throws ServletException, IOException { + internalDoFilterEvent(ev); + return null; + } + } + ); + } catch( PrivilegedActionException pe) { + Exception e = pe.getException(); + if (e instanceof ServletException) + throw (ServletException) e; + else if (e instanceof IOException) + throw (IOException) e; + else if (e instanceof RuntimeException) + throw (RuntimeException) e; + else + throw new ServletException(e.getMessage(), e); + } + } else { + internalDoFilterEvent(event); + } + } + + private void internalDoFilterEvent(CometEvent event) + throws IOException, ServletException { + + // Call the next filter if there is one + if (pos < n) { + ApplicationFilterConfig filterConfig = filters[pos++]; + CometFilter filter = null; + try { + filter = (CometFilter) filterConfig.getFilter(); + // FIXME: No instance listener processing for events for now + /* + support.fireInstanceEvent(InstanceEvent.BEFORE_FILTER_EVENT, + filter, event); + */ + + if( System.getSecurityManager() != null ) { + final CometEvent ev = event; + Principal principal = + ev.getHttpServletRequest().getUserPrincipal(); + + Object[] args = new Object[]{ev, this}; + SecurityUtil.doAsPrivilege + ("doFilterEvent", (Filter) filter, cometClassType, args); + + args = null; + } else { + filter.doFilterEvent(event, this); + } + + /*support.fireInstanceEvent(InstanceEvent.AFTER_FILTER_EVENT, + filter, event);*/ + } catch (IOException e) { + /* + if (filter != null) + support.fireInstanceEvent(InstanceEvent.AFTER_FILTER_EVENT, + filter, event, e); + */ + throw e; + } catch (ServletException e) { + /* + if (filter != null) + support.fireInstanceEvent(InstanceEvent.AFTER_FILTER_EVENT, + filter, event, e); + */ + throw e; + } catch (RuntimeException e) { + /* + if (filter != null) + support.fireInstanceEvent(InstanceEvent.AFTER_FILTER_EVENT, + filter, event, e); + */ + throw e; + } catch (Throwable e) { + /*if (filter != null) + support.fireInstanceEvent(InstanceEvent.AFTER_FILTER_EVENT, + filter, event, e);*/ + throw new ServletException + (sm.getString("filterChain.filter"), e); + } + return; + } + + // We fell off the end of the chain -- call the servlet instance + try { + /* + support.fireInstanceEvent(InstanceEvent.BEFORE_SERVICE_EVENT, + servlet, request, response); + */ + if( System.getSecurityManager() != null ) { + final CometEvent ev = event; + Principal principal = + ev.getHttpServletRequest().getUserPrincipal(); + Object[] args = new Object[]{ ev }; + SecurityUtil.doAsPrivilege("event", + servlet, + classTypeUsedInEvent, + args, + principal); + args = null; + } else { + ((CometProcessor) servlet).event(event); + } + /* + support.fireInstanceEvent(InstanceEvent.AFTER_SERVICE_EVENT, + servlet, request, response);*/ + } catch (IOException e) { + /* + support.fireInstanceEvent(InstanceEvent.AFTER_SERVICE_EVENT, + servlet, request, response, e); + */ + throw e; + } catch (ServletException e) { + /* + support.fireInstanceEvent(InstanceEvent.AFTER_SERVICE_EVENT, + servlet, request, response, e); + */ + throw e; + } catch (RuntimeException e) { + /* + support.fireInstanceEvent(InstanceEvent.AFTER_SERVICE_EVENT, + servlet, request, response, e); + */ + throw e; + } catch (Throwable e) { + /* + support.fireInstanceEvent(InstanceEvent.AFTER_SERVICE_EVENT, + servlet, request, response, e); + */ + throw new ServletException + (sm.getString("filterChain.servlet"), e); + } + + } + + // -------------------------------------------------------- Package Methods @@ -315,6 +490,14 @@ final class ApplicationFilterChain implements FilterChain { /** + * Prepare for reuse of the filters and wrapper executed by this chain. + */ + void reuse() { + pos = 0; + } + + + /** * Set the servlet that will be executed at the end of this chain. * * @param servlet The Wrapper for the servlet to be executed diff --git a/java/org/apache/catalina/core/ApplicationFilterFactory.java b/java/org/apache/catalina/core/ApplicationFilterFactory.java index 630f0e136..89b588dfa 100644 --- a/java/org/apache/catalina/core/ApplicationFilterFactory.java +++ b/java/org/apache/catalina/core/ApplicationFilterFactory.java @@ -22,6 +22,7 @@ import javax.servlet.Servlet; import javax.servlet.ServletRequest; import javax.servlet.http.HttpServletRequest; +import org.apache.catalina.CometFilter; import org.apache.catalina.Globals; import org.apache.catalina.Wrapper; import org.apache.catalina.connector.Request; @@ -118,6 +119,8 @@ public final class ApplicationFilterFactory { if (servlet == null) return (null); + boolean comet = false; + // Create and initialize a filter chain object ApplicationFilterChain filterChain = null; if ((securityManager == null) && (request instanceof Request)) { @@ -127,6 +130,7 @@ public final class ApplicationFilterFactory { filterChain = new ApplicationFilterChain(); req.setFilterChain(filterChain); } + comet = req.isComet(); } else { // Security: Do not recycle filterChain = new ApplicationFilterChain(); @@ -148,8 +152,6 @@ public final class ApplicationFilterFactory { // Acquire the information we will need to match filter mappings String servletName = wrapper.getName(); - int n = 0; - // Add the relevant path-mapped filters to this filter chain for (int i = 0; i < filterMaps.length; i++) { if (!matchDispatcher(filterMaps[i] ,dispatcher)) { @@ -163,8 +165,21 @@ public final class ApplicationFilterFactory { ; // FIXME - log configuration problem continue; } - filterChain.addFilter(filterConfig); - n++; + boolean isCometFilter = false; + if (comet) { + try { + isCometFilter = filterConfig.getFilter() instanceof CometFilter; + } catch (Exception e) { + // Note: The try catch is there because getFilter has a lot of + // declared exceptions. However, the filter is allocated much + // earlier + } + if (isCometFilter) { + filterChain.addFilter(filterConfig); + } + } else { + filterChain.addFilter(filterConfig); + } } // Add filters that match on servlet name second @@ -180,8 +195,21 @@ public final class ApplicationFilterFactory { ; // FIXME - log configuration problem continue; } - filterChain.addFilter(filterConfig); - n++; + boolean isCometFilter = false; + if (comet) { + try { + isCometFilter = filterConfig.getFilter() instanceof CometFilter; + } catch (Exception e) { + // Note: The try catch is there because getFilter has a lot of + // declared exceptions. However, the filter is allocated much + // earlier + } + if (isCometFilter) { + filterChain.addFilter(filterConfig); + } + } else { + filterChain.addFilter(filterConfig); + } } // Return the completed filter chain diff --git a/java/org/apache/catalina/core/StandardContext.java b/java/org/apache/catalina/core/StandardContext.java index be61c347a..9c6277ded 100644 --- a/java/org/apache/catalina/core/StandardContext.java +++ b/java/org/apache/catalina/core/StandardContext.java @@ -4473,9 +4473,6 @@ public class StandardContext try { - // Stop our filters - filterStop(); - // Stop our child containers, if any Container[] children = findChildren(); for (int i = 0; i < children.length; i++) { @@ -4483,6 +4480,9 @@ public class StandardContext ((Lifecycle) children[i]).stop(); } + // Stop our filters + filterStop(); + // Stop our application listeners listenerStop(); diff --git a/java/org/apache/catalina/core/StandardContextValve.java b/java/org/apache/catalina/core/StandardContextValve.java index a86ef492e..f8023c096 100644 --- a/java/org/apache/catalina/core/StandardContextValve.java +++ b/java/org/apache/catalina/core/StandardContextValve.java @@ -26,6 +26,7 @@ import javax.servlet.ServletRequestEvent; import javax.servlet.ServletRequestListener; import javax.servlet.http.HttpServletResponse; +import org.apache.catalina.CometEvent; import org.apache.catalina.Container; import org.apache.catalina.Globals; import org.apache.catalina.Wrapper; @@ -33,8 +34,6 @@ import org.apache.catalina.connector.Request; import org.apache.catalina.connector.Response; import org.apache.catalina.util.StringManager; import org.apache.catalina.valves.ValveBase; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; import org.apache.tomcat.util.buf.MessageBytes; /** @@ -69,9 +68,6 @@ final class StandardContextValve StringManager.getManager(Constants.Package); - private static Log log = LogFactory.getLog(StandardContextValve.class); - - private StandardContext context = null; @@ -201,6 +197,85 @@ final class StandardContextValve } + /** + * Select the appropriate child Wrapper to process this request, + * based on the specified request URI. If no matching Wrapper can + * be found, return an appropriate HTTP error. + * + * @param request Request to be processed + * @param response Response to be produced + * @param valveContext Valve context used to forward to the next Valve + * + * @exception IOException if an input/output error occurred + * @exception ServletException if a servlet error occurred + */ + public final void event(Request request, Response response, CometEvent event) + throws IOException, ServletException { + + // Select the Wrapper to be used for this Request + Wrapper wrapper = request.getWrapper(); + + // Normal request processing + // FIXME: This could be an addition to the core API too + /* + Object instances[] = context.getApplicationEventListeners(); + + ServletRequestEvent event = null; + + if ((instances != null) + && (instances.length > 0)) { + event = new ServletRequestEvent + (((StandardContext) container).getServletContext(), + request.getRequest()); + // create pre-service event + for (int i = 0; i < instances.length; i++) { + if (instances[i] == null) + continue; + if (!(instances[i] instanceof ServletRequestListener)) + continue; + ServletRequestListener listener = + (ServletRequestListener) instances[i]; + try { + listener.requestInitialized(event); + } catch (Throwable t) { + container.getLogger().error(sm.getString("requestListenerValve.requestInit", + instances[i].getClass().getName()), t); + ServletRequest sreq = request.getRequest(); + sreq.setAttribute(Globals.EXCEPTION_ATTR,t); + return; + } + } + } + */ + + wrapper.getPipeline().getFirst().event(request, response, event); + + /* + if ((instances !=null ) && + (instances.length > 0)) { + // create post-service event + for (int i = 0; i < instances.length; i++) { + if (instances[i] == null) + continue; + if (!(instances[i] instanceof ServletRequestListener)) + continue; + ServletRequestListener listener = + (ServletRequestListener) instances[i]; + try { + listener.requestDestroyed(event); + } catch (Throwable t) { + container.getLogger().error(sm.getString("requestListenerValve.requestDestroy", + instances[i].getClass().getName()), t); + ServletRequest sreq = request.getRequest(); + sreq.setAttribute(Globals.EXCEPTION_ATTR,t); + } + } + } + */ + + } + + // -------------------------------------------------------- Private Methods diff --git a/java/org/apache/catalina/core/StandardEngineValve.java b/java/org/apache/catalina/core/StandardEngineValve.java index 71fe32d70..9c4259a23 100644 --- a/java/org/apache/catalina/core/StandardEngineValve.java +++ b/java/org/apache/catalina/core/StandardEngineValve.java @@ -23,6 +23,7 @@ import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.http.HttpServletResponse; +import org.apache.catalina.CometEvent; import org.apache.catalina.Host; import org.apache.catalina.connector.Request; import org.apache.catalina.connector.Response; @@ -108,4 +109,23 @@ final class StandardEngineValve } + + /** + * Process Comet event. + * + * @param request Request to be processed + * @param response Response to be produced + * @param valveContext Valve context used to forward to the next Valve + * + * @exception IOException if an input/output error occurred + * @exception ServletException if a servlet error occurred + */ + public final void event(Request request, Response response, CometEvent event) + throws IOException, ServletException { + + // Ask this Host to process this request + request.getHost().getPipeline().getFirst().event(request, response, event); + + } + } diff --git a/java/org/apache/catalina/core/StandardHostValve.java b/java/org/apache/catalina/core/StandardHostValve.java index d5771d0fc..64813215a 100644 --- a/java/org/apache/catalina/core/StandardHostValve.java +++ b/java/org/apache/catalina/core/StandardHostValve.java @@ -25,6 +25,7 @@ import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.http.HttpServletResponse; +import org.apache.catalina.CometEvent; import org.apache.catalina.Context; import org.apache.catalina.Globals; import org.apache.catalina.Wrapper; @@ -149,6 +150,57 @@ final class StandardHostValve } + /** + * Process Comet event. + * + * @param request Request to be processed + * @param response Response to be produced + * @param valveContext Valve context used to forward to the next Valve + * + * @exception IOException if an input/output error occurred + * @exception ServletException if a servlet error occurred + */ + public final void event(Request request, Response response, CometEvent event) + throws IOException, ServletException { + + // Select the Context to be used for this Request + Context context = request.getContext(); + + // Bind the context CL to the current thread + if( context.getLoader() != null ) { + // Not started - it should check for availability first + // This should eventually move to Engine, it's generic. + Thread.currentThread().setContextClassLoader + (context.getLoader().getClassLoader()); + } + + // Ask this Context to process this request + context.getPipeline().getFirst().event(request, response, event); + + // Access a session (if present) to update last accessed time, based on a + // strict interpretation of the specification + if (Globals.STRICT_SERVLET_COMPLIANCE) { + request.getSession(false); + } + + // Error page processing + response.setSuspended(false); + + Throwable t = (Throwable) request.getAttribute(Globals.EXCEPTION_ATTR); + + if (t != null) { + throwable(request, response, t); + } else { + status(request, response); + } + + // Restore the context classloader + Thread.currentThread().setContextClassLoader + (StandardHostValve.class.getClassLoader()); + + } + + // ------------------------------------------------------ Protected Methods diff --git a/java/org/apache/catalina/core/StandardWrapperValve.java b/java/org/apache/catalina/core/StandardWrapperValve.java index 464d60072..f18019455 100644 --- a/java/org/apache/catalina/core/StandardWrapperValve.java +++ b/java/org/apache/catalina/core/StandardWrapperValve.java @@ -27,6 +27,8 @@ import javax.servlet.ServletException; import javax.servlet.UnavailableException; import javax.servlet.http.HttpServletResponse; +import org.apache.catalina.CometEvent; +import org.apache.catalina.CometProcessor; import org.apache.catalina.Context; import org.apache.catalina.Globals; import org.apache.catalina.connector.ClientAbortException; @@ -155,6 +157,12 @@ final class StandardWrapperValve servlet = null; } + // Identify if the request is Comet related now that the servlet has been allocated + if (servlet instanceof CometProcessor + && request.getAttribute("org.apache.tomcat.comet.support") == Boolean.TRUE) { + request.setComet(true); + } + // Acknowlege the request try { response.sendAcknowledgement(); @@ -196,13 +204,16 @@ final class StandardWrapperValve else request.removeAttribute(Globals.JSP_FILE_ATTR); if ((servlet != null) && (filterChain != null)) { - // Swallow output if needed if (context.getSwallowOutput()) { try { SystemLogHandler.startCapture(); - filterChain.doFilter(request.getRequest(), - response.getResponse()); + if (request.isComet()) { + filterChain.doFilterEvent(request.getEvent()); + } else { + filterChain.doFilter(request.getRequest(), + response.getResponse()); + } } finally { String log = SystemLogHandler.stopCapture(); if (log != null && log.length() > 0) { @@ -210,8 +221,12 @@ final class StandardWrapperValve } } } else { - filterChain.doFilter - (request.getRequest(), response.getResponse()); + if (request.isComet()) { + filterChain.doFilterEvent(request.getEvent()); + } else { + filterChain.doFilter + (request.getRequest(), response.getResponse()); + } } } @@ -264,11 +279,23 @@ final class StandardWrapperValve } // Release the filter chain (if any) for this request - try { - if (filterChain != null) + if (filterChain != null) { + if (request.isComet()) { + // If this is a Comet request, then the same chain will be used for the + // processing of all subsequent events. + filterChain.reuse(); + } else { filterChain.release(); + } + } + + // Deallocate the allocated servlet instance + try { + if (servlet != null) { + wrapper.deallocate(servlet); + } } catch (Throwable e) { - container.getLogger().error(sm.getString("standardWrapper.releaseFilters", + container.getLogger().error(sm.getString("standardWrapper.deallocateException", wrapper.getName()), e); if (throwable == null) { throwable = e; @@ -276,6 +303,159 @@ final class StandardWrapperValve } } + // 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 e) { + container.getLogger().error(sm.getString("standardWrapper.unloadException", + wrapper.getName()), e); + if (throwable == null) { + throwable = e; + exception(request, response, e); + } + } + long t2=System.currentTimeMillis(); + + long time=t2-t1; + processingTime += time; + if( time > maxTime) maxTime=time; + if( time < minTime) minTime=time; + + } + + + /** + * Process a Comet event. The main differences here are to not use sendError + * (the response is committed), to avoid creating a new filter chain + * (which would work but be pointless), and a few very minor tweaks. + * + * @param request The servlet request to be processed + * @param response The servlet response to be created + * + * @exception IOException if an input/output error occurs, or is thrown + * by a subsequently invoked Valve, Filter, or Servlet + * @exception ServletException if a servlet error occurs, or is thrown + * by a subsequently invoked Valve, Filter, or Servlet + */ + public void event(Request request, Response response, CometEvent event) + throws IOException, ServletException { + + // Initialize local variables we may need + Throwable throwable = null; + // This should be a Request attribute... + long t1=System.currentTimeMillis(); + // FIXME: Add a flag to count the total amount of events processed ? requestCount++; + StandardWrapper wrapper = (StandardWrapper) getContainer(); + Servlet servlet = null; + Context context = (Context) wrapper.getParent(); + + // Check for the application being marked unavailable + boolean unavailable = !context.getAvailable() || wrapper.isUnavailable(); + + // Allocate a servlet instance to process this request + try { + if (!unavailable) { + servlet = wrapper.allocate(); + } + } catch (UnavailableException e) { + // The response is already committed, so it's not possible to do anything + } catch (ServletException e) { + container.getLogger().error(sm.getString("standardWrapper.allocateException", + wrapper.getName()), StandardWrapper.getRootCause(e)); + throwable = e; + exception(request, response, e); + servlet = null; + } catch (Throwable e) { + container.getLogger().error(sm.getString("standardWrapper.allocateException", + wrapper.getName()), e); + throwable = e; + exception(request, response, e); + servlet = null; + } + + MessageBytes requestPathMB = null; + if (request != null) { + requestPathMB = request.getRequestPathMB(); + } + request.setAttribute + (ApplicationFilterFactory.DISPATCHER_TYPE_ATTR, + ApplicationFilterFactory.REQUEST_INTEGER); + request.setAttribute + (ApplicationFilterFactory.DISPATCHER_REQUEST_PATH_ATTR, + requestPathMB); + // Get the current (unchanged) filter chain for this request + ApplicationFilterChain filterChain = + (ApplicationFilterChain) request.getFilterChain(); + + // Call the filter chain for this request + // NOTE: This also calls the servlet's event() method + try { + String jspFile = wrapper.getJspFile(); + if (jspFile != null) + request.setAttribute(Globals.JSP_FILE_ATTR, jspFile); + else + request.removeAttribute(Globals.JSP_FILE_ATTR); + if ((servlet != null) && (filterChain != null)) { + + // Swallow output if needed + if (context.getSwallowOutput()) { + try { + SystemLogHandler.startCapture(); + filterChain.doFilterEvent(request.getEvent()); + } finally { + String log = SystemLogHandler.stopCapture(); + if (log != null && log.length() > 0) { + context.getLogger().info(log); + } + } + } else { + filterChain.doFilterEvent(request.getEvent()); + } + + } + request.removeAttribute(Globals.JSP_FILE_ATTR); + } catch (ClientAbortException e) { + request.removeAttribute(Globals.JSP_FILE_ATTR); + throwable = e; + exception(request, response, e); + } catch (IOException e) { + request.removeAttribute(Globals.JSP_FILE_ATTR); + container.getLogger().warn(sm.getString("standardWrapper.serviceException", + wrapper.getName()), e); + throwable = e; + exception(request, response, e); + } catch (UnavailableException e) { + request.removeAttribute(Globals.JSP_FILE_ATTR); + container.getLogger().warn(sm.getString("standardWrapper.serviceException", + wrapper.getName()), e); + // Do not save exception in 'throwable', because we + // do not want to do exception(request, response, e) processing + } catch (ServletException e) { + request.removeAttribute(Globals.JSP_FILE_ATTR); + Throwable rootCause = StandardWrapper.getRootCause(e); + if (!(rootCause instanceof ClientAbortException)) { + container.getLogger().error(sm.getString("standardWrapper.serviceException", + wrapper.getName()), rootCause); + } + throwable = e; + exception(request, response, e); + } catch (Throwable e) { + request.removeAttribute(Globals.JSP_FILE_ATTR); + container.getLogger().error(sm.getString("standardWrapper.serviceException", + wrapper.getName()), e); + throwable = e; + exception(request, response, e); + } + + // Release the filter chain (if any) for this request + if (filterChain != null) { + filterChain.reuse(); + } + // Deallocate the allocated servlet instance try { if (servlet != null) { @@ -305,6 +485,7 @@ final class StandardWrapperValve exception(request, response, e); } } + long t2=System.currentTimeMillis(); long time=t2-t1; diff --git a/java/org/apache/catalina/servlets/CometServlet.java b/java/org/apache/catalina/servlets/CometServlet.java deleted file mode 100644 index ff21e158f..000000000 --- a/java/org/apache/catalina/servlets/CometServlet.java +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright 2006 The Apache Software Foundation. - * - * Licensed 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.servlets; - - -import java.io.IOException; -import java.io.InputStream; - -import javax.servlet.ServletException; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.apache.catalina.CometProcessor; - - -/** - * Helper class to implement Comet functionality. - */ -public abstract class CometServlet - extends HttpServlet implements CometProcessor { - - public void begin(HttpServletRequest request, HttpServletResponse response) - throws IOException, ServletException { - request.setAttribute("org.apache.tomcat.comet", Boolean.TRUE); - } - - public void end(HttpServletRequest request, HttpServletResponse response) - throws IOException, ServletException { - request.removeAttribute("org.apache.tomcat.comet"); - } - - public void error(HttpServletRequest request, HttpServletResponse response) - throws IOException, ServletException { - end(request, response); - } - - public boolean read(HttpServletRequest request, HttpServletResponse response) - throws IOException, ServletException { - InputStream is = request.getInputStream(); - byte[] buf = new byte[512]; - do { - int n = is.read(buf); - if (n > 0) { - // Do something with the data - } else if (n < 0) { - return false; - } - } while (is.available() > 0); - return true; - } - - protected void service(HttpServletRequest request, HttpServletResponse response) - throws IOException, ServletException { - - if (request.getAttribute("org.apache.tomcat.comet.support") == Boolean.TRUE) { - begin(request, response); - } else { - // No Comet support: regular servlet handling - begin(request, response); - boolean error = true; - try { - // Loop reading data - while (read(request, response)); - error = false; - } finally { - if (error) { - error(request, response); - } else { - end(request, response); - } - } - } - } - - public void setTimeout(HttpServletRequest request, HttpServletResponse response, int timeout) - throws IOException, ServletException, UnsupportedOperationException { - if (request.getAttribute("org.apache.tomcat.comet.timeout.support") == Boolean.TRUE) { - request.setAttribute("org.apache.tomcat.comet.timeout",new Integer(timeout)); - } else { - throw new UnsupportedOperationException(); - } - } - - -} diff --git a/java/org/apache/catalina/valves/ValveBase.java b/java/org/apache/catalina/valves/ValveBase.java index 6f1f94caf..9d45284e9 100644 --- a/java/org/apache/catalina/valves/ValveBase.java +++ b/java/org/apache/catalina/valves/ValveBase.java @@ -26,6 +26,7 @@ import javax.management.MalformedObjectNameException; import javax.management.ObjectName; import javax.servlet.ServletException; +import org.apache.catalina.CometEvent; import org.apache.catalina.Contained; import org.apache.catalina.Container; import org.apache.catalina.Context; @@ -180,6 +181,26 @@ public abstract class ValveBase /** + * Process a Comet event. This method will rarely need to be provided by + * a subclass, unless it needs to reassociate a particular object with + * the thread that is processing the request. + * + * @param request The servlet request to be processed + * @param response The servlet response to be created + * + * @exception IOException if an input/output error occurs, or is thrown + * by a subsequently invoked Valve, Filter, or Servlet + * @exception ServletException if a servlet error occurs, or is thrown + * by a subsequently invoked Valve, Filter, or Servlet + */ + public void event(Request request, Response response, CometEvent event) + throws IOException, ServletException { + // Perform the request + getNext().event(request, response, event); + } + + + /** * Return a String rendering of this object. */ public String toString() { -- 2.11.0