From f9ee9942f000cc4a1ac22859ecc1acc39735860a Mon Sep 17 00:00:00 2001 From: Lachlan Roberts Date: Fri, 3 Jan 2025 17:58:46 +1100 Subject: [PATCH] Issue #74 - remove trimmed servlets logic from Jetty12 EE8 & EE10 runtimes Signed-off-by: Lachlan Roberts --- .../jetty/ee10/AppEngineWebAppContext.java | 487 +++------------ .../jetty/ee8/AppEngineWebAppContext.java | 575 ++++-------------- 2 files changed, 223 insertions(+), 839 deletions(-) diff --git a/runtime/runtime_impl_jetty12/src/main/java/com/google/apphosting/runtime/jetty/ee10/AppEngineWebAppContext.java b/runtime/runtime_impl_jetty12/src/main/java/com/google/apphosting/runtime/jetty/ee10/AppEngineWebAppContext.java index 633f1291..4e23eebd 100644 --- a/runtime/runtime_impl_jetty12/src/main/java/com/google/apphosting/runtime/jetty/ee10/AppEngineWebAppContext.java +++ b/runtime/runtime_impl_jetty12/src/main/java/com/google/apphosting/runtime/jetty/ee10/AppEngineWebAppContext.java @@ -16,25 +16,30 @@ package com.google.apphosting.runtime.jetty.ee10; +import static com.google.common.base.StandardSystemProperty.JAVA_IO_TMPDIR; +import static java.nio.charset.StandardCharsets.UTF_8; + import com.google.apphosting.api.ApiProxy; import com.google.apphosting.api.ApiProxy.LogRecord; import com.google.apphosting.runtime.jetty.EE10AppEngineAuthentication; -import com.google.apphosting.utils.servlet.ee10.DeferredTaskServlet; -import com.google.apphosting.utils.servlet.ee10.JdbcMySqlConnectionCleanupFilter; -import com.google.apphosting.utils.servlet.ee10.SessionCleanupServlet; -import com.google.apphosting.utils.servlet.ee10.SnapshotServlet; -import com.google.apphosting.utils.servlet.ee10.WarmupServlet; -import com.google.common.collect.ImmutableSet; import jakarta.servlet.DispatcherType; import jakarta.servlet.Filter; import jakarta.servlet.Servlet; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.EnumSet; +import java.util.EventListener; +import java.util.List; +import java.util.ListIterator; +import java.util.Objects; +import java.util.Scanner; +import java.util.concurrent.CopyOnWriteArrayList; import org.eclipse.jetty.ee10.servlet.FilterHolder; -import org.eclipse.jetty.ee10.servlet.FilterMapping; -import org.eclipse.jetty.ee10.servlet.Holder; import org.eclipse.jetty.ee10.servlet.ListenerHolder; import org.eclipse.jetty.ee10.servlet.ServletHandler; import org.eclipse.jetty.ee10.servlet.ServletHolder; -import org.eclipse.jetty.ee10.servlet.ServletMapping; import org.eclipse.jetty.ee10.servlet.security.ConstraintMapping; import org.eclipse.jetty.ee10.servlet.security.ConstraintSecurityHandler; import org.eclipse.jetty.ee10.webapp.WebAppContext; @@ -45,25 +50,6 @@ import org.eclipse.jetty.util.resource.Resource; import org.eclipse.jetty.util.resource.ResourceFactory; -import java.io.File; -import java.io.FileNotFoundException; -import java.io.PrintWriter; -import java.io.StringWriter; -import java.util.ArrayList; -import java.util.EnumSet; -import java.util.EventListener; -import java.util.HashMap; -import java.util.List; -import java.util.ListIterator; -import java.util.Map; -import java.util.Objects; -import java.util.Scanner; -import java.util.Set; -import java.util.concurrent.CopyOnWriteArrayList; - -import static com.google.common.base.StandardSystemProperty.JAVA_IO_TMPDIR; -import static java.nio.charset.StandardCharsets.UTF_8; - /** * {@code AppEngineWebAppContext} is a customization of Jetty's {@link WebAppContext} that is aware * of the {@link ApiProxy} and can provide custom logging and authentication. @@ -91,37 +77,13 @@ public class AppEngineWebAppContext extends WebAppContext { private final List requestListeners = new CopyOnWriteArrayList<>(); private final boolean ignoreContentLength; - // These are deprecated filters and servlets - private static final ImmutableSet DEPRECATED_SERVLETS_FILTERS = - ImmutableSet.of( - // Remove unused filters that may still be instantiated by - // deprecated webdefault.xml in old SDKs - new HolderMatcher( - "AbandonedTransactionDetector", - "com.google.apphosting.utils.servlet.TransactionCleanupFilter"), - new HolderMatcher( - "SaveSessionFilter", "com.google.apphosting.runtime.jetty.SaveSessionFilter"), - new HolderMatcher( - "_ah_ParseBlobUploadFilter", - "com.google.apphosting.utils.servlet.ParseBlobUploadFilter"), - new HolderMatcher( - "_ah_default", "com.google.apphosting.runtime.jetty.ResourceFileServlet"), - new HolderMatcher( - "default", "com.google.apphosting.runtime.jetty.ee10.NamedDefaultServlet"), - new HolderMatcher("jsp", "com.google.apphosting.runtime.jetty.NoJspSerlvet"), - - // remove application filters and servlets that are known to only be applicable to - // the java 7 runtime - new HolderMatcher(null, "com.google.appengine.tools.appstats.AppstatsFilter"), - new HolderMatcher(null, "com.google.appengine.tools.appstats.AppstatsServlet")); - @Override public boolean checkAlias(String path, Resource resource) { return true; } public AppEngineWebAppContext(File appDir, String serverInfo) { - this(appDir, serverInfo, /*extractWar=*/ true); + this(appDir, serverInfo, /* extractWar= */ true); } public AppEngineWebAppContext(File appDir, String serverInfo, boolean extractWar) { @@ -209,10 +171,10 @@ private static boolean isAppIdForNonContentLength() { @Override public boolean addEventListener(EventListener listener) { if (super.addEventListener(listener)) { - if (listener instanceof RequestListener) { - requestListeners.add((RequestListener)listener); - } - return true; + if (listener instanceof RequestListener) { + requestListeners.add((RequestListener) listener); + } + return true; } return false; } @@ -220,10 +182,10 @@ public boolean addEventListener(EventListener listener) { @Override public boolean removeEventListener(EventListener listener) { if (super.removeEventListener(listener)) { - if (listener instanceof RequestListener) { - requestListeners.remove((RequestListener)listener); - } - return true; + if (listener instanceof RequestListener) { + requestListeners.remove((RequestListener) listener); + } + return true; } return false; } @@ -238,48 +200,25 @@ public void doStart() throws Exception { protected void startWebapp() throws Exception { // startWebapp is called after the web.xml metadata has been resolved, so we can // clean configuration here: - // - Removed deprecated filters and servlets + // - Set AsyncSupported to the value defined by the system property. // - Ensure known runtime filters/servlets are instantiated from this classloader - // - Ensure known runtime mappings exist. ServletHandler servletHandler = getServletHandler(); - TrimmedFilters trimmedFilters = - new TrimmedFilters( - servletHandler.getFilters(), - servletHandler.getFilterMappings(), - DEPRECATED_SERVLETS_FILTERS); - trimmedFilters.ensure( - "CloudSqlConnectionCleanupFilter", JdbcMySqlConnectionCleanupFilter.class, "/*"); - - TrimmedServlets trimmedServlets = - new TrimmedServlets( - servletHandler.getServlets(), - servletHandler.getServletMappings(), - DEPRECATED_SERVLETS_FILTERS); - trimmedServlets.ensure("_ah_warmup", WarmupServlet.class, "/_ah/warmup"); - trimmedServlets.ensure( - "_ah_sessioncleanup", SessionCleanupServlet.class, "/_ah/sessioncleanup"); - trimmedServlets.ensure( - "_ah_queue_deferred", DeferredTaskServlet.class, "/_ah/queue/__deferred__"); - trimmedServlets.ensure("_ah_snapshot", SnapshotServlet.class, "/_ah/snapshot"); - trimmedServlets.ensure("_ah_default", ResourceFileServlet.class, "/"); - trimmedServlets.ensure("default", NamedDefaultServlet.class); - trimmedServlets.ensure("jsp", NamedJspServlet.class); - - trimmedServlets.instantiateJettyServlets(); - trimmedFilters.instantiateJettyFilters(); - instantiateJettyListeners(); - - servletHandler.setFilters(trimmedFilters.getHolders()); - servletHandler.setFilterMappings(trimmedFilters.getMappings()); - servletHandler.setServlets(trimmedServlets.getHolders()); - servletHandler.setServletMappings(trimmedServlets.getMappings()); + for (ServletHolder holder : servletHandler.getServlets()) { + holder.setAsyncSupported(APP_IS_ASYNC); + } + for (FilterHolder holder : servletHandler.getFilters()) { + holder.setAsyncSupported(APP_IS_ASYNC); + } + instantiateJettyServlets(servletHandler); + instantiateJettyFilters(servletHandler); + instantiateJettyListeners(servletHandler); servletHandler.setAllowDuplicateMappings(true); // Protect deferred task queue with constraint ConstraintSecurityHandler security = (ConstraintSecurityHandler) getSecurityHandler(); ConstraintMapping cm = new ConstraintMapping(); cm.setConstraint( - Constraint.from("deferred_queue", Constraint.Authorization.SPECIFIC_ROLE, "admin")); + Constraint.from("deferred_queue", Constraint.Authorization.SPECIFIC_ROLE, "admin")); cm.setPathSpec("/_ah/queue/__deferred__"); security.addConstraintMapping(cm); @@ -287,6 +226,61 @@ protected void startWebapp() throws Exception { super.startWebapp(); } + /** + * Instantiate any registrations of a jetty provided servlet + * + * @throws ReflectiveOperationException If a new instance of the servlet cannot be instantiated + */ + private static void instantiateJettyServlets(ServletHandler servletHandler) + throws ReflectiveOperationException { + for (ServletHolder h : servletHandler.getServlets()) { + if (h.getClassName() != null && h.getClassName().startsWith(JETTY_PACKAGE)) { + Class servlet = + ServletHolder.class + .getClassLoader() + .loadClass(h.getClassName()) + .asSubclass(Servlet.class); + h.setServlet(servlet.getConstructor().newInstance()); + } + } + } + + /** + * Instantiate any registrations of a jetty provided filter + * + * @throws ReflectiveOperationException If a new instance of the filter cannot be instantiated + */ + private static void instantiateJettyFilters(ServletHandler servletHandler) + throws ReflectiveOperationException { + for (FilterHolder h : servletHandler.getFilters()) { + if (h.getClassName().startsWith(JETTY_PACKAGE)) { + Class filter = + ServletHolder.class + .getClassLoader() + .loadClass(h.getClassName()) + .asSubclass(Filter.class); + h.setFilter(filter.getConstructor().newInstance()); + } + } + } + + /* Instantiate any jetty listeners from the container classloader */ + private static void instantiateJettyListeners(ServletHandler servletHandler) throws ReflectiveOperationException { + ListenerHolder[] listeners = servletHandler.getListeners(); + if (listeners != null) { + for (ListenerHolder h : listeners) { + if (h.getClassName().startsWith(JETTY_PACKAGE)) { + Class listener = + ServletHandler.class + .getClassLoader() + .loadClass(h.getClassName()) + .asSubclass(EventListener.class); + h.setListener(listener.getConstructor().newInstance()); + } + } + } + } + @Override public boolean handle(Request request, Response response, Callback callback) throws Exception { ListIterator iter = requestListeners.listIterator(); @@ -314,23 +308,6 @@ protected ServletHandler newServletHandler() { return handler; } - /* Instantiate any jetty listeners from the container classloader */ - private void instantiateJettyListeners() throws ReflectiveOperationException { - ListenerHolder[] listeners = getServletHandler().getListeners(); - if (listeners != null) { - for (ListenerHolder h : listeners) { - if (h.getClassName().startsWith(JETTY_PACKAGE)) { - Class listener = - ServletHandler.class - .getClassLoader() - .loadClass(h.getClassName()) - .asSubclass(EventListener.class); - h.setListener(listener.getConstructor().newInstance()); - } - } - } - } - @Override protected void createTempDirectory() { File tempDir = getTempDirectory(); @@ -401,290 +378,4 @@ public void log(String message, Throwable throwable) { new ApiProxy.LogRecord(logLevel, System.currentTimeMillis() * 1000L, writer.toString())); } } - - /** A class to hold a Holder name and/or className and/or source location for matching. */ - private static class HolderMatcher { - final String name; - final String className; - - /** - * @param name The name of a filter/servlet to match, or null if not matching on name. - * @param className The class name of a filter/servlet to match, or null if not matching on - * className - */ - HolderMatcher(String name, String className) { - this.name = name; - this.className = className; - } - - /** - * @param holder The holder to match - * @return true IFF this matcher matches the holder. - */ - boolean appliesTo(Holder holder) { - if (name != null && !name.equals(holder.getName())) { - return false; - } - - if (className != null && !className.equals(holder.getClassName())) { - return false; - } - - return true; - } - } - - /** - * TrimmedServlets is in charge of handling web applications that got deployed previously with the - * previous webdefault.xml content(prior to this CL changing it). We still need to be able to load - * old apps defined with the previous webdefault.xml file that generated an obsolete - * quickstart.xml having the servlets defined in webdefault.xml. - * - *

New deployements would not need this processing (no-op), but we need to handle all apps, - * deployed now or in the past. - */ - private static class TrimmedServlets { - private final Map holders = new HashMap<>(); - private final List mappings = new ArrayList<>(); - - TrimmedServlets( - ServletHolder[] holders, ServletMapping[] mappings, Set deprecations) { - for (ServletHolder servletHolder : holders) { - boolean deprecated = false; - servletHolder.setAsyncSupported(APP_IS_ASYNC); - for (HolderMatcher holderMatcher : deprecations) { - deprecated |= holderMatcher.appliesTo(servletHolder); - } - - if (!deprecated) { - this.holders.put(servletHolder.getName(), servletHolder); - } - } - - for (ServletMapping m : mappings) { - this.mappings.add(m); - } - } - - /** - * Ensure the registration of a container provided servlet: - * - *

    - *
  • If any existing servlet registrations are for the passed servlet class, then their - * holder is updated with a new instance created on the containers classpath. - *
  • If a servlet registration for the passed servlet name does not exist, one is created to - * the passed servlet class. - *
- * - * @param name The servlet name - * @param servlet The servlet class - * @throws ReflectiveOperationException If a new instance of the servlet cannot be instantiated - */ - void ensure(String name, Class servlet) throws ReflectiveOperationException { - // Instantiate any holders referencing this servlet (may be application instances) - for (ServletHolder h : holders.values()) { - if (servlet.getName().equals(h.getClassName())) { - h.setServlet(servlet.getConstructor().newInstance()); - h.setAsyncSupported(APP_IS_ASYNC); - } - } - - // Look for (or instantiate) our named instance - ServletHolder holder = holders.get(name); - if (holder == null) { - holder = new ServletHolder(servlet.getConstructor().newInstance()); - holder.setInitOrder(1); - holder.setName(name); - holder.setAsyncSupported(APP_IS_ASYNC); - holders.put(name, holder); - } - } - - /** - * Ensure the registration of a container provided servlet: - * - *
    - *
  • If any existing servlet registrations are for the passed servlet class, then their - * holder is updated with a new instance created on the containers classpath. - *
  • If a servlet registration for the passed servlet name does not exist, one is created to - * the passed servlet class. - *
  • If a servlet mapping for the passed servlet name and pathSpec does not exist, one is - * created. - *
- * - * @param name The servlet name - * @param servlet The servlet class - * @param pathSpec The servlet pathspec - * @throws ReflectiveOperationException If a new instance of the servlet cannot be instantiated - */ - void ensure(String name, Class servlet, String pathSpec) - throws ReflectiveOperationException { - // Ensure Servlet - ensure(name, servlet); - - // Ensure mapping - if (pathSpec != null) { - boolean mapped = false; - for (ServletMapping mapping : mappings) { - if (mapping.containsPathSpec(pathSpec)) { - mapped = true; - break; - } - } - if (!mapped) { - ServletMapping mapping = new ServletMapping(); - mapping.setServletName(name); - mapping.setPathSpec(pathSpec); - if (pathSpec.equals("/")) { - mapping.setFromDefaultDescriptor(true); - } - mappings.add(mapping); - } - } - } - - /** - * Instantiate any registrations of a jetty provided servlet - * - * @throws ReflectiveOperationException If a new instance of the servlet cannot be instantiated - */ - void instantiateJettyServlets() throws ReflectiveOperationException { - for (ServletHolder h : holders.values()) { - if (h.getClassName() != null && h.getClassName().startsWith(JETTY_PACKAGE)) { - Class servlet = - ServletHolder.class - .getClassLoader() - .loadClass(h.getClassName()) - .asSubclass(Servlet.class); - h.setServlet(servlet.getConstructor().newInstance()); - } - } - } - - ServletHolder[] getHolders() { - return holders.values().toArray(new ServletHolder[0]); - } - - ServletMapping[] getMappings() { - List trimmed = new ArrayList<>(mappings.size()); - for (ServletMapping m : mappings) { - if (this.holders.containsKey(m.getServletName())) { - trimmed.add(m); - } - } - return trimmed.toArray(new ServletMapping[0]); - } - } - - private static class TrimmedFilters { - private final Map holders = new HashMap<>(); - private final List mappings = new ArrayList<>(); - - TrimmedFilters( - FilterHolder[] holders, FilterMapping[] mappings, Set deprecations) { - for (FilterHolder h : holders) { - boolean deprecated = false; - h.setAsyncSupported(APP_IS_ASYNC); - for (HolderMatcher m : deprecations) { - deprecated |= m.appliesTo(h); - } - - if (!deprecated) { - this.holders.put(h.getName(), h); - } - } - - for (FilterMapping m : mappings) { - this.mappings.add(m); - } - } - - /** - * Ensure the registration of a container provided filter: - * - *
    - *
  • If any existing filter registrations are for the passed filter class, then their holder - * is updated with a new instance created on the containers classpath. - *
  • If a filter registration for the passed filter name does not exist, one is created to - * the passed filter class. - *
  • If a filter mapping for the passed filter name and pathSpec does not exist, one is - * created. - *
- * - * @param name The filter name - * @param filter The filter class - * @param pathSpec The servlet pathspec - * @throws ReflectiveOperationException If a new instance of the servlet cannot be instantiated - */ - void ensure(String name, Class filter, String pathSpec) throws Exception { - - // Instantiate any holders referencing this filter (may be application instances) - for (FilterHolder h : holders.values()) { - if (filter.getName().equals(h.getClassName())) { - h.setFilter(filter.getConstructor().newInstance()); - h.setAsyncSupported(APP_IS_ASYNC); - } - } - - // Look for (or instantiate) our named instance - FilterHolder holder = holders.get(name); - if (holder == null) { - holder = new FilterHolder(filter.getConstructor().newInstance()); - holder.setName(name); - holders.put(name, holder); - holder.setAsyncSupported(APP_IS_ASYNC); - } - - // Ensure mapping - boolean mapped = false; - for (FilterMapping mapping : mappings) { - - for (String ps : mapping.getPathSpecs()) { - if (pathSpec.equals(ps) && name.equals(mapping.getFilterName())) { - mapped = true; - break; - } - } - } - if (!mapped) { - FilterMapping mapping = new FilterMapping(); - mapping.setFilterName(name); - mapping.setPathSpec(pathSpec); - mapping.setDispatches(FilterMapping.REQUEST); - mappings.add(mapping); - } - } - - /** - * Instantiate any registrations of a jetty provided filter - * - * @throws ReflectiveOperationException If a new instance of the filter cannot be instantiated - */ - void instantiateJettyFilters() throws ReflectiveOperationException { - for (FilterHolder h : holders.values()) { - if (h.getClassName().startsWith(JETTY_PACKAGE)) { - Class filter = - ServletHolder.class - .getClassLoader() - .loadClass(h.getClassName()) - .asSubclass(Filter.class); - h.setFilter(filter.getConstructor().newInstance()); - } - } - } - - FilterHolder[] getHolders() { - return holders.values().toArray(new FilterHolder[0]); - } - - FilterMapping[] getMappings() { - List trimmed = new ArrayList<>(mappings.size()); - for (FilterMapping m : mappings) { - if (this.holders.containsKey(m.getFilterName())) { - trimmed.add(m); - } - } - return trimmed.toArray(new FilterMapping[0]); - } - } } diff --git a/runtime/runtime_impl_jetty12/src/main/java/com/google/apphosting/runtime/jetty/ee8/AppEngineWebAppContext.java b/runtime/runtime_impl_jetty12/src/main/java/com/google/apphosting/runtime/jetty/ee8/AppEngineWebAppContext.java index 4785de30..ff4a4c3b 100644 --- a/runtime/runtime_impl_jetty12/src/main/java/com/google/apphosting/runtime/jetty/ee8/AppEngineWebAppContext.java +++ b/runtime/runtime_impl_jetty12/src/main/java/com/google/apphosting/runtime/jetty/ee8/AppEngineWebAppContext.java @@ -16,52 +16,38 @@ package com.google.apphosting.runtime.jetty.ee8; +import static com.google.common.base.StandardSystemProperty.JAVA_IO_TMPDIR; +import static java.nio.charset.StandardCharsets.UTF_8; + import com.google.apphosting.api.ApiProxy; import com.google.apphosting.api.ApiProxy.LogRecord; import com.google.apphosting.runtime.jetty.AppEngineAuthentication; -import com.google.apphosting.utils.servlet.DeferredTaskServlet; -import com.google.apphosting.utils.servlet.JdbcMySqlConnectionCleanupFilter; -import com.google.apphosting.utils.servlet.SessionCleanupServlet; -import com.google.apphosting.utils.servlet.SnapshotServlet; -import com.google.apphosting.utils.servlet.WarmupServlet; -import com.google.common.collect.ImmutableSet; -import org.eclipse.jetty.ee8.nested.ServletConstraint; -import org.eclipse.jetty.ee8.security.ConstraintMapping; -import org.eclipse.jetty.ee8.security.ConstraintSecurityHandler; -import org.eclipse.jetty.ee8.servlet.FilterHolder; -import org.eclipse.jetty.ee8.servlet.FilterMapping; -import org.eclipse.jetty.ee8.servlet.Holder; -import org.eclipse.jetty.ee8.servlet.ListenerHolder; -import org.eclipse.jetty.ee8.servlet.ServletHandler; -import org.eclipse.jetty.ee8.servlet.ServletHolder; -import org.eclipse.jetty.ee8.servlet.ServletMapping; -import org.eclipse.jetty.ee8.webapp.WebAppContext; -import org.eclipse.jetty.util.resource.Resource; -import org.eclipse.jetty.util.resource.ResourceFactory; - -import javax.servlet.Filter; -import javax.servlet.Servlet; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; -import java.util.ArrayList; import java.util.EventListener; -import java.util.HashMap; import java.util.List; import java.util.ListIterator; -import java.util.Map; import java.util.Objects; import java.util.Scanner; -import java.util.Set; import java.util.concurrent.CopyOnWriteArrayList; - -import static com.google.common.base.StandardSystemProperty.JAVA_IO_TMPDIR; -import static java.nio.charset.StandardCharsets.UTF_8; +import javax.servlet.Filter; +import javax.servlet.Servlet; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.eclipse.jetty.ee8.nested.ServletConstraint; +import org.eclipse.jetty.ee8.security.ConstraintMapping; +import org.eclipse.jetty.ee8.security.ConstraintSecurityHandler; +import org.eclipse.jetty.ee8.servlet.FilterHolder; +import org.eclipse.jetty.ee8.servlet.ListenerHolder; +import org.eclipse.jetty.ee8.servlet.ServletHandler; +import org.eclipse.jetty.ee8.servlet.ServletHolder; +import org.eclipse.jetty.ee8.webapp.WebAppContext; +import org.eclipse.jetty.util.resource.Resource; +import org.eclipse.jetty.util.resource.ResourceFactory; /** * {@code AppEngineWebAppContext} is a customization of Jetty's {@link WebAppContext} that is aware @@ -87,48 +73,22 @@ public class AppEngineWebAppContext extends WebAppContext { "/base/java8_runtime/appengine.ignore-content-length"; private final String serverInfo; - private final boolean extractWar; private final List requestListeners = new CopyOnWriteArrayList<>(); private final boolean ignoreContentLength; - - // These are deprecated filters and servlets - private static final ImmutableSet DEPRECATED_SERVLETS_FILTERS = - ImmutableSet.of( - // Remove unused filters that may still be instantiated by - // deprecated webdefault.xml in old SDKs - new HolderMatcher( - "AbandonedTransactionDetector", - "com.google.apphosting.utils.servlet.TransactionCleanupFilter"), - new HolderMatcher( - "SaveSessionFilter", "com.google.apphosting.runtime.jetty.SaveSessionFilter"), - new HolderMatcher( - "_ah_ParseBlobUploadFilter", - "com.google.apphosting.utils.servlet.ParseBlobUploadFilter"), - new HolderMatcher( - "_ah_default", "com.google.apphosting.runtime.jetty.ResourceFileServlet"), - new HolderMatcher("default", "com.google.apphosting.runtime.jetty.ee8.NamedDefaultServlet"), - new HolderMatcher("jsp", "com.google.apphosting.runtime.jetty.NoJspSerlvet"), - - // remove application filters and servlets that are known to only be applicable to - // the java 7 runtime - new HolderMatcher(null, "com.google.appengine.tools.appstats.AppstatsFilter"), - new HolderMatcher(null, "com.google.appengine.tools.appstats.AppstatsServlet")); - + @Override public boolean checkAlias(String path, Resource resource) { return true; } public AppEngineWebAppContext(File appDir, String serverInfo) { - this(appDir, serverInfo, /*extractWar=*/ true); + this(appDir, serverInfo, /* extractWar= */ true); } public AppEngineWebAppContext(File appDir, String serverInfo, boolean extractWar) { // We set the contextPath to / for all applications. super(appDir.getPath(), "/"); - this.extractWar = extractWar; - // If the application fails to start, we throw so the JVM can exit. setThrowUnavailableOnStartupException(true); @@ -165,7 +125,8 @@ public AppEngineWebAppContext(File appDir, String serverInfo, boolean extractWar // Configure the Jetty SecurityHandler to understand our method of // authentication (via the UserService). - AppEngineAuthentication.configureSecurityHandler((ConstraintSecurityHandler) getSecurityHandler()); + AppEngineAuthentication.configureSecurityHandler( + (ConstraintSecurityHandler) getSecurityHandler()); setMaxFormContentSize(MAX_RESPONSE_SIZE); @@ -180,20 +141,19 @@ protected ClassLoader configureClassLoader(ClassLoader loader) { } @Override - public APIContext getServletContext() - { - /* TODO only does this for logging? - // Override the default HttpServletContext implementation. - // TODO: maybe not needed when there is no securrity manager. - // see - // https://github.com/GoogleCloudPlatform/appengine-java-vm-runtime/commit/43c37fd039fb619608cfffdc5461ecddb4d90ebc - _scontext = new AppEngineServletContext(); - */ - - return super.getServletContext(); - } + public APIContext getServletContext() { + /* TODO only does this for logging? + // Override the default HttpServletContext implementation. + // TODO: maybe not needed when there is no securrity manager. + // see + // https://github.com/GoogleCloudPlatform/appengine-java-vm-runtime/commit/43c37fd039fb619608cfffdc5461ecddb4d90ebc + _scontext = new AppEngineServletContext(); + */ + + return super.getServletContext(); + } - private static boolean isAppIdForNonContentLength() { + private static boolean isAppIdForNonContentLength() { String projectId = System.getenv("GOOGLE_CLOUD_PROJECT"); if (projectId == null) { return false; @@ -213,10 +173,10 @@ private static boolean isAppIdForNonContentLength() { @Override public boolean addEventListener(EventListener listener) { if (super.addEventListener(listener)) { - if (listener instanceof RequestListener) { - requestListeners.add((RequestListener)listener); - } - return true; + if (listener instanceof RequestListener) { + requestListeners.add((RequestListener) listener); + } + return true; } return false; } @@ -224,10 +184,10 @@ public boolean addEventListener(EventListener listener) { @Override public boolean removeEventListener(EventListener listener) { if (super.removeEventListener(listener)) { - if (listener instanceof RequestListener) { - requestListeners.remove((RequestListener)listener); - } - return true; + if (listener instanceof RequestListener) { + requestListeners.remove((RequestListener) listener); + } + return true; } return false; } @@ -238,90 +198,76 @@ public void doStart() throws Exception { addEventListener(new TransactionCleanupListener(getClassLoader())); } - @Override - protected void startWebapp() throws Exception { - // This Listener doStart is called after the web.xml metadata has been resolved, so we can - // clean configuration here: - // - Removed deprecated filters and servlets - // - Ensure known runtime filters/servlets are instantiated from this classloader - // - Ensure known runtime mappings exist. - ServletHandler servletHandler = getServletHandler(); - TrimmedFilters trimmedFilters = - new TrimmedFilters( - servletHandler.getFilters(), - servletHandler.getFilterMappings(), - DEPRECATED_SERVLETS_FILTERS); - trimmedFilters.ensure( - "CloudSqlConnectionCleanupFilter", JdbcMySqlConnectionCleanupFilter.class, "/*"); - - TrimmedServlets trimmedServlets = - new TrimmedServlets( - servletHandler.getServlets(), - servletHandler.getServletMappings(), - DEPRECATED_SERVLETS_FILTERS); - trimmedServlets.ensure("_ah_warmup", WarmupServlet.class, "/_ah/warmup"); - trimmedServlets.ensure( - "_ah_sessioncleanup", SessionCleanupServlet.class, "/_ah/sessioncleanup"); - trimmedServlets.ensure( - "_ah_queue_deferred", DeferredTaskServlet.class, "/_ah/queue/__deferred__"); - trimmedServlets.ensure("_ah_snapshot", SnapshotServlet.class, "/_ah/snapshot"); - trimmedServlets.ensure("_ah_default", ResourceFileServlet.class, "/"); - trimmedServlets.ensure("default", NamedDefaultServlet.class); - trimmedServlets.ensure("jsp", NamedJspServlet.class); - - trimmedServlets.instantiateJettyServlets(); - trimmedFilters.instantiateJettyFilters(); - instantiateJettyListeners(); - - servletHandler.setFilters(trimmedFilters.getHolders()); - servletHandler.setFilterMappings(trimmedFilters.getMappings()); - servletHandler.setServlets(trimmedServlets.getHolders()); - servletHandler.setServletMappings(trimmedServlets.getMappings()); - servletHandler.setAllowDuplicateMappings(true); - - // Protect deferred task queue with constraint - ConstraintSecurityHandler security = getChildHandlerByClass(ConstraintSecurityHandler.class); - ConstraintMapping cm = new ConstraintMapping(); - cm.setConstraint(new ServletConstraint("deferred_queue", "admin")); - cm.setPathSpec("/_ah/queue/__deferred__"); - security.addConstraintMapping(cm); - + @Override + protected void startWebapp() throws Exception { + // startWebapp is called after the web.xml metadata has been resolved, so we can + // clean configuration here: + // - Set AsyncSupported to the value defined by the system property. + // - Ensure known runtime filters/servlets are instantiated from this classloader + ServletHandler servletHandler = getServletHandler(); + for (ServletHolder holder : servletHandler.getServlets()) { + holder.setAsyncSupported(APP_IS_ASYNC); + } + for (FilterHolder holder : servletHandler.getFilters()) { + holder.setAsyncSupported(APP_IS_ASYNC); + } + instantiateJettyServlets(servletHandler); + instantiateJettyFilters(servletHandler); + instantiateJettyListeners(servletHandler); + servletHandler.setAllowDuplicateMappings(true); + + // Protect deferred task queue with constraint + ConstraintSecurityHandler security = getChildHandlerByClass(ConstraintSecurityHandler.class); + ConstraintMapping cm = new ConstraintMapping(); + cm.setConstraint(new ServletConstraint("deferred_queue", "admin")); + cm.setPathSpec("/_ah/queue/__deferred__"); + security.addConstraintMapping(cm); // continue starting the webapp super.startWebapp(); } - @Override - public void doHandle(String target, org.eclipse.jetty.ee8.nested.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { - - ListIterator iter = requestListeners.listIterator(); - while (iter.hasNext()) { - iter.next().requestReceived(this, baseRequest); - } - try { - if (ignoreContentLength) { - response = new IgnoreContentLengthResponseWrapper(response); - } - - super.doHandle(target, baseRequest, request, response); - } finally { - // TODO: this finally approach is ok until async request handling is supported - while (iter.hasPrevious()) { - iter.previous().requestComplete(this, baseRequest); - } + /** + * Instantiate any registrations of a jetty provided servlet + * + * @throws ReflectiveOperationException If a new instance of the servlet cannot be instantiated + */ + private static void instantiateJettyServlets(ServletHandler servletHandler) + throws ReflectiveOperationException { + for (ServletHolder h : servletHandler.getServlets()) { + if (h.getClassName() != null && h.getClassName().startsWith(JETTY_PACKAGE)) { + Class servlet = + ServletHolder.class + .getClassLoader() + .loadClass(h.getClassName()) + .asSubclass(Servlet.class); + h.setServlet(servlet.getConstructor().newInstance()); } + } } - @Override - protected ServletHandler newServletHandler() { - ServletHandler handler = new ServletHandler(); - handler.setAllowDuplicateMappings(true); - return handler; + /** + * Instantiate any registrations of a jetty provided filter + * + * @throws ReflectiveOperationException If a new instance of the filter cannot be instantiated + */ + private static void instantiateJettyFilters(ServletHandler servletHandler) + throws ReflectiveOperationException { + for (FilterHolder h : servletHandler.getFilters()) { + if (h.getClassName().startsWith(JETTY_PACKAGE)) { + Class filter = + ServletHolder.class + .getClassLoader() + .loadClass(h.getClassName()) + .asSubclass(Filter.class); + h.setFilter(filter.getConstructor().newInstance()); + } + } } /* Instantiate any jetty listeners from the container classloader */ - private void instantiateJettyListeners() throws ReflectiveOperationException { - ListenerHolder[] listeners = getServletHandler().getListeners(); + private static void instantiateJettyListeners(ServletHandler servletHandler) throws ReflectiveOperationException { + ListenerHolder[] listeners = servletHandler.getListeners(); if (listeners != null) { for (ListenerHolder h : listeners) { if (h.getClassName().startsWith(JETTY_PACKAGE)) { @@ -336,6 +282,39 @@ private void instantiateJettyListeners() throws ReflectiveOperationException { } } + @Override + public void doHandle( + String target, + org.eclipse.jetty.ee8.nested.Request baseRequest, + HttpServletRequest request, + HttpServletResponse response) + throws IOException, ServletException { + + ListIterator iter = requestListeners.listIterator(); + while (iter.hasNext()) { + iter.next().requestReceived(this, baseRequest); + } + try { + if (ignoreContentLength) { + response = new IgnoreContentLengthResponseWrapper(response); + } + + super.doHandle(target, baseRequest, request, response); + } finally { + // TODO: this finally approach is ok until async request handling is supported + while (iter.hasPrevious()) { + iter.previous().requestComplete(this, baseRequest); + } + } + } + + @Override + protected ServletHandler newServletHandler() { + ServletHandler handler = new ServletHandler(); + handler.setAllowDuplicateMappings(true); + return handler; + } + private void createTempDirectory() { File tempDir = getTempDirectory(); if (tempDir != null) { @@ -410,290 +389,4 @@ public void log(Exception exception, String msg) { log(msg, exception); } } - - /** A class to hold a Holder name and/or className and/or source location for matching. */ - private static class HolderMatcher { - final String name; - final String className; - - /** - * @param name The name of a filter/servlet to match, or null if not matching on name. - * @param className The class name of a filter/servlet to match, or null if not matching on - * className - */ - HolderMatcher(String name, String className) { - this.name = name; - this.className = className; - } - - /** - * @param holder The holder to match - * @return true IFF this matcher matches the holder. - */ - boolean appliesTo(Holder holder) { - if (name != null && !name.equals(holder.getName())) { - return false; - } - - if (className != null && !className.equals(holder.getClassName())) { - return false; - } - - return true; - } - } - - /** - * TrimmedServlets is in charge of handling web applications that got deployed previously with the - * previous webdefault.xml content(prior to this CL changing it). We still need to be able to load - * old apps defined with the previous webdefault.xml file that generated an obsolete - * quickstart.xml having the servlets defined in webdefault.xml. - * - *

New deployements would not need this processing (no-op), but we need to handle all apps, - * deployed now or in the past. - */ - private static class TrimmedServlets { - private final Map holders = new HashMap<>(); - private final List mappings = new ArrayList<>(); - - TrimmedServlets( - ServletHolder[] holders, ServletMapping[] mappings, Set deprecations) { - for (ServletHolder servletHolder : holders) { - boolean deprecated = false; - servletHolder.setAsyncSupported(APP_IS_ASYNC); - for (HolderMatcher holderMatcher : deprecations) { - deprecated |= holderMatcher.appliesTo(servletHolder); - } - - if (!deprecated) { - this.holders.put(servletHolder.getName(), servletHolder); - } - } - - for (ServletMapping m : mappings) { - this.mappings.add(m); - } - } - - /** - * Ensure the registration of a container provided servlet: - * - *

    - *
  • If any existing servlet registrations are for the passed servlet class, then their - * holder is updated with a new instance created on the containers classpath. - *
  • If a servlet registration for the passed servlet name does not exist, one is created to - * the passed servlet class. - *
- * - * @param name The servlet name - * @param servlet The servlet class - * @throws ReflectiveOperationException If a new instance of the servlet cannot be instantiated - */ - void ensure(String name, Class servlet) throws ReflectiveOperationException { - // Instantiate any holders referencing this servlet (may be application instances) - for (ServletHolder h : holders.values()) { - if (servlet.getName().equals(h.getClassName())) { - h.setServlet(servlet.getConstructor().newInstance()); - h.setAsyncSupported(APP_IS_ASYNC); - } - } - - // Look for (or instantiate) our named instance - ServletHolder holder = holders.get(name); - if (holder == null) { - holder = new ServletHolder(servlet.getConstructor().newInstance()); - holder.setInitOrder(1); - holder.setName(name); - holder.setAsyncSupported(APP_IS_ASYNC); - holders.put(name, holder); - } - } - - /** - * Ensure the registration of a container provided servlet: - * - *
    - *
  • If any existing servlet registrations are for the passed servlet class, then their - * holder is updated with a new instance created on the containers classpath. - *
  • If a servlet registration for the passed servlet name does not exist, one is created to - * the passed servlet class. - *
  • If a servlet mapping for the passed servlet name and pathSpec does not exist, one is - * created. - *
- * - * @param name The servlet name - * @param servlet The servlet class - * @param pathSpec The servlet pathspec - * @throws ReflectiveOperationException If a new instance of the servlet cannot be instantiated - */ - void ensure(String name, Class servlet, String pathSpec) - throws ReflectiveOperationException { - // Ensure Servlet - ensure(name, servlet); - - // Ensure mapping - if (pathSpec != null) { - boolean mapped = false; - for (ServletMapping mapping : mappings) { - if (mapping.containsPathSpec(pathSpec)) { - mapped = true; - break; - } - } - if (!mapped) { - ServletMapping mapping = new ServletMapping(); - mapping.setServletName(name); - mapping.setPathSpec(pathSpec); - if (pathSpec.equals("/")) { - mapping.setFromDefaultDescriptor(true); - } - mappings.add(mapping); - } - } - } - - /** - * Instantiate any registrations of a jetty provided servlet - * - * @throws ReflectiveOperationException If a new instance of the servlet cannot be instantiated - */ - void instantiateJettyServlets() throws ReflectiveOperationException { - for (ServletHolder h : holders.values()) { - if (h.getClassName() != null && h.getClassName().startsWith(JETTY_PACKAGE)) { - Class servlet = - ServletHolder.class - .getClassLoader() - .loadClass(h.getClassName()) - .asSubclass(Servlet.class); - h.setServlet(servlet.getConstructor().newInstance()); - } - } - } - - ServletHolder[] getHolders() { - return holders.values().toArray(new ServletHolder[0]); - } - - ServletMapping[] getMappings() { - List trimmed = new ArrayList<>(mappings.size()); - for (ServletMapping m : mappings) { - if (this.holders.containsKey(m.getServletName())) { - trimmed.add(m); - } - } - return trimmed.toArray(new ServletMapping[0]); - } - } - - private static class TrimmedFilters { - private final Map holders = new HashMap<>(); - private final List mappings = new ArrayList<>(); - - TrimmedFilters( - FilterHolder[] holders, FilterMapping[] mappings, Set deprecations) { - for (FilterHolder h : holders) { - boolean deprecated = false; - h.setAsyncSupported(APP_IS_ASYNC); - for (HolderMatcher m : deprecations) { - deprecated |= m.appliesTo(h); - } - - if (!deprecated) { - this.holders.put(h.getName(), h); - } - } - - for (FilterMapping m : mappings) { - this.mappings.add(m); - } - } - - /** - * Ensure the registration of a container provided filter: - * - *
    - *
  • If any existing filter registrations are for the passed filter class, then their holder - * is updated with a new instance created on the containers classpath. - *
  • If a filter registration for the passed filter name does not exist, one is created to - * the passed filter class. - *
  • If a filter mapping for the passed filter name and pathSpec does not exist, one is - * created. - *
- * - * @param name The filter name - * @param filter The filter class - * @param pathSpec The servlet pathspec - * @throws ReflectiveOperationException If a new instance of the servlet cannot be instantiated - */ - void ensure(String name, Class filter, String pathSpec) throws Exception { - - // Instantiate any holders referencing this filter (may be application instances) - for (FilterHolder h : holders.values()) { - if (filter.getName().equals(h.getClassName())) { - h.setFilter(filter.getConstructor().newInstance()); - h.setAsyncSupported(APP_IS_ASYNC); - } - } - - // Look for (or instantiate) our named instance - FilterHolder holder = holders.get(name); - if (holder == null) { - holder = new FilterHolder(filter.getConstructor().newInstance()); - holder.setName(name); - holders.put(name, holder); - holder.setAsyncSupported(APP_IS_ASYNC); - } - - // Ensure mapping - boolean mapped = false; - for (FilterMapping mapping : mappings) { - - for (String ps : mapping.getPathSpecs()) { - if (pathSpec.equals(ps) && name.equals(mapping.getFilterName())) { - mapped = true; - break; - } - } - } - if (!mapped) { - FilterMapping mapping = new FilterMapping(); - mapping.setFilterName(name); - mapping.setPathSpec(pathSpec); - mapping.setDispatches(FilterMapping.REQUEST); - mappings.add(mapping); - } - } - - /** - * Instantiate any registrations of a jetty provided filter - * - * @throws ReflectiveOperationException If a new instance of the filter cannot be instantiated - */ - void instantiateJettyFilters() throws ReflectiveOperationException { - for (FilterHolder h : holders.values()) { - if (h.getClassName().startsWith(JETTY_PACKAGE)) { - Class filter = - ServletHolder.class - .getClassLoader() - .loadClass(h.getClassName()) - .asSubclass(Filter.class); - h.setFilter(filter.getConstructor().newInstance()); - } - } - } - - FilterHolder[] getHolders() { - return holders.values().toArray(new FilterHolder[0]); - } - - FilterMapping[] getMappings() { - List trimmed = new ArrayList<>(mappings.size()); - for (FilterMapping m : mappings) { - if (this.holders.containsKey(m.getFilterName())) { - trimmed.add(m); - } - } - return trimmed.toArray(new FilterMapping[0]); - } - } }