From 94bafba6acda7bd31e35f8188c98d644dd01e1cf Mon Sep 17 00:00:00 2001 From: Lachlan Roberts Date: Fri, 9 Oct 2020 17:31:41 +1100 Subject: [PATCH 01/25] simplify the usage of WebSocketUpgradeFilter Signed-off-by: Lachlan Roberts --- .../eclipse/jetty/servlet/FilterHolder.java | 34 ++++++++++++++++++ .../util/server/WebSocketUpgradeFilter.java | 36 ++++++++++--------- .../server/internal/WebSocketMapping.java | 2 -- 3 files changed, 53 insertions(+), 19 deletions(-) diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/FilterHolder.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/FilterHolder.java index 579ae8dddb2..14dc3c94b74 100644 --- a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/FilterHolder.java +++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/FilterHolder.java @@ -36,6 +36,7 @@ import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.util.Loader; import org.eclipse.jetty.util.TypeUtil; import org.eclipse.jetty.util.component.Dumpable; import org.eclipse.jetty.util.component.DumpableCollection; @@ -221,6 +222,39 @@ public class FilterHolder extends Holder } } + /** + * Work out the class of the held {@link Filter} even before the {@link FilterHolder} has been started. + * @return the class of the held {@link Filter}, or null if not available. + */ + @SuppressWarnings("unchecked") + public Class getFilterClass() + { + if (_filter != null) + return _filter.getClass(); + + Filter filter = getInstance(); + if (filter != null) + return filter.getClass(); + + Class heldClass = getHeldClass(); + if (heldClass != null) + return heldClass; + + String className = getClassName(); + if (className != null) + { + try + { + return Loader.loadClass(className); + } + catch (ClassNotFoundException e) + { + LOG.warn("Could not load filter class", e); + } + } + return null; + } + @Override public void dump(Appendable out, String indent) throws IOException { diff --git a/jetty-websocket/websocket-util-server/src/main/java/org/eclipse/jetty/websocket/util/server/WebSocketUpgradeFilter.java b/jetty-websocket/websocket-util-server/src/main/java/org/eclipse/jetty/websocket/util/server/WebSocketUpgradeFilter.java index d756b0bcc18..a89d7e67a03 100644 --- a/jetty-websocket/websocket-util-server/src/main/java/org/eclipse/jetty/websocket/util/server/WebSocketUpgradeFilter.java +++ b/jetty-websocket/websocket-util-server/src/main/java/org/eclipse/jetty/websocket/util/server/WebSocketUpgradeFilter.java @@ -40,7 +40,6 @@ import org.eclipse.jetty.util.annotation.ManagedObject; import org.eclipse.jetty.util.component.Dumpable; import org.eclipse.jetty.util.thread.AutoLock; import org.eclipse.jetty.websocket.core.Configuration; -import org.eclipse.jetty.websocket.core.server.WebSocketServerComponents; import org.eclipse.jetty.websocket.util.server.internal.WebSocketMapping; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -78,34 +77,40 @@ public class WebSocketUpgradeFilter implements Filter, Dumpable private static final Logger LOG = LoggerFactory.getLogger(WebSocketUpgradeFilter.class); private static final AutoLock LOCK = new AutoLock(); + /** + * The init parameter name used to define {@link ServletContext} attribute used to share the {@link WebSocketMapping}. + */ + public static final String MAPPING_ATTRIBUTE_INIT_PARAM = "org.eclipse.jetty.websocket.util.server.internal.WebSocketMapping.key"; + + /** + * Return any {@link WebSocketUpgradeFilter} already present on the {@link ServletContext}. + * + * @param servletContext the {@link ServletContext} to use. + * @return the configured default {@link WebSocketUpgradeFilter} instance. + */ private static FilterHolder getFilter(ServletContext servletContext) { ContextHandler contextHandler = Objects.requireNonNull(ContextHandler.getContextHandler(servletContext)); ServletHandler servletHandler = contextHandler.getChildHandlerByClass(ServletHandler.class); - for (FilterHolder holder : servletHandler.getFilters()) { - if (holder.getInitParameter(MAPPING_ATTRIBUTE_INIT_PARAM) != null) + if (WebSocketUpgradeFilter.class.isAssignableFrom(holder.getFilterClass())) return holder; } - return null; } /** - * Configure the default WebSocketUpgradeFilter. - * - *

- * This will return the default {@link WebSocketUpgradeFilter} on the - * provided {@link ServletContext}, creating the filter if necessary. + * Ensure a {@link WebSocketUpgradeFilter} is available on the provided {@link ServletContext}, + * a new filter will added if one does not already exist. *

*

* The default {@link WebSocketUpgradeFilter} is also available via * the {@link ServletContext} attribute named {@code org.eclipse.jetty.websocket.server.WebSocketUpgradeFilter} *

* - * @param servletContext the {@link ServletContext} to use - * @return the configured default {@link WebSocketUpgradeFilter} instance + * @param servletContext the {@link ServletContext} to use. + * @return the configured default {@link WebSocketUpgradeFilter} instance. */ public static FilterHolder ensureFilter(ServletContext servletContext) { @@ -132,8 +137,6 @@ public class WebSocketUpgradeFilter implements Filter, Dumpable } } - public static final String MAPPING_ATTRIBUTE_INIT_PARAM = "org.eclipse.jetty.websocket.util.server.internal.WebSocketMapping.key"; - private final Configuration.ConfigurationCustomizer defaultCustomizer = new Configuration.ConfigurationCustomizer(); private WebSocketMapping mapping; @@ -174,10 +177,9 @@ public class WebSocketUpgradeFilter implements Filter, Dumpable final ServletContext context = config.getServletContext(); String mappingKey = config.getInitParameter(MAPPING_ATTRIBUTE_INIT_PARAM); - if (mappingKey != null) - mapping = WebSocketMapping.ensureMapping(context, mappingKey); - else - mapping = new WebSocketMapping(WebSocketServerComponents.ensureWebSocketComponents(context)); + if (mappingKey == null) + mappingKey = WebSocketMapping.DEFAULT_KEY; + mapping = WebSocketMapping.ensureMapping(context, mappingKey); String max = config.getInitParameter("idleTimeout"); if (max == null) diff --git a/jetty-websocket/websocket-util-server/src/main/java/org/eclipse/jetty/websocket/util/server/internal/WebSocketMapping.java b/jetty-websocket/websocket-util-server/src/main/java/org/eclipse/jetty/websocket/util/server/internal/WebSocketMapping.java index 49283dbf8e1..40d58edd2ec 100644 --- a/jetty-websocket/websocket-util-server/src/main/java/org/eclipse/jetty/websocket/util/server/internal/WebSocketMapping.java +++ b/jetty-websocket/websocket-util-server/src/main/java/org/eclipse/jetty/websocket/util/server/internal/WebSocketMapping.java @@ -63,7 +63,6 @@ public class WebSocketMapping implements Dumpable, LifeCycle.Listener public static WebSocketMapping getMapping(ServletContext servletContext, String mappingKey) { Object mappingObject = servletContext.getAttribute(mappingKey); - if (mappingObject != null) { if (mappingObject instanceof WebSocketMapping) @@ -86,7 +85,6 @@ public class WebSocketMapping implements Dumpable, LifeCycle.Listener public static WebSocketMapping ensureMapping(ServletContext servletContext, String mappingKey) { WebSocketMapping mapping = getMapping(servletContext, mappingKey); - if (mapping == null) { mapping = new WebSocketMapping(WebSocketServerComponents.ensureWebSocketComponents(servletContext)); From 2f295c126f3d82be535b2ceae54f0ac0ee839c8c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 30 Oct 2020 18:18:43 +0000 Subject: [PATCH 02/25] Bump mariadb-java-client from 2.6.0 to 2.7.0 Bumps [mariadb-java-client](https://github.com/mariadb-corporation/mariadb-connector-j) from 2.6.0 to 2.7.0. - [Release notes](https://github.com/mariadb-corporation/mariadb-connector-j/releases) - [Changelog](https://github.com/mariadb-corporation/mariadb-connector-j/blob/master/CHANGELOG.md) - [Commits](https://github.com/mariadb-corporation/mariadb-connector-j/compare/2.6.0...2.7.0) Signed-off-by: dependabot[bot] --- tests/test-sessions/test-jdbc-sessions/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test-sessions/test-jdbc-sessions/pom.xml b/tests/test-sessions/test-jdbc-sessions/pom.xml index d38ef82fb85..c325780f0e4 100644 --- a/tests/test-sessions/test-jdbc-sessions/pom.xml +++ b/tests/test-sessions/test-jdbc-sessions/pom.xml @@ -83,7 +83,7 @@ org.mariadb.jdbc mariadb-java-client - 2.6.0 + 2.7.0 test From 5d9c81a511d635b03beb3aa870945d6ff0e3fa30 Mon Sep 17 00:00:00 2001 From: Lachlan Roberts Date: Mon, 2 Nov 2020 15:44:14 +1100 Subject: [PATCH 03/25] revert to using init parameter to identify the WebSocketUpgradeFilter Signed-off-by: Lachlan Roberts --- .../eclipse/jetty/servlet/FilterHolder.java | 34 ------------------- .../util/server/WebSocketUpgradeFilter.java | 6 ++-- .../server/internal/WebSocketMapping.java | 2 +- 3 files changed, 4 insertions(+), 38 deletions(-) diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/FilterHolder.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/FilterHolder.java index 14dc3c94b74..579ae8dddb2 100644 --- a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/FilterHolder.java +++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/FilterHolder.java @@ -36,7 +36,6 @@ import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import org.eclipse.jetty.server.Request; -import org.eclipse.jetty.util.Loader; import org.eclipse.jetty.util.TypeUtil; import org.eclipse.jetty.util.component.Dumpable; import org.eclipse.jetty.util.component.DumpableCollection; @@ -222,39 +221,6 @@ public class FilterHolder extends Holder } } - /** - * Work out the class of the held {@link Filter} even before the {@link FilterHolder} has been started. - * @return the class of the held {@link Filter}, or null if not available. - */ - @SuppressWarnings("unchecked") - public Class getFilterClass() - { - if (_filter != null) - return _filter.getClass(); - - Filter filter = getInstance(); - if (filter != null) - return filter.getClass(); - - Class heldClass = getHeldClass(); - if (heldClass != null) - return heldClass; - - String className = getClassName(); - if (className != null) - { - try - { - return Loader.loadClass(className); - } - catch (ClassNotFoundException e) - { - LOG.warn("Could not load filter class", e); - } - } - return null; - } - @Override public void dump(Appendable out, String indent) throws IOException { diff --git a/jetty-websocket/websocket-util-server/src/main/java/org/eclipse/jetty/websocket/util/server/WebSocketUpgradeFilter.java b/jetty-websocket/websocket-util-server/src/main/java/org/eclipse/jetty/websocket/util/server/WebSocketUpgradeFilter.java index a89d7e67a03..176544be380 100644 --- a/jetty-websocket/websocket-util-server/src/main/java/org/eclipse/jetty/websocket/util/server/WebSocketUpgradeFilter.java +++ b/jetty-websocket/websocket-util-server/src/main/java/org/eclipse/jetty/websocket/util/server/WebSocketUpgradeFilter.java @@ -80,7 +80,7 @@ public class WebSocketUpgradeFilter implements Filter, Dumpable /** * The init parameter name used to define {@link ServletContext} attribute used to share the {@link WebSocketMapping}. */ - public static final String MAPPING_ATTRIBUTE_INIT_PARAM = "org.eclipse.jetty.websocket.util.server.internal.WebSocketMapping.key"; + public static final String MAPPING_ATTRIBUTE_INIT_PARAM = "jetty.websocket.WebSocketMapping"; /** * Return any {@link WebSocketUpgradeFilter} already present on the {@link ServletContext}. @@ -94,7 +94,7 @@ public class WebSocketUpgradeFilter implements Filter, Dumpable ServletHandler servletHandler = contextHandler.getChildHandlerByClass(ServletHandler.class); for (FilterHolder holder : servletHandler.getFilters()) { - if (WebSocketUpgradeFilter.class.isAssignableFrom(holder.getFilterClass())) + if (holder.getInitParameter(MAPPING_ATTRIBUTE_INIT_PARAM) != null) return holder; } return null; @@ -178,7 +178,7 @@ public class WebSocketUpgradeFilter implements Filter, Dumpable String mappingKey = config.getInitParameter(MAPPING_ATTRIBUTE_INIT_PARAM); if (mappingKey == null) - mappingKey = WebSocketMapping.DEFAULT_KEY; + throw new ServletException("the WebSocketMapping init param must be set"); mapping = WebSocketMapping.ensureMapping(context, mappingKey); String max = config.getInitParameter("idleTimeout"); diff --git a/jetty-websocket/websocket-util-server/src/main/java/org/eclipse/jetty/websocket/util/server/internal/WebSocketMapping.java b/jetty-websocket/websocket-util-server/src/main/java/org/eclipse/jetty/websocket/util/server/internal/WebSocketMapping.java index 40d58edd2ec..21ab0dad0fc 100644 --- a/jetty-websocket/websocket-util-server/src/main/java/org/eclipse/jetty/websocket/util/server/internal/WebSocketMapping.java +++ b/jetty-websocket/websocket-util-server/src/main/java/org/eclipse/jetty/websocket/util/server/internal/WebSocketMapping.java @@ -133,7 +133,7 @@ public class WebSocketMapping implements Dumpable, LifeCycle.Listener throw new IllegalArgumentException("Unrecognized path spec syntax [" + rawSpec + "]"); } - public static final String DEFAULT_KEY = "org.eclipse.jetty.websocket.util.server.internal.WebSocketMapping"; + public static final String DEFAULT_KEY = "jetty.websocket.defaultMapping"; private final PathMappings mappings = new PathMappings<>(); private final WebSocketComponents components; From b327992f75a949b2e245ddd188af0ac61cb313a4 Mon Sep 17 00:00:00 2001 From: Lachlan Roberts Date: Mon, 2 Nov 2020 15:57:58 +1100 Subject: [PATCH 04/25] Don't enforce the default WebSocketMapping. If a user wants to use the WebSocketUpgradeFilter with a different WebSocketMapping, they can add the ServerContainer manually and put their own WebSocketMapping in. Signed-off-by: Lachlan Roberts --- .../server/JettyWebSocketServerContainer.java | 11 +++++++++-- .../JettyWebSocketServletContainerInitializer.java | 9 ++++----- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/jetty-websocket/websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/JettyWebSocketServerContainer.java b/jetty-websocket/websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/JettyWebSocketServerContainer.java index 52c2d6b2f68..a3843136502 100644 --- a/jetty-websocket/websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/JettyWebSocketServerContainer.java +++ b/jetty-websocket/websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/JettyWebSocketServerContainer.java @@ -91,6 +91,7 @@ public class JettyWebSocketServerContainer extends ContainerLifeCycle implements private final ServletContextHandler contextHandler; private final WebSocketMapping webSocketMapping; + private final WebSocketComponents components; private final FrameHandlerFactory frameHandlerFactory; private final Executor executor; private final Configuration.ConfigurationCustomizer customizer = new Configuration.ConfigurationCustomizer(); @@ -102,14 +103,15 @@ public class JettyWebSocketServerContainer extends ContainerLifeCycle implements * Main entry point for {@link JettyWebSocketServletContainerInitializer}. * * @param webSocketMapping the {@link WebSocketMapping} that this container belongs to - * @param webSocketComponents the {@link WebSocketComponents} instance to use + * @param components the {@link WebSocketComponents} instance to use * @param executor the {@link Executor} to use */ - JettyWebSocketServerContainer(ServletContextHandler contextHandler, WebSocketMapping webSocketMapping, WebSocketComponents webSocketComponents, Executor executor) + JettyWebSocketServerContainer(ServletContextHandler contextHandler, WebSocketMapping webSocketMapping, WebSocketComponents components, Executor executor) { this.contextHandler = contextHandler; this.webSocketMapping = webSocketMapping; this.executor = executor; + this.components = components; // Ensure there is a FrameHandlerFactory JettyServerFrameHandlerFactory factory = contextHandler.getBean(JettyServerFrameHandlerFactory.class); @@ -155,6 +157,11 @@ public class JettyWebSocketServerContainer extends ContainerLifeCycle implements }); } + public WebSocketComponents getWebSocketComponents() + { + return components; + } + @Override public Executor getExecutor() { diff --git a/jetty-websocket/websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/config/JettyWebSocketServletContainerInitializer.java b/jetty-websocket/websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/config/JettyWebSocketServletContainerInitializer.java index 4740fbc75f8..b7a79ec8df7 100644 --- a/jetty-websocket/websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/config/JettyWebSocketServletContainerInitializer.java +++ b/jetty-websocket/websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/config/JettyWebSocketServletContainerInitializer.java @@ -88,12 +88,9 @@ public class JettyWebSocketServletContainerInitializer implements ServletContain */ private static JettyWebSocketServerContainer initialize(ServletContextHandler context) { - WebSocketComponents components = WebSocketServerComponents.ensureWebSocketComponents(context.getServletContext()); - WebSocketMapping mapping = WebSocketMapping.ensureMapping(context.getServletContext(), WebSocketMapping.DEFAULT_KEY); JettyWebSocketServerContainer container = JettyWebSocketServerContainer.ensureContainer(context.getServletContext()); - if (LOG.isDebugEnabled()) - LOG.debug("configureContext {} {} {}", container, mapping, components); + LOG.debug("initialize {}", container); return container; } @@ -102,6 +99,8 @@ public class JettyWebSocketServletContainerInitializer implements ServletContain public void onStartup(Set> c, ServletContext context) { ServletContextHandler contextHandler = ServletContextHandler.getServletContextHandler(context, "Jetty WebSocket SCI"); - JettyWebSocketServletContainerInitializer.initialize(contextHandler); + JettyWebSocketServerContainer container = JettyWebSocketServletContainerInitializer.initialize(contextHandler); + if (LOG.isDebugEnabled()) + LOG.debug("onStartup {}", container); } } From 10243b675e97ba3959e3593b8474f7d5b300a1ae Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Nov 2020 06:38:03 +0000 Subject: [PATCH 05/25] Bump org.eclipse.osgi from 3.6.0.v20100517 to 3.7.1 Bumps org.eclipse.osgi from 3.6.0.v20100517 to 3.7.1. Signed-off-by: dependabot[bot] --- jetty-osgi/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jetty-osgi/pom.xml b/jetty-osgi/pom.xml index 25b05e8b809..e84d255911a 100644 --- a/jetty-osgi/pom.xml +++ b/jetty-osgi/pom.xml @@ -12,7 +12,7 @@ pom - 3.6.0.v20100517 + 3.7.1 3.2.100.v20100503 1.0.0-v20070606 From 9b4b52df59cb3130c1030ea7b127e813fe3fa626 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Nov 2020 06:40:10 +0000 Subject: [PATCH 06/25] Bump pax-url-wrap from 2.5.2 to 2.6.1 Bumps [pax-url-wrap](https://github.com/ops4j/org.ops4j.pax.url) from 2.5.2 to 2.6.1. - [Release notes](https://github.com/ops4j/org.ops4j.pax.url/releases) - [Commits](https://github.com/ops4j/org.ops4j.pax.url/compare/url-2.5.2...url-2.6.1) Signed-off-by: dependabot[bot] --- jetty-osgi/test-jetty-osgi/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jetty-osgi/test-jetty-osgi/pom.xml b/jetty-osgi/test-jetty-osgi/pom.xml index 6552794ddec..1ef3836b1ce 100644 --- a/jetty-osgi/test-jetty-osgi/pom.xml +++ b/jetty-osgi/test-jetty-osgi/pom.xml @@ -71,7 +71,7 @@ org.ops4j.pax.url pax-url-wrap - 2.5.2 + 2.6.1 test From 365bab153c14c5895e7fab284a6e3182c432e816 Mon Sep 17 00:00:00 2001 From: gregw Date: Mon, 2 Nov 2020 10:19:08 +0100 Subject: [PATCH 07/25] Fixes #5555 NPE if Filter of named servlet Fixed #5555 NPE if there is a filter with a servlet name mapping, but a request is received for a servlet without a name match. Added more simple tests for servlet and filter mappings --- .../eclipse/jetty/servlet/ServletHandler.java | 10 +- .../jetty/servlet/ServletHandlerTest.java | 118 ++++++++++++++++++ 2 files changed, 125 insertions(+), 3 deletions(-) diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHandler.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHandler.java index ba41caf3285..bd06cb7bd1e 100644 --- a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHandler.java +++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHandler.java @@ -614,10 +614,14 @@ public class ServletHandler extends ScopedHandler for (FilterMapping mapping : _wildFilterNameMappings) chain = newFilterChain(mapping.getFilterHolder(), chain == null ? new ChainEnd(servletHolder) : chain); - for (FilterMapping mapping : _filterNameMappings.get(servletHolder.getName())) + List nameMappings = _filterNameMappings.get(servletHolder.getName()); + if (nameMappings != null) { - if (mapping.appliesTo(dispatch)) - chain = newFilterChain(mapping.getFilterHolder(), chain == null ? new ChainEnd(servletHolder) : chain); + for (FilterMapping mapping : nameMappings) + { + if (mapping.appliesTo(dispatch)) + chain = newFilterChain(mapping.getFilterHolder(), chain == null ? new ChainEnd(servletHolder) : chain); + } } } diff --git a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/ServletHandlerTest.java b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/ServletHandlerTest.java index 29dc90ad1c1..5bddd101f30 100644 --- a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/ServletHandlerTest.java +++ b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/ServletHandlerTest.java @@ -18,18 +18,29 @@ package org.eclipse.jetty.servlet; +import java.io.IOException; import java.util.ArrayList; import java.util.EnumSet; import java.util.List; import javax.servlet.DispatcherType; +import javax.servlet.Filter; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSessionEvent; import javax.servlet.http.HttpSessionListener; import org.eclipse.jetty.http.pathmap.MappedResource; +import org.eclipse.jetty.server.LocalConnector; +import org.eclipse.jetty.server.Server; import org.eclipse.jetty.util.component.Container; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -730,4 +741,111 @@ public class ServletHandlerTest assertTrue(removeResults.contains(sh1)); assertTrue(removeResults.contains(lh1)); } + + @Test + public void testServletMappings() throws Exception + { + Server server = new Server(); + ServletHandler handler = new ServletHandler(); + server.setHandler(handler); + for (final String mapping : new String[] {"/", "/foo", "/bar/*", "*.bob"}) + { + handler.addServletWithMapping(new ServletHolder(new HttpServlet() + { + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException + { + resp.getOutputStream().println("mapping='" + mapping + "'"); + } + }), mapping); + } + // add servlet with no mapping + handler.addServlet(new ServletHolder(new HttpServlet() {})); + + LocalConnector connector = new LocalConnector(server); + server.addConnector(connector); + + server.start(); + + assertThat(connector.getResponse("GET /default HTTP/1.0\r\n\r\n"), containsString("mapping='/'")); + assertThat(connector.getResponse("GET /foo HTTP/1.0\r\n\r\n"), containsString("mapping='/foo'")); + assertThat(connector.getResponse("GET /bar HTTP/1.0\r\n\r\n"), containsString("mapping='/bar/*'")); + assertThat(connector.getResponse("GET /bar/bob HTTP/1.0\r\n\r\n"), containsString("mapping='/bar/*'")); + assertThat(connector.getResponse("GET /bar/foo.bob HTTP/1.0\r\n\r\n"), containsString("mapping='/bar/*'")); + assertThat(connector.getResponse("GET /other/foo.bob HTTP/1.0\r\n\r\n"), containsString("mapping='*.bob'")); + } + + @Test + public void testFilterMappings() throws Exception + { + Server server = new Server(); + ServletHandler handler = new ServletHandler(); + server.setHandler(handler); + + ServletHolder foo = new ServletHolder(new HttpServlet() + { + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException + { + resp.getOutputStream().println("FOO"); + } + }); + foo.setName("foo"); + handler.addServletWithMapping(foo, "/foo/*"); + + ServletHolder def = new ServletHolder(new HttpServlet() + { + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException + { + resp.getOutputStream().println("default"); + } + }); + def.setName("default"); + handler.addServletWithMapping(def, "/"); + + for (final String mapping : new String[]{"/*", "/foo", "/bar/*", "*.bob"}) + { + handler.addFilterWithMapping(new FilterHolder((TestFilter)(request, response, chain) -> + { + response.getOutputStream().print("path-" + mapping + "-"); + chain.doFilter(request, response); + }), mapping, EnumSet.of(DispatcherType.REQUEST)); + } + + FilterHolder fooFilter = new FilterHolder((TestFilter)(request, response, chain) -> + { + response.getOutputStream().print("name-foo-"); + chain.doFilter(request, response); + }); + fooFilter.setName("fooFilter"); + FilterMapping named = new FilterMapping(); + named.setFilterHolder(fooFilter); + named.setServletName("foo"); + handler.addFilter(fooFilter, named); + + LocalConnector connector = new LocalConnector(server); + server.addConnector(connector); + + server.start(); + + assertThat(connector.getResponse("GET /default HTTP/1.0\r\n\r\n"), containsString("path-/*-default")); + assertThat(connector.getResponse("GET /foo HTTP/1.0\r\n\r\n"), containsString("path-/*-path-/foo-name-foo-FOO")); + assertThat(connector.getResponse("GET /foo/bar HTTP/1.0\r\n\r\n"), containsString("path-/*-name-foo-FOO")); + assertThat(connector.getResponse("GET /foo/bar.bob HTTP/1.0\r\n\r\n"), containsString("path-/*-path-*.bob-name-foo-FOO")); + assertThat(connector.getResponse("GET /other.bob HTTP/1.0\r\n\r\n"), containsString("path-/*-path-*.bob-default")); + } + + private interface TestFilter extends Filter + { + default void init(FilterConfig filterConfig) throws ServletException + { + } + + @Override + default void destroy() + { + } + } + } From 4c49ca5184dfc45a4e670c9d740c07e1b08792d9 Mon Sep 17 00:00:00 2001 From: gregw Date: Mon, 2 Nov 2020 14:08:46 +0100 Subject: [PATCH 08/25] More cleanup for #5555 Some more cleanup for #5555 --- .../eclipse/jetty/servlet/ServletHandler.java | 21 +++++-------------- 1 file changed, 5 insertions(+), 16 deletions(-) diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHandler.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHandler.java index bd06cb7bd1e..4f29e4e76d8 100644 --- a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHandler.java +++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHandler.java @@ -521,21 +521,8 @@ public class ServletHandler extends ScopedHandler FilterChain chain = null; // find the servlet - if (target.startsWith("/")) - { - if (servletHolder != null && _filterMappings != null && _filterMappings.length > 0) - chain = getFilterChain(baseRequest, target, servletHolder); - } - else - { - if (servletHolder != null) - { - if (_filterMappings != null && _filterMappings.length > 0) - { - chain = getFilterChain(baseRequest, null, servletHolder); - } - } - } + if (servletHolder != null && _filterMappings != null && _filterMappings.length > 0) + chain = getFilterChain(baseRequest, target.startsWith("/") ? target : null, servletHolder); if (LOG.isDebugEnabled()) LOG.debug("chain={}", chain); @@ -593,6 +580,7 @@ public class ServletHandler extends ScopedHandler protected FilterChain getFilterChain(Request baseRequest, String pathInContext, ServletHolder servletHolder) { + Objects.requireNonNull(servletHolder); String key = pathInContext == null ? servletHolder.getName() : pathInContext; int dispatch = FilterMapping.dispatch(baseRequest.getDispatcherType()); @@ -608,7 +596,7 @@ public class ServletHandler extends ScopedHandler // The mappings lists have been reversed to make this simple and fast. FilterChain chain = null; - if (servletHolder != null && _filterNameMappings != null && !_filterNameMappings.isEmpty()) + if (_filterNameMappings != null && !_filterNameMappings.isEmpty()) { if (_wildFilterNameMappings != null) for (FilterMapping mapping : _wildFilterNameMappings) @@ -1626,6 +1614,7 @@ public class ServletHandler extends ScopedHandler ChainEnd(ServletHolder holder) { + Objects.requireNonNull(holder); _servletHolder = holder; } From e46af88704a893fc12cb0e3bf46e2c7b48a009e7 Mon Sep 17 00:00:00 2001 From: Joakim Erdfelt Date: Mon, 2 Nov 2020 08:03:51 -0600 Subject: [PATCH 09/25] Updating to version 9.4.34.v20201102 --- VERSION.txt | 11 +- aggregates/jetty-all-compact3/pom.xml | 2 +- aggregates/jetty-all/pom.xml | 2 +- apache-jsp/pom.xml | 2 +- apache-jstl/pom.xml | 2 +- build-resources/pom.xml | 2 +- examples/async-rest/async-rest-jar/pom.xml | 2 +- examples/async-rest/async-rest-webapp/pom.xml | 2 +- examples/async-rest/pom.xml | 2 +- examples/embedded/pom.xml | 2 +- examples/pom.xml | 2 +- jetty-alpn/jetty-alpn-client/pom.xml | 2 +- .../jetty-alpn-conscrypt-client/pom.xml | 2 +- .../jetty-alpn-conscrypt-server/pom.xml | 2 +- jetty-alpn/jetty-alpn-java-client/pom.xml | 2 +- jetty-alpn/jetty-alpn-java-server/pom.xml | 2 +- jetty-alpn/jetty-alpn-openjdk8-client/pom.xml | 2 +- jetty-alpn/jetty-alpn-openjdk8-server/pom.xml | 2 +- jetty-alpn/jetty-alpn-server/pom.xml | 2 +- jetty-alpn/pom.xml | 2 +- jetty-annotations/pom.xml | 2 +- jetty-ant/pom.xml | 2 +- jetty-bom/pom.xml | 134 +++++++++--------- jetty-cdi/pom.xml | 2 +- jetty-client/pom.xml | 2 +- jetty-continuation/pom.xml | 2 +- jetty-deploy/pom.xml | 2 +- jetty-distribution/pom.xml | 2 +- jetty-documentation/pom.xml | 2 +- jetty-fcgi/fcgi-client/pom.xml | 2 +- jetty-fcgi/fcgi-server/pom.xml | 2 +- jetty-fcgi/pom.xml | 2 +- .../jetty-gcloud-session-manager/pom.xml | 2 +- jetty-gcloud/pom.xml | 2 +- jetty-hazelcast/pom.xml | 2 +- jetty-home/pom.xml | 2 +- jetty-http-spi/pom.xml | 2 +- jetty-http/pom.xml | 2 +- jetty-http2/http2-alpn-tests/pom.xml | 2 +- jetty-http2/http2-client/pom.xml | 2 +- jetty-http2/http2-common/pom.xml | 2 +- jetty-http2/http2-hpack/pom.xml | 2 +- .../http2-http-client-transport/pom.xml | 2 +- jetty-http2/http2-server/pom.xml | 2 +- jetty-http2/pom.xml | 2 +- jetty-infinispan/infinispan-common/pom.xml | 2 +- .../infinispan-embedded-query/pom.xml | 2 +- jetty-infinispan/infinispan-embedded/pom.xml | 2 +- .../infinispan-remote-query/pom.xml | 2 +- jetty-infinispan/infinispan-remote/pom.xml | 2 +- jetty-infinispan/pom.xml | 2 +- jetty-io/pom.xml | 2 +- jetty-jaas/pom.xml | 2 +- jetty-jaspi/pom.xml | 2 +- jetty-jmh/pom.xml | 2 +- jetty-jmx/pom.xml | 2 +- jetty-jndi/pom.xml | 2 +- jetty-jspc-maven-plugin/pom.xml | 2 +- jetty-maven-plugin/pom.xml | 2 +- .../jetty-memcached-sessions/pom.xml | 2 +- jetty-memcached/pom.xml | 2 +- jetty-nosql/pom.xml | 2 +- jetty-openid/pom.xml | 2 +- jetty-osgi/jetty-osgi-alpn/pom.xml | 2 +- jetty-osgi/jetty-osgi-boot-jsp/pom.xml | 2 +- jetty-osgi/jetty-osgi-boot-warurl/pom.xml | 2 +- jetty-osgi/jetty-osgi-boot/pom.xml | 2 +- jetty-osgi/jetty-osgi-httpservice/pom.xml | 2 +- jetty-osgi/pom.xml | 2 +- jetty-osgi/test-jetty-osgi-context/pom.xml | 2 +- jetty-osgi/test-jetty-osgi-fragment/pom.xml | 2 +- jetty-osgi/test-jetty-osgi-server/pom.xml | 2 +- .../test-jetty-osgi-webapp-resources/pom.xml | 2 +- jetty-osgi/test-jetty-osgi-webapp/pom.xml | 2 +- jetty-osgi/test-jetty-osgi/pom.xml | 2 +- jetty-plus/pom.xml | 2 +- jetty-proxy/pom.xml | 2 +- jetty-quickstart/pom.xml | 2 +- jetty-rewrite/pom.xml | 2 +- jetty-runner/pom.xml | 2 +- jetty-security/pom.xml | 2 +- jetty-server/pom.xml | 2 +- jetty-servlet/pom.xml | 2 +- jetty-servlets/pom.xml | 2 +- jetty-spring/pom.xml | 2 +- jetty-start/pom.xml | 2 +- jetty-unixsocket/pom.xml | 2 +- jetty-util-ajax/pom.xml | 2 +- jetty-util/pom.xml | 2 +- jetty-webapp/pom.xml | 2 +- .../javax-websocket-client-impl/pom.xml | 2 +- .../javax-websocket-server-impl/pom.xml | 2 +- jetty-websocket/jetty-websocket-tests/pom.xml | 2 +- jetty-websocket/pom.xml | 2 +- jetty-websocket/websocket-api/pom.xml | 2 +- jetty-websocket/websocket-client/pom.xml | 2 +- jetty-websocket/websocket-common/pom.xml | 2 +- jetty-websocket/websocket-server/pom.xml | 2 +- jetty-websocket/websocket-servlet/pom.xml | 2 +- jetty-xml/pom.xml | 2 +- pom.xml | 2 +- tests/pom.xml | 2 +- tests/test-continuation/pom.xml | 2 +- tests/test-distribution/pom.xml | 2 +- tests/test-http-client-transport/pom.xml | 2 +- tests/test-integration/pom.xml | 2 +- tests/test-jmx/jmx-webapp-it/pom.xml | 2 +- tests/test-jmx/jmx-webapp/pom.xml | 2 +- tests/test-jmx/pom.xml | 2 +- tests/test-loginservice/pom.xml | 2 +- tests/test-quickstart/pom.xml | 2 +- tests/test-sessions/pom.xml | 2 +- .../test-sessions/test-file-sessions/pom.xml | 2 +- .../test-gcloud-sessions/pom.xml | 2 +- .../test-hazelcast-sessions/pom.xml | 2 +- .../test-infinispan-sessions/pom.xml | 2 +- .../test-sessions/test-jdbc-sessions/pom.xml | 2 +- .../test-memcached-sessions/pom.xml | 2 +- .../test-mongodb-sessions/pom.xml | 2 +- .../test-sessions-common/pom.xml | 2 +- tests/test-webapps/pom.xml | 2 +- .../test-cdi-common-webapp/pom.xml | 2 +- tests/test-webapps/test-felix-webapp/pom.xml | 2 +- tests/test-webapps/test-http2-webapp/pom.xml | 2 +- tests/test-webapps/test-jaas-webapp/pom.xml | 2 +- tests/test-webapps/test-jetty-webapp/pom.xml | 2 +- tests/test-webapps/test-jndi-webapp/pom.xml | 2 +- .../test-webapps/test-mock-resources/pom.xml | 2 +- .../test-webapps/test-owb-cdi-webapp/pom.xml | 2 +- tests/test-webapps/test-proxy-webapp/pom.xml | 2 +- tests/test-webapps/test-servlet-spec/pom.xml | 2 +- .../test-container-initializer/pom.xml | 2 +- .../test-spec-webapp/pom.xml | 2 +- .../test-web-fragment/pom.xml | 2 +- tests/test-webapps/test-simple-webapp/pom.xml | 2 +- .../test-webapps/test-webapp-rfc2616/pom.xml | 2 +- .../pom.xml | 2 +- .../test-websocket-client-webapp/pom.xml | 2 +- .../test-webapps/test-weld-cdi-webapp/pom.xml | 2 +- 139 files changed, 213 insertions(+), 206 deletions(-) diff --git a/VERSION.txt b/VERSION.txt index 4aba2b75352..b616a9a89ec 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1,4 +1,11 @@ -jetty-9.4.34-SNAPSHOT +jetty-9.4.34.v20201102 - 02 November 2020 + + 5320 Using WebSocketClient with jetty-websocket-httpclient.xml in a Jetty + web application causes ClassCastException + + 5488 jetty-dir.css not found when using JPMS + + 5498 ServletHolder lifecycle correctness + + 5521 ResourceCollection NPE in list() + + 5535 Support regex in SslContextFactory include/exclude of protocols + + 5555 NPE for servlet with no mapping jetty-9.4.33.v20201020 - 20 October 2020 + 5022 Cleanup ServletHandler, specifically with respect to making filter @@ -15,7 +22,7 @@ jetty-9.4.33.v20201020 - 20 October 2020 + 5451 Improve Working Directory creation + 5454 Request error context is not reset + 5475 Update to spifly 1.3.2 and asm 9 - + 5480 NPE from WebInfConfiguration.deconfigure during WebAppContext shutdown + + 5480 NPE from WebInfConfiguration.deconfigure during WebAppContext shutdown jetty-9.4.32.v20200930 - 30 September 2020 + 2796 HTTP/2 max local stream count exceeded when request fails diff --git a/aggregates/jetty-all-compact3/pom.xml b/aggregates/jetty-all-compact3/pom.xml index a8bb9cc1025..8220204de21 100644 --- a/aggregates/jetty-all-compact3/pom.xml +++ b/aggregates/jetty-all-compact3/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.34-SNAPSHOT + 9.4.34.v20201102 ../../pom.xml 4.0.0 diff --git a/aggregates/jetty-all/pom.xml b/aggregates/jetty-all/pom.xml index c43ae8961a3..4ea53b077a4 100644 --- a/aggregates/jetty-all/pom.xml +++ b/aggregates/jetty-all/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.34-SNAPSHOT + 9.4.34.v20201102 ../../pom.xml 4.0.0 diff --git a/apache-jsp/pom.xml b/apache-jsp/pom.xml index 7267eabffc7..f0c948c8609 100644 --- a/apache-jsp/pom.xml +++ b/apache-jsp/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.34-SNAPSHOT + 9.4.34.v20201102 4.0.0 apache-jsp diff --git a/apache-jstl/pom.xml b/apache-jstl/pom.xml index dcaaa3e2456..8326711ad5b 100644 --- a/apache-jstl/pom.xml +++ b/apache-jstl/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.34-SNAPSHOT + 9.4.34.v20201102 4.0.0 apache-jstl diff --git a/build-resources/pom.xml b/build-resources/pom.xml index a029c7b7d24..bc516653a03 100644 --- a/build-resources/pom.xml +++ b/build-resources/pom.xml @@ -2,7 +2,7 @@ 4.0.0 org.eclipse.jetty build-resources - 9.4.34-SNAPSHOT + 9.4.34.v20201102 jar Jetty :: Build Resources diff --git a/examples/async-rest/async-rest-jar/pom.xml b/examples/async-rest/async-rest-jar/pom.xml index ed8d7115d62..215bd7f1055 100644 --- a/examples/async-rest/async-rest-jar/pom.xml +++ b/examples/async-rest/async-rest-jar/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty example-async-rest - 9.4.34-SNAPSHOT + 9.4.34.v20201102 4.0.0 diff --git a/examples/async-rest/async-rest-webapp/pom.xml b/examples/async-rest/async-rest-webapp/pom.xml index 66aaef0c0c9..9e4568f19b0 100644 --- a/examples/async-rest/async-rest-webapp/pom.xml +++ b/examples/async-rest/async-rest-webapp/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty example-async-rest - 9.4.34-SNAPSHOT + 9.4.34.v20201102 4.0.0 diff --git a/examples/async-rest/pom.xml b/examples/async-rest/pom.xml index 6e493913177..1cc07209b7f 100644 --- a/examples/async-rest/pom.xml +++ b/examples/async-rest/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty.examples examples-parent - 9.4.34-SNAPSHOT + 9.4.34.v20201102 4.0.0 diff --git a/examples/embedded/pom.xml b/examples/embedded/pom.xml index 73c32602035..623cd669d3b 100644 --- a/examples/embedded/pom.xml +++ b/examples/embedded/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty.examples examples-parent - 9.4.34-SNAPSHOT + 9.4.34.v20201102 ../pom.xml 4.0.0 diff --git a/examples/pom.xml b/examples/pom.xml index 70fb91b7f62..da6bd76972c 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty jetty-project - 9.4.34-SNAPSHOT + 9.4.34.v20201102 4.0.0 diff --git a/jetty-alpn/jetty-alpn-client/pom.xml b/jetty-alpn/jetty-alpn-client/pom.xml index 49c5de24037..182b11be917 100644 --- a/jetty-alpn/jetty-alpn-client/pom.xml +++ b/jetty-alpn/jetty-alpn-client/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-alpn-parent - 9.4.34-SNAPSHOT + 9.4.34.v20201102 4.0.0 jetty-alpn-client diff --git a/jetty-alpn/jetty-alpn-conscrypt-client/pom.xml b/jetty-alpn/jetty-alpn-conscrypt-client/pom.xml index 11709632c58..d970721525b 100644 --- a/jetty-alpn/jetty-alpn-conscrypt-client/pom.xml +++ b/jetty-alpn/jetty-alpn-conscrypt-client/pom.xml @@ -4,7 +4,7 @@ org.eclipse.jetty jetty-alpn-parent - 9.4.34-SNAPSHOT + 9.4.34.v20201102 4.0.0 diff --git a/jetty-alpn/jetty-alpn-conscrypt-server/pom.xml b/jetty-alpn/jetty-alpn-conscrypt-server/pom.xml index d3db84f311e..f141fb5296f 100644 --- a/jetty-alpn/jetty-alpn-conscrypt-server/pom.xml +++ b/jetty-alpn/jetty-alpn-conscrypt-server/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty jetty-alpn-parent - 9.4.34-SNAPSHOT + 9.4.34.v20201102 4.0.0 diff --git a/jetty-alpn/jetty-alpn-java-client/pom.xml b/jetty-alpn/jetty-alpn-java-client/pom.xml index d58eb8c1a4d..671287e7c15 100644 --- a/jetty-alpn/jetty-alpn-java-client/pom.xml +++ b/jetty-alpn/jetty-alpn-java-client/pom.xml @@ -4,7 +4,7 @@ org.eclipse.jetty jetty-alpn-parent - 9.4.34-SNAPSHOT + 9.4.34.v20201102 4.0.0 diff --git a/jetty-alpn/jetty-alpn-java-server/pom.xml b/jetty-alpn/jetty-alpn-java-server/pom.xml index c65fba0db72..580cef0929e 100644 --- a/jetty-alpn/jetty-alpn-java-server/pom.xml +++ b/jetty-alpn/jetty-alpn-java-server/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty jetty-alpn-parent - 9.4.34-SNAPSHOT + 9.4.34.v20201102 4.0.0 diff --git a/jetty-alpn/jetty-alpn-openjdk8-client/pom.xml b/jetty-alpn/jetty-alpn-openjdk8-client/pom.xml index 03a0d465d52..9406aa37f9f 100644 --- a/jetty-alpn/jetty-alpn-openjdk8-client/pom.xml +++ b/jetty-alpn/jetty-alpn-openjdk8-client/pom.xml @@ -4,7 +4,7 @@ org.eclipse.jetty jetty-alpn-parent - 9.4.34-SNAPSHOT + 9.4.34.v20201102 4.0.0 diff --git a/jetty-alpn/jetty-alpn-openjdk8-server/pom.xml b/jetty-alpn/jetty-alpn-openjdk8-server/pom.xml index b3970e41928..7b1c5e680de 100644 --- a/jetty-alpn/jetty-alpn-openjdk8-server/pom.xml +++ b/jetty-alpn/jetty-alpn-openjdk8-server/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty jetty-alpn-parent - 9.4.34-SNAPSHOT + 9.4.34.v20201102 4.0.0 diff --git a/jetty-alpn/jetty-alpn-server/pom.xml b/jetty-alpn/jetty-alpn-server/pom.xml index 124ad095a56..2f387f701f1 100644 --- a/jetty-alpn/jetty-alpn-server/pom.xml +++ b/jetty-alpn/jetty-alpn-server/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-alpn-parent - 9.4.34-SNAPSHOT + 9.4.34.v20201102 4.0.0 jetty-alpn-server diff --git a/jetty-alpn/pom.xml b/jetty-alpn/pom.xml index 1b60f1edd6d..04fc37fa8ca 100644 --- a/jetty-alpn/pom.xml +++ b/jetty-alpn/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.34-SNAPSHOT + 9.4.34.v20201102 4.0.0 jetty-alpn-parent diff --git a/jetty-annotations/pom.xml b/jetty-annotations/pom.xml index 74bcfa9051c..82bffad63f1 100644 --- a/jetty-annotations/pom.xml +++ b/jetty-annotations/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.34-SNAPSHOT + 9.4.34.v20201102 4.0.0 jetty-annotations diff --git a/jetty-ant/pom.xml b/jetty-ant/pom.xml index 3231c89d015..a9536884966 100644 --- a/jetty-ant/pom.xml +++ b/jetty-ant/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.34-SNAPSHOT + 9.4.34.v20201102 4.0.0 jetty-ant diff --git a/jetty-bom/pom.xml b/jetty-bom/pom.xml index dd79bb0eeed..20e2fcba358 100644 --- a/jetty-bom/pom.xml +++ b/jetty-bom/pom.xml @@ -9,7 +9,7 @@ org.eclipse.jetty jetty-project - 9.4.34-SNAPSHOT + 9.4.34.v20201102 @@ -53,336 +53,336 @@ org.eclipse.jetty apache-jsp - 9.4.34-SNAPSHOT + 9.4.34.v20201102 org.eclipse.jetty apache-jstl - 9.4.34-SNAPSHOT + 9.4.34.v20201102 org.eclipse.jetty jetty-alpn-client - 9.4.34-SNAPSHOT + 9.4.34.v20201102 org.eclipse.jetty jetty-alpn-java-client - 9.4.34-SNAPSHOT + 9.4.34.v20201102 org.eclipse.jetty jetty-alpn-java-server - 9.4.34-SNAPSHOT + 9.4.34.v20201102 org.eclipse.jetty jetty-alpn-openjdk8-client - 9.4.34-SNAPSHOT + 9.4.34.v20201102 org.eclipse.jetty jetty-alpn-openjdk8-server - 9.4.34-SNAPSHOT + 9.4.34.v20201102 org.eclipse.jetty jetty-alpn-conscrypt-client - 9.4.34-SNAPSHOT + 9.4.34.v20201102 org.eclipse.jetty jetty-alpn-conscrypt-server - 9.4.34-SNAPSHOT + 9.4.34.v20201102 org.eclipse.jetty jetty-alpn-server - 9.4.34-SNAPSHOT + 9.4.34.v20201102 org.eclipse.jetty jetty-annotations - 9.4.34-SNAPSHOT + 9.4.34.v20201102 org.eclipse.jetty jetty-ant - 9.4.34-SNAPSHOT + 9.4.34.v20201102 org.eclipse.jetty jetty-client - 9.4.34-SNAPSHOT + 9.4.34.v20201102 org.eclipse.jetty jetty-continuation - 9.4.34-SNAPSHOT + 9.4.34.v20201102 org.eclipse.jetty jetty-deploy - 9.4.34-SNAPSHOT + 9.4.34.v20201102 org.eclipse.jetty jetty-distribution - 9.4.34-SNAPSHOT + 9.4.34.v20201102 zip org.eclipse.jetty jetty-distribution - 9.4.34-SNAPSHOT + 9.4.34.v20201102 tar.gz org.eclipse.jetty.fcgi fcgi-client - 9.4.34-SNAPSHOT + 9.4.34.v20201102 org.eclipse.jetty.fcgi fcgi-server - 9.4.34-SNAPSHOT + 9.4.34.v20201102 org.eclipse.jetty.gcloud jetty-gcloud-session-manager - 9.4.34-SNAPSHOT + 9.4.34.v20201102 org.eclipse.jetty jetty-home - 9.4.34-SNAPSHOT + 9.4.34.v20201102 zip org.eclipse.jetty jetty-home - 9.4.34-SNAPSHOT + 9.4.34.v20201102 tar.gz org.eclipse.jetty jetty-http - 9.4.34-SNAPSHOT + 9.4.34.v20201102 org.eclipse.jetty.http2 http2-client - 9.4.34-SNAPSHOT + 9.4.34.v20201102 org.eclipse.jetty.http2 http2-common - 9.4.34-SNAPSHOT + 9.4.34.v20201102 org.eclipse.jetty.http2 http2-hpack - 9.4.34-SNAPSHOT + 9.4.34.v20201102 org.eclipse.jetty.http2 http2-http-client-transport - 9.4.34-SNAPSHOT + 9.4.34.v20201102 org.eclipse.jetty.http2 http2-server - 9.4.34-SNAPSHOT + 9.4.34.v20201102 org.eclipse.jetty jetty-http-spi - 9.4.34-SNAPSHOT + 9.4.34.v20201102 org.eclipse.jetty infinispan-common - 9.4.34-SNAPSHOT + 9.4.34.v20201102 org.eclipse.jetty infinispan-remote-query - 9.4.34-SNAPSHOT + 9.4.34.v20201102 org.eclipse.jetty infinispan-embedded-query - 9.4.34-SNAPSHOT + 9.4.34.v20201102 org.eclipse.jetty jetty-hazelcast - 9.4.34-SNAPSHOT + 9.4.34.v20201102 org.eclipse.jetty jetty-io - 9.4.34-SNAPSHOT + 9.4.34.v20201102 org.eclipse.jetty jetty-jaas - 9.4.34-SNAPSHOT + 9.4.34.v20201102 org.eclipse.jetty jetty-jaspi - 9.4.34-SNAPSHOT + 9.4.34.v20201102 org.eclipse.jetty jetty-jmx - 9.4.34-SNAPSHOT + 9.4.34.v20201102 org.eclipse.jetty jetty-jndi - 9.4.34-SNAPSHOT + 9.4.34.v20201102 org.eclipse.jetty.memcached jetty-memcached-sessions - 9.4.34-SNAPSHOT + 9.4.34.v20201102 org.eclipse.jetty jetty-nosql - 9.4.34-SNAPSHOT + 9.4.34.v20201102 org.eclipse.jetty.osgi jetty-osgi-boot - 9.4.34-SNAPSHOT + 9.4.34.v20201102 org.eclipse.jetty.osgi jetty-osgi-boot-jsp - 9.4.34-SNAPSHOT + 9.4.34.v20201102 org.eclipse.jetty.osgi jetty-osgi-boot-warurl - 9.4.34-SNAPSHOT + 9.4.34.v20201102 org.eclipse.jetty.osgi jetty-httpservice - 9.4.34-SNAPSHOT + 9.4.34.v20201102 org.eclipse.jetty jetty-plus - 9.4.34-SNAPSHOT + 9.4.34.v20201102 org.eclipse.jetty jetty-proxy - 9.4.34-SNAPSHOT + 9.4.34.v20201102 org.eclipse.jetty jetty-quickstart - 9.4.34-SNAPSHOT + 9.4.34.v20201102 org.eclipse.jetty jetty-rewrite - 9.4.34-SNAPSHOT + 9.4.34.v20201102 org.eclipse.jetty jetty-security - 9.4.34-SNAPSHOT + 9.4.34.v20201102 org.eclipse.jetty jetty-openid - 9.4.34-SNAPSHOT + 9.4.34.v20201102 org.eclipse.jetty jetty-server - 9.4.34-SNAPSHOT + 9.4.34.v20201102 org.eclipse.jetty jetty-servlet - 9.4.34-SNAPSHOT + 9.4.34.v20201102 org.eclipse.jetty jetty-servlets - 9.4.34-SNAPSHOT + 9.4.34.v20201102 org.eclipse.jetty jetty-spring - 9.4.34-SNAPSHOT + 9.4.34.v20201102 org.eclipse.jetty jetty-unixsocket - 9.4.34-SNAPSHOT + 9.4.34.v20201102 org.eclipse.jetty jetty-util - 9.4.34-SNAPSHOT + 9.4.34.v20201102 org.eclipse.jetty jetty-util-ajax - 9.4.34-SNAPSHOT + 9.4.34.v20201102 org.eclipse.jetty jetty-webapp - 9.4.34-SNAPSHOT + 9.4.34.v20201102 org.eclipse.jetty.websocket javax-websocket-client-impl - 9.4.34-SNAPSHOT + 9.4.34.v20201102 org.eclipse.jetty.websocket javax-websocket-server-impl - 9.4.34-SNAPSHOT + 9.4.34.v20201102 org.eclipse.jetty.websocket websocket-api - 9.4.34-SNAPSHOT + 9.4.34.v20201102 org.eclipse.jetty.websocket websocket-client - 9.4.34-SNAPSHOT + 9.4.34.v20201102 org.eclipse.jetty.websocket websocket-common - 9.4.34-SNAPSHOT + 9.4.34.v20201102 org.eclipse.jetty.websocket websocket-server - 9.4.34-SNAPSHOT + 9.4.34.v20201102 org.eclipse.jetty.websocket websocket-servlet - 9.4.34-SNAPSHOT + 9.4.34.v20201102 org.eclipse.jetty jetty-xml - 9.4.34-SNAPSHOT + 9.4.34.v20201102 diff --git a/jetty-cdi/pom.xml b/jetty-cdi/pom.xml index 35f2f4078ef..eed89d15c94 100644 --- a/jetty-cdi/pom.xml +++ b/jetty-cdi/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.34-SNAPSHOT + 9.4.34.v20201102 4.0.0 org.eclipse.jetty diff --git a/jetty-client/pom.xml b/jetty-client/pom.xml index 6e190e8e79b..8e323733a51 100644 --- a/jetty-client/pom.xml +++ b/jetty-client/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.34-SNAPSHOT + 9.4.34.v20201102 4.0.0 diff --git a/jetty-continuation/pom.xml b/jetty-continuation/pom.xml index 7e502b143b3..17eafb54b3f 100644 --- a/jetty-continuation/pom.xml +++ b/jetty-continuation/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.34-SNAPSHOT + 9.4.34.v20201102 4.0.0 jetty-continuation diff --git a/jetty-deploy/pom.xml b/jetty-deploy/pom.xml index 60a6d83462c..3ca3fd1ad6f 100644 --- a/jetty-deploy/pom.xml +++ b/jetty-deploy/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.34-SNAPSHOT + 9.4.34.v20201102 4.0.0 jetty-deploy diff --git a/jetty-distribution/pom.xml b/jetty-distribution/pom.xml index eedfcd7a258..e37c16044ad 100644 --- a/jetty-distribution/pom.xml +++ b/jetty-distribution/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.34-SNAPSHOT + 9.4.34.v20201102 4.0.0 jetty-distribution diff --git a/jetty-documentation/pom.xml b/jetty-documentation/pom.xml index 6ef6042d217..1ef087c1585 100644 --- a/jetty-documentation/pom.xml +++ b/jetty-documentation/pom.xml @@ -4,7 +4,7 @@ org.eclipse.jetty jetty-project - 9.4.34-SNAPSHOT + 9.4.34.v20201102 jetty-documentation Jetty :: Documentation diff --git a/jetty-fcgi/fcgi-client/pom.xml b/jetty-fcgi/fcgi-client/pom.xml index 943dd6f8aaa..eefac290eb6 100644 --- a/jetty-fcgi/fcgi-client/pom.xml +++ b/jetty-fcgi/fcgi-client/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.fcgi fcgi-parent - 9.4.34-SNAPSHOT + 9.4.34.v20201102 4.0.0 diff --git a/jetty-fcgi/fcgi-server/pom.xml b/jetty-fcgi/fcgi-server/pom.xml index ad845f991d4..7ec037f70b0 100644 --- a/jetty-fcgi/fcgi-server/pom.xml +++ b/jetty-fcgi/fcgi-server/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.fcgi fcgi-parent - 9.4.34-SNAPSHOT + 9.4.34.v20201102 4.0.0 diff --git a/jetty-fcgi/pom.xml b/jetty-fcgi/pom.xml index a02eba73d9b..c4427462911 100644 --- a/jetty-fcgi/pom.xml +++ b/jetty-fcgi/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty jetty-project - 9.4.34-SNAPSHOT + 9.4.34.v20201102 4.0.0 diff --git a/jetty-gcloud/jetty-gcloud-session-manager/pom.xml b/jetty-gcloud/jetty-gcloud-session-manager/pom.xml index 021225cd5b3..c8c54e843e7 100644 --- a/jetty-gcloud/jetty-gcloud-session-manager/pom.xml +++ b/jetty-gcloud/jetty-gcloud-session-manager/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.gcloud gcloud-parent - 9.4.34-SNAPSHOT + 9.4.34.v20201102 4.0.0 diff --git a/jetty-gcloud/pom.xml b/jetty-gcloud/pom.xml index 238eb8edca7..1061ff2eae2 100644 --- a/jetty-gcloud/pom.xml +++ b/jetty-gcloud/pom.xml @@ -3,7 +3,7 @@ jetty-project org.eclipse.jetty - 9.4.34-SNAPSHOT + 9.4.34.v20201102 4.0.0 diff --git a/jetty-hazelcast/pom.xml b/jetty-hazelcast/pom.xml index 44714c95c33..94e150f5b96 100644 --- a/jetty-hazelcast/pom.xml +++ b/jetty-hazelcast/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty jetty-project - 9.4.34-SNAPSHOT + 9.4.34.v20201102 4.0.0 diff --git a/jetty-home/pom.xml b/jetty-home/pom.xml index dc9b3940b63..6267c0047dd 100644 --- a/jetty-home/pom.xml +++ b/jetty-home/pom.xml @@ -3,7 +3,7 @@ jetty-project org.eclipse.jetty - 9.4.34-SNAPSHOT + 9.4.34.v20201102 4.0.0 jetty-home diff --git a/jetty-http-spi/pom.xml b/jetty-http-spi/pom.xml index cedb5026ffe..93f906b4cd8 100644 --- a/jetty-http-spi/pom.xml +++ b/jetty-http-spi/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.34-SNAPSHOT + 9.4.34.v20201102 4.0.0 jetty-http-spi diff --git a/jetty-http/pom.xml b/jetty-http/pom.xml index 7213a20b658..395c271e0ba 100644 --- a/jetty-http/pom.xml +++ b/jetty-http/pom.xml @@ -3,7 +3,7 @@ jetty-project org.eclipse.jetty - 9.4.34-SNAPSHOT + 9.4.34.v20201102 4.0.0 jetty-http diff --git a/jetty-http2/http2-alpn-tests/pom.xml b/jetty-http2/http2-alpn-tests/pom.xml index 3abef984e36..33cebbcade4 100644 --- a/jetty-http2/http2-alpn-tests/pom.xml +++ b/jetty-http2/http2-alpn-tests/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.http2 http2-parent - 9.4.34-SNAPSHOT + 9.4.34.v20201102 4.0.0 diff --git a/jetty-http2/http2-client/pom.xml b/jetty-http2/http2-client/pom.xml index f9a61861d3c..badb0dddc1f 100644 --- a/jetty-http2/http2-client/pom.xml +++ b/jetty-http2/http2-client/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.http2 http2-parent - 9.4.34-SNAPSHOT + 9.4.34.v20201102 4.0.0 diff --git a/jetty-http2/http2-common/pom.xml b/jetty-http2/http2-common/pom.xml index bca19a00272..91ca3bff273 100644 --- a/jetty-http2/http2-common/pom.xml +++ b/jetty-http2/http2-common/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.http2 http2-parent - 9.4.34-SNAPSHOT + 9.4.34.v20201102 4.0.0 diff --git a/jetty-http2/http2-hpack/pom.xml b/jetty-http2/http2-hpack/pom.xml index c9e535b5753..1a21f5ba175 100644 --- a/jetty-http2/http2-hpack/pom.xml +++ b/jetty-http2/http2-hpack/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.http2 http2-parent - 9.4.34-SNAPSHOT + 9.4.34.v20201102 4.0.0 diff --git a/jetty-http2/http2-http-client-transport/pom.xml b/jetty-http2/http2-http-client-transport/pom.xml index 7a115afa2e0..e4b4233bbed 100644 --- a/jetty-http2/http2-http-client-transport/pom.xml +++ b/jetty-http2/http2-http-client-transport/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.http2 http2-parent - 9.4.34-SNAPSHOT + 9.4.34.v20201102 4.0.0 diff --git a/jetty-http2/http2-server/pom.xml b/jetty-http2/http2-server/pom.xml index 4dd62f2dcd0..406a871567c 100644 --- a/jetty-http2/http2-server/pom.xml +++ b/jetty-http2/http2-server/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.http2 http2-parent - 9.4.34-SNAPSHOT + 9.4.34.v20201102 4.0.0 diff --git a/jetty-http2/pom.xml b/jetty-http2/pom.xml index bfd9743d5d3..1e4b6841536 100644 --- a/jetty-http2/pom.xml +++ b/jetty-http2/pom.xml @@ -3,7 +3,7 @@ jetty-project org.eclipse.jetty - 9.4.34-SNAPSHOT + 9.4.34.v20201102 4.0.0 diff --git a/jetty-infinispan/infinispan-common/pom.xml b/jetty-infinispan/infinispan-common/pom.xml index 5abc71fead1..2c8aec025b6 100644 --- a/jetty-infinispan/infinispan-common/pom.xml +++ b/jetty-infinispan/infinispan-common/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty infinispan-parent - 9.4.34-SNAPSHOT + 9.4.34.v20201102 4.0.0 infinispan-common diff --git a/jetty-infinispan/infinispan-embedded-query/pom.xml b/jetty-infinispan/infinispan-embedded-query/pom.xml index 569b258e358..fbfea31e741 100644 --- a/jetty-infinispan/infinispan-embedded-query/pom.xml +++ b/jetty-infinispan/infinispan-embedded-query/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty infinispan-parent - 9.4.34-SNAPSHOT + 9.4.34.v20201102 4.0.0 infinispan-embedded-query diff --git a/jetty-infinispan/infinispan-embedded/pom.xml b/jetty-infinispan/infinispan-embedded/pom.xml index b33669b6d28..15e8f396cbe 100644 --- a/jetty-infinispan/infinispan-embedded/pom.xml +++ b/jetty-infinispan/infinispan-embedded/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty infinispan-parent - 9.4.34-SNAPSHOT + 9.4.34.v20201102 4.0.0 infinispan-embedded diff --git a/jetty-infinispan/infinispan-remote-query/pom.xml b/jetty-infinispan/infinispan-remote-query/pom.xml index 8c76d5cb9af..ff8a81ffcae 100644 --- a/jetty-infinispan/infinispan-remote-query/pom.xml +++ b/jetty-infinispan/infinispan-remote-query/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty infinispan-parent - 9.4.34-SNAPSHOT + 9.4.34.v20201102 4.0.0 infinispan-remote-query diff --git a/jetty-infinispan/infinispan-remote/pom.xml b/jetty-infinispan/infinispan-remote/pom.xml index 5f64be1d368..9920c7c7650 100644 --- a/jetty-infinispan/infinispan-remote/pom.xml +++ b/jetty-infinispan/infinispan-remote/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty infinispan-parent - 9.4.34-SNAPSHOT + 9.4.34.v20201102 4.0.0 infinispan-remote diff --git a/jetty-infinispan/pom.xml b/jetty-infinispan/pom.xml index d70f4798797..349a1bda8e5 100644 --- a/jetty-infinispan/pom.xml +++ b/jetty-infinispan/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty jetty-project - 9.4.34-SNAPSHOT + 9.4.34.v20201102 4.0.0 diff --git a/jetty-io/pom.xml b/jetty-io/pom.xml index 05c3b919b94..e163e1925cf 100644 --- a/jetty-io/pom.xml +++ b/jetty-io/pom.xml @@ -3,7 +3,7 @@ jetty-project org.eclipse.jetty - 9.4.34-SNAPSHOT + 9.4.34.v20201102 4.0.0 jetty-io diff --git a/jetty-jaas/pom.xml b/jetty-jaas/pom.xml index bc47adbe2c3..35c26871a0b 100644 --- a/jetty-jaas/pom.xml +++ b/jetty-jaas/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.34-SNAPSHOT + 9.4.34.v20201102 4.0.0 jetty-jaas diff --git a/jetty-jaspi/pom.xml b/jetty-jaspi/pom.xml index dee2fa03168..b8c028e0b68 100644 --- a/jetty-jaspi/pom.xml +++ b/jetty-jaspi/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.34-SNAPSHOT + 9.4.34.v20201102 4.0.0 diff --git a/jetty-jmh/pom.xml b/jetty-jmh/pom.xml index 1d5eaf806b7..ce9c693908e 100644 --- a/jetty-jmh/pom.xml +++ b/jetty-jmh/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.34-SNAPSHOT + 9.4.34.v20201102 4.0.0 diff --git a/jetty-jmx/pom.xml b/jetty-jmx/pom.xml index 15365c97c99..d824ab7b12a 100644 --- a/jetty-jmx/pom.xml +++ b/jetty-jmx/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.34-SNAPSHOT + 9.4.34.v20201102 4.0.0 jetty-jmx diff --git a/jetty-jndi/pom.xml b/jetty-jndi/pom.xml index 574730befaa..e1865c894c0 100644 --- a/jetty-jndi/pom.xml +++ b/jetty-jndi/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.34-SNAPSHOT + 9.4.34.v20201102 4.0.0 jetty-jndi diff --git a/jetty-jspc-maven-plugin/pom.xml b/jetty-jspc-maven-plugin/pom.xml index 40ece734ded..68427f51ef1 100644 --- a/jetty-jspc-maven-plugin/pom.xml +++ b/jetty-jspc-maven-plugin/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty jetty-project - 9.4.34-SNAPSHOT + 9.4.34.v20201102 4.0.0 jetty-jspc-maven-plugin diff --git a/jetty-maven-plugin/pom.xml b/jetty-maven-plugin/pom.xml index 06f970daff6..e62fad09b6a 100644 --- a/jetty-maven-plugin/pom.xml +++ b/jetty-maven-plugin/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.34-SNAPSHOT + 9.4.34.v20201102 4.0.0 jetty-maven-plugin diff --git a/jetty-memcached/jetty-memcached-sessions/pom.xml b/jetty-memcached/jetty-memcached-sessions/pom.xml index 9faf06175b9..8bffef807d7 100644 --- a/jetty-memcached/jetty-memcached-sessions/pom.xml +++ b/jetty-memcached/jetty-memcached-sessions/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.memcached memcached-parent - 9.4.34-SNAPSHOT + 9.4.34.v20201102 4.0.0 diff --git a/jetty-memcached/pom.xml b/jetty-memcached/pom.xml index d230706bdbd..f307ceabc61 100644 --- a/jetty-memcached/pom.xml +++ b/jetty-memcached/pom.xml @@ -3,7 +3,7 @@ jetty-project org.eclipse.jetty - 9.4.34-SNAPSHOT + 9.4.34.v20201102 4.0.0 diff --git a/jetty-nosql/pom.xml b/jetty-nosql/pom.xml index 75a3f2a89d2..4a340fda4d3 100644 --- a/jetty-nosql/pom.xml +++ b/jetty-nosql/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.34-SNAPSHOT + 9.4.34.v20201102 4.0.0 jetty-nosql diff --git a/jetty-openid/pom.xml b/jetty-openid/pom.xml index 763f663ebe1..be876473aa6 100644 --- a/jetty-openid/pom.xml +++ b/jetty-openid/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.34-SNAPSHOT + 9.4.34.v20201102 4.0.0 diff --git a/jetty-osgi/jetty-osgi-alpn/pom.xml b/jetty-osgi/jetty-osgi-alpn/pom.xml index a3b92eb0df4..b5b0a777fad 100644 --- a/jetty-osgi/jetty-osgi-alpn/pom.xml +++ b/jetty-osgi/jetty-osgi-alpn/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty.osgi jetty-osgi-project - 9.4.34-SNAPSHOT + 9.4.34.v20201102 4.0.0 jetty-osgi-alpn diff --git a/jetty-osgi/jetty-osgi-boot-jsp/pom.xml b/jetty-osgi/jetty-osgi-boot-jsp/pom.xml index aaf775bc804..5dfe99a62d3 100644 --- a/jetty-osgi/jetty-osgi-boot-jsp/pom.xml +++ b/jetty-osgi/jetty-osgi-boot-jsp/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty.osgi jetty-osgi-project - 9.4.34-SNAPSHOT + 9.4.34.v20201102 4.0.0 jetty-osgi-boot-jsp diff --git a/jetty-osgi/jetty-osgi-boot-warurl/pom.xml b/jetty-osgi/jetty-osgi-boot-warurl/pom.xml index 50ecd9a2f5f..7dc89206160 100644 --- a/jetty-osgi/jetty-osgi-boot-warurl/pom.xml +++ b/jetty-osgi/jetty-osgi-boot-warurl/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty.osgi jetty-osgi-project - 9.4.34-SNAPSHOT + 9.4.34.v20201102 ../pom.xml 4.0.0 diff --git a/jetty-osgi/jetty-osgi-boot/pom.xml b/jetty-osgi/jetty-osgi-boot/pom.xml index 02528818ace..379500e56ee 100644 --- a/jetty-osgi/jetty-osgi-boot/pom.xml +++ b/jetty-osgi/jetty-osgi-boot/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty.osgi jetty-osgi-project - 9.4.34-SNAPSHOT + 9.4.34.v20201102 4.0.0 jetty-osgi-boot diff --git a/jetty-osgi/jetty-osgi-httpservice/pom.xml b/jetty-osgi/jetty-osgi-httpservice/pom.xml index 40f46b8a7ba..9ed9765e99d 100644 --- a/jetty-osgi/jetty-osgi-httpservice/pom.xml +++ b/jetty-osgi/jetty-osgi-httpservice/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty.osgi jetty-osgi-project - 9.4.34-SNAPSHOT + 9.4.34.v20201102 4.0.0 jetty-httpservice diff --git a/jetty-osgi/pom.xml b/jetty-osgi/pom.xml index b394d03db51..451eb4f89f3 100644 --- a/jetty-osgi/pom.xml +++ b/jetty-osgi/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.34-SNAPSHOT + 9.4.34.v20201102 4.0.0 diff --git a/jetty-osgi/test-jetty-osgi-context/pom.xml b/jetty-osgi/test-jetty-osgi-context/pom.xml index 43b99424d73..1a769c204c6 100644 --- a/jetty-osgi/test-jetty-osgi-context/pom.xml +++ b/jetty-osgi/test-jetty-osgi-context/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty.osgi jetty-osgi-project - 9.4.34-SNAPSHOT + 9.4.34.v20201102 4.0.0 test-jetty-osgi-context diff --git a/jetty-osgi/test-jetty-osgi-fragment/pom.xml b/jetty-osgi/test-jetty-osgi-fragment/pom.xml index e6fa5b55b81..b19641ae905 100644 --- a/jetty-osgi/test-jetty-osgi-fragment/pom.xml +++ b/jetty-osgi/test-jetty-osgi-fragment/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty.osgi jetty-osgi-project - 9.4.34-SNAPSHOT + 9.4.34.v20201102 ../pom.xml 4.0.0 diff --git a/jetty-osgi/test-jetty-osgi-server/pom.xml b/jetty-osgi/test-jetty-osgi-server/pom.xml index 7d0de44c235..e8940aaad07 100644 --- a/jetty-osgi/test-jetty-osgi-server/pom.xml +++ b/jetty-osgi/test-jetty-osgi-server/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty.osgi jetty-osgi-project - 9.4.34-SNAPSHOT + 9.4.34.v20201102 4.0.0 test-jetty-osgi-server diff --git a/jetty-osgi/test-jetty-osgi-webapp-resources/pom.xml b/jetty-osgi/test-jetty-osgi-webapp-resources/pom.xml index 732e51732ea..0beaad07682 100644 --- a/jetty-osgi/test-jetty-osgi-webapp-resources/pom.xml +++ b/jetty-osgi/test-jetty-osgi-webapp-resources/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.osgi jetty-osgi-project - 9.4.34-SNAPSHOT + 9.4.34.v20201102 4.0.0 test-jetty-osgi-webapp-resources diff --git a/jetty-osgi/test-jetty-osgi-webapp/pom.xml b/jetty-osgi/test-jetty-osgi-webapp/pom.xml index 3a86c3f1a3c..516a2e190a4 100644 --- a/jetty-osgi/test-jetty-osgi-webapp/pom.xml +++ b/jetty-osgi/test-jetty-osgi-webapp/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty.osgi jetty-osgi-project - 9.4.34-SNAPSHOT + 9.4.34.v20201102 ../pom.xml 4.0.0 diff --git a/jetty-osgi/test-jetty-osgi/pom.xml b/jetty-osgi/test-jetty-osgi/pom.xml index 6552794ddec..a6e510fa167 100644 --- a/jetty-osgi/test-jetty-osgi/pom.xml +++ b/jetty-osgi/test-jetty-osgi/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty.osgi jetty-osgi-project - 9.4.34-SNAPSHOT + 9.4.34.v20201102 ../pom.xml 4.0.0 diff --git a/jetty-plus/pom.xml b/jetty-plus/pom.xml index 74d54f4bace..e6fe36d8349 100644 --- a/jetty-plus/pom.xml +++ b/jetty-plus/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.34-SNAPSHOT + 9.4.34.v20201102 4.0.0 jetty-plus diff --git a/jetty-proxy/pom.xml b/jetty-proxy/pom.xml index 5b5996fdacf..e3da8476cf6 100644 --- a/jetty-proxy/pom.xml +++ b/jetty-proxy/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.34-SNAPSHOT + 9.4.34.v20201102 4.0.0 jetty-proxy diff --git a/jetty-quickstart/pom.xml b/jetty-quickstart/pom.xml index 1c2a49c39c7..795b30e96c8 100644 --- a/jetty-quickstart/pom.xml +++ b/jetty-quickstart/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.34-SNAPSHOT + 9.4.34.v20201102 4.0.0 org.eclipse.jetty diff --git a/jetty-rewrite/pom.xml b/jetty-rewrite/pom.xml index 56997d1ad4f..3a591e0c129 100644 --- a/jetty-rewrite/pom.xml +++ b/jetty-rewrite/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.34-SNAPSHOT + 9.4.34.v20201102 4.0.0 jetty-rewrite diff --git a/jetty-runner/pom.xml b/jetty-runner/pom.xml index abc4fa523b2..77740bbf714 100644 --- a/jetty-runner/pom.xml +++ b/jetty-runner/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.34-SNAPSHOT + 9.4.34.v20201102 4.0.0 jetty-runner diff --git a/jetty-security/pom.xml b/jetty-security/pom.xml index a4a619982f7..b94c1dbeafc 100644 --- a/jetty-security/pom.xml +++ b/jetty-security/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.34-SNAPSHOT + 9.4.34.v20201102 4.0.0 jetty-security diff --git a/jetty-server/pom.xml b/jetty-server/pom.xml index 4e58f516a95..c5712a12b18 100644 --- a/jetty-server/pom.xml +++ b/jetty-server/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.34-SNAPSHOT + 9.4.34.v20201102 4.0.0 jetty-server diff --git a/jetty-servlet/pom.xml b/jetty-servlet/pom.xml index 4d6dd0df7df..d7384f8acc6 100644 --- a/jetty-servlet/pom.xml +++ b/jetty-servlet/pom.xml @@ -3,7 +3,7 @@ jetty-project org.eclipse.jetty - 9.4.34-SNAPSHOT + 9.4.34.v20201102 4.0.0 jetty-servlet diff --git a/jetty-servlets/pom.xml b/jetty-servlets/pom.xml index d5eb6cda20f..42fe102c930 100644 --- a/jetty-servlets/pom.xml +++ b/jetty-servlets/pom.xml @@ -3,7 +3,7 @@ jetty-project org.eclipse.jetty - 9.4.34-SNAPSHOT + 9.4.34.v20201102 4.0.0 jetty-servlets diff --git a/jetty-spring/pom.xml b/jetty-spring/pom.xml index b292f1d60f5..4dad16313b5 100644 --- a/jetty-spring/pom.xml +++ b/jetty-spring/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.34-SNAPSHOT + 9.4.34.v20201102 4.0.0 jetty-spring diff --git a/jetty-start/pom.xml b/jetty-start/pom.xml index faed9c6b443..6dad94c3071 100644 --- a/jetty-start/pom.xml +++ b/jetty-start/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.34-SNAPSHOT + 9.4.34.v20201102 4.0.0 jetty-start diff --git a/jetty-unixsocket/pom.xml b/jetty-unixsocket/pom.xml index 97418cb7e69..3fe186bd594 100644 --- a/jetty-unixsocket/pom.xml +++ b/jetty-unixsocket/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.34-SNAPSHOT + 9.4.34.v20201102 4.0.0 jetty-unixsocket diff --git a/jetty-util-ajax/pom.xml b/jetty-util-ajax/pom.xml index b082beb79fb..941cb8b11b6 100644 --- a/jetty-util-ajax/pom.xml +++ b/jetty-util-ajax/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.34-SNAPSHOT + 9.4.34.v20201102 4.0.0 jetty-util-ajax diff --git a/jetty-util/pom.xml b/jetty-util/pom.xml index dc65f9f7ebf..067a511d269 100644 --- a/jetty-util/pom.xml +++ b/jetty-util/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.34-SNAPSHOT + 9.4.34.v20201102 4.0.0 jetty-util diff --git a/jetty-webapp/pom.xml b/jetty-webapp/pom.xml index 8154b7ee84c..a5c8b7483f9 100644 --- a/jetty-webapp/pom.xml +++ b/jetty-webapp/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.34-SNAPSHOT + 9.4.34.v20201102 4.0.0 jetty-webapp diff --git a/jetty-websocket/javax-websocket-client-impl/pom.xml b/jetty-websocket/javax-websocket-client-impl/pom.xml index 43a38067e8b..79f51febade 100644 --- a/jetty-websocket/javax-websocket-client-impl/pom.xml +++ b/jetty-websocket/javax-websocket-client-impl/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.websocket websocket-parent - 9.4.34-SNAPSHOT + 9.4.34.v20201102 4.0.0 diff --git a/jetty-websocket/javax-websocket-server-impl/pom.xml b/jetty-websocket/javax-websocket-server-impl/pom.xml index b037bbd1f5a..6fc199c0736 100644 --- a/jetty-websocket/javax-websocket-server-impl/pom.xml +++ b/jetty-websocket/javax-websocket-server-impl/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.websocket websocket-parent - 9.4.34-SNAPSHOT + 9.4.34.v20201102 4.0.0 diff --git a/jetty-websocket/jetty-websocket-tests/pom.xml b/jetty-websocket/jetty-websocket-tests/pom.xml index e25f47d4ef7..be5ea05925b 100644 --- a/jetty-websocket/jetty-websocket-tests/pom.xml +++ b/jetty-websocket/jetty-websocket-tests/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.websocket websocket-parent - 9.4.34-SNAPSHOT + 9.4.34.v20201102 4.0.0 diff --git a/jetty-websocket/pom.xml b/jetty-websocket/pom.xml index 25af902913c..d0dddfdaaa5 100644 --- a/jetty-websocket/pom.xml +++ b/jetty-websocket/pom.xml @@ -3,7 +3,7 @@ jetty-project org.eclipse.jetty - 9.4.34-SNAPSHOT + 9.4.34.v20201102 4.0.0 diff --git a/jetty-websocket/websocket-api/pom.xml b/jetty-websocket/websocket-api/pom.xml index e8a7b49e72f..c2d98aa17f0 100644 --- a/jetty-websocket/websocket-api/pom.xml +++ b/jetty-websocket/websocket-api/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.websocket websocket-parent - 9.4.34-SNAPSHOT + 9.4.34.v20201102 4.0.0 diff --git a/jetty-websocket/websocket-client/pom.xml b/jetty-websocket/websocket-client/pom.xml index 031f3a509b9..b775407fde7 100644 --- a/jetty-websocket/websocket-client/pom.xml +++ b/jetty-websocket/websocket-client/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.websocket websocket-parent - 9.4.34-SNAPSHOT + 9.4.34.v20201102 4.0.0 diff --git a/jetty-websocket/websocket-common/pom.xml b/jetty-websocket/websocket-common/pom.xml index e3d7b715658..fdcea95dbfb 100644 --- a/jetty-websocket/websocket-common/pom.xml +++ b/jetty-websocket/websocket-common/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.websocket websocket-parent - 9.4.34-SNAPSHOT + 9.4.34.v20201102 4.0.0 diff --git a/jetty-websocket/websocket-server/pom.xml b/jetty-websocket/websocket-server/pom.xml index 678c199e54d..433dc315f52 100644 --- a/jetty-websocket/websocket-server/pom.xml +++ b/jetty-websocket/websocket-server/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.websocket websocket-parent - 9.4.34-SNAPSHOT + 9.4.34.v20201102 4.0.0 diff --git a/jetty-websocket/websocket-servlet/pom.xml b/jetty-websocket/websocket-servlet/pom.xml index ea7bbe4f119..00458d8df52 100644 --- a/jetty-websocket/websocket-servlet/pom.xml +++ b/jetty-websocket/websocket-servlet/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.websocket websocket-parent - 9.4.34-SNAPSHOT + 9.4.34.v20201102 4.0.0 diff --git a/jetty-xml/pom.xml b/jetty-xml/pom.xml index ea6e9ee8538..efbbf37c2b5 100644 --- a/jetty-xml/pom.xml +++ b/jetty-xml/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.34-SNAPSHOT + 9.4.34.v20201102 4.0.0 jetty-xml diff --git a/pom.xml b/pom.xml index 73d1ccd5b88..c4dd144087d 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 org.eclipse.jetty jetty-project - 9.4.34-SNAPSHOT + 9.4.34.v20201102 Jetty :: Project The Eclipse Jetty Project pom diff --git a/tests/pom.xml b/tests/pom.xml index 5a9593494a5..f66a70a9e42 100644 --- a/tests/pom.xml +++ b/tests/pom.xml @@ -4,7 +4,7 @@ org.eclipse.jetty jetty-project - 9.4.34-SNAPSHOT + 9.4.34.v20201102 ../pom.xml org.eclipse.jetty.tests diff --git a/tests/test-continuation/pom.xml b/tests/test-continuation/pom.xml index 64af455e280..20563ea361a 100644 --- a/tests/test-continuation/pom.xml +++ b/tests/test-continuation/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.tests tests-parent - 9.4.34-SNAPSHOT + 9.4.34.v20201102 ../pom.xml 4.0.0 diff --git a/tests/test-distribution/pom.xml b/tests/test-distribution/pom.xml index 76326d54c86..edaf5375eb0 100644 --- a/tests/test-distribution/pom.xml +++ b/tests/test-distribution/pom.xml @@ -2,7 +2,7 @@ tests-parent org.eclipse.jetty.tests - 9.4.34-SNAPSHOT + 9.4.34.v20201102 4.0.0 diff --git a/tests/test-http-client-transport/pom.xml b/tests/test-http-client-transport/pom.xml index b3020edbbc0..9d3b33de021 100644 --- a/tests/test-http-client-transport/pom.xml +++ b/tests/test-http-client-transport/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.tests tests-parent - 9.4.34-SNAPSHOT + 9.4.34.v20201102 4.0.0 diff --git a/tests/test-integration/pom.xml b/tests/test-integration/pom.xml index 8365326f36b..ed3606aabf9 100644 --- a/tests/test-integration/pom.xml +++ b/tests/test-integration/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.tests tests-parent - 9.4.34-SNAPSHOT + 9.4.34.v20201102 4.0.0 test-integration diff --git a/tests/test-jmx/jmx-webapp-it/pom.xml b/tests/test-jmx/jmx-webapp-it/pom.xml index cdf9b3f44a5..c9d4fe4614f 100644 --- a/tests/test-jmx/jmx-webapp-it/pom.xml +++ b/tests/test-jmx/jmx-webapp-it/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.tests test-jmx-parent - 9.4.34-SNAPSHOT + 9.4.34.v20201102 4.0.0 jmx-webapp-it diff --git a/tests/test-jmx/jmx-webapp/pom.xml b/tests/test-jmx/jmx-webapp/pom.xml index 57d8efbe003..f83ed45486d 100644 --- a/tests/test-jmx/jmx-webapp/pom.xml +++ b/tests/test-jmx/jmx-webapp/pom.xml @@ -4,7 +4,7 @@ org.eclipse.jetty.tests test-jmx-parent - 9.4.34-SNAPSHOT + 9.4.34.v20201102 jmx-webapp war diff --git a/tests/test-jmx/pom.xml b/tests/test-jmx/pom.xml index 736e848987a..1d8d5a9a0b6 100644 --- a/tests/test-jmx/pom.xml +++ b/tests/test-jmx/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.tests tests-parent - 9.4.34-SNAPSHOT + 9.4.34.v20201102 4.0.0 test-jmx-parent diff --git a/tests/test-loginservice/pom.xml b/tests/test-loginservice/pom.xml index 021ba05eb73..c64abdfcc8d 100644 --- a/tests/test-loginservice/pom.xml +++ b/tests/test-loginservice/pom.xml @@ -4,7 +4,7 @@ org.eclipse.jetty.tests tests-parent - 9.4.34-SNAPSHOT + 9.4.34.v20201102 test-loginservice Jetty Tests :: Login Service diff --git a/tests/test-quickstart/pom.xml b/tests/test-quickstart/pom.xml index db4c32b37f7..df7aa100881 100644 --- a/tests/test-quickstart/pom.xml +++ b/tests/test-quickstart/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.tests tests-parent - 9.4.34-SNAPSHOT + 9.4.34.v20201102 ../pom.xml 4.0.0 diff --git a/tests/test-sessions/pom.xml b/tests/test-sessions/pom.xml index 227ca989049..93d9081b527 100644 --- a/tests/test-sessions/pom.xml +++ b/tests/test-sessions/pom.xml @@ -4,7 +4,7 @@ org.eclipse.jetty.tests tests-parent - 9.4.34-SNAPSHOT + 9.4.34.v20201102 test-sessions-parent Jetty Tests :: Sessions :: Parent diff --git a/tests/test-sessions/test-file-sessions/pom.xml b/tests/test-sessions/test-file-sessions/pom.xml index 2164a3ab542..0736a17d041 100644 --- a/tests/test-sessions/test-file-sessions/pom.xml +++ b/tests/test-sessions/test-file-sessions/pom.xml @@ -4,7 +4,7 @@ org.eclipse.jetty.tests test-sessions-parent - 9.4.34-SNAPSHOT + 9.4.34.v20201102 test-file-sessions Jetty Tests :: Sessions :: File diff --git a/tests/test-sessions/test-gcloud-sessions/pom.xml b/tests/test-sessions/test-gcloud-sessions/pom.xml index bce88380e4e..b903f7b9f54 100644 --- a/tests/test-sessions/test-gcloud-sessions/pom.xml +++ b/tests/test-sessions/test-gcloud-sessions/pom.xml @@ -4,7 +4,7 @@ org.eclipse.jetty.tests test-sessions-parent - 9.4.34-SNAPSHOT + 9.4.34.v20201102 test-gcloud-sessions Jetty Tests :: Sessions :: GCloud diff --git a/tests/test-sessions/test-hazelcast-sessions/pom.xml b/tests/test-sessions/test-hazelcast-sessions/pom.xml index c6da70f57af..c654fc867ca 100644 --- a/tests/test-sessions/test-hazelcast-sessions/pom.xml +++ b/tests/test-sessions/test-hazelcast-sessions/pom.xml @@ -4,7 +4,7 @@ org.eclipse.jetty.tests test-sessions-parent - 9.4.34-SNAPSHOT + 9.4.34.v20201102 test-hazelcast-sessions Jetty Tests :: Sessions :: Hazelcast diff --git a/tests/test-sessions/test-infinispan-sessions/pom.xml b/tests/test-sessions/test-infinispan-sessions/pom.xml index a76f441b583..62d62738f0e 100644 --- a/tests/test-sessions/test-infinispan-sessions/pom.xml +++ b/tests/test-sessions/test-infinispan-sessions/pom.xml @@ -4,7 +4,7 @@ org.eclipse.jetty.tests test-sessions-parent - 9.4.34-SNAPSHOT + 9.4.34.v20201102 test-infinispan-sessions Jetty Tests :: Sessions :: Infinispan diff --git a/tests/test-sessions/test-jdbc-sessions/pom.xml b/tests/test-sessions/test-jdbc-sessions/pom.xml index d38ef82fb85..44dd4df2cfd 100644 --- a/tests/test-sessions/test-jdbc-sessions/pom.xml +++ b/tests/test-sessions/test-jdbc-sessions/pom.xml @@ -4,7 +4,7 @@ org.eclipse.jetty.tests test-sessions-parent - 9.4.34-SNAPSHOT + 9.4.34.v20201102 test-jdbc-sessions Jetty Tests :: Sessions :: JDBC diff --git a/tests/test-sessions/test-memcached-sessions/pom.xml b/tests/test-sessions/test-memcached-sessions/pom.xml index 668e4f5cc93..6db4e58ffba 100644 --- a/tests/test-sessions/test-memcached-sessions/pom.xml +++ b/tests/test-sessions/test-memcached-sessions/pom.xml @@ -4,7 +4,7 @@ org.eclipse.jetty.tests test-sessions-parent - 9.4.34-SNAPSHOT + 9.4.34.v20201102 test-memcached-sessions Jetty Tests :: Sessions :: Memcached diff --git a/tests/test-sessions/test-mongodb-sessions/pom.xml b/tests/test-sessions/test-mongodb-sessions/pom.xml index 793eb26875b..8049c252c32 100644 --- a/tests/test-sessions/test-mongodb-sessions/pom.xml +++ b/tests/test-sessions/test-mongodb-sessions/pom.xml @@ -4,7 +4,7 @@ org.eclipse.jetty.tests test-sessions-parent - 9.4.34-SNAPSHOT + 9.4.34.v20201102 test-mongodb-sessions Jetty Tests :: Sessions :: Mongo diff --git a/tests/test-sessions/test-sessions-common/pom.xml b/tests/test-sessions/test-sessions-common/pom.xml index 36b1adae281..74326c85abe 100644 --- a/tests/test-sessions/test-sessions-common/pom.xml +++ b/tests/test-sessions/test-sessions-common/pom.xml @@ -4,7 +4,7 @@ org.eclipse.jetty.tests test-sessions-parent - 9.4.34-SNAPSHOT + 9.4.34.v20201102 test-sessions-common Jetty Tests :: Sessions :: Common diff --git a/tests/test-webapps/pom.xml b/tests/test-webapps/pom.xml index 69c4014557f..106eb671d80 100644 --- a/tests/test-webapps/pom.xml +++ b/tests/test-webapps/pom.xml @@ -4,7 +4,7 @@ org.eclipse.jetty.tests tests-parent - 9.4.34-SNAPSHOT + 9.4.34.v20201102 ../pom.xml test-webapps-parent diff --git a/tests/test-webapps/test-cdi-common-webapp/pom.xml b/tests/test-webapps/test-cdi-common-webapp/pom.xml index 922b6ada194..2d7f8134d1a 100644 --- a/tests/test-webapps/test-cdi-common-webapp/pom.xml +++ b/tests/test-webapps/test-cdi-common-webapp/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.tests test-webapps-parent - 9.4.34-SNAPSHOT + 9.4.34.v20201102 4.0.0 diff --git a/tests/test-webapps/test-felix-webapp/pom.xml b/tests/test-webapps/test-felix-webapp/pom.xml index 394bfbf8c8b..a394c2335cd 100644 --- a/tests/test-webapps/test-felix-webapp/pom.xml +++ b/tests/test-webapps/test-felix-webapp/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.tests test-webapps-parent - 9.4.34-SNAPSHOT + 9.4.34.v20201102 4.0.0 diff --git a/tests/test-webapps/test-http2-webapp/pom.xml b/tests/test-webapps/test-http2-webapp/pom.xml index 102fb0f39ec..101f1052319 100644 --- a/tests/test-webapps/test-http2-webapp/pom.xml +++ b/tests/test-webapps/test-http2-webapp/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.tests test-webapps-parent - 9.4.34-SNAPSHOT + 9.4.34.v20201102 4.0.0 diff --git a/tests/test-webapps/test-jaas-webapp/pom.xml b/tests/test-webapps/test-jaas-webapp/pom.xml index b0a5026f07d..b483e6b4d97 100644 --- a/tests/test-webapps/test-jaas-webapp/pom.xml +++ b/tests/test-webapps/test-jaas-webapp/pom.xml @@ -4,7 +4,7 @@ org.eclipse.jetty.tests test-webapps-parent - 9.4.34-SNAPSHOT + 9.4.34.v20201102 test-jaas-webapp Jetty Tests :: WebApp :: JAAS diff --git a/tests/test-webapps/test-jetty-webapp/pom.xml b/tests/test-webapps/test-jetty-webapp/pom.xml index 1c320f8adb6..197396e0c66 100644 --- a/tests/test-webapps/test-jetty-webapp/pom.xml +++ b/tests/test-webapps/test-jetty-webapp/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.tests test-webapps-parent - 9.4.34-SNAPSHOT + 9.4.34.v20201102 ../pom.xml 4.0.0 diff --git a/tests/test-webapps/test-jndi-webapp/pom.xml b/tests/test-webapps/test-jndi-webapp/pom.xml index 0d30fe3028e..6c8a4f35e2b 100644 --- a/tests/test-webapps/test-jndi-webapp/pom.xml +++ b/tests/test-webapps/test-jndi-webapp/pom.xml @@ -4,7 +4,7 @@ org.eclipse.jetty.tests test-webapps-parent - 9.4.34-SNAPSHOT + 9.4.34.v20201102 test-jndi-webapp Jetty Tests :: WebApp :: JNDI diff --git a/tests/test-webapps/test-mock-resources/pom.xml b/tests/test-webapps/test-mock-resources/pom.xml index a9d666689fa..258c470c203 100644 --- a/tests/test-webapps/test-mock-resources/pom.xml +++ b/tests/test-webapps/test-mock-resources/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.tests test-webapps-parent - 9.4.34-SNAPSHOT + 9.4.34.v20201102 Jetty Tests :: WebApp :: Mock Resources test-mock-resources diff --git a/tests/test-webapps/test-owb-cdi-webapp/pom.xml b/tests/test-webapps/test-owb-cdi-webapp/pom.xml index 2f796578ab7..030b56c5883 100644 --- a/tests/test-webapps/test-owb-cdi-webapp/pom.xml +++ b/tests/test-webapps/test-owb-cdi-webapp/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.tests test-webapps-parent - 9.4.34-SNAPSHOT + 9.4.34.v20201102 4.0.0 diff --git a/tests/test-webapps/test-proxy-webapp/pom.xml b/tests/test-webapps/test-proxy-webapp/pom.xml index 7c434eb60a0..5800c5ad830 100644 --- a/tests/test-webapps/test-proxy-webapp/pom.xml +++ b/tests/test-webapps/test-proxy-webapp/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.tests test-webapps-parent - 9.4.34-SNAPSHOT + 9.4.34.v20201102 ../pom.xml 4.0.0 diff --git a/tests/test-webapps/test-servlet-spec/pom.xml b/tests/test-webapps/test-servlet-spec/pom.xml index 69857dcb9d7..54739c314b2 100644 --- a/tests/test-webapps/test-servlet-spec/pom.xml +++ b/tests/test-webapps/test-servlet-spec/pom.xml @@ -4,7 +4,7 @@ org.eclipse.jetty.tests test-webapps-parent - 9.4.34-SNAPSHOT + 9.4.34.v20201102 test-servlet-spec-parent Jetty Tests :: Spec Test WebApp :: Parent diff --git a/tests/test-webapps/test-servlet-spec/test-container-initializer/pom.xml b/tests/test-webapps/test-servlet-spec/test-container-initializer/pom.xml index 97118c437ee..a3655acfe28 100644 --- a/tests/test-webapps/test-servlet-spec/test-container-initializer/pom.xml +++ b/tests/test-webapps/test-servlet-spec/test-container-initializer/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.tests test-servlet-spec-parent - 9.4.34-SNAPSHOT + 9.4.34.v20201102 test-container-initializer jar diff --git a/tests/test-webapps/test-servlet-spec/test-spec-webapp/pom.xml b/tests/test-webapps/test-servlet-spec/test-spec-webapp/pom.xml index b0f04aa29e1..8acd6d42edc 100644 --- a/tests/test-webapps/test-servlet-spec/test-spec-webapp/pom.xml +++ b/tests/test-webapps/test-servlet-spec/test-spec-webapp/pom.xml @@ -4,7 +4,7 @@ org.eclipse.jetty.tests test-servlet-spec-parent - 9.4.34-SNAPSHOT + 9.4.34.v20201102 Jetty Tests :: Webapps :: Spec Webapp test-spec-webapp diff --git a/tests/test-webapps/test-servlet-spec/test-web-fragment/pom.xml b/tests/test-webapps/test-servlet-spec/test-web-fragment/pom.xml index cc8bfc47d5d..e95534d0df5 100644 --- a/tests/test-webapps/test-servlet-spec/test-web-fragment/pom.xml +++ b/tests/test-webapps/test-servlet-spec/test-web-fragment/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.tests test-servlet-spec-parent - 9.4.34-SNAPSHOT + 9.4.34.v20201102 Jetty Tests :: WebApp :: Servlet Spec :: Fragment Jar diff --git a/tests/test-webapps/test-simple-webapp/pom.xml b/tests/test-webapps/test-simple-webapp/pom.xml index ba7d13d5820..7e5c3b93ae5 100644 --- a/tests/test-webapps/test-simple-webapp/pom.xml +++ b/tests/test-webapps/test-simple-webapp/pom.xml @@ -4,7 +4,7 @@ org.eclipse.jetty.tests test-webapps-parent - 9.4.34-SNAPSHOT + 9.4.34.v20201102 test-simple-webapp diff --git a/tests/test-webapps/test-webapp-rfc2616/pom.xml b/tests/test-webapps/test-webapp-rfc2616/pom.xml index 3f84697ad40..7abc2755ce9 100644 --- a/tests/test-webapps/test-webapp-rfc2616/pom.xml +++ b/tests/test-webapps/test-webapp-rfc2616/pom.xml @@ -4,7 +4,7 @@ org.eclipse.jetty.tests test-webapps-parent - 9.4.34-SNAPSHOT + 9.4.34.v20201102 test-webapp-rfc2616 Jetty Tests :: WebApp :: RFC2616 diff --git a/tests/test-webapps/test-websocket-client-provided-webapp/pom.xml b/tests/test-webapps/test-websocket-client-provided-webapp/pom.xml index 9f57388e81e..b7c6ad7a106 100644 --- a/tests/test-webapps/test-websocket-client-provided-webapp/pom.xml +++ b/tests/test-webapps/test-websocket-client-provided-webapp/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.tests test-webapps-parent - 9.4.34-SNAPSHOT + 9.4.34.v20201102 4.0.0 diff --git a/tests/test-webapps/test-websocket-client-webapp/pom.xml b/tests/test-webapps/test-websocket-client-webapp/pom.xml index d4e41a770ec..1962337b764 100644 --- a/tests/test-webapps/test-websocket-client-webapp/pom.xml +++ b/tests/test-webapps/test-websocket-client-webapp/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.tests test-webapps-parent - 9.4.34-SNAPSHOT + 9.4.34.v20201102 4.0.0 diff --git a/tests/test-webapps/test-weld-cdi-webapp/pom.xml b/tests/test-webapps/test-weld-cdi-webapp/pom.xml index a72ad4b0d17..7ddacf0d0bb 100644 --- a/tests/test-webapps/test-weld-cdi-webapp/pom.xml +++ b/tests/test-webapps/test-weld-cdi-webapp/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.tests test-webapps-parent - 9.4.34-SNAPSHOT + 9.4.34.v20201102 4.0.0 From edaadff55b5ba884272c622ce659ca0ec72b5431 Mon Sep 17 00:00:00 2001 From: Joakim Erdfelt Date: Mon, 2 Nov 2020 09:04:51 -0600 Subject: [PATCH 10/25] Updating to version 9.4.35-SNAPSHOT --- VERSION.txt | 2 + aggregates/jetty-all-compact3/pom.xml | 2 +- aggregates/jetty-all/pom.xml | 2 +- apache-jsp/pom.xml | 2 +- apache-jstl/pom.xml | 2 +- build-resources/pom.xml | 2 +- examples/async-rest/async-rest-jar/pom.xml | 2 +- examples/async-rest/async-rest-webapp/pom.xml | 2 +- examples/async-rest/pom.xml | 2 +- examples/embedded/pom.xml | 2 +- examples/pom.xml | 2 +- jetty-alpn/jetty-alpn-client/pom.xml | 2 +- .../jetty-alpn-conscrypt-client/pom.xml | 2 +- .../jetty-alpn-conscrypt-server/pom.xml | 2 +- jetty-alpn/jetty-alpn-java-client/pom.xml | 2 +- jetty-alpn/jetty-alpn-java-server/pom.xml | 2 +- jetty-alpn/jetty-alpn-openjdk8-client/pom.xml | 2 +- jetty-alpn/jetty-alpn-openjdk8-server/pom.xml | 2 +- jetty-alpn/jetty-alpn-server/pom.xml | 2 +- jetty-alpn/pom.xml | 2 +- jetty-annotations/pom.xml | 2 +- jetty-ant/pom.xml | 2 +- jetty-bom/pom.xml | 134 +++++++++--------- jetty-cdi/pom.xml | 2 +- jetty-client/pom.xml | 2 +- jetty-continuation/pom.xml | 2 +- jetty-deploy/pom.xml | 2 +- jetty-distribution/pom.xml | 2 +- jetty-documentation/pom.xml | 2 +- jetty-fcgi/fcgi-client/pom.xml | 2 +- jetty-fcgi/fcgi-server/pom.xml | 2 +- jetty-fcgi/pom.xml | 2 +- .../jetty-gcloud-session-manager/pom.xml | 2 +- jetty-gcloud/pom.xml | 2 +- jetty-hazelcast/pom.xml | 2 +- jetty-home/pom.xml | 2 +- jetty-http-spi/pom.xml | 2 +- jetty-http/pom.xml | 2 +- jetty-http2/http2-alpn-tests/pom.xml | 2 +- jetty-http2/http2-client/pom.xml | 2 +- jetty-http2/http2-common/pom.xml | 2 +- jetty-http2/http2-hpack/pom.xml | 2 +- .../http2-http-client-transport/pom.xml | 2 +- jetty-http2/http2-server/pom.xml | 2 +- jetty-http2/pom.xml | 2 +- jetty-infinispan/infinispan-common/pom.xml | 2 +- .../infinispan-embedded-query/pom.xml | 2 +- jetty-infinispan/infinispan-embedded/pom.xml | 2 +- .../infinispan-remote-query/pom.xml | 2 +- jetty-infinispan/infinispan-remote/pom.xml | 2 +- jetty-infinispan/pom.xml | 2 +- jetty-io/pom.xml | 2 +- jetty-jaas/pom.xml | 2 +- jetty-jaspi/pom.xml | 2 +- jetty-jmh/pom.xml | 2 +- jetty-jmx/pom.xml | 2 +- jetty-jndi/pom.xml | 2 +- jetty-jspc-maven-plugin/pom.xml | 2 +- jetty-maven-plugin/pom.xml | 2 +- .../jetty-memcached-sessions/pom.xml | 2 +- jetty-memcached/pom.xml | 2 +- jetty-nosql/pom.xml | 2 +- jetty-openid/pom.xml | 2 +- jetty-osgi/jetty-osgi-alpn/pom.xml | 2 +- jetty-osgi/jetty-osgi-boot-jsp/pom.xml | 2 +- jetty-osgi/jetty-osgi-boot-warurl/pom.xml | 2 +- jetty-osgi/jetty-osgi-boot/pom.xml | 2 +- jetty-osgi/jetty-osgi-httpservice/pom.xml | 2 +- jetty-osgi/pom.xml | 2 +- jetty-osgi/test-jetty-osgi-context/pom.xml | 2 +- jetty-osgi/test-jetty-osgi-fragment/pom.xml | 2 +- jetty-osgi/test-jetty-osgi-server/pom.xml | 2 +- .../test-jetty-osgi-webapp-resources/pom.xml | 2 +- jetty-osgi/test-jetty-osgi-webapp/pom.xml | 2 +- jetty-osgi/test-jetty-osgi/pom.xml | 2 +- jetty-plus/pom.xml | 2 +- jetty-proxy/pom.xml | 2 +- jetty-quickstart/pom.xml | 2 +- jetty-rewrite/pom.xml | 2 +- jetty-runner/pom.xml | 2 +- jetty-security/pom.xml | 2 +- jetty-server/pom.xml | 2 +- jetty-servlet/pom.xml | 2 +- jetty-servlets/pom.xml | 2 +- jetty-spring/pom.xml | 2 +- jetty-start/pom.xml | 2 +- jetty-unixsocket/pom.xml | 2 +- jetty-util-ajax/pom.xml | 2 +- jetty-util/pom.xml | 2 +- jetty-webapp/pom.xml | 2 +- .../javax-websocket-client-impl/pom.xml | 2 +- .../javax-websocket-server-impl/pom.xml | 2 +- jetty-websocket/jetty-websocket-tests/pom.xml | 2 +- jetty-websocket/pom.xml | 2 +- jetty-websocket/websocket-api/pom.xml | 2 +- jetty-websocket/websocket-client/pom.xml | 2 +- jetty-websocket/websocket-common/pom.xml | 2 +- jetty-websocket/websocket-server/pom.xml | 2 +- jetty-websocket/websocket-servlet/pom.xml | 2 +- jetty-xml/pom.xml | 2 +- pom.xml | 2 +- tests/pom.xml | 2 +- tests/test-continuation/pom.xml | 2 +- tests/test-distribution/pom.xml | 2 +- tests/test-http-client-transport/pom.xml | 2 +- tests/test-integration/pom.xml | 2 +- tests/test-jmx/jmx-webapp-it/pom.xml | 2 +- tests/test-jmx/jmx-webapp/pom.xml | 2 +- tests/test-jmx/pom.xml | 2 +- tests/test-loginservice/pom.xml | 2 +- tests/test-quickstart/pom.xml | 2 +- tests/test-sessions/pom.xml | 2 +- .../test-sessions/test-file-sessions/pom.xml | 2 +- .../test-gcloud-sessions/pom.xml | 2 +- .../test-hazelcast-sessions/pom.xml | 2 +- .../test-infinispan-sessions/pom.xml | 2 +- .../test-sessions/test-jdbc-sessions/pom.xml | 2 +- .../test-memcached-sessions/pom.xml | 2 +- .../test-mongodb-sessions/pom.xml | 2 +- .../test-sessions-common/pom.xml | 2 +- tests/test-webapps/pom.xml | 2 +- .../test-cdi-common-webapp/pom.xml | 2 +- tests/test-webapps/test-felix-webapp/pom.xml | 2 +- tests/test-webapps/test-http2-webapp/pom.xml | 2 +- tests/test-webapps/test-jaas-webapp/pom.xml | 2 +- tests/test-webapps/test-jetty-webapp/pom.xml | 2 +- tests/test-webapps/test-jndi-webapp/pom.xml | 2 +- .../test-webapps/test-mock-resources/pom.xml | 2 +- .../test-webapps/test-owb-cdi-webapp/pom.xml | 2 +- tests/test-webapps/test-proxy-webapp/pom.xml | 2 +- tests/test-webapps/test-servlet-spec/pom.xml | 2 +- .../test-container-initializer/pom.xml | 2 +- .../test-spec-webapp/pom.xml | 2 +- .../test-web-fragment/pom.xml | 2 +- tests/test-webapps/test-simple-webapp/pom.xml | 2 +- .../test-webapps/test-webapp-rfc2616/pom.xml | 2 +- .../pom.xml | 2 +- .../test-websocket-client-webapp/pom.xml | 2 +- .../test-webapps/test-weld-cdi-webapp/pom.xml | 2 +- 139 files changed, 206 insertions(+), 204 deletions(-) diff --git a/VERSION.txt b/VERSION.txt index b616a9a89ec..6e5594947fd 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1,3 +1,5 @@ +jetty-9.4.35-SNAPSHOT + jetty-9.4.34.v20201102 - 02 November 2020 + 5320 Using WebSocketClient with jetty-websocket-httpclient.xml in a Jetty web application causes ClassCastException diff --git a/aggregates/jetty-all-compact3/pom.xml b/aggregates/jetty-all-compact3/pom.xml index 8220204de21..8f7a3e9d725 100644 --- a/aggregates/jetty-all-compact3/pom.xml +++ b/aggregates/jetty-all-compact3/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.34.v20201102 + 9.4.35-SNAPSHOT ../../pom.xml 4.0.0 diff --git a/aggregates/jetty-all/pom.xml b/aggregates/jetty-all/pom.xml index 4ea53b077a4..dc1c09c011b 100644 --- a/aggregates/jetty-all/pom.xml +++ b/aggregates/jetty-all/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.34.v20201102 + 9.4.35-SNAPSHOT ../../pom.xml 4.0.0 diff --git a/apache-jsp/pom.xml b/apache-jsp/pom.xml index f0c948c8609..b96ca00c47c 100644 --- a/apache-jsp/pom.xml +++ b/apache-jsp/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.34.v20201102 + 9.4.35-SNAPSHOT 4.0.0 apache-jsp diff --git a/apache-jstl/pom.xml b/apache-jstl/pom.xml index 8326711ad5b..8aea2af5252 100644 --- a/apache-jstl/pom.xml +++ b/apache-jstl/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.34.v20201102 + 9.4.35-SNAPSHOT 4.0.0 apache-jstl diff --git a/build-resources/pom.xml b/build-resources/pom.xml index bc516653a03..c310358282b 100644 --- a/build-resources/pom.xml +++ b/build-resources/pom.xml @@ -2,7 +2,7 @@ 4.0.0 org.eclipse.jetty build-resources - 9.4.34.v20201102 + 9.4.35-SNAPSHOT jar Jetty :: Build Resources diff --git a/examples/async-rest/async-rest-jar/pom.xml b/examples/async-rest/async-rest-jar/pom.xml index 215bd7f1055..bf36e23152d 100644 --- a/examples/async-rest/async-rest-jar/pom.xml +++ b/examples/async-rest/async-rest-jar/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty example-async-rest - 9.4.34.v20201102 + 9.4.35-SNAPSHOT 4.0.0 diff --git a/examples/async-rest/async-rest-webapp/pom.xml b/examples/async-rest/async-rest-webapp/pom.xml index 9e4568f19b0..d23c5ec61a5 100644 --- a/examples/async-rest/async-rest-webapp/pom.xml +++ b/examples/async-rest/async-rest-webapp/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty example-async-rest - 9.4.34.v20201102 + 9.4.35-SNAPSHOT 4.0.0 diff --git a/examples/async-rest/pom.xml b/examples/async-rest/pom.xml index 1cc07209b7f..f7851b7e1a3 100644 --- a/examples/async-rest/pom.xml +++ b/examples/async-rest/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty.examples examples-parent - 9.4.34.v20201102 + 9.4.35-SNAPSHOT 4.0.0 diff --git a/examples/embedded/pom.xml b/examples/embedded/pom.xml index 623cd669d3b..f0040539f81 100644 --- a/examples/embedded/pom.xml +++ b/examples/embedded/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty.examples examples-parent - 9.4.34.v20201102 + 9.4.35-SNAPSHOT ../pom.xml 4.0.0 diff --git a/examples/pom.xml b/examples/pom.xml index da6bd76972c..20b01606aea 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty jetty-project - 9.4.34.v20201102 + 9.4.35-SNAPSHOT 4.0.0 diff --git a/jetty-alpn/jetty-alpn-client/pom.xml b/jetty-alpn/jetty-alpn-client/pom.xml index 182b11be917..fe779ebee0a 100644 --- a/jetty-alpn/jetty-alpn-client/pom.xml +++ b/jetty-alpn/jetty-alpn-client/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-alpn-parent - 9.4.34.v20201102 + 9.4.35-SNAPSHOT 4.0.0 jetty-alpn-client diff --git a/jetty-alpn/jetty-alpn-conscrypt-client/pom.xml b/jetty-alpn/jetty-alpn-conscrypt-client/pom.xml index d970721525b..86edf9a28a8 100644 --- a/jetty-alpn/jetty-alpn-conscrypt-client/pom.xml +++ b/jetty-alpn/jetty-alpn-conscrypt-client/pom.xml @@ -4,7 +4,7 @@ org.eclipse.jetty jetty-alpn-parent - 9.4.34.v20201102 + 9.4.35-SNAPSHOT 4.0.0 diff --git a/jetty-alpn/jetty-alpn-conscrypt-server/pom.xml b/jetty-alpn/jetty-alpn-conscrypt-server/pom.xml index f141fb5296f..40b0ba47688 100644 --- a/jetty-alpn/jetty-alpn-conscrypt-server/pom.xml +++ b/jetty-alpn/jetty-alpn-conscrypt-server/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty jetty-alpn-parent - 9.4.34.v20201102 + 9.4.35-SNAPSHOT 4.0.0 diff --git a/jetty-alpn/jetty-alpn-java-client/pom.xml b/jetty-alpn/jetty-alpn-java-client/pom.xml index 671287e7c15..4af11b0c1d7 100644 --- a/jetty-alpn/jetty-alpn-java-client/pom.xml +++ b/jetty-alpn/jetty-alpn-java-client/pom.xml @@ -4,7 +4,7 @@ org.eclipse.jetty jetty-alpn-parent - 9.4.34.v20201102 + 9.4.35-SNAPSHOT 4.0.0 diff --git a/jetty-alpn/jetty-alpn-java-server/pom.xml b/jetty-alpn/jetty-alpn-java-server/pom.xml index 580cef0929e..c588a705e9d 100644 --- a/jetty-alpn/jetty-alpn-java-server/pom.xml +++ b/jetty-alpn/jetty-alpn-java-server/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty jetty-alpn-parent - 9.4.34.v20201102 + 9.4.35-SNAPSHOT 4.0.0 diff --git a/jetty-alpn/jetty-alpn-openjdk8-client/pom.xml b/jetty-alpn/jetty-alpn-openjdk8-client/pom.xml index 9406aa37f9f..36e5fc83061 100644 --- a/jetty-alpn/jetty-alpn-openjdk8-client/pom.xml +++ b/jetty-alpn/jetty-alpn-openjdk8-client/pom.xml @@ -4,7 +4,7 @@ org.eclipse.jetty jetty-alpn-parent - 9.4.34.v20201102 + 9.4.35-SNAPSHOT 4.0.0 diff --git a/jetty-alpn/jetty-alpn-openjdk8-server/pom.xml b/jetty-alpn/jetty-alpn-openjdk8-server/pom.xml index 7b1c5e680de..e19fcd35f96 100644 --- a/jetty-alpn/jetty-alpn-openjdk8-server/pom.xml +++ b/jetty-alpn/jetty-alpn-openjdk8-server/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty jetty-alpn-parent - 9.4.34.v20201102 + 9.4.35-SNAPSHOT 4.0.0 diff --git a/jetty-alpn/jetty-alpn-server/pom.xml b/jetty-alpn/jetty-alpn-server/pom.xml index 2f387f701f1..ea6d3cc3126 100644 --- a/jetty-alpn/jetty-alpn-server/pom.xml +++ b/jetty-alpn/jetty-alpn-server/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-alpn-parent - 9.4.34.v20201102 + 9.4.35-SNAPSHOT 4.0.0 jetty-alpn-server diff --git a/jetty-alpn/pom.xml b/jetty-alpn/pom.xml index 04fc37fa8ca..45eaef69070 100644 --- a/jetty-alpn/pom.xml +++ b/jetty-alpn/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.34.v20201102 + 9.4.35-SNAPSHOT 4.0.0 jetty-alpn-parent diff --git a/jetty-annotations/pom.xml b/jetty-annotations/pom.xml index 82bffad63f1..d0d335f9fc5 100644 --- a/jetty-annotations/pom.xml +++ b/jetty-annotations/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.34.v20201102 + 9.4.35-SNAPSHOT 4.0.0 jetty-annotations diff --git a/jetty-ant/pom.xml b/jetty-ant/pom.xml index a9536884966..210099d1ef1 100644 --- a/jetty-ant/pom.xml +++ b/jetty-ant/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.34.v20201102 + 9.4.35-SNAPSHOT 4.0.0 jetty-ant diff --git a/jetty-bom/pom.xml b/jetty-bom/pom.xml index 20e2fcba358..1715e8f3fd2 100644 --- a/jetty-bom/pom.xml +++ b/jetty-bom/pom.xml @@ -9,7 +9,7 @@ org.eclipse.jetty jetty-project - 9.4.34.v20201102 + 9.4.35-SNAPSHOT @@ -53,336 +53,336 @@ org.eclipse.jetty apache-jsp - 9.4.34.v20201102 + 9.4.35-SNAPSHOT org.eclipse.jetty apache-jstl - 9.4.34.v20201102 + 9.4.35-SNAPSHOT org.eclipse.jetty jetty-alpn-client - 9.4.34.v20201102 + 9.4.35-SNAPSHOT org.eclipse.jetty jetty-alpn-java-client - 9.4.34.v20201102 + 9.4.35-SNAPSHOT org.eclipse.jetty jetty-alpn-java-server - 9.4.34.v20201102 + 9.4.35-SNAPSHOT org.eclipse.jetty jetty-alpn-openjdk8-client - 9.4.34.v20201102 + 9.4.35-SNAPSHOT org.eclipse.jetty jetty-alpn-openjdk8-server - 9.4.34.v20201102 + 9.4.35-SNAPSHOT org.eclipse.jetty jetty-alpn-conscrypt-client - 9.4.34.v20201102 + 9.4.35-SNAPSHOT org.eclipse.jetty jetty-alpn-conscrypt-server - 9.4.34.v20201102 + 9.4.35-SNAPSHOT org.eclipse.jetty jetty-alpn-server - 9.4.34.v20201102 + 9.4.35-SNAPSHOT org.eclipse.jetty jetty-annotations - 9.4.34.v20201102 + 9.4.35-SNAPSHOT org.eclipse.jetty jetty-ant - 9.4.34.v20201102 + 9.4.35-SNAPSHOT org.eclipse.jetty jetty-client - 9.4.34.v20201102 + 9.4.35-SNAPSHOT org.eclipse.jetty jetty-continuation - 9.4.34.v20201102 + 9.4.35-SNAPSHOT org.eclipse.jetty jetty-deploy - 9.4.34.v20201102 + 9.4.35-SNAPSHOT org.eclipse.jetty jetty-distribution - 9.4.34.v20201102 + 9.4.35-SNAPSHOT zip org.eclipse.jetty jetty-distribution - 9.4.34.v20201102 + 9.4.35-SNAPSHOT tar.gz org.eclipse.jetty.fcgi fcgi-client - 9.4.34.v20201102 + 9.4.35-SNAPSHOT org.eclipse.jetty.fcgi fcgi-server - 9.4.34.v20201102 + 9.4.35-SNAPSHOT org.eclipse.jetty.gcloud jetty-gcloud-session-manager - 9.4.34.v20201102 + 9.4.35-SNAPSHOT org.eclipse.jetty jetty-home - 9.4.34.v20201102 + 9.4.35-SNAPSHOT zip org.eclipse.jetty jetty-home - 9.4.34.v20201102 + 9.4.35-SNAPSHOT tar.gz org.eclipse.jetty jetty-http - 9.4.34.v20201102 + 9.4.35-SNAPSHOT org.eclipse.jetty.http2 http2-client - 9.4.34.v20201102 + 9.4.35-SNAPSHOT org.eclipse.jetty.http2 http2-common - 9.4.34.v20201102 + 9.4.35-SNAPSHOT org.eclipse.jetty.http2 http2-hpack - 9.4.34.v20201102 + 9.4.35-SNAPSHOT org.eclipse.jetty.http2 http2-http-client-transport - 9.4.34.v20201102 + 9.4.35-SNAPSHOT org.eclipse.jetty.http2 http2-server - 9.4.34.v20201102 + 9.4.35-SNAPSHOT org.eclipse.jetty jetty-http-spi - 9.4.34.v20201102 + 9.4.35-SNAPSHOT org.eclipse.jetty infinispan-common - 9.4.34.v20201102 + 9.4.35-SNAPSHOT org.eclipse.jetty infinispan-remote-query - 9.4.34.v20201102 + 9.4.35-SNAPSHOT org.eclipse.jetty infinispan-embedded-query - 9.4.34.v20201102 + 9.4.35-SNAPSHOT org.eclipse.jetty jetty-hazelcast - 9.4.34.v20201102 + 9.4.35-SNAPSHOT org.eclipse.jetty jetty-io - 9.4.34.v20201102 + 9.4.35-SNAPSHOT org.eclipse.jetty jetty-jaas - 9.4.34.v20201102 + 9.4.35-SNAPSHOT org.eclipse.jetty jetty-jaspi - 9.4.34.v20201102 + 9.4.35-SNAPSHOT org.eclipse.jetty jetty-jmx - 9.4.34.v20201102 + 9.4.35-SNAPSHOT org.eclipse.jetty jetty-jndi - 9.4.34.v20201102 + 9.4.35-SNAPSHOT org.eclipse.jetty.memcached jetty-memcached-sessions - 9.4.34.v20201102 + 9.4.35-SNAPSHOT org.eclipse.jetty jetty-nosql - 9.4.34.v20201102 + 9.4.35-SNAPSHOT org.eclipse.jetty.osgi jetty-osgi-boot - 9.4.34.v20201102 + 9.4.35-SNAPSHOT org.eclipse.jetty.osgi jetty-osgi-boot-jsp - 9.4.34.v20201102 + 9.4.35-SNAPSHOT org.eclipse.jetty.osgi jetty-osgi-boot-warurl - 9.4.34.v20201102 + 9.4.35-SNAPSHOT org.eclipse.jetty.osgi jetty-httpservice - 9.4.34.v20201102 + 9.4.35-SNAPSHOT org.eclipse.jetty jetty-plus - 9.4.34.v20201102 + 9.4.35-SNAPSHOT org.eclipse.jetty jetty-proxy - 9.4.34.v20201102 + 9.4.35-SNAPSHOT org.eclipse.jetty jetty-quickstart - 9.4.34.v20201102 + 9.4.35-SNAPSHOT org.eclipse.jetty jetty-rewrite - 9.4.34.v20201102 + 9.4.35-SNAPSHOT org.eclipse.jetty jetty-security - 9.4.34.v20201102 + 9.4.35-SNAPSHOT org.eclipse.jetty jetty-openid - 9.4.34.v20201102 + 9.4.35-SNAPSHOT org.eclipse.jetty jetty-server - 9.4.34.v20201102 + 9.4.35-SNAPSHOT org.eclipse.jetty jetty-servlet - 9.4.34.v20201102 + 9.4.35-SNAPSHOT org.eclipse.jetty jetty-servlets - 9.4.34.v20201102 + 9.4.35-SNAPSHOT org.eclipse.jetty jetty-spring - 9.4.34.v20201102 + 9.4.35-SNAPSHOT org.eclipse.jetty jetty-unixsocket - 9.4.34.v20201102 + 9.4.35-SNAPSHOT org.eclipse.jetty jetty-util - 9.4.34.v20201102 + 9.4.35-SNAPSHOT org.eclipse.jetty jetty-util-ajax - 9.4.34.v20201102 + 9.4.35-SNAPSHOT org.eclipse.jetty jetty-webapp - 9.4.34.v20201102 + 9.4.35-SNAPSHOT org.eclipse.jetty.websocket javax-websocket-client-impl - 9.4.34.v20201102 + 9.4.35-SNAPSHOT org.eclipse.jetty.websocket javax-websocket-server-impl - 9.4.34.v20201102 + 9.4.35-SNAPSHOT org.eclipse.jetty.websocket websocket-api - 9.4.34.v20201102 + 9.4.35-SNAPSHOT org.eclipse.jetty.websocket websocket-client - 9.4.34.v20201102 + 9.4.35-SNAPSHOT org.eclipse.jetty.websocket websocket-common - 9.4.34.v20201102 + 9.4.35-SNAPSHOT org.eclipse.jetty.websocket websocket-server - 9.4.34.v20201102 + 9.4.35-SNAPSHOT org.eclipse.jetty.websocket websocket-servlet - 9.4.34.v20201102 + 9.4.35-SNAPSHOT org.eclipse.jetty jetty-xml - 9.4.34.v20201102 + 9.4.35-SNAPSHOT diff --git a/jetty-cdi/pom.xml b/jetty-cdi/pom.xml index eed89d15c94..9cd215d9bda 100644 --- a/jetty-cdi/pom.xml +++ b/jetty-cdi/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.34.v20201102 + 9.4.35-SNAPSHOT 4.0.0 org.eclipse.jetty diff --git a/jetty-client/pom.xml b/jetty-client/pom.xml index 8e323733a51..e11a6e2c6b2 100644 --- a/jetty-client/pom.xml +++ b/jetty-client/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.34.v20201102 + 9.4.35-SNAPSHOT 4.0.0 diff --git a/jetty-continuation/pom.xml b/jetty-continuation/pom.xml index 17eafb54b3f..f004890a149 100644 --- a/jetty-continuation/pom.xml +++ b/jetty-continuation/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.34.v20201102 + 9.4.35-SNAPSHOT 4.0.0 jetty-continuation diff --git a/jetty-deploy/pom.xml b/jetty-deploy/pom.xml index 3ca3fd1ad6f..42defc19f98 100644 --- a/jetty-deploy/pom.xml +++ b/jetty-deploy/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.34.v20201102 + 9.4.35-SNAPSHOT 4.0.0 jetty-deploy diff --git a/jetty-distribution/pom.xml b/jetty-distribution/pom.xml index e37c16044ad..c05f9996251 100644 --- a/jetty-distribution/pom.xml +++ b/jetty-distribution/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.34.v20201102 + 9.4.35-SNAPSHOT 4.0.0 jetty-distribution diff --git a/jetty-documentation/pom.xml b/jetty-documentation/pom.xml index 1ef087c1585..69af45cad11 100644 --- a/jetty-documentation/pom.xml +++ b/jetty-documentation/pom.xml @@ -4,7 +4,7 @@ org.eclipse.jetty jetty-project - 9.4.34.v20201102 + 9.4.35-SNAPSHOT jetty-documentation Jetty :: Documentation diff --git a/jetty-fcgi/fcgi-client/pom.xml b/jetty-fcgi/fcgi-client/pom.xml index eefac290eb6..98e02b7284b 100644 --- a/jetty-fcgi/fcgi-client/pom.xml +++ b/jetty-fcgi/fcgi-client/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.fcgi fcgi-parent - 9.4.34.v20201102 + 9.4.35-SNAPSHOT 4.0.0 diff --git a/jetty-fcgi/fcgi-server/pom.xml b/jetty-fcgi/fcgi-server/pom.xml index 7ec037f70b0..ee9dfc22827 100644 --- a/jetty-fcgi/fcgi-server/pom.xml +++ b/jetty-fcgi/fcgi-server/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.fcgi fcgi-parent - 9.4.34.v20201102 + 9.4.35-SNAPSHOT 4.0.0 diff --git a/jetty-fcgi/pom.xml b/jetty-fcgi/pom.xml index c4427462911..73b504ff749 100644 --- a/jetty-fcgi/pom.xml +++ b/jetty-fcgi/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty jetty-project - 9.4.34.v20201102 + 9.4.35-SNAPSHOT 4.0.0 diff --git a/jetty-gcloud/jetty-gcloud-session-manager/pom.xml b/jetty-gcloud/jetty-gcloud-session-manager/pom.xml index c8c54e843e7..fd470b725ee 100644 --- a/jetty-gcloud/jetty-gcloud-session-manager/pom.xml +++ b/jetty-gcloud/jetty-gcloud-session-manager/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.gcloud gcloud-parent - 9.4.34.v20201102 + 9.4.35-SNAPSHOT 4.0.0 diff --git a/jetty-gcloud/pom.xml b/jetty-gcloud/pom.xml index 1061ff2eae2..fb45d489a03 100644 --- a/jetty-gcloud/pom.xml +++ b/jetty-gcloud/pom.xml @@ -3,7 +3,7 @@ jetty-project org.eclipse.jetty - 9.4.34.v20201102 + 9.4.35-SNAPSHOT 4.0.0 diff --git a/jetty-hazelcast/pom.xml b/jetty-hazelcast/pom.xml index 94e150f5b96..038e94f23d6 100644 --- a/jetty-hazelcast/pom.xml +++ b/jetty-hazelcast/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty jetty-project - 9.4.34.v20201102 + 9.4.35-SNAPSHOT 4.0.0 diff --git a/jetty-home/pom.xml b/jetty-home/pom.xml index 6267c0047dd..cff85999334 100644 --- a/jetty-home/pom.xml +++ b/jetty-home/pom.xml @@ -3,7 +3,7 @@ jetty-project org.eclipse.jetty - 9.4.34.v20201102 + 9.4.35-SNAPSHOT 4.0.0 jetty-home diff --git a/jetty-http-spi/pom.xml b/jetty-http-spi/pom.xml index 93f906b4cd8..abf2a2bf306 100644 --- a/jetty-http-spi/pom.xml +++ b/jetty-http-spi/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.34.v20201102 + 9.4.35-SNAPSHOT 4.0.0 jetty-http-spi diff --git a/jetty-http/pom.xml b/jetty-http/pom.xml index 395c271e0ba..870ac2e6a38 100644 --- a/jetty-http/pom.xml +++ b/jetty-http/pom.xml @@ -3,7 +3,7 @@ jetty-project org.eclipse.jetty - 9.4.34.v20201102 + 9.4.35-SNAPSHOT 4.0.0 jetty-http diff --git a/jetty-http2/http2-alpn-tests/pom.xml b/jetty-http2/http2-alpn-tests/pom.xml index 33cebbcade4..d56dae3c83f 100644 --- a/jetty-http2/http2-alpn-tests/pom.xml +++ b/jetty-http2/http2-alpn-tests/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.http2 http2-parent - 9.4.34.v20201102 + 9.4.35-SNAPSHOT 4.0.0 diff --git a/jetty-http2/http2-client/pom.xml b/jetty-http2/http2-client/pom.xml index badb0dddc1f..56d7ef0fe9d 100644 --- a/jetty-http2/http2-client/pom.xml +++ b/jetty-http2/http2-client/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.http2 http2-parent - 9.4.34.v20201102 + 9.4.35-SNAPSHOT 4.0.0 diff --git a/jetty-http2/http2-common/pom.xml b/jetty-http2/http2-common/pom.xml index 91ca3bff273..96e5cd39173 100644 --- a/jetty-http2/http2-common/pom.xml +++ b/jetty-http2/http2-common/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.http2 http2-parent - 9.4.34.v20201102 + 9.4.35-SNAPSHOT 4.0.0 diff --git a/jetty-http2/http2-hpack/pom.xml b/jetty-http2/http2-hpack/pom.xml index 1a21f5ba175..c53a31b8e8a 100644 --- a/jetty-http2/http2-hpack/pom.xml +++ b/jetty-http2/http2-hpack/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.http2 http2-parent - 9.4.34.v20201102 + 9.4.35-SNAPSHOT 4.0.0 diff --git a/jetty-http2/http2-http-client-transport/pom.xml b/jetty-http2/http2-http-client-transport/pom.xml index e4b4233bbed..490cf39557a 100644 --- a/jetty-http2/http2-http-client-transport/pom.xml +++ b/jetty-http2/http2-http-client-transport/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.http2 http2-parent - 9.4.34.v20201102 + 9.4.35-SNAPSHOT 4.0.0 diff --git a/jetty-http2/http2-server/pom.xml b/jetty-http2/http2-server/pom.xml index 406a871567c..3d5f9df12d1 100644 --- a/jetty-http2/http2-server/pom.xml +++ b/jetty-http2/http2-server/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.http2 http2-parent - 9.4.34.v20201102 + 9.4.35-SNAPSHOT 4.0.0 diff --git a/jetty-http2/pom.xml b/jetty-http2/pom.xml index 1e4b6841536..f62f8b34043 100644 --- a/jetty-http2/pom.xml +++ b/jetty-http2/pom.xml @@ -3,7 +3,7 @@ jetty-project org.eclipse.jetty - 9.4.34.v20201102 + 9.4.35-SNAPSHOT 4.0.0 diff --git a/jetty-infinispan/infinispan-common/pom.xml b/jetty-infinispan/infinispan-common/pom.xml index 2c8aec025b6..18a83003868 100644 --- a/jetty-infinispan/infinispan-common/pom.xml +++ b/jetty-infinispan/infinispan-common/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty infinispan-parent - 9.4.34.v20201102 + 9.4.35-SNAPSHOT 4.0.0 infinispan-common diff --git a/jetty-infinispan/infinispan-embedded-query/pom.xml b/jetty-infinispan/infinispan-embedded-query/pom.xml index fbfea31e741..e6143092de6 100644 --- a/jetty-infinispan/infinispan-embedded-query/pom.xml +++ b/jetty-infinispan/infinispan-embedded-query/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty infinispan-parent - 9.4.34.v20201102 + 9.4.35-SNAPSHOT 4.0.0 infinispan-embedded-query diff --git a/jetty-infinispan/infinispan-embedded/pom.xml b/jetty-infinispan/infinispan-embedded/pom.xml index 15e8f396cbe..34d2b319386 100644 --- a/jetty-infinispan/infinispan-embedded/pom.xml +++ b/jetty-infinispan/infinispan-embedded/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty infinispan-parent - 9.4.34.v20201102 + 9.4.35-SNAPSHOT 4.0.0 infinispan-embedded diff --git a/jetty-infinispan/infinispan-remote-query/pom.xml b/jetty-infinispan/infinispan-remote-query/pom.xml index ff8a81ffcae..171e01935d8 100644 --- a/jetty-infinispan/infinispan-remote-query/pom.xml +++ b/jetty-infinispan/infinispan-remote-query/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty infinispan-parent - 9.4.34.v20201102 + 9.4.35-SNAPSHOT 4.0.0 infinispan-remote-query diff --git a/jetty-infinispan/infinispan-remote/pom.xml b/jetty-infinispan/infinispan-remote/pom.xml index 9920c7c7650..0ab9effa08f 100644 --- a/jetty-infinispan/infinispan-remote/pom.xml +++ b/jetty-infinispan/infinispan-remote/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty infinispan-parent - 9.4.34.v20201102 + 9.4.35-SNAPSHOT 4.0.0 infinispan-remote diff --git a/jetty-infinispan/pom.xml b/jetty-infinispan/pom.xml index 349a1bda8e5..c9998fb72a0 100644 --- a/jetty-infinispan/pom.xml +++ b/jetty-infinispan/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty jetty-project - 9.4.34.v20201102 + 9.4.35-SNAPSHOT 4.0.0 diff --git a/jetty-io/pom.xml b/jetty-io/pom.xml index e163e1925cf..24fc208acf0 100644 --- a/jetty-io/pom.xml +++ b/jetty-io/pom.xml @@ -3,7 +3,7 @@ jetty-project org.eclipse.jetty - 9.4.34.v20201102 + 9.4.35-SNAPSHOT 4.0.0 jetty-io diff --git a/jetty-jaas/pom.xml b/jetty-jaas/pom.xml index 35c26871a0b..d1e861398dd 100644 --- a/jetty-jaas/pom.xml +++ b/jetty-jaas/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.34.v20201102 + 9.4.35-SNAPSHOT 4.0.0 jetty-jaas diff --git a/jetty-jaspi/pom.xml b/jetty-jaspi/pom.xml index b8c028e0b68..668945447b8 100644 --- a/jetty-jaspi/pom.xml +++ b/jetty-jaspi/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.34.v20201102 + 9.4.35-SNAPSHOT 4.0.0 diff --git a/jetty-jmh/pom.xml b/jetty-jmh/pom.xml index ce9c693908e..1daab5d6041 100644 --- a/jetty-jmh/pom.xml +++ b/jetty-jmh/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.34.v20201102 + 9.4.35-SNAPSHOT 4.0.0 diff --git a/jetty-jmx/pom.xml b/jetty-jmx/pom.xml index d824ab7b12a..a2f891e0395 100644 --- a/jetty-jmx/pom.xml +++ b/jetty-jmx/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.34.v20201102 + 9.4.35-SNAPSHOT 4.0.0 jetty-jmx diff --git a/jetty-jndi/pom.xml b/jetty-jndi/pom.xml index e1865c894c0..b47193e0a98 100644 --- a/jetty-jndi/pom.xml +++ b/jetty-jndi/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.34.v20201102 + 9.4.35-SNAPSHOT 4.0.0 jetty-jndi diff --git a/jetty-jspc-maven-plugin/pom.xml b/jetty-jspc-maven-plugin/pom.xml index 68427f51ef1..a4ae84d322e 100644 --- a/jetty-jspc-maven-plugin/pom.xml +++ b/jetty-jspc-maven-plugin/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty jetty-project - 9.4.34.v20201102 + 9.4.35-SNAPSHOT 4.0.0 jetty-jspc-maven-plugin diff --git a/jetty-maven-plugin/pom.xml b/jetty-maven-plugin/pom.xml index e62fad09b6a..3371ba1c7a2 100644 --- a/jetty-maven-plugin/pom.xml +++ b/jetty-maven-plugin/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.34.v20201102 + 9.4.35-SNAPSHOT 4.0.0 jetty-maven-plugin diff --git a/jetty-memcached/jetty-memcached-sessions/pom.xml b/jetty-memcached/jetty-memcached-sessions/pom.xml index 8bffef807d7..299fcac6335 100644 --- a/jetty-memcached/jetty-memcached-sessions/pom.xml +++ b/jetty-memcached/jetty-memcached-sessions/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.memcached memcached-parent - 9.4.34.v20201102 + 9.4.35-SNAPSHOT 4.0.0 diff --git a/jetty-memcached/pom.xml b/jetty-memcached/pom.xml index f307ceabc61..9f3245b33fa 100644 --- a/jetty-memcached/pom.xml +++ b/jetty-memcached/pom.xml @@ -3,7 +3,7 @@ jetty-project org.eclipse.jetty - 9.4.34.v20201102 + 9.4.35-SNAPSHOT 4.0.0 diff --git a/jetty-nosql/pom.xml b/jetty-nosql/pom.xml index 4a340fda4d3..b58500377bc 100644 --- a/jetty-nosql/pom.xml +++ b/jetty-nosql/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.34.v20201102 + 9.4.35-SNAPSHOT 4.0.0 jetty-nosql diff --git a/jetty-openid/pom.xml b/jetty-openid/pom.xml index be876473aa6..b4cef4b2805 100644 --- a/jetty-openid/pom.xml +++ b/jetty-openid/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.34.v20201102 + 9.4.35-SNAPSHOT 4.0.0 diff --git a/jetty-osgi/jetty-osgi-alpn/pom.xml b/jetty-osgi/jetty-osgi-alpn/pom.xml index b5b0a777fad..0c62b91eaf3 100644 --- a/jetty-osgi/jetty-osgi-alpn/pom.xml +++ b/jetty-osgi/jetty-osgi-alpn/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty.osgi jetty-osgi-project - 9.4.34.v20201102 + 9.4.35-SNAPSHOT 4.0.0 jetty-osgi-alpn diff --git a/jetty-osgi/jetty-osgi-boot-jsp/pom.xml b/jetty-osgi/jetty-osgi-boot-jsp/pom.xml index 5dfe99a62d3..d4b9fb145c9 100644 --- a/jetty-osgi/jetty-osgi-boot-jsp/pom.xml +++ b/jetty-osgi/jetty-osgi-boot-jsp/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty.osgi jetty-osgi-project - 9.4.34.v20201102 + 9.4.35-SNAPSHOT 4.0.0 jetty-osgi-boot-jsp diff --git a/jetty-osgi/jetty-osgi-boot-warurl/pom.xml b/jetty-osgi/jetty-osgi-boot-warurl/pom.xml index 7dc89206160..75fe90b3f38 100644 --- a/jetty-osgi/jetty-osgi-boot-warurl/pom.xml +++ b/jetty-osgi/jetty-osgi-boot-warurl/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty.osgi jetty-osgi-project - 9.4.34.v20201102 + 9.4.35-SNAPSHOT ../pom.xml 4.0.0 diff --git a/jetty-osgi/jetty-osgi-boot/pom.xml b/jetty-osgi/jetty-osgi-boot/pom.xml index 379500e56ee..56742710e81 100644 --- a/jetty-osgi/jetty-osgi-boot/pom.xml +++ b/jetty-osgi/jetty-osgi-boot/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty.osgi jetty-osgi-project - 9.4.34.v20201102 + 9.4.35-SNAPSHOT 4.0.0 jetty-osgi-boot diff --git a/jetty-osgi/jetty-osgi-httpservice/pom.xml b/jetty-osgi/jetty-osgi-httpservice/pom.xml index 9ed9765e99d..7f5b7453357 100644 --- a/jetty-osgi/jetty-osgi-httpservice/pom.xml +++ b/jetty-osgi/jetty-osgi-httpservice/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty.osgi jetty-osgi-project - 9.4.34.v20201102 + 9.4.35-SNAPSHOT 4.0.0 jetty-httpservice diff --git a/jetty-osgi/pom.xml b/jetty-osgi/pom.xml index 451eb4f89f3..6a6b7583f0c 100644 --- a/jetty-osgi/pom.xml +++ b/jetty-osgi/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.34.v20201102 + 9.4.35-SNAPSHOT 4.0.0 diff --git a/jetty-osgi/test-jetty-osgi-context/pom.xml b/jetty-osgi/test-jetty-osgi-context/pom.xml index 1a769c204c6..1b1d72e09c3 100644 --- a/jetty-osgi/test-jetty-osgi-context/pom.xml +++ b/jetty-osgi/test-jetty-osgi-context/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty.osgi jetty-osgi-project - 9.4.34.v20201102 + 9.4.35-SNAPSHOT 4.0.0 test-jetty-osgi-context diff --git a/jetty-osgi/test-jetty-osgi-fragment/pom.xml b/jetty-osgi/test-jetty-osgi-fragment/pom.xml index b19641ae905..d3a99d082ac 100644 --- a/jetty-osgi/test-jetty-osgi-fragment/pom.xml +++ b/jetty-osgi/test-jetty-osgi-fragment/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty.osgi jetty-osgi-project - 9.4.34.v20201102 + 9.4.35-SNAPSHOT ../pom.xml 4.0.0 diff --git a/jetty-osgi/test-jetty-osgi-server/pom.xml b/jetty-osgi/test-jetty-osgi-server/pom.xml index e8940aaad07..3603adba1b8 100644 --- a/jetty-osgi/test-jetty-osgi-server/pom.xml +++ b/jetty-osgi/test-jetty-osgi-server/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty.osgi jetty-osgi-project - 9.4.34.v20201102 + 9.4.35-SNAPSHOT 4.0.0 test-jetty-osgi-server diff --git a/jetty-osgi/test-jetty-osgi-webapp-resources/pom.xml b/jetty-osgi/test-jetty-osgi-webapp-resources/pom.xml index 0beaad07682..0b073ba82ab 100644 --- a/jetty-osgi/test-jetty-osgi-webapp-resources/pom.xml +++ b/jetty-osgi/test-jetty-osgi-webapp-resources/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.osgi jetty-osgi-project - 9.4.34.v20201102 + 9.4.35-SNAPSHOT 4.0.0 test-jetty-osgi-webapp-resources diff --git a/jetty-osgi/test-jetty-osgi-webapp/pom.xml b/jetty-osgi/test-jetty-osgi-webapp/pom.xml index 516a2e190a4..ab5a76e7f6c 100644 --- a/jetty-osgi/test-jetty-osgi-webapp/pom.xml +++ b/jetty-osgi/test-jetty-osgi-webapp/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty.osgi jetty-osgi-project - 9.4.34.v20201102 + 9.4.35-SNAPSHOT ../pom.xml 4.0.0 diff --git a/jetty-osgi/test-jetty-osgi/pom.xml b/jetty-osgi/test-jetty-osgi/pom.xml index a6e510fa167..25a8c1cddc0 100644 --- a/jetty-osgi/test-jetty-osgi/pom.xml +++ b/jetty-osgi/test-jetty-osgi/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty.osgi jetty-osgi-project - 9.4.34.v20201102 + 9.4.35-SNAPSHOT ../pom.xml 4.0.0 diff --git a/jetty-plus/pom.xml b/jetty-plus/pom.xml index e6fe36d8349..a3a8275f0e1 100644 --- a/jetty-plus/pom.xml +++ b/jetty-plus/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.34.v20201102 + 9.4.35-SNAPSHOT 4.0.0 jetty-plus diff --git a/jetty-proxy/pom.xml b/jetty-proxy/pom.xml index e3da8476cf6..790f4007780 100644 --- a/jetty-proxy/pom.xml +++ b/jetty-proxy/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.34.v20201102 + 9.4.35-SNAPSHOT 4.0.0 jetty-proxy diff --git a/jetty-quickstart/pom.xml b/jetty-quickstart/pom.xml index 795b30e96c8..fda76985e3a 100644 --- a/jetty-quickstart/pom.xml +++ b/jetty-quickstart/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.34.v20201102 + 9.4.35-SNAPSHOT 4.0.0 org.eclipse.jetty diff --git a/jetty-rewrite/pom.xml b/jetty-rewrite/pom.xml index 3a591e0c129..1ddf64175cb 100644 --- a/jetty-rewrite/pom.xml +++ b/jetty-rewrite/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.34.v20201102 + 9.4.35-SNAPSHOT 4.0.0 jetty-rewrite diff --git a/jetty-runner/pom.xml b/jetty-runner/pom.xml index 77740bbf714..a6ac806e5ae 100644 --- a/jetty-runner/pom.xml +++ b/jetty-runner/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.34.v20201102 + 9.4.35-SNAPSHOT 4.0.0 jetty-runner diff --git a/jetty-security/pom.xml b/jetty-security/pom.xml index b94c1dbeafc..ad174fedaca 100644 --- a/jetty-security/pom.xml +++ b/jetty-security/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.34.v20201102 + 9.4.35-SNAPSHOT 4.0.0 jetty-security diff --git a/jetty-server/pom.xml b/jetty-server/pom.xml index c5712a12b18..f151fa1c9ff 100644 --- a/jetty-server/pom.xml +++ b/jetty-server/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.34.v20201102 + 9.4.35-SNAPSHOT 4.0.0 jetty-server diff --git a/jetty-servlet/pom.xml b/jetty-servlet/pom.xml index d7384f8acc6..00abe4bbbb0 100644 --- a/jetty-servlet/pom.xml +++ b/jetty-servlet/pom.xml @@ -3,7 +3,7 @@ jetty-project org.eclipse.jetty - 9.4.34.v20201102 + 9.4.35-SNAPSHOT 4.0.0 jetty-servlet diff --git a/jetty-servlets/pom.xml b/jetty-servlets/pom.xml index 42fe102c930..385464af381 100644 --- a/jetty-servlets/pom.xml +++ b/jetty-servlets/pom.xml @@ -3,7 +3,7 @@ jetty-project org.eclipse.jetty - 9.4.34.v20201102 + 9.4.35-SNAPSHOT 4.0.0 jetty-servlets diff --git a/jetty-spring/pom.xml b/jetty-spring/pom.xml index 4dad16313b5..b502831988e 100644 --- a/jetty-spring/pom.xml +++ b/jetty-spring/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.34.v20201102 + 9.4.35-SNAPSHOT 4.0.0 jetty-spring diff --git a/jetty-start/pom.xml b/jetty-start/pom.xml index 6dad94c3071..39ef94ad638 100644 --- a/jetty-start/pom.xml +++ b/jetty-start/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.34.v20201102 + 9.4.35-SNAPSHOT 4.0.0 jetty-start diff --git a/jetty-unixsocket/pom.xml b/jetty-unixsocket/pom.xml index 3fe186bd594..b5a7e6d07bd 100644 --- a/jetty-unixsocket/pom.xml +++ b/jetty-unixsocket/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.34.v20201102 + 9.4.35-SNAPSHOT 4.0.0 jetty-unixsocket diff --git a/jetty-util-ajax/pom.xml b/jetty-util-ajax/pom.xml index 941cb8b11b6..2f96269a47d 100644 --- a/jetty-util-ajax/pom.xml +++ b/jetty-util-ajax/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.34.v20201102 + 9.4.35-SNAPSHOT 4.0.0 jetty-util-ajax diff --git a/jetty-util/pom.xml b/jetty-util/pom.xml index 067a511d269..d1c9f04ed5f 100644 --- a/jetty-util/pom.xml +++ b/jetty-util/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.34.v20201102 + 9.4.35-SNAPSHOT 4.0.0 jetty-util diff --git a/jetty-webapp/pom.xml b/jetty-webapp/pom.xml index a5c8b7483f9..9532030c0bc 100644 --- a/jetty-webapp/pom.xml +++ b/jetty-webapp/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.34.v20201102 + 9.4.35-SNAPSHOT 4.0.0 jetty-webapp diff --git a/jetty-websocket/javax-websocket-client-impl/pom.xml b/jetty-websocket/javax-websocket-client-impl/pom.xml index 79f51febade..033a7fd9e21 100644 --- a/jetty-websocket/javax-websocket-client-impl/pom.xml +++ b/jetty-websocket/javax-websocket-client-impl/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.websocket websocket-parent - 9.4.34.v20201102 + 9.4.35-SNAPSHOT 4.0.0 diff --git a/jetty-websocket/javax-websocket-server-impl/pom.xml b/jetty-websocket/javax-websocket-server-impl/pom.xml index 6fc199c0736..81445c35aa0 100644 --- a/jetty-websocket/javax-websocket-server-impl/pom.xml +++ b/jetty-websocket/javax-websocket-server-impl/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.websocket websocket-parent - 9.4.34.v20201102 + 9.4.35-SNAPSHOT 4.0.0 diff --git a/jetty-websocket/jetty-websocket-tests/pom.xml b/jetty-websocket/jetty-websocket-tests/pom.xml index be5ea05925b..ffc823b1053 100644 --- a/jetty-websocket/jetty-websocket-tests/pom.xml +++ b/jetty-websocket/jetty-websocket-tests/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.websocket websocket-parent - 9.4.34.v20201102 + 9.4.35-SNAPSHOT 4.0.0 diff --git a/jetty-websocket/pom.xml b/jetty-websocket/pom.xml index d0dddfdaaa5..9093b527eaa 100644 --- a/jetty-websocket/pom.xml +++ b/jetty-websocket/pom.xml @@ -3,7 +3,7 @@ jetty-project org.eclipse.jetty - 9.4.34.v20201102 + 9.4.35-SNAPSHOT 4.0.0 diff --git a/jetty-websocket/websocket-api/pom.xml b/jetty-websocket/websocket-api/pom.xml index c2d98aa17f0..03562a56af3 100644 --- a/jetty-websocket/websocket-api/pom.xml +++ b/jetty-websocket/websocket-api/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.websocket websocket-parent - 9.4.34.v20201102 + 9.4.35-SNAPSHOT 4.0.0 diff --git a/jetty-websocket/websocket-client/pom.xml b/jetty-websocket/websocket-client/pom.xml index b775407fde7..48a72fb1b5e 100644 --- a/jetty-websocket/websocket-client/pom.xml +++ b/jetty-websocket/websocket-client/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.websocket websocket-parent - 9.4.34.v20201102 + 9.4.35-SNAPSHOT 4.0.0 diff --git a/jetty-websocket/websocket-common/pom.xml b/jetty-websocket/websocket-common/pom.xml index fdcea95dbfb..6240f785609 100644 --- a/jetty-websocket/websocket-common/pom.xml +++ b/jetty-websocket/websocket-common/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.websocket websocket-parent - 9.4.34.v20201102 + 9.4.35-SNAPSHOT 4.0.0 diff --git a/jetty-websocket/websocket-server/pom.xml b/jetty-websocket/websocket-server/pom.xml index 433dc315f52..2a2cec63809 100644 --- a/jetty-websocket/websocket-server/pom.xml +++ b/jetty-websocket/websocket-server/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.websocket websocket-parent - 9.4.34.v20201102 + 9.4.35-SNAPSHOT 4.0.0 diff --git a/jetty-websocket/websocket-servlet/pom.xml b/jetty-websocket/websocket-servlet/pom.xml index 00458d8df52..e3663eb1d85 100644 --- a/jetty-websocket/websocket-servlet/pom.xml +++ b/jetty-websocket/websocket-servlet/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.websocket websocket-parent - 9.4.34.v20201102 + 9.4.35-SNAPSHOT 4.0.0 diff --git a/jetty-xml/pom.xml b/jetty-xml/pom.xml index efbbf37c2b5..b3928128f3e 100644 --- a/jetty-xml/pom.xml +++ b/jetty-xml/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.34.v20201102 + 9.4.35-SNAPSHOT 4.0.0 jetty-xml diff --git a/pom.xml b/pom.xml index c4dd144087d..bc970e45d99 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 org.eclipse.jetty jetty-project - 9.4.34.v20201102 + 9.4.35-SNAPSHOT Jetty :: Project The Eclipse Jetty Project pom diff --git a/tests/pom.xml b/tests/pom.xml index f66a70a9e42..d6aa9c817a0 100644 --- a/tests/pom.xml +++ b/tests/pom.xml @@ -4,7 +4,7 @@ org.eclipse.jetty jetty-project - 9.4.34.v20201102 + 9.4.35-SNAPSHOT ../pom.xml org.eclipse.jetty.tests diff --git a/tests/test-continuation/pom.xml b/tests/test-continuation/pom.xml index 20563ea361a..792edd34b8f 100644 --- a/tests/test-continuation/pom.xml +++ b/tests/test-continuation/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.tests tests-parent - 9.4.34.v20201102 + 9.4.35-SNAPSHOT ../pom.xml 4.0.0 diff --git a/tests/test-distribution/pom.xml b/tests/test-distribution/pom.xml index edaf5375eb0..2edaa313664 100644 --- a/tests/test-distribution/pom.xml +++ b/tests/test-distribution/pom.xml @@ -2,7 +2,7 @@ tests-parent org.eclipse.jetty.tests - 9.4.34.v20201102 + 9.4.35-SNAPSHOT 4.0.0 diff --git a/tests/test-http-client-transport/pom.xml b/tests/test-http-client-transport/pom.xml index 9d3b33de021..4db7d2e3a0b 100644 --- a/tests/test-http-client-transport/pom.xml +++ b/tests/test-http-client-transport/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.tests tests-parent - 9.4.34.v20201102 + 9.4.35-SNAPSHOT 4.0.0 diff --git a/tests/test-integration/pom.xml b/tests/test-integration/pom.xml index ed3606aabf9..ec74ebb9130 100644 --- a/tests/test-integration/pom.xml +++ b/tests/test-integration/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.tests tests-parent - 9.4.34.v20201102 + 9.4.35-SNAPSHOT 4.0.0 test-integration diff --git a/tests/test-jmx/jmx-webapp-it/pom.xml b/tests/test-jmx/jmx-webapp-it/pom.xml index c9d4fe4614f..a1b4a7dce3a 100644 --- a/tests/test-jmx/jmx-webapp-it/pom.xml +++ b/tests/test-jmx/jmx-webapp-it/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.tests test-jmx-parent - 9.4.34.v20201102 + 9.4.35-SNAPSHOT 4.0.0 jmx-webapp-it diff --git a/tests/test-jmx/jmx-webapp/pom.xml b/tests/test-jmx/jmx-webapp/pom.xml index f83ed45486d..8801d5187e8 100644 --- a/tests/test-jmx/jmx-webapp/pom.xml +++ b/tests/test-jmx/jmx-webapp/pom.xml @@ -4,7 +4,7 @@ org.eclipse.jetty.tests test-jmx-parent - 9.4.34.v20201102 + 9.4.35-SNAPSHOT jmx-webapp war diff --git a/tests/test-jmx/pom.xml b/tests/test-jmx/pom.xml index 1d8d5a9a0b6..9f495ce54d9 100644 --- a/tests/test-jmx/pom.xml +++ b/tests/test-jmx/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.tests tests-parent - 9.4.34.v20201102 + 9.4.35-SNAPSHOT 4.0.0 test-jmx-parent diff --git a/tests/test-loginservice/pom.xml b/tests/test-loginservice/pom.xml index c64abdfcc8d..ce644f84e79 100644 --- a/tests/test-loginservice/pom.xml +++ b/tests/test-loginservice/pom.xml @@ -4,7 +4,7 @@ org.eclipse.jetty.tests tests-parent - 9.4.34.v20201102 + 9.4.35-SNAPSHOT test-loginservice Jetty Tests :: Login Service diff --git a/tests/test-quickstart/pom.xml b/tests/test-quickstart/pom.xml index df7aa100881..7a6fcbc4a84 100644 --- a/tests/test-quickstart/pom.xml +++ b/tests/test-quickstart/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.tests tests-parent - 9.4.34.v20201102 + 9.4.35-SNAPSHOT ../pom.xml 4.0.0 diff --git a/tests/test-sessions/pom.xml b/tests/test-sessions/pom.xml index 93d9081b527..9c32d2653f5 100644 --- a/tests/test-sessions/pom.xml +++ b/tests/test-sessions/pom.xml @@ -4,7 +4,7 @@ org.eclipse.jetty.tests tests-parent - 9.4.34.v20201102 + 9.4.35-SNAPSHOT test-sessions-parent Jetty Tests :: Sessions :: Parent diff --git a/tests/test-sessions/test-file-sessions/pom.xml b/tests/test-sessions/test-file-sessions/pom.xml index 0736a17d041..3aaafd382ec 100644 --- a/tests/test-sessions/test-file-sessions/pom.xml +++ b/tests/test-sessions/test-file-sessions/pom.xml @@ -4,7 +4,7 @@ org.eclipse.jetty.tests test-sessions-parent - 9.4.34.v20201102 + 9.4.35-SNAPSHOT test-file-sessions Jetty Tests :: Sessions :: File diff --git a/tests/test-sessions/test-gcloud-sessions/pom.xml b/tests/test-sessions/test-gcloud-sessions/pom.xml index b903f7b9f54..606509801db 100644 --- a/tests/test-sessions/test-gcloud-sessions/pom.xml +++ b/tests/test-sessions/test-gcloud-sessions/pom.xml @@ -4,7 +4,7 @@ org.eclipse.jetty.tests test-sessions-parent - 9.4.34.v20201102 + 9.4.35-SNAPSHOT test-gcloud-sessions Jetty Tests :: Sessions :: GCloud diff --git a/tests/test-sessions/test-hazelcast-sessions/pom.xml b/tests/test-sessions/test-hazelcast-sessions/pom.xml index c654fc867ca..a891fa39f27 100644 --- a/tests/test-sessions/test-hazelcast-sessions/pom.xml +++ b/tests/test-sessions/test-hazelcast-sessions/pom.xml @@ -4,7 +4,7 @@ org.eclipse.jetty.tests test-sessions-parent - 9.4.34.v20201102 + 9.4.35-SNAPSHOT test-hazelcast-sessions Jetty Tests :: Sessions :: Hazelcast diff --git a/tests/test-sessions/test-infinispan-sessions/pom.xml b/tests/test-sessions/test-infinispan-sessions/pom.xml index 62d62738f0e..aab0c142663 100644 --- a/tests/test-sessions/test-infinispan-sessions/pom.xml +++ b/tests/test-sessions/test-infinispan-sessions/pom.xml @@ -4,7 +4,7 @@ org.eclipse.jetty.tests test-sessions-parent - 9.4.34.v20201102 + 9.4.35-SNAPSHOT test-infinispan-sessions Jetty Tests :: Sessions :: Infinispan diff --git a/tests/test-sessions/test-jdbc-sessions/pom.xml b/tests/test-sessions/test-jdbc-sessions/pom.xml index 44dd4df2cfd..88b341b2de1 100644 --- a/tests/test-sessions/test-jdbc-sessions/pom.xml +++ b/tests/test-sessions/test-jdbc-sessions/pom.xml @@ -4,7 +4,7 @@ org.eclipse.jetty.tests test-sessions-parent - 9.4.34.v20201102 + 9.4.35-SNAPSHOT test-jdbc-sessions Jetty Tests :: Sessions :: JDBC diff --git a/tests/test-sessions/test-memcached-sessions/pom.xml b/tests/test-sessions/test-memcached-sessions/pom.xml index 6db4e58ffba..784e8672e92 100644 --- a/tests/test-sessions/test-memcached-sessions/pom.xml +++ b/tests/test-sessions/test-memcached-sessions/pom.xml @@ -4,7 +4,7 @@ org.eclipse.jetty.tests test-sessions-parent - 9.4.34.v20201102 + 9.4.35-SNAPSHOT test-memcached-sessions Jetty Tests :: Sessions :: Memcached diff --git a/tests/test-sessions/test-mongodb-sessions/pom.xml b/tests/test-sessions/test-mongodb-sessions/pom.xml index 8049c252c32..7fc62c95fd8 100644 --- a/tests/test-sessions/test-mongodb-sessions/pom.xml +++ b/tests/test-sessions/test-mongodb-sessions/pom.xml @@ -4,7 +4,7 @@ org.eclipse.jetty.tests test-sessions-parent - 9.4.34.v20201102 + 9.4.35-SNAPSHOT test-mongodb-sessions Jetty Tests :: Sessions :: Mongo diff --git a/tests/test-sessions/test-sessions-common/pom.xml b/tests/test-sessions/test-sessions-common/pom.xml index 74326c85abe..3a177247ae9 100644 --- a/tests/test-sessions/test-sessions-common/pom.xml +++ b/tests/test-sessions/test-sessions-common/pom.xml @@ -4,7 +4,7 @@ org.eclipse.jetty.tests test-sessions-parent - 9.4.34.v20201102 + 9.4.35-SNAPSHOT test-sessions-common Jetty Tests :: Sessions :: Common diff --git a/tests/test-webapps/pom.xml b/tests/test-webapps/pom.xml index 106eb671d80..ecb57de631e 100644 --- a/tests/test-webapps/pom.xml +++ b/tests/test-webapps/pom.xml @@ -4,7 +4,7 @@ org.eclipse.jetty.tests tests-parent - 9.4.34.v20201102 + 9.4.35-SNAPSHOT ../pom.xml test-webapps-parent diff --git a/tests/test-webapps/test-cdi-common-webapp/pom.xml b/tests/test-webapps/test-cdi-common-webapp/pom.xml index 2d7f8134d1a..215a1d6a029 100644 --- a/tests/test-webapps/test-cdi-common-webapp/pom.xml +++ b/tests/test-webapps/test-cdi-common-webapp/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.tests test-webapps-parent - 9.4.34.v20201102 + 9.4.35-SNAPSHOT 4.0.0 diff --git a/tests/test-webapps/test-felix-webapp/pom.xml b/tests/test-webapps/test-felix-webapp/pom.xml index a394c2335cd..78179b3f355 100644 --- a/tests/test-webapps/test-felix-webapp/pom.xml +++ b/tests/test-webapps/test-felix-webapp/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.tests test-webapps-parent - 9.4.34.v20201102 + 9.4.35-SNAPSHOT 4.0.0 diff --git a/tests/test-webapps/test-http2-webapp/pom.xml b/tests/test-webapps/test-http2-webapp/pom.xml index 101f1052319..b7ed1071569 100644 --- a/tests/test-webapps/test-http2-webapp/pom.xml +++ b/tests/test-webapps/test-http2-webapp/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.tests test-webapps-parent - 9.4.34.v20201102 + 9.4.35-SNAPSHOT 4.0.0 diff --git a/tests/test-webapps/test-jaas-webapp/pom.xml b/tests/test-webapps/test-jaas-webapp/pom.xml index b483e6b4d97..4071e7b8260 100644 --- a/tests/test-webapps/test-jaas-webapp/pom.xml +++ b/tests/test-webapps/test-jaas-webapp/pom.xml @@ -4,7 +4,7 @@ org.eclipse.jetty.tests test-webapps-parent - 9.4.34.v20201102 + 9.4.35-SNAPSHOT test-jaas-webapp Jetty Tests :: WebApp :: JAAS diff --git a/tests/test-webapps/test-jetty-webapp/pom.xml b/tests/test-webapps/test-jetty-webapp/pom.xml index 197396e0c66..51791c1b2f0 100644 --- a/tests/test-webapps/test-jetty-webapp/pom.xml +++ b/tests/test-webapps/test-jetty-webapp/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.tests test-webapps-parent - 9.4.34.v20201102 + 9.4.35-SNAPSHOT ../pom.xml 4.0.0 diff --git a/tests/test-webapps/test-jndi-webapp/pom.xml b/tests/test-webapps/test-jndi-webapp/pom.xml index 6c8a4f35e2b..cbd2472eac1 100644 --- a/tests/test-webapps/test-jndi-webapp/pom.xml +++ b/tests/test-webapps/test-jndi-webapp/pom.xml @@ -4,7 +4,7 @@ org.eclipse.jetty.tests test-webapps-parent - 9.4.34.v20201102 + 9.4.35-SNAPSHOT test-jndi-webapp Jetty Tests :: WebApp :: JNDI diff --git a/tests/test-webapps/test-mock-resources/pom.xml b/tests/test-webapps/test-mock-resources/pom.xml index 258c470c203..748386ec010 100644 --- a/tests/test-webapps/test-mock-resources/pom.xml +++ b/tests/test-webapps/test-mock-resources/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.tests test-webapps-parent - 9.4.34.v20201102 + 9.4.35-SNAPSHOT Jetty Tests :: WebApp :: Mock Resources test-mock-resources diff --git a/tests/test-webapps/test-owb-cdi-webapp/pom.xml b/tests/test-webapps/test-owb-cdi-webapp/pom.xml index 030b56c5883..9876e7bd2fd 100644 --- a/tests/test-webapps/test-owb-cdi-webapp/pom.xml +++ b/tests/test-webapps/test-owb-cdi-webapp/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.tests test-webapps-parent - 9.4.34.v20201102 + 9.4.35-SNAPSHOT 4.0.0 diff --git a/tests/test-webapps/test-proxy-webapp/pom.xml b/tests/test-webapps/test-proxy-webapp/pom.xml index 5800c5ad830..f2daf296a1e 100644 --- a/tests/test-webapps/test-proxy-webapp/pom.xml +++ b/tests/test-webapps/test-proxy-webapp/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.tests test-webapps-parent - 9.4.34.v20201102 + 9.4.35-SNAPSHOT ../pom.xml 4.0.0 diff --git a/tests/test-webapps/test-servlet-spec/pom.xml b/tests/test-webapps/test-servlet-spec/pom.xml index 54739c314b2..1a3654eb216 100644 --- a/tests/test-webapps/test-servlet-spec/pom.xml +++ b/tests/test-webapps/test-servlet-spec/pom.xml @@ -4,7 +4,7 @@ org.eclipse.jetty.tests test-webapps-parent - 9.4.34.v20201102 + 9.4.35-SNAPSHOT test-servlet-spec-parent Jetty Tests :: Spec Test WebApp :: Parent diff --git a/tests/test-webapps/test-servlet-spec/test-container-initializer/pom.xml b/tests/test-webapps/test-servlet-spec/test-container-initializer/pom.xml index a3655acfe28..7513470d4d8 100644 --- a/tests/test-webapps/test-servlet-spec/test-container-initializer/pom.xml +++ b/tests/test-webapps/test-servlet-spec/test-container-initializer/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.tests test-servlet-spec-parent - 9.4.34.v20201102 + 9.4.35-SNAPSHOT test-container-initializer jar diff --git a/tests/test-webapps/test-servlet-spec/test-spec-webapp/pom.xml b/tests/test-webapps/test-servlet-spec/test-spec-webapp/pom.xml index 8acd6d42edc..e078d8dcf63 100644 --- a/tests/test-webapps/test-servlet-spec/test-spec-webapp/pom.xml +++ b/tests/test-webapps/test-servlet-spec/test-spec-webapp/pom.xml @@ -4,7 +4,7 @@ org.eclipse.jetty.tests test-servlet-spec-parent - 9.4.34.v20201102 + 9.4.35-SNAPSHOT Jetty Tests :: Webapps :: Spec Webapp test-spec-webapp diff --git a/tests/test-webapps/test-servlet-spec/test-web-fragment/pom.xml b/tests/test-webapps/test-servlet-spec/test-web-fragment/pom.xml index e95534d0df5..94a2e79e84d 100644 --- a/tests/test-webapps/test-servlet-spec/test-web-fragment/pom.xml +++ b/tests/test-webapps/test-servlet-spec/test-web-fragment/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.tests test-servlet-spec-parent - 9.4.34.v20201102 + 9.4.35-SNAPSHOT Jetty Tests :: WebApp :: Servlet Spec :: Fragment Jar diff --git a/tests/test-webapps/test-simple-webapp/pom.xml b/tests/test-webapps/test-simple-webapp/pom.xml index 7e5c3b93ae5..a19600394ad 100644 --- a/tests/test-webapps/test-simple-webapp/pom.xml +++ b/tests/test-webapps/test-simple-webapp/pom.xml @@ -4,7 +4,7 @@ org.eclipse.jetty.tests test-webapps-parent - 9.4.34.v20201102 + 9.4.35-SNAPSHOT test-simple-webapp diff --git a/tests/test-webapps/test-webapp-rfc2616/pom.xml b/tests/test-webapps/test-webapp-rfc2616/pom.xml index 7abc2755ce9..7011dae4bc9 100644 --- a/tests/test-webapps/test-webapp-rfc2616/pom.xml +++ b/tests/test-webapps/test-webapp-rfc2616/pom.xml @@ -4,7 +4,7 @@ org.eclipse.jetty.tests test-webapps-parent - 9.4.34.v20201102 + 9.4.35-SNAPSHOT test-webapp-rfc2616 Jetty Tests :: WebApp :: RFC2616 diff --git a/tests/test-webapps/test-websocket-client-provided-webapp/pom.xml b/tests/test-webapps/test-websocket-client-provided-webapp/pom.xml index b7c6ad7a106..d4463bfbf55 100644 --- a/tests/test-webapps/test-websocket-client-provided-webapp/pom.xml +++ b/tests/test-webapps/test-websocket-client-provided-webapp/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.tests test-webapps-parent - 9.4.34.v20201102 + 9.4.35-SNAPSHOT 4.0.0 diff --git a/tests/test-webapps/test-websocket-client-webapp/pom.xml b/tests/test-webapps/test-websocket-client-webapp/pom.xml index 1962337b764..da831550f03 100644 --- a/tests/test-webapps/test-websocket-client-webapp/pom.xml +++ b/tests/test-webapps/test-websocket-client-webapp/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.tests test-webapps-parent - 9.4.34.v20201102 + 9.4.35-SNAPSHOT 4.0.0 diff --git a/tests/test-webapps/test-weld-cdi-webapp/pom.xml b/tests/test-webapps/test-weld-cdi-webapp/pom.xml index 7ddacf0d0bb..a70965a307d 100644 --- a/tests/test-webapps/test-weld-cdi-webapp/pom.xml +++ b/tests/test-webapps/test-weld-cdi-webapp/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.tests test-webapps-parent - 9.4.34.v20201102 + 9.4.35-SNAPSHOT 4.0.0 From 761c67804a8033952f72f8ebb2e297b00ebdbfa9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 3 Nov 2020 14:36:07 +0000 Subject: [PATCH 11/25] Bump maven-javadoc-plugin from 3.1.1 to 3.2.0 Bumps [maven-javadoc-plugin](https://github.com/apache/maven-javadoc-plugin) from 3.1.1 to 3.2.0. - [Release notes](https://github.com/apache/maven-javadoc-plugin/releases) - [Commits](https://github.com/apache/maven-javadoc-plugin/compare/maven-javadoc-plugin-3.1.1...maven-javadoc-plugin-3.2.0) Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 73d1ccd5b88..8221c056294 100644 --- a/pom.xml +++ b/pom.xml @@ -575,7 +575,7 @@ org.apache.maven.plugins maven-javadoc-plugin - 3.1.1 + 3.2.0 8 UTF-8 From 40bde46b13527fee43426b04b210dc0e831916f8 Mon Sep 17 00:00:00 2001 From: Joakim Erdfelt Date: Tue, 3 Nov 2020 08:46:27 -0600 Subject: [PATCH 12/25] Bump maven-antrun-plugin from 1.8 to 3.0.0 + and update configuration/task to configuration/target Signed-off-by: Joakim Erdfelt --- jetty-distribution/pom.xml | 12 ++++++------ jetty-gcloud/jetty-gcloud-session-manager/pom.xml | 8 ++++---- jetty-home/pom.xml | 4 ++-- jetty-infinispan/infinispan-embedded-query/pom.xml | 8 ++++---- jetty-infinispan/infinispan-embedded/pom.xml | 8 ++++---- jetty-infinispan/infinispan-remote-query/pom.xml | 8 ++++---- jetty-infinispan/infinispan-remote/pom.xml | 8 ++++---- jetty-osgi/jetty-osgi-boot/pom.xml | 4 ++-- jetty-osgi/jetty-osgi-httpservice/pom.xml | 4 ++-- jetty-unixsocket/pom.xml | 4 ++-- pom.xml | 2 +- tests/test-webapps/test-jndi-webapp/pom.xml | 4 ++-- .../test-servlet-spec/test-spec-webapp/pom.xml | 4 ++-- 13 files changed, 39 insertions(+), 39 deletions(-) diff --git a/jetty-distribution/pom.xml b/jetty-distribution/pom.xml index eedfcd7a258..df08119524e 100644 --- a/jetty-distribution/pom.xml +++ b/jetty-distribution/pom.xml @@ -264,11 +264,11 @@ run - + - + @@ -278,9 +278,9 @@ run - + - + @@ -290,9 +290,9 @@ run - + - + diff --git a/jetty-gcloud/jetty-gcloud-session-manager/pom.xml b/jetty-gcloud/jetty-gcloud-session-manager/pom.xml index 021225cd5b3..aa8b9cf42b4 100644 --- a/jetty-gcloud/jetty-gcloud-session-manager/pom.xml +++ b/jetty-gcloud/jetty-gcloud-session-manager/pom.xml @@ -113,10 +113,10 @@ run - + - + @@ -126,12 +126,12 @@ run - + - + diff --git a/jetty-home/pom.xml b/jetty-home/pom.xml index dc9b3940b63..1329fbe1a13 100644 --- a/jetty-home/pom.xml +++ b/jetty-home/pom.xml @@ -488,9 +488,9 @@ run - + - + diff --git a/jetty-infinispan/infinispan-embedded-query/pom.xml b/jetty-infinispan/infinispan-embedded-query/pom.xml index 569b258e358..4fbd42af2ea 100644 --- a/jetty-infinispan/infinispan-embedded-query/pom.xml +++ b/jetty-infinispan/infinispan-embedded-query/pom.xml @@ -45,10 +45,10 @@ run - + - + @@ -58,12 +58,12 @@ run - + - + diff --git a/jetty-infinispan/infinispan-embedded/pom.xml b/jetty-infinispan/infinispan-embedded/pom.xml index b33669b6d28..85d174cde9b 100644 --- a/jetty-infinispan/infinispan-embedded/pom.xml +++ b/jetty-infinispan/infinispan-embedded/pom.xml @@ -46,10 +46,10 @@ run - + - + @@ -59,12 +59,12 @@ run - + - + diff --git a/jetty-infinispan/infinispan-remote-query/pom.xml b/jetty-infinispan/infinispan-remote-query/pom.xml index 8c76d5cb9af..149972c7fa5 100644 --- a/jetty-infinispan/infinispan-remote-query/pom.xml +++ b/jetty-infinispan/infinispan-remote-query/pom.xml @@ -45,11 +45,11 @@ run - + - + @@ -59,12 +59,12 @@ run - + - + diff --git a/jetty-infinispan/infinispan-remote/pom.xml b/jetty-infinispan/infinispan-remote/pom.xml index 5f64be1d368..4a0d6fcaee1 100644 --- a/jetty-infinispan/infinispan-remote/pom.xml +++ b/jetty-infinispan/infinispan-remote/pom.xml @@ -46,11 +46,11 @@ run - + - + @@ -60,12 +60,12 @@ run - + - + diff --git a/jetty-osgi/jetty-osgi-boot/pom.xml b/jetty-osgi/jetty-osgi-boot/pom.xml index 02528818ace..2200c68e79b 100644 --- a/jetty-osgi/jetty-osgi-boot/pom.xml +++ b/jetty-osgi/jetty-osgi-boot/pom.xml @@ -46,14 +46,14 @@ process-resources - + - + run diff --git a/jetty-osgi/jetty-osgi-httpservice/pom.xml b/jetty-osgi/jetty-osgi-httpservice/pom.xml index 40f46b8a7ba..b268ba01b11 100644 --- a/jetty-osgi/jetty-osgi-httpservice/pom.xml +++ b/jetty-osgi/jetty-osgi-httpservice/pom.xml @@ -43,11 +43,11 @@ process-resources - + - + run diff --git a/jetty-unixsocket/pom.xml b/jetty-unixsocket/pom.xml index 97418cb7e69..61d844c0650 100644 --- a/jetty-unixsocket/pom.xml +++ b/jetty-unixsocket/pom.xml @@ -80,7 +80,7 @@ run - + @@ -92,7 +92,7 @@ - + diff --git a/pom.xml b/pom.xml index 73d1ccd5b88..9ef5dd5f2c5 100644 --- a/pom.xml +++ b/pom.xml @@ -463,7 +463,7 @@ org.apache.maven.plugins maven-antrun-plugin - 1.8 + 3.0.0 org.apache.maven.plugins diff --git a/tests/test-webapps/test-jndi-webapp/pom.xml b/tests/test-webapps/test-jndi-webapp/pom.xml index 0d30fe3028e..f5143c36bb9 100644 --- a/tests/test-webapps/test-jndi-webapp/pom.xml +++ b/tests/test-webapps/test-jndi-webapp/pom.xml @@ -29,14 +29,14 @@ generate-xml-files process-resources - + - + run diff --git a/tests/test-webapps/test-servlet-spec/test-spec-webapp/pom.xml b/tests/test-webapps/test-servlet-spec/test-spec-webapp/pom.xml index b0f04aa29e1..ff9b200adc7 100644 --- a/tests/test-webapps/test-servlet-spec/test-spec-webapp/pom.xml +++ b/tests/test-webapps/test-servlet-spec/test-spec-webapp/pom.xml @@ -92,14 +92,14 @@ generate-xml-files process-resources - + - + run From f88f09a1484c77ab9c3ec7f1e072895579b2d009 Mon Sep 17 00:00:00 2001 From: Greg Wilkins Date: Tue, 3 Nov 2020 16:22:26 +0100 Subject: [PATCH 13/25] SessionCookieConfig name may be null (#5557) * SessionCookieConfig name may be null Protect against NPE by make a null name in SessionCookieConfig deactive session cookies. * SessionCookieConfig name may be null Protect against NPE by make a null name in SessionCookieConfig deactive session cookies. * SessionCookieConfig name may be null Protect against NPE by make a null name in SessionCookieConfig deactive session cookies. * feedback from review added static method to convert null name to default. --- .../jetty/server/session/SessionHandler.java | 24 ++++++++++++++----- .../webapp/StandardDescriptorProcessor.java | 5 ++-- 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/session/SessionHandler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/session/SessionHandler.java index 255b4b88eaf..f11e73ea49d 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/session/SessionHandler.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/session/SessionHandler.java @@ -46,6 +46,7 @@ import javax.servlet.http.HttpSessionListener; import org.eclipse.jetty.http.BadMessageException; import org.eclipse.jetty.http.HttpCookie; +import org.eclipse.jetty.http.Syntax; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.SessionIdManager; @@ -662,7 +663,7 @@ public class SessionHandler extends ScopedHandler HttpCookie cookie = null; cookie = new HttpCookie( - _cookieConfig.getName(), + getSessionCookieName(_cookieConfig), id, _cookieConfig.getDomain(), sessionPath, @@ -1378,6 +1379,13 @@ public class SessionHandler extends ScopedHandler Session getSession(); } + public static String getSessionCookieName(SessionCookieConfig config) + { + if (config == null || config.getName() == null) + return __DefaultSessionCookie; + return config.getName(); + } + /** * CookieConfig * @@ -1466,6 +1474,10 @@ public class SessionHandler extends ScopedHandler { if (_context != null && _context.getContextHandler().isAvailable()) throw new IllegalStateException("CookieConfig cannot be set after ServletContext is started"); + if ("".equals(name)) + throw new IllegalArgumentException("Blank cookie name"); + if (name != null) + Syntax.requireValidRFC2616Token(name, "Bad Session cookie name"); _sessionCookie = name; } @@ -1645,18 +1657,18 @@ public class SessionHandler extends ScopedHandler Cookie[] cookies = request.getCookies(); if (cookies != null && cookies.length > 0) { - final String sessionCookie = getSessionCookieConfig().getName(); - for (int i = 0; i < cookies.length; i++) + final String sessionCookie = getSessionCookieName(getSessionCookieConfig()); + for (Cookie cookie : cookies) { - if (sessionCookie.equalsIgnoreCase(cookies[i].getName())) + if (sessionCookie.equalsIgnoreCase(cookie.getName())) { - String id = cookies[i].getValue(); + String id = cookie.getValue(); requestedSessionIdFromCookie = true; if (LOG.isDebugEnabled()) LOG.debug("Got Session ID {} from cookie {}", id, sessionCookie); HttpSession s = getHttpSession(id); - + if (requestedSessionId == null) { //no previous id, always accept this one diff --git a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/StandardDescriptorProcessor.java b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/StandardDescriptorProcessor.java index 3f959ce88bd..33967487af3 100644 --- a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/StandardDescriptorProcessor.java +++ b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/StandardDescriptorProcessor.java @@ -38,6 +38,7 @@ import org.eclipse.jetty.http.pathmap.ServletPathSpec; import org.eclipse.jetty.security.ConstraintAware; import org.eclipse.jetty.security.ConstraintMapping; import org.eclipse.jetty.security.authentication.FormAuthenticator; +import org.eclipse.jetty.server.session.SessionHandler; import org.eclipse.jetty.servlet.ErrorPageErrorHandler; import org.eclipse.jetty.servlet.FilterHolder; import org.eclipse.jetty.servlet.FilterMapping; @@ -732,7 +733,7 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor case WebFragment: { //a web-fragment set the value, all web-fragments must have the same value - if (!context.getSessionHandler().getSessionCookieConfig().getName().equals(name)) + if (!name.equals(SessionHandler.getSessionCookieName(context.getSessionHandler().getSessionCookieConfig()))) throw new IllegalStateException("Conflicting cookie-config name " + name + " in " + descriptor.getResource()); break; } @@ -806,7 +807,7 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor case WebFragment: { //a web-fragment set the value, all web-fragments must have the same value - if (!context.getSessionHandler().getSessionCookieConfig().getPath().equals(path)) + if (!path.equals(context.getSessionHandler().getSessionCookieConfig().getPath())) throw new IllegalStateException("Conflicting cookie-config path " + path + " in " + descriptor.getResource()); break; } From 5a24c8dd174689b194e656b282242debc8e5e3bb Mon Sep 17 00:00:00 2001 From: Ludovic Orban Date: Thu, 16 Jan 2020 18:49:25 +0100 Subject: [PATCH 14/25] HTTP and FCGI pass all tests (Milestone 1) Signed-off-by: Ludovic Orban --- .../fcgi/server/HttpChannelOverFCGI.java | 9 + .../org/eclipse/jetty/server/HttpChannel.java | 3 +- .../eclipse/jetty/server/HttpConnection.java | 25 +- .../org/eclipse/jetty/server/HttpInput.java | 1048 +---------------- .../jetty/server/HttpInputOverFCGI.java | 699 +++++++++++ .../jetty/server/HttpInputOverHTTP.java | 708 ++++++++++- .../jetty/server/HttpInputOverHTTP2.java | 180 +++ .../org/eclipse/jetty/server/Request.java | 2 +- .../jetty/server/HttpInputAsyncStateTest.java | 735 ------------ .../eclipse/jetty/server/HttpInputTest.java | 614 ---------- 10 files changed, 1648 insertions(+), 2375 deletions(-) create mode 100644 jetty-server/src/main/java/org/eclipse/jetty/server/HttpInputOverFCGI.java create mode 100644 jetty-server/src/main/java/org/eclipse/jetty/server/HttpInputOverHTTP2.java delete mode 100644 jetty-server/src/test/java/org/eclipse/jetty/server/HttpInputAsyncStateTest.java delete mode 100644 jetty-server/src/test/java/org/eclipse/jetty/server/HttpInputTest.java diff --git a/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/HttpChannelOverFCGI.java b/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/HttpChannelOverFCGI.java index 0c7b168604b..38a46350aae 100644 --- a/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/HttpChannelOverFCGI.java +++ b/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/HttpChannelOverFCGI.java @@ -33,7 +33,10 @@ import org.eclipse.jetty.http.MetaData; import org.eclipse.jetty.io.EndPoint; import org.eclipse.jetty.server.Connector; import org.eclipse.jetty.server.HttpChannel; +import org.eclipse.jetty.server.HttpChannelState; import org.eclipse.jetty.server.HttpConfiguration; +import org.eclipse.jetty.server.HttpInput; +import org.eclipse.jetty.server.HttpInputOverFCGI; import org.eclipse.jetty.server.HttpTransport; import org.eclipse.jetty.util.StringUtil; import org.slf4j.Logger; @@ -57,6 +60,12 @@ public class HttpChannelOverFCGI extends HttpChannel this.dispatcher = new Dispatcher(connector.getServer().getThreadPool(), this); } + @Override + protected HttpInput newHttpInput(HttpChannelState state) + { + return new HttpInputOverFCGI(state); + } + protected void header(HttpField field) { String name = field.getName(); diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannel.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannel.java index cf8e24d5516..12ff69c7ed0 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannel.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannel.java @@ -121,7 +121,8 @@ public class HttpChannel implements Runnable, HttpOutput.Interceptor protected HttpInput newHttpInput(HttpChannelState state) { - return new HttpInput(state); + //TODO the HTTP2 impl instantiation should be in a subclass + return new HttpInputOverHTTP2(state); } protected HttpOutput newHttpOutput() diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnection.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnection.java index 9b8e1061607..e5a2b1a7697 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnection.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnection.java @@ -34,6 +34,7 @@ import org.eclipse.jetty.http.HttpHeaderValue; import org.eclipse.jetty.http.HttpMethod; import org.eclipse.jetty.http.HttpParser; import org.eclipse.jetty.http.HttpParser.RequestHandler; +import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.http.MetaData; import org.eclipse.jetty.http.PreEncodedHttpField; import org.eclipse.jetty.io.AbstractConnection; @@ -316,21 +317,20 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http } /** - * Fill and parse data looking for content - * - * @return true if an {@link RequestHandler} method was called and it returned true; + * Parse and fill data, looking for content */ - protected boolean fillAndParseForContent() + protected void parseAndFillForContent() { - boolean handled = false; + // parseRequestBuffer() must always be called after fillRequestBuffer() otherwise this method doesn't trigger EOF/earlyEOF + // which breaks AsyncRequestReadTest.testPartialReadThenShutdown() + int filled = Integer.MAX_VALUE; while (_parser.inContentState()) { - int filled = fillRequestBuffer(); - handled = parseRequestBuffer(); + boolean handled = parseRequestBuffer(); if (handled || filled <= 0 || _input.hasContent()) break; + filled = fillRequestBuffer(); } - return handled; } private int fillRequestBuffer() @@ -655,8 +655,15 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http @Override public void succeeded() { - if (_contentBufferReferences.decrementAndGet() == 0) + int counter = _contentBufferReferences.decrementAndGet(); + if (counter == 0) releaseRequestBuffer(); + // TODO: this should do something (warn? fail?) if _contentBufferReferences goes below 0 + if (counter < 0) + { + LOG.warn("Content reference counting went below zero: {}", counter); + _contentBufferReferences.incrementAndGet(); + } } @Override diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpInput.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpInput.java index 02c0be87ba4..ac3bd8444ab 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpInput.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpInput.java @@ -18,542 +18,50 @@ package org.eclipse.jetty.server; -import java.io.EOFException; import java.io.IOException; import java.nio.ByteBuffer; -import java.util.ArrayDeque; -import java.util.Deque; -import java.util.Objects; -import java.util.concurrent.Executor; -import java.util.concurrent.TimeUnit; -import javax.servlet.ReadListener; import javax.servlet.ServletInputStream; -import org.eclipse.jetty.http.BadMessageException; -import org.eclipse.jetty.http.HttpStatus; -import org.eclipse.jetty.io.EofException; -import org.eclipse.jetty.io.RuntimeIOException; +import org.eclipse.jetty.server.handler.ContextHandler; import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.Callback; -import org.eclipse.jetty.util.component.Destroyable; -import org.eclipse.jetty.util.thread.AutoLock; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** - * {@link HttpInput} provides an implementation of {@link ServletInputStream} for {@link HttpChannel}. - *

- * Content may arrive in patterns such as [content(), content(), messageComplete()] - * so that this class maintains two states: the content state that tells - * whether there is content to consume and the EOF state that tells whether an EOF has arrived. - * Only once the content has been consumed the content state is moved to the EOF state. - *

+ *

This would be an interface if ServletInputStream was an interface too.

+ *

While this class is-a Runnable, it should never be dispatched in it's own thread. It is a runnable only so that the calling thread can use {@link + * ContextHandler#handle(Runnable)} to setup classloaders etc.

*/ -public class HttpInput extends ServletInputStream implements Runnable +public abstract class HttpInput extends ServletInputStream implements Runnable { - /** - * An interceptor for HTTP Request input. - *

- * Unlike InputStream wrappers that can be applied by filters, an interceptor - * is entirely transparent and works with async IO APIs. - *

- *

- * An Interceptor may consume data from the passed content and the interceptor - * will continue to be called for the same content until the interceptor returns - * null or an empty content. Thus even if the passed content is completely consumed - * the interceptor will be called with the same content until it can no longer - * produce more content. - *

- * - * @see HttpInput#setInterceptor(Interceptor) - * @see HttpInput#addInterceptor(Interceptor) - */ - public interface Interceptor - { - /** - * @param content The content to be intercepted (may be empty or a {@link SentinelContent}. - * The content will be modified with any data the interceptor consumes, but there is no requirement - * that all the data is consumed by the interceptor. - * @return The intercepted content or null if interception is completed for that content. - */ - Content readFrom(Content content); - } - /** - * An {@link Interceptor} that chains two other {@link Interceptor}s together. - * The {@link Interceptor#readFrom(Content)} calls the previous {@link Interceptor}'s - * {@link Interceptor#readFrom(Content)} and then passes any {@link Content} returned - * to the next {@link Interceptor}. - */ - public static class ChainedInterceptor implements Interceptor, Destroyable - { - private final Interceptor _prev; - private final Interceptor _next; - - public ChainedInterceptor(Interceptor prev, Interceptor next) - { - _prev = prev; - _next = next; - } - - public Interceptor getPrev() - { - return _prev; - } - - public Interceptor getNext() - { - return _next; - } - - @Override - public Content readFrom(Content content) - { - return getNext().readFrom(getPrev().readFrom(content)); - } - - @Override - public void destroy() - { - if (_prev instanceof Destroyable) - ((Destroyable)_prev).destroy(); - if (_next instanceof Destroyable) - ((Destroyable)_next).destroy(); - } - } - - private static final Logger LOG = LoggerFactory.getLogger(HttpInput.class); - static final Content EOF_CONTENT = new EofContent("EOF"); - static final Content EARLY_EOF_CONTENT = new EofContent("EARLY_EOF"); - - private final AutoLock.WithCondition _lock = new AutoLock.WithCondition(); - private final byte[] _oneByteBuffer = new byte[1]; - private Content _content; - private Content _intercepted; - private final Deque _inputQ = new ArrayDeque<>(); - private final HttpChannelState _channelState; - private ReadListener _listener; - private State _state = STREAM; - private long _firstByteTimeStamp = -1; - private long _contentArrived; - private long _contentConsumed; - private long _blockUntil; - private boolean _waitingForContent; - private Interceptor _interceptor; - - public HttpInput(HttpChannelState state) - { - _channelState = state; - } - - protected HttpChannelState getHttpChannelState() - { - return _channelState; - } - - public void recycle() - { - try (AutoLock l = _lock.lock()) - { - if (_content != null) - _content.failed(null); - _content = null; - Content item = _inputQ.poll(); - while (item != null) - { - item.failed(null); - item = _inputQ.poll(); - } - _listener = null; - _state = STREAM; - _contentArrived = 0; - _contentConsumed = 0; - _firstByteTimeStamp = -1; - _blockUntil = 0; - _waitingForContent = false; - if (_interceptor instanceof Destroyable) - ((Destroyable)_interceptor).destroy(); - _interceptor = null; - } - } + public abstract void recycle(); /** * @return The current Interceptor, or null if none set */ - public Interceptor getInterceptor() - { - return _interceptor; - } + public abstract Interceptor getInterceptor(); /** * Set the interceptor. * * @param interceptor The interceptor to use. */ - public void setInterceptor(Interceptor interceptor) - { - _interceptor = interceptor; - } + public abstract void setInterceptor(Interceptor interceptor); /** - * Set the {@link Interceptor}, using a {@link ChainedInterceptor} if - * an {@link Interceptor} is already set. + * Set the {@link org.eclipse.jetty.server.HttpInput.Interceptor}, chaining it to the existing one if + * an {@link org.eclipse.jetty.server.HttpInput.Interceptor} is already set. * - * @param interceptor the next {@link Interceptor} in a chain + * @param interceptor the next {@link org.eclipse.jetty.server.HttpInput.Interceptor} in a chain */ - public void addInterceptor(Interceptor interceptor) - { - if (_interceptor == null) - _interceptor = interceptor; - else - _interceptor = new ChainedInterceptor(_interceptor, interceptor); - } - - @Override - public int available() - { - int available = 0; - boolean woken = false; - try (AutoLock l = _lock.lock()) - { - if (_content == null) - _content = _inputQ.poll(); - if (_content == null) - { - try - { - produceContent(); - } - catch (IOException e) - { - woken = failed(e); - } - if (_content == null) - _content = _inputQ.poll(); - } - - if (_content != null) - available = _content.remaining(); - } - - if (woken) - wake(); - return available; - } - - protected void wake() - { - HttpChannel channel = _channelState.getHttpChannel(); - Executor executor = channel.getConnector().getServer().getThreadPool(); - executor.execute(channel); - } - - @Override - public int read() throws IOException - { - int read = read(_oneByteBuffer, 0, 1); - if (read == 0) - throw new IllegalStateException("unready read=0"); - return read < 0 ? -1 : _oneByteBuffer[0] & 0xFF; - } - - @Override - public int read(byte[] b, int off, int len) throws IOException - { - boolean wake = false; - int read; - try (AutoLock l = _lock.lock()) - { - // Calculate minimum request rate for DOS protection - long minRequestDataRate = _channelState.getHttpChannel().getHttpConfiguration().getMinRequestDataRate(); - if (minRequestDataRate > 0 && _firstByteTimeStamp != -1) - { - long period = System.nanoTime() - _firstByteTimeStamp; - if (period > 0) - { - long minimumData = minRequestDataRate * TimeUnit.NANOSECONDS.toMillis(period) / TimeUnit.SECONDS.toMillis(1); - if (_contentArrived < minimumData) - { - BadMessageException bad = new BadMessageException(HttpStatus.REQUEST_TIMEOUT_408, - String.format("Request content data rate < %d B/s", minRequestDataRate)); - if (_channelState.isResponseCommitted()) - _channelState.getHttpChannel().abort(bad); - throw bad; - } - } - } - - // Consume content looking for bytes to read - while (true) - { - Content item = nextContent(); - if (item != null) - { - read = get(item, b, off, len); - if (LOG.isDebugEnabled()) - LOG.debug("{} read {} from {}", this, read, item); - - // Consume any following poison pills - if (item.isEmpty()) - nextInterceptedContent(); - break; - } - - // No content, so should we block? - if (!_state.blockForContent(this)) - { - // Not blocking, so what should we return? - read = _state.noContent(); - - if (read < 0) - // If EOF do we need to wake for allDataRead callback? - wake = _channelState.onReadEof(); - break; - } - } - } - - if (wake) - wake(); - return read; - } - - /** - * Called when derived implementations should attempt to produce more Content and add it via {@link #addContent(Content)}. For protocols that are constantly - * producing (eg HTTP2) this can be left as a noop; - * - * @throws IOException if unable to produce content - */ - protected void produceContent() throws IOException - { - } + public abstract void addInterceptor(Interceptor interceptor); /** * Called by channel when asynchronous IO needs to produce more content * * @throws IOException if unable to produce content */ - public void asyncReadProduce() throws IOException - { - try (AutoLock l = _lock.lock()) - { - produceContent(); - } - } - - /** - * Get the next content from the inputQ, calling {@link #produceContent()} if need be. EOF is processed and state changed. - * - * @return the content or null if none available. - * @throws IOException if retrieving the content fails - */ - protected Content nextContent() throws IOException - { - Content content = nextNonSentinelContent(); - if (content == null && !isFinished()) - { - produceContent(); - content = nextNonSentinelContent(); - } - return content; - } - - /** - * Poll the inputQ for Content. Consumed buffers and {@link SentinelContent}s are removed and EOF state updated if need be. - * - * @return Content or null - */ - protected Content nextNonSentinelContent() - { - while (true) - { - // Get the next content (or EOF) - Content content = nextInterceptedContent(); - - // If it is EOF, consume it here - if (content instanceof SentinelContent) - { - // Consume the EOF content, either if it was original content - // or if it was produced by interception - consume(content); - continue; - } - - return content; - } - } - - /** - * Get the next readable from the inputQ, calling {@link #produceContent()} if need be. EOF is NOT processed and state is not changed. - * - * @return the content or EOF or null if none available. - * @throws IOException if retrieving the content fails - */ - protected Content produceNextContext() throws IOException - { - Content content = nextInterceptedContent(); - if (content == null && !isFinished()) - { - produceContent(); - content = nextInterceptedContent(); - } - return content; - } - - /** - * Poll the inputQ for Content or EOF. Consumed buffers and non EOF {@link SentinelContent}s are removed. EOF state is not updated. - * Interception is done within this method. - * - * @return Content with remaining, a {@link SentinelContent}, or null - */ - protected Content nextInterceptedContent() - { - // If we have a chunk produced by interception - if (_intercepted != null) - { - // Use it if it has any remaining content - if (_intercepted.hasContent()) - return _intercepted; - - // succeed the chunk - _intercepted.succeeded(); - _intercepted = null; - } - - // If we don't have a Content under consideration, get - // the next one off the input Q. - if (_content == null) - _content = _inputQ.poll(); - - // While we have content to consider. - while (_content != null) - { - // Are we intercepting? - if (_interceptor != null) - { - // Intercept the current content (may be called several - // times for the same content - _intercepted = _interceptor.readFrom(_content); - - // If interception produced new content - if (_intercepted != null && _intercepted != _content) - { - // if it is not empty use it - if (_intercepted.hasContent()) - return _intercepted; - _intercepted.succeeded(); - } - - // intercepted content consumed - _intercepted = null; - - // fall through so that the unintercepted _content is - // considered for any remaining content, for EOF and to - // succeed it if it is entirely consumed. - } - - // If the content has content or is an EOF marker, use it - if (_content.hasContent() || _content instanceof SentinelContent) - return _content; - - // The content is consumed, so get the next one. Note that EOF - // content is never consumed here, but in #pollContent - _content.succeeded(); - _content = _inputQ.poll(); - } - - return null; - } - - private void consume(Content content) - { - if (!isError() && content instanceof EofContent) - { - if (content == EARLY_EOF_CONTENT) - _state = EARLY_EOF; - else if (_listener == null) - _state = EOF; - else - _state = AEOF; - } - - // Consume the content, either if it was original content - // or if it was produced by interception - content.succeeded(); - if (_content == content) - _content = null; - else if (_intercepted == content) - _intercepted = null; - } - - /** - * Copies the given content into the given byte buffer. - * - * @param content the content to copy from - * @param buffer the buffer to copy into - * @param offset the buffer offset to start copying from - * @param length the space available in the buffer - * @return the number of bytes actually copied - */ - protected int get(Content content, byte[] buffer, int offset, int length) - { - int l = content.get(buffer, offset, length); - _contentConsumed += l; - return l; - } - - /** - * Consumes the given content. Calls the content succeeded if all content consumed. - * - * @param content the content to consume - * @param length the number of bytes to consume - */ - protected void skip(Content content, int length) - { - int l = content.skip(length); - - _contentConsumed += l; - if (l > 0 && content.isEmpty()) - nextNonSentinelContent(); // hungry succeed - } - - /** - * Blocks until some content or some end-of-file event arrives. - * - * @throws IOException if the wait is interrupted - */ - protected void blockForContent() throws IOException - { - assert _lock.isHeldByCurrentThread(); - try - { - _waitingForContent = true; - _channelState.getHttpChannel().onBlockWaitForContent(); - - boolean loop = false; - long timeout = 0; - while (true) - { - // This method is called from a loop, so we just - // need to check the timeout before and after waiting. - if (loop) - break; - - if (LOG.isDebugEnabled()) - LOG.debug("{} blocking for content timeout={}", this, timeout); - if (timeout > 0) - _lock.await(timeout, TimeUnit.MILLISECONDS); - else - _lock.await(); - - loop = true; - } - } - catch (Throwable x) - { - _channelState.getHttpChannel().onBlockWaitForContentFailure(x); - } - } + public abstract void asyncReadProduce() throws IOException; /** * Adds some content to this input stream. @@ -561,70 +69,17 @@ public class HttpInput extends ServletInputStream implements Runnable * @param content the content to add * @return true if content channel woken for read */ - public boolean addContent(Content content) - { - try (AutoLock l = _lock.lock()) - { - _waitingForContent = false; - if (_firstByteTimeStamp == -1) - _firstByteTimeStamp = System.nanoTime(); + public abstract boolean addContent(Content content); - if (isFinished()) - { - Throwable failure = isError() ? _state.getError() : new EOFException("Content after EOF"); - content.failed(failure); - return false; - } - else - { - _contentArrived += content.remaining(); + public abstract boolean hasContent(); - if (_content == null && _inputQ.isEmpty()) - _content = content; - else - _inputQ.offer(content); + public abstract void unblock(); - if (LOG.isDebugEnabled()) - LOG.debug("{} addContent {}", this, content); - - if (nextInterceptedContent() != null) - return wakeup(); - else - return false; - } - } - } - - public boolean hasContent() - { - try (AutoLock l = _lock.lock()) - { - return _content != null || _inputQ.size() > 0; - } - } - - public void unblock() - { - try (AutoLock.WithCondition l = _lock.lock()) - { - l.signal(); - } - } - - public long getContentConsumed() - { - try (AutoLock l = _lock.lock()) - { - return _contentConsumed; - } - } + public abstract long getContentLength(); public long getContentReceived() { - synchronized (_inputQ) - { - return _contentArrived; - } + return getContentLength(); } /** @@ -634,350 +89,40 @@ public class HttpInput extends ServletInputStream implements Runnable * * @return true if content channel woken for read */ - public boolean earlyEOF() - { - return addContent(EARLY_EOF_CONTENT); - } + public abstract boolean earlyEOF(); /** * This method should be called to signal that all the expected content arrived. * * @return true if content channel woken for read */ - public boolean eof() - { - return addContent(EOF_CONTENT); - } + public abstract boolean eof(); - public boolean consumeAll() - { - try (AutoLock l = _lock.lock()) - { - try - { - while (true) - { - Content item = nextContent(); - if (item == null) - break; // Let's not bother blocking + public abstract boolean consumeAll(); - skip(item, item.remaining()); - } - if (isFinished()) - return !isError(); + public abstract boolean isError(); - _state = EARLY_EOF; - return false; - } - catch (Throwable e) - { - if (LOG.isDebugEnabled()) - LOG.debug("Unable to consume all input", e); - _state = new ErrorState(e); - return false; - } - } - } + public abstract boolean isAsync(); - public boolean isError() - { - try (AutoLock l = _lock.lock()) - { - return _state instanceof ErrorState; - } - } + public abstract boolean onIdleTimeout(Throwable x); - public boolean isAsync() - { - try (AutoLock l = _lock.lock()) - { - return _state == ASYNC; - } - } + public abstract boolean failed(Throwable x); @Override - public boolean isFinished() - { - try (AutoLock l = _lock.lock()) - { - return _state instanceof EOFState; - } - } + public abstract int read(byte[] b, int off, int len) throws IOException; @Override - public boolean isReady() + public abstract int available() throws IOException; + + public interface Interceptor { - try - { - try (AutoLock l = _lock.lock()) - { - if (_listener == null) - return true; - if (_state instanceof EOFState) - return true; - if (_waitingForContent) - return false; - if (produceNextContext() != null) - return true; - _channelState.onReadUnready(); - _waitingForContent = true; - } - return false; - } - catch (IOException e) - { - LOG.trace("IGNORED", e); - return true; - } - } - - @Override - public void setReadListener(ReadListener readListener) - { - boolean woken = false; - try - { - try (AutoLock l = _lock.lock()) - { - if (_listener != null) - throw new IllegalStateException("ReadListener already set"); - - _listener = Objects.requireNonNull(readListener); - - //illegal if async not started - if (!_channelState.isAsyncStarted()) - throw new IllegalStateException("Async not started"); - - if (isError()) - { - woken = _channelState.onReadReady(); - } - else - { - Content content = produceNextContext(); - if (content != null) - { - _state = ASYNC; - woken = _channelState.onReadReady(); - } - else if (_state == EOF) - { - _state = AEOF; - woken = _channelState.onReadEof(); - } - else - { - _state = ASYNC; - _channelState.onReadUnready(); - _waitingForContent = true; - } - } - } - } - catch (IOException e) - { - throw new RuntimeIOException(e); - } - - if (woken) - wake(); - } - - public boolean onIdleTimeout(Throwable x) - { - try (AutoLock l = _lock.lock()) - { - boolean neverDispatched = getHttpChannelState().isIdle(); - if ((_waitingForContent || neverDispatched) && !isError()) - { - x.addSuppressed(new Throwable("HttpInput idle timeout")); - _state = new ErrorState(x); - return wakeup(); - } - return false; - } - } - - public boolean failed(Throwable x) - { - try (AutoLock l = _lock.lock()) - { - // Errors may be reported multiple times, for example - // a local idle timeout and a remote I/O failure. - if (isError()) - { - if (LOG.isDebugEnabled()) - { - // Log both the original and current failure - // without modifying the original failure. - Throwable failure = new Throwable(_state.getError()); - failure.addSuppressed(x); - LOG.debug("HttpInput failure", failure); - } - } - else - { - // Add a suppressed throwable to capture this stack - // trace without wrapping/hiding the original failure. - x.addSuppressed(new Throwable("HttpInput failure")); - _state = new ErrorState(x); - } - return wakeup(); - } - } - - private boolean wakeup() - { - assert _lock.isHeldByCurrentThread(); - if (_listener != null) - return _channelState.onContentAdded(); - _lock.signal(); - return false; - } - - /* - *

While this class is-a Runnable, it should never be dispatched in it's own thread. It is a runnable only so that the calling thread can use {@link - * ContextHandler#handle(Runnable)} to setup classloaders etc.

- */ - @Override - public void run() - { - final ReadListener listener; - Throwable error; - boolean aeof = false; - - try (AutoLock l = _lock.lock()) - { - listener = _listener; - - if (_state == EOF) - return; - - if (_state == AEOF) - { - _state = EOF; - aeof = true; - } - - error = _state.getError(); - - if (!aeof && error == null) - { - Content content = nextInterceptedContent(); - if (content == null) - return; - - // Consume a directly received EOF without first calling onDataAvailable - // So -1 will never be read and only onAddDataRread or onError will be called - if (content instanceof EofContent) - { - consume(content); - if (_state == EARLY_EOF) - error = _state.getError(); - else if (_state == AEOF) - { - aeof = true; - _state = EOF; - } - } - } - } - - try - { - if (error != null) - { - // TODO is this necessary to add here? - _channelState.getHttpChannel().getResponse().getHttpFields().add(HttpConnection.CONNECTION_CLOSE); - listener.onError(error); - } - else if (aeof) - { - listener.onAllDataRead(); - } - else - { - listener.onDataAvailable(); - // If -1 was read, then HttpChannelState#onEOF will have been called and a subsequent - // unhandle will call run again so onAllDataRead() can be called. - } - } - catch (Throwable e) - { - if (LOG.isDebugEnabled()) - LOG.warn("Unable to notify listener", e); - else - LOG.warn("Unable to notify listener: {}", e.toString()); - try - { - if (aeof || error == null) - { - _channelState.getHttpChannel().getResponse().getHttpFields().add(HttpConnection.CONNECTION_CLOSE); - listener.onError(e); - } - } - catch (Throwable e2) - { - String msg = "Unable to notify error to listener"; - if (LOG.isDebugEnabled()) - LOG.warn(msg, e2); - else - LOG.warn("{}: {}", msg, e2.toString()); - throw new RuntimeIOException(msg, e2); - } - } - } - - @Override - public String toString() - { - State state; - long consumed; - int q; - Content content; - try (AutoLock l = _lock.lock()) - { - state = _state; - consumed = _contentConsumed; - q = _inputQ.size(); - content = _inputQ.peekFirst(); - } - return String.format("%s@%x[c=%d,q=%d,[0]=%s,s=%s]", - getClass().getSimpleName(), - hashCode(), - consumed, - q, - content, - state); - } - - /** - * A Sentinel Content, which has zero length content but - * indicates some other event in the input stream (eg EOF) - */ - public static class SentinelContent extends Content - { - private final String _name; - - public SentinelContent(String name) - { - super(BufferUtil.EMPTY_BUFFER); - _name = name; - } - - @Override - public String toString() - { - return _name; - } - } - - public static class EofContent extends SentinelContent - { - EofContent(String name) - { - super(name); - } + /** + * @param content The content to be intercepted. + * The content will be modified with any data the interceptor consumes, but there is no requirement + * that all the data is consumed by the interceptor. + * @return The intercepted content or null if interception is completed for that content. + */ + Content readFrom(Content content); } public static class Content implements Callback @@ -1036,125 +181,4 @@ public class HttpInput extends ServletInputStream implements Runnable } } - protected abstract static class State - { - public boolean blockForContent(HttpInput in) throws IOException - { - return false; - } - - public int noContent() throws IOException - { - return -1; - } - - public Throwable getError() - { - return null; - } - } - - protected static class EOFState extends State - { - } - - protected class ErrorState extends EOFState - { - final Throwable _error; - - ErrorState(Throwable error) - { - _error = error; - } - - @Override - public Throwable getError() - { - return _error; - } - - @Override - public int noContent() throws IOException - { - if (_error instanceof IOException) - throw (IOException)_error; - throw new IOException(_error); - } - - @Override - public String toString() - { - return "ERROR:" + _error; - } - } - - protected static final State STREAM = new State() - { - @Override - public boolean blockForContent(HttpInput input) throws IOException - { - input.blockForContent(); - return true; - } - - @Override - public String toString() - { - return "STREAM"; - } - }; - - protected static final State ASYNC = new State() - { - @Override - public int noContent() throws IOException - { - return 0; - } - - @Override - public String toString() - { - return "ASYNC"; - } - }; - - protected static final State EARLY_EOF = new EOFState() - { - @Override - public int noContent() throws IOException - { - throw getError(); - } - - @Override - public String toString() - { - return "EARLY_EOF"; - } - - @Override - public IOException getError() - { - return new EofException("Early EOF"); - } - }; - - protected static final State EOF = new EOFState() - { - @Override - public String toString() - { - return "EOF"; - } - }; - - protected static final State AEOF = new EOFState() - { - @Override - public String toString() - { - return "AEOF"; - } - }; } diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpInputOverFCGI.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpInputOverFCGI.java new file mode 100644 index 00000000000..03da948c2f1 --- /dev/null +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpInputOverFCGI.java @@ -0,0 +1,699 @@ +// +// ======================================================================== +// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under +// the terms of the Eclipse Public License 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0 +// +// This Source Code may also be made available under the following +// Secondary Licenses when the conditions for such availability set +// forth in the Eclipse Public License, v. 2.0 are satisfied: +// the Apache License v2.0 which is available at +// https://www.apache.org/licenses/LICENSE-2.0 +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +package org.eclipse.jetty.server; + +import java.io.IOException; +import java.util.Objects; +import java.util.Queue; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.locks.Condition; +import javax.servlet.ReadListener; + +import org.eclipse.jetty.io.EofException; +import org.eclipse.jetty.util.component.Destroyable; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; +import org.eclipse.jetty.util.thread.AutoLock; + +import static org.eclipse.jetty.server.HttpInputOverHTTP.Eof; + +public class HttpInputOverFCGI extends HttpInput +{ + private static final Logger LOG = Log.getLogger(HttpInputOverFCGI.class); + + private final byte[] _oneByteBuffer = new byte[1]; + private final HttpChannelState _channelState; + + private final AutoLock _contentLock = new AutoLock(); + private final Condition _contentLockCondition = _contentLock.newCondition(); + + private final ContentProducer _contentProducer; + private Eof _eof = Eof.NOT_YET; + private Throwable _error; + private ReadListener _readListener; + + public HttpInputOverFCGI(HttpChannelState state) + { + _channelState = state; + _contentProducer = new ContentProducer(); + } + + /* HttpInput */ + + @Override + public void recycle() + { + try (AutoLock lock = _contentLock.lock()) + { + if (LOG.isDebugEnabled()) + LOG.debug("recycle"); + _contentProducer.recycle(); + _eof = Eof.NOT_YET; + _error = null; + _readListener = null; + } + } + + @Override + public Interceptor getInterceptor() + { + try (AutoLock lock = _contentLock.lock()) + { + return _contentProducer.getInterceptor(); + } + } + + @Override + public void setInterceptor(Interceptor interceptor) + { + try (AutoLock lock = _contentLock.lock()) + { + _contentProducer.setInterceptor(interceptor); + } + } + + @Override + public void addInterceptor(Interceptor interceptor) + { + try (AutoLock lock = _contentLock.lock()) + { + _contentProducer.addInterceptor(interceptor); + } + } + + @Override + public void asyncReadProduce() + { + try (AutoLock lock = _contentLock.lock()) + { + if (LOG.isDebugEnabled()) + LOG.debug("asyncReadProduce {}", _contentProducer); + _contentProducer.produceRawContent(); + } + } + + @Override + public boolean addContent(Content content) + { + try (AutoLock lock = _contentLock.lock()) + { + if (LOG.isDebugEnabled()) + LOG.debug("addContent {} {}", content, _contentProducer); + long contentArrived = _contentProducer.addContent(content); + long requestContentLength = _channelState.getHttpChannel().getRequest().getContentLengthLong(); + // return false to make the parser go on, true to make it stop + // -> tell the parser to stop adding content, unless we have read everything + boolean stopParsing = requestContentLength == -1 || contentArrived < requestContentLength; + if (isAsync()) + _channelState.onContentAdded(); + return stopParsing; + } + } + + @Override + public boolean hasContent() + { + try (AutoLock lock = _contentLock.lock()) + { + return _contentProducer.hasRawContent(); + } + } + + @Override + public void unblock() + { + try (AutoLock lock = _contentLock.lock()) + { + if (LOG.isDebugEnabled()) + LOG.debug("signalling blocked thread to wake up"); + _contentLockCondition.signal(); + } + } + + @Override + public long getContentLength() + { + try (AutoLock lock = _contentLock.lock()) + { + return _contentProducer.getRawContentArrived(); + } + } + + @Override + public boolean earlyEOF() + { + try (AutoLock lock = _contentLock.lock()) + { + if (LOG.isDebugEnabled()) + LOG.debug("received early EOF"); + _eof = Eof.EARLY_EOF; + if (isAsync()) + return _channelState.onContentAdded(); + unblock(); + return false; + } + } + + @Override + public boolean eof() + { + try (AutoLock lock = _contentLock.lock()) + { + if (LOG.isDebugEnabled()) + LOG.debug("received EOF"); + _eof = Eof.EOF; + if (isAsync()) + return _channelState.onContentAdded(); + unblock(); + return false; + } + } + + @Override + public boolean consumeAll() + { + try (AutoLock lock = _contentLock.lock()) + { + if (LOG.isDebugEnabled()) + LOG.debug("consume all"); + _contentProducer.consumeAll(); + if (_eof.isEof()) + _eof = Eof.CONSUMED_EOF; + + if (isFinished()) + return !isError(); + + _eof = Eof.EARLY_EOF; + return false; + } + } + + @Override + public boolean isError() + { + try (AutoLock lock = _contentLock.lock()) + { + return _error != null; + } + } + + @Override + public boolean isAsync() + { + try (AutoLock lock = _contentLock.lock()) + { + return _readListener != null; + } + } + + @Override + public boolean onIdleTimeout(Throwable x) + { + //TODO implement me! + return false; + } + + @Override + public boolean failed(Throwable x) + { + try (AutoLock lock = _contentLock.lock()) + { + if (LOG.isDebugEnabled()) + LOG.debug("failed " + x); + if (_error != null) + _error.addSuppressed(x); + else + _error = x; + + if (isAsync()) + return _channelState.onContentAdded(); + unblock(); + return false; + } + } + + /* ServletInputStream */ + + @Override + public boolean isFinished() + { + try (AutoLock lock = _contentLock.lock()) + { + boolean finished = !_contentProducer.hasRawContent() && _eof.isConsumed(); + if (LOG.isDebugEnabled()) + LOG.debug("isFinished? {}", finished); + return finished; + } + } + + @Override + public boolean isReady() + { + try (AutoLock lock = _contentLock.lock()) + { + // calling _contentProducer.hasTransformedContent() might change the _eof state, so the following test order matters + if (_contentProducer.hasTransformedContent() || _eof.isEof()) + { + if (LOG.isDebugEnabled()) + LOG.debug("isReady? true"); + return true; + } + if (LOG.isDebugEnabled()) + LOG.debug("isReady? false"); + _channelState.onReadUnready(); + return false; + } + } + + @Override + public void setReadListener(ReadListener readListener) + { + try (AutoLock lock = _contentLock.lock()) + { + if (_readListener != null) + throw new IllegalStateException("ReadListener already set"); + _readListener = Objects.requireNonNull(readListener); + + if (LOG.isDebugEnabled()) + LOG.debug("setReadListener error=" + _error + " eof=" + _eof + " " + _contentProducer); + boolean woken; + if (isError()) + { + woken = _channelState.onReadReady(); + } + else + { + if (_contentProducer.hasTransformedContent()) + { + woken = _channelState.onReadReady(); + } + else if (_eof.isEof()) + { + woken = _channelState.onReadEof(); + } + else + { + _channelState.onReadUnready(); + woken = false; + } + } + + if (LOG.isDebugEnabled()) + LOG.debug("setReadListener woken=" + woken); + if (woken) + scheduleReadListenerNotification(); + } + } + + private void scheduleReadListenerNotification() + { + HttpChannel channel = _channelState.getHttpChannel(); + channel.execute(channel); + } + + @Override + public int read() throws IOException + { + int read = read(_oneByteBuffer, 0, 1); + if (read == 0) + throw new IOException("unready read=0"); + return read < 0 ? -1 : _oneByteBuffer[0] & 0xFF; + } + + @Override + public int read(byte[] b, int off, int len) throws IOException + { + try (AutoLock lock = _contentLock.lock()) + { + while (true) + { + int read = _contentProducer.read(b, off, len); + if (LOG.isDebugEnabled()) + LOG.debug("read produced {} byte(s)", read); + if (read > 0) + return read; + + if (LOG.isDebugEnabled()) + LOG.debug("read error = " + _error); + if (_error != null) + throw new IOException(_error); + + if (LOG.isDebugEnabled()) + LOG.debug("read EOF = {}", _eof); + if (_eof.isEarly()) + throw new EofException("Early EOF"); + + if (LOG.isDebugEnabled()) + LOG.debug("read async = {}", isAsync()); + if (!isAsync()) + { + if (_eof.isEof()) + { + _eof = Eof.CONSUMED_EOF; + if (LOG.isDebugEnabled()) + LOG.debug("read on EOF, switching to CONSUMED_EOF and returning"); + return -1; + } + if (LOG.isDebugEnabled()) + LOG.debug("read blocked"); + blockForContent(); + if (LOG.isDebugEnabled()) + LOG.debug("read unblocked"); + } + else + { + if (_eof.isEof()) + { + boolean wasInAsyncWait = _channelState.onReadEof(); + if (wasInAsyncWait) + scheduleReadListenerNotification(); + if (LOG.isDebugEnabled()) + LOG.debug("async read on EOF (was in async wait? {}), switching to CONSUMED_EOF and returning", wasInAsyncWait); + _eof = Eof.CONSUMED_EOF; + return -1; + } + else + { + //TODO returning 0 breaks the InputStream contract. Shouldn't IOException be thrown instead? + _channelState.getHttpChannel().onAsyncWaitForContent(); // switches on fill interested + return 0; + } + } + } + } + } + + @Override + public int available() + { + try (AutoLock lock = _contentLock.lock()) + { + int available = _contentProducer.available(); + if (LOG.isDebugEnabled()) + LOG.debug("available = {}", available); + return available; + } + } + + private void blockForContent() + { + try + { + _channelState.getHttpChannel().onBlockWaitForContent(); // switches on fill interested + if (LOG.isDebugEnabled()) + LOG.debug("waiting for signal to wake up"); + _contentLockCondition.await(); + if (LOG.isDebugEnabled()) + LOG.debug("signalled to wake up"); + } + catch (Throwable x) + { + _channelState.getHttpChannel().onBlockWaitForContentFailure(x); + } + } + + /* Runnable */ + + /* + *

While this class is-a Runnable, it should never be dispatched in it's own thread. It is a runnable only so that the calling thread can use {@link + * ContextHandler#handle(Runnable)} to setup classloaders etc.

+ */ + @Override + public void run() + { + try (AutoLock lock = _contentLock.lock()) + { + if (LOG.isDebugEnabled()) + LOG.debug("running"); + if (!_contentProducer.hasRawContent()) + { + if (_error != null || _eof.isEarly()) + { + // TODO is this necessary to add here? + _channelState.getHttpChannel().getResponse().getHttpFields().add(HttpConnection.CONNECTION_CLOSE); + if (_error != null) + _readListener.onError(_error); + else + _readListener.onError(new EofException("Early EOF")); + } + else if (_eof.isEof()) + { + try + { + _readListener.onAllDataRead(); + } + catch (Throwable x) + { + _readListener.onError(x); + } + } + // else: !hasContent() && !error && !EOF -> no-op + } + else + { + try + { + _readListener.onDataAvailable(); + } + catch (Throwable x) + { + _readListener.onError(x); + } + } + } + } + + private static class ContentProducer + { + // Note: _rawContent can never be null for as long as _transformedContent is not null. + private final Queue _rawContentQueue = new LinkedBlockingQueue<>(); + private HttpInput.Content _currentRawContent; + private HttpInput.Content _transformedContent; + private long _rawContentArrived; + private HttpInput.Interceptor _interceptor; + + void recycle() + { + if (LOG.isDebugEnabled()) + LOG.debug("recycle {}", this); + if (_transformedContent == _currentRawContent) + _transformedContent = null; + if (_transformedContent != null && !_transformedContent.isEmpty()) + _transformedContent.failed(null); + _transformedContent = null; + if (_currentRawContent != null && !_currentRawContent.isEmpty()) + _currentRawContent.failed(null); + _currentRawContent = null; + _rawContentQueue.clear(); + _rawContentArrived = 0L; + if (_interceptor instanceof Destroyable) + ((Destroyable)_interceptor).destroy(); + _interceptor = null; + } + + int available() + { + if (_currentRawContent == null) + produceRawContent(); + return _currentRawContent == null ? 0 : _currentRawContent.remaining(); + } + + long getRawContentArrived() + { + return _rawContentArrived; + } + + boolean hasRawContent() + { + return _currentRawContent != null; + } + + boolean hasTransformedContent() + { + if (_transformedContent != null) + return true; + if (_currentRawContent == null) + produceRawContent(); + produceTransformedContent(); + return _transformedContent != null; + } + + HttpInput.Interceptor getInterceptor() + { + return _interceptor; + } + + void setInterceptor(HttpInput.Interceptor interceptor) + { + this._interceptor = interceptor; + } + + void addInterceptor(HttpInput.Interceptor interceptor) + { + if (_interceptor == null) + _interceptor = interceptor; + else + _interceptor = new HttpInputOverHTTP.ChainedInterceptor(_interceptor, interceptor); + } + + long addContent(HttpInput.Content content) + { + if (LOG.isDebugEnabled()) + LOG.debug("{} addContent {}", this, content); + if (content == null) + throw new AssertionError("Cannot add null content"); + + _rawContentQueue.offer(content); + _rawContentArrived += content.remaining(); + + return _rawContentArrived; + } + + void consumeAll() + { + if (LOG.isDebugEnabled()) + LOG.debug("{} consumeAll", this); + // start by depleting the current _transformedContent + if (_transformedContent != null) + { + _transformedContent.skip(_transformedContent.remaining()); + if (_transformedContent != _currentRawContent) + _transformedContent.succeeded(); + _transformedContent = null; + } + + // don't bother transforming content, directly deplete the raw one + while (true) + { + if (_currentRawContent == null) + produceRawContent(); + if (_currentRawContent == null) + break; + + _currentRawContent.skip(_currentRawContent.remaining()); + _currentRawContent.succeeded(); + _currentRawContent = null; + } + } + + int read(byte[] b, int off, int len) + { + if (LOG.isDebugEnabled()) + LOG.debug("{} read", this); + while (_transformedContent == null) + { + if (_currentRawContent == null) + { + produceRawContent(); + if (_currentRawContent == null) + return 0; + } + produceTransformedContent(); + } + + int read = _transformedContent.get(b, off, len); + if (_transformedContent.isEmpty()) + produceTransformedContent(); + + return read; + } + + /** + * Call the parser so that it's going to continue parsing of the request buffer, filling it with the socket's buffer + * if needed until either the request buffer is empty with no bytes left in the socket's buffer or {@link #addContent(Content)} + * is called. + */ + void produceRawContent() + { + if (LOG.isDebugEnabled()) + LOG.debug("{} produceRawContent", this); + _currentRawContent = _rawContentQueue.poll(); + } + + /** + * Read {@code _rawContent} and {@code _transformedContent} to produce the next non-empty content to work with and store it in {@code _transformedContent}, + * or store null in {@code _transformedContent} if there is no content to work with. + * Depleted content gets succeeded and its field nullified, which can happen for both {@code _rawContent} and {@code _transformedContent}. + */ + private void produceTransformedContent() + { + if (LOG.isDebugEnabled()) + LOG.debug("{} produceTransformedContent", this); + if (_interceptor == null) + { + // no interceptor set + if (_currentRawContent != null && _currentRawContent.isEmpty()) + { + _currentRawContent.succeeded(); + _currentRawContent = null; + _transformedContent = null; + } + else + { + _transformedContent = _currentRawContent; + } + } + else + { + // interceptor set + transformContent(); + if (_transformedContent == null) + { + if (_currentRawContent != null && _currentRawContent.isEmpty()) + { + _currentRawContent.succeeded(); + _currentRawContent = null; + } + else + { + _transformedContent = _currentRawContent; + } + } + } + } + + /** + * Read {@code _rawContent} and write {@code _transformedContent} to produce content using the interceptor. + * The produced content is guaranteed to either be null or not empty. + */ + private void transformContent() + { + if (LOG.isDebugEnabled()) + LOG.debug("{} transformContent", this); + if (_currentRawContent == null) + return; + + _transformedContent = _interceptor.readFrom(_currentRawContent); + + if (_transformedContent != null && _transformedContent.isEmpty()) + { + if (_transformedContent != _currentRawContent) + _transformedContent.succeeded(); + _transformedContent = null; + } + } + + @Override + public String toString() + { + return getClass().getSimpleName() + "[i=" + _interceptor + ",b=" + _rawContentArrived + + ",r=" + _currentRawContent + ",t=" + _transformedContent + "]"; + } + } +} diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpInputOverHTTP.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpInputOverHTTP.java index 6f646c4ce61..a7f1ee9f22e 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpInputOverHTTP.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpInputOverHTTP.java @@ -19,17 +19,719 @@ package org.eclipse.jetty.server; import java.io.IOException; +import java.util.Objects; +import javax.servlet.ReadListener; +import org.eclipse.jetty.io.EofException; +import org.eclipse.jetty.util.component.Destroyable; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; + +// tests used: RequestTest, PartialRFC2616Test, AsyncRequestReadTest, AsyncIOServletTest, GzipHandlerTest public class HttpInputOverHTTP extends HttpInput { + private static final Logger LOG = Log.getLogger(HttpInputOverHTTP.class); + + private final byte[] _oneByteBuffer = new byte[1]; + private final HttpChannelState _channelState; + + private final NotifyingSemaphore _semaphore = new NotifyingSemaphore(); + + // TODO: think about thread visibility of the below variables + private final ContentProducer _contentProducer; + private Eof _eof = Eof.NOT_YET; + private Throwable _error; + private ReadListener _readListener; + public HttpInputOverHTTP(HttpChannelState state) { - super(state); + _channelState = state; + _contentProducer = new ContentProducer(() -> ((HttpConnection)state.getHttpChannel().getEndPoint().getConnection()).parseAndFillForContent()); + } + + /* HttpInput */ + + @Override + public void recycle() + { + if (LOG.isDebugEnabled()) + LOG.debug("recycle"); + _contentProducer.recycle(); + _eof = Eof.NOT_YET; + _error = null; + _readListener = null; } @Override - protected void produceContent() throws IOException + public Interceptor getInterceptor() { - ((HttpConnection)getHttpChannelState().getHttpChannel().getEndPoint().getConnection()).fillAndParseForContent(); + return _contentProducer.getInterceptor(); + } + + @Override + public void setInterceptor(Interceptor interceptor) + { + _contentProducer.setInterceptor(interceptor); + } + + @Override + public void addInterceptor(Interceptor interceptor) + { + _contentProducer.addInterceptor(interceptor); + } + + @Override + public void asyncReadProduce() + { + if (LOG.isDebugEnabled()) + LOG.debug("asyncReadProduce {}", _contentProducer); + _contentProducer.produceRawContent(); + } + + @Override + public boolean addContent(Content content) + { + if (LOG.isDebugEnabled()) + LOG.debug("addContent {} {}", content, _contentProducer); + long contentArrived = _contentProducer.addContent(content); + long requestContentLength = _channelState.getHttpChannel().getRequest().getContentLengthLong(); + // return false to make the parser go on, true to make it stop + // -> tell the parser to stop adding content, unless we have read everything + boolean stopParsing = requestContentLength == -1 || contentArrived < requestContentLength; + if (isAsync()) + _channelState.onContentAdded(); + return stopParsing; + } + + @Override + public boolean hasContent() + { + return _contentProducer.hasRawContent(); + } + + @Override + public void unblock() + { + if (LOG.isDebugEnabled()) + LOG.debug("signalling blocked thread to wake up"); + _semaphore.release(); + } + + @Override + public long getContentLength() + { + return _contentProducer.getRawContentArrived(); + } + + @Override + public boolean earlyEOF() + { + if (LOG.isDebugEnabled()) + LOG.debug("received early EOF"); + _eof = Eof.EARLY_EOF; + if (isAsync()) + return _channelState.onContentAdded(); + unblock(); + return false; + } + + @Override + public boolean eof() + { + if (LOG.isDebugEnabled()) + LOG.debug("received EOF"); + _eof = Eof.EOF; + if (isAsync()) + return _channelState.onContentAdded(); + unblock(); + return false; + } + + @Override + public boolean consumeAll() + { + if (LOG.isDebugEnabled()) + LOG.debug("consume all"); + _contentProducer.consumeAll(); + if (_eof.isEof()) + _eof = Eof.CONSUMED_EOF; + + if (isFinished()) + return !isError(); + + _eof = Eof.EARLY_EOF; + return false; + } + + @Override + public boolean isError() + { + return _error != null; + } + + @Override + public boolean isAsync() + { + return _readListener != null; + } + + @Override + public boolean onIdleTimeout(Throwable x) + { + //TODO implement me! + return false; + } + + @Override + public boolean failed(Throwable x) + { + if (LOG.isDebugEnabled()) + LOG.debug("failed " + x); + if (_error != null) + _error.addSuppressed(x); + else + _error = x; + + if (isAsync()) + return _channelState.onContentAdded(); + unblock(); + return false; + } + + /* ServletInputStream */ + + @Override + public boolean isFinished() + { + boolean finished = !_contentProducer.hasRawContent() && _eof.isConsumed(); + if (LOG.isDebugEnabled()) + LOG.debug("isFinished? {}", finished); + return finished; + } + + @Override + public boolean isReady() + { + // calling _contentProducer.hasTransformedContent() might change the _eof state, so the following test order matters + if (_contentProducer.hasTransformedContent() || _eof.isEof()) + { + if (LOG.isDebugEnabled()) + LOG.debug("isReady? true"); + return true; + } + if (LOG.isDebugEnabled()) + LOG.debug("isReady? false"); + _channelState.onReadUnready(); + return false; + } + + @Override + public void setReadListener(ReadListener readListener) + { + if (_readListener != null) + throw new IllegalStateException("ReadListener already set"); + _readListener = Objects.requireNonNull(readListener); + + if (LOG.isDebugEnabled()) + LOG.debug("setReadListener error=" + _error + " eof=" + _eof + " " + _contentProducer); + boolean woken; + if (isError()) + { + woken = _channelState.onReadReady(); + } + else + { + if (_contentProducer.hasTransformedContent()) + { + woken = _channelState.onReadReady(); + } + else if (_eof.isEof()) + { + woken = _channelState.onReadEof(); + } + else + { + _channelState.onReadUnready(); + woken = false; + } + } + + if (LOG.isDebugEnabled()) + LOG.debug("setReadListener woken=" + woken); + if (woken) + scheduleReadListenerNotification(); + } + + private void scheduleReadListenerNotification() + { + HttpChannel channel = _channelState.getHttpChannel(); + channel.execute(channel); + } + + @Override + public int read() throws IOException + { + int read = read(_oneByteBuffer, 0, 1); + if (read == 0) + throw new IOException("unready read=0"); + return read < 0 ? -1 : _oneByteBuffer[0] & 0xFF; + } + + @Override + public int read(byte[] b, int off, int len) throws IOException + { + while (true) + { + int read = _contentProducer.read(b, off, len); + if (LOG.isDebugEnabled()) + LOG.debug("read produced {} byte(s)", read); + if (read > 0) + return read; + + if (LOG.isDebugEnabled()) + LOG.debug("read error = " + _error); + if (_error != null) + throw new IOException(_error); + + if (LOG.isDebugEnabled()) + LOG.debug("read EOF = {}", _eof); + if (_eof.isEarly()) + throw new EofException("Early EOF"); + + if (LOG.isDebugEnabled()) + LOG.debug("read async = {}", isAsync()); + if (!isAsync()) + { + if (_eof.isEof()) + { + _eof = Eof.CONSUMED_EOF; + if (LOG.isDebugEnabled()) + LOG.debug("read on EOF, switching to CONSUMED_EOF and returning"); + return -1; + } + if (LOG.isDebugEnabled()) + LOG.debug("read blocked"); + blockForContent(); + if (LOG.isDebugEnabled()) + LOG.debug("read unblocked"); + } + else + { + if (_eof.isEof()) + { + boolean wasInAsyncWait = _channelState.onReadEof(); + if (wasInAsyncWait) + scheduleReadListenerNotification(); + if (LOG.isDebugEnabled()) + LOG.debug("async read on EOF (was in async wait? {}), switching to CONSUMED_EOF and returning", wasInAsyncWait); + _eof = Eof.CONSUMED_EOF; + return -1; + } + else + { + //TODO returning 0 breaks the InputStream contract. Shouldn't IOException be thrown instead? + _channelState.getHttpChannel().onAsyncWaitForContent(); // switches on fill interested + return 0; + } + } + } + } + + @Override + public int available() + { + int available = _contentProducer.available(); + if (LOG.isDebugEnabled()) + LOG.debug("available = {}", available); + return available; + } + + private void blockForContent() + { + try + { + _semaphore.acquire(() -> + { + if (LOG.isDebugEnabled()) + LOG.debug("waiting for signal to wake up"); + _channelState.getHttpChannel().onBlockWaitForContent(); // switches on fill interested if it blocks + }); + } + catch (Throwable x) + { + _channelState.getHttpChannel().onBlockWaitForContentFailure(x); + } + } + + /* Runnable */ + + /* + *

While this class is-a Runnable, it should never be dispatched in it's own thread. It is a runnable only so that the calling thread can use {@link + * ContextHandler#handle(Runnable)} to setup classloaders etc.

+ */ + @Override + public void run() + { + if (LOG.isDebugEnabled()) + LOG.debug("running"); + if (!_contentProducer.hasRawContent()) + { + if (_error != null || _eof.isEarly()) + { + // TODO is this necessary to add here? + _channelState.getHttpChannel().getResponse().getHttpFields().add(HttpConnection.CONNECTION_CLOSE); + if (_error != null) + _readListener.onError(_error); + else + _readListener.onError(new EofException("Early EOF")); + } + else if (_eof.isEof()) + { + try + { + _readListener.onAllDataRead(); + } + catch (Throwable x) + { + _readListener.onError(x); + } + } + // else: !hasContent() && !error && !EOF -> no-op + } + else + { + try + { + _readListener.onDataAvailable(); + } + catch (Throwable x) + { + _readListener.onError(x); + } + } + } + + /** + * An {@link Interceptor} that chains two other {@link Interceptor}s together. + * The {@link Interceptor#readFrom(Content)} calls the previous {@link Interceptor}'s + * {@link Interceptor#readFrom(Content)} and then passes any {@link Content} returned + * to the next {@link Interceptor}. + */ + static class ChainedInterceptor implements Interceptor, Destroyable + { + private final Interceptor _prev; + private final Interceptor _next; + + public ChainedInterceptor(Interceptor prev, Interceptor next) + { + _prev = prev; + _next = next; + } + + public Interceptor getPrev() + { + return _prev; + } + + public Interceptor getNext() + { + return _next; + } + + @Override + public Content readFrom(Content content) + { + return getNext().readFrom(getPrev().readFrom(content)); + } + + @Override + public void destroy() + { + if (_prev instanceof Destroyable) + ((Destroyable)_prev).destroy(); + if (_next instanceof Destroyable) + ((Destroyable)_next).destroy(); + } + } + + enum Eof + { + NOT_YET(false, false, false), + EOF(true, false, false), + CONSUMED_EOF(true, true, false), + EARLY_EOF(true, false, true), + ; + + private final boolean _eof; + private final boolean _consumed; + private final boolean _early; + + Eof(boolean eof, boolean consumed, boolean early) + { + _eof = eof; + _consumed = consumed; + _early = early; + } + + public boolean isEof() + { + return _eof; + } + + public boolean isConsumed() + { + return _consumed; + } + + public boolean isEarly() + { + return _early; + } + } + + private static class ContentProducer + { + private final Runnable _rawContentProducer; + // TODO: think about thread visibility of the below variables + // Note: _rawContent can never be null for as long as _transformedContent is not null. + private HttpInput.Content _rawContent; + private HttpInput.Content _transformedContent; + private long _rawContentArrived; + private HttpInput.Interceptor _interceptor; + + public ContentProducer(Runnable rawContentProducer) + { + _rawContentProducer = rawContentProducer; + } + + void recycle() + { + if (LOG.isDebugEnabled()) + LOG.debug("recycle {}", this); + if (_transformedContent == _rawContent) + _transformedContent = null; + if (_transformedContent != null && !_transformedContent.isEmpty()) + _transformedContent.failed(null); + _transformedContent = null; + if (_rawContent != null && !_rawContent.isEmpty()) + _rawContent.failed(null); + _rawContent = null; + _rawContentArrived = 0L; + if (_interceptor instanceof Destroyable) + ((Destroyable)_interceptor).destroy(); + _interceptor = null; + } + + int available() + { + if (_rawContent == null) + produceRawContent(); + return _rawContent == null ? 0 : _rawContent.remaining(); + } + + long getRawContentArrived() + { + return _rawContentArrived; + } + + boolean hasRawContent() + { + return _rawContent != null; + } + + boolean hasTransformedContent() + { + if (_transformedContent != null) + return true; + if (_rawContent == null) + produceRawContent(); + produceTransformedContent(); + return _transformedContent != null; + } + + HttpInput.Interceptor getInterceptor() + { + return _interceptor; + } + + void setInterceptor(HttpInput.Interceptor interceptor) + { + this._interceptor = interceptor; + } + + void addInterceptor(HttpInput.Interceptor interceptor) + { + if (_interceptor == null) + _interceptor = interceptor; + else + _interceptor = new HttpInputOverHTTP.ChainedInterceptor(_interceptor, interceptor); + } + + long addContent(HttpInput.Content content) + { + if (LOG.isDebugEnabled()) + LOG.debug("{} addContent {}", this, content); + if (content == null) + throw new AssertionError("Cannot add null content"); + if (_rawContent != null) + throw new AssertionError("Cannot add new content while current one hasn't been processed"); + + _rawContent = content; + _rawContentArrived += content.remaining(); + + return _rawContentArrived; + } + + void consumeAll() + { + if (LOG.isDebugEnabled()) + LOG.debug("{} consumeAll", this); + // start by depleting the current _transformedContent + if (_transformedContent != null) + { + _transformedContent.skip(_transformedContent.remaining()); + if (_transformedContent != _rawContent) + _transformedContent.succeeded(); + _transformedContent = null; + } + + // don't bother transforming content, directly deplete the raw one + while (true) + { + if (_rawContent == null) + produceRawContent(); + if (_rawContent == null) + break; + + _rawContent.skip(_rawContent.remaining()); + _rawContent.succeeded(); + _rawContent = null; + } + } + + int read(byte[] b, int off, int len) + { + if (LOG.isDebugEnabled()) + LOG.debug("{} read", this); + while (_transformedContent == null) + { + if (_rawContent == null) + { + produceRawContent(); + if (_rawContent == null) + return 0; + } + produceTransformedContent(); + } + + int read = _transformedContent.get(b, off, len); + if (_transformedContent.isEmpty()) + produceTransformedContent(); + + return read; + } + + /** + * Call the parser so that it's going to continue parsing of the request buffer, filling it with the socket's buffer + * if needed until either the request buffer is empty with no bytes left in the socket's buffer or {@link #addContent(Content)} + * is called. + */ + void produceRawContent() + { + if (LOG.isDebugEnabled()) + LOG.debug("{} produceRawContent", this); + _rawContentProducer.run(); + } + + /** + * Read {@code _rawContent} and {@code _transformedContent} to produce the next non-empty content to work with and store it in {@code _transformedContent}, + * or store null in {@code _transformedContent} if there is no content to work with. + * Depleted content gets succeeded and its field nullified, which can happen for both {@code _rawContent} and {@code _transformedContent}. + */ + private void produceTransformedContent() + { + if (LOG.isDebugEnabled()) + LOG.debug("{} produceTransformedContent", this); + if (_interceptor == null) + { + // no interceptor set + if (_rawContent != null && _rawContent.isEmpty()) + { + _rawContent.succeeded(); + _rawContent = null; + _transformedContent = null; + } + else + { + _transformedContent = _rawContent; + } + } + else + { + // interceptor set + transformContent(); + if (_transformedContent == null) + { + if (_rawContent != null && _rawContent.isEmpty()) + { + _rawContent.succeeded(); + _rawContent = null; + } + else + { + _transformedContent = _rawContent; + } + } + } + } + + /** + * Read {@code _rawContent} and write {@code _transformedContent} to produce content using the interceptor. + * The produced content is guaranteed to either be null or not empty. + */ + private void transformContent() + { + if (LOG.isDebugEnabled()) + LOG.debug("{} transformContent", this); + if (_rawContent == null) + return; + + _transformedContent = _interceptor.readFrom(_rawContent); + + if (_transformedContent != null && _transformedContent.isEmpty()) + { + if (_transformedContent != _rawContent) + _transformedContent.succeeded(); + _transformedContent = null; + } + } + + @Override + public String toString() + { + return getClass().getSimpleName() + "[i=" + _interceptor + ",b=" + _rawContentArrived + + ",r=" + _rawContent + ",t=" + _transformedContent + "]"; + } + } + + private static class NotifyingSemaphore + { + private int permits; + + public synchronized void acquire(Runnable onBlocking) throws InterruptedException + { + if (permits == 0) + onBlocking.run(); + while (permits == 0) + wait(); + permits--; + } + + public synchronized void release() + { + permits++; + if (permits == 1) + notify(); + } } } diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpInputOverHTTP2.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpInputOverHTTP2.java new file mode 100644 index 00000000000..e97009217c1 --- /dev/null +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpInputOverHTTP2.java @@ -0,0 +1,180 @@ +// +// ======================================================================== +// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under +// the terms of the Eclipse Public License 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0 +// +// This Source Code may also be made available under the following +// Secondary Licenses when the conditions for such availability set +// forth in the Eclipse Public License, v. 2.0 are satisfied: +// the Apache License v2.0 which is available at +// https://www.apache.org/licenses/LICENSE-2.0 +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +package org.eclipse.jetty.server; + +import java.io.IOException; +import javax.servlet.ReadListener; + +public class HttpInputOverHTTP2 extends HttpInput +{ + private final byte[] _oneByteBuffer = new byte[1]; + private final HttpChannelState _channelState; + + public HttpInputOverHTTP2(HttpChannelState state) + { + _channelState = state; + } + + /* HttpInput */ + + @Override + public void recycle() + { + + } + + @Override + public Interceptor getInterceptor() + { + return null; + } + + @Override + public void setInterceptor(Interceptor interceptor) + { + + } + + @Override + public void addInterceptor(Interceptor interceptor) + { + + } + + @Override + public void asyncReadProduce() throws IOException + { + + } + + @Override + public boolean addContent(Content content) + { + return false; + } + + @Override + public boolean hasContent() + { + return false; + } + + @Override + public void unblock() + { + + } + + @Override + public long getContentLength() + { + return 0; + } + + @Override + public boolean earlyEOF() + { + return false; + } + + @Override + public boolean eof() + { + return false; + } + + @Override + public boolean consumeAll() + { + return false; + } + + @Override + public boolean isError() + { + return false; + } + + @Override + public boolean isAsync() + { + return false; + } + + @Override + public boolean onIdleTimeout(Throwable x) + { + return false; + } + + @Override + public boolean failed(Throwable x) + { + return false; + } + + /* ServletInputStream */ + + @Override + public boolean isFinished() + { + return false; + } + + @Override + public boolean isReady() + { + return false; + } + + @Override + public void setReadListener(ReadListener readListener) + { + + } + + @Override + public int read() throws IOException + { + int read = read(_oneByteBuffer, 0, 1); + if (read == 0) + throw new IllegalStateException("unready read=0"); + return read < 0 ? -1 : _oneByteBuffer[0] & 0xFF; + } + + @Override + public int read(byte[] b, int off, int len) throws IOException + { + return 0; + } + + @Override + public int available() throws IOException + { + return 0; + } + + /* Runnable */ + + @Override + public void run() + { + + } +} diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java b/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java index a431184250a..5e3908048fb 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java @@ -749,7 +749,7 @@ public class Request implements HttpServletRequest public long getContentRead() { - return _input.getContentConsumed(); + return _input.getContentLength(); } @Override diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/HttpInputAsyncStateTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/HttpInputAsyncStateTest.java deleted file mode 100644 index 4401d4daa58..00000000000 --- a/jetty-server/src/test/java/org/eclipse/jetty/server/HttpInputAsyncStateTest.java +++ /dev/null @@ -1,735 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. -// -// This program and the accompanying materials are made available under -// the terms of the Eclipse Public License 2.0 which is available at -// https://www.eclipse.org/legal/epl-2.0 -// -// This Source Code may also be made available under the following -// Secondary Licenses when the conditions for such availability set -// forth in the Eclipse Public License, v. 2.0 are satisfied: -// the Apache License v2.0 which is available at -// https://www.apache.org/licenses/LICENSE-2.0 -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// ======================================================================== -// - -package org.eclipse.jetty.server; - -import java.io.IOException; -import java.nio.ByteBuffer; -import java.util.Queue; -import java.util.concurrent.LinkedBlockingQueue; -import javax.servlet.ReadListener; - -import org.eclipse.jetty.server.HttpChannelState.Action; -import org.eclipse.jetty.server.HttpInput.Content; -import org.eclipse.jetty.util.BufferUtil; -import org.eclipse.jetty.util.thread.Scheduler; -import org.hamcrest.Matchers; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import static org.eclipse.jetty.server.HttpInput.EARLY_EOF_CONTENT; -import static org.eclipse.jetty.server.HttpInput.EOF_CONTENT; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.empty; -import static org.hamcrest.Matchers.equalTo; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.fail; - -/** - * this tests HttpInput and its interaction with HttpChannelState - */ - -public class HttpInputAsyncStateTest -{ - - private static final Queue __history = new LinkedBlockingQueue<>(); - private ByteBuffer _expected = BufferUtil.allocate(16 * 1024); - private boolean _eof; - private boolean _noReadInDataAvailable; - private boolean _completeInOnDataAvailable; - - private final ReadListener _listener = new ReadListener() - { - @Override - public void onError(Throwable t) - { - __history.add("onError:" + t); - } - - @Override - public void onDataAvailable() throws IOException - { - __history.add("onDataAvailable"); - if (!_noReadInDataAvailable && readAvailable() && _completeInOnDataAvailable) - { - __history.add("complete"); - _state.complete(); - } - } - - @Override - public void onAllDataRead() throws IOException - { - __history.add("onAllDataRead"); - } - }; - private HttpInput _in; - HttpChannelState _state; - - public static class TContent extends HttpInput.Content - { - public TContent(String content) - { - super(BufferUtil.toBuffer(content)); - } - } - - @BeforeEach - public void before() - { - _noReadInDataAvailable = false; - _in = new HttpInput(new HttpChannelState(new HttpChannel(new MockConnector(), new HttpConfiguration(), null, null) - { - @Override - public void onAsyncWaitForContent() - { - __history.add("onAsyncWaitForContent"); - } - - @Override - public Scheduler getScheduler() - { - return null; - } - }) - { - @Override - public void onReadUnready() - { - super.onReadUnready(); - __history.add("onReadUnready"); - } - - @Override - public boolean onContentAdded() - { - boolean wake = super.onContentAdded(); - __history.add("onReadPossible " + wake); - return wake; - } - - @Override - public boolean onReadReady() - { - boolean wake = super.onReadReady(); - __history.add("onReadReady " + wake); - return wake; - } - }) - { - @Override - public void wake() - { - __history.add("wake"); - } - }; - - _state = _in.getHttpChannelState(); - __history.clear(); - } - - private void check(String... history) - { - if (history == null || history.length == 0) - assertThat(__history, empty()); - else - assertThat(__history.toArray(new String[__history.size()]), Matchers.arrayContaining(history)); - __history.clear(); - } - - private void wake() - { - handle(null); - } - - private void handle() - { - handle(null); - } - - private void handle(Runnable run) - { - Action action = _state.handling(); - loop: - while (true) - { - switch (action) - { - case DISPATCH: - if (run == null) - fail("Run is null during DISPATCH"); - run.run(); - break; - - case READ_CALLBACK: - _in.run(); - break; - - case TERMINATED: - case WAIT: - break loop; - - case COMPLETE: - __history.add("COMPLETE"); - break; - - case READ_REGISTER: - _state.getHttpChannel().onAsyncWaitForContent(); - break; - - default: - fail("Bad Action: " + action); - } - action = _state.unhandle(); - } - } - - private void deliver(Content... content) - { - if (content != null) - { - for (Content c : content) - { - if (c == EOF_CONTENT) - { - _in.eof(); - _eof = true; - } - else if (c == HttpInput.EARLY_EOF_CONTENT) - { - _in.earlyEOF(); - _eof = true; - } - else - { - _in.addContent(c); - BufferUtil.append(_expected, c.getByteBuffer().slice()); - } - } - } - } - - boolean readAvailable() throws IOException - { - int len = 0; - try - { - while (_in.isReady()) - { - int b = _in.read(); - - if (b < 0) - { - if (len > 0) - __history.add("read " + len); - __history.add("read -1"); - assertTrue(BufferUtil.isEmpty(_expected)); - assertTrue(_eof); - return true; - } - else - { - len++; - assertFalse(BufferUtil.isEmpty(_expected)); - int a = 0xff & _expected.get(); - assertThat(b, equalTo(a)); - } - } - __history.add("read " + len); - assertTrue(BufferUtil.isEmpty(_expected)); - } - catch (IOException e) - { - if (len > 0) - __history.add("read " + len); - __history.add("read " + e); - throw e; - } - return false; - } - - @AfterEach - public void after() - { - assertThat(__history.poll(), Matchers.nullValue()); - } - - @Test - public void testInitialEmptyListenInHandle() throws Exception - { - deliver(EOF_CONTENT); - check(); - - handle(() -> - { - _state.startAsync(null); - _in.setReadListener(_listener); - check("onReadReady false"); - }); - - check("onAllDataRead"); - } - - @Test - public void testInitialEmptyListenAfterHandle() throws Exception - { - deliver(EOF_CONTENT); - - handle(() -> - { - _state.startAsync(null); - check(); - }); - - _in.setReadListener(_listener); - check("onReadReady true", "wake"); - wake(); - check("onAllDataRead"); - } - - @Test - public void testListenInHandleEmpty() throws Exception - { - handle(() -> - { - _state.startAsync(null); - _in.setReadListener(_listener); - check("onReadUnready"); - }); - - check("onAsyncWaitForContent"); - - deliver(EOF_CONTENT); - check("onReadPossible true"); - handle(); - check("onAllDataRead"); - } - - @Test - public void testEmptyListenAfterHandle() throws Exception - { - handle(() -> - { - _state.startAsync(null); - check(); - }); - - deliver(EOF_CONTENT); - check(); - - _in.setReadListener(_listener); - check("onReadReady true", "wake"); - wake(); - check("onAllDataRead"); - } - - @Test - public void testListenAfterHandleEmpty() throws Exception - { - handle(() -> - { - _state.startAsync(null); - check(); - }); - - _in.setReadListener(_listener); - check("onAsyncWaitForContent", "onReadUnready"); - - deliver(EOF_CONTENT); - check("onReadPossible true"); - - handle(); - check("onAllDataRead"); - } - - @Test - public void testInitialEarlyEOFListenInHandle() throws Exception - { - deliver(EARLY_EOF_CONTENT); - check(); - - handle(() -> - { - _state.startAsync(null); - _in.setReadListener(_listener); - check("onReadReady false"); - }); - - check("onError:org.eclipse.jetty.io.EofException: Early EOF"); - } - - @Test - public void testInitialEarlyEOFListenAfterHandle() throws Exception - { - deliver(EARLY_EOF_CONTENT); - - handle(() -> - { - _state.startAsync(null); - check(); - }); - - _in.setReadListener(_listener); - check("onReadReady true", "wake"); - wake(); - check("onError:org.eclipse.jetty.io.EofException: Early EOF"); - } - - @Test - public void testListenInHandleEarlyEOF() throws Exception - { - handle(() -> - { - _state.startAsync(null); - _in.setReadListener(_listener); - check("onReadUnready"); - }); - - check("onAsyncWaitForContent"); - - deliver(EARLY_EOF_CONTENT); - check("onReadPossible true"); - handle(); - check("onError:org.eclipse.jetty.io.EofException: Early EOF"); - } - - @Test - public void testEarlyEOFListenAfterHandle() throws Exception - { - handle(() -> - { - _state.startAsync(null); - check(); - }); - - deliver(EARLY_EOF_CONTENT); - check(); - - _in.setReadListener(_listener); - check("onReadReady true", "wake"); - wake(); - check("onError:org.eclipse.jetty.io.EofException: Early EOF"); - } - - @Test - public void testListenAfterHandleEarlyEOF() throws Exception - { - handle(() -> - { - _state.startAsync(null); - check(); - }); - - _in.setReadListener(_listener); - check("onAsyncWaitForContent", "onReadUnready"); - - deliver(EARLY_EOF_CONTENT); - check("onReadPossible true"); - - handle(); - check("onError:org.eclipse.jetty.io.EofException: Early EOF"); - } - - @Test - public void testInitialAllContentListenInHandle() throws Exception - { - deliver(new TContent("Hello"), EOF_CONTENT); - check(); - - handle(() -> - { - _state.startAsync(null); - _in.setReadListener(_listener); - check("onReadReady false"); - }); - - check("onDataAvailable", "read 5", "read -1", "onAllDataRead"); - } - - @Test - public void testInitialAllContentListenAfterHandle() throws Exception - { - deliver(new TContent("Hello"), EOF_CONTENT); - - handle(() -> - { - _state.startAsync(null); - check(); - }); - - _in.setReadListener(_listener); - check("onReadReady true", "wake"); - wake(); - check("onDataAvailable", "read 5", "read -1", "onAllDataRead"); - } - - @Test - public void testListenInHandleAllContent() throws Exception - { - handle(() -> - { - _state.startAsync(null); - _in.setReadListener(_listener); - check("onReadUnready"); - }); - - check("onAsyncWaitForContent"); - - deliver(new TContent("Hello"), EOF_CONTENT); - check("onReadPossible true", "onReadPossible false"); - handle(); - check("onDataAvailable", "read 5", "read -1", "onAllDataRead"); - } - - @Test - public void testAllContentListenAfterHandle() throws Exception - { - handle(() -> - { - _state.startAsync(null); - check(); - }); - - deliver(new TContent("Hello"), EOF_CONTENT); - check(); - - _in.setReadListener(_listener); - check("onReadReady true", "wake"); - wake(); - check("onDataAvailable", "read 5", "read -1", "onAllDataRead"); - } - - @Test - public void testListenAfterHandleAllContent() throws Exception - { - handle(() -> - { - _state.startAsync(null); - check(); - }); - - _in.setReadListener(_listener); - check("onAsyncWaitForContent", "onReadUnready"); - - deliver(new TContent("Hello"), EOF_CONTENT); - check("onReadPossible true", "onReadPossible false"); - - handle(); - check("onDataAvailable", "read 5", "read -1", "onAllDataRead"); - } - - @Test - public void testInitialIncompleteContentListenInHandle() throws Exception - { - deliver(new TContent("Hello"), EARLY_EOF_CONTENT); - check(); - - handle(() -> - { - _state.startAsync(null); - _in.setReadListener(_listener); - check("onReadReady false"); - }); - - check( - "onDataAvailable", - "read 5", - "read org.eclipse.jetty.io.EofException: Early EOF", - "onError:org.eclipse.jetty.io.EofException: Early EOF"); - } - - @Test - public void testInitialPartialContentListenAfterHandle() throws Exception - { - deliver(new TContent("Hello"), EARLY_EOF_CONTENT); - - handle(() -> - { - _state.startAsync(null); - check(); - }); - - _in.setReadListener(_listener); - check("onReadReady true", "wake"); - wake(); - check( - "onDataAvailable", - "read 5", - "read org.eclipse.jetty.io.EofException: Early EOF", - "onError:org.eclipse.jetty.io.EofException: Early EOF"); - } - - @Test - public void testListenInHandlePartialContent() throws Exception - { - handle(() -> - { - _state.startAsync(null); - _in.setReadListener(_listener); - check("onReadUnready"); - }); - - check("onAsyncWaitForContent"); - - deliver(new TContent("Hello"), EARLY_EOF_CONTENT); - check("onReadPossible true", "onReadPossible false"); - handle(); - check( - "onDataAvailable", - "read 5", - "read org.eclipse.jetty.io.EofException: Early EOF", - "onError:org.eclipse.jetty.io.EofException: Early EOF"); - } - - @Test - public void testPartialContentListenAfterHandle() throws Exception - { - handle(() -> - { - _state.startAsync(null); - check(); - }); - - deliver(new TContent("Hello"), EARLY_EOF_CONTENT); - check(); - - _in.setReadListener(_listener); - check("onReadReady true", "wake"); - wake(); - check( - "onDataAvailable", - "read 5", - "read org.eclipse.jetty.io.EofException: Early EOF", - "onError:org.eclipse.jetty.io.EofException: Early EOF"); - } - - @Test - public void testListenAfterHandlePartialContent() throws Exception - { - handle(() -> - { - _state.startAsync(null); - check(); - }); - - _in.setReadListener(_listener); - check("onAsyncWaitForContent", "onReadUnready"); - - deliver(new TContent("Hello"), EARLY_EOF_CONTENT); - check("onReadPossible true", "onReadPossible false"); - - handle(); - check( - "onDataAvailable", - "read 5", - "read org.eclipse.jetty.io.EofException: Early EOF", - "onError:org.eclipse.jetty.io.EofException: Early EOF"); - } - - @Test - public void testReadAfterOnDataAvailable() throws Exception - { - _noReadInDataAvailable = true; - handle(() -> - { - _state.startAsync(null); - _in.setReadListener(_listener); - check("onReadUnready"); - }); - - check("onAsyncWaitForContent"); - - deliver(new TContent("Hello"), EOF_CONTENT); - check("onReadPossible true", "onReadPossible false"); - - handle(); - check("onDataAvailable"); - - readAvailable(); - check("wake", "read 5", "read -1"); - wake(); - check("onAllDataRead"); - } - - @Test - public void testReadOnlyExpectedAfterOnDataAvailable() throws Exception - { - _noReadInDataAvailable = true; - handle(() -> - { - _state.startAsync(null); - _in.setReadListener(_listener); - check("onReadUnready"); - }); - - check("onAsyncWaitForContent"); - - deliver(new TContent("Hello"), EOF_CONTENT); - check("onReadPossible true", "onReadPossible false"); - - handle(); - check("onDataAvailable"); - - byte[] buffer = new byte[_expected.remaining()]; - assertThat(_in.read(buffer), equalTo(buffer.length)); - assertThat(new String(buffer), equalTo(BufferUtil.toString(_expected))); - BufferUtil.clear(_expected); - check(); - - assertTrue(_in.isReady()); - check(); - - assertThat(_in.read(), equalTo(-1)); - check("wake"); - - wake(); - check("onAllDataRead"); - } - - @Test - public void testReadAndCompleteInOnDataAvailable() throws Exception - { - _completeInOnDataAvailable = true; - handle(() -> - { - _state.startAsync(null); - _in.setReadListener(_listener); - check("onReadUnready"); - }); - - check("onAsyncWaitForContent"); - - deliver(new TContent("Hello"), EOF_CONTENT); - check("onReadPossible true", "onReadPossible false"); - - handle(() -> - { - __history.add(_state.getState().toString()); - }); - System.err.println(__history); - check( - "onDataAvailable", - "read 5", - "read -1", - "complete", - "COMPLETE" - ); - } -} diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/HttpInputTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/HttpInputTest.java deleted file mode 100644 index 833fb70352b..00000000000 --- a/jetty-server/src/test/java/org/eclipse/jetty/server/HttpInputTest.java +++ /dev/null @@ -1,614 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. -// -// This program and the accompanying materials are made available under -// the terms of the Eclipse Public License 2.0 which is available at -// https://www.eclipse.org/legal/epl-2.0 -// -// This Source Code may also be made available under the following -// Secondary Licenses when the conditions for such availability set -// forth in the Eclipse Public License, v. 2.0 are satisfied: -// the Apache License v2.0 which is available at -// https://www.apache.org/licenses/LICENSE-2.0 -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// ======================================================================== -// - -package org.eclipse.jetty.server; - -import java.io.EOFException; -import java.io.IOException; -import java.util.Queue; -import java.util.concurrent.LinkedBlockingQueue; -import java.util.concurrent.TimeoutException; -import javax.servlet.ReadListener; - -import org.eclipse.jetty.util.BufferUtil; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.equalTo; -import static org.hamcrest.Matchers.instanceOf; -import static org.hamcrest.Matchers.nullValue; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; - -public class HttpInputTest -{ - private final Queue _history = new LinkedBlockingQueue<>(); - private final Queue _fillAndParseSimulate = new LinkedBlockingQueue<>(); - private final ReadListener _listener = new ReadListener() - { - @Override - public void onError(Throwable t) - { - _history.add("l.onError:" + t); - } - - @Override - public void onDataAvailable() throws IOException - { - _history.add("l.onDataAvailable"); - } - - @Override - public void onAllDataRead() throws IOException - { - _history.add("l.onAllDataRead"); - } - }; - private HttpInput _in; - - public class TContent extends HttpInput.Content - { - private final String _content; - - public TContent(String content) - { - super(BufferUtil.toBuffer(content)); - _content = content; - } - - @Override - public void succeeded() - { - _history.add("Content succeeded " + _content); - super.succeeded(); - } - - @Override - public void failed(Throwable x) - { - _history.add("Content failed " + _content); - super.failed(x); - } - } - - public class TestHttpInput extends HttpInput - { - public TestHttpInput(HttpChannelState state) - { - super(state); - } - - @Override - protected void produceContent() throws IOException - { - _history.add("produceContent " + _fillAndParseSimulate.size()); - - for (String s = _fillAndParseSimulate.poll(); s != null; s = _fillAndParseSimulate.poll()) - { - if ("_EOF_".equals(s)) - _in.eof(); - else - _in.addContent(new TContent(s)); - } - } - - @Override - protected void blockForContent() throws IOException - { - _history.add("blockForContent"); - super.blockForContent(); - } - } - - public class TestHttpChannelState extends HttpChannelState - { - private boolean _fakeAsyncState; - - public TestHttpChannelState(HttpChannel channel) - { - super(channel); - } - - public boolean isFakeAsyncState() - { - return _fakeAsyncState; - } - - public void setFakeAsyncState(boolean fakeAsyncState) - { - _fakeAsyncState = fakeAsyncState; - } - - @Override - public boolean isAsyncStarted() - { - if (isFakeAsyncState()) - return true; - return super.isAsyncStarted(); - } - - @Override - public void onReadUnready() - { - _history.add("s.onReadUnready"); - super.onReadUnready(); - } - - @Override - public boolean onReadPossible() - { - _history.add("s.onReadPossible"); - return super.onReadPossible(); - } - - @Override - public boolean onContentAdded() - { - _history.add("s.onDataAvailable"); - return super.onContentAdded(); - } - - @Override - public boolean onReadReady() - { - _history.add("s.onReadReady"); - return super.onReadReady(); - } - } - - @BeforeEach - public void before() - { - _in = new TestHttpInput(new TestHttpChannelState(new HttpChannel(new MockConnector(), new HttpConfiguration(), null, null) - { - @Override - public void onAsyncWaitForContent() - { - _history.add("asyncReadInterested"); - } - }) - ); - } - - @AfterEach - public void after() - { - assertThat(_history.poll(), nullValue()); - } - - @Test - public void testEmpty() throws Exception - { - assertThat(_in.available(), equalTo(0)); - assertThat(_history.poll(), equalTo("produceContent 0")); - assertThat(_history.poll(), nullValue()); - - assertThat(_in.isFinished(), equalTo(false)); - assertThat(_in.isReady(), equalTo(true)); - assertThat(_history.poll(), nullValue()); - } - - @Test - public void testRead() throws Exception - { - _in.addContent(new TContent("AB")); - _in.addContent(new TContent("CD")); - _fillAndParseSimulate.offer("EF"); - _fillAndParseSimulate.offer("GH"); - assertThat(_in.available(), equalTo(2)); - assertThat(_in.isFinished(), equalTo(false)); - assertThat(_in.isReady(), equalTo(true)); - - assertThat(_in.getContentConsumed(), equalTo(0L)); - assertThat(_in.read(), equalTo((int)'A')); - assertThat(_in.getContentConsumed(), equalTo(1L)); - assertThat(_in.read(), equalTo((int)'B')); - assertThat(_in.getContentConsumed(), equalTo(2L)); - - assertThat(_history.poll(), equalTo("Content succeeded AB")); - assertThat(_history.poll(), nullValue()); - - assertThat(_in.read(), equalTo((int)'C')); - assertThat(_in.read(), equalTo((int)'D')); - - assertThat(_history.poll(), equalTo("Content succeeded CD")); - assertThat(_history.poll(), nullValue()); - - assertThat(_in.read(), equalTo((int)'E')); - assertThat(_in.read(), equalTo((int)'F')); - - assertThat(_history.poll(), equalTo("produceContent 2")); - assertThat(_history.poll(), equalTo("Content succeeded EF")); - assertThat(_history.poll(), nullValue()); - - assertThat(_in.read(), equalTo((int)'G')); - assertThat(_in.read(), equalTo((int)'H')); - - assertThat(_history.poll(), equalTo("Content succeeded GH")); - assertThat(_history.poll(), nullValue()); - - assertThat(_in.getContentConsumed(), equalTo(8L)); - - assertThat(_history.poll(), nullValue()); - } - - @Test - public void testBlockingRead() throws Exception - { - new Thread(() -> - { - try - { - Thread.sleep(500); - _in.addContent(new TContent("AB")); - } - catch (Throwable th) - { - th.printStackTrace(); - } - }).start(); - - assertThat(_in.read(), equalTo((int)'A')); - - assertThat(_history.poll(), equalTo("produceContent 0")); - assertThat(_history.poll(), equalTo("blockForContent")); - assertThat(_history.poll(), nullValue()); - - assertThat(_in.read(), equalTo((int)'B')); - - assertThat(_history.poll(), equalTo("Content succeeded AB")); - assertThat(_history.poll(), nullValue()); - } - - @Test - public void testReadEOF() throws Exception - { - _in.addContent(new TContent("AB")); - _in.addContent(new TContent("CD")); - _in.eof(); - - assertThat(_in.isFinished(), equalTo(false)); - assertThat(_in.available(), equalTo(2)); - assertThat(_in.isFinished(), equalTo(false)); - - assertThat(_in.read(), equalTo((int)'A')); - assertThat(_in.read(), equalTo((int)'B')); - assertThat(_history.poll(), equalTo("Content succeeded AB")); - assertThat(_history.poll(), nullValue()); - - assertThat(_in.read(), equalTo((int)'C')); - assertThat(_in.isFinished(), equalTo(false)); - assertThat(_in.read(), equalTo((int)'D')); - assertThat(_history.poll(), equalTo("Content succeeded CD")); - assertThat(_history.poll(), nullValue()); - assertThat(_in.isFinished(), equalTo(false)); - - assertThat(_in.read(), equalTo(-1)); - assertThat(_in.isFinished(), equalTo(true)); - - assertThat(_history.poll(), nullValue()); - } - - @Test - public void testReadEarlyEOF() throws Exception - { - _in.addContent(new TContent("AB")); - _in.addContent(new TContent("CD")); - _in.earlyEOF(); - - assertThat(_in.isFinished(), equalTo(false)); - assertThat(_in.available(), equalTo(2)); - assertThat(_in.isFinished(), equalTo(false)); - - assertThat(_in.read(), equalTo((int)'A')); - assertThat(_in.read(), equalTo((int)'B')); - - assertThat(_in.read(), equalTo((int)'C')); - assertThat(_in.isFinished(), equalTo(false)); - assertThat(_in.read(), equalTo((int)'D')); - - assertThrows(EOFException.class, () -> _in.read()); - assertTrue(_in.isFinished()); - - assertThat(_history.poll(), equalTo("Content succeeded AB")); - assertThat(_history.poll(), equalTo("Content succeeded CD")); - assertThat(_history.poll(), nullValue()); - } - - @Test - public void testBlockingEOF() throws Exception - { - new Thread(() -> - { - try - { - Thread.sleep(500); - _in.eof(); - } - catch (Throwable th) - { - th.printStackTrace(); - } - }).start(); - - assertThat(_in.isFinished(), equalTo(false)); - assertThat(_in.read(), equalTo(-1)); - assertThat(_in.isFinished(), equalTo(true)); - - assertThat(_history.poll(), equalTo("produceContent 0")); - assertThat(_history.poll(), equalTo("blockForContent")); - assertThat(_history.poll(), nullValue()); - } - - @Test - public void testAsyncEmpty() throws Exception - { - ((TestHttpChannelState)_in.getHttpChannelState()).setFakeAsyncState(true); - _in.setReadListener(_listener); - ((TestHttpChannelState)_in.getHttpChannelState()).setFakeAsyncState(false); - assertThat(_history.poll(), equalTo("produceContent 0")); - assertThat(_history.poll(), equalTo("s.onReadUnready")); - assertThat(_history.poll(), nullValue()); - - assertThat(_in.isReady(), equalTo(false)); - assertThat(_history.poll(), nullValue()); - - assertThat(_in.isReady(), equalTo(false)); - assertThat(_history.poll(), nullValue()); - } - - @Test - public void testAsyncRead() throws Exception - { - ((TestHttpChannelState)_in.getHttpChannelState()).setFakeAsyncState(true); - _in.setReadListener(_listener); - ((TestHttpChannelState)_in.getHttpChannelState()).setFakeAsyncState(false); - - assertThat(_history.poll(), equalTo("produceContent 0")); - assertThat(_history.poll(), equalTo("s.onReadUnready")); - assertThat(_history.poll(), nullValue()); - - assertThat(_in.isReady(), equalTo(false)); - assertThat(_history.poll(), nullValue()); - - _in.addContent(new TContent("AB")); - _fillAndParseSimulate.add("CD"); - - assertThat(_history.poll(), equalTo("s.onDataAvailable")); - assertThat(_history.poll(), nullValue()); - _in.run(); - assertThat(_history.poll(), equalTo("l.onDataAvailable")); - assertThat(_history.poll(), nullValue()); - - assertThat(_in.isReady(), equalTo(true)); - assertThat(_in.read(), equalTo((int)'A')); - - assertThat(_in.isReady(), equalTo(true)); - assertThat(_in.read(), equalTo((int)'B')); - - assertThat(_history.poll(), equalTo("Content succeeded AB")); - assertThat(_history.poll(), nullValue()); - - assertThat(_in.isReady(), equalTo(true)); - assertThat(_history.poll(), equalTo("produceContent 1")); - assertThat(_history.poll(), equalTo("s.onDataAvailable")); - assertThat(_history.poll(), nullValue()); - - assertThat(_in.read(), equalTo((int)'C')); - - assertThat(_in.isReady(), equalTo(true)); - assertThat(_in.read(), equalTo((int)'D')); - assertThat(_history.poll(), equalTo("Content succeeded CD")); - assertThat(_history.poll(), nullValue()); - - assertThat(_in.isReady(), equalTo(false)); - assertThat(_history.poll(), equalTo("produceContent 0")); - assertThat(_history.poll(), equalTo("s.onReadUnready")); - assertThat(_history.poll(), nullValue()); - } - - @Test - public void testAsyncEOF() throws Exception - { - ((TestHttpChannelState)_in.getHttpChannelState()).setFakeAsyncState(true); - _in.setReadListener(_listener); - ((TestHttpChannelState)_in.getHttpChannelState()).setFakeAsyncState(false); - assertThat(_history.poll(), equalTo("produceContent 0")); - assertThat(_history.poll(), equalTo("s.onReadUnready")); - assertThat(_history.poll(), nullValue()); - - _in.eof(); - assertThat(_in.isReady(), equalTo(true)); - assertThat(_in.isFinished(), equalTo(false)); - assertThat(_history.poll(), equalTo("s.onDataAvailable")); - assertThat(_history.poll(), nullValue()); - - assertThat(_in.read(), equalTo(-1)); - assertThat(_in.isFinished(), equalTo(true)); - assertThat(_history.poll(), nullValue()); - } - - @Test - public void testAsyncReadEOF() throws Exception - { - ((TestHttpChannelState)_in.getHttpChannelState()).setFakeAsyncState(true); - _in.setReadListener(_listener); - ((TestHttpChannelState)_in.getHttpChannelState()).setFakeAsyncState(false); - assertThat(_history.poll(), equalTo("produceContent 0")); - assertThat(_history.poll(), equalTo("s.onReadUnready")); - assertThat(_history.poll(), nullValue()); - - assertThat(_in.isReady(), equalTo(false)); - assertThat(_history.poll(), nullValue()); - - _in.addContent(new TContent("AB")); - _fillAndParseSimulate.add("_EOF_"); - - assertThat(_history.poll(), equalTo("s.onDataAvailable")); - assertThat(_history.poll(), nullValue()); - - _in.run(); - assertThat(_history.poll(), equalTo("l.onDataAvailable")); - assertThat(_history.poll(), nullValue()); - - assertThat(_in.isReady(), equalTo(true)); - assertThat(_in.read(), equalTo((int)'A')); - - assertThat(_in.isReady(), equalTo(true)); - assertThat(_in.read(), equalTo((int)'B')); - - assertThat(_history.poll(), equalTo("Content succeeded AB")); - assertThat(_history.poll(), nullValue()); - - assertThat(_in.isFinished(), equalTo(false)); - assertThat(_in.isReady(), equalTo(true)); - assertThat(_history.poll(), equalTo("produceContent 1")); - assertThat(_history.poll(), equalTo("s.onDataAvailable")); - assertThat(_history.poll(), nullValue()); - - assertThat(_in.isFinished(), equalTo(false)); - assertThat(_in.read(), equalTo(-1)); - assertThat(_in.isFinished(), equalTo(true)); - assertThat(_history.poll(), nullValue()); - - assertThat(_in.isReady(), equalTo(true)); - assertThat(_history.poll(), nullValue()); - } - - @Test - public void testAsyncError() throws Exception - { - ((TestHttpChannelState)_in.getHttpChannelState()).setFakeAsyncState(true); - _in.setReadListener(_listener); - ((TestHttpChannelState)_in.getHttpChannelState()).setFakeAsyncState(false); - assertThat(_history.poll(), equalTo("produceContent 0")); - assertThat(_history.poll(), equalTo("s.onReadUnready")); - assertThat(_history.poll(), nullValue()); - - assertThat(_in.isReady(), equalTo(false)); - assertThat(_history.poll(), nullValue()); - - _in.failed(new TimeoutException()); - assertThat(_history.poll(), equalTo("s.onDataAvailable")); - assertThat(_history.poll(), nullValue()); - - _in.run(); - assertThat(_in.isFinished(), equalTo(true)); - assertThat(_history.poll(), equalTo("l.onError:java.util.concurrent.TimeoutException")); - assertThat(_history.poll(), nullValue()); - - assertThat(_in.isReady(), equalTo(true)); - - IOException e = assertThrows(IOException.class, () -> _in.read()); - assertThat(e.getCause(), instanceOf(TimeoutException.class)); - assertThat(_in.isFinished(), equalTo(true)); - - assertThat(_history.poll(), nullValue()); - } - - @Test - public void testSetListenerWithNull() throws Exception - { - //test can't be null - assertThrows(NullPointerException.class, () -> - { - _in.setReadListener(null); - }); - } - - @Test - public void testSetListenerNotAsync() throws Exception - { - //test not async - assertThrows(IllegalStateException.class, () -> - { - _in.setReadListener(_listener); - }); - } - - @Test - public void testSetListenerAlreadySet() throws Exception - { - //set up a listener - ((TestHttpChannelState)_in.getHttpChannelState()).setFakeAsyncState(true); - _in.setReadListener(_listener); - //throw away any events generated by setting the listener - _history.clear(); - ((TestHttpChannelState)_in.getHttpChannelState()).setFakeAsyncState(false); - //now test that you can't set another listener - assertThrows(IllegalStateException.class, () -> - { - _in.setReadListener(_listener); - }); - } - - @Test - public void testRecycle() throws Exception - { - testAsyncRead(); - _in.recycle(); - testAsyncRead(); - _in.recycle(); - testReadEOF(); - } - - @Test - public void testConsumeAll() throws Exception - { - _in.addContent(new TContent("AB")); - _in.addContent(new TContent("CD")); - _fillAndParseSimulate.offer("EF"); - _fillAndParseSimulate.offer("GH"); - assertThat(_in.read(), equalTo((int)'A')); - - assertFalse(_in.consumeAll()); - assertThat(_in.getContentConsumed(), equalTo(8L)); - - assertThat(_history.poll(), equalTo("Content succeeded AB")); - assertThat(_history.poll(), equalTo("Content succeeded CD")); - assertThat(_history.poll(), equalTo("produceContent 2")); - assertThat(_history.poll(), equalTo("Content succeeded EF")); - assertThat(_history.poll(), equalTo("Content succeeded GH")); - assertThat(_history.poll(), equalTo("produceContent 0")); - assertThat(_history.poll(), nullValue()); - } - - @Test - public void testConsumeAllEOF() throws Exception - { - _in.addContent(new TContent("AB")); - _in.addContent(new TContent("CD")); - _fillAndParseSimulate.offer("EF"); - _fillAndParseSimulate.offer("GH"); - _fillAndParseSimulate.offer("_EOF_"); - assertThat(_in.read(), equalTo((int)'A')); - - assertTrue(_in.consumeAll()); - assertThat(_in.getContentConsumed(), equalTo(8L)); - - assertThat(_history.poll(), equalTo("Content succeeded AB")); - assertThat(_history.poll(), equalTo("Content succeeded CD")); - assertThat(_history.poll(), equalTo("produceContent 3")); - assertThat(_history.poll(), equalTo("Content succeeded EF")); - assertThat(_history.poll(), equalTo("Content succeeded GH")); - assertThat(_history.poll(), nullValue()); - } -} From 946f334810cf58cdca34513d69c059e1119188e3 Mon Sep 17 00:00:00 2001 From: Ludovic Orban Date: Tue, 11 Feb 2020 17:02:55 +0100 Subject: [PATCH 15/25] http2 impl (Milestone 2) Signed-off-by: Ludovic Orban --- .../org/eclipse/jetty/http2/HTTP2Stream.java | 24 + .../jetty/http2/HTTP2StreamEndPoint.java | 8 +- .../java/org/eclipse/jetty/http2/IStream.java | 2 + .../org/eclipse/jetty/http2/api/Stream.java | 5 +- .../server/HTTP2ServerConnectionFactory.java | 2 +- .../http2/server/HttpChannelOverHTTP2.java | 19 +- .../http2/server/HttpInputOverHTTP2.java | 70 ++ .../SpnegoAuthenticatorTest.java | 13 + .../jetty/server/AbstractHttpInput.java | 769 ++++++++++++++++++ .../jetty/server/AbstractLockedHttpInput.java | 236 ++++++ .../org/eclipse/jetty/server/HttpChannel.java | 10 +- .../jetty/server/HttpInputOverFCGI.java | 68 +- .../jetty/server/HttpInputOverHTTP.java | 707 +--------------- .../jetty/server/HttpInputOverHTTP2.java | 180 ---- .../eclipse/jetty/server/HttpWriterTest.java | 6 + .../eclipse/jetty/server/ResponseTest.java | 9 +- 16 files changed, 1230 insertions(+), 898 deletions(-) create mode 100644 jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HttpInputOverHTTP2.java create mode 100644 jetty-server/src/main/java/org/eclipse/jetty/server/AbstractHttpInput.java create mode 100644 jetty-server/src/main/java/org/eclipse/jetty/server/AbstractLockedHttpInput.java delete mode 100644 jetty-server/src/main/java/org/eclipse/jetty/server/HttpInputOverHTTP2.java diff --git a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Stream.java b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Stream.java index 973c957d4c5..aa42a5fce8b 100644 --- a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Stream.java +++ b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Stream.java @@ -235,6 +235,22 @@ public class HTTP2Stream extends IdleTimeout implements IStream, Callback, Dumpa return state == CloseState.REMOTELY_CLOSED || state == CloseState.CLOSING || state == CloseState.CLOSED; } + @Override + public void fail(Throwable x) + { + try (AutoLock l = lock.lock()) + { + dataDemand = Long.MIN_VALUE; + while (true) + { + DataEntry dataEntry = dataQueue.poll(); + if (dataEntry == null) + break; + dataEntry.callback.failed(x); + } + } + } + public boolean isLocallyClosed() { return closeState.get() == CloseState.LOCALLY_CLOSED; @@ -400,6 +416,12 @@ public class HTTP2Stream extends IdleTimeout implements IStream, Callback, Dumpa DataEntry entry = new DataEntry(frame, callback); try (AutoLock l = lock.lock()) { + if (dataDemand == Long.MIN_VALUE) + { + // stream has been failed + callback.failed(null); + return; + } dataQueue.offer(entry); initial = dataInitial; if (initial) @@ -439,6 +461,8 @@ public class HTTP2Stream extends IdleTimeout implements IStream, Callback, Dumpa boolean proceed = false; try (AutoLock l = lock.lock()) { + if (dataDemand == Long.MIN_VALUE) + return; // stream has been failed demand = dataDemand = MathUtils.cappedAdd(dataDemand, n); if (!dataProcess) dataProcess = proceed = !dataQueue.isEmpty(); diff --git a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2StreamEndPoint.java b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2StreamEndPoint.java index 0251f720672..08e3fd78317 100644 --- a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2StreamEndPoint.java +++ b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2StreamEndPoint.java @@ -43,6 +43,7 @@ import org.slf4j.LoggerFactory; public abstract class HTTP2StreamEndPoint implements EndPoint { private static final Logger LOG = LoggerFactory.getLogger(HTTP2StreamEndPoint.class); + private static final Throwable EOF = new Throwable(); private final AutoLock lock = new AutoLock(); private final Deque dataQueue = new ArrayDeque<>(); @@ -530,7 +531,7 @@ public abstract class HTTP2StreamEndPoint implements EndPoint { if (buffer.hasRemaining()) offer(buffer, Callback.from(Callback.NOOP::succeeded, callback::failed), null); - offer(BufferUtil.EMPTY_BUFFER, callback, Entry.EOF); + offer(BufferUtil.EMPTY_BUFFER, callback, EOF); } else { @@ -581,10 +582,8 @@ public abstract class HTTP2StreamEndPoint implements EndPoint writeState); } - private static class Entry + private class Entry { - private static final Throwable EOF = new Throwable(); - private final ByteBuffer buffer; private final Callback callback; private final Throwable failure; @@ -611,6 +610,7 @@ public abstract class HTTP2StreamEndPoint implements EndPoint private void succeed() { callback.succeeded(); + stream.demand(1); } private void fail(Throwable failure) diff --git a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/IStream.java b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/IStream.java index 81a38ae9f7b..4c3b470204d 100644 --- a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/IStream.java +++ b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/IStream.java @@ -126,6 +126,8 @@ public interface IStream extends Stream, Attachable, Closeable */ boolean isResetOrFailed(); + void fail(Throwable x); + /** *

An ordered list of frames belonging to the same stream.

*/ diff --git a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/api/Stream.java b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/api/Stream.java index bf41f0fe2d2..ac72f5333ad 100644 --- a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/api/Stream.java +++ b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/api/Stream.java @@ -242,7 +242,10 @@ public interface Stream * @param callback the callback to complete when the bytes of the DATA frame have been consumed * @see #onDataDemanded(Stream, DataFrame, Callback) */ - public void onData(Stream stream, DataFrame frame, Callback callback); + public default void onData(Stream stream, DataFrame frame, Callback callback) + { + callback.succeeded(); + } /** *

Callback method invoked when a DATA frame has been demanded.

diff --git a/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HTTP2ServerConnectionFactory.java b/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HTTP2ServerConnectionFactory.java index d9c49d60399..6b9202803ab 100644 --- a/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HTTP2ServerConnectionFactory.java +++ b/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HTTP2ServerConnectionFactory.java @@ -157,7 +157,7 @@ public class HTTP2ServerConnectionFactory extends AbstractHTTP2ServerConnectionF } @Override - public void onData(Stream stream, DataFrame frame, Callback callback) + public void onDataDemanded(Stream stream, DataFrame frame, Callback callback) { getConnection().onData((IStream)stream, frame, callback); } diff --git a/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HttpChannelOverHTTP2.java b/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HttpChannelOverHTTP2.java index a54e6769c55..d6586c99358 100644 --- a/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HttpChannelOverHTTP2.java +++ b/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HttpChannelOverHTTP2.java @@ -41,6 +41,7 @@ import org.eclipse.jetty.io.EndPoint; import org.eclipse.jetty.io.WriteFlusher; import org.eclipse.jetty.server.Connector; import org.eclipse.jetty.server.HttpChannel; +import org.eclipse.jetty.server.HttpChannelState; import org.eclipse.jetty.server.HttpConfiguration; import org.eclipse.jetty.server.HttpInput; import org.eclipse.jetty.server.handler.ContextHandler; @@ -103,6 +104,12 @@ public class HttpChannelOverHTTP2 extends HttpChannel implements Closeable, Writ getResponse().getHttpOutput().onFlushed(bytes); } + @Override + protected HttpInput newHttpInput(HttpChannelState state) + { + return new HttpInputOverHTTP2(state); + } + public Runnable onRequest(HeadersFrame frame) { try @@ -131,9 +138,17 @@ public class HttpChannelOverHTTP2 extends HttpChannel implements Closeable, Writ _delayedUntilContent = getHttpConfiguration().isDelayDispatchUntilContent() && !endStream && !_expect100Continue && !connect; - // Delay the demand of DATA frames for CONNECT with :protocol. - if (!connect || request.getProtocol() == null) + // Delay the demand of DATA frames for CONNECT with :protocol + // or for normal requests expecting 100 continue. + if (!connect) + { + if (!_expect100Continue) + getStream().demand(1); + } + else if (request.getProtocol() == null) + { getStream().demand(1); + } if (LOG.isDebugEnabled()) { diff --git a/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HttpInputOverHTTP2.java b/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HttpInputOverHTTP2.java new file mode 100644 index 00000000000..1e15b6fb803 --- /dev/null +++ b/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HttpInputOverHTTP2.java @@ -0,0 +1,70 @@ +// +// ======================================================================== +// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under +// the terms of the Eclipse Public License 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0 +// +// This Source Code may also be made available under the following +// Secondary Licenses when the conditions for such availability set +// forth in the Eclipse Public License, v. 2.0 are satisfied: +// the Apache License v2.0 which is available at +// https://www.apache.org/licenses/LICENSE-2.0 +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +package org.eclipse.jetty.http2.server; + +import org.eclipse.jetty.server.AbstractLockedHttpInput; +import org.eclipse.jetty.server.HttpChannelState; +import org.eclipse.jetty.util.thread.AutoLock; + +public class HttpInputOverHTTP2 extends AbstractLockedHttpInput +{ + private boolean _producing; + + public HttpInputOverHTTP2(HttpChannelState state) + { + super(state); + } + + @Override + public void recycle() + { + try (AutoLock lock = _contentLock.lock()) + { + super.recycle(); + _producing = false; + } + } + + @Override + public boolean addContent(Content content) + { + try (AutoLock lock = _contentLock.lock()) + { + boolean b = super.addContent(content); + _producing = false; + return b; + } + } + + @Override + protected void produceRawContent() + { + if (!_producing) + { + _producing = true; + ((HttpChannelOverHTTP2)_channelState.getHttpChannel()).getStream().demand(1); + } + } + + @Override + protected void failRawContent(Throwable failure) + { + ((HttpChannelOverHTTP2)_channelState.getHttpChannel()).getStream().fail(failure); + } +} diff --git a/jetty-security/src/test/java/org/eclipse/jetty/security/authentication/SpnegoAuthenticatorTest.java b/jetty-security/src/test/java/org/eclipse/jetty/security/authentication/SpnegoAuthenticatorTest.java index 5740c2da290..c155f834e67 100644 --- a/jetty-security/src/test/java/org/eclipse/jetty/security/authentication/SpnegoAuthenticatorTest.java +++ b/jetty-security/src/test/java/org/eclipse/jetty/security/authentication/SpnegoAuthenticatorTest.java @@ -30,6 +30,7 @@ import org.eclipse.jetty.server.Authentication; import org.eclipse.jetty.server.HttpChannel; import org.eclipse.jetty.server.HttpChannelState; import org.eclipse.jetty.server.HttpConfiguration; +import org.eclipse.jetty.server.HttpInput; import org.eclipse.jetty.server.HttpOutput; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.Response; @@ -62,6 +63,12 @@ public class SpnegoAuthenticatorTest return null; } + @Override + protected HttpInput newHttpInput(HttpChannelState state) + { + return null; + } + @Override protected HttpOutput newHttpOutput() { @@ -97,6 +104,12 @@ public class SpnegoAuthenticatorTest return null; } + @Override + protected HttpInput newHttpInput(HttpChannelState state) + { + return null; + } + @Override protected HttpOutput newHttpOutput() { diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/AbstractHttpInput.java b/jetty-server/src/main/java/org/eclipse/jetty/server/AbstractHttpInput.java new file mode 100644 index 00000000000..39b3c33387a --- /dev/null +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/AbstractHttpInput.java @@ -0,0 +1,769 @@ +// +// ======================================================================== +// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under +// the terms of the Eclipse Public License 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0 +// +// This Source Code may also be made available under the following +// Secondary Licenses when the conditions for such availability set +// forth in the Eclipse Public License, v. 2.0 are satisfied: +// the Apache License v2.0 which is available at +// https://www.apache.org/licenses/LICENSE-2.0 +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +package org.eclipse.jetty.server; + +import java.io.IOException; +import java.util.Objects; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.Condition; +import javax.servlet.ReadListener; + +import org.eclipse.jetty.http.BadMessageException; +import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.io.EofException; +import org.eclipse.jetty.util.component.Destroyable; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; +import org.eclipse.jetty.util.thread.AutoLock; + +public abstract class AbstractHttpInput extends HttpInput +{ + private static final Logger LOG = Log.getLogger(AbstractHttpInput.class); + + private final byte[] _oneByteBuffer = new byte[1]; + + protected final HttpChannelState _channelState; + protected final ContentProducer _contentProducer; + protected final AutoLock _contentLock = new AutoLock(); + protected final Condition _contentLockCondition = _contentLock.newCondition(); + + private Eof _eof = Eof.NOT_YET; + private Throwable _error; + private ReadListener _readListener; + private long _firstByteTimeStamp = Long.MIN_VALUE; + + public AbstractHttpInput(HttpChannelState state) + { + _channelState = state; + _contentProducer = new ContentProducer(this::produceRawContent); + } + + /* HttpInput */ + + @Override + public void recycle() + { + if (LOG.isDebugEnabled()) + LOG.debug("recycle"); + _contentProducer.recycle(); + _eof = Eof.NOT_YET; + _error = null; + _readListener = null; + _firstByteTimeStamp = Long.MIN_VALUE; + } + + @Override + public Interceptor getInterceptor() + { + return _contentProducer.getInterceptor(); + } + + @Override + public void setInterceptor(Interceptor interceptor) + { + _contentProducer.setInterceptor(interceptor); + } + + @Override + public void addInterceptor(Interceptor interceptor) + { + _contentProducer.addInterceptor(interceptor); + } + + @Override + public void asyncReadProduce() + { + if (LOG.isDebugEnabled()) + LOG.debug("asyncReadProduce {}", _contentProducer); + _contentProducer.produceRawContent(); + } + + @Override + public boolean addContent(Content content) + { + if (LOG.isDebugEnabled()) + LOG.debug("addContent {} {}", content, _contentProducer); + if (_firstByteTimeStamp == Long.MIN_VALUE) + { + _firstByteTimeStamp = System.nanoTime(); + if (_firstByteTimeStamp == Long.MIN_VALUE) + _firstByteTimeStamp++; + } + _contentProducer.addContent(content); + if (isAsync()) + return _channelState.onContentAdded(); + unblock(); + return false; + } + + @Override + public boolean hasContent() + { + return _contentProducer.hasRawContent(); + } + + @Override + public void unblock() + { + try (AutoLock lock = _contentLock.lock()) + { + if (LOG.isDebugEnabled()) + LOG.debug("signalling blocked thread to wake up"); + _contentLockCondition.signal(); + } + } + + @Override + public long getContentLength() + { + return _contentProducer.getRawContentArrived(); + } + + @Override + public boolean earlyEOF() + { + if (LOG.isDebugEnabled()) + LOG.debug("received early EOF"); + _eof = Eof.EARLY_EOF; + if (isAsync()) + return _channelState.onContentAdded(); + unblock(); + return false; + } + + @Override + public boolean eof() + { + if (LOG.isDebugEnabled()) + LOG.debug("received EOF"); + _eof = Eof.EOF; + if (isAsync()) + return _channelState.onContentAdded(); + unblock(); + return false; + } + + @Override + public boolean consumeAll() + { + if (LOG.isDebugEnabled()) + LOG.debug("consume all"); + _contentProducer.consumeTransformedContent(() -> failRawContent(new IOException("Unconsumed content"))); + if (_eof.isEof()) + _eof = Eof.CONSUMED_EOF; + + if (isFinished()) + return !isError(); + + _eof = Eof.EARLY_EOF; + return false; + } + + @Override + public boolean isError() + { + return _error != null; + } + + @Override + public boolean isAsync() + { + return _readListener != null; + } + + @Override + public boolean onIdleTimeout(Throwable x) + { + boolean neverDispatched = _channelState.isIdle(); + boolean waitingForContent = _contentProducer.available() == 0 && !_eof.isEof(); + if ((waitingForContent || neverDispatched) && !isError()) + { + x.addSuppressed(new Throwable("HttpInput idle timeout")); + _error = x; + if (isAsync()) + return _channelState.onContentAdded(); + unblock(); + } + return false; + } + + @Override + public boolean failed(Throwable x) + { + if (LOG.isDebugEnabled()) + LOG.debug("failed " + x); + if (_error != null) + _error.addSuppressed(x); + else + _error = x; + + if (isAsync()) + return _channelState.onContentAdded(); + unblock(); + return false; + } + + /* ServletInputStream */ + + @Override + public boolean isFinished() + { + boolean finished = !_contentProducer.hasRawContent() && _eof.isConsumed(); + if (LOG.isDebugEnabled()) + LOG.debug("isFinished? {}", finished); + return finished; + } + + @Override + public boolean isReady() + { + // calling _contentProducer.available() might change the _eof state, so the following test order matters + if (_contentProducer.available() > 0 || _eof.isEof()) + { + if (LOG.isDebugEnabled()) + LOG.debug("isReady? true"); + return true; + } + if (LOG.isDebugEnabled()) + LOG.debug("isReady? false"); + _channelState.onReadUnready(); + return false; + } + + @Override + public void setReadListener(ReadListener readListener) + { + if (_readListener != null) + throw new IllegalStateException("ReadListener already set"); + _readListener = Objects.requireNonNull(readListener); + + if (LOG.isDebugEnabled()) + LOG.debug("setReadListener error=" + _error + " eof=" + _eof + " " + _contentProducer); + boolean woken; + if (isError()) + { + woken = _channelState.onReadReady(); + } + else + { + if (_contentProducer.available() > 0) + { + woken = _channelState.onReadReady(); + } + else if (_eof.isEof()) + { + woken = _channelState.onReadEof(); + } + else + { + _channelState.onReadUnready(); + woken = false; + } + } + + if (LOG.isDebugEnabled()) + LOG.debug("setReadListener woken=" + woken); + if (woken) + scheduleReadListenerNotification(); + } + + private void scheduleReadListenerNotification() + { + HttpChannel channel = _channelState.getHttpChannel(); + channel.execute(channel); + } + + @Override + public int read() throws IOException + { + int read = read(_oneByteBuffer, 0, 1); + if (read == 0) + throw new IOException("unready read=0"); + return read < 0 ? -1 : _oneByteBuffer[0] & 0xFF; + } + + @Override + public int read(byte[] b, int off, int len) throws IOException + { + // Calculate minimum request rate for DOS protection + long minRequestDataRate = _channelState.getHttpChannel().getHttpConfiguration().getMinRequestDataRate(); + if (minRequestDataRate > 0 && _firstByteTimeStamp != Long.MIN_VALUE) + { + long period = System.nanoTime() - _firstByteTimeStamp; + if (period > 0) + { + long minimumData = minRequestDataRate * TimeUnit.NANOSECONDS.toMillis(period) / TimeUnit.SECONDS.toMillis(1); + if (_contentProducer.getRawContentArrived() < minimumData) + { + BadMessageException bad = new BadMessageException(HttpStatus.REQUEST_TIMEOUT_408, + String.format("Request content data rate < %d B/s", minRequestDataRate)); + if (_channelState.isResponseCommitted()) + _channelState.getHttpChannel().abort(bad); + throw bad; + } + } + } + + while (true) + { + int read = _contentProducer.read(b, off, len); + if (LOG.isDebugEnabled()) + LOG.debug("read produced {} byte(s)", read); + if (read > 0) + return read; + + if (LOG.isDebugEnabled()) + LOG.debug("read error = " + _error); + if (_error != null) + throw new IOException(_error); + + if (LOG.isDebugEnabled()) + LOG.debug("read EOF = {}", _eof); + if (_eof.isEarly()) + throw new EofException("Early EOF"); + + if (LOG.isDebugEnabled()) + LOG.debug("read async = {}", isAsync()); + if (!isAsync()) + { + if (_eof.isEof()) + { + _eof = Eof.CONSUMED_EOF; + if (LOG.isDebugEnabled()) + LOG.debug("read on EOF, switching to CONSUMED_EOF and returning"); + return -1; + } + if (LOG.isDebugEnabled()) + LOG.debug("read blocked"); + blockForContent(); + if (LOG.isDebugEnabled()) + LOG.debug("read unblocked"); + } + else + { + if (_eof.isEof()) + { + boolean wasInAsyncWait = _channelState.onReadEof(); + if (wasInAsyncWait) + scheduleReadListenerNotification(); + if (LOG.isDebugEnabled()) + LOG.debug("async read on EOF (was in async wait? {}), switching to CONSUMED_EOF and returning", wasInAsyncWait); + _eof = Eof.CONSUMED_EOF; + return -1; + } + else + { + //TODO returning 0 breaks the InputStream contract. Shouldn't IOException be thrown instead? + _channelState.getHttpChannel().onAsyncWaitForContent(); // switches on fill interested + return 0; + } + } + } + } + + @Override + public int available() + { + int available = _contentProducer.available(); + if (LOG.isDebugEnabled()) + LOG.debug("available = {}", available); + return available; + } + + private void blockForContent() + { + try (AutoLock lock = _contentLock.lock()) + { + _channelState.getHttpChannel().onBlockWaitForContent(); // switches on fill interested + if (LOG.isDebugEnabled()) + LOG.debug("waiting for signal to wake up"); + _contentLockCondition.await(); + if (LOG.isDebugEnabled()) + LOG.debug("signalled to wake up"); + } + catch (Throwable x) + { + _channelState.getHttpChannel().onBlockWaitForContentFailure(x); + } + } + + /* Runnable */ + + /* + *

While this class is-a Runnable, it should never be dispatched in it's own thread. It is a runnable only so that the calling thread can use {@link + * ContextHandler#handle(Runnable)} to setup classloaders etc.

+ */ + @Override + public void run() + { + if (!_contentProducer.hasRawContent()) + { + if (LOG.isDebugEnabled()) + LOG.debug("running has no raw content; error: {}, EOF = {}", _error, _eof); + if (_error != null || _eof.isEarly()) + { + // TODO is this necessary to add here? + _channelState.getHttpChannel().getResponse().getHttpFields().add(HttpConnection.CONNECTION_CLOSE); + if (_error != null) + _readListener.onError(_error); + else + _readListener.onError(new EofException("Early EOF")); + } + else if (_eof.isEof()) + { + try + { + _readListener.onAllDataRead(); + } + catch (Throwable x) + { + if (LOG.isDebugEnabled()) + LOG.debug("running failed onAllDataRead", x); + _readListener.onError(x); + } + } + // else: !hasContent() && !error && !EOF -> no-op + } + else + { + if (LOG.isDebugEnabled()) + LOG.debug("running has raw content"); + try + { + _readListener.onDataAvailable(); + } + catch (Throwable x) + { + if (LOG.isDebugEnabled()) + LOG.debug("running failed onDataAvailable", x); + _readListener.onError(x); + } + } + } + + protected abstract void produceRawContent(); + + protected abstract void failRawContent(Throwable failure); + + /** + * An {@link Interceptor} that chains two other {@link Interceptor}s together. + * The {@link Interceptor#readFrom(Content)} calls the previous {@link Interceptor}'s + * {@link Interceptor#readFrom(Content)} and then passes any {@link Content} returned + * to the next {@link Interceptor}. + */ + static class ChainedInterceptor implements Interceptor, Destroyable + { + private final Interceptor _prev; + private final Interceptor _next; + + public ChainedInterceptor(Interceptor prev, Interceptor next) + { + _prev = prev; + _next = next; + } + + public Interceptor getPrev() + { + return _prev; + } + + public Interceptor getNext() + { + return _next; + } + + @Override + public Content readFrom(Content content) + { + return getNext().readFrom(getPrev().readFrom(content)); + } + + @Override + public void destroy() + { + if (_prev instanceof Destroyable) + ((Destroyable)_prev).destroy(); + if (_next instanceof Destroyable) + ((Destroyable)_next).destroy(); + } + } + + enum Eof + { + NOT_YET(false, false, false), + EOF(true, false, false), + CONSUMED_EOF(true, true, false), + EARLY_EOF(true, false, true), + ; + + private final boolean _eof; + private final boolean _consumed; + private final boolean _early; + + Eof(boolean eof, boolean consumed, boolean early) + { + _eof = eof; + _consumed = consumed; + _early = early; + } + + public boolean isEof() + { + return _eof; + } + + public boolean isConsumed() + { + return _consumed; + } + + public boolean isEarly() + { + return _early; + } + } + + protected static class ContentProducer + { + private final Runnable _rawContentProducer; + // Note: _rawContent can never be null for as long as _transformedContent is not null. + private Content _rawContent; + private Content _transformedContent; + private long _rawContentArrived; + private Interceptor _interceptor; + private boolean _allConsumed; + + public ContentProducer(Runnable rawContentProducer) + { + _rawContentProducer = rawContentProducer; + } + + void recycle() + { + if (LOG.isDebugEnabled()) + LOG.debug("recycle {}", this); + if (_transformedContent == _rawContent) + _transformedContent = null; + if (_transformedContent != null && !_transformedContent.isEmpty()) + _transformedContent.failed(null); + _transformedContent = null; + if (_rawContent != null && !_rawContent.isEmpty()) + _rawContent.failed(null); + _rawContent = null; + _rawContentArrived = 0L; + if (_interceptor instanceof Destroyable) + ((Destroyable)_interceptor).destroy(); + _interceptor = null; + _allConsumed = false; + } + + int available() + { + if (_transformedContent != null) + return _transformedContent.remaining(); + if (_rawContent == null) + produceRawContent(); + produceTransformedContent(); + return _transformedContent == null ? 0 : _transformedContent.remaining(); + } + + long getRawContentArrived() + { + return _rawContentArrived; + } + + boolean hasRawContent() + { + return _rawContent != null; + } + + Interceptor getInterceptor() + { + return _interceptor; + } + + void setInterceptor(Interceptor interceptor) + { + this._interceptor = interceptor; + } + + void addInterceptor(Interceptor interceptor) + { + if (_interceptor == null) + _interceptor = interceptor; + else + _interceptor = new ChainedInterceptor(_interceptor, interceptor); + } + + void addContent(Content content) + { + if (LOG.isDebugEnabled()) + LOG.debug("{} addContent {}", this, content); + if (content == null) + throw new AssertionError("Cannot add null content"); + if (_allConsumed) + { + content.failed(null); + return; + } + if (_rawContent != null) + throw new AssertionError("Cannot add new content while current one hasn't been processed"); + + _rawContent = content; + _rawContentArrived += content.remaining(); + } + + void consumeTransformedContent(Runnable failRawContent) + { + if (LOG.isDebugEnabled()) + LOG.debug("{} consumeTransformedContent", this); + // start by depleting the current _transformedContent + if (_transformedContent != null) + { + _transformedContent.skip(_transformedContent.remaining()); + if (_transformedContent != _rawContent) + _transformedContent.succeeded(); + _transformedContent = null; + } + + // don't bother transforming content, directly deplete the raw one + consumeRawContent(); + + // fail whatever other content the producer may have + failRawContent.run(); + _allConsumed = true; + } + + void consumeRawContent() + { + if (LOG.isDebugEnabled()) + LOG.debug("{} consumeRawContent", this); + if (_rawContent != null) + { + _rawContent.skip(_rawContent.remaining()); + _rawContent.succeeded(); + _rawContent = null; + } + } + + int read(byte[] b, int off, int len) + { + if (LOG.isDebugEnabled()) + LOG.debug("{} read", this); + while (_transformedContent == null) + { + if (_rawContent == null) + { + produceRawContent(); + if (_rawContent == null) + return 0; + } + produceTransformedContent(); + } + + int read = _transformedContent.get(b, off, len); + if (_transformedContent.isEmpty()) + produceTransformedContent(); //TODO: this should be something like cleanupTransformedContent() instead + + return read; + } + + /** + * Call the parser so that it's going to continue parsing of the request buffer, filling it with the socket's buffer + * if needed until either the request buffer is empty with no bytes left in the socket's buffer or {@link #addContent(Content)} + * is called. + */ + void produceRawContent() + { + if (LOG.isDebugEnabled()) + LOG.debug("{} produceRawContent", this); + _rawContentProducer.run(); + } + + /** + * Read {@code _rawContent} and {@code _transformedContent} to produce the next non-empty content to work with and store it in {@code _transformedContent}, + * or store null in {@code _transformedContent} if there is no content to work with. + * Depleted content gets succeeded and its field nullified, which can happen for both {@code _rawContent} and {@code _transformedContent}. + */ + private void produceTransformedContent() + { + if (LOG.isDebugEnabled()) + LOG.debug("{} produceTransformedContent", this); + if (_interceptor == null) + { + // no interceptor set + if (_rawContent != null && _rawContent.isEmpty()) + { + _rawContent.succeeded(); + _rawContent = null; + _transformedContent = null; + } + else + { + _transformedContent = _rawContent; + } + } + else + { + // interceptor set + transformContent(); + if (_transformedContent == null) + { + if (_rawContent != null && _rawContent.isEmpty()) + { + _rawContent.succeeded(); + _rawContent = null; + } + else + { + _transformedContent = _rawContent; + } + } + } + } + + /** + * Read {@code _rawContent} and write {@code _transformedContent} to produce content using the interceptor. + * The produced content is guaranteed to either be null or not empty. + */ + private void transformContent() + { + if (LOG.isDebugEnabled()) + LOG.debug("{} transformContent", this); + if (_rawContent == null) + return; + + _transformedContent = _interceptor.readFrom(_rawContent); + + if (_transformedContent != null && _transformedContent.isEmpty()) + { + if (_transformedContent != _rawContent) + _transformedContent.succeeded(); + _transformedContent = null; + } + } + + @Override + public String toString() + { + return getClass().getSimpleName() + "[i=" + _interceptor + ",b=" + _rawContentArrived + + ",r=" + _rawContent + ",t=" + _transformedContent + "]"; + } + } +} diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/AbstractLockedHttpInput.java b/jetty-server/src/main/java/org/eclipse/jetty/server/AbstractLockedHttpInput.java new file mode 100644 index 00000000000..fb11f83687b --- /dev/null +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/AbstractLockedHttpInput.java @@ -0,0 +1,236 @@ +// +// ======================================================================== +// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under +// the terms of the Eclipse Public License 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0 +// +// This Source Code may also be made available under the following +// Secondary Licenses when the conditions for such availability set +// forth in the Eclipse Public License, v. 2.0 are satisfied: +// the Apache License v2.0 which is available at +// https://www.apache.org/licenses/LICENSE-2.0 +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +package org.eclipse.jetty.server; + +import java.io.IOException; +import javax.servlet.ReadListener; + +import org.eclipse.jetty.util.thread.AutoLock; + +public abstract class AbstractLockedHttpInput extends AbstractHttpInput +{ + public AbstractLockedHttpInput(HttpChannelState state) + { + super(state); + } + + /* HttpInput */ + + @Override + public void recycle() + { + try (AutoLock lock = _contentLock.lock()) + { + super.recycle(); + } + } + + @Override + public Interceptor getInterceptor() + { + try (AutoLock lock = _contentLock.lock()) + { + return super.getInterceptor(); + } + } + + @Override + public void setInterceptor(Interceptor interceptor) + { + try (AutoLock lock = _contentLock.lock()) + { + super.setInterceptor(interceptor); + } + } + + @Override + public void addInterceptor(Interceptor interceptor) + { + try (AutoLock lock = _contentLock.lock()) + { + super.addInterceptor(interceptor); + } + } + + @Override + public void asyncReadProduce() + { + try (AutoLock lock = _contentLock.lock()) + { + super.asyncReadProduce(); + } + } + + @Override + public boolean addContent(Content content) + { + try (AutoLock lock = _contentLock.lock()) + { + return super.addContent(content); + } + } + + @Override + public boolean hasContent() + { + try (AutoLock lock = _contentLock.lock()) + { + return super.hasContent(); + } + } + + @Override + public long getContentLength() + { + try (AutoLock lock = _contentLock.lock()) + { + return super.getContentLength(); + } + } + + @Override + public boolean earlyEOF() + { + try (AutoLock lock = _contentLock.lock()) + { + return super.earlyEOF(); + } + } + + @Override + public boolean eof() + { + try (AutoLock lock = _contentLock.lock()) + { + return super.eof(); + } + } + + @Override + public boolean consumeAll() + { + try (AutoLock lock = _contentLock.lock()) + { + return super.consumeAll(); + } + } + + @Override + public boolean isError() + { + try (AutoLock lock = _contentLock.lock()) + { + return super.isError(); + } + } + + @Override + public boolean isAsync() + { + try (AutoLock lock = _contentLock.lock()) + { + return super.isAsync(); + } + } + + @Override + public boolean onIdleTimeout(Throwable x) + { + try (AutoLock lock = _contentLock.lock()) + { + return super.onIdleTimeout(x); + } + } + + @Override + public boolean failed(Throwable x) + { + try (AutoLock lock = _contentLock.lock()) + { + return super.failed(x); + } + } + + /* ServletInputStream */ + + @Override + public boolean isFinished() + { + try (AutoLock lock = _contentLock.lock()) + { + return super.isFinished(); + } + } + + @Override + public boolean isReady() + { + try (AutoLock lock = _contentLock.lock()) + { + return super.isReady(); + } + } + + @Override + public void setReadListener(ReadListener readListener) + { + try (AutoLock lock = _contentLock.lock()) + { + super.setReadListener(readListener); + } + } + + @Override + public int read() throws IOException + { + try (AutoLock lock = _contentLock.lock()) + { + return super.read(); + } + } + + @Override + public int read(byte[] b, int off, int len) throws IOException + { + try (AutoLock lock = _contentLock.lock()) + { + return super.read(b, off, len); + } + } + + @Override + public int available() + { + try (AutoLock lock = _contentLock.lock()) + { + return super.available(); + } + } + + /* Runnable */ + + @Override + public void run() + { + try (AutoLock lock = _contentLock.lock()) + { + super.run(); + } + } +} diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannel.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannel.java index 12ff69c7ed0..e9787547db3 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannel.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannel.java @@ -64,7 +64,7 @@ import org.slf4j.LoggerFactory; * HttpParser.RequestHandler callbacks. The completion of the active phase is signalled by a call to * HttpTransport.completed(). */ -public class HttpChannel implements Runnable, HttpOutput.Interceptor +public abstract class HttpChannel implements Runnable, HttpOutput.Interceptor { public static Listener NOOP_LISTENER = new Listener() {}; private static final Logger LOG = LoggerFactory.getLogger(HttpChannel.class); @@ -119,11 +119,7 @@ public class HttpChannel implements Runnable, HttpOutput.Interceptor return _state.isSendError(); } - protected HttpInput newHttpInput(HttpChannelState state) - { - //TODO the HTTP2 impl instantiation should be in a subclass - return new HttpInputOverHTTP2(state); - } + protected abstract HttpInput newHttpInput(HttpChannelState state); protected HttpOutput newHttpOutput() { @@ -947,7 +943,7 @@ public class HttpChannel implements Runnable, HttpOutput.Interceptor return null; } - protected void execute(Runnable task) + public void execute(Runnable task) { _executor.execute(task); } diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpInputOverFCGI.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpInputOverFCGI.java index 03da948c2f1..ad225549ad2 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpInputOverFCGI.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpInputOverFCGI.java @@ -22,17 +22,19 @@ import java.io.IOException; import java.util.Objects; import java.util.Queue; import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Condition; import javax.servlet.ReadListener; +import org.eclipse.jetty.http.BadMessageException; +import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.io.EofException; +import org.eclipse.jetty.server.AbstractHttpInput.Eof; import org.eclipse.jetty.util.component.Destroyable; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; import org.eclipse.jetty.util.thread.AutoLock; -import static org.eclipse.jetty.server.HttpInputOverHTTP.Eof; - public class HttpInputOverFCGI extends HttpInput { private static final Logger LOG = Log.getLogger(HttpInputOverFCGI.class); @@ -47,6 +49,7 @@ public class HttpInputOverFCGI extends HttpInput private Eof _eof = Eof.NOT_YET; private Throwable _error; private ReadListener _readListener; + private long _firstByteTimeStamp = Long.MIN_VALUE; public HttpInputOverFCGI(HttpChannelState state) { @@ -67,6 +70,7 @@ public class HttpInputOverFCGI extends HttpInput _eof = Eof.NOT_YET; _error = null; _readListener = null; + _firstByteTimeStamp = Long.MIN_VALUE; } } @@ -115,6 +119,12 @@ public class HttpInputOverFCGI extends HttpInput { if (LOG.isDebugEnabled()) LOG.debug("addContent {} {}", content, _contentProducer); + if (_firstByteTimeStamp == Long.MIN_VALUE) + { + _firstByteTimeStamp = System.nanoTime(); + if (_firstByteTimeStamp == Long.MIN_VALUE) + _firstByteTimeStamp++; + } long contentArrived = _contentProducer.addContent(content); long requestContentLength = _channelState.getHttpChannel().getRequest().getContentLengthLong(); // return false to make the parser go on, true to make it stop @@ -225,8 +235,20 @@ public class HttpInputOverFCGI extends HttpInput @Override public boolean onIdleTimeout(Throwable x) { - //TODO implement me! - return false; + try (AutoLock lock = _contentLock.lock()) + { + boolean neverDispatched = _channelState.isIdle(); + boolean waitingForContent = !_contentProducer.hasTransformedContent() && !_eof.isEof(); + if ((waitingForContent || neverDispatched) && !isError()) + { + x.addSuppressed(new Throwable("HttpInput idle timeout")); + _error = x; + if (isAsync()) + return _channelState.onContentAdded(); + unblock(); + } + return false; + } } @Override @@ -341,6 +363,25 @@ public class HttpInputOverFCGI extends HttpInput { try (AutoLock lock = _contentLock.lock()) { + // Calculate minimum request rate for DOS protection + long minRequestDataRate = _channelState.getHttpChannel().getHttpConfiguration().getMinRequestDataRate(); + if (minRequestDataRate > 0 && _firstByteTimeStamp != Long.MIN_VALUE) + { + long period = System.nanoTime() - _firstByteTimeStamp; + if (period > 0) + { + long minimumData = minRequestDataRate * TimeUnit.NANOSECONDS.toMillis(period) / TimeUnit.SECONDS.toMillis(1); + if (_contentProducer.getRawContentArrived() < minimumData) + { + BadMessageException bad = new BadMessageException(HttpStatus.REQUEST_TIMEOUT_408, + String.format("Request content data rate < %d B/s", minRequestDataRate)); + if (_channelState.isResponseCommitted()) + _channelState.getHttpChannel().abort(bad); + throw bad; + } + } + } + while (true) { int read = _contentProducer.read(b, off, len); @@ -439,10 +480,10 @@ public class HttpInputOverFCGI extends HttpInput { try (AutoLock lock = _contentLock.lock()) { - if (LOG.isDebugEnabled()) - LOG.debug("running"); if (!_contentProducer.hasRawContent()) { + if (LOG.isDebugEnabled()) + LOG.debug("running has no raw content; error: {}, EOF = {}", _error, _eof); if (_error != null || _eof.isEarly()) { // TODO is this necessary to add here? @@ -460,6 +501,8 @@ public class HttpInputOverFCGI extends HttpInput } catch (Throwable x) { + if (LOG.isDebugEnabled()) + LOG.debug("running failed onAllDataRead", x); _readListener.onError(x); } } @@ -467,12 +510,16 @@ public class HttpInputOverFCGI extends HttpInput } else { + if (LOG.isDebugEnabled()) + LOG.debug("running has raw content"); try { _readListener.onDataAvailable(); } catch (Throwable x) { + if (LOG.isDebugEnabled()) + LOG.debug("running failed onDataAvailable", x); _readListener.onError(x); } } @@ -500,6 +547,7 @@ public class HttpInputOverFCGI extends HttpInput if (_currentRawContent != null && !_currentRawContent.isEmpty()) _currentRawContent.failed(null); _currentRawContent = null; + _rawContentQueue.forEach(c -> c.failed(null)); _rawContentQueue.clear(); _rawContentArrived = 0L; if (_interceptor instanceof Destroyable) @@ -507,11 +555,15 @@ public class HttpInputOverFCGI extends HttpInput _interceptor = null; } + //TODO: factor out similarities with read and hasTransformedContent int available() { + if (_transformedContent != null) + return _transformedContent.remaining(); if (_currentRawContent == null) produceRawContent(); - return _currentRawContent == null ? 0 : _currentRawContent.remaining(); + produceTransformedContent(); + return _transformedContent == null ? 0 : _transformedContent.remaining(); } long getRawContentArrived() @@ -521,6 +573,8 @@ public class HttpInputOverFCGI extends HttpInput boolean hasRawContent() { + if (_currentRawContent == null) + produceRawContent(); return _currentRawContent != null; } diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpInputOverHTTP.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpInputOverHTTP.java index a7f1ee9f22e..3e1d79cfd12 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpInputOverHTTP.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpInputOverHTTP.java @@ -18,720 +18,37 @@ package org.eclipse.jetty.server; -import java.io.IOException; -import java.util.Objects; -import javax.servlet.ReadListener; - -import org.eclipse.jetty.io.EofException; -import org.eclipse.jetty.util.component.Destroyable; -import org.eclipse.jetty.util.log.Log; -import org.eclipse.jetty.util.log.Logger; - // tests used: RequestTest, PartialRFC2616Test, AsyncRequestReadTest, AsyncIOServletTest, GzipHandlerTest -public class HttpInputOverHTTP extends HttpInput +public class HttpInputOverHTTP extends AbstractHttpInput { - private static final Logger LOG = Log.getLogger(HttpInputOverHTTP.class); - - private final byte[] _oneByteBuffer = new byte[1]; - private final HttpChannelState _channelState; - - private final NotifyingSemaphore _semaphore = new NotifyingSemaphore(); - - // TODO: think about thread visibility of the below variables - private final ContentProducer _contentProducer; - private Eof _eof = Eof.NOT_YET; - private Throwable _error; - private ReadListener _readListener; - public HttpInputOverHTTP(HttpChannelState state) { - _channelState = state; - _contentProducer = new ContentProducer(() -> ((HttpConnection)state.getHttpChannel().getEndPoint().getConnection()).parseAndFillForContent()); - } - - /* HttpInput */ - - @Override - public void recycle() - { - if (LOG.isDebugEnabled()) - LOG.debug("recycle"); - _contentProducer.recycle(); - _eof = Eof.NOT_YET; - _error = null; - _readListener = null; - } - - @Override - public Interceptor getInterceptor() - { - return _contentProducer.getInterceptor(); - } - - @Override - public void setInterceptor(Interceptor interceptor) - { - _contentProducer.setInterceptor(interceptor); - } - - @Override - public void addInterceptor(Interceptor interceptor) - { - _contentProducer.addInterceptor(interceptor); - } - - @Override - public void asyncReadProduce() - { - if (LOG.isDebugEnabled()) - LOG.debug("asyncReadProduce {}", _contentProducer); - _contentProducer.produceRawContent(); + super(state); } @Override public boolean addContent(Content content) { - if (LOG.isDebugEnabled()) - LOG.debug("addContent {} {}", content, _contentProducer); - long contentArrived = _contentProducer.addContent(content); - long requestContentLength = _channelState.getHttpChannel().getRequest().getContentLengthLong(); - // return false to make the parser go on, true to make it stop - // -> tell the parser to stop adding content, unless we have read everything - boolean stopParsing = requestContentLength == -1 || contentArrived < requestContentLength; - if (isAsync()) - _channelState.onContentAdded(); - return stopParsing; + super.addContent(content); + return true; } @Override - public boolean hasContent() + protected void produceRawContent() { - return _contentProducer.hasRawContent(); + ((HttpConnection)_channelState.getHttpChannel().getEndPoint().getConnection()).parseAndFillForContent(); } @Override - public void unblock() - { - if (LOG.isDebugEnabled()) - LOG.debug("signalling blocked thread to wake up"); - _semaphore.release(); - } - - @Override - public long getContentLength() - { - return _contentProducer.getRawContentArrived(); - } - - @Override - public boolean earlyEOF() - { - if (LOG.isDebugEnabled()) - LOG.debug("received early EOF"); - _eof = Eof.EARLY_EOF; - if (isAsync()) - return _channelState.onContentAdded(); - unblock(); - return false; - } - - @Override - public boolean eof() - { - if (LOG.isDebugEnabled()) - LOG.debug("received EOF"); - _eof = Eof.EOF; - if (isAsync()) - return _channelState.onContentAdded(); - unblock(); - return false; - } - - @Override - public boolean consumeAll() - { - if (LOG.isDebugEnabled()) - LOG.debug("consume all"); - _contentProducer.consumeAll(); - if (_eof.isEof()) - _eof = Eof.CONSUMED_EOF; - - if (isFinished()) - return !isError(); - - _eof = Eof.EARLY_EOF; - return false; - } - - @Override - public boolean isError() - { - return _error != null; - } - - @Override - public boolean isAsync() - { - return _readListener != null; - } - - @Override - public boolean onIdleTimeout(Throwable x) - { - //TODO implement me! - return false; - } - - @Override - public boolean failed(Throwable x) - { - if (LOG.isDebugEnabled()) - LOG.debug("failed " + x); - if (_error != null) - _error.addSuppressed(x); - else - _error = x; - - if (isAsync()) - return _channelState.onContentAdded(); - unblock(); - return false; - } - - /* ServletInputStream */ - - @Override - public boolean isFinished() - { - boolean finished = !_contentProducer.hasRawContent() && _eof.isConsumed(); - if (LOG.isDebugEnabled()) - LOG.debug("isFinished? {}", finished); - return finished; - } - - @Override - public boolean isReady() - { - // calling _contentProducer.hasTransformedContent() might change the _eof state, so the following test order matters - if (_contentProducer.hasTransformedContent() || _eof.isEof()) - { - if (LOG.isDebugEnabled()) - LOG.debug("isReady? true"); - return true; - } - if (LOG.isDebugEnabled()) - LOG.debug("isReady? false"); - _channelState.onReadUnready(); - return false; - } - - @Override - public void setReadListener(ReadListener readListener) - { - if (_readListener != null) - throw new IllegalStateException("ReadListener already set"); - _readListener = Objects.requireNonNull(readListener); - - if (LOG.isDebugEnabled()) - LOG.debug("setReadListener error=" + _error + " eof=" + _eof + " " + _contentProducer); - boolean woken; - if (isError()) - { - woken = _channelState.onReadReady(); - } - else - { - if (_contentProducer.hasTransformedContent()) - { - woken = _channelState.onReadReady(); - } - else if (_eof.isEof()) - { - woken = _channelState.onReadEof(); - } - else - { - _channelState.onReadUnready(); - woken = false; - } - } - - if (LOG.isDebugEnabled()) - LOG.debug("setReadListener woken=" + woken); - if (woken) - scheduleReadListenerNotification(); - } - - private void scheduleReadListenerNotification() - { - HttpChannel channel = _channelState.getHttpChannel(); - channel.execute(channel); - } - - @Override - public int read() throws IOException - { - int read = read(_oneByteBuffer, 0, 1); - if (read == 0) - throw new IOException("unready read=0"); - return read < 0 ? -1 : _oneByteBuffer[0] & 0xFF; - } - - @Override - public int read(byte[] b, int off, int len) throws IOException + protected void failRawContent(Throwable failure) { while (true) { - int read = _contentProducer.read(b, off, len); - if (LOG.isDebugEnabled()) - LOG.debug("read produced {} byte(s)", read); - if (read > 0) - return read; - - if (LOG.isDebugEnabled()) - LOG.debug("read error = " + _error); - if (_error != null) - throw new IOException(_error); - - if (LOG.isDebugEnabled()) - LOG.debug("read EOF = {}", _eof); - if (_eof.isEarly()) - throw new EofException("Early EOF"); - - if (LOG.isDebugEnabled()) - LOG.debug("read async = {}", isAsync()); - if (!isAsync()) - { - if (_eof.isEof()) - { - _eof = Eof.CONSUMED_EOF; - if (LOG.isDebugEnabled()) - LOG.debug("read on EOF, switching to CONSUMED_EOF and returning"); - return -1; - } - if (LOG.isDebugEnabled()) - LOG.debug("read blocked"); - blockForContent(); - if (LOG.isDebugEnabled()) - LOG.debug("read unblocked"); - } - else - { - if (_eof.isEof()) - { - boolean wasInAsyncWait = _channelState.onReadEof(); - if (wasInAsyncWait) - scheduleReadListenerNotification(); - if (LOG.isDebugEnabled()) - LOG.debug("async read on EOF (was in async wait? {}), switching to CONSUMED_EOF and returning", wasInAsyncWait); - _eof = Eof.CONSUMED_EOF; - return -1; - } - else - { - //TODO returning 0 breaks the InputStream contract. Shouldn't IOException be thrown instead? - _channelState.getHttpChannel().onAsyncWaitForContent(); // switches on fill interested - return 0; - } - } - } - } - - @Override - public int available() - { - int available = _contentProducer.available(); - if (LOG.isDebugEnabled()) - LOG.debug("available = {}", available); - return available; - } - - private void blockForContent() - { - try - { - _semaphore.acquire(() -> - { - if (LOG.isDebugEnabled()) - LOG.debug("waiting for signal to wake up"); - _channelState.getHttpChannel().onBlockWaitForContent(); // switches on fill interested if it blocks - }); - } - catch (Throwable x) - { - _channelState.getHttpChannel().onBlockWaitForContentFailure(x); - } - } - - /* Runnable */ - - /* - *

While this class is-a Runnable, it should never be dispatched in it's own thread. It is a runnable only so that the calling thread can use {@link - * ContextHandler#handle(Runnable)} to setup classloaders etc.

- */ - @Override - public void run() - { - if (LOG.isDebugEnabled()) - LOG.debug("running"); - if (!_contentProducer.hasRawContent()) - { - if (_error != null || _eof.isEarly()) - { - // TODO is this necessary to add here? - _channelState.getHttpChannel().getResponse().getHttpFields().add(HttpConnection.CONNECTION_CLOSE); - if (_error != null) - _readListener.onError(_error); - else - _readListener.onError(new EofException("Early EOF")); - } - else if (_eof.isEof()) - { - try - { - _readListener.onAllDataRead(); - } - catch (Throwable x) - { - _readListener.onError(x); - } - } - // else: !hasContent() && !error && !EOF -> no-op - } - else - { - try - { - _readListener.onDataAvailable(); - } - catch (Throwable x) - { - _readListener.onError(x); - } - } - } - - /** - * An {@link Interceptor} that chains two other {@link Interceptor}s together. - * The {@link Interceptor#readFrom(Content)} calls the previous {@link Interceptor}'s - * {@link Interceptor#readFrom(Content)} and then passes any {@link Content} returned - * to the next {@link Interceptor}. - */ - static class ChainedInterceptor implements Interceptor, Destroyable - { - private final Interceptor _prev; - private final Interceptor _next; - - public ChainedInterceptor(Interceptor prev, Interceptor next) - { - _prev = prev; - _next = next; - } - - public Interceptor getPrev() - { - return _prev; - } - - public Interceptor getNext() - { - return _next; - } - - @Override - public Content readFrom(Content content) - { - return getNext().readFrom(getPrev().readFrom(content)); - } - - @Override - public void destroy() - { - if (_prev instanceof Destroyable) - ((Destroyable)_prev).destroy(); - if (_next instanceof Destroyable) - ((Destroyable)_next).destroy(); - } - } - - enum Eof - { - NOT_YET(false, false, false), - EOF(true, false, false), - CONSUMED_EOF(true, true, false), - EARLY_EOF(true, false, true), - ; - - private final boolean _eof; - private final boolean _consumed; - private final boolean _early; - - Eof(boolean eof, boolean consumed, boolean early) - { - _eof = eof; - _consumed = consumed; - _early = early; - } - - public boolean isEof() - { - return _eof; - } - - public boolean isConsumed() - { - return _consumed; - } - - public boolean isEarly() - { - return _early; - } - } - - private static class ContentProducer - { - private final Runnable _rawContentProducer; - // TODO: think about thread visibility of the below variables - // Note: _rawContent can never be null for as long as _transformedContent is not null. - private HttpInput.Content _rawContent; - private HttpInput.Content _transformedContent; - private long _rawContentArrived; - private HttpInput.Interceptor _interceptor; - - public ContentProducer(Runnable rawContentProducer) - { - _rawContentProducer = rawContentProducer; - } - - void recycle() - { - if (LOG.isDebugEnabled()) - LOG.debug("recycle {}", this); - if (_transformedContent == _rawContent) - _transformedContent = null; - if (_transformedContent != null && !_transformedContent.isEmpty()) - _transformedContent.failed(null); - _transformedContent = null; - if (_rawContent != null && !_rawContent.isEmpty()) - _rawContent.failed(null); - _rawContent = null; - _rawContentArrived = 0L; - if (_interceptor instanceof Destroyable) - ((Destroyable)_interceptor).destroy(); - _interceptor = null; - } - - int available() - { - if (_rawContent == null) - produceRawContent(); - return _rawContent == null ? 0 : _rawContent.remaining(); - } - - long getRawContentArrived() - { - return _rawContentArrived; - } - - boolean hasRawContent() - { - return _rawContent != null; - } - - boolean hasTransformedContent() - { - if (_transformedContent != null) - return true; - if (_rawContent == null) - produceRawContent(); - produceTransformedContent(); - return _transformedContent != null; - } - - HttpInput.Interceptor getInterceptor() - { - return _interceptor; - } - - void setInterceptor(HttpInput.Interceptor interceptor) - { - this._interceptor = interceptor; - } - - void addInterceptor(HttpInput.Interceptor interceptor) - { - if (_interceptor == null) - _interceptor = interceptor; - else - _interceptor = new HttpInputOverHTTP.ChainedInterceptor(_interceptor, interceptor); - } - - long addContent(HttpInput.Content content) - { - if (LOG.isDebugEnabled()) - LOG.debug("{} addContent {}", this, content); - if (content == null) - throw new AssertionError("Cannot add null content"); - if (_rawContent != null) - throw new AssertionError("Cannot add new content while current one hasn't been processed"); - - _rawContent = content; - _rawContentArrived += content.remaining(); - - return _rawContentArrived; - } - - void consumeAll() - { - if (LOG.isDebugEnabled()) - LOG.debug("{} consumeAll", this); - // start by depleting the current _transformedContent - if (_transformedContent != null) - { - _transformedContent.skip(_transformedContent.remaining()); - if (_transformedContent != _rawContent) - _transformedContent.succeeded(); - _transformedContent = null; - } - - // don't bother transforming content, directly deplete the raw one - while (true) - { - if (_rawContent == null) - produceRawContent(); - if (_rawContent == null) - break; - - _rawContent.skip(_rawContent.remaining()); - _rawContent.succeeded(); - _rawContent = null; - } - } - - int read(byte[] b, int off, int len) - { - if (LOG.isDebugEnabled()) - LOG.debug("{} read", this); - while (_transformedContent == null) - { - if (_rawContent == null) - { - produceRawContent(); - if (_rawContent == null) - return 0; - } - produceTransformedContent(); - } - - int read = _transformedContent.get(b, off, len); - if (_transformedContent.isEmpty()) - produceTransformedContent(); - - return read; - } - - /** - * Call the parser so that it's going to continue parsing of the request buffer, filling it with the socket's buffer - * if needed until either the request buffer is empty with no bytes left in the socket's buffer or {@link #addContent(Content)} - * is called. - */ - void produceRawContent() - { - if (LOG.isDebugEnabled()) - LOG.debug("{} produceRawContent", this); - _rawContentProducer.run(); - } - - /** - * Read {@code _rawContent} and {@code _transformedContent} to produce the next non-empty content to work with and store it in {@code _transformedContent}, - * or store null in {@code _transformedContent} if there is no content to work with. - * Depleted content gets succeeded and its field nullified, which can happen for both {@code _rawContent} and {@code _transformedContent}. - */ - private void produceTransformedContent() - { - if (LOG.isDebugEnabled()) - LOG.debug("{} produceTransformedContent", this); - if (_interceptor == null) - { - // no interceptor set - if (_rawContent != null && _rawContent.isEmpty()) - { - _rawContent.succeeded(); - _rawContent = null; - _transformedContent = null; - } - else - { - _transformedContent = _rawContent; - } - } - else - { - // interceptor set - transformContent(); - if (_transformedContent == null) - { - if (_rawContent != null && _rawContent.isEmpty()) - { - _rawContent.succeeded(); - _rawContent = null; - } - else - { - _transformedContent = _rawContent; - } - } - } - } - - /** - * Read {@code _rawContent} and write {@code _transformedContent} to produce content using the interceptor. - * The produced content is guaranteed to either be null or not empty. - */ - private void transformContent() - { - if (LOG.isDebugEnabled()) - LOG.debug("{} transformContent", this); - if (_rawContent == null) - return; - - _transformedContent = _interceptor.readFrom(_rawContent); - - if (_transformedContent != null && _transformedContent.isEmpty()) - { - if (_transformedContent != _rawContent) - _transformedContent.succeeded(); - _transformedContent = null; - } - } - - @Override - public String toString() - { - return getClass().getSimpleName() + "[i=" + _interceptor + ",b=" + _rawContentArrived + - ",r=" + _rawContent + ",t=" + _transformedContent + "]"; - } - } - - private static class NotifyingSemaphore - { - private int permits; - - public synchronized void acquire(Runnable onBlocking) throws InterruptedException - { - if (permits == 0) - onBlocking.run(); - while (permits == 0) - wait(); - permits--; - } - - public synchronized void release() - { - permits++; - if (permits == 1) - notify(); + if (!_contentProducer.hasRawContent()) + _contentProducer.produceRawContent(); + if (!_contentProducer.hasRawContent()) + break; + _contentProducer.consumeRawContent(); } } } diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpInputOverHTTP2.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpInputOverHTTP2.java deleted file mode 100644 index e97009217c1..00000000000 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpInputOverHTTP2.java +++ /dev/null @@ -1,180 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. -// -// This program and the accompanying materials are made available under -// the terms of the Eclipse Public License 2.0 which is available at -// https://www.eclipse.org/legal/epl-2.0 -// -// This Source Code may also be made available under the following -// Secondary Licenses when the conditions for such availability set -// forth in the Eclipse Public License, v. 2.0 are satisfied: -// the Apache License v2.0 which is available at -// https://www.apache.org/licenses/LICENSE-2.0 -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// ======================================================================== -// - -package org.eclipse.jetty.server; - -import java.io.IOException; -import javax.servlet.ReadListener; - -public class HttpInputOverHTTP2 extends HttpInput -{ - private final byte[] _oneByteBuffer = new byte[1]; - private final HttpChannelState _channelState; - - public HttpInputOverHTTP2(HttpChannelState state) - { - _channelState = state; - } - - /* HttpInput */ - - @Override - public void recycle() - { - - } - - @Override - public Interceptor getInterceptor() - { - return null; - } - - @Override - public void setInterceptor(Interceptor interceptor) - { - - } - - @Override - public void addInterceptor(Interceptor interceptor) - { - - } - - @Override - public void asyncReadProduce() throws IOException - { - - } - - @Override - public boolean addContent(Content content) - { - return false; - } - - @Override - public boolean hasContent() - { - return false; - } - - @Override - public void unblock() - { - - } - - @Override - public long getContentLength() - { - return 0; - } - - @Override - public boolean earlyEOF() - { - return false; - } - - @Override - public boolean eof() - { - return false; - } - - @Override - public boolean consumeAll() - { - return false; - } - - @Override - public boolean isError() - { - return false; - } - - @Override - public boolean isAsync() - { - return false; - } - - @Override - public boolean onIdleTimeout(Throwable x) - { - return false; - } - - @Override - public boolean failed(Throwable x) - { - return false; - } - - /* ServletInputStream */ - - @Override - public boolean isFinished() - { - return false; - } - - @Override - public boolean isReady() - { - return false; - } - - @Override - public void setReadListener(ReadListener readListener) - { - - } - - @Override - public int read() throws IOException - { - int read = read(_oneByteBuffer, 0, 1); - if (read == 0) - throw new IllegalStateException("unready read=0"); - return read < 0 ? -1 : _oneByteBuffer[0] & 0xFF; - } - - @Override - public int read(byte[] b, int off, int len) throws IOException - { - return 0; - } - - @Override - public int available() throws IOException - { - return 0; - } - - /* Runnable */ - - @Override - public void run() - { - - } -} diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/HttpWriterTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/HttpWriterTest.java index 661d857b31d..36eb48fd854 100644 --- a/jetty-server/src/test/java/org/eclipse/jetty/server/HttpWriterTest.java +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/HttpWriterTest.java @@ -48,6 +48,12 @@ public class HttpWriterTest HttpChannel channel = new HttpChannel(new MockConnector(), new HttpConfiguration(), null, null) { + @Override + protected HttpInput newHttpInput(HttpChannelState state) + { + return null; + } + @Override public ByteBufferPool getByteBufferPool() { diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/ResponseTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/ResponseTest.java index 8ff0b6cbc51..41829dc1f13 100644 --- a/jetty-server/src/test/java/org/eclipse/jetty/server/ResponseTest.java +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/ResponseTest.java @@ -175,7 +175,14 @@ public class ResponseTest { _channelError = failure; } - }); + }) + { + @Override + protected HttpInput newHttpInput(HttpChannelState state) + { + return new HttpInputOverHTTP(state); + } + }; } @AfterEach From 982ddc0c482171e3e386080191f2a3067865a42b Mon Sep 17 00:00:00 2001 From: Ludovic Orban Date: Thu, 27 Feb 2020 11:18:10 +0100 Subject: [PATCH 16/25] FCGI queue and pull model, merge all common code between H1, H2 and FCGI (Milestone 3) Signed-off-by: Ludovic Orban --- .../fcgi/server/HttpChannelOverFCGI.java | 45 +- .../jetty/fcgi/server/HttpInputOverFCGI.java | 42 + .../fcgi/server/ServerFCGIConnection.java | 2 +- .../jetty/server/HttpInputOverFCGI.java | 753 ------------------ 4 files changed, 87 insertions(+), 755 deletions(-) create mode 100644 jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/HttpInputOverFCGI.java delete mode 100644 jetty-server/src/main/java/org/eclipse/jetty/server/HttpInputOverFCGI.java diff --git a/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/HttpChannelOverFCGI.java b/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/HttpChannelOverFCGI.java index 38a46350aae..0fa1e5da408 100644 --- a/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/HttpChannelOverFCGI.java +++ b/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/HttpChannelOverFCGI.java @@ -18,7 +18,9 @@ package org.eclipse.jetty.fcgi.server; +import java.util.LinkedList; import java.util.Locale; +import java.util.Queue; import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicReference; @@ -36,7 +38,6 @@ import org.eclipse.jetty.server.HttpChannel; import org.eclipse.jetty.server.HttpChannelState; import org.eclipse.jetty.server.HttpConfiguration; import org.eclipse.jetty.server.HttpInput; -import org.eclipse.jetty.server.HttpInputOverFCGI; import org.eclipse.jetty.server.HttpTransport; import org.eclipse.jetty.util.StringUtil; import org.slf4j.Logger; @@ -46,6 +47,8 @@ public class HttpChannelOverFCGI extends HttpChannel { private static final Logger LOG = LoggerFactory.getLogger(HttpChannelOverFCGI.class); + private final Queue _contentQueue = new LinkedList<>(); + private boolean _contentFailed; private final HttpFields.Mutable fields = HttpFields.build(); private final Dispatcher dispatcher; private String method; @@ -60,6 +63,46 @@ public class HttpChannelOverFCGI extends HttpChannel this.dispatcher = new Dispatcher(connector.getServer().getThreadPool(), this); } + void enqueueContent(HttpInput.Content content) + { + synchronized (_contentQueue) + { + if (_contentFailed) + content.failed(null); + else + _contentQueue.offer(content); + } + } + + void pushContent() + { + HttpInput.Content content; + synchronized (_contentQueue) + { + if (_contentFailed) + content = null; + else + content = _contentQueue.poll(); + } + if (content != null) + onContent(content); + } + + void failContent(Throwable failure) + { + synchronized (_contentQueue) + { + _contentFailed = true; + while (true) + { + HttpInput.Content content = _contentQueue.poll(); + if (content == null) + break; + content.failed(failure); + } + } + } + @Override protected HttpInput newHttpInput(HttpChannelState state) { diff --git a/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/HttpInputOverFCGI.java b/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/HttpInputOverFCGI.java new file mode 100644 index 00000000000..1c662aeb51a --- /dev/null +++ b/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/HttpInputOverFCGI.java @@ -0,0 +1,42 @@ +// +// ======================================================================== +// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under +// the terms of the Eclipse Public License 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0 +// +// This Source Code may also be made available under the following +// Secondary Licenses when the conditions for such availability set +// forth in the Eclipse Public License, v. 2.0 are satisfied: +// the Apache License v2.0 which is available at +// https://www.apache.org/licenses/LICENSE-2.0 +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +package org.eclipse.jetty.fcgi.server; + +import org.eclipse.jetty.server.AbstractLockedHttpInput; +import org.eclipse.jetty.server.HttpChannelState; + +public class HttpInputOverFCGI extends AbstractLockedHttpInput +{ + public HttpInputOverFCGI(HttpChannelState state) + { + super(state); + } + + @Override + protected void produceRawContent() + { + ((HttpChannelOverFCGI)_channelState.getHttpChannel()).pushContent(); + } + + @Override + protected void failRawContent(Throwable failure) + { + ((HttpChannelOverFCGI)_channelState.getHttpChannel()).failContent(failure); + } +} diff --git a/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/ServerFCGIConnection.java b/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/ServerFCGIConnection.java index 27b1b9ba465..820dfc3e3e8 100644 --- a/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/ServerFCGIConnection.java +++ b/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/ServerFCGIConnection.java @@ -197,7 +197,7 @@ public class ServerFCGIConnection extends AbstractConnection { ByteBuffer copy = ByteBuffer.allocate(buffer.remaining()); copy.put(buffer).flip(); - channel.onContent(new HttpInput.Content(copy)); + channel.enqueueContent(new HttpInput.Content(copy)); } return false; } diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpInputOverFCGI.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpInputOverFCGI.java deleted file mode 100644 index ad225549ad2..00000000000 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpInputOverFCGI.java +++ /dev/null @@ -1,753 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. -// -// This program and the accompanying materials are made available under -// the terms of the Eclipse Public License 2.0 which is available at -// https://www.eclipse.org/legal/epl-2.0 -// -// This Source Code may also be made available under the following -// Secondary Licenses when the conditions for such availability set -// forth in the Eclipse Public License, v. 2.0 are satisfied: -// the Apache License v2.0 which is available at -// https://www.apache.org/licenses/LICENSE-2.0 -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// ======================================================================== -// - -package org.eclipse.jetty.server; - -import java.io.IOException; -import java.util.Objects; -import java.util.Queue; -import java.util.concurrent.LinkedBlockingQueue; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.locks.Condition; -import javax.servlet.ReadListener; - -import org.eclipse.jetty.http.BadMessageException; -import org.eclipse.jetty.http.HttpStatus; -import org.eclipse.jetty.io.EofException; -import org.eclipse.jetty.server.AbstractHttpInput.Eof; -import org.eclipse.jetty.util.component.Destroyable; -import org.eclipse.jetty.util.log.Log; -import org.eclipse.jetty.util.log.Logger; -import org.eclipse.jetty.util.thread.AutoLock; - -public class HttpInputOverFCGI extends HttpInput -{ - private static final Logger LOG = Log.getLogger(HttpInputOverFCGI.class); - - private final byte[] _oneByteBuffer = new byte[1]; - private final HttpChannelState _channelState; - - private final AutoLock _contentLock = new AutoLock(); - private final Condition _contentLockCondition = _contentLock.newCondition(); - - private final ContentProducer _contentProducer; - private Eof _eof = Eof.NOT_YET; - private Throwable _error; - private ReadListener _readListener; - private long _firstByteTimeStamp = Long.MIN_VALUE; - - public HttpInputOverFCGI(HttpChannelState state) - { - _channelState = state; - _contentProducer = new ContentProducer(); - } - - /* HttpInput */ - - @Override - public void recycle() - { - try (AutoLock lock = _contentLock.lock()) - { - if (LOG.isDebugEnabled()) - LOG.debug("recycle"); - _contentProducer.recycle(); - _eof = Eof.NOT_YET; - _error = null; - _readListener = null; - _firstByteTimeStamp = Long.MIN_VALUE; - } - } - - @Override - public Interceptor getInterceptor() - { - try (AutoLock lock = _contentLock.lock()) - { - return _contentProducer.getInterceptor(); - } - } - - @Override - public void setInterceptor(Interceptor interceptor) - { - try (AutoLock lock = _contentLock.lock()) - { - _contentProducer.setInterceptor(interceptor); - } - } - - @Override - public void addInterceptor(Interceptor interceptor) - { - try (AutoLock lock = _contentLock.lock()) - { - _contentProducer.addInterceptor(interceptor); - } - } - - @Override - public void asyncReadProduce() - { - try (AutoLock lock = _contentLock.lock()) - { - if (LOG.isDebugEnabled()) - LOG.debug("asyncReadProduce {}", _contentProducer); - _contentProducer.produceRawContent(); - } - } - - @Override - public boolean addContent(Content content) - { - try (AutoLock lock = _contentLock.lock()) - { - if (LOG.isDebugEnabled()) - LOG.debug("addContent {} {}", content, _contentProducer); - if (_firstByteTimeStamp == Long.MIN_VALUE) - { - _firstByteTimeStamp = System.nanoTime(); - if (_firstByteTimeStamp == Long.MIN_VALUE) - _firstByteTimeStamp++; - } - long contentArrived = _contentProducer.addContent(content); - long requestContentLength = _channelState.getHttpChannel().getRequest().getContentLengthLong(); - // return false to make the parser go on, true to make it stop - // -> tell the parser to stop adding content, unless we have read everything - boolean stopParsing = requestContentLength == -1 || contentArrived < requestContentLength; - if (isAsync()) - _channelState.onContentAdded(); - return stopParsing; - } - } - - @Override - public boolean hasContent() - { - try (AutoLock lock = _contentLock.lock()) - { - return _contentProducer.hasRawContent(); - } - } - - @Override - public void unblock() - { - try (AutoLock lock = _contentLock.lock()) - { - if (LOG.isDebugEnabled()) - LOG.debug("signalling blocked thread to wake up"); - _contentLockCondition.signal(); - } - } - - @Override - public long getContentLength() - { - try (AutoLock lock = _contentLock.lock()) - { - return _contentProducer.getRawContentArrived(); - } - } - - @Override - public boolean earlyEOF() - { - try (AutoLock lock = _contentLock.lock()) - { - if (LOG.isDebugEnabled()) - LOG.debug("received early EOF"); - _eof = Eof.EARLY_EOF; - if (isAsync()) - return _channelState.onContentAdded(); - unblock(); - return false; - } - } - - @Override - public boolean eof() - { - try (AutoLock lock = _contentLock.lock()) - { - if (LOG.isDebugEnabled()) - LOG.debug("received EOF"); - _eof = Eof.EOF; - if (isAsync()) - return _channelState.onContentAdded(); - unblock(); - return false; - } - } - - @Override - public boolean consumeAll() - { - try (AutoLock lock = _contentLock.lock()) - { - if (LOG.isDebugEnabled()) - LOG.debug("consume all"); - _contentProducer.consumeAll(); - if (_eof.isEof()) - _eof = Eof.CONSUMED_EOF; - - if (isFinished()) - return !isError(); - - _eof = Eof.EARLY_EOF; - return false; - } - } - - @Override - public boolean isError() - { - try (AutoLock lock = _contentLock.lock()) - { - return _error != null; - } - } - - @Override - public boolean isAsync() - { - try (AutoLock lock = _contentLock.lock()) - { - return _readListener != null; - } - } - - @Override - public boolean onIdleTimeout(Throwable x) - { - try (AutoLock lock = _contentLock.lock()) - { - boolean neverDispatched = _channelState.isIdle(); - boolean waitingForContent = !_contentProducer.hasTransformedContent() && !_eof.isEof(); - if ((waitingForContent || neverDispatched) && !isError()) - { - x.addSuppressed(new Throwable("HttpInput idle timeout")); - _error = x; - if (isAsync()) - return _channelState.onContentAdded(); - unblock(); - } - return false; - } - } - - @Override - public boolean failed(Throwable x) - { - try (AutoLock lock = _contentLock.lock()) - { - if (LOG.isDebugEnabled()) - LOG.debug("failed " + x); - if (_error != null) - _error.addSuppressed(x); - else - _error = x; - - if (isAsync()) - return _channelState.onContentAdded(); - unblock(); - return false; - } - } - - /* ServletInputStream */ - - @Override - public boolean isFinished() - { - try (AutoLock lock = _contentLock.lock()) - { - boolean finished = !_contentProducer.hasRawContent() && _eof.isConsumed(); - if (LOG.isDebugEnabled()) - LOG.debug("isFinished? {}", finished); - return finished; - } - } - - @Override - public boolean isReady() - { - try (AutoLock lock = _contentLock.lock()) - { - // calling _contentProducer.hasTransformedContent() might change the _eof state, so the following test order matters - if (_contentProducer.hasTransformedContent() || _eof.isEof()) - { - if (LOG.isDebugEnabled()) - LOG.debug("isReady? true"); - return true; - } - if (LOG.isDebugEnabled()) - LOG.debug("isReady? false"); - _channelState.onReadUnready(); - return false; - } - } - - @Override - public void setReadListener(ReadListener readListener) - { - try (AutoLock lock = _contentLock.lock()) - { - if (_readListener != null) - throw new IllegalStateException("ReadListener already set"); - _readListener = Objects.requireNonNull(readListener); - - if (LOG.isDebugEnabled()) - LOG.debug("setReadListener error=" + _error + " eof=" + _eof + " " + _contentProducer); - boolean woken; - if (isError()) - { - woken = _channelState.onReadReady(); - } - else - { - if (_contentProducer.hasTransformedContent()) - { - woken = _channelState.onReadReady(); - } - else if (_eof.isEof()) - { - woken = _channelState.onReadEof(); - } - else - { - _channelState.onReadUnready(); - woken = false; - } - } - - if (LOG.isDebugEnabled()) - LOG.debug("setReadListener woken=" + woken); - if (woken) - scheduleReadListenerNotification(); - } - } - - private void scheduleReadListenerNotification() - { - HttpChannel channel = _channelState.getHttpChannel(); - channel.execute(channel); - } - - @Override - public int read() throws IOException - { - int read = read(_oneByteBuffer, 0, 1); - if (read == 0) - throw new IOException("unready read=0"); - return read < 0 ? -1 : _oneByteBuffer[0] & 0xFF; - } - - @Override - public int read(byte[] b, int off, int len) throws IOException - { - try (AutoLock lock = _contentLock.lock()) - { - // Calculate minimum request rate for DOS protection - long minRequestDataRate = _channelState.getHttpChannel().getHttpConfiguration().getMinRequestDataRate(); - if (minRequestDataRate > 0 && _firstByteTimeStamp != Long.MIN_VALUE) - { - long period = System.nanoTime() - _firstByteTimeStamp; - if (period > 0) - { - long minimumData = minRequestDataRate * TimeUnit.NANOSECONDS.toMillis(period) / TimeUnit.SECONDS.toMillis(1); - if (_contentProducer.getRawContentArrived() < minimumData) - { - BadMessageException bad = new BadMessageException(HttpStatus.REQUEST_TIMEOUT_408, - String.format("Request content data rate < %d B/s", minRequestDataRate)); - if (_channelState.isResponseCommitted()) - _channelState.getHttpChannel().abort(bad); - throw bad; - } - } - } - - while (true) - { - int read = _contentProducer.read(b, off, len); - if (LOG.isDebugEnabled()) - LOG.debug("read produced {} byte(s)", read); - if (read > 0) - return read; - - if (LOG.isDebugEnabled()) - LOG.debug("read error = " + _error); - if (_error != null) - throw new IOException(_error); - - if (LOG.isDebugEnabled()) - LOG.debug("read EOF = {}", _eof); - if (_eof.isEarly()) - throw new EofException("Early EOF"); - - if (LOG.isDebugEnabled()) - LOG.debug("read async = {}", isAsync()); - if (!isAsync()) - { - if (_eof.isEof()) - { - _eof = Eof.CONSUMED_EOF; - if (LOG.isDebugEnabled()) - LOG.debug("read on EOF, switching to CONSUMED_EOF and returning"); - return -1; - } - if (LOG.isDebugEnabled()) - LOG.debug("read blocked"); - blockForContent(); - if (LOG.isDebugEnabled()) - LOG.debug("read unblocked"); - } - else - { - if (_eof.isEof()) - { - boolean wasInAsyncWait = _channelState.onReadEof(); - if (wasInAsyncWait) - scheduleReadListenerNotification(); - if (LOG.isDebugEnabled()) - LOG.debug("async read on EOF (was in async wait? {}), switching to CONSUMED_EOF and returning", wasInAsyncWait); - _eof = Eof.CONSUMED_EOF; - return -1; - } - else - { - //TODO returning 0 breaks the InputStream contract. Shouldn't IOException be thrown instead? - _channelState.getHttpChannel().onAsyncWaitForContent(); // switches on fill interested - return 0; - } - } - } - } - } - - @Override - public int available() - { - try (AutoLock lock = _contentLock.lock()) - { - int available = _contentProducer.available(); - if (LOG.isDebugEnabled()) - LOG.debug("available = {}", available); - return available; - } - } - - private void blockForContent() - { - try - { - _channelState.getHttpChannel().onBlockWaitForContent(); // switches on fill interested - if (LOG.isDebugEnabled()) - LOG.debug("waiting for signal to wake up"); - _contentLockCondition.await(); - if (LOG.isDebugEnabled()) - LOG.debug("signalled to wake up"); - } - catch (Throwable x) - { - _channelState.getHttpChannel().onBlockWaitForContentFailure(x); - } - } - - /* Runnable */ - - /* - *

While this class is-a Runnable, it should never be dispatched in it's own thread. It is a runnable only so that the calling thread can use {@link - * ContextHandler#handle(Runnable)} to setup classloaders etc.

- */ - @Override - public void run() - { - try (AutoLock lock = _contentLock.lock()) - { - if (!_contentProducer.hasRawContent()) - { - if (LOG.isDebugEnabled()) - LOG.debug("running has no raw content; error: {}, EOF = {}", _error, _eof); - if (_error != null || _eof.isEarly()) - { - // TODO is this necessary to add here? - _channelState.getHttpChannel().getResponse().getHttpFields().add(HttpConnection.CONNECTION_CLOSE); - if (_error != null) - _readListener.onError(_error); - else - _readListener.onError(new EofException("Early EOF")); - } - else if (_eof.isEof()) - { - try - { - _readListener.onAllDataRead(); - } - catch (Throwable x) - { - if (LOG.isDebugEnabled()) - LOG.debug("running failed onAllDataRead", x); - _readListener.onError(x); - } - } - // else: !hasContent() && !error && !EOF -> no-op - } - else - { - if (LOG.isDebugEnabled()) - LOG.debug("running has raw content"); - try - { - _readListener.onDataAvailable(); - } - catch (Throwable x) - { - if (LOG.isDebugEnabled()) - LOG.debug("running failed onDataAvailable", x); - _readListener.onError(x); - } - } - } - } - - private static class ContentProducer - { - // Note: _rawContent can never be null for as long as _transformedContent is not null. - private final Queue _rawContentQueue = new LinkedBlockingQueue<>(); - private HttpInput.Content _currentRawContent; - private HttpInput.Content _transformedContent; - private long _rawContentArrived; - private HttpInput.Interceptor _interceptor; - - void recycle() - { - if (LOG.isDebugEnabled()) - LOG.debug("recycle {}", this); - if (_transformedContent == _currentRawContent) - _transformedContent = null; - if (_transformedContent != null && !_transformedContent.isEmpty()) - _transformedContent.failed(null); - _transformedContent = null; - if (_currentRawContent != null && !_currentRawContent.isEmpty()) - _currentRawContent.failed(null); - _currentRawContent = null; - _rawContentQueue.forEach(c -> c.failed(null)); - _rawContentQueue.clear(); - _rawContentArrived = 0L; - if (_interceptor instanceof Destroyable) - ((Destroyable)_interceptor).destroy(); - _interceptor = null; - } - - //TODO: factor out similarities with read and hasTransformedContent - int available() - { - if (_transformedContent != null) - return _transformedContent.remaining(); - if (_currentRawContent == null) - produceRawContent(); - produceTransformedContent(); - return _transformedContent == null ? 0 : _transformedContent.remaining(); - } - - long getRawContentArrived() - { - return _rawContentArrived; - } - - boolean hasRawContent() - { - if (_currentRawContent == null) - produceRawContent(); - return _currentRawContent != null; - } - - boolean hasTransformedContent() - { - if (_transformedContent != null) - return true; - if (_currentRawContent == null) - produceRawContent(); - produceTransformedContent(); - return _transformedContent != null; - } - - HttpInput.Interceptor getInterceptor() - { - return _interceptor; - } - - void setInterceptor(HttpInput.Interceptor interceptor) - { - this._interceptor = interceptor; - } - - void addInterceptor(HttpInput.Interceptor interceptor) - { - if (_interceptor == null) - _interceptor = interceptor; - else - _interceptor = new HttpInputOverHTTP.ChainedInterceptor(_interceptor, interceptor); - } - - long addContent(HttpInput.Content content) - { - if (LOG.isDebugEnabled()) - LOG.debug("{} addContent {}", this, content); - if (content == null) - throw new AssertionError("Cannot add null content"); - - _rawContentQueue.offer(content); - _rawContentArrived += content.remaining(); - - return _rawContentArrived; - } - - void consumeAll() - { - if (LOG.isDebugEnabled()) - LOG.debug("{} consumeAll", this); - // start by depleting the current _transformedContent - if (_transformedContent != null) - { - _transformedContent.skip(_transformedContent.remaining()); - if (_transformedContent != _currentRawContent) - _transformedContent.succeeded(); - _transformedContent = null; - } - - // don't bother transforming content, directly deplete the raw one - while (true) - { - if (_currentRawContent == null) - produceRawContent(); - if (_currentRawContent == null) - break; - - _currentRawContent.skip(_currentRawContent.remaining()); - _currentRawContent.succeeded(); - _currentRawContent = null; - } - } - - int read(byte[] b, int off, int len) - { - if (LOG.isDebugEnabled()) - LOG.debug("{} read", this); - while (_transformedContent == null) - { - if (_currentRawContent == null) - { - produceRawContent(); - if (_currentRawContent == null) - return 0; - } - produceTransformedContent(); - } - - int read = _transformedContent.get(b, off, len); - if (_transformedContent.isEmpty()) - produceTransformedContent(); - - return read; - } - - /** - * Call the parser so that it's going to continue parsing of the request buffer, filling it with the socket's buffer - * if needed until either the request buffer is empty with no bytes left in the socket's buffer or {@link #addContent(Content)} - * is called. - */ - void produceRawContent() - { - if (LOG.isDebugEnabled()) - LOG.debug("{} produceRawContent", this); - _currentRawContent = _rawContentQueue.poll(); - } - - /** - * Read {@code _rawContent} and {@code _transformedContent} to produce the next non-empty content to work with and store it in {@code _transformedContent}, - * or store null in {@code _transformedContent} if there is no content to work with. - * Depleted content gets succeeded and its field nullified, which can happen for both {@code _rawContent} and {@code _transformedContent}. - */ - private void produceTransformedContent() - { - if (LOG.isDebugEnabled()) - LOG.debug("{} produceTransformedContent", this); - if (_interceptor == null) - { - // no interceptor set - if (_currentRawContent != null && _currentRawContent.isEmpty()) - { - _currentRawContent.succeeded(); - _currentRawContent = null; - _transformedContent = null; - } - else - { - _transformedContent = _currentRawContent; - } - } - else - { - // interceptor set - transformContent(); - if (_transformedContent == null) - { - if (_currentRawContent != null && _currentRawContent.isEmpty()) - { - _currentRawContent.succeeded(); - _currentRawContent = null; - } - else - { - _transformedContent = _currentRawContent; - } - } - } - } - - /** - * Read {@code _rawContent} and write {@code _transformedContent} to produce content using the interceptor. - * The produced content is guaranteed to either be null or not empty. - */ - private void transformContent() - { - if (LOG.isDebugEnabled()) - LOG.debug("{} transformContent", this); - if (_currentRawContent == null) - return; - - _transformedContent = _interceptor.readFrom(_currentRawContent); - - if (_transformedContent != null && _transformedContent.isEmpty()) - { - if (_transformedContent != _currentRawContent) - _transformedContent.succeeded(); - _transformedContent = null; - } - } - - @Override - public String toString() - { - return getClass().getSimpleName() + "[i=" + _interceptor + ",b=" + _rawContentArrived + - ",r=" + _currentRawContent + ",t=" + _transformedContent + "]"; - } - } -} From d0f04a8eae8bbb550b6f89a888392dde0d5d54b5 Mon Sep 17 00:00:00 2001 From: Ludovic Orban Date: Mon, 9 Mar 2020 12:21:06 +0100 Subject: [PATCH 17/25] addContent now returns void (Milestone 4) Signed-off-by: Ludovic Orban --- .../http2/server/HttpChannelOverHTTP2.java | 122 +++++++++++++++--- .../http2/server/HttpInputOverHTTP2.java | 30 +---- .../jetty/server/AbstractHttpInput.java | 6 +- .../jetty/server/AbstractLockedHttpInput.java | 4 +- .../org/eclipse/jetty/server/HttpChannel.java | 4 +- .../jetty/server/HttpChannelOverHttp.java | 5 +- .../org/eclipse/jetty/server/HttpInput.java | 3 +- .../jetty/server/HttpInputOverHTTP.java | 3 +- 8 files changed, 115 insertions(+), 62 deletions(-) diff --git a/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HttpChannelOverHTTP2.java b/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HttpChannelOverHTTP2.java index d6586c99358..934c9f81c0b 100644 --- a/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HttpChannelOverHTTP2.java +++ b/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HttpChannelOverHTTP2.java @@ -58,6 +58,64 @@ public class HttpChannelOverHTTP2 extends HttpChannel implements Closeable, Writ private boolean _expect100Continue; private boolean _delayedUntilContent; private boolean _useOutputDirectByteBuffers; + private final RequestContent _requestContent = new RequestContent(); + + private class RequestContent + { + private final ThreadLocal _syncFetchContentTl = new ThreadLocal<>(); + private HttpInput.Content _content; + private boolean _endStream; + private boolean _producing; + + public void demand() + { + if (!_producing) + { + _producing = true; + _syncFetchContentTl.set(Boolean.TRUE); + try + { + getStream().demand(1); + } + finally + { + _syncFetchContentTl.remove(); + } + } + } + + public void offerContent(HttpInput.Content content) + { + if (_content != null) + throw new AssertionError("content cannot be queued"); + _content = content; + _producing = false; + } + + public HttpInput.Content takeContent() + { + HttpInput.Content contentCopy = _content; + _content = null; + return contentCopy; + } + + public void reachedEndOfStream(boolean endStream) + { + _endStream = endStream; + } + + public boolean hasReachedEndOfStream() + { + boolean copy = _endStream; + _endStream = false; + return copy; + } + + private boolean isFetchingContent() + { + return !Boolean.TRUE.equals(_syncFetchContentTl.get()); + } + } public HttpChannelOverHTTP2(Connector connector, HttpConfiguration configuration, EndPoint endPoint, HttpTransportOverHTTP2 transport) { @@ -257,7 +315,21 @@ public class HttpChannelOverHTTP2 extends HttpChannel implements Closeable, Writ ByteBuffer buffer = frame.getData(); int length = buffer.remaining(); - boolean handle = onContent(new HttpInput.Content(buffer) + + _requestContent.reachedEndOfStream(frame.isEndStream()); + + if (LOG.isDebugEnabled()) + { + LOG.debug("HTTP2 Request #{}/{}: {} bytes of content", + stream.getId(), + Integer.toHexString(stream.getSession().hashCode()), + length); + } + + boolean wasDelayed = _delayedUntilContent; + _delayedUntilContent = false; + + _requestContent.offerContent(new HttpInput.Content(buffer) { @Override public void succeeded() @@ -277,28 +349,42 @@ public class HttpChannelOverHTTP2 extends HttpChannel implements Closeable, Writ return callback.getInvocationType(); } }); - - boolean endStream = frame.isEndStream(); - if (endStream) + if (getState().isAsync()) { - boolean handleContent = onContentComplete(); - boolean handleRequest = onRequestComplete(); - handle |= handleContent | handleRequest; + boolean handle = _requestContent.isFetchingContent() && getState().onReadPossible(); + return handle || wasDelayed ? this : null; + } + else + { + getRequest().getHttpInput().unblock(); + return wasDelayed ? this : null; + } + } + + void fetchContent() + { + // HttpInputOverHttp2 calls this method via produceRawContent; + // this is the equivalent of Http1 parseAndFill(). + + HttpInput.Content content = _requestContent.takeContent(); + if (content != null) + { + onContent(content); + } + else + { + _requestContent.demand(); + // If content was produced synchronously, consume it right away. + content = _requestContent.takeContent(); + if (content != null) + onContent(content); } - if (LOG.isDebugEnabled()) + if (_requestContent.hasReachedEndOfStream()) { - LOG.debug("HTTP2 Request #{}/{}: {} bytes of {} content, handle: {}", - stream.getId(), - Integer.toHexString(stream.getSession().hashCode()), - length, - endStream ? "last" : "some", - handle); + onContentComplete(); + onRequestComplete(); } - - boolean wasDelayed = _delayedUntilContent; - _delayedUntilContent = false; - return handle || wasDelayed ? this : null; } @Override diff --git a/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HttpInputOverHTTP2.java b/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HttpInputOverHTTP2.java index 1e15b6fb803..0093ef2eda5 100644 --- a/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HttpInputOverHTTP2.java +++ b/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HttpInputOverHTTP2.java @@ -20,46 +20,18 @@ package org.eclipse.jetty.http2.server; import org.eclipse.jetty.server.AbstractLockedHttpInput; import org.eclipse.jetty.server.HttpChannelState; -import org.eclipse.jetty.util.thread.AutoLock; public class HttpInputOverHTTP2 extends AbstractLockedHttpInput { - private boolean _producing; - public HttpInputOverHTTP2(HttpChannelState state) { super(state); } - @Override - public void recycle() - { - try (AutoLock lock = _contentLock.lock()) - { - super.recycle(); - _producing = false; - } - } - - @Override - public boolean addContent(Content content) - { - try (AutoLock lock = _contentLock.lock()) - { - boolean b = super.addContent(content); - _producing = false; - return b; - } - } - @Override protected void produceRawContent() { - if (!_producing) - { - _producing = true; - ((HttpChannelOverHTTP2)_channelState.getHttpChannel()).getStream().demand(1); - } + ((HttpChannelOverHTTP2)_channelState.getHttpChannel()).fetchContent(); } @Override diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/AbstractHttpInput.java b/jetty-server/src/main/java/org/eclipse/jetty/server/AbstractHttpInput.java index 39b3c33387a..c3a5ad1faba 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/AbstractHttpInput.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/AbstractHttpInput.java @@ -95,7 +95,7 @@ public abstract class AbstractHttpInput extends HttpInput } @Override - public boolean addContent(Content content) + public void addContent(Content content) { if (LOG.isDebugEnabled()) LOG.debug("addContent {} {}", content, _contentProducer); @@ -107,9 +107,7 @@ public abstract class AbstractHttpInput extends HttpInput } _contentProducer.addContent(content); if (isAsync()) - return _channelState.onContentAdded(); - unblock(); - return false; + _channelState.onContentAdded(); } @Override diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/AbstractLockedHttpInput.java b/jetty-server/src/main/java/org/eclipse/jetty/server/AbstractLockedHttpInput.java index fb11f83687b..b97b61230d7 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/AbstractLockedHttpInput.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/AbstractLockedHttpInput.java @@ -78,11 +78,11 @@ public abstract class AbstractLockedHttpInput extends AbstractHttpInput } @Override - public boolean addContent(Content content) + public void addContent(Content content) { try (AutoLock lock = _contentLock.lock()) { - return super.addContent(content); + super.addContent(content); } } diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannel.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannel.java index e9787547db3..000e103232d 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannel.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannel.java @@ -698,12 +698,12 @@ public abstract class HttpChannel implements Runnable, HttpOutput.Interceptor request.getFields()); } - public boolean onContent(HttpInput.Content content) + public void onContent(HttpInput.Content content) { if (LOG.isDebugEnabled()) LOG.debug("onContent {} {}", this, content); _combinedListener.onRequestContent(_request, content.getByteBuffer()); - return _request.getHttpInput().addContent(content); + _request.getHttpInput().addContent(content); } public boolean onContentComplete() diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelOverHttp.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelOverHttp.java index 3dfd130306d..1f252fbce95 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelOverHttp.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelOverHttp.java @@ -98,10 +98,9 @@ public class HttpChannelOverHttp extends HttpChannel implements HttpParser.Reque @Override public boolean content(ByteBuffer content) { - HttpInput.Content c = _httpConnection.newContent(content); - boolean handle = onContent(c) || _delayedForContent; + onContent(_httpConnection.newContent(content)); _delayedForContent = false; - return handle; + return true; } @Override diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpInput.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpInput.java index ac3bd8444ab..28db165796b 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpInput.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpInput.java @@ -67,9 +67,8 @@ public abstract class HttpInput extends ServletInputStream implements Runnable * Adds some content to this input stream. * * @param content the content to add - * @return true if content channel woken for read */ - public abstract boolean addContent(Content content); + public abstract void addContent(Content content); public abstract boolean hasContent(); diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpInputOverHTTP.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpInputOverHTTP.java index 3e1d79cfd12..f7cd9fdca87 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpInputOverHTTP.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpInputOverHTTP.java @@ -27,10 +27,9 @@ public class HttpInputOverHTTP extends AbstractHttpInput } @Override - public boolean addContent(Content content) + public void addContent(Content content) { super.addContent(content); - return true; } @Override From a4258ec9c09ca36e59b9cff26cd2f267838b407a Mon Sep 17 00:00:00 2001 From: Ludovic Orban Date: Mon, 9 Mar 2020 16:21:43 +0100 Subject: [PATCH 18/25] Merge all HttpInput* impls into a single class, push all differences to HttpChannel* impls (Milestone 5) Signed-off-by: Ludovic Orban --- .../fcgi/server/HttpChannelOverFCGI.java | 44 +- .../jetty/fcgi/server/HttpInputOverFCGI.java | 42 - .../org/eclipse/jetty/http2/HTTP2Stream.java | 10 +- .../http2/server/HttpChannelOverHTTP2.java | 121 ++- .../http2/server/HttpInputOverHTTP2.java | 42 - .../SpnegoAuthenticatorTest.java | 17 +- .../jetty/server/AbstractHttpInput.java | 767 ------------------ .../jetty/server/AbstractLockedHttpInput.java | 236 ------ .../org/eclipse/jetty/server/HttpChannel.java | 9 +- .../jetty/server/HttpChannelOverHttp.java | 18 +- .../jetty/server/HttpChannelState.java | 5 + .../eclipse/jetty/server/HttpConnection.java | 16 +- .../org/eclipse/jetty/server/HttpInput.java | 734 ++++++++++++++++- .../jetty/server/HttpInputOverHTTP.java | 53 -- .../eclipse/jetty/server/HttpWriterTest.java | 8 +- .../eclipse/jetty/server/ResponseTest.java | 8 +- .../jetty/http/client/AsyncIOServletTest.java | 252 +++++- 17 files changed, 1105 insertions(+), 1277 deletions(-) delete mode 100644 jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/HttpInputOverFCGI.java delete mode 100644 jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HttpInputOverHTTP2.java delete mode 100644 jetty-server/src/main/java/org/eclipse/jetty/server/AbstractHttpInput.java delete mode 100644 jetty-server/src/main/java/org/eclipse/jetty/server/AbstractLockedHttpInput.java delete mode 100644 jetty-server/src/main/java/org/eclipse/jetty/server/HttpInputOverHTTP.java diff --git a/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/HttpChannelOverFCGI.java b/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/HttpChannelOverFCGI.java index 0fa1e5da408..ba279a9aabf 100644 --- a/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/HttpChannelOverFCGI.java +++ b/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/HttpChannelOverFCGI.java @@ -18,7 +18,9 @@ package org.eclipse.jetty.fcgi.server; +import java.util.ArrayList; import java.util.LinkedList; +import java.util.List; import java.util.Locale; import java.util.Queue; import java.util.concurrent.Executor; @@ -35,7 +37,6 @@ import org.eclipse.jetty.http.MetaData; import org.eclipse.jetty.io.EndPoint; import org.eclipse.jetty.server.Connector; import org.eclipse.jetty.server.HttpChannel; -import org.eclipse.jetty.server.HttpChannelState; import org.eclipse.jetty.server.HttpConfiguration; import org.eclipse.jetty.server.HttpInput; import org.eclipse.jetty.server.HttpTransport; @@ -48,7 +49,7 @@ public class HttpChannelOverFCGI extends HttpChannel private static final Logger LOG = LoggerFactory.getLogger(HttpChannelOverFCGI.class); private final Queue _contentQueue = new LinkedList<>(); - private boolean _contentFailed; + private Throwable _contentFailure; private final HttpFields.Mutable fields = HttpFields.build(); private final Dispatcher dispatcher; private String method; @@ -65,21 +66,24 @@ public class HttpChannelOverFCGI extends HttpChannel void enqueueContent(HttpInput.Content content) { + Throwable failure; synchronized (_contentQueue) { - if (_contentFailed) - content.failed(null); - else + failure = _contentFailure; + if (failure == null) _contentQueue.offer(content); } + if (failure != null) + content.failed(failure); } - void pushContent() + @Override + public void produceContent() { HttpInput.Content content; synchronized (_contentQueue) { - if (_contentFailed) + if (_contentFailure != null) content = null; else content = _contentQueue.poll(); @@ -88,25 +92,21 @@ public class HttpChannelOverFCGI extends HttpChannel onContent(content); } - void failContent(Throwable failure) + @Override + public void failContent(Throwable failure) { + List copy; synchronized (_contentQueue) { - _contentFailed = true; - while (true) - { - HttpInput.Content content = _contentQueue.poll(); - if (content == null) - break; - content.failed(failure); - } - } - } + if (_contentFailure == null) + _contentFailure = failure; + else if (_contentFailure != failure) + _contentFailure.addSuppressed(failure); - @Override - protected HttpInput newHttpInput(HttpChannelState state) - { - return new HttpInputOverFCGI(state); + copy = new ArrayList<>(_contentQueue); + _contentQueue.clear(); + } + copy.forEach(content -> content.failed(failure)); } protected void header(HttpField field) diff --git a/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/HttpInputOverFCGI.java b/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/HttpInputOverFCGI.java deleted file mode 100644 index 1c662aeb51a..00000000000 --- a/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/HttpInputOverFCGI.java +++ /dev/null @@ -1,42 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. -// -// This program and the accompanying materials are made available under -// the terms of the Eclipse Public License 2.0 which is available at -// https://www.eclipse.org/legal/epl-2.0 -// -// This Source Code may also be made available under the following -// Secondary Licenses when the conditions for such availability set -// forth in the Eclipse Public License, v. 2.0 are satisfied: -// the Apache License v2.0 which is available at -// https://www.apache.org/licenses/LICENSE-2.0 -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// ======================================================================== -// - -package org.eclipse.jetty.fcgi.server; - -import org.eclipse.jetty.server.AbstractLockedHttpInput; -import org.eclipse.jetty.server.HttpChannelState; - -public class HttpInputOverFCGI extends AbstractLockedHttpInput -{ - public HttpInputOverFCGI(HttpChannelState state) - { - super(state); - } - - @Override - protected void produceRawContent() - { - ((HttpChannelOverFCGI)_channelState.getHttpChannel()).pushContent(); - } - - @Override - protected void failRawContent(Throwable failure) - { - ((HttpChannelOverFCGI)_channelState.getHttpChannel()).failContent(failure); - } -} diff --git a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Stream.java b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Stream.java index aa42a5fce8b..a24030c266a 100644 --- a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Stream.java +++ b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Stream.java @@ -77,6 +77,7 @@ public class HTTP2Stream extends IdleTimeout implements IStream, Callback, Dumpa private Listener listener; private long dataLength; private long dataDemand; + private Throwable failure; private boolean dataInitial; private boolean dataProcess; @@ -240,7 +241,8 @@ public class HTTP2Stream extends IdleTimeout implements IStream, Callback, Dumpa { try (AutoLock l = lock.lock()) { - dataDemand = Long.MIN_VALUE; + dataDemand = 0; + failure = x; while (true) { DataEntry dataEntry = dataQueue.poll(); @@ -416,10 +418,10 @@ public class HTTP2Stream extends IdleTimeout implements IStream, Callback, Dumpa DataEntry entry = new DataEntry(frame, callback); try (AutoLock l = lock.lock()) { - if (dataDemand == Long.MIN_VALUE) + if (failure != null) { // stream has been failed - callback.failed(null); + callback.failed(failure); return; } dataQueue.offer(entry); @@ -461,7 +463,7 @@ public class HTTP2Stream extends IdleTimeout implements IStream, Callback, Dumpa boolean proceed = false; try (AutoLock l = lock.lock()) { - if (dataDemand == Long.MIN_VALUE) + if (failure != null) return; // stream has been failed demand = dataDemand = MathUtils.cappedAdd(dataDemand, n); if (!dataProcess) diff --git a/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HttpChannelOverHTTP2.java b/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HttpChannelOverHTTP2.java index 934c9f81c0b..f5f60da88e5 100644 --- a/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HttpChannelOverHTTP2.java +++ b/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HttpChannelOverHTTP2.java @@ -41,11 +41,11 @@ import org.eclipse.jetty.io.EndPoint; import org.eclipse.jetty.io.WriteFlusher; import org.eclipse.jetty.server.Connector; import org.eclipse.jetty.server.HttpChannel; -import org.eclipse.jetty.server.HttpChannelState; import org.eclipse.jetty.server.HttpConfiguration; import org.eclipse.jetty.server.HttpInput; import org.eclipse.jetty.server.handler.ContextHandler; import org.eclipse.jetty.util.Callback; +import org.eclipse.jetty.util.thread.AutoLock; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -62,58 +62,58 @@ public class HttpChannelOverHTTP2 extends HttpChannel implements Closeable, Writ private class RequestContent { - private final ThreadLocal _syncFetchContentTl = new ThreadLocal<>(); private HttpInput.Content _content; private boolean _endStream; private boolean _producing; + private final AutoLock _lock = new AutoLock(); - public void demand() + void setContent(boolean endStream, HttpInput.Content content) { - if (!_producing) + try (AutoLock ignored = _lock.lock()) { - _producing = true; - _syncFetchContentTl.set(Boolean.TRUE); - try - { - getStream().demand(1); - } - finally - { - _syncFetchContentTl.remove(); - } + if (_content != null) + throw new AssertionError("content cannot be queued; stream=" + getStream()); + _endStream = endStream; + _content = content; + _producing = false; } } - public void offerContent(HttpInput.Content content) + private HttpInput.Content takeContent(boolean[] endStreamResult) { - if (_content != null) - throw new AssertionError("content cannot be queued"); - _content = content; - _producing = false; + try (AutoLock ignored = _lock.lock()) + { + if (_content == null) + return null; + HttpInput.Content contentCopy = _content; + endStreamResult[0] = _endStream; + _content = null; + _endStream = false; + return contentCopy; + } } - public HttpInput.Content takeContent() + HttpInput.Content takeContentOrDemand(boolean[] endStreamResult) { - HttpInput.Content contentCopy = _content; - _content = null; - return contentCopy; - } + HttpInput.Content content = takeContent(endStreamResult); + if (content != null) + return content; - public void reachedEndOfStream(boolean endStream) - { - _endStream = endStream; - } + boolean demand; + try (AutoLock ignored = _lock.lock()) + { + demand = !_producing; + if (demand) + { + if (_content != null) + throw new AssertionError("_content should be null"); + _producing = true; + } + } + if (demand) + getStream().demand(1); - public boolean hasReachedEndOfStream() - { - boolean copy = _endStream; - _endStream = false; - return copy; - } - - private boolean isFetchingContent() - { - return !Boolean.TRUE.equals(_syncFetchContentTl.get()); + return takeContent(endStreamResult); } } @@ -162,12 +162,6 @@ public class HttpChannelOverHTTP2 extends HttpChannel implements Closeable, Writ getResponse().getHttpOutput().onFlushed(bytes); } - @Override - protected HttpInput newHttpInput(HttpChannelState state) - { - return new HttpInputOverHTTP2(state); - } - public Runnable onRequest(HeadersFrame frame) { try @@ -316,8 +310,6 @@ public class HttpChannelOverHTTP2 extends HttpChannel implements Closeable, Writ ByteBuffer buffer = frame.getData(); int length = buffer.remaining(); - _requestContent.reachedEndOfStream(frame.isEndStream()); - if (LOG.isDebugEnabled()) { LOG.debug("HTTP2 Request #{}/{}: {} bytes of content", @@ -329,7 +321,7 @@ public class HttpChannelOverHTTP2 extends HttpChannel implements Closeable, Writ boolean wasDelayed = _delayedUntilContent; _delayedUntilContent = false; - _requestContent.offerContent(new HttpInput.Content(buffer) + _requestContent.setContent(frame.isEndStream(), new HttpInput.Content(buffer) { @Override public void succeeded() @@ -351,8 +343,8 @@ public class HttpChannelOverHTTP2 extends HttpChannel implements Closeable, Writ }); if (getState().isAsync()) { - boolean handle = _requestContent.isFetchingContent() && getState().onReadPossible(); - return handle || wasDelayed ? this : null; + boolean handle = getState().onReadPossible(); + return handle || wasDelayed ? this : null; } else { @@ -361,30 +353,29 @@ public class HttpChannelOverHTTP2 extends HttpChannel implements Closeable, Writ } } - void fetchContent() + @Override + public void produceContent() { - // HttpInputOverHttp2 calls this method via produceRawContent; + // HttpInputOverHttp2 calls this method via produceRawContent(); // this is the equivalent of Http1 parseAndFill(). - HttpInput.Content content = _requestContent.takeContent(); + boolean[] endStreamResult = new boolean[1]; + HttpInput.Content content = _requestContent.takeContentOrDemand(endStreamResult); if (content != null) { onContent(content); + if (endStreamResult[0]) + { + onContentComplete(); + onRequestComplete(); + } } - else - { - _requestContent.demand(); - // If content was produced synchronously, consume it right away. - content = _requestContent.takeContent(); - if (content != null) - onContent(content); - } + } - if (_requestContent.hasReachedEndOfStream()) - { - onContentComplete(); - onRequestComplete(); - } + @Override + public void failContent(Throwable failure) + { + getStream().fail(failure); } @Override diff --git a/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HttpInputOverHTTP2.java b/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HttpInputOverHTTP2.java deleted file mode 100644 index 0093ef2eda5..00000000000 --- a/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HttpInputOverHTTP2.java +++ /dev/null @@ -1,42 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. -// -// This program and the accompanying materials are made available under -// the terms of the Eclipse Public License 2.0 which is available at -// https://www.eclipse.org/legal/epl-2.0 -// -// This Source Code may also be made available under the following -// Secondary Licenses when the conditions for such availability set -// forth in the Eclipse Public License, v. 2.0 are satisfied: -// the Apache License v2.0 which is available at -// https://www.apache.org/licenses/LICENSE-2.0 -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// ======================================================================== -// - -package org.eclipse.jetty.http2.server; - -import org.eclipse.jetty.server.AbstractLockedHttpInput; -import org.eclipse.jetty.server.HttpChannelState; - -public class HttpInputOverHTTP2 extends AbstractLockedHttpInput -{ - public HttpInputOverHTTP2(HttpChannelState state) - { - super(state); - } - - @Override - protected void produceRawContent() - { - ((HttpChannelOverHTTP2)_channelState.getHttpChannel()).fetchContent(); - } - - @Override - protected void failRawContent(Throwable failure) - { - ((HttpChannelOverHTTP2)_channelState.getHttpChannel()).getStream().fail(failure); - } -} diff --git a/jetty-security/src/test/java/org/eclipse/jetty/security/authentication/SpnegoAuthenticatorTest.java b/jetty-security/src/test/java/org/eclipse/jetty/security/authentication/SpnegoAuthenticatorTest.java index c155f834e67..218769c2519 100644 --- a/jetty-security/src/test/java/org/eclipse/jetty/security/authentication/SpnegoAuthenticatorTest.java +++ b/jetty-security/src/test/java/org/eclipse/jetty/security/authentication/SpnegoAuthenticatorTest.java @@ -30,7 +30,6 @@ import org.eclipse.jetty.server.Authentication; import org.eclipse.jetty.server.HttpChannel; import org.eclipse.jetty.server.HttpChannelState; import org.eclipse.jetty.server.HttpConfiguration; -import org.eclipse.jetty.server.HttpInput; import org.eclipse.jetty.server.HttpOutput; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.Response; @@ -64,9 +63,13 @@ public class SpnegoAuthenticatorTest } @Override - protected HttpInput newHttpInput(HttpChannelState state) + public void produceContent() + { + } + + @Override + public void failContent(Throwable failure) { - return null; } @Override @@ -105,9 +108,13 @@ public class SpnegoAuthenticatorTest } @Override - protected HttpInput newHttpInput(HttpChannelState state) + public void produceContent() + { + } + + @Override + public void failContent(Throwable failure) { - return null; } @Override diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/AbstractHttpInput.java b/jetty-server/src/main/java/org/eclipse/jetty/server/AbstractHttpInput.java deleted file mode 100644 index c3a5ad1faba..00000000000 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/AbstractHttpInput.java +++ /dev/null @@ -1,767 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. -// -// This program and the accompanying materials are made available under -// the terms of the Eclipse Public License 2.0 which is available at -// https://www.eclipse.org/legal/epl-2.0 -// -// This Source Code may also be made available under the following -// Secondary Licenses when the conditions for such availability set -// forth in the Eclipse Public License, v. 2.0 are satisfied: -// the Apache License v2.0 which is available at -// https://www.apache.org/licenses/LICENSE-2.0 -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// ======================================================================== -// - -package org.eclipse.jetty.server; - -import java.io.IOException; -import java.util.Objects; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.locks.Condition; -import javax.servlet.ReadListener; - -import org.eclipse.jetty.http.BadMessageException; -import org.eclipse.jetty.http.HttpStatus; -import org.eclipse.jetty.io.EofException; -import org.eclipse.jetty.util.component.Destroyable; -import org.eclipse.jetty.util.log.Log; -import org.eclipse.jetty.util.log.Logger; -import org.eclipse.jetty.util.thread.AutoLock; - -public abstract class AbstractHttpInput extends HttpInput -{ - private static final Logger LOG = Log.getLogger(AbstractHttpInput.class); - - private final byte[] _oneByteBuffer = new byte[1]; - - protected final HttpChannelState _channelState; - protected final ContentProducer _contentProducer; - protected final AutoLock _contentLock = new AutoLock(); - protected final Condition _contentLockCondition = _contentLock.newCondition(); - - private Eof _eof = Eof.NOT_YET; - private Throwable _error; - private ReadListener _readListener; - private long _firstByteTimeStamp = Long.MIN_VALUE; - - public AbstractHttpInput(HttpChannelState state) - { - _channelState = state; - _contentProducer = new ContentProducer(this::produceRawContent); - } - - /* HttpInput */ - - @Override - public void recycle() - { - if (LOG.isDebugEnabled()) - LOG.debug("recycle"); - _contentProducer.recycle(); - _eof = Eof.NOT_YET; - _error = null; - _readListener = null; - _firstByteTimeStamp = Long.MIN_VALUE; - } - - @Override - public Interceptor getInterceptor() - { - return _contentProducer.getInterceptor(); - } - - @Override - public void setInterceptor(Interceptor interceptor) - { - _contentProducer.setInterceptor(interceptor); - } - - @Override - public void addInterceptor(Interceptor interceptor) - { - _contentProducer.addInterceptor(interceptor); - } - - @Override - public void asyncReadProduce() - { - if (LOG.isDebugEnabled()) - LOG.debug("asyncReadProduce {}", _contentProducer); - _contentProducer.produceRawContent(); - } - - @Override - public void addContent(Content content) - { - if (LOG.isDebugEnabled()) - LOG.debug("addContent {} {}", content, _contentProducer); - if (_firstByteTimeStamp == Long.MIN_VALUE) - { - _firstByteTimeStamp = System.nanoTime(); - if (_firstByteTimeStamp == Long.MIN_VALUE) - _firstByteTimeStamp++; - } - _contentProducer.addContent(content); - if (isAsync()) - _channelState.onContentAdded(); - } - - @Override - public boolean hasContent() - { - return _contentProducer.hasRawContent(); - } - - @Override - public void unblock() - { - try (AutoLock lock = _contentLock.lock()) - { - if (LOG.isDebugEnabled()) - LOG.debug("signalling blocked thread to wake up"); - _contentLockCondition.signal(); - } - } - - @Override - public long getContentLength() - { - return _contentProducer.getRawContentArrived(); - } - - @Override - public boolean earlyEOF() - { - if (LOG.isDebugEnabled()) - LOG.debug("received early EOF"); - _eof = Eof.EARLY_EOF; - if (isAsync()) - return _channelState.onContentAdded(); - unblock(); - return false; - } - - @Override - public boolean eof() - { - if (LOG.isDebugEnabled()) - LOG.debug("received EOF"); - _eof = Eof.EOF; - if (isAsync()) - return _channelState.onContentAdded(); - unblock(); - return false; - } - - @Override - public boolean consumeAll() - { - if (LOG.isDebugEnabled()) - LOG.debug("consume all"); - _contentProducer.consumeTransformedContent(() -> failRawContent(new IOException("Unconsumed content"))); - if (_eof.isEof()) - _eof = Eof.CONSUMED_EOF; - - if (isFinished()) - return !isError(); - - _eof = Eof.EARLY_EOF; - return false; - } - - @Override - public boolean isError() - { - return _error != null; - } - - @Override - public boolean isAsync() - { - return _readListener != null; - } - - @Override - public boolean onIdleTimeout(Throwable x) - { - boolean neverDispatched = _channelState.isIdle(); - boolean waitingForContent = _contentProducer.available() == 0 && !_eof.isEof(); - if ((waitingForContent || neverDispatched) && !isError()) - { - x.addSuppressed(new Throwable("HttpInput idle timeout")); - _error = x; - if (isAsync()) - return _channelState.onContentAdded(); - unblock(); - } - return false; - } - - @Override - public boolean failed(Throwable x) - { - if (LOG.isDebugEnabled()) - LOG.debug("failed " + x); - if (_error != null) - _error.addSuppressed(x); - else - _error = x; - - if (isAsync()) - return _channelState.onContentAdded(); - unblock(); - return false; - } - - /* ServletInputStream */ - - @Override - public boolean isFinished() - { - boolean finished = !_contentProducer.hasRawContent() && _eof.isConsumed(); - if (LOG.isDebugEnabled()) - LOG.debug("isFinished? {}", finished); - return finished; - } - - @Override - public boolean isReady() - { - // calling _contentProducer.available() might change the _eof state, so the following test order matters - if (_contentProducer.available() > 0 || _eof.isEof()) - { - if (LOG.isDebugEnabled()) - LOG.debug("isReady? true"); - return true; - } - if (LOG.isDebugEnabled()) - LOG.debug("isReady? false"); - _channelState.onReadUnready(); - return false; - } - - @Override - public void setReadListener(ReadListener readListener) - { - if (_readListener != null) - throw new IllegalStateException("ReadListener already set"); - _readListener = Objects.requireNonNull(readListener); - - if (LOG.isDebugEnabled()) - LOG.debug("setReadListener error=" + _error + " eof=" + _eof + " " + _contentProducer); - boolean woken; - if (isError()) - { - woken = _channelState.onReadReady(); - } - else - { - if (_contentProducer.available() > 0) - { - woken = _channelState.onReadReady(); - } - else if (_eof.isEof()) - { - woken = _channelState.onReadEof(); - } - else - { - _channelState.onReadUnready(); - woken = false; - } - } - - if (LOG.isDebugEnabled()) - LOG.debug("setReadListener woken=" + woken); - if (woken) - scheduleReadListenerNotification(); - } - - private void scheduleReadListenerNotification() - { - HttpChannel channel = _channelState.getHttpChannel(); - channel.execute(channel); - } - - @Override - public int read() throws IOException - { - int read = read(_oneByteBuffer, 0, 1); - if (read == 0) - throw new IOException("unready read=0"); - return read < 0 ? -1 : _oneByteBuffer[0] & 0xFF; - } - - @Override - public int read(byte[] b, int off, int len) throws IOException - { - // Calculate minimum request rate for DOS protection - long minRequestDataRate = _channelState.getHttpChannel().getHttpConfiguration().getMinRequestDataRate(); - if (minRequestDataRate > 0 && _firstByteTimeStamp != Long.MIN_VALUE) - { - long period = System.nanoTime() - _firstByteTimeStamp; - if (period > 0) - { - long minimumData = minRequestDataRate * TimeUnit.NANOSECONDS.toMillis(period) / TimeUnit.SECONDS.toMillis(1); - if (_contentProducer.getRawContentArrived() < minimumData) - { - BadMessageException bad = new BadMessageException(HttpStatus.REQUEST_TIMEOUT_408, - String.format("Request content data rate < %d B/s", minRequestDataRate)); - if (_channelState.isResponseCommitted()) - _channelState.getHttpChannel().abort(bad); - throw bad; - } - } - } - - while (true) - { - int read = _contentProducer.read(b, off, len); - if (LOG.isDebugEnabled()) - LOG.debug("read produced {} byte(s)", read); - if (read > 0) - return read; - - if (LOG.isDebugEnabled()) - LOG.debug("read error = " + _error); - if (_error != null) - throw new IOException(_error); - - if (LOG.isDebugEnabled()) - LOG.debug("read EOF = {}", _eof); - if (_eof.isEarly()) - throw new EofException("Early EOF"); - - if (LOG.isDebugEnabled()) - LOG.debug("read async = {}", isAsync()); - if (!isAsync()) - { - if (_eof.isEof()) - { - _eof = Eof.CONSUMED_EOF; - if (LOG.isDebugEnabled()) - LOG.debug("read on EOF, switching to CONSUMED_EOF and returning"); - return -1; - } - if (LOG.isDebugEnabled()) - LOG.debug("read blocked"); - blockForContent(); - if (LOG.isDebugEnabled()) - LOG.debug("read unblocked"); - } - else - { - if (_eof.isEof()) - { - boolean wasInAsyncWait = _channelState.onReadEof(); - if (wasInAsyncWait) - scheduleReadListenerNotification(); - if (LOG.isDebugEnabled()) - LOG.debug("async read on EOF (was in async wait? {}), switching to CONSUMED_EOF and returning", wasInAsyncWait); - _eof = Eof.CONSUMED_EOF; - return -1; - } - else - { - //TODO returning 0 breaks the InputStream contract. Shouldn't IOException be thrown instead? - _channelState.getHttpChannel().onAsyncWaitForContent(); // switches on fill interested - return 0; - } - } - } - } - - @Override - public int available() - { - int available = _contentProducer.available(); - if (LOG.isDebugEnabled()) - LOG.debug("available = {}", available); - return available; - } - - private void blockForContent() - { - try (AutoLock lock = _contentLock.lock()) - { - _channelState.getHttpChannel().onBlockWaitForContent(); // switches on fill interested - if (LOG.isDebugEnabled()) - LOG.debug("waiting for signal to wake up"); - _contentLockCondition.await(); - if (LOG.isDebugEnabled()) - LOG.debug("signalled to wake up"); - } - catch (Throwable x) - { - _channelState.getHttpChannel().onBlockWaitForContentFailure(x); - } - } - - /* Runnable */ - - /* - *

While this class is-a Runnable, it should never be dispatched in it's own thread. It is a runnable only so that the calling thread can use {@link - * ContextHandler#handle(Runnable)} to setup classloaders etc.

- */ - @Override - public void run() - { - if (!_contentProducer.hasRawContent()) - { - if (LOG.isDebugEnabled()) - LOG.debug("running has no raw content; error: {}, EOF = {}", _error, _eof); - if (_error != null || _eof.isEarly()) - { - // TODO is this necessary to add here? - _channelState.getHttpChannel().getResponse().getHttpFields().add(HttpConnection.CONNECTION_CLOSE); - if (_error != null) - _readListener.onError(_error); - else - _readListener.onError(new EofException("Early EOF")); - } - else if (_eof.isEof()) - { - try - { - _readListener.onAllDataRead(); - } - catch (Throwable x) - { - if (LOG.isDebugEnabled()) - LOG.debug("running failed onAllDataRead", x); - _readListener.onError(x); - } - } - // else: !hasContent() && !error && !EOF -> no-op - } - else - { - if (LOG.isDebugEnabled()) - LOG.debug("running has raw content"); - try - { - _readListener.onDataAvailable(); - } - catch (Throwable x) - { - if (LOG.isDebugEnabled()) - LOG.debug("running failed onDataAvailable", x); - _readListener.onError(x); - } - } - } - - protected abstract void produceRawContent(); - - protected abstract void failRawContent(Throwable failure); - - /** - * An {@link Interceptor} that chains two other {@link Interceptor}s together. - * The {@link Interceptor#readFrom(Content)} calls the previous {@link Interceptor}'s - * {@link Interceptor#readFrom(Content)} and then passes any {@link Content} returned - * to the next {@link Interceptor}. - */ - static class ChainedInterceptor implements Interceptor, Destroyable - { - private final Interceptor _prev; - private final Interceptor _next; - - public ChainedInterceptor(Interceptor prev, Interceptor next) - { - _prev = prev; - _next = next; - } - - public Interceptor getPrev() - { - return _prev; - } - - public Interceptor getNext() - { - return _next; - } - - @Override - public Content readFrom(Content content) - { - return getNext().readFrom(getPrev().readFrom(content)); - } - - @Override - public void destroy() - { - if (_prev instanceof Destroyable) - ((Destroyable)_prev).destroy(); - if (_next instanceof Destroyable) - ((Destroyable)_next).destroy(); - } - } - - enum Eof - { - NOT_YET(false, false, false), - EOF(true, false, false), - CONSUMED_EOF(true, true, false), - EARLY_EOF(true, false, true), - ; - - private final boolean _eof; - private final boolean _consumed; - private final boolean _early; - - Eof(boolean eof, boolean consumed, boolean early) - { - _eof = eof; - _consumed = consumed; - _early = early; - } - - public boolean isEof() - { - return _eof; - } - - public boolean isConsumed() - { - return _consumed; - } - - public boolean isEarly() - { - return _early; - } - } - - protected static class ContentProducer - { - private final Runnable _rawContentProducer; - // Note: _rawContent can never be null for as long as _transformedContent is not null. - private Content _rawContent; - private Content _transformedContent; - private long _rawContentArrived; - private Interceptor _interceptor; - private boolean _allConsumed; - - public ContentProducer(Runnable rawContentProducer) - { - _rawContentProducer = rawContentProducer; - } - - void recycle() - { - if (LOG.isDebugEnabled()) - LOG.debug("recycle {}", this); - if (_transformedContent == _rawContent) - _transformedContent = null; - if (_transformedContent != null && !_transformedContent.isEmpty()) - _transformedContent.failed(null); - _transformedContent = null; - if (_rawContent != null && !_rawContent.isEmpty()) - _rawContent.failed(null); - _rawContent = null; - _rawContentArrived = 0L; - if (_interceptor instanceof Destroyable) - ((Destroyable)_interceptor).destroy(); - _interceptor = null; - _allConsumed = false; - } - - int available() - { - if (_transformedContent != null) - return _transformedContent.remaining(); - if (_rawContent == null) - produceRawContent(); - produceTransformedContent(); - return _transformedContent == null ? 0 : _transformedContent.remaining(); - } - - long getRawContentArrived() - { - return _rawContentArrived; - } - - boolean hasRawContent() - { - return _rawContent != null; - } - - Interceptor getInterceptor() - { - return _interceptor; - } - - void setInterceptor(Interceptor interceptor) - { - this._interceptor = interceptor; - } - - void addInterceptor(Interceptor interceptor) - { - if (_interceptor == null) - _interceptor = interceptor; - else - _interceptor = new ChainedInterceptor(_interceptor, interceptor); - } - - void addContent(Content content) - { - if (LOG.isDebugEnabled()) - LOG.debug("{} addContent {}", this, content); - if (content == null) - throw new AssertionError("Cannot add null content"); - if (_allConsumed) - { - content.failed(null); - return; - } - if (_rawContent != null) - throw new AssertionError("Cannot add new content while current one hasn't been processed"); - - _rawContent = content; - _rawContentArrived += content.remaining(); - } - - void consumeTransformedContent(Runnable failRawContent) - { - if (LOG.isDebugEnabled()) - LOG.debug("{} consumeTransformedContent", this); - // start by depleting the current _transformedContent - if (_transformedContent != null) - { - _transformedContent.skip(_transformedContent.remaining()); - if (_transformedContent != _rawContent) - _transformedContent.succeeded(); - _transformedContent = null; - } - - // don't bother transforming content, directly deplete the raw one - consumeRawContent(); - - // fail whatever other content the producer may have - failRawContent.run(); - _allConsumed = true; - } - - void consumeRawContent() - { - if (LOG.isDebugEnabled()) - LOG.debug("{} consumeRawContent", this); - if (_rawContent != null) - { - _rawContent.skip(_rawContent.remaining()); - _rawContent.succeeded(); - _rawContent = null; - } - } - - int read(byte[] b, int off, int len) - { - if (LOG.isDebugEnabled()) - LOG.debug("{} read", this); - while (_transformedContent == null) - { - if (_rawContent == null) - { - produceRawContent(); - if (_rawContent == null) - return 0; - } - produceTransformedContent(); - } - - int read = _transformedContent.get(b, off, len); - if (_transformedContent.isEmpty()) - produceTransformedContent(); //TODO: this should be something like cleanupTransformedContent() instead - - return read; - } - - /** - * Call the parser so that it's going to continue parsing of the request buffer, filling it with the socket's buffer - * if needed until either the request buffer is empty with no bytes left in the socket's buffer or {@link #addContent(Content)} - * is called. - */ - void produceRawContent() - { - if (LOG.isDebugEnabled()) - LOG.debug("{} produceRawContent", this); - _rawContentProducer.run(); - } - - /** - * Read {@code _rawContent} and {@code _transformedContent} to produce the next non-empty content to work with and store it in {@code _transformedContent}, - * or store null in {@code _transformedContent} if there is no content to work with. - * Depleted content gets succeeded and its field nullified, which can happen for both {@code _rawContent} and {@code _transformedContent}. - */ - private void produceTransformedContent() - { - if (LOG.isDebugEnabled()) - LOG.debug("{} produceTransformedContent", this); - if (_interceptor == null) - { - // no interceptor set - if (_rawContent != null && _rawContent.isEmpty()) - { - _rawContent.succeeded(); - _rawContent = null; - _transformedContent = null; - } - else - { - _transformedContent = _rawContent; - } - } - else - { - // interceptor set - transformContent(); - if (_transformedContent == null) - { - if (_rawContent != null && _rawContent.isEmpty()) - { - _rawContent.succeeded(); - _rawContent = null; - } - else - { - _transformedContent = _rawContent; - } - } - } - } - - /** - * Read {@code _rawContent} and write {@code _transformedContent} to produce content using the interceptor. - * The produced content is guaranteed to either be null or not empty. - */ - private void transformContent() - { - if (LOG.isDebugEnabled()) - LOG.debug("{} transformContent", this); - if (_rawContent == null) - return; - - _transformedContent = _interceptor.readFrom(_rawContent); - - if (_transformedContent != null && _transformedContent.isEmpty()) - { - if (_transformedContent != _rawContent) - _transformedContent.succeeded(); - _transformedContent = null; - } - } - - @Override - public String toString() - { - return getClass().getSimpleName() + "[i=" + _interceptor + ",b=" + _rawContentArrived + - ",r=" + _rawContent + ",t=" + _transformedContent + "]"; - } - } -} diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/AbstractLockedHttpInput.java b/jetty-server/src/main/java/org/eclipse/jetty/server/AbstractLockedHttpInput.java deleted file mode 100644 index b97b61230d7..00000000000 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/AbstractLockedHttpInput.java +++ /dev/null @@ -1,236 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. -// -// This program and the accompanying materials are made available under -// the terms of the Eclipse Public License 2.0 which is available at -// https://www.eclipse.org/legal/epl-2.0 -// -// This Source Code may also be made available under the following -// Secondary Licenses when the conditions for such availability set -// forth in the Eclipse Public License, v. 2.0 are satisfied: -// the Apache License v2.0 which is available at -// https://www.apache.org/licenses/LICENSE-2.0 -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// ======================================================================== -// - -package org.eclipse.jetty.server; - -import java.io.IOException; -import javax.servlet.ReadListener; - -import org.eclipse.jetty.util.thread.AutoLock; - -public abstract class AbstractLockedHttpInput extends AbstractHttpInput -{ - public AbstractLockedHttpInput(HttpChannelState state) - { - super(state); - } - - /* HttpInput */ - - @Override - public void recycle() - { - try (AutoLock lock = _contentLock.lock()) - { - super.recycle(); - } - } - - @Override - public Interceptor getInterceptor() - { - try (AutoLock lock = _contentLock.lock()) - { - return super.getInterceptor(); - } - } - - @Override - public void setInterceptor(Interceptor interceptor) - { - try (AutoLock lock = _contentLock.lock()) - { - super.setInterceptor(interceptor); - } - } - - @Override - public void addInterceptor(Interceptor interceptor) - { - try (AutoLock lock = _contentLock.lock()) - { - super.addInterceptor(interceptor); - } - } - - @Override - public void asyncReadProduce() - { - try (AutoLock lock = _contentLock.lock()) - { - super.asyncReadProduce(); - } - } - - @Override - public void addContent(Content content) - { - try (AutoLock lock = _contentLock.lock()) - { - super.addContent(content); - } - } - - @Override - public boolean hasContent() - { - try (AutoLock lock = _contentLock.lock()) - { - return super.hasContent(); - } - } - - @Override - public long getContentLength() - { - try (AutoLock lock = _contentLock.lock()) - { - return super.getContentLength(); - } - } - - @Override - public boolean earlyEOF() - { - try (AutoLock lock = _contentLock.lock()) - { - return super.earlyEOF(); - } - } - - @Override - public boolean eof() - { - try (AutoLock lock = _contentLock.lock()) - { - return super.eof(); - } - } - - @Override - public boolean consumeAll() - { - try (AutoLock lock = _contentLock.lock()) - { - return super.consumeAll(); - } - } - - @Override - public boolean isError() - { - try (AutoLock lock = _contentLock.lock()) - { - return super.isError(); - } - } - - @Override - public boolean isAsync() - { - try (AutoLock lock = _contentLock.lock()) - { - return super.isAsync(); - } - } - - @Override - public boolean onIdleTimeout(Throwable x) - { - try (AutoLock lock = _contentLock.lock()) - { - return super.onIdleTimeout(x); - } - } - - @Override - public boolean failed(Throwable x) - { - try (AutoLock lock = _contentLock.lock()) - { - return super.failed(x); - } - } - - /* ServletInputStream */ - - @Override - public boolean isFinished() - { - try (AutoLock lock = _contentLock.lock()) - { - return super.isFinished(); - } - } - - @Override - public boolean isReady() - { - try (AutoLock lock = _contentLock.lock()) - { - return super.isReady(); - } - } - - @Override - public void setReadListener(ReadListener readListener) - { - try (AutoLock lock = _contentLock.lock()) - { - super.setReadListener(readListener); - } - } - - @Override - public int read() throws IOException - { - try (AutoLock lock = _contentLock.lock()) - { - return super.read(); - } - } - - @Override - public int read(byte[] b, int off, int len) throws IOException - { - try (AutoLock lock = _contentLock.lock()) - { - return super.read(b, off, len); - } - } - - @Override - public int available() - { - try (AutoLock lock = _contentLock.lock()) - { - return super.available(); - } - } - - /* Runnable */ - - @Override - public void run() - { - try (AutoLock lock = _contentLock.lock()) - { - super.run(); - } - } -} diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannel.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannel.java index 000e103232d..9b9b460fd78 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannel.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannel.java @@ -119,7 +119,14 @@ public abstract class HttpChannel implements Runnable, HttpOutput.Interceptor return _state.isSendError(); } - protected abstract HttpInput newHttpInput(HttpChannelState state); + private HttpInput newHttpInput(HttpChannelState state) + { + return new HttpInput(state); + } + + public abstract void produceContent(); + + public abstract void failContent(Throwable failure); protected HttpOutput newHttpOutput() { diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelOverHttp.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelOverHttp.java index 1f252fbce95..0dee629f952 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelOverHttp.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelOverHttp.java @@ -75,6 +75,18 @@ public class HttpChannelOverHttp extends HttpChannel implements HttpParser.Reque _httpConnection.getGenerator().setPersistent(false); } + @Override + public void produceContent() + { + ((HttpConnection)getEndPoint().getConnection()).parseAndFillForContent(); + } + + @Override + public void failContent(Throwable failure) + { + ((HttpConnection)getEndPoint().getConnection()).failContent(failure); + } + @Override public void badMessage(BadMessageException failure) { @@ -458,12 +470,6 @@ public class HttpChannelOverHttp extends HttpChannel implements HttpParser.Reque super.handleException(x); } - @Override - protected HttpInput newHttpInput(HttpChannelState state) - { - return new HttpInputOverHTTP(state); - } - /** *

Attempts to perform an HTTP/1.1 upgrade.

*

The upgrade looks up a {@link ConnectionFactory.Upgrading} from the connector diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelState.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelState.java index 3474a9dc271..6798ce18bfd 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelState.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelState.java @@ -1370,6 +1370,11 @@ public class HttpChannelState } break; + case IDLE: + case READY: + case REGISTER: + break; + default: throw new IllegalStateException(toStringLocked()); } diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnection.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnection.java index e5a2b1a7697..5d0cc2755f8 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnection.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnection.java @@ -319,7 +319,7 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http /** * Parse and fill data, looking for content */ - protected void parseAndFillForContent() + void parseAndFillForContent() { // parseRequestBuffer() must always be called after fillRequestBuffer() otherwise this method doesn't trigger EOF/earlyEOF // which breaks AsyncRequestReadTest.testPartialReadThenShutdown() @@ -333,6 +333,20 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http } } + void failContent(Throwable failure) + { + int filled = Integer.MAX_VALUE; + while (_parser.inContentState()) + { + // The parser is going generate and forward contents to the HttpInput + // so it's up to it to fail them individually. + parseRequestBuffer(); + if (filled <= 0 || _input.hasContent()) + break; + filled = fillRequestBuffer(); + } + } + private int fillRequestBuffer() { if (_contentBufferReferences.get() > 0) diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpInput.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpInput.java index 28db165796b..e9dceaf08f7 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpInput.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpInput.java @@ -20,61 +20,152 @@ package org.eclipse.jetty.server; import java.io.IOException; import java.nio.ByteBuffer; +import java.util.Objects; +import java.util.concurrent.Semaphore; +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; +import javax.servlet.ReadListener; import javax.servlet.ServletInputStream; +import org.eclipse.jetty.http.BadMessageException; +import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.io.EofException; import org.eclipse.jetty.server.handler.ContextHandler; import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.Callback; +import org.eclipse.jetty.util.component.Destroyable; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; /** - *

This would be an interface if ServletInputStream was an interface too.

*

While this class is-a Runnable, it should never be dispatched in it's own thread. It is a runnable only so that the calling thread can use {@link * ContextHandler#handle(Runnable)} to setup classloaders etc.

*/ -public abstract class HttpInput extends ServletInputStream implements Runnable +public class HttpInput extends ServletInputStream implements Runnable { + private static final Logger LOG = Log.getLogger(HttpInput.class); - public abstract void recycle(); + private final byte[] _oneByteBuffer = new byte[1]; + + private final HttpChannelState _channelState; + private final ContentProducer _contentProducer = new ContentProducer(); + // This semaphore is only used in blocking mode, and a standard lock with a condition variable + // cannot work here because there is a race condition between the _contentProducer.read() call + // and the blockForContent() call: content can be produced any time between these two calls so + // the call to unblock() done by the content-producing thread to wake up the user thread executing read() + // must 'remember' the unblock() call, such as if it happens before the thread executing read() reaches the + // blockForContent() method, it will not get stuck in it forever waiting for an unblock() call it missed. + private final Semaphore _semaphore = new Semaphore(0); + + private Eof _eof = Eof.NOT_YET; + private Throwable _error; + private ReadListener _readListener; + private long _firstByteTimeStamp = Long.MIN_VALUE; + + public HttpInput(HttpChannelState state) + { + _channelState = state; + } + + /* HttpInput */ + + public void recycle() + { + if (LOG.isDebugEnabled()) + LOG.debug("recycle"); + _contentProducer.recycle(); + _eof = Eof.NOT_YET; + _error = null; + _readListener = null; + _firstByteTimeStamp = Long.MIN_VALUE; + } /** * @return The current Interceptor, or null if none set */ - public abstract Interceptor getInterceptor(); + public Interceptor getInterceptor() + { + return _contentProducer.getInterceptor(); + } /** * Set the interceptor. * * @param interceptor The interceptor to use. */ - public abstract void setInterceptor(Interceptor interceptor); + public void setInterceptor(Interceptor interceptor) + { + _contentProducer.setInterceptor(interceptor); + } /** - * Set the {@link org.eclipse.jetty.server.HttpInput.Interceptor}, chaining it to the existing one if - * an {@link org.eclipse.jetty.server.HttpInput.Interceptor} is already set. + * Set the {@link Interceptor}, chaining it to the existing one if + * an {@link Interceptor} is already set. * - * @param interceptor the next {@link org.eclipse.jetty.server.HttpInput.Interceptor} in a chain + * @param interceptor the next {@link Interceptor} in a chain */ - public abstract void addInterceptor(Interceptor interceptor); + public void addInterceptor(Interceptor interceptor) + { + Interceptor currentInterceptor = _contentProducer.getInterceptor(); + if (currentInterceptor == null) + _contentProducer.setInterceptor(interceptor); + else + _contentProducer.setInterceptor(new ChainedInterceptor(currentInterceptor, interceptor)); + } /** * Called by channel when asynchronous IO needs to produce more content - * - * @throws IOException if unable to produce content */ - public abstract void asyncReadProduce() throws IOException; + public void asyncReadProduce() + { + if (LOG.isDebugEnabled()) + LOG.debug("asyncReadProduce {}", _contentProducer); + produceContent(); + } /** * Adds some content to this input stream. * * @param content the content to add */ - public abstract void addContent(Content content); + public void addContent(Content content) + { + if (LOG.isDebugEnabled()) + LOG.debug("addContent {} {}", content, _contentProducer); + if (_firstByteTimeStamp == Long.MIN_VALUE) + { + _firstByteTimeStamp = System.nanoTime(); + if (_firstByteTimeStamp == Long.MIN_VALUE) + _firstByteTimeStamp++; + } + _contentProducer.addContent(content); + if (isAsync() && _contentProducer.available(this::produceContent) > 0) + _channelState.onContentAdded(); + } - public abstract boolean hasContent(); + public boolean hasContent() + { + return _contentProducer.hasRawContent(); + } - public abstract void unblock(); + // There are 3 sources which can call this method in parallel: + // 1) HTTP2 read() that has a demand served on the app thread; + // 2) HTTP2 read() that has a demand served by a server thread; + // 3) onIdleTimeout called by a server thread; + // which means the semaphore can have up to 2 permits. + public void unblock() + { + if (LOG.isDebugEnabled()) + LOG.debug("signalling blocked thread to wake up"); + if (!isError() && !_eof.isEof() && _semaphore.availablePermits() > 1) + throw new AssertionError("Only one thread should call unblock and only if we are blocked"); + _semaphore.release(); + } - public abstract long getContentLength(); + public long getContentLength() + { + return _contentProducer.getRawContentArrived(); + } public long getContentReceived() { @@ -88,30 +179,623 @@ public abstract class HttpInput extends ServletInputStream implements Runnable * * @return true if content channel woken for read */ - public abstract boolean earlyEOF(); + public boolean earlyEOF() + { + if (LOG.isDebugEnabled()) + LOG.debug("received early EOF"); + _eof = Eof.EARLY_EOF; + if (isAsync()) + return _channelState.onContentAdded(); + unblock(); + return false; + } /** * This method should be called to signal that all the expected content arrived. * * @return true if content channel woken for read */ - public abstract boolean eof(); + public boolean eof() + { + if (LOG.isDebugEnabled()) + LOG.debug("received EOF"); + _eof = Eof.EOF; + if (isAsync()) + return _channelState.onContentAdded(); + unblock(); + return false; + } - public abstract boolean consumeAll(); + public boolean consumeAll() + { + if (LOG.isDebugEnabled()) + LOG.debug("consume all"); + _contentProducer.consumeTransformedContent(this::failContent, new IOException("Unconsumed content")); + if (_eof.isEof()) + _eof = Eof.CONSUMED_EOF; - public abstract boolean isError(); + if (isFinished()) + return !isError(); - public abstract boolean isAsync(); + _eof = Eof.EARLY_EOF; + return false; + } - public abstract boolean onIdleTimeout(Throwable x); + public boolean isError() + { + return _error != null; + } - public abstract boolean failed(Throwable x); + public boolean isAsync() + { + return _readListener != null; + } + + public boolean onIdleTimeout(Throwable x) + { + boolean neverDispatched = _channelState.isIdle(); + boolean waitingForContent = _contentProducer.available(this::produceContent) == 0 && !_eof.isEof(); + if ((waitingForContent || neverDispatched) && !isError()) + { + x.addSuppressed(new Throwable("HttpInput idle timeout")); + _error = x; + if (isAsync()) + return _channelState.onContentAdded(); + unblock(); + } + return false; + } + + public boolean failed(Throwable x) + { + if (LOG.isDebugEnabled()) + LOG.debug("failed " + x); + if (_error != null && _error != x) + _error.addSuppressed(x); + else + _error = x; + + if (isAsync()) + return _channelState.onContentAdded(); + unblock(); + return false; + } + + /* ServletInputStream */ @Override - public abstract int read(byte[] b, int off, int len) throws IOException; + public boolean isFinished() + { + boolean finished = !_contentProducer.hasRawContent() && _eof.isConsumed(); + if (LOG.isDebugEnabled()) + LOG.debug("isFinished? {}", finished); + return finished; + } @Override - public abstract int available() throws IOException; + public boolean isReady() + { + // calling _contentProducer.available() might change the _eof state, so the following test order matters + if (_contentProducer.available(this::produceContent) > 0 || _eof.isEof()) + { + if (LOG.isDebugEnabled()) + LOG.debug("isReady? true"); + return true; + } + if (LOG.isDebugEnabled()) + LOG.debug("isReady? false"); + _channelState.onReadUnready(); + return false; + } + + @Override + public void setReadListener(ReadListener readListener) + { + if (_readListener != null) + throw new IllegalStateException("ReadListener already set"); + _readListener = Objects.requireNonNull(readListener); + //illegal if async not started + if (!_channelState.isAsyncStarted()) + throw new IllegalStateException("Async not started"); + + if (LOG.isDebugEnabled()) + LOG.debug("setReadListener error=" + _error + " eof=" + _eof + " " + _contentProducer); + boolean woken; + if (isError()) + { + woken = _channelState.onReadReady(); + } + else + { + if (_contentProducer.available(this::produceContent) > 0) + { + woken = _channelState.onReadReady(); + } + else if (_eof.isEof()) + { + woken = _channelState.onReadEof(); + } + else + { + _channelState.onReadUnready(); + woken = false; + } + } + + if (LOG.isDebugEnabled()) + LOG.debug("setReadListener woken=" + woken); + if (woken) + scheduleReadListenerNotification(); + } + + private void scheduleReadListenerNotification() + { + HttpChannel channel = _channelState.getHttpChannel(); + channel.execute(channel); + } + + @Override + public int read() throws IOException + { + int read = read(_oneByteBuffer, 0, 1); + if (read == 0) + throw new IOException("unready read=0"); + return read < 0 ? -1 : _oneByteBuffer[0] & 0xFF; + } + + @Override + public int read(byte[] b, int off, int len) throws IOException + { + // Calculate minimum request rate for DOS protection + long minRequestDataRate = _channelState.getHttpChannel().getHttpConfiguration().getMinRequestDataRate(); + if (minRequestDataRate > 0 && _firstByteTimeStamp != Long.MIN_VALUE) + { + long period = System.nanoTime() - _firstByteTimeStamp; + if (period > 0) + { + long minimumData = minRequestDataRate * TimeUnit.NANOSECONDS.toMillis(period) / TimeUnit.SECONDS.toMillis(1); + if (_contentProducer.getRawContentArrived() < minimumData) + { + BadMessageException bad = new BadMessageException(HttpStatus.REQUEST_TIMEOUT_408, + String.format("Request content data rate < %d B/s", minRequestDataRate)); + if (_channelState.isResponseCommitted()) + _channelState.getHttpChannel().abort(bad); + throw bad; + } + } + } + + while (true) + { + // The semaphore's permits must be drained before we call read() because: + // 1) _contentProducer.read() may call unblock() which enqueues a permit even if the content was produced + // by the exact thread that called HttpInput.read(), hence leaving around an unconsumed permit that would + // be consumed the next time HttpInput.read() is called, mistakenly believing that content was produced. + // 2) HTTP2 demand served asynchronously does call unblock which does enqueue a permit in the semaphore; + // this permit would then be mistakenly consumed by the next call to blockForContent() once all the produced + // content got consumed. + if (!isAsync()) + _semaphore.drainPermits(); + int read = _contentProducer.read(this::produceContent, b, off, len); + if (LOG.isDebugEnabled()) + LOG.debug("read produced {} byte(s)", read); + if (read > 0) + return read; + + if (LOG.isDebugEnabled()) + LOG.debug("read error = " + _error); + if (_error != null) + throw new IOException(_error); + + if (LOG.isDebugEnabled()) + LOG.debug("read EOF = {}", _eof); + if (_eof.isEarly()) + throw new EofException("Early EOF"); + + if (LOG.isDebugEnabled()) + LOG.debug("read async = {}", isAsync()); + if (!isAsync()) + { + if (_eof.isEof()) + { + _eof = Eof.CONSUMED_EOF; + if (LOG.isDebugEnabled()) + LOG.debug("read on EOF, switching to CONSUMED_EOF and returning"); + return -1; + } + if (LOG.isDebugEnabled()) + LOG.debug("read blocked"); + blockForContent(); + if (LOG.isDebugEnabled()) + LOG.debug("read unblocked"); + } + else + { + if (_eof.isEof()) + { + _eof = Eof.CONSUMED_EOF; + boolean wasInAsyncWait = _channelState.onReadEof(); + if (LOG.isDebugEnabled()) + LOG.debug("async read on EOF (was in async wait? {}), switching to CONSUMED_EOF and returning", wasInAsyncWait); + if (wasInAsyncWait) + scheduleReadListenerNotification(); + return -1; + } + else + { + //TODO returning 0 breaks the InputStream contract. Shouldn't IOException be thrown instead? + _channelState.getHttpChannel().onAsyncWaitForContent(); // switches on fill interested + return 0; + } + } + } + } + + @Override + public int available() + { + int available = _contentProducer.available(this::produceContent); + if (LOG.isDebugEnabled()) + LOG.debug("available = {}", available); + return available; + } + + private void blockForContent() + { + try + { + _channelState.getHttpChannel().onBlockWaitForContent(); // switches on fill interested + if (LOG.isDebugEnabled()) + LOG.debug("waiting for signal to wake up"); + _semaphore.acquire(); + if (LOG.isDebugEnabled()) + LOG.debug("signalled to wake up"); + } + catch (Throwable x) + { + _channelState.getHttpChannel().onBlockWaitForContentFailure(x); + } + } + + /* Runnable */ + + /* + *

While this class is-a Runnable, it should never be dispatched in it's own thread. It is a runnable only so that the calling thread can use {@link + * ContextHandler#handle(Runnable)} to setup classloaders etc.

+ */ + @Override + public void run() + { + if (!_contentProducer.hasRawContent()) + { + if (LOG.isDebugEnabled()) + LOG.debug("running has no raw content; error: {}, EOF = {}", _error, _eof); + if (_error != null || _eof.isEarly()) + { + // TODO is this necessary to add here? + _channelState.getHttpChannel().getResponse().getHttpFields().add(HttpConnection.CONNECTION_CLOSE); + if (_error != null) + _readListener.onError(_error); + else + _readListener.onError(new EofException("Early EOF")); + } + else if (_eof.isEof()) + { + try + { + _readListener.onAllDataRead(); + } + catch (Throwable x) + { + if (LOG.isDebugEnabled()) + LOG.debug("running failed onAllDataRead", x); + _readListener.onError(x); + } + } + // else: !hasContent() && !error && !EOF -> no-op + } + else + { + if (LOG.isDebugEnabled()) + LOG.debug("running has raw content"); + try + { + _readListener.onDataAvailable(); + } + catch (Throwable x) + { + if (LOG.isDebugEnabled()) + LOG.debug("running failed onDataAvailable", x); + _readListener.onError(x); + } + } + } + + private void produceContent() + { + if (LOG.isDebugEnabled()) + LOG.debug("produceContent {}", _contentProducer); + _channelState.getHttpChannel().produceContent(); + } + + private void failContent(Throwable failure) + { + if (LOG.isDebugEnabled()) + LOG.debug("failContent {} - " + failure, _contentProducer); + _channelState.getHttpChannel().failContent(failure); + } + + private enum Eof + { + NOT_YET(false, false, false), + EOF(true, false, false), + CONSUMED_EOF(true, true, false), + EARLY_EOF(true, false, true), + ; + + private final boolean _eof; + private final boolean _consumed; + private final boolean _early; + + Eof(boolean eof, boolean consumed, boolean early) + { + _eof = eof; + _consumed = consumed; + _early = early; + } + + boolean isEof() + { + return _eof; + } + + boolean isConsumed() + { + return _consumed; + } + + boolean isEarly() + { + return _early; + } + } + + // All methods of this class have to be synchronized because a HTTP2 reset can call consumeTransformedContent() + // while nextNonEmptyContent() is executing, hence all accesses to _rawContent and _transformedContent must be + // mutually excluded. + // TODO: maybe the locking could be more fine grained, by only protecting the if (null|!null) blocks? + private static class ContentProducer + { + // Note: _rawContent can never be null for as long as _transformedContent is not null. + private Content _rawContent; + private Content _transformedContent; + private long _rawContentArrived; + private Interceptor _interceptor; + private Throwable _consumeFailure; + + void recycle() + { + synchronized (this) + { + if (LOG.isDebugEnabled()) + LOG.debug("recycle {}", this); + if (_transformedContent == _rawContent) + _transformedContent = null; + if (_transformedContent != null) + _transformedContent.failed(null); + _transformedContent = null; + if (_rawContent != null) + _rawContent.failed(null); + _rawContent = null; + _rawContentArrived = 0L; + if (_interceptor instanceof Destroyable) + ((Destroyable)_interceptor).destroy(); + _interceptor = null; + _consumeFailure = null; + } + } + + long getRawContentArrived() + { + synchronized (this) + { + return _rawContentArrived; + } + } + + boolean hasRawContent() + { + synchronized (this) + { + return _rawContent != null; + } + } + + Interceptor getInterceptor() + { + synchronized (this) + { + return _interceptor; + } + } + + void setInterceptor(Interceptor interceptor) + { + synchronized (this) + { + this._interceptor = interceptor; + } + } + + void addContent(Content content) + { + synchronized (this) + { + if (LOG.isDebugEnabled()) + LOG.debug("{} addContent {}", this, content); + if (content == null) + throw new AssertionError("Cannot add null content"); + if (_consumeFailure != null) + { + content.failed(_consumeFailure); + return; + } + if (_rawContent != null) + throw new AssertionError("Cannot add new content while current one hasn't been processed"); + + _rawContent = content; + _rawContentArrived += content.remaining(); + } + } + + void consumeTransformedContent(Consumer failRawContent, Throwable failure) + { + synchronized (this) + { + if (LOG.isDebugEnabled()) + LOG.debug("{} consumeTransformedContent", this); + // start by depleting the current _transformedContent + if (_transformedContent != null) + { + _transformedContent.skip(_transformedContent.remaining()); + if (_transformedContent != _rawContent) + _transformedContent.failed(failure); + _transformedContent = null; + } + + // don't bother transforming content, directly deplete the raw one + if (_rawContent != null) + { + _rawContent.skip(_rawContent.remaining()); + _rawContent.failed(failure); + _rawContent = null; + } + + // fail whatever other content the producer may have + _consumeFailure = failure; + failRawContent.accept(failure); + } + } + + int available(Runnable rawContentProducer) + { + synchronized (this) + { + Content content = nextNonEmptyContent(rawContentProducer); + return content == null ? 0 : content.remaining(); + } + } + + int read(Runnable rawContentProducer, byte[] b, int off, int len) + { + synchronized (this) + { + if (LOG.isDebugEnabled()) + LOG.debug("{} read", this); + Content content = nextNonEmptyContent(rawContentProducer); + return content == null ? 0 : content.get(b, off, len); + } + } + + private Content nextNonEmptyContent(Runnable rawContentProducer) + { + if (_rawContent == null) + { + rawContentProducer.run(); + if (_rawContent == null) + return null; + } + + if (_transformedContent != null && _transformedContent.isEmpty()) + { + if (_transformedContent != _rawContent) + _transformedContent.succeeded(); + _transformedContent = null; + } + + while (_transformedContent == null) + { + if (_interceptor != null) + _transformedContent = _interceptor.readFrom(_rawContent); + else + _transformedContent = _rawContent; + + if (_transformedContent != null && _transformedContent.isEmpty()) + { + if (_transformedContent != _rawContent) + _transformedContent.succeeded(); + _transformedContent = null; + } + + if (_transformedContent == null) + { + if (_rawContent.isEmpty()) + { + _rawContent.succeeded(); + _rawContent = null; + rawContentProducer.run(); + if (_rawContent == null) + return null; + } + } + } + + return _transformedContent; + } + + @Override + public String toString() + { + return getClass().getSimpleName() + "[i=" + _interceptor + ",b=" + _rawContentArrived + + ",r=" + _rawContent + ",t=" + _transformedContent + "]"; + } + } + + /** + * An {@link Interceptor} that chains two other {@link Interceptor}s together. + * The {@link Interceptor#readFrom(Content)} calls the previous {@link Interceptor}'s + * {@link Interceptor#readFrom(Content)} and then passes any {@link Content} returned + * to the next {@link Interceptor}. + */ + private static class ChainedInterceptor implements Interceptor, Destroyable + { + private final Interceptor _prev; + private final Interceptor _next; + + ChainedInterceptor(Interceptor prev, Interceptor next) + { + _prev = prev; + _next = next; + } + + Interceptor getPrev() + { + return _prev; + } + + Interceptor getNext() + { + return _next; + } + + @Override + public Content readFrom(Content content) + { + Content c = getPrev().readFrom(content); + if (c == null) + return null; + return getNext().readFrom(c); + } + + @Override + public void destroy() + { + if (_prev instanceof Destroyable) + ((Destroyable)_prev).destroy(); + if (_next instanceof Destroyable) + ((Destroyable)_next).destroy(); + } + } public interface Interceptor { diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpInputOverHTTP.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpInputOverHTTP.java deleted file mode 100644 index f7cd9fdca87..00000000000 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpInputOverHTTP.java +++ /dev/null @@ -1,53 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. -// -// This program and the accompanying materials are made available under -// the terms of the Eclipse Public License 2.0 which is available at -// https://www.eclipse.org/legal/epl-2.0 -// -// This Source Code may also be made available under the following -// Secondary Licenses when the conditions for such availability set -// forth in the Eclipse Public License, v. 2.0 are satisfied: -// the Apache License v2.0 which is available at -// https://www.apache.org/licenses/LICENSE-2.0 -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// ======================================================================== -// - -package org.eclipse.jetty.server; - -// tests used: RequestTest, PartialRFC2616Test, AsyncRequestReadTest, AsyncIOServletTest, GzipHandlerTest -public class HttpInputOverHTTP extends AbstractHttpInput -{ - public HttpInputOverHTTP(HttpChannelState state) - { - super(state); - } - - @Override - public void addContent(Content content) - { - super.addContent(content); - } - - @Override - protected void produceRawContent() - { - ((HttpConnection)_channelState.getHttpChannel().getEndPoint().getConnection()).parseAndFillForContent(); - } - - @Override - protected void failRawContent(Throwable failure) - { - while (true) - { - if (!_contentProducer.hasRawContent()) - _contentProducer.produceRawContent(); - if (!_contentProducer.hasRawContent()) - break; - _contentProducer.consumeRawContent(); - } - } -} diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/HttpWriterTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/HttpWriterTest.java index 36eb48fd854..d584037a1c2 100644 --- a/jetty-server/src/test/java/org/eclipse/jetty/server/HttpWriterTest.java +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/HttpWriterTest.java @@ -49,9 +49,13 @@ public class HttpWriterTest HttpChannel channel = new HttpChannel(new MockConnector(), new HttpConfiguration(), null, null) { @Override - protected HttpInput newHttpInput(HttpChannelState state) + public void produceContent() + { + } + + @Override + public void failContent(Throwable failure) { - return null; } @Override diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/ResponseTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/ResponseTest.java index 41829dc1f13..fb17e86da8e 100644 --- a/jetty-server/src/test/java/org/eclipse/jetty/server/ResponseTest.java +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/ResponseTest.java @@ -178,9 +178,13 @@ public class ResponseTest }) { @Override - protected HttpInput newHttpInput(HttpChannelState state) + public void produceContent() + { + } + + @Override + public void failContent(Throwable failure) { - return new HttpInputOverHTTP(state); } }; } diff --git a/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/AsyncIOServletTest.java b/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/AsyncIOServletTest.java index f2239a533b9..cbc3bd800a6 100644 --- a/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/AsyncIOServletTest.java +++ b/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/AsyncIOServletTest.java @@ -36,6 +36,7 @@ import java.util.concurrent.LinkedBlockingDeque; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; +import java.util.zip.GZIPOutputStream; import javax.servlet.AsyncContext; import javax.servlet.DispatcherType; import javax.servlet.ReadListener; @@ -71,6 +72,7 @@ import org.eclipse.jetty.server.HttpInput.Content; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.handler.ContextHandler; import org.eclipse.jetty.server.handler.ContextHandler.Context; +import org.eclipse.jetty.server.handler.gzip.GzipHttpInputInterceptor; import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.FuturePromise; import org.hamcrest.Matchers; @@ -1204,8 +1206,8 @@ public class AsyncIOServletTest extends AbstractTest + { + ByteBuffer byteBuffer = content.getByteBuffer(); + byte[] bytes = new byte[2]; + bytes[1] = byteBuffer.get(); + bytes[0] = byteBuffer.get(); + return new Content(wrap(bytes)); + }); + + AsyncContext asyncContext = request.startAsync(); + ServletInputStream input = request.getInputStream(); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + + input.setReadListener(new ReadListener() + { + @Override + public void onDataAvailable() throws IOException + { + while (input.isReady()) + { + int b = input.read(); + if (b > 0) + { + // System.err.printf("0x%2x %s %n", b, Character.isISOControl(b)?"?":(""+(char)b)); + out.write(b); + } + else if (b < 0) + return; + } + } + + @Override + public void onAllDataRead() throws IOException + { + response.getOutputStream().write(out.toByteArray()); + asyncContext.complete(); + } + + @Override + public void onError(Throwable x) + { + } + }); + } + }); + + DeferredContentProvider contentProvider = new DeferredContentProvider(); + CountDownLatch clientLatch = new CountDownLatch(1); + + String expected = + "0S" + + "1S" + + "2S" + + "3S" + + "4S" + + "5S" + + "6S"; + + scenario.client.newRequest(scenario.newURI()) + .method(HttpMethod.POST) + .path(scenario.servletPath) + .content(contentProvider) + .send(new BufferingResponseListener() + { + @Override + public void onComplete(Result result) + { + if (result.isSucceeded()) + { + Response response = result.getResponse(); + assertThat(response.getStatus(), Matchers.equalTo(HttpStatus.OK_200)); + assertThat(getContentAsString(), Matchers.equalTo(expected)); + clientLatch.countDown(); + } + } + }); + + contentProvider.offer(gzipToBuffer("S0")); + contentProvider.flush(); + contentProvider.offer(gzipToBuffer("S1")); + contentProvider.flush(); + contentProvider.offer(gzipToBuffer("S2")); + contentProvider.flush(); + contentProvider.offer(gzipToBuffer("S3")); + contentProvider.flush(); + contentProvider.offer(gzipToBuffer("S4")); + contentProvider.flush(); + contentProvider.offer(gzipToBuffer("S5")); + contentProvider.flush(); + contentProvider.offer(gzipToBuffer("S6")); + contentProvider.close(); + + assertTrue(clientLatch.await(10, TimeUnit.SECONDS)); + } + + @ParameterizedTest + @ArgumentsSource(TransportProvider.class) + public void testAsyncInterceptedTwiceWithNulls(Transport transport) throws Exception + { + init(transport); + scenario.start(new HttpServlet() + { + @Override + protected void service(HttpServletRequest request, HttpServletResponse response) throws IOException + { + System.err.println("Service " + request); + + final HttpInput httpInput = ((Request)request).getHttpInput(); + httpInput.addInterceptor(content -> + { + if (content.isEmpty()) + return content; + + // skip contents with odd numbers + ByteBuffer duplicate = content.getByteBuffer().duplicate(); + duplicate.get(); + byte integer = duplicate.get(); + int idx = Character.getNumericValue(integer); + Content contentCopy = new Content(content.getByteBuffer().duplicate()); + content.skip(content.remaining()); + if (idx % 2 == 0) + return contentCopy; + return null; + }); + httpInput.addInterceptor(content -> + { + if (content.isEmpty()) + return content; + + // reverse the bytes + ByteBuffer byteBuffer = content.getByteBuffer(); + byte[] bytes = new byte[2]; + bytes[1] = byteBuffer.get(); + bytes[0] = byteBuffer.get(); + return new Content(wrap(bytes)); + }); + + AsyncContext asyncContext = request.startAsync(); + ServletInputStream input = request.getInputStream(); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + + input.setReadListener(new ReadListener() + { + @Override + public void onDataAvailable() throws IOException + { + while (input.isReady()) + { + int b = input.read(); + if (b > 0) + { + // System.err.printf("0x%2x %s %n", b, Character.isISOControl(b)?"?":(""+(char)b)); + out.write(b); + } + else if (b < 0) + return; + } + } + + @Override + public void onAllDataRead() throws IOException + { + response.getOutputStream().write(out.toByteArray()); + asyncContext.complete(); + } + + @Override + public void onError(Throwable x) + { + } + }); + } + }); + + DeferredContentProvider contentProvider = new DeferredContentProvider(); + CountDownLatch clientLatch = new CountDownLatch(1); + + String expected = + "0S" + + "2S" + + "4S" + + "6S"; + + scenario.client.newRequest(scenario.newURI()) + .method(HttpMethod.POST) + .path(scenario.servletPath) + .content(contentProvider) + .send(new BufferingResponseListener() + { + @Override + public void onComplete(Result result) + { + if (result.isSucceeded()) + { + Response response = result.getResponse(); + assertThat(response.getStatus(), Matchers.equalTo(HttpStatus.OK_200)); + assertThat(getContentAsString(), Matchers.equalTo(expected)); + clientLatch.countDown(); + } + } + }); + + contentProvider.offer(BufferUtil.toBuffer("S0")); + contentProvider.flush(); + contentProvider.offer(BufferUtil.toBuffer("S1")); + contentProvider.flush(); + contentProvider.offer(BufferUtil.toBuffer("S2")); + contentProvider.flush(); + contentProvider.offer(BufferUtil.toBuffer("S3")); + contentProvider.flush(); + contentProvider.offer(BufferUtil.toBuffer("S4")); + contentProvider.flush(); + contentProvider.offer(BufferUtil.toBuffer("S5")); + contentProvider.flush(); + contentProvider.offer(BufferUtil.toBuffer("S6")); + contentProvider.close(); + + assertTrue(clientLatch.await(10, TimeUnit.SECONDS)); + } + + private ByteBuffer gzipToBuffer(String s) throws IOException + { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + GZIPOutputStream gzos = new GZIPOutputStream(baos); + gzos.write(s.getBytes(StandardCharsets.ISO_8859_1)); + gzos.close(); + return BufferUtil.toBuffer(baos.toByteArray()); + } + @ParameterizedTest @ArgumentsSource(TransportProvider.class) public void testWriteListenerFromOtherThread(Transport transport) throws Exception From 814dc69803f6c218b85c4f98306068e81be83eaa Mon Sep 17 00:00:00 2001 From: Ludovic Orban Date: Fri, 20 Mar 2020 09:48:11 +0100 Subject: [PATCH 19/25] Get rid of addContent() by making produceContent() return Content instead. Make EOF and errors be special content. Transition to a much simplified FSM by using the needContent() / produceContent() model. Implement blocking on top of async, this way there is only one FSM. (Milestone 6) Signed-off-by: Ludovic Orban --- .../jetty/client/ssl/SslBytesServerTest.java | 28 +- .../fcgi/server/HttpChannelOverFCGI.java | 128 ++- .../fcgi/server/ServerFCGIConnection.java | 2 +- .../org/eclipse/jetty/http2/HTTP2Stream.java | 26 +- .../jetty/http2/HTTP2StreamEndPoint.java | 11 +- .../java/org/eclipse/jetty/http2/IStream.java | 10 +- .../http2/server/ContentDemander_state.puml | 26 + .../http2/server/HttpChannelOverHTTP2.java | 485 +++++++--- .../SpnegoAuthenticatorTest.java | 49 +- .../jetty/server/AsyncContentProducer.java | 354 +++++++ .../jetty/server/BlockingContentProducer.java | 164 ++++ .../eclipse/jetty/server/ContentProducer.java | 141 +++ .../org/eclipse/jetty/server/HttpChannel.java | 80 +- .../jetty/server/HttpChannelOverHttp.java | 198 +++- .../jetty/server/HttpChannelState.java | 214 ++--- .../jetty/server/HttpChannelState_input.puml | 84 ++ .../eclipse/jetty/server/HttpConnection.java | 79 +- .../org/eclipse/jetty/server/HttpInput.java | 901 +++++++----------- .../eclipse/jetty/server/HttpInputState.puml | 16 + .../eclipse/jetty/server/HttpInput_async.puml | 114 +++ .../jetty/server/HttpInput_blocking.puml | 64 ++ .../org/eclipse/jetty/server/Request.java | 2 +- .../server/AsyncContentProducerTest.java | 340 +++++++ .../server/BlockingContentProducerTest.java | 320 +++++++ .../eclipse/jetty/server/HttpWriterTest.java | 24 +- .../eclipse/jetty/server/ResponseTest.java | 24 +- .../jetty/http/client/AsyncIOServletTest.java | 133 ++- 27 files changed, 2997 insertions(+), 1020 deletions(-) create mode 100644 jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/ContentDemander_state.puml create mode 100644 jetty-server/src/main/java/org/eclipse/jetty/server/AsyncContentProducer.java create mode 100644 jetty-server/src/main/java/org/eclipse/jetty/server/BlockingContentProducer.java create mode 100644 jetty-server/src/main/java/org/eclipse/jetty/server/ContentProducer.java create mode 100644 jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelState_input.puml create mode 100644 jetty-server/src/main/java/org/eclipse/jetty/server/HttpInputState.puml create mode 100644 jetty-server/src/main/java/org/eclipse/jetty/server/HttpInput_async.puml create mode 100644 jetty-server/src/main/java/org/eclipse/jetty/server/HttpInput_blocking.puml create mode 100644 jetty-server/src/test/java/org/eclipse/jetty/server/AsyncContentProducerTest.java create mode 100644 jetty-server/src/test/java/org/eclipse/jetty/server/BlockingContentProducerTest.java diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/ssl/SslBytesServerTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/ssl/SslBytesServerTest.java index 39089a1e801..bd476883a85 100644 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/ssl/SslBytesServerTest.java +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/ssl/SslBytesServerTest.java @@ -1377,9 +1377,12 @@ public class SslBytesServerTest extends SslBytesTest // Check that we did not spin TimeUnit.MILLISECONDS.sleep(500); - assertThat(sslFills.get(), Matchers.lessThan(100)); + // The new HttpInput impl tends to call fill and parse more often than the previous one + // b/c HttpChannel.needContent() does a fill and parse before doing a fill interested; + // this runs the parser an goes to the OS more often but requires less rescheduling. + assertThat(sslFills.get(), Matchers.lessThan(150)); assertThat(sslFlushes.get(), Matchers.lessThan(50)); - assertThat(httpParses.get(), Matchers.lessThan(100)); + assertThat(httpParses.get(), Matchers.lessThan(150)); assertNull(request.get(5, TimeUnit.SECONDS)); @@ -1399,9 +1402,12 @@ public class SslBytesServerTest extends SslBytesTest // Check that we did not spin TimeUnit.MILLISECONDS.sleep(500); - assertThat(sslFills.get(), Matchers.lessThan(100)); + // The new HttpInput impl tends to call fill and parse more often than the previous one + // b/c HttpChannel.needContent() does a fill and parse before doing a fill interested; + // this runs the parser an goes to the OS more often but requires less rescheduling. + assertThat(sslFills.get(), Matchers.lessThan(150)); assertThat(sslFlushes.get(), Matchers.lessThan(50)); - assertThat(httpParses.get(), Matchers.lessThan(100)); + assertThat(httpParses.get(), Matchers.lessThan(150)); closeClient(client); } @@ -1596,9 +1602,12 @@ public class SslBytesServerTest extends SslBytesTest // Check that we did not spin TimeUnit.MILLISECONDS.sleep(500); - assertThat(sslFills.get(), Matchers.lessThan(50)); + // The new HttpInput impl tends to call fill and parse more often than the previous one + // b/c HttpChannel.needContent() does a fill and parse before doing a fill interested; + // this runs the parser and goes to the OS more often but requires less rescheduling. + assertThat(sslFills.get(), Matchers.lessThan(70)); assertThat(sslFlushes.get(), Matchers.lessThan(20)); - assertThat(httpParses.get(), Matchers.lessThan(50)); + assertThat(httpParses.get(), Matchers.lessThan(70)); closeClient(client); } @@ -1743,9 +1752,12 @@ public class SslBytesServerTest extends SslBytesTest // Check that we did not spin TimeUnit.MILLISECONDS.sleep(500); - assertThat(sslFills.get(), Matchers.lessThan(50)); + // The new HttpInput impl tends to call fill and parse more often than the previous one + // b/c HttpChannel.needContent() does a fill and parse before doing a fill interested; + // this runs the parser and goes to the OS more often but requires less rescheduling. + assertThat(sslFills.get(), Matchers.lessThan(80)); assertThat(sslFlushes.get(), Matchers.lessThan(20)); - assertThat(httpParses.get(), Matchers.lessThan(100)); + assertThat(httpParses.get(), Matchers.lessThan(120)); closeClient(client); } diff --git a/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/HttpChannelOverFCGI.java b/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/HttpChannelOverFCGI.java index ba279a9aabf..5759121ca55 100644 --- a/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/HttpChannelOverFCGI.java +++ b/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/HttpChannelOverFCGI.java @@ -41,6 +41,7 @@ import org.eclipse.jetty.server.HttpConfiguration; import org.eclipse.jetty.server.HttpInput; import org.eclipse.jetty.server.HttpTransport; import org.eclipse.jetty.util.StringUtil; +import org.eclipse.jetty.util.thread.AutoLock; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -49,7 +50,8 @@ public class HttpChannelOverFCGI extends HttpChannel private static final Logger LOG = LoggerFactory.getLogger(HttpChannelOverFCGI.class); private final Queue _contentQueue = new LinkedList<>(); - private Throwable _contentFailure; + private final AutoLock _lock = new AutoLock(); + private HttpInput.Content _specialContent; private final HttpFields.Mutable fields = HttpFields.build(); private final Dispatcher dispatcher; private String method; @@ -64,49 +66,99 @@ public class HttpChannelOverFCGI extends HttpChannel this.dispatcher = new Dispatcher(connector.getServer().getThreadPool(), this); } - void enqueueContent(HttpInput.Content content) + @Override + public boolean onContent(HttpInput.Content content) { + boolean b = super.onContent(content); + Throwable failure; - synchronized (_contentQueue) + try (AutoLock l = _lock.lock()) { - failure = _contentFailure; + failure = _specialContent == null ? null : _specialContent.getError(); if (failure == null) _contentQueue.offer(content); } if (failure != null) content.failed(failure); + + return b; } @Override - public void produceContent() + public boolean needContent() + { + try (AutoLock l = _lock.lock()) + { + boolean hasContent = _specialContent != null || !_contentQueue.isEmpty(); + if (LOG.isDebugEnabled()) + LOG.debug("needContent has content? {}", hasContent); + return hasContent; + } + } + + @Override + public HttpInput.Content produceContent() { HttpInput.Content content; - synchronized (_contentQueue) + try (AutoLock l = _lock.lock()) { - if (_contentFailure != null) - content = null; - else - content = _contentQueue.poll(); + content = _contentQueue.poll(); + if (content == null) + content = _specialContent; } - if (content != null) - onContent(content); + if (LOG.isDebugEnabled()) + LOG.debug("produceContent has produced {}", content); + return content; } @Override - public void failContent(Throwable failure) + public boolean failAllContent(Throwable failure) { + if (LOG.isDebugEnabled()) + LOG.debug("failing all content with {}", (Object)failure); List copy; - synchronized (_contentQueue) + try (AutoLock l = _lock.lock()) { - if (_contentFailure == null) - _contentFailure = failure; - else if (_contentFailure != failure) - _contentFailure.addSuppressed(failure); - copy = new ArrayList<>(_contentQueue); _contentQueue.clear(); } - copy.forEach(content -> content.failed(failure)); + copy.forEach(c -> c.failed(failure)); + HttpInput.Content lastContent = copy.isEmpty() ? null : copy.get(copy.size() - 1); + boolean atEof = lastContent != null && lastContent.isEof(); + if (LOG.isDebugEnabled()) + LOG.debug("failed all content, EOF = {}", atEof); + return atEof; + } + + @Override + public boolean failed(Throwable x) + { + if (LOG.isDebugEnabled()) + LOG.debug("failed " + x); + + try (AutoLock l = _lock.lock()) + { + Throwable error = _specialContent == null ? null : _specialContent.getError(); + + if (error != null && error != x) + error.addSuppressed(x); + else + _specialContent = new HttpInput.ErrorContent(x); + } + + return getRequest().getHttpInput().onContentProducible(); + } + + @Override + protected boolean eof() + { + if (LOG.isDebugEnabled()) + LOG.debug("received EOF"); + try (AutoLock l = _lock.lock()) + { + _specialContent = new HttpInput.EofContent(); + } + return getRequest().getHttpInput().onContentProducible(); } protected void header(HttpField field) @@ -179,12 +231,46 @@ public class HttpChannelOverFCGI extends HttpChannel public boolean onIdleTimeout(Throwable timeout) { - boolean handle = getRequest().getHttpInput().onIdleTimeout(timeout); + boolean handle = doOnIdleTimeout(timeout); if (handle) execute(this); return !handle; } + private boolean doOnIdleTimeout(Throwable x) + { + boolean neverDispatched = getState().isIdle(); + boolean waitingForContent; + HttpInput.Content specialContent; + try (AutoLock l = _lock.lock()) + { + waitingForContent = _contentQueue.isEmpty() || _contentQueue.peek().remaining() == 0; + specialContent = _specialContent; + } + if ((waitingForContent || neverDispatched) && specialContent == null) + { + x.addSuppressed(new Throwable("HttpInput idle timeout")); + try (AutoLock l = _lock.lock()) + { + _specialContent = new HttpInput.ErrorContent(x); + } + return getRequest().getHttpInput().onContentProducible(); + } + return false; + } + + @Override + public void recycle() + { + try (AutoLock l = _lock.lock()) + { + if (!_contentQueue.isEmpty()) + throw new AssertionError("unconsumed content: " + _contentQueue); + _specialContent = null; + } + super.recycle(); + } + private static class Dispatcher implements Runnable { private final AtomicReference state = new AtomicReference<>(State.IDLE); diff --git a/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/ServerFCGIConnection.java b/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/ServerFCGIConnection.java index 820dfc3e3e8..27b1b9ba465 100644 --- a/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/ServerFCGIConnection.java +++ b/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/ServerFCGIConnection.java @@ -197,7 +197,7 @@ public class ServerFCGIConnection extends AbstractConnection { ByteBuffer copy = ByteBuffer.allocate(buffer.remaining()); copy.put(buffer).flip(); - channel.enqueueContent(new HttpInput.Content(copy)); + channel.onContent(new HttpInput.Content(copy)); } return false; } diff --git a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Stream.java b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Stream.java index a24030c266a..7040c82407a 100644 --- a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Stream.java +++ b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Stream.java @@ -22,6 +22,7 @@ import java.io.EOFException; import java.io.IOException; import java.nio.channels.WritePendingException; import java.util.ArrayDeque; +import java.util.ArrayList; import java.util.List; import java.util.Queue; import java.util.concurrent.ConcurrentHashMap; @@ -77,7 +78,6 @@ public class HTTP2Stream extends IdleTimeout implements IStream, Callback, Dumpa private Listener listener; private long dataLength; private long dataDemand; - private Throwable failure; private boolean dataInitial; private boolean dataProcess; @@ -237,20 +237,18 @@ public class HTTP2Stream extends IdleTimeout implements IStream, Callback, Dumpa } @Override - public void fail(Throwable x) + public boolean failAllData(Throwable x) { + List copy; try (AutoLock l = lock.lock()) { dataDemand = 0; - failure = x; - while (true) - { - DataEntry dataEntry = dataQueue.poll(); - if (dataEntry == null) - break; - dataEntry.callback.failed(x); - } + copy = new ArrayList<>(dataQueue); + dataQueue.clear(); } + copy.forEach(dataEntry -> dataEntry.callback.failed(x)); + DataEntry lastDataEntry = copy.isEmpty() ? null : copy.get(copy.size() - 1); + return lastDataEntry != null && lastDataEntry.frame.isEndStream(); } public boolean isLocallyClosed() @@ -418,12 +416,6 @@ public class HTTP2Stream extends IdleTimeout implements IStream, Callback, Dumpa DataEntry entry = new DataEntry(frame, callback); try (AutoLock l = lock.lock()) { - if (failure != null) - { - // stream has been failed - callback.failed(failure); - return; - } dataQueue.offer(entry); initial = dataInitial; if (initial) @@ -463,8 +455,6 @@ public class HTTP2Stream extends IdleTimeout implements IStream, Callback, Dumpa boolean proceed = false; try (AutoLock l = lock.lock()) { - if (failure != null) - return; // stream has been failed demand = dataDemand = MathUtils.cappedAdd(dataDemand, n); if (!dataProcess) dataProcess = proceed = !dataQueue.isEmpty(); diff --git a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2StreamEndPoint.java b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2StreamEndPoint.java index 08e3fd78317..3f6fefc937e 100644 --- a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2StreamEndPoint.java +++ b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2StreamEndPoint.java @@ -43,7 +43,6 @@ import org.slf4j.LoggerFactory; public abstract class HTTP2StreamEndPoint implements EndPoint { private static final Logger LOG = LoggerFactory.getLogger(HTTP2StreamEndPoint.class); - private static final Throwable EOF = new Throwable(); private final AutoLock lock = new AutoLock(); private final Deque dataQueue = new ArrayDeque<>(); @@ -217,6 +216,9 @@ public abstract class HTTP2StreamEndPoint implements EndPoint else { entry.succeed(); + // WebSocket does not have a backpressure API so you must always demand + // the next frame after succeeding the previous one. + stream.demand(1); } return length; } @@ -531,7 +533,7 @@ public abstract class HTTP2StreamEndPoint implements EndPoint { if (buffer.hasRemaining()) offer(buffer, Callback.from(Callback.NOOP::succeeded, callback::failed), null); - offer(BufferUtil.EMPTY_BUFFER, callback, EOF); + offer(BufferUtil.EMPTY_BUFFER, callback, Entry.EOF); } else { @@ -582,8 +584,10 @@ public abstract class HTTP2StreamEndPoint implements EndPoint writeState); } - private class Entry + private static class Entry { + private static final Throwable EOF = new Throwable(); + private final ByteBuffer buffer; private final Callback callback; private final Throwable failure; @@ -610,7 +614,6 @@ public abstract class HTTP2StreamEndPoint implements EndPoint private void succeed() { callback.succeeded(); - stream.demand(1); } private void fail(Throwable failure) diff --git a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/IStream.java b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/IStream.java index 4c3b470204d..6b97474febf 100644 --- a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/IStream.java +++ b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/IStream.java @@ -119,6 +119,14 @@ public interface IStream extends Stream, Attachable, Closeable */ boolean isRemotelyClosed(); + /** + * Fail all data queued in the stream and reset + * demand to 0. + * @param x the exception to fail the data with. + * @return true if the end of the stream was reached, false otherwise. + */ + boolean failAllData(Throwable x); + /** * @return whether this stream has been reset (locally or remotely) or has been failed * @see #isReset() @@ -126,8 +134,6 @@ public interface IStream extends Stream, Attachable, Closeable */ boolean isResetOrFailed(); - void fail(Throwable x); - /** *

An ordered list of frames belonging to the same stream.

*/ diff --git a/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/ContentDemander_state.puml b/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/ContentDemander_state.puml new file mode 100644 index 00000000000..f9ee5b4af5a --- /dev/null +++ b/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/ContentDemander_state.puml @@ -0,0 +1,26 @@ +@startuml + +null: +content: +DEMANDING: +EOF: + +[*] --> null + +null --> DEMANDING : demand() +null --> EOF : eof() +null -left-> null : onTimeout() + +DEMANDING --> DEMANDING : demand() +DEMANDING --> content : onContent()\n onTimeout() +DEMANDING --> EOF : eof() + +EOF --> EOF : eof()\n onTimeout() + +note bottom of content: content1 -> content2 is only\nvalid if content1 is special +note top of content: content -> null only happens\nwhen content is not special +content --> content : onContent()\n onTimeout() +content --> null: take() +content --> EOF: eof() + +@enduml diff --git a/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HttpChannelOverHTTP2.java b/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HttpChannelOverHTTP2.java index f5f60da88e5..f3b8100042c 100644 --- a/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HttpChannelOverHTTP2.java +++ b/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HttpChannelOverHTTP2.java @@ -21,6 +21,7 @@ package org.eclipse.jetty.http2.server; import java.io.Closeable; import java.io.IOException; import java.nio.ByteBuffer; +import java.util.concurrent.atomic.AtomicReference; import java.util.function.Consumer; import org.eclipse.jetty.http.BadMessageException; @@ -45,7 +46,6 @@ import org.eclipse.jetty.server.HttpConfiguration; import org.eclipse.jetty.server.HttpInput; import org.eclipse.jetty.server.handler.ContextHandler; import org.eclipse.jetty.util.Callback; -import org.eclipse.jetty.util.thread.AutoLock; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -58,68 +58,12 @@ public class HttpChannelOverHTTP2 extends HttpChannel implements Closeable, Writ private boolean _expect100Continue; private boolean _delayedUntilContent; private boolean _useOutputDirectByteBuffers; - private final RequestContent _requestContent = new RequestContent(); - - private class RequestContent - { - private HttpInput.Content _content; - private boolean _endStream; - private boolean _producing; - private final AutoLock _lock = new AutoLock(); - - void setContent(boolean endStream, HttpInput.Content content) - { - try (AutoLock ignored = _lock.lock()) - { - if (_content != null) - throw new AssertionError("content cannot be queued; stream=" + getStream()); - _endStream = endStream; - _content = content; - _producing = false; - } - } - - private HttpInput.Content takeContent(boolean[] endStreamResult) - { - try (AutoLock ignored = _lock.lock()) - { - if (_content == null) - return null; - HttpInput.Content contentCopy = _content; - endStreamResult[0] = _endStream; - _content = null; - _endStream = false; - return contentCopy; - } - } - - HttpInput.Content takeContentOrDemand(boolean[] endStreamResult) - { - HttpInput.Content content = takeContent(endStreamResult); - if (content != null) - return content; - - boolean demand; - try (AutoLock ignored = _lock.lock()) - { - demand = !_producing; - if (demand) - { - if (_content != null) - throw new AssertionError("_content should be null"); - _producing = true; - } - } - if (demand) - getStream().demand(1); - - return takeContent(endStreamResult); - } - } + private final ContentDemander _contentDemander; public HttpChannelOverHTTP2(Connector connector, HttpConfiguration configuration, EndPoint endPoint, HttpTransportOverHTTP2 transport) { super(connector, configuration, endPoint, transport); + _contentDemander = new ContentDemander(); } protected IStream getStream() @@ -192,14 +136,15 @@ public class HttpChannelOverHTTP2 extends HttpChannel implements Closeable, Writ // Delay the demand of DATA frames for CONNECT with :protocol // or for normal requests expecting 100 continue. - if (!connect) + if (connect) { - if (!_expect100Continue) - getStream().demand(1); + if (request.getProtocol() == null) + _contentDemander.demand(false); } - else if (request.getProtocol() == null) + else { - getStream().demand(1); + if (_delayedUntilContent) + _contentDemander.demand(false); } if (LOG.isDebugEnabled()) @@ -271,6 +216,7 @@ public class HttpChannelOverHTTP2 extends HttpChannel implements Closeable, Writ { _expect100Continue = false; _delayedUntilContent = false; + _contentDemander.recycle(); super.recycle(); getHttpTransport().recycle(); } @@ -291,38 +237,16 @@ public class HttpChannelOverHTTP2 extends HttpChannel implements Closeable, Writ @Override public Runnable onData(DataFrame frame, Callback callback) { - return onRequestContent(frame, callback); - } - - public Runnable onRequestContent(DataFrame frame, final Callback callback) - { - Stream stream = getStream(); - if (stream.isReset()) - { - // Consume previously queued content to - // enlarge the session flow control window. - consumeInput(); - // Consume immediately this content. - callback.succeeded(); - return null; - } - ByteBuffer buffer = frame.getData(); int length = buffer.remaining(); - - if (LOG.isDebugEnabled()) + HttpInput.Content content = new HttpInput.Content(buffer) { - LOG.debug("HTTP2 Request #{}/{}: {} bytes of content", - stream.getId(), - Integer.toHexString(stream.getSession().hashCode()), - length); - } + @Override + public boolean isEof() + { + return frame.isEndStream(); + } - boolean wasDelayed = _delayedUntilContent; - _delayedUntilContent = false; - - _requestContent.setContent(frame.isEndStream(), new HttpInput.Content(buffer) - { @Override public void succeeded() { @@ -340,42 +264,357 @@ public class HttpChannelOverHTTP2 extends HttpChannel implements Closeable, Writ { return callback.getInvocationType(); } - }); - if (getState().isAsync()) + }; + boolean needed = _contentDemander.onContent(content); + boolean handle = onContent(content); + + boolean endStream = frame.isEndStream(); + if (endStream) { - boolean handle = getState().onReadPossible(); - return handle || wasDelayed ? this : null; + boolean handleContent = onContentComplete(); + // This will generate EOF -> must happen before onContentProducible. + boolean handleRequest = onRequestComplete(); + handle |= handleContent | handleRequest; } - else + + boolean woken = needed && getRequest().getHttpInput().onContentProducible(); + handle |= woken; + if (LOG.isDebugEnabled()) { - getRequest().getHttpInput().unblock(); - return wasDelayed ? this : null; + Stream stream = getStream(); + LOG.debug("HTTP2 Request #{}/{}: {} bytes of {} content, woken: {}, needed: {}, handle: {}", + stream.getId(), + Integer.toHexString(stream.getSession().hashCode()), + length, + endStream ? "last" : "some", + woken, + needed, + handle); + } + + boolean wasDelayed = _delayedUntilContent; + _delayedUntilContent = false; + return handle || wasDelayed ? this : null; + } + + /** + * Demanding content is a marker content that is used to remember that a demand was + * registered into the stream. The {@code needed} flag indicates if the demand originated + * from a call to {@link #produceContent()} when false or {@link #needContent()} + * when true, as {@link HttpInput#onContentProducible()} must only be called + * only when {@link #needContent()} was called. + * Instances of this class must never escape the scope of this channel impl, + * so {@link #produceContent()} must never return one. + */ + private static final class DemandingContent extends HttpInput.SpecialContent + { + private final boolean needed; + + private DemandingContent(boolean needed) + { + this.needed = needed; } } - @Override - public void produceContent() - { - // HttpInputOverHttp2 calls this method via produceRawContent(); - // this is the equivalent of Http1 parseAndFill(). + private static final HttpInput.Content EOF = new HttpInput.EofContent(); + private static final HttpInput.Content DEMANDING_NEEDED = new DemandingContent(true); + private static final HttpInput.Content DEMANDING_NOT_NEEDED = new DemandingContent(false); - boolean[] endStreamResult = new boolean[1]; - HttpInput.Content content = _requestContent.takeContentOrDemand(endStreamResult); - if (content != null) + private class ContentDemander + { + private final AtomicReference _content = new AtomicReference<>(); + + public void recycle() { - onContent(content); - if (endStreamResult[0]) + if (LOG.isDebugEnabled()) + LOG.debug("recycle {}", this); + HttpInput.Content c = _content.getAndSet(null); + if (c != null && !c.isSpecial()) + throw new AssertionError("unconsumed content: " + c); + } + + public HttpInput.Content poll() + { + while (true) { - onContentComplete(); - onRequestComplete(); + HttpInput.Content c = _content.get(); + if (LOG.isDebugEnabled()) + LOG.debug("poll, content = {}", c); + if (c == null || c.isSpecial() || _content.compareAndSet(c, c.isEof() ? EOF : null)) + { + if (LOG.isDebugEnabled()) + LOG.debug("returning current content"); + return c; + } } } + + public boolean demand(boolean needed) + { + while (true) + { + HttpInput.Content c = _content.get(); + if (LOG.isDebugEnabled()) + LOG.debug("demand({}), content = {}", needed, c); + if (c instanceof DemandingContent) + { + if (needed && !((DemandingContent)c).needed) + { + if (!_content.compareAndSet(c, DEMANDING_NEEDED)) + { + if (LOG.isDebugEnabled()) + LOG.debug("already demanding but switched needed flag to true"); + continue; + } + } + if (LOG.isDebugEnabled()) + LOG.debug("already demanding, returning false"); + return false; + } + if (c != null) + { + if (LOG.isDebugEnabled()) + LOG.debug("content available, returning true"); + return true; + } + if (_content.compareAndSet(null, needed ? DEMANDING_NEEDED : DEMANDING_NOT_NEEDED)) + { + IStream stream = getStream(); + if (stream == null) + { + _content.set(null); + if (LOG.isDebugEnabled()) + LOG.debug("no content available, switched to demanding but stream is now null"); + return false; + } + if (LOG.isDebugEnabled()) + LOG.debug("no content available, demanding stream {}", stream); + stream.demand(1); + c = _content.get(); + boolean hasContent = !(c instanceof DemandingContent) && c != null; + if (LOG.isDebugEnabled()) + LOG.debug("has content now? {}", hasContent); + return hasContent; + } + } + } + + public boolean onContent(HttpInput.Content content) + { + while (true) + { + HttpInput.Content c = _content.get(); + if (LOG.isDebugEnabled()) + LOG.debug("content delivered by stream: {}, current content: {}", content, c); + if (c instanceof DemandingContent) + { + if (_content.compareAndSet(c, content)) + { + boolean needed = ((DemandingContent)c).needed; + if (LOG.isDebugEnabled()) + LOG.debug("replacing demand content with {} succeeded; returning {}", content, needed); + return needed; + } + } + else if (c == null) + { + if (!content.isSpecial()) + { + // This should never happen, consider as a bug. + content.failed(new IllegalStateException("Non special content without demand : " + content)); + return false; + } + if (_content.compareAndSet(null, content)) + { + if (LOG.isDebugEnabled()) + LOG.debug("replacing null content with {} succeeded", content); + return false; + } + } + else if (c.isEof() && content.isEof() && content.isEmpty()) + { + content.succeeded(); + return true; + } + else if (content.getError() != null) + { + if (c.getError() != null) + { + if (c.getError() != content.getError()) + c.getError().addSuppressed(content.getError()); + return true; + } + if (_content.compareAndSet(c, content)) + { + c.failed(content.getError()); + if (LOG.isDebugEnabled()) + LOG.debug("replacing current content with {} succeeded", content); + return true; + } + } + else if (c.getError() != null && content.remaining() == 0) + { + content.succeeded(); + return true; + } + else + { + // This should never happen, consider as a bug. + content.failed(new IllegalStateException("Cannot overwrite exiting content " + c + " with " + content)); + return false; + } + } + } + + public boolean onTimeout(Throwable failure) + { + while (true) + { + HttpInput.Content c = _content.get(); + if (LOG.isDebugEnabled()) + LOG.debug("onTimeout with current content: {} and failure = {}", c, failure); + if (!(c instanceof DemandingContent)) + return false; + if (_content.compareAndSet(c, new HttpInput.ErrorContent(failure))) + { + if (LOG.isDebugEnabled()) + LOG.debug("replacing current content with error succeeded"); + return true; + } + } + } + + public void eof() + { + while (true) + { + HttpInput.Content c = _content.get(); + if (LOG.isDebugEnabled()) + LOG.debug("eof with current content: {}", c); + if (c instanceof DemandingContent) + { + if (_content.compareAndSet(c, EOF)) + { + if (LOG.isDebugEnabled()) + LOG.debug("replacing current content with special EOF succeeded"); + return; + } + } + else if (c == null) + { + if (_content.compareAndSet(null, EOF)) + { + if (LOG.isDebugEnabled()) + LOG.debug("replacing null content with special EOF succeeded"); + return; + } + } + else if (c.isEof()) + { + if (LOG.isDebugEnabled()) + LOG.debug("current content already is EOF"); + return; + } + else if (c.remaining() == 0) + { + if (_content.compareAndSet(c, EOF)) + { + if (LOG.isDebugEnabled()) + LOG.debug("replacing current content with special EOF succeeded"); + return; + } + } + else + { + // EOF may arrive with HEADERS frame (e.g. a trailer) that is not flow controlled, so we need to wrap the existing content. + // Covered by HttpTrailersTest.testRequestTrailersWithContent. + HttpInput.Content content = new HttpInput.WrappingContent(c, true); + if (_content.compareAndSet(c, content)) + { + if (LOG.isDebugEnabled()) + LOG.debug("replacing current content with {} succeeded", content); + return; + } + } + } + } + + public boolean failContent(Throwable failure) + { + while (true) + { + HttpInput.Content c = _content.get(); + if (LOG.isDebugEnabled()) + LOG.debug("failing current content: {} with failure: {}", c, failure); + if (c == null) + return false; + if (c.isSpecial()) + return c.isEof(); + if (_content.compareAndSet(c, null)) + { + c.failed(failure); + if (LOG.isDebugEnabled()) + LOG.debug("replacing current content with null succeeded"); + return false; + } + } + } + + @Override + public String toString() + { + return getClass().getSimpleName() + "@" + hashCode() + " _content=" + _content; + } } @Override - public void failContent(Throwable failure) + public boolean needContent() { - getStream().fail(failure); + boolean hasContent = _contentDemander.demand(true); + if (LOG.isDebugEnabled()) + LOG.debug("needContent has content? {}", hasContent); + return hasContent; + } + + @Override + public HttpInput.Content produceContent() + { + HttpInput.Content content = null; + if (_contentDemander.demand(false)) + content = _contentDemander.poll(); + if (LOG.isDebugEnabled()) + LOG.debug("produceContent produced {}", content); + return content; + } + + @Override + public boolean failAllContent(Throwable failure) + { + if (LOG.isDebugEnabled()) + LOG.debug("failing all content with {}", (Object)failure); + boolean atEof = getStream().failAllData(failure); + atEof |= _contentDemander.failContent(failure); + if (LOG.isDebugEnabled()) + LOG.debug("failed all content, reached EOF? {}", atEof); + return atEof; + } + + @Override + public boolean failed(Throwable x) + { + if (LOG.isDebugEnabled()) + LOG.debug("failed " + x); + + _contentDemander.onContent(new HttpInput.ErrorContent(x)); + + return getRequest().getHttpInput().onContentProducible(); + } + + @Override + protected boolean eof() + { + _contentDemander.eof(); + return false; } @Override @@ -393,7 +632,10 @@ public class HttpChannelOverHTTP2 extends HttpChannel implements Closeable, Writ System.lineSeparator(), trailers); } + // This will generate EOF -> need to call onContentProducible. boolean handle = onRequestComplete(); + boolean woken = getRequest().getHttpInput().onContentProducible(); + handle |= woken; boolean wasDelayed = _delayedUntilContent; _delayedUntilContent = false; @@ -412,25 +654,30 @@ public class HttpChannelOverHTTP2 extends HttpChannel implements Closeable, Writ final boolean delayed = _delayedUntilContent; _delayedUntilContent = false; - boolean result = isIdle(); - if (result) + boolean reset = isIdle(); + if (reset) consumeInput(); getHttpTransport().onStreamTimeout(failure); - if (getRequest().getHttpInput().onIdleTimeout(failure) || delayed) + + failure.addSuppressed(new Throwable("HttpInput idle timeout")); + _contentDemander.onTimeout(failure); + boolean needed = getRequest().getHttpInput().onContentProducible(); + + if (needed || delayed) { consumer.accept(this::handleWithContext); - result = false; + reset = false; } - return result; + return reset; } @Override public Runnable onFailure(Throwable failure, Callback callback) { getHttpTransport().onStreamFailure(failure); - boolean handle = getRequest().getHttpInput().failed(failure); + boolean handle = failed(failure); consumeInput(); return new FailureTask(failure, callback, handle); } diff --git a/jetty-security/src/test/java/org/eclipse/jetty/security/authentication/SpnegoAuthenticatorTest.java b/jetty-security/src/test/java/org/eclipse/jetty/security/authentication/SpnegoAuthenticatorTest.java index 218769c2519..22d7c0e8950 100644 --- a/jetty-security/src/test/java/org/eclipse/jetty/security/authentication/SpnegoAuthenticatorTest.java +++ b/jetty-security/src/test/java/org/eclipse/jetty/security/authentication/SpnegoAuthenticatorTest.java @@ -30,6 +30,7 @@ import org.eclipse.jetty.server.Authentication; import org.eclipse.jetty.server.HttpChannel; import org.eclipse.jetty.server.HttpChannelState; import org.eclipse.jetty.server.HttpConfiguration; +import org.eclipse.jetty.server.HttpInput; import org.eclipse.jetty.server.HttpOutput; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.Response; @@ -63,13 +64,33 @@ public class SpnegoAuthenticatorTest } @Override - public void produceContent() + public boolean failed(Throwable x) { + return false; } @Override - public void failContent(Throwable failure) + protected boolean eof() { + return false; + } + + @Override + public boolean needContent() + { + return false; + } + + @Override + public HttpInput.Content produceContent() + { + return null; + } + + @Override + public boolean failAllContent(Throwable failure) + { + return false; } @Override @@ -108,13 +129,33 @@ public class SpnegoAuthenticatorTest } @Override - public void produceContent() + public boolean failed(Throwable x) { + return false; } @Override - public void failContent(Throwable failure) + protected boolean eof() { + return false; + } + + @Override + public boolean needContent() + { + return false; + } + + @Override + public HttpInput.Content produceContent() + { + return null; + } + + @Override + public boolean failAllContent(Throwable failure) + { + return false; } @Override diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/AsyncContentProducer.java b/jetty-server/src/main/java/org/eclipse/jetty/server/AsyncContentProducer.java new file mode 100644 index 00000000000..253097a40be --- /dev/null +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/AsyncContentProducer.java @@ -0,0 +1,354 @@ +// +// ======================================================================== +// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under +// the terms of the Eclipse Public License 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0 +// +// This Source Code may also be made available under the following +// Secondary Licenses when the conditions for such availability set +// forth in the Eclipse Public License, v. 2.0 are satisfied: +// the Apache License v2.0 which is available at +// https://www.apache.org/licenses/LICENSE-2.0 +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +package org.eclipse.jetty.server; + +import java.util.concurrent.TimeUnit; + +import org.eclipse.jetty.http.BadMessageException; +import org.eclipse.jetty.http.HttpStatus; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Non-blocking {@link ContentProducer} implementation. Calling {@link #nextContent()} will never block + * but will return null when there is no available content. + */ +class AsyncContentProducer implements ContentProducer +{ + private static final Logger LOG = LoggerFactory.getLogger(AsyncContentProducer.class); + + private final HttpChannel _httpChannel; + private HttpInput.Interceptor _interceptor; + private HttpInput.Content _rawContent; + private HttpInput.Content _transformedContent; + private boolean _error; + private long _firstByteTimeStamp = Long.MIN_VALUE; + private long _rawContentArrived; + + AsyncContentProducer(HttpChannel httpChannel) + { + _httpChannel = httpChannel; + } + + @Override + public void recycle() + { + if (LOG.isDebugEnabled()) + LOG.debug("recycling {}", this); + _interceptor = null; + _rawContent = null; + _transformedContent = null; + _error = false; + _firstByteTimeStamp = Long.MIN_VALUE; + _rawContentArrived = 0L; + } + + @Override + public HttpInput.Interceptor getInterceptor() + { + return _interceptor; + } + + @Override + public void setInterceptor(HttpInput.Interceptor interceptor) + { + this._interceptor = interceptor; + } + + @Override + public int available() + { + HttpInput.Content content = nextTransformedContent(); + int available = content == null ? 0 : content.remaining(); + if (LOG.isDebugEnabled()) + LOG.debug("available = {}", available); + return available; + } + + @Override + public boolean hasContent() + { + boolean hasContent = _rawContent != null; + if (LOG.isDebugEnabled()) + LOG.debug("hasContent = {}", hasContent); + return hasContent; + } + + @Override + public boolean isError() + { + if (LOG.isDebugEnabled()) + LOG.debug("isError = {}", _error); + return _error; + } + + @Override + public void checkMinDataRate() + { + long minRequestDataRate = _httpChannel.getHttpConfiguration().getMinRequestDataRate(); + if (LOG.isDebugEnabled()) + LOG.debug("checkMinDataRate [m={},t={}]", minRequestDataRate, _firstByteTimeStamp); + if (minRequestDataRate > 0 && _firstByteTimeStamp != Long.MIN_VALUE) + { + long period = System.nanoTime() - _firstByteTimeStamp; + if (period > 0) + { + long minimumData = minRequestDataRate * TimeUnit.NANOSECONDS.toMillis(period) / TimeUnit.SECONDS.toMillis(1); + if (getRawContentArrived() < minimumData) + { + if (LOG.isDebugEnabled()) + LOG.debug("checkMinDataRate check failed"); + BadMessageException bad = new BadMessageException(HttpStatus.REQUEST_TIMEOUT_408, + String.format("Request content data rate < %d B/s", minRequestDataRate)); + if (_httpChannel.getState().isResponseCommitted()) + { + if (LOG.isDebugEnabled()) + LOG.debug("checkMinDataRate aborting channel"); + _httpChannel.abort(bad); + } + failCurrentContent(bad); + throw bad; + } + } + } + } + + @Override + public long getRawContentArrived() + { + if (LOG.isDebugEnabled()) + LOG.debug("getRawContentArrived = {}", _rawContentArrived); + return _rawContentArrived; + } + + @Override + public boolean consumeAll(Throwable x) + { + if (LOG.isDebugEnabled()) + LOG.debug("consumeAll [e={}]", (Object)x); + failCurrentContent(x); + // A specific HttpChannel mechanism must be used as the following code + // does not guarantee that the channel will synchronously deliver all + // content it already contains: + // while (true) + // { + // HttpInput.Content content = _httpChannel.produceContent(); + // ... + // } + // as the HttpChannel's produceContent() contract makes no such promise; + // for instance the H2 implementation calls Stream.demand() that may + // deliver the content asynchronously. Tests in StreamResetTest cover this. + boolean atEof = _httpChannel.failAllContent(x); + if (LOG.isDebugEnabled()) + LOG.debug("failed all content of http channel; at EOF? {}", atEof); + return atEof; + } + + private void failCurrentContent(Throwable x) + { + if (LOG.isDebugEnabled()) + LOG.debug("failing currently held content [r={},t={}]", _rawContent, _transformedContent, x); + if (_transformedContent != null && !_transformedContent.isSpecial()) + { + if (_transformedContent != _rawContent) + { + _transformedContent.skip(_transformedContent.remaining()); + _transformedContent.failed(x); + } + _transformedContent = null; + } + + if (_rawContent != null && !_rawContent.isSpecial()) + { + _rawContent.skip(_rawContent.remaining()); + _rawContent.failed(x); + _rawContent = null; + } + } + + @Override + public boolean onContentProducible() + { + if (LOG.isDebugEnabled()) + LOG.debug("onContentProducible"); + return _httpChannel.getState().onReadReady(); + } + + @Override + public HttpInput.Content nextContent() + { + HttpInput.Content content = nextTransformedContent(); + if (LOG.isDebugEnabled()) + LOG.debug("nextContent = {}", content); + if (content != null) + _httpChannel.getState().onReadIdle(); + return content; + } + + @Override + public void reclaim(HttpInput.Content content) + { + if (LOG.isDebugEnabled()) + LOG.debug("reclaim {} [t={}]", content, _transformedContent); + if (_transformedContent == content) + { + content.succeeded(); + if (_transformedContent == _rawContent) + _rawContent = null; + _transformedContent = null; + } + } + + @Override + public boolean isReady() + { + HttpInput.Content content = nextTransformedContent(); + if (content == null) + { + _httpChannel.getState().onReadUnready(); + if (_httpChannel.needContent()) + { + content = nextTransformedContent(); + if (LOG.isDebugEnabled()) + LOG.debug("isReady got transformed content after needContent retry {}", content); + if (content != null) + _httpChannel.getState().onContentAdded(); + } + else + { + if (LOG.isDebugEnabled()) + LOG.debug("isReady has no transformed content after needContent"); + } + } + else + { + if (LOG.isDebugEnabled()) + LOG.debug("isReady got transformed content {}", content); + _httpChannel.getState().onContentAdded(); + } + boolean ready = content != null; + if (LOG.isDebugEnabled()) + LOG.debug("isReady = {}", ready); + return ready; + } + + private HttpInput.Content nextTransformedContent() + { + if (LOG.isDebugEnabled()) + LOG.debug("nextTransformedContent [r={},t={}]", _rawContent, _transformedContent); + if (_rawContent == null) + { + _rawContent = produceRawContent(); + if (_rawContent == null) + return null; + } + + if (_transformedContent != null && _transformedContent.isEmpty()) + { + if (_transformedContent != _rawContent) + _transformedContent.succeeded(); + if (LOG.isDebugEnabled()) + LOG.debug("nulling depleted transformed content"); + _transformedContent = null; + } + + while (_transformedContent == null) + { + if (_rawContent.isSpecial()) + { + // TODO does EOF need to be passed to the interceptors? + + _error = _rawContent.getError() != null; + if (LOG.isDebugEnabled()) + LOG.debug("raw content is special (with error = {}), returning it", _error); + return _rawContent; + } + + if (_interceptor != null) + { + if (LOG.isDebugEnabled()) + LOG.debug("using interceptor {} to transform raw content", _interceptor); + _transformedContent = _interceptor.readFrom(_rawContent); + } + else + { + if (LOG.isDebugEnabled()) + LOG.debug("null interceptor, transformed content = raw content"); + _transformedContent = _rawContent; + } + + if (_transformedContent != null && _transformedContent.isEmpty()) + { + if (_transformedContent != _rawContent) + _transformedContent.succeeded(); + if (LOG.isDebugEnabled()) + LOG.debug("nulling depleted transformed content"); + _transformedContent = null; + } + + if (_transformedContent == null) + { + if (_rawContent.isEmpty()) + { + _rawContent.succeeded(); + _rawContent = null; + if (LOG.isDebugEnabled()) + LOG.debug("nulling depleted raw content"); + _rawContent = produceRawContent(); + if (_rawContent == null) + { + if (LOG.isDebugEnabled()) + LOG.debug("produced null raw content, returning null"); + return null; + } + } + else + { + if (LOG.isDebugEnabled()) + LOG.debug("raw content is not empty"); + } + } + else + { + if (LOG.isDebugEnabled()) + LOG.debug("transformed content is not empty"); + } + } + + if (LOG.isDebugEnabled()) + LOG.debug("returning transformed content {}", _transformedContent); + return _transformedContent; + } + + private HttpInput.Content produceRawContent() + { + HttpInput.Content content = _httpChannel.produceContent(); + if (content != null) + { + _rawContentArrived += content.remaining(); + if (_firstByteTimeStamp == Long.MIN_VALUE) + _firstByteTimeStamp = System.nanoTime(); + if (LOG.isDebugEnabled()) + LOG.debug("produceRawContent updated rawContentArrived to {} and firstByteTimeStamp to {}", _rawContentArrived, _firstByteTimeStamp); + } + if (LOG.isDebugEnabled()) + LOG.debug("produceRawContent produced {}", content); + return content; + } +} diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/BlockingContentProducer.java b/jetty-server/src/main/java/org/eclipse/jetty/server/BlockingContentProducer.java new file mode 100644 index 00000000000..3dff5c88594 --- /dev/null +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/BlockingContentProducer.java @@ -0,0 +1,164 @@ +// +// ======================================================================== +// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under +// the terms of the Eclipse Public License 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0 +// +// This Source Code may also be made available under the following +// Secondary Licenses when the conditions for such availability set +// forth in the Eclipse Public License, v. 2.0 are satisfied: +// the Apache License v2.0 which is available at +// https://www.apache.org/licenses/LICENSE-2.0 +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +package org.eclipse.jetty.server; + +import java.util.concurrent.Semaphore; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Blocking implementation of {@link ContentProducer}. Calling {@link #nextContent()} will block when + * there is no available content but will never return null. + */ +class BlockingContentProducer implements ContentProducer +{ + private static final Logger LOG = LoggerFactory.getLogger(BlockingContentProducer.class); + + private final Semaphore _semaphore = new Semaphore(0); + private final AsyncContentProducer _asyncContentProducer; + + BlockingContentProducer(AsyncContentProducer delegate) + { + _asyncContentProducer = delegate; + } + + @Override + public void recycle() + { + if (LOG.isDebugEnabled()) + LOG.debug("recycling {}", this); + _asyncContentProducer.recycle(); + _semaphore.drainPermits(); + } + + @Override + public int available() + { + return _asyncContentProducer.available(); + } + + @Override + public boolean hasContent() + { + return _asyncContentProducer.hasContent(); + } + + @Override + public boolean isError() + { + return _asyncContentProducer.isError(); + } + + @Override + public void checkMinDataRate() + { + _asyncContentProducer.checkMinDataRate(); + } + + @Override + public long getRawContentArrived() + { + return _asyncContentProducer.getRawContentArrived(); + } + + @Override + public boolean consumeAll(Throwable x) + { + return _asyncContentProducer.consumeAll(x); + } + + @Override + public HttpInput.Content nextContent() + { + while (true) + { + HttpInput.Content content = _asyncContentProducer.nextContent(); + if (LOG.isDebugEnabled()) + LOG.debug("nextContent async producer returned {}", content); + if (content != null) + return content; + + // IFF isReady() returns false then HttpChannel.needContent() has been called, + // thus we know that eventually a call to onContentProducible will come. + if (_asyncContentProducer.isReady()) + { + if (LOG.isDebugEnabled()) + LOG.debug("nextContent async producer is ready, retrying"); + continue; + } + if (LOG.isDebugEnabled()) + LOG.debug("nextContent async producer is not ready, waiting on semaphore {}", _semaphore); + + try + { + _semaphore.acquire(); + } + catch (InterruptedException e) + { + return new HttpInput.ErrorContent(e); + } + } + } + + @Override + public void reclaim(HttpInput.Content content) + { + _asyncContentProducer.reclaim(content); + } + + @Override + public boolean isReady() + { + boolean ready = available() > 0; + if (LOG.isDebugEnabled()) + LOG.debug("isReady = {}", ready); + return ready; + } + + @Override + public HttpInput.Interceptor getInterceptor() + { + return _asyncContentProducer.getInterceptor(); + } + + @Override + public void setInterceptor(HttpInput.Interceptor interceptor) + { + _asyncContentProducer.setInterceptor(interceptor); + } + + @Override + public boolean onContentProducible() + { + // In blocking mode, the dispatched thread normally does not have to be rescheduled as it is normally in state + // DISPATCHED blocked on the semaphore that just needs to be released for the dispatched thread to resume. This is why + // this method always returns false. + // But async errors can occur while the dispatched thread is NOT blocked reading (i.e.: in state WAITING), + // so the WAITING to WOKEN transition must be done by the error-notifying thread which then has to reschedule the + // dispatched thread after HttpChannelState.asyncError() is called. + // Calling _asyncContentProducer.wakeup() changes the channel state from WAITING to WOKEN which would prevent the + // subsequent call to HttpChannelState.asyncError() from rescheduling the thread. + // AsyncServletTest.testStartAsyncThenClientStreamIdleTimeout() tests this. + if (LOG.isDebugEnabled()) + LOG.debug("onContentProducible releasing semaphore {}", _semaphore); + _semaphore.release(); + return false; + } +} diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/ContentProducer.java b/jetty-server/src/main/java/org/eclipse/jetty/server/ContentProducer.java new file mode 100644 index 00000000000..1a2a477001e --- /dev/null +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/ContentProducer.java @@ -0,0 +1,141 @@ +// +// ======================================================================== +// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under +// the terms of the Eclipse Public License 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0 +// +// This Source Code may also be made available under the following +// Secondary Licenses when the conditions for such availability set +// forth in the Eclipse Public License, v. 2.0 are satisfied: +// the Apache License v2.0 which is available at +// https://www.apache.org/licenses/LICENSE-2.0 +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +package org.eclipse.jetty.server; + +/** + * ContentProducer is the bridge between {@link HttpInput} and {@link HttpChannel}. + * It wraps a {@link HttpChannel} and uses the {@link HttpChannel#needContent()}, + * {@link HttpChannel#produceContent()} and {@link HttpChannel#failAllContent(Throwable)} + * methods, tracks the current state of the channel's input by updating the + * {@link HttpChannelState} and provides the necessary mechanism to unblock + * the reader thread when using a blocking implementation or to know if the reader thread + * has to be rescheduled when using an async implementation. + */ +public interface ContentProducer +{ + /** + * Reset all internal state and clear any held resources. + */ + void recycle(); + + /** + * Fail all content currently available in this {@link ContentProducer} instance + * as well as in the underlying {@link HttpChannel}. + * + * This call is always non-blocking. + * Doesn't change state. + * @return true if EOF was reached. + */ + boolean consumeAll(Throwable x); + + /** + * Check if the current data rate consumption is above the minimal rate. + * Abort the channel, fail the content currently available and throw a + * BadMessageException(REQUEST_TIMEOUT_408) if the check fails. + */ + void checkMinDataRate(); + + /** + * Get the byte count produced by the underlying {@link HttpChannel}. + * + * This call is always non-blocking. + * Doesn't change state. + * @return the byte count produced by the underlying {@link HttpChannel}. + */ + long getRawContentArrived(); + + /** + * Get the byte count that can immediately be read from this + * {@link ContentProducer} instance or the underlying {@link HttpChannel}. + * + * This call is always non-blocking. + * Doesn't change state. + * @return the available byte count. + */ + int available(); + + /** + * Check if this {@link ContentProducer} instance contains some + * content without querying the underlying {@link HttpChannel}. + * + * This call is always non-blocking. + * Doesn't change state. + * Doesn't query the HttpChannel. + * @return true if this {@link ContentProducer} instance contains content, false otherwise. + */ + boolean hasContent(); + + /** + * Check if the underlying {@link HttpChannel} reached an error content. + * This call is always non-blocking. + * Doesn't change state. + * Doesn't query the HttpChannel. + * @return true if the underlying {@link HttpChannel} reached an error content, false otherwise. + */ + boolean isError(); + + /** + * Get the next content that can be read from or that describes the special condition + * that was reached (error, eof). + * This call may or may not block until some content is available, depending on the implementation. + * The returned content is decoded by the interceptor set with {@link #setInterceptor(HttpInput.Interceptor)} + * or left as-is if no intercept is set. + * After this call, state can be either of UNREADY or IDLE. + * @return the next content that can be read from or null if the implementation does not block + * and has no available content. + */ + HttpInput.Content nextContent(); + + /** + * Free up the content by calling {@link HttpInput.Content#succeeded()} on it + * and updating this instance' internal state. + */ + void reclaim(HttpInput.Content content); + + /** + * Check if this {@link ContentProducer} instance has some content that can be read without blocking. + * If there is some, the next call to {@link #nextContent()} will not block. + * If there isn't any and the implementation does not block, this method will trigger a + * {@link javax.servlet.ReadListener} callback once some content is available. + * This call is always non-blocking. + * After this call, state can be either of UNREADY or READY. + * @return true if some content is immediately available, false otherwise. + */ + boolean isReady(); + + /** + * Get the {@link org.eclipse.jetty.server.HttpInput.Interceptor}. + * @return The {@link org.eclipse.jetty.server.HttpInput.Interceptor}, or null if none set. + */ + HttpInput.Interceptor getInterceptor(); + + /** + * Set the interceptor. + * @param interceptor The interceptor to use. + */ + void setInterceptor(HttpInput.Interceptor interceptor); + + /** + * Wake up the thread that is waiting for the next content. + * After this call, state can be READY. + * @return true if the thread has to be rescheduled, false otherwise. + */ + boolean onContentProducible(); +} + diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannel.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannel.java index 9b9b460fd78..ca40821980a 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannel.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannel.java @@ -124,9 +124,47 @@ public abstract class HttpChannel implements Runnable, HttpOutput.Interceptor return new HttpInput(state); } - public abstract void produceContent(); + /** + * Notify the channel that content is needed. If some content is immediately available, true is returned and + * {@link #produceContent()} has to be called and will return a non-null object. + * If no content is immediately available, {@link HttpInput#onContentProducible()} is called once some content arrives + * and {@link #produceContent()} can be called without returning null. + * If a failure happens, then {@link HttpInput#onContentProducible()} will be called and an error content will return the + * error on the next call to {@link #produceContent()}. + * @return true if content is immediately available. + */ + public abstract boolean needContent(); - public abstract void failContent(Throwable failure); + /** + * Produce a {@link HttpInput.Content} object with data currently stored within the channel. The produced content + * can be special (meaning calling {@link HttpInput.Content#isSpecial()} returns true) if the channel reached a special + * state, like EOF or an error. + * Once a special content has been returned, all subsequent calls to this method will always return a special content + * of the same kind and {@link #needContent()} will always return true. + * The returned content is "raw", i.e.: not decoded. + * @return a {@link HttpInput.Content} object if one is immediately available without blocking, null otherwise. + */ + public abstract HttpInput.Content produceContent(); + + /** + * Fail all content that is currently stored within the channel. + * @param failure the failure to fail the content with. + * @return true if EOF was reached while failing all content, false otherwise. + */ + public abstract boolean failAllContent(Throwable failure); + + /** + * Fail the channel's input. + * @param failure the failure. + * @return true if the channel needs to be rescheduled. + */ + public abstract boolean failed(Throwable failure); + + /** + * Mark the channel's input as EOF. + * @return true if the channel needs to be rescheduled. + */ + protected abstract boolean eof(); protected HttpOutput newHttpOutput() { @@ -307,19 +345,6 @@ public abstract class HttpChannel implements Runnable, HttpOutput.Interceptor _transientListeners.clear(); } - public void onAsyncWaitForContent() - { - } - - public void onBlockWaitForContent() - { - } - - public void onBlockWaitForContentFailure(Throwable failure) - { - getRequest().getHttpInput().failed(failure); - } - @Override public void run() { @@ -449,18 +474,6 @@ public abstract class HttpChannel implements Runnable, HttpOutput.Interceptor throw _state.getAsyncContextEvent().getThrowable(); } - case READ_REGISTER: - { - onAsyncWaitForContent(); - break; - } - - case READ_PRODUCE: - { - _request.getHttpInput().asyncReadProduce(); - break; - } - case READ_CALLBACK: { ContextHandler handler = _state.getContextHandler(); @@ -705,12 +718,12 @@ public abstract class HttpChannel implements Runnable, HttpOutput.Interceptor request.getFields()); } - public void onContent(HttpInput.Content content) + public boolean onContent(HttpInput.Content content) { if (LOG.isDebugEnabled()) LOG.debug("onContent {} {}", this, content); _combinedListener.onRequestContent(_request, content.getByteBuffer()); - _request.getHttpInput().addContent(content); + return false; } public boolean onContentComplete() @@ -733,7 +746,7 @@ public abstract class HttpChannel implements Runnable, HttpOutput.Interceptor { if (LOG.isDebugEnabled()) LOG.debug("onRequestComplete {}", this); - boolean result = _request.getHttpInput().eof(); + boolean result = eof(); _combinedListener.onRequestEnd(_request); return result; } @@ -769,11 +782,6 @@ public abstract class HttpChannel implements Runnable, HttpOutput.Interceptor _transport.onCompleted(); } - public boolean onEarlyEOF() - { - return _request.getHttpInput().earlyEOF(); - } - public void onBadMessage(BadMessageException failure) { int status = failure.getCode(); @@ -950,7 +958,7 @@ public abstract class HttpChannel implements Runnable, HttpOutput.Interceptor return null; } - public void execute(Runnable task) + protected void execute(Runnable task) { _executor.execute(task); } diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelOverHttp.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelOverHttp.java index 0dee629f952..f580e325d2b 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelOverHttp.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelOverHttp.java @@ -40,6 +40,7 @@ import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.http.MetaData; import org.eclipse.jetty.io.Connection; import org.eclipse.jetty.io.EndPoint; +import org.eclipse.jetty.io.EofException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -50,6 +51,7 @@ public class HttpChannelOverHttp extends HttpChannel implements HttpParser.Reque { private static final Logger LOG = LoggerFactory.getLogger(HttpChannelOverHttp.class); private static final HttpField PREAMBLE_UPGRADE_H2C = new HttpField(HttpHeader.UPGRADE, "h2c"); + private static final HttpInput.Content EOF = new HttpInput.EofContent(); private final HttpConnection _httpConnection; private final RequestBuilder _requestBuilder = new RequestBuilder(); private MetaData.Request _metadata; @@ -61,6 +63,14 @@ public class HttpChannelOverHttp extends HttpChannel implements HttpParser.Reque private boolean _expect102Processing = false; private List _complianceViolations; private HttpFields.Mutable _trailers; + // Field _content doesn't need to be volatile nor protected by a lock + // as it is always accessed by the same thread, i.e.: we get notified by onFillable + // that the socket contains new bytes and either schedule an onDataAvailable + // call that is going to read the socket or release the blocking semaphore to wake up + // the blocked reader and make it read the socket. The same logic is true for async + // events like timeout: we get notified and either schedule onError or release the + // blocking semaphore. + private HttpInput.Content _content; public HttpChannelOverHttp(HttpConnection httpConnection, Connector connector, HttpConfiguration config, EndPoint endPoint, HttpTransport transport) { @@ -76,15 +86,76 @@ public class HttpChannelOverHttp extends HttpChannel implements HttpParser.Reque } @Override - public void produceContent() + public boolean needContent() { - ((HttpConnection)getEndPoint().getConnection()).parseAndFillForContent(); + if (_content != null) + { + if (LOG.isDebugEnabled()) + LOG.debug("needContent has content immediately available: {}", _content); + return true; + } + _httpConnection.parseAndFillForContent(); + if (_content != null) + { + if (LOG.isDebugEnabled()) + LOG.debug("needContent has content after parseAndFillForContent: {}", _content); + return true; + } + + if (LOG.isDebugEnabled()) + LOG.debug("needContent has no content"); + _httpConnection.asyncReadFillInterested(); + return false; } @Override - public void failContent(Throwable failure) + public HttpInput.Content produceContent() { - ((HttpConnection)getEndPoint().getConnection()).failContent(failure); + if (_content == null) + { + if (LOG.isDebugEnabled()) + LOG.debug("produceContent has no content, parsing and filling"); + _httpConnection.parseAndFillForContent(); + } + HttpInput.Content result = _content; + if (result != null && !result.isSpecial()) + _content = result.isEof() ? EOF : null; + if (LOG.isDebugEnabled()) + LOG.debug("produceContent produced {}", result); + return result; + } + + @Override + public boolean failAllContent(Throwable failure) + { + if (LOG.isDebugEnabled()) + LOG.debug("failing all content with {}", (Object)failure); + if (_content != null && !_content.isSpecial()) + { + _content.failed(failure); + _content = _content.isEof() ? EOF : null; + if (_content == EOF) + return true; + } + while (true) + { + HttpInput.Content c = produceContent(); + if (c == null) + { + if (LOG.isDebugEnabled()) + LOG.debug("failed all content, EOF was not reached"); + return false; + } + c.skip(c.remaining()); + c.failed(failure); + if (c.isSpecial()) + { + boolean atEof = c.isEof(); + if (LOG.isDebugEnabled()) + LOG.debug("failed all content, EOF = {}", atEof); + return atEof; + } + } } @Override @@ -97,7 +168,7 @@ public class HttpChannelOverHttp extends HttpChannel implements HttpParser.Reque if (_metadata == null) _metadata = _requestBuilder.build(); onRequest(_metadata); - getRequest().getHttpInput().earlyEOF(); + markEarlyEOF(); } catch (Exception e) { @@ -108,10 +179,22 @@ public class HttpChannelOverHttp extends HttpChannel implements HttpParser.Reque } @Override - public boolean content(ByteBuffer content) + public boolean content(ByteBuffer buffer) { - onContent(_httpConnection.newContent(content)); - _delayedForContent = false; + HttpInput.Content content = _httpConnection.newContent(buffer); + if (_content != null) + { + if (_content.isSpecial()) + content.failed(_content.getError()); + else + throw new AssertionError("Cannot overwrite exiting content " + _content + " with " + content); + } + else + { + _content = content; + onContent(_content); + _delayedForContent = false; + } return true; } @@ -158,12 +241,69 @@ public class HttpChannelOverHttp extends HttpChannel implements HttpParser.Reque _httpConnection.getGenerator().setPersistent(false); // If we have no request yet, just close if (_metadata == null) - _httpConnection.close(); - else if (onEarlyEOF() || _delayedForContent) { - _delayedForContent = false; - handle(); + _httpConnection.close(); } + else + { + markEarlyEOF(); + if (_delayedForContent) + { + _delayedForContent = false; + handle(); + } + } + } + + private void markEarlyEOF() + { + if (LOG.isDebugEnabled()) + LOG.debug("received early EOF, content = {}", _content); + EofException failure = new EofException("Early EOF"); + if (_content != null) + _content.failed(failure); + _content = new HttpInput.ErrorContent(failure); + } + + @Override + protected boolean eof() + { + if (LOG.isDebugEnabled()) + LOG.debug("received EOF, content = {}", _content); + if (_content == null) + { + _content = EOF; + } + else + { + HttpInput.Content c = _content; + _content = new HttpInput.WrappingContent(c, true); + } + return false; + } + + @Override + public boolean failed(Throwable x) + { + if (LOG.isDebugEnabled()) + LOG.debug("failed {}, content = {}", x, _content); + + Throwable error = null; + if (_content != null && _content.isSpecial()) + error = _content.getError(); + + if (error != null && error != x) + { + error.addSuppressed(x); + } + else + { + if (_content != null) + _content.failed(x); + _content = new HttpInput.ErrorContent(x); + } + + return getRequest().getHttpInput().onContentProducible(); } @Override @@ -320,24 +460,6 @@ public class HttpChannelOverHttp extends HttpChannel implements HttpParser.Reque return onRequestComplete(); } - @Override - public void onAsyncWaitForContent() - { - _httpConnection.asyncReadFillInterested(); - } - - @Override - public void onBlockWaitForContent() - { - _httpConnection.blockingReadFillInterested(); - } - - @Override - public void onBlockWaitForContentFailure(Throwable failure) - { - _httpConnection.blockingReadFailure(failure); - } - @Override public void onComplianceViolation(ComplianceViolation.Mode mode, ComplianceViolation violation, String details) { @@ -445,6 +567,9 @@ public class HttpChannelOverHttp extends HttpChannel implements HttpParser.Reque _upgrade = null; _trailers = null; _metadata = null; + if (_content != null && !_content.isSpecial()) + throw new AssertionError("unconsumed content: " + _content); + _content = null; } @Override @@ -539,13 +664,24 @@ public class HttpChannelOverHttp extends HttpChannel implements HttpParser.Reque if (_delayedForContent) { _delayedForContent = false; - getRequest().getHttpInput().onIdleTimeout(timeout); + doOnIdleTimeout(timeout); execute(this); return false; } return true; } + private void doOnIdleTimeout(Throwable x) + { + boolean neverDispatched = getState().isIdle(); + boolean waitingForContent = _content == null || _content.remaining() == 0; + if ((waitingForContent || neverDispatched) && (_content == null || !_content.isSpecial())) + { + x.addSuppressed(new Throwable("HttpInput idle timeout")); + _content = new HttpInput.ErrorContent(x); + } + } + private static class RequestBuilder { private final HttpFields.Mutable _fieldsBuilder = HttpFields.build(); diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelState.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelState.java index 6798ce18bfd..c08291fbe07 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelState.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelState.java @@ -107,12 +107,9 @@ public class HttpChannelState */ private enum InputState { - IDLE, // No isReady; No data - REGISTER, // isReady()==false handling; No data - REGISTERED, // isReady()==false !handling; No data - POSSIBLE, // isReady()==false async read callback called (http/1 only) - PRODUCING, // isReady()==false READ_PRODUCE action is being handled (http/1 only) - READY // isReady() was false, onContentAdded has been called + IDLE, // No isReady; No data + UNREADY, // isReady()==false; No data + READY // isReady() was false; data is available } /* @@ -137,8 +134,6 @@ public class HttpChannelState ASYNC_ERROR, // handle an async error ASYNC_TIMEOUT, // call asyncContext onTimeout WRITE_CALLBACK, // handle an IO write callback - READ_REGISTER, // Register for fill interest - READ_PRODUCE, // Check is a read is possible by parsing/filling READ_CALLBACK, // handle an IO read callback COMPLETE, // Complete the response by closing output TERMINATED, // No further actions @@ -465,19 +460,12 @@ public class HttpChannelState case ASYNC: switch (_inputState) { - case POSSIBLE: - _inputState = InputState.PRODUCING; - return Action.READ_PRODUCE; + case IDLE: + case UNREADY: + break; case READY: _inputState = InputState.IDLE; return Action.READ_CALLBACK; - case REGISTER: - case PRODUCING: - _inputState = InputState.REGISTERED; - return Action.READ_REGISTER; - case IDLE: - case REGISTERED: - break; default: throw new IllegalStateException(getStatusStringLocked()); @@ -1222,99 +1210,8 @@ public class HttpChannelState _channel.getRequest().setAttribute(name, attribute); } - /** - * Called to signal async read isReady() has returned false. - * This indicates that there is no content available to be consumed - * and that once the channel enters the ASYNC_WAIT state it will - * register for read interest by calling {@link HttpChannel#onAsyncWaitForContent()} - * either from this method or from a subsequent call to {@link #unhandle()}. - */ - public void onReadUnready() - { - boolean interested = false; - try (AutoLock l = lock()) - { - if (LOG.isDebugEnabled()) - LOG.debug("onReadUnready {}", toStringLocked()); - - switch (_inputState) - { - case IDLE: - case READY: - if (_state == State.WAITING) - { - interested = true; - _inputState = InputState.REGISTERED; - } - else - { - _inputState = InputState.REGISTER; - } - break; - - case REGISTER: - case REGISTERED: - case POSSIBLE: - case PRODUCING: - break; - - default: - throw new IllegalStateException(toStringLocked()); - } - } - - if (interested) - _channel.onAsyncWaitForContent(); - } - - /** - * Called to signal that content is now available to read. - * If the channel is in ASYNC_WAIT state and unready (ie isReady() has - * returned false), then the state is changed to ASYNC_WOKEN and true - * is returned. - * - * @return True IFF the channel was unready and in ASYNC_WAIT state - */ - public boolean onContentAdded() - { - boolean woken = false; - try (AutoLock l = lock()) - { - if (LOG.isDebugEnabled()) - LOG.debug("onContentAdded {}", toStringLocked()); - - switch (_inputState) - { - case IDLE: - case READY: - break; - - case PRODUCING: - _inputState = InputState.READY; - break; - - case REGISTER: - case REGISTERED: - _inputState = InputState.READY; - if (_state == State.WAITING) - { - woken = true; - _state = State.WOKEN; - } - break; - - default: - throw new IllegalStateException(toStringLocked()); - } - } - return woken; - } - /** * Called to signal that the channel is ready for a callback. - * This is similar to calling {@link #onReadUnready()} followed by - * {@link #onContentAdded()}, except that as content is already - * available, read interest is never set. * * @return true if woken */ @@ -1328,7 +1225,11 @@ public class HttpChannelState switch (_inputState) { + case READY: + _inputState = InputState.READY; + break; case IDLE: + case UNREADY: _inputState = InputState.READY; if (_state == State.WAITING) { @@ -1344,25 +1245,20 @@ public class HttpChannelState return woken; } - /** - * Called to indicate that more content may be available, - * but that a handling thread may need to produce (fill/parse) - * it. Typically called by the async read success callback. - * - * @return {@code true} if more content may be available - */ - public boolean onReadPossible() + public boolean onReadEof() { boolean woken = false; try (AutoLock l = lock()) { if (LOG.isDebugEnabled()) - LOG.debug("onReadPossible {}", toStringLocked()); + LOG.debug("onReadEof {}", toStringLocked()); switch (_inputState) { - case REGISTERED: - _inputState = InputState.POSSIBLE; + case IDLE: + case READY: + case UNREADY: + _inputState = InputState.READY; if (_state == State.WAITING) { woken = true; @@ -1370,11 +1266,6 @@ public class HttpChannelState } break; - case IDLE: - case READY: - case REGISTER: - break; - default: throw new IllegalStateException(toStringLocked()); } @@ -1382,29 +1273,72 @@ public class HttpChannelState return woken; } - /** - * Called to signal that a read has read -1. - * Will wake if the read was called while in ASYNC_WAIT state - * - * @return {@code true} if woken - */ - public boolean onReadEof() + public void onContentAdded() { - boolean woken = false; try (AutoLock l = lock()) { if (LOG.isDebugEnabled()) - LOG.debug("onEof {}", toStringLocked()); + LOG.debug("onContentAdded {}", toStringLocked()); - // Force read ready so onAllDataRead can be called - _inputState = InputState.READY; - if (_state == State.WAITING) + switch (_inputState) { - woken = true; - _state = State.WOKEN; + case IDLE: + case UNREADY: + case READY: + _inputState = InputState.READY; + break; + + default: + throw new IllegalStateException(toStringLocked()); + } + } + } + + public void onReadIdle() + { + try (AutoLock l = lock()) + { + if (LOG.isDebugEnabled()) + LOG.debug("onReadIdle {}", toStringLocked()); + + switch (_inputState) + { + case UNREADY: + case READY: + case IDLE: + _inputState = InputState.IDLE; + break; + + default: + throw new IllegalStateException(toStringLocked()); + } + } + } + + /** + * Called to indicate that more content may be available, + * but that a handling thread may need to produce (fill/parse) + * it. Typically called by the async read success callback. + */ + public void onReadUnready() + { + try (AutoLock l = lock()) + { + if (LOG.isDebugEnabled()) + LOG.debug("onReadUnready {}", toStringLocked()); + + switch (_inputState) + { + case IDLE: + case UNREADY: + case READY: // READY->UNREADY is needed by AsyncServletIOTest.testStolenAsyncRead + _inputState = InputState.UNREADY; + break; + + default: + throw new IllegalStateException(toStringLocked()); } } - return woken; } public boolean onWritePossible() diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelState_input.puml b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelState_input.puml new file mode 100644 index 00000000000..13eb5dc325b --- /dev/null +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelState_input.puml @@ -0,0 +1,84 @@ +@startuml +title HttpChannelState + +note top of onReadReady_inputState: onReadReady + +state "input state" as onReadReady_inputState { + state "IDLE" as onReadReady_IDLE + state "UNREADY" as onReadReady_UNREADY + state "READY" as onReadReady_READY + + state "channel state" as onReadReady_channelState { + state "WAITING" as onReadReady_WAITING + state "WOKEN" as onReadReady_WOKEN + onReadReady_WAITING --> onReadReady_WOKEN + } + + onReadReady_IDLE --> onReadReady_channelState + onReadReady_UNREADY --> onReadReady_channelState + + onReadReady_channelState --> onReadReady_READY + onReadReady_READY --> onReadReady_READY +} + + +note top of onReadEof_inputState: onReadEof + +state "input state" as onReadEof_inputState { + state "IDLE" as onReadEof_IDLE + state "UNREADY" as onReadEof_UNREADY + state "READY" as onReadEof_READY + + state "channel state" as onReadEof_channelState { + state "WAITING" as onReadEof_WAITING + state "WOKEN" as onReadEof_WOKEN + onReadEof_WAITING --> onReadEof_WOKEN + } + + onReadEof_IDLE --> onReadEof_channelState + onReadEof_UNREADY --> onReadEof_channelState + onReadEof_READY --> onReadEof_channelState + + onReadEof_channelState --> onReadEof_READY +} + + +note top of onReadIdle_inputState: onReadIdle + +state "input state" as onReadIdle_inputState { + state "IDLE" as onReadIdle_IDLE + state "UNREADY" as onReadIdle_UNREADY + state "READY" as onReadIdle_READY + + onReadIdle_IDLE --> onReadIdle_IDLE + onReadIdle_UNREADY --> onReadIdle_IDLE + onReadIdle_READY --> onReadIdle_IDLE +} + + +note top of onReadUnready_inputState: onReadUnready + +state "input state" as onReadUnready_inputState { + state "IDLE" as onReadUnready_IDLE + state "UNREADY" as onReadUnready_UNREADY + state "READY" as onReadUnready_READY + + onReadUnready_IDLE --> onReadUnready_UNREADY + onReadUnready_UNREADY --> onReadUnready_UNREADY + onReadUnready_READY --> onReadUnready_UNREADY +} + + +note top of onContentAdded_inputState: onContentAdded + +state "input state" as onContentAdded_inputState { + state "IDLE" as onContentAdded_IDLE + state "UNREADY" as onContentAdded_UNREADY + state "READY" as onContentAdded_READY + + onContentAdded_IDLE --> onContentAdded_READY + onContentAdded_UNREADY --> onContentAdded_READY + onContentAdded_READY --> onContentAdded_READY +} + +@enduml diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnection.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnection.java index 5d0cc2755f8..add0c44b9d3 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnection.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnection.java @@ -33,8 +33,6 @@ import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpHeaderValue; import org.eclipse.jetty.http.HttpMethod; import org.eclipse.jetty.http.HttpParser; -import org.eclipse.jetty.http.HttpParser.RequestHandler; -import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.http.MetaData; import org.eclipse.jetty.http.PreEncodedHttpField; import org.eclipse.jetty.io.AbstractConnection; @@ -69,7 +67,6 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http private final HttpParser _parser; private final AtomicInteger _contentBufferReferences = new AtomicInteger(); private volatile ByteBuffer _requestBuffer = null; - private final BlockingReadCallback _blockingReadCallback = new BlockingReadCallback(); private final AsyncReadCallback _asyncReadCallback = new AsyncReadCallback(); private final SendCallback _sendCallback = new SendCallback(); private final boolean _recordHttpComplianceViolations; @@ -321,27 +318,13 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http */ void parseAndFillForContent() { - // parseRequestBuffer() must always be called after fillRequestBuffer() otherwise this method doesn't trigger EOF/earlyEOF - // which breaks AsyncRequestReadTest.testPartialReadThenShutdown() + // When fillRequestBuffer() is called, it must always be followed by a parseRequestBuffer() call otherwise this method + // doesn't trigger EOF/earlyEOF which breaks AsyncRequestReadTest.testPartialReadThenShutdown() int filled = Integer.MAX_VALUE; while (_parser.inContentState()) { boolean handled = parseRequestBuffer(); - if (handled || filled <= 0 || _input.hasContent()) - break; - filled = fillRequestBuffer(); - } - } - - void failContent(Throwable failure) - { - int filled = Integer.MAX_VALUE; - while (_parser.inContentState()) - { - // The parser is going generate and forward contents to the HttpInput - // so it's up to it to fail them individually. - parseRequestBuffer(); - if (filled <= 0 || _input.hasContent()) + if (handled || filled <= 0) break; filled = fillRequestBuffer(); } @@ -614,25 +597,7 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http public void asyncReadFillInterested() { - getEndPoint().fillInterested(_asyncReadCallback); - } - - public void blockingReadFillInterested() - { - // We try fillInterested here because of SSL and - // spurious wakeups. With blocking reads, we read in a loop - // that tries to read/parse content and blocks waiting if there is - // none available. The loop can be woken up by incoming encrypted - // bytes, which due to SSL might not produce any decrypted bytes. - // Thus the loop needs to register fill interest again. However if - // the loop is woken up spuriously, then the register interest again - // can result in a pending read exception, unless we use tryFillInterested. - getEndPoint().tryFillInterested(_blockingReadCallback); - } - - public void blockingReadFailure(Throwable e) - { - _blockingReadCallback.failed(e); + getEndPoint().tryFillInterested(_asyncReadCallback); } @Override @@ -687,44 +652,30 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http } } - private class BlockingReadCallback implements Callback - { - @Override - public void succeeded() - { - _input.unblock(); - } - - @Override - public void failed(Throwable x) - { - _input.failed(x); - } - - @Override - public InvocationType getInvocationType() - { - // This callback does not block, rather it wakes up the - // thread that is blocked waiting on the read. - return InvocationType.NON_BLOCKING; - } - } - private class AsyncReadCallback implements Callback { @Override public void succeeded() { - if (_channel.getState().onReadPossible()) + if (_channel.getRequest().getHttpInput().onContentProducible()) _channel.handle(); } @Override public void failed(Throwable x) { - if (_input.failed(x)) + if (_channel.failed(x)) _channel.handle(); } + + @Override + public InvocationType getInvocationType() + { + // This callback does not block when the HttpInput is in blocking mode, + // rather it wakes up the thread that is blocked waiting on the read; + // but it can if it is in async mode, hence the varying InvocationType. + return _channel.getRequest().getHttpInput().isAsync() ? InvocationType.BLOCKING : InvocationType.NON_BLOCKING; + } } private class SendCallback extends IteratingCallback diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpInput.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpInput.java index e9dceaf08f7..d1aaa1d5c8f 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpInput.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpInput.java @@ -21,21 +21,15 @@ package org.eclipse.jetty.server; import java.io.IOException; import java.nio.ByteBuffer; import java.util.Objects; -import java.util.concurrent.Semaphore; -import java.util.concurrent.TimeUnit; -import java.util.function.Consumer; import javax.servlet.ReadListener; import javax.servlet.ServletInputStream; -import org.eclipse.jetty.http.BadMessageException; -import org.eclipse.jetty.http.HttpStatus; -import org.eclipse.jetty.io.EofException; import org.eclipse.jetty.server.handler.ContextHandler; import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.component.Destroyable; -import org.eclipse.jetty.util.log.Log; -import org.eclipse.jetty.util.log.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** *

While this class is-a Runnable, it should never be dispatched in it's own thread. It is a runnable only so that the calling thread can use {@link @@ -43,28 +37,23 @@ import org.eclipse.jetty.util.log.Logger; */ public class HttpInput extends ServletInputStream implements Runnable { - private static final Logger LOG = Log.getLogger(HttpInput.class); + private static final Logger LOG = LoggerFactory.getLogger(HttpInput.class); private final byte[] _oneByteBuffer = new byte[1]; + private final BlockingContentProducer _blockingContentProducer; + private final AsyncContentProducer _asyncContentProducer; private final HttpChannelState _channelState; - private final ContentProducer _contentProducer = new ContentProducer(); - // This semaphore is only used in blocking mode, and a standard lock with a condition variable - // cannot work here because there is a race condition between the _contentProducer.read() call - // and the blockForContent() call: content can be produced any time between these two calls so - // the call to unblock() done by the content-producing thread to wake up the user thread executing read() - // must 'remember' the unblock() call, such as if it happens before the thread executing read() reaches the - // blockForContent() method, it will not get stuck in it forever waiting for an unblock() call it missed. - private final Semaphore _semaphore = new Semaphore(0); - - private Eof _eof = Eof.NOT_YET; - private Throwable _error; + private ContentProducer _contentProducer; + private boolean _consumedEof; private ReadListener _readListener; - private long _firstByteTimeStamp = Long.MIN_VALUE; public HttpInput(HttpChannelState state) { _channelState = state; + _asyncContentProducer = new AsyncContentProducer(state.getHttpChannel()); + _blockingContentProducer = new BlockingContentProducer(_asyncContentProducer); + _contentProducer = _blockingContentProducer; } /* HttpInput */ @@ -72,12 +61,11 @@ public class HttpInput extends ServletInputStream implements Runnable public void recycle() { if (LOG.isDebugEnabled()) - LOG.debug("recycle"); - _contentProducer.recycle(); - _eof = Eof.NOT_YET; - _error = null; + LOG.debug("recycle {}", this); + _blockingContentProducer.recycle(); + _contentProducer = _blockingContentProducer; + _consumedEof = false; _readListener = null; - _firstByteTimeStamp = Long.MIN_VALUE; } /** @@ -95,6 +83,8 @@ public class HttpInput extends ServletInputStream implements Runnable */ public void setInterceptor(Interceptor interceptor) { + if (LOG.isDebugEnabled()) + LOG.debug("setting interceptor to {}", interceptor); _contentProducer.setInterceptor(interceptor); } @@ -108,157 +98,52 @@ public class HttpInput extends ServletInputStream implements Runnable { Interceptor currentInterceptor = _contentProducer.getInterceptor(); if (currentInterceptor == null) - _contentProducer.setInterceptor(interceptor); - else - _contentProducer.setInterceptor(new ChainedInterceptor(currentInterceptor, interceptor)); - } - - /** - * Called by channel when asynchronous IO needs to produce more content - */ - public void asyncReadProduce() - { - if (LOG.isDebugEnabled()) - LOG.debug("asyncReadProduce {}", _contentProducer); - produceContent(); - } - - /** - * Adds some content to this input stream. - * - * @param content the content to add - */ - public void addContent(Content content) - { - if (LOG.isDebugEnabled()) - LOG.debug("addContent {} {}", content, _contentProducer); - if (_firstByteTimeStamp == Long.MIN_VALUE) { - _firstByteTimeStamp = System.nanoTime(); - if (_firstByteTimeStamp == Long.MIN_VALUE) - _firstByteTimeStamp++; + if (LOG.isDebugEnabled()) + LOG.debug("adding single interceptor: {}", interceptor); + _contentProducer.setInterceptor(interceptor); + } + else + { + ChainedInterceptor chainedInterceptor = new ChainedInterceptor(currentInterceptor, interceptor); + if (LOG.isDebugEnabled()) + LOG.debug("adding chained interceptor: {}", chainedInterceptor); + _contentProducer.setInterceptor(chainedInterceptor); } - _contentProducer.addContent(content); - if (isAsync() && _contentProducer.available(this::produceContent) > 0) - _channelState.onContentAdded(); - } - - public boolean hasContent() - { - return _contentProducer.hasRawContent(); - } - - // There are 3 sources which can call this method in parallel: - // 1) HTTP2 read() that has a demand served on the app thread; - // 2) HTTP2 read() that has a demand served by a server thread; - // 3) onIdleTimeout called by a server thread; - // which means the semaphore can have up to 2 permits. - public void unblock() - { - if (LOG.isDebugEnabled()) - LOG.debug("signalling blocked thread to wake up"); - if (!isError() && !_eof.isEof() && _semaphore.availablePermits() > 1) - throw new AssertionError("Only one thread should call unblock and only if we are blocked"); - _semaphore.release(); - } - - public long getContentLength() - { - return _contentProducer.getRawContentArrived(); } public long getContentReceived() { - return getContentLength(); - } - - /** - * This method should be called to signal that an EOF has been detected before all the expected content arrived. - *

- * Typically this will result in an EOFException being thrown from a subsequent read rather than a -1 return. - * - * @return true if content channel woken for read - */ - public boolean earlyEOF() - { - if (LOG.isDebugEnabled()) - LOG.debug("received early EOF"); - _eof = Eof.EARLY_EOF; - if (isAsync()) - return _channelState.onContentAdded(); - unblock(); - return false; - } - - /** - * This method should be called to signal that all the expected content arrived. - * - * @return true if content channel woken for read - */ - public boolean eof() - { - if (LOG.isDebugEnabled()) - LOG.debug("received EOF"); - _eof = Eof.EOF; - if (isAsync()) - return _channelState.onContentAdded(); - unblock(); - return false; + return _contentProducer.getRawContentArrived(); } public boolean consumeAll() { if (LOG.isDebugEnabled()) LOG.debug("consume all"); - _contentProducer.consumeTransformedContent(this::failContent, new IOException("Unconsumed content")); - if (_eof.isEof()) - _eof = Eof.CONSUMED_EOF; + boolean atEof = _contentProducer.consumeAll(new IOException("Unconsumed content")); + if (atEof) + _consumedEof = true; if (isFinished()) return !isError(); - _eof = Eof.EARLY_EOF; return false; } public boolean isError() { - return _error != null; + boolean error = _contentProducer.isError(); + if (LOG.isDebugEnabled()) + LOG.debug("isError = {}", error); + return error; } public boolean isAsync() - { - return _readListener != null; - } - - public boolean onIdleTimeout(Throwable x) - { - boolean neverDispatched = _channelState.isIdle(); - boolean waitingForContent = _contentProducer.available(this::produceContent) == 0 && !_eof.isEof(); - if ((waitingForContent || neverDispatched) && !isError()) - { - x.addSuppressed(new Throwable("HttpInput idle timeout")); - _error = x; - if (isAsync()) - return _channelState.onContentAdded(); - unblock(); - } - return false; - } - - public boolean failed(Throwable x) { if (LOG.isDebugEnabled()) - LOG.debug("failed " + x); - if (_error != null && _error != x) - _error.addSuppressed(x); - else - _error = x; - - if (isAsync()) - return _channelState.onContentAdded(); - unblock(); - return false; + LOG.debug("isAsync read listener = " + _readListener); + return _readListener != null; } /* ServletInputStream */ @@ -266,7 +151,7 @@ public class HttpInput extends ServletInputStream implements Runnable @Override public boolean isFinished() { - boolean finished = !_contentProducer.hasRawContent() && _eof.isConsumed(); + boolean finished = _consumedEof; if (LOG.isDebugEnabled()) LOG.debug("isFinished? {}", finished); return finished; @@ -275,22 +160,24 @@ public class HttpInput extends ServletInputStream implements Runnable @Override public boolean isReady() { - // calling _contentProducer.available() might change the _eof state, so the following test order matters - if (_contentProducer.available(this::produceContent) > 0 || _eof.isEof()) + boolean ready = _contentProducer.isReady(); + if (!ready) { if (LOG.isDebugEnabled()) - LOG.debug("isReady? true"); - return true; + LOG.debug("isReady? false"); + return false; } + if (LOG.isDebugEnabled()) - LOG.debug("isReady? false"); - _channelState.onReadUnready(); - return false; + LOG.debug("isReady? true"); + return true; } @Override public void setReadListener(ReadListener readListener) { + if (LOG.isDebugEnabled()) + LOG.debug("setting read listener to {}", readListener); if (_readListener != null) throw new IllegalStateException("ReadListener already set"); _readListener = Objects.requireNonNull(readListener); @@ -298,40 +185,15 @@ public class HttpInput extends ServletInputStream implements Runnable if (!_channelState.isAsyncStarted()) throw new IllegalStateException("Async not started"); - if (LOG.isDebugEnabled()) - LOG.debug("setReadListener error=" + _error + " eof=" + _eof + " " + _contentProducer); - boolean woken; - if (isError()) - { - woken = _channelState.onReadReady(); - } - else - { - if (_contentProducer.available(this::produceContent) > 0) - { - woken = _channelState.onReadReady(); - } - else if (_eof.isEof()) - { - woken = _channelState.onReadEof(); - } - else - { - _channelState.onReadUnready(); - woken = false; - } - } - - if (LOG.isDebugEnabled()) - LOG.debug("setReadListener woken=" + woken); - if (woken) - scheduleReadListenerNotification(); + _contentProducer = _asyncContentProducer; + // trigger content production + if (isReady() && _channelState.onReadEof()) // onReadEof b/c we want to transition from WAITING to WOKEN + scheduleReadListenerNotification(); // this is needed by AsyncServletIOTest.testStolenAsyncRead } - private void scheduleReadListenerNotification() + public boolean onContentProducible() { - HttpChannel channel = _channelState.getHttpChannel(); - channel.execute(channel); + return _contentProducer.onContentProducible(); } @Override @@ -346,117 +208,76 @@ public class HttpInput extends ServletInputStream implements Runnable @Override public int read(byte[] b, int off, int len) throws IOException { - // Calculate minimum request rate for DOS protection - long minRequestDataRate = _channelState.getHttpChannel().getHttpConfiguration().getMinRequestDataRate(); - if (minRequestDataRate > 0 && _firstByteTimeStamp != Long.MIN_VALUE) - { - long period = System.nanoTime() - _firstByteTimeStamp; - if (period > 0) - { - long minimumData = minRequestDataRate * TimeUnit.NANOSECONDS.toMillis(period) / TimeUnit.SECONDS.toMillis(1); - if (_contentProducer.getRawContentArrived() < minimumData) - { - BadMessageException bad = new BadMessageException(HttpStatus.REQUEST_TIMEOUT_408, - String.format("Request content data rate < %d B/s", minRequestDataRate)); - if (_channelState.isResponseCommitted()) - _channelState.getHttpChannel().abort(bad); - throw bad; - } - } - } + // Calculate minimum request rate for DoS protection + _contentProducer.checkMinDataRate(); - while (true) + Content content = _contentProducer.nextContent(); + if (content == null) + throw new IllegalStateException("read on unready input"); + if (!content.isSpecial()) { - // The semaphore's permits must be drained before we call read() because: - // 1) _contentProducer.read() may call unblock() which enqueues a permit even if the content was produced - // by the exact thread that called HttpInput.read(), hence leaving around an unconsumed permit that would - // be consumed the next time HttpInput.read() is called, mistakenly believing that content was produced. - // 2) HTTP2 demand served asynchronously does call unblock which does enqueue a permit in the semaphore; - // this permit would then be mistakenly consumed by the next call to blockForContent() once all the produced - // content got consumed. - if (!isAsync()) - _semaphore.drainPermits(); - int read = _contentProducer.read(this::produceContent, b, off, len); + int read = content.get(b, off, len); if (LOG.isDebugEnabled()) LOG.debug("read produced {} byte(s)", read); - if (read > 0) - return read; - - if (LOG.isDebugEnabled()) - LOG.debug("read error = " + _error); - if (_error != null) - throw new IOException(_error); - - if (LOG.isDebugEnabled()) - LOG.debug("read EOF = {}", _eof); - if (_eof.isEarly()) - throw new EofException("Early EOF"); - - if (LOG.isDebugEnabled()) - LOG.debug("read async = {}", isAsync()); - if (!isAsync()) - { - if (_eof.isEof()) - { - _eof = Eof.CONSUMED_EOF; - if (LOG.isDebugEnabled()) - LOG.debug("read on EOF, switching to CONSUMED_EOF and returning"); - return -1; - } - if (LOG.isDebugEnabled()) - LOG.debug("read blocked"); - blockForContent(); - if (LOG.isDebugEnabled()) - LOG.debug("read unblocked"); - } - else - { - if (_eof.isEof()) - { - _eof = Eof.CONSUMED_EOF; - boolean wasInAsyncWait = _channelState.onReadEof(); - if (LOG.isDebugEnabled()) - LOG.debug("async read on EOF (was in async wait? {}), switching to CONSUMED_EOF and returning", wasInAsyncWait); - if (wasInAsyncWait) - scheduleReadListenerNotification(); - return -1; - } - else - { - //TODO returning 0 breaks the InputStream contract. Shouldn't IOException be thrown instead? - _channelState.getHttpChannel().onAsyncWaitForContent(); // switches on fill interested - return 0; - } - } + if (content.isEmpty()) + _contentProducer.reclaim(content); + return read; } + + Throwable error = content.getError(); + if (LOG.isDebugEnabled()) + LOG.debug("read error = " + error); + if (error != null) + { + if (error instanceof IOException) + throw (IOException)error; + throw new IOException(error); + } + + if (content.isEof()) + { + if (LOG.isDebugEnabled()) + LOG.debug("read at EOF, setting consumed EOF to true"); + _consumedEof = true; + // If EOF do we need to wake for allDataRead callback? + if (onContentProducible()) + scheduleReadListenerNotification(); + return -1; + } + + throw new AssertionError("no data, no error and not EOF"); + } + + private void scheduleReadListenerNotification() + { + HttpChannel channel = _channelState.getHttpChannel(); + channel.execute(channel); + } + + /** + * Check if this HttpInput instance has content stored internally, without fetching/parsing + * anything from the underlying channel. + * @return true if the input contains content, false otherwise. + */ + public boolean hasContent() + { + // Do not call _contentProducer.available() as it calls HttpChannel.produceContent() + // which is forbidden by this method's contract. + boolean hasContent = _contentProducer.hasContent(); + if (LOG.isDebugEnabled()) + LOG.debug("hasContent = {}", hasContent); + return hasContent; } @Override public int available() { - int available = _contentProducer.available(this::produceContent); + int available = _contentProducer.available(); if (LOG.isDebugEnabled()) LOG.debug("available = {}", available); return available; } - private void blockForContent() - { - try - { - _channelState.getHttpChannel().onBlockWaitForContent(); // switches on fill interested - if (LOG.isDebugEnabled()) - LOG.debug("waiting for signal to wake up"); - _semaphore.acquire(); - if (LOG.isDebugEnabled()) - LOG.debug("signalled to wake up"); - } - catch (Throwable x) - { - _channelState.getHttpChannel().onBlockWaitForContentFailure(x); - } - } - /* Runnable */ /* @@ -466,23 +287,40 @@ public class HttpInput extends ServletInputStream implements Runnable @Override public void run() { - if (!_contentProducer.hasRawContent()) + Content content = _contentProducer.nextContent(); + if (LOG.isDebugEnabled()) + LOG.debug("running on content {}", content); + // The nextContent() call could return null if the transformer ate all + // the raw bytes without producing any transformed content. + if (content == null) + return; + + // This check is needed when a request is started async but no read listener is registered. + if (_readListener == null) { if (LOG.isDebugEnabled()) - LOG.debug("running has no raw content; error: {}, EOF = {}", _error, _eof); - if (_error != null || _eof.isEarly()) + LOG.debug("running without a read listener"); + onContentProducible(); + return; + } + + if (content.isSpecial()) + { + Throwable error = content.getError(); + if (error != null) { + if (LOG.isDebugEnabled()) + LOG.debug("running has error: {}", (Object)error); // TODO is this necessary to add here? _channelState.getHttpChannel().getResponse().getHttpFields().add(HttpConnection.CONNECTION_CLOSE); - if (_error != null) - _readListener.onError(_error); - else - _readListener.onError(new EofException("Early EOF")); + _readListener.onError(error); } - else if (_eof.isEof()) + else if (content.isEof()) { try { + if (LOG.isDebugEnabled()) + LOG.debug("running at EOF"); _readListener.onAllDataRead(); } catch (Throwable x) @@ -492,12 +330,11 @@ public class HttpInput extends ServletInputStream implements Runnable _readListener.onError(x); } } - // else: !hasContent() && !error && !EOF -> no-op } else { if (LOG.isDebugEnabled()) - LOG.debug("running has raw content"); + LOG.debug("running has content"); try { _readListener.onDataAvailable(); @@ -511,244 +348,24 @@ public class HttpInput extends ServletInputStream implements Runnable } } - private void produceContent() + @Override + public String toString() { - if (LOG.isDebugEnabled()) - LOG.debug("produceContent {}", _contentProducer); - _channelState.getHttpChannel().produceContent(); + return getClass().getSimpleName() + "@" + hashCode() + + " cs=" + _channelState + + " cp=" + _contentProducer + + " eof=" + _consumedEof; } - private void failContent(Throwable failure) + public interface Interceptor { - if (LOG.isDebugEnabled()) - LOG.debug("failContent {} - " + failure, _contentProducer); - _channelState.getHttpChannel().failContent(failure); - } - - private enum Eof - { - NOT_YET(false, false, false), - EOF(true, false, false), - CONSUMED_EOF(true, true, false), - EARLY_EOF(true, false, true), - ; - - private final boolean _eof; - private final boolean _consumed; - private final boolean _early; - - Eof(boolean eof, boolean consumed, boolean early) - { - _eof = eof; - _consumed = consumed; - _early = early; - } - - boolean isEof() - { - return _eof; - } - - boolean isConsumed() - { - return _consumed; - } - - boolean isEarly() - { - return _early; - } - } - - // All methods of this class have to be synchronized because a HTTP2 reset can call consumeTransformedContent() - // while nextNonEmptyContent() is executing, hence all accesses to _rawContent and _transformedContent must be - // mutually excluded. - // TODO: maybe the locking could be more fine grained, by only protecting the if (null|!null) blocks? - private static class ContentProducer - { - // Note: _rawContent can never be null for as long as _transformedContent is not null. - private Content _rawContent; - private Content _transformedContent; - private long _rawContentArrived; - private Interceptor _interceptor; - private Throwable _consumeFailure; - - void recycle() - { - synchronized (this) - { - if (LOG.isDebugEnabled()) - LOG.debug("recycle {}", this); - if (_transformedContent == _rawContent) - _transformedContent = null; - if (_transformedContent != null) - _transformedContent.failed(null); - _transformedContent = null; - if (_rawContent != null) - _rawContent.failed(null); - _rawContent = null; - _rawContentArrived = 0L; - if (_interceptor instanceof Destroyable) - ((Destroyable)_interceptor).destroy(); - _interceptor = null; - _consumeFailure = null; - } - } - - long getRawContentArrived() - { - synchronized (this) - { - return _rawContentArrived; - } - } - - boolean hasRawContent() - { - synchronized (this) - { - return _rawContent != null; - } - } - - Interceptor getInterceptor() - { - synchronized (this) - { - return _interceptor; - } - } - - void setInterceptor(Interceptor interceptor) - { - synchronized (this) - { - this._interceptor = interceptor; - } - } - - void addContent(Content content) - { - synchronized (this) - { - if (LOG.isDebugEnabled()) - LOG.debug("{} addContent {}", this, content); - if (content == null) - throw new AssertionError("Cannot add null content"); - if (_consumeFailure != null) - { - content.failed(_consumeFailure); - return; - } - if (_rawContent != null) - throw new AssertionError("Cannot add new content while current one hasn't been processed"); - - _rawContent = content; - _rawContentArrived += content.remaining(); - } - } - - void consumeTransformedContent(Consumer failRawContent, Throwable failure) - { - synchronized (this) - { - if (LOG.isDebugEnabled()) - LOG.debug("{} consumeTransformedContent", this); - // start by depleting the current _transformedContent - if (_transformedContent != null) - { - _transformedContent.skip(_transformedContent.remaining()); - if (_transformedContent != _rawContent) - _transformedContent.failed(failure); - _transformedContent = null; - } - - // don't bother transforming content, directly deplete the raw one - if (_rawContent != null) - { - _rawContent.skip(_rawContent.remaining()); - _rawContent.failed(failure); - _rawContent = null; - } - - // fail whatever other content the producer may have - _consumeFailure = failure; - failRawContent.accept(failure); - } - } - - int available(Runnable rawContentProducer) - { - synchronized (this) - { - Content content = nextNonEmptyContent(rawContentProducer); - return content == null ? 0 : content.remaining(); - } - } - - int read(Runnable rawContentProducer, byte[] b, int off, int len) - { - synchronized (this) - { - if (LOG.isDebugEnabled()) - LOG.debug("{} read", this); - Content content = nextNonEmptyContent(rawContentProducer); - return content == null ? 0 : content.get(b, off, len); - } - } - - private Content nextNonEmptyContent(Runnable rawContentProducer) - { - if (_rawContent == null) - { - rawContentProducer.run(); - if (_rawContent == null) - return null; - } - - if (_transformedContent != null && _transformedContent.isEmpty()) - { - if (_transformedContent != _rawContent) - _transformedContent.succeeded(); - _transformedContent = null; - } - - while (_transformedContent == null) - { - if (_interceptor != null) - _transformedContent = _interceptor.readFrom(_rawContent); - else - _transformedContent = _rawContent; - - if (_transformedContent != null && _transformedContent.isEmpty()) - { - if (_transformedContent != _rawContent) - _transformedContent.succeeded(); - _transformedContent = null; - } - - if (_transformedContent == null) - { - if (_rawContent.isEmpty()) - { - _rawContent.succeeded(); - _rawContent = null; - rawContentProducer.run(); - if (_rawContent == null) - return null; - } - } - } - - return _transformedContent; - } - - @Override - public String toString() - { - return getClass().getSimpleName() + "[i=" + _interceptor + ",b=" + _rawContentArrived + - ",r=" + _rawContent + ",t=" + _transformedContent + "]"; - } + /** + * @param content The content to be intercepted. + * The content will be modified with any data the interceptor consumes, but there is no requirement + * that all the data is consumed by the interceptor. + * @return The intercepted content or null if interception is completed for that content. + */ + Content readFrom(Content content); } /** @@ -795,19 +412,22 @@ public class HttpInput extends ServletInputStream implements Runnable if (_next instanceof Destroyable) ((Destroyable)_next).destroy(); } + + @Override + public String toString() + { + return getClass().getSimpleName() + "@" + hashCode() + " [p=" + _prev + ",n=" + _next + "]"; + } } - public interface Interceptor - { - /** - * @param content The content to be intercepted. - * The content will be modified with any data the interceptor consumes, but there is no requirement - * that all the data is consumed by the interceptor. - * @return The intercepted content or null if interception is completed for that content. - */ - Content readFrom(Content content); - } - + /** + * A content represents the production of a {@link HttpChannel} returned by {@link HttpChannel#produceContent()}. + * There are two fundamental types of content: special and non-special. + * Non-special content always wraps a byte buffer that can be consumed and must be recycled once it is empty, either + * via {@link #succeeded()} or {@link #failed(Throwable)}. + * Special content indicates a special event, like EOF or an error and never wraps a byte buffer. Calling + * {@link #succeeded()} or {@link #failed(Throwable)} on those have no effect. + */ public static class Content implements Callback { protected final ByteBuffer _content; @@ -817,6 +437,10 @@ public class HttpInput extends ServletInputStream implements Runnable _content = content; } + /** + * Get the wrapped byte buffer. Throws {@link IllegalStateException} if the content is special. + * @return the wrapped byte buffer. + */ public ByteBuffer getByteBuffer() { return _content; @@ -828,6 +452,13 @@ public class HttpInput extends ServletInputStream implements Runnable return InvocationType.NON_BLOCKING; } + /** + * Read the wrapped byte buffer. Throws {@link IllegalStateException} if the content is special. + * @param buffer The array into which bytes are to be written. + * @param offset The offset within the array of the first byte to be written. + * @param length The maximum number of bytes to be written to the given array. + * @return The amount of bytes read from the buffer. + */ public int get(byte[] buffer, int offset, int length) { length = Math.min(_content.remaining(), length); @@ -835,6 +466,11 @@ public class HttpInput extends ServletInputStream implements Runnable return length; } + /** + * Skip some bytes from the buffer. Has no effect on a special content. + * @param length How many bytes to skip. + * @return How many bytes were skipped. + */ public int skip(int length) { length = Math.min(_content.remaining(), length); @@ -842,26 +478,207 @@ public class HttpInput extends ServletInputStream implements Runnable return length; } + /** + * Check if there is at least one byte left in the buffer. + * Always false on a special content. + * @return true if there is at least one byte left in the buffer. + */ public boolean hasContent() { return _content.hasRemaining(); } + /** + * Get the number of bytes remaining in the buffer. + * Always 0 on a special content. + * @return the number of bytes remaining in the buffer. + */ public int remaining() { return _content.remaining(); } + /** + * Check if the buffer is empty. + * Always true on a special content. + * @return true if there is 0 byte left in the buffer. + */ public boolean isEmpty() { return !_content.hasRemaining(); } + /** + * Check if the content is special. + * @return true if the content is special, false otherwise. + */ + public boolean isSpecial() + { + return false; + } + + /** + * Check if EOF was reached. Both special and non-special content + * can have this flag set to true but in the case of non-special content, + * this can be interpreted as a hint as it is always going to be followed + * by another content that is both special and EOF. + * @return true if EOF was reached, false otherwise. + */ + public boolean isEof() + { + return false; + } + + /** + * Get the reported error. Only special contents can have an error. + * @return the error or null if there is none. + */ + public Throwable getError() + { + return null; + } + @Override public String toString() { - return String.format("Content@%x{%s}", hashCode(), BufferUtil.toDetailString(_content)); + return String.format("%s@%x{%s,spc=%s,eof=%s,err=%s}", getClass().getSimpleName(), hashCode(), + BufferUtil.toDetailString(_content), isSpecial(), isEof(), getError()); } } + /** + * Simple non-special content wrapper allow overriding the EOF flag. + */ + public static class WrappingContent extends Content + { + private final Content _delegate; + private final boolean _eof; + + public WrappingContent(Content delegate, boolean eof) + { + super(delegate.getByteBuffer()); + _delegate = delegate; + _eof = eof; + } + + @Override + public boolean isEof() + { + return _eof; + } + + @Override + public void succeeded() + { + _delegate.succeeded(); + } + + @Override + public void failed(Throwable x) + { + _delegate.failed(x); + } + + @Override + public InvocationType getInvocationType() + { + return _delegate.getInvocationType(); + } + } + + /** + * Abstract class that implements the standard special content behavior. + */ + public abstract static class SpecialContent extends Content + { + public SpecialContent() + { + super(null); + } + + @Override + public final ByteBuffer getByteBuffer() + { + throw new IllegalStateException(this + " has no buffer"); + } + + @Override + public final int get(byte[] buffer, int offset, int length) + { + throw new IllegalStateException(this + " has no buffer"); + } + + @Override + public final int skip(int length) + { + return 0; + } + + @Override + public final boolean hasContent() + { + return false; + } + + @Override + public final int remaining() + { + return 0; + } + + @Override + public final boolean isEmpty() + { + return true; + } + + @Override + public final boolean isSpecial() + { + return true; + } + } + + /** + * EOF special content. + */ + public static final class EofContent extends SpecialContent + { + @Override + public boolean isEof() + { + return true; + } + + @Override + public String toString() + { + return getClass().getSimpleName(); + } + } + + /** + * Error special content. + */ + public static final class ErrorContent extends SpecialContent + { + private final Throwable _error; + + public ErrorContent(Throwable error) + { + _error = error; + } + + @Override + public Throwable getError() + { + return _error; + } + + @Override + public String toString() + { + return getClass().getSimpleName() + " [" + _error + "]"; + } + } } diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpInputState.puml b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpInputState.puml new file mode 100644 index 00000000000..0ef1896b5fe --- /dev/null +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpInputState.puml @@ -0,0 +1,16 @@ +@startuml + +IDLE: +READY: +UNREADY: + +[*] --> IDLE + +IDLE --> UNREADY : isReady +IDLE -right->READY : isReady + +UNREADY -up-> READY : ASYNC onContentProducible + +READY -left->IDLE : nextContent + +@enduml diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpInput_async.puml b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpInput_async.puml new file mode 100644 index 00000000000..c520361faef --- /dev/null +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpInput_async.puml @@ -0,0 +1,114 @@ +@startuml +title "HttpInput" + +participant AsyncContentDelivery as "[async\ncontent\ndelivery]" +participant HttpChannel as "Http\nChannel\n" +participant HttpChannelState as "Http\nChannel\nState" +participant HttpInputInterceptor as "Http\nInput.\nInterceptor" +participant AsyncContentProducer as "Async\nContent\nProducer" +participant HttpInput as "Http\nInput\n" +participant Application as "\nApplication\n" + +autoactivate on + +== Async Read == + +Application->HttpInput: read +activate Application + HttpInput->AsyncContentProducer: nextContent + AsyncContentProducer->AsyncContentProducer: next\nTransformed\nContent + AsyncContentProducer->HttpChannel: produceContent + return raw content or null + alt if raw content is not null + AsyncContentProducer->HttpInputInterceptor: readFrom + return transformed content + end + return + alt if transformed content is not null + AsyncContentProducer->HttpChannelState: onReadIdle + return + end + return content or null + note over HttpInput + throw ISE + if content + is null + end note + HttpInput->AsyncContentProducer: reclaim + return +return +deactivate Application + +== isReady == + +Application->HttpInput: isReady +activate Application + HttpInput->AsyncContentProducer: isReady + AsyncContentProducer->AsyncContentProducer: next\nTransformed\nContent + AsyncContentProducer->HttpChannel: produceContent + return raw content or null + alt if raw content is not null + AsyncContentProducer->HttpInputInterceptor: readFrom + return transformed content + end + return + alt if transformed content is not null + AsyncContentProducer->HttpChannelState: onContentAdded + return + else transformed content is null + AsyncContentProducer->HttpChannelState: onReadUnready + return + AsyncContentProducer->HttpChannel: needContent + return + alt if needContent returns true + AsyncContentProducer->AsyncContentProducer: next\nTransformed\nContent + return + alt if transformed content is not null + AsyncContentProducer->HttpChannelState: onContentAdded + return + end + end + end + return boolean\n[transformed\ncontent is not null] +return +deactivate Application + +alt if content arrives + AsyncContentDelivery->HttpInput: onContentProducible + HttpInput->AsyncContentProducer: onContentProducible + alt if not at EOF + AsyncContentProducer->HttpChannelState: onReadReady + return true if woken + else if at EOF + AsyncContentProducer->HttpChannelState: onReadEof + return true if woken + end + return true if woken + return true if woken + alt onContentProducible returns true + AsyncContentDelivery->HttpChannel: execute(HttpChannel) + return + end +end + +||| + +== available == + +Application->HttpInput: available +activate Application + HttpInput->AsyncContentProducer: available + AsyncContentProducer->AsyncContentProducer: next\nTransformed\nContent + AsyncContentProducer->HttpChannel: produceContent + return raw content or null + alt if raw content is not null + AsyncContentProducer->HttpInputInterceptor: readFrom + return transformed content + end + return + return content size or\n0 if content is null +return +deactivate Application + +||| +@enduml diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpInput_blocking.puml b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpInput_blocking.puml new file mode 100644 index 00000000000..06cb82c4cfd --- /dev/null +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpInput_blocking.puml @@ -0,0 +1,64 @@ +@startuml +title "HttpInput" + +participant AsyncContentDelivery as "[async\ncontent\ndelivery]" +participant HttpChannel as "Http\nChannel\n" +participant HttpChannelState as "Http\nChannel\nState" +participant AsyncContentProducer as "Async\nContent\nProducer" +participant Semaphore as "\nSemaphore\n" +participant BlockingContentProducer as "Blocking\nContent\nProducer" +participant HttpInput as "Http\nInput\n" +participant Application as "\nApplication\n" + +autoactivate on + +== Blocking Read == + +Application->HttpInput: read +activate Application + HttpInput->BlockingContentProducer: nextContent + loop + BlockingContentProducer->AsyncContentProducer: nextContent + AsyncContentProducer->AsyncContentProducer: nextTransformedContent + AsyncContentProducer->HttpChannel: produceContent + return + return + alt content is not null + AsyncContentProducer->HttpChannelState: onReadIdle + return + end + return content or null + alt content is null + BlockingContentProducer->HttpChannelState: onReadUnready + return + BlockingContentProducer->HttpChannel: needContent + return + alt needContent returns false + BlockingContentProducer->Semaphore: acquire + return + else needContent returns true + note over BlockingContentProducer + continue loop + end note + end + else content is not null + return non-null content + end + end + ' return from BlockingContentProducer: nextContent + HttpInput->BlockingContentProducer: reclaim + BlockingContentProducer->AsyncContentProducer: reclaim + return + return +return +deactivate Application + +alt if content arrives + AsyncContentDelivery->HttpInput: wakeup + HttpInput->BlockingContentProducer: wakeup + BlockingContentProducer->Semaphore: release + return + return false + return false +end +@enduml diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java b/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java index 5e3908048fb..934cd2e6a37 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java @@ -749,7 +749,7 @@ public class Request implements HttpServletRequest public long getContentRead() { - return _input.getContentLength(); + return _input.getContentReceived(); } @Override diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/AsyncContentProducerTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/AsyncContentProducerTest.java new file mode 100644 index 00000000000..379a9386431 --- /dev/null +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/AsyncContentProducerTest.java @@ -0,0 +1,340 @@ +// +// ======================================================================== +// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under +// the terms of the Eclipse Public License 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0 +// +// This Source Code may also be made available under the following +// Secondary Licenses when the conditions for such availability set +// forth in the Eclipse Public License, v. 2.0 are satisfied: +// the Apache License v2.0 which is available at +// https://www.apache.org/licenses/LICENSE-2.0 +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +package org.eclipse.jetty.server; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.util.concurrent.BrokenBarrierException; +import java.util.concurrent.CyclicBarrier; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.zip.GZIPOutputStream; + +import org.eclipse.jetty.io.ArrayByteBufferPool; +import org.eclipse.jetty.io.EofException; +import org.eclipse.jetty.server.handler.gzip.GzipHttpInputInterceptor; +import org.eclipse.jetty.util.compression.CompressionPool; +import org.eclipse.jetty.util.compression.InflaterPool; +import org.hamcrest.core.Is; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; +import static org.hamcrest.Matchers.nullValue; + +public class AsyncContentProducerTest +{ + private ScheduledExecutorService scheduledExecutorService; + private InflaterPool inflaterPool; + + @BeforeEach + public void setUp() + { + scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(); + inflaterPool = new InflaterPool(-1, true); + } + + @AfterEach + public void tearDown() + { + scheduledExecutorService.shutdownNow(); + } + + @Test + public void testAsyncContentProducerNoInterceptor() throws Exception + { + ByteBuffer[] buffers = new ByteBuffer[3]; + buffers[0] = ByteBuffer.wrap("1 hello 1".getBytes(StandardCharsets.ISO_8859_1)); + buffers[1] = ByteBuffer.wrap("2 howdy 2".getBytes(StandardCharsets.ISO_8859_1)); + buffers[2] = ByteBuffer.wrap("3 hey ya 3".getBytes(StandardCharsets.ISO_8859_1)); + final int totalContentBytesCount = countRemaining(buffers); + final String originalContentString = asString(buffers); + + CyclicBarrier barrier = new CyclicBarrier(2); + + ContentProducer contentProducer = new AsyncContentProducer(new ArrayDelayedHttpChannel(buffers, new HttpInput.EofContent(), scheduledExecutorService, barrier)); + + Throwable error = readAndAssertContent(totalContentBytesCount, originalContentString, contentProducer, (buffers.length + 1) * 2, 0, 4, barrier); + assertThat(error, nullValue()); + } + + @Test + public void testAsyncContentProducerNoInterceptorWithError() throws Exception + { + ByteBuffer[] buffers = new ByteBuffer[3]; + buffers[0] = ByteBuffer.wrap("1 hello 1".getBytes(StandardCharsets.ISO_8859_1)); + buffers[1] = ByteBuffer.wrap("2 howdy 2".getBytes(StandardCharsets.ISO_8859_1)); + buffers[2] = ByteBuffer.wrap("3 hey ya 3".getBytes(StandardCharsets.ISO_8859_1)); + final int totalContentBytesCount = countRemaining(buffers); + final String originalContentString = asString(buffers); + final Throwable expectedError = new EofException("Early EOF"); + + CyclicBarrier barrier = new CyclicBarrier(2); + + ContentProducer contentProducer = new AsyncContentProducer(new ArrayDelayedHttpChannel(buffers, new HttpInput.ErrorContent(expectedError), scheduledExecutorService, barrier)); + + Throwable error = readAndAssertContent(totalContentBytesCount, originalContentString, contentProducer, (buffers.length + 1) * 2, 0, 4, barrier); + assertThat(error, Is.is(expectedError)); + } + + @Test + public void testAsyncContentProducerGzipInterceptor() throws Exception + { + ByteBuffer[] uncompressedBuffers = new ByteBuffer[3]; + uncompressedBuffers[0] = ByteBuffer.wrap("1 hello 1".getBytes(StandardCharsets.ISO_8859_1)); + uncompressedBuffers[1] = ByteBuffer.wrap("2 howdy 2".getBytes(StandardCharsets.ISO_8859_1)); + uncompressedBuffers[2] = ByteBuffer.wrap("3 hey ya 3".getBytes(StandardCharsets.ISO_8859_1)); + final int totalContentBytesCount = countRemaining(uncompressedBuffers); + final String originalContentString = asString(uncompressedBuffers); + + ByteBuffer[] buffers = new ByteBuffer[3]; + buffers[0] = gzipByteBuffer(uncompressedBuffers[0]); + buffers[1] = gzipByteBuffer(uncompressedBuffers[1]); + buffers[2] = gzipByteBuffer(uncompressedBuffers[2]); + + CyclicBarrier barrier = new CyclicBarrier(2); + + ContentProducer contentProducer = new AsyncContentProducer(new ArrayDelayedHttpChannel(buffers, new HttpInput.EofContent(), scheduledExecutorService, barrier)); + contentProducer.setInterceptor(new GzipHttpInputInterceptor(inflaterPool, new ArrayByteBufferPool(1, 1, 2), 32)); + + Throwable error = readAndAssertContent(totalContentBytesCount, originalContentString, contentProducer, (buffers.length + 1) * 2, 0, 4, barrier); + assertThat(error, nullValue()); + } + + @Test + public void testAsyncContentProducerGzipInterceptorWithTinyBuffers() throws Exception + { + ByteBuffer[] uncompressedBuffers = new ByteBuffer[3]; + uncompressedBuffers[0] = ByteBuffer.wrap("1 hello 1".getBytes(StandardCharsets.ISO_8859_1)); + uncompressedBuffers[1] = ByteBuffer.wrap("2 howdy 2".getBytes(StandardCharsets.ISO_8859_1)); + uncompressedBuffers[2] = ByteBuffer.wrap("3 hey ya 3".getBytes(StandardCharsets.ISO_8859_1)); + final int totalContentBytesCount = countRemaining(uncompressedBuffers); + final String originalContentString = asString(uncompressedBuffers); + + ByteBuffer[] buffers = new ByteBuffer[3]; + buffers[0] = gzipByteBuffer(uncompressedBuffers[0]); + buffers[1] = gzipByteBuffer(uncompressedBuffers[1]); + buffers[2] = gzipByteBuffer(uncompressedBuffers[2]); + + CyclicBarrier barrier = new CyclicBarrier(2); + + ContentProducer contentProducer = new AsyncContentProducer(new ArrayDelayedHttpChannel(buffers, new HttpInput.EofContent(), scheduledExecutorService, barrier)); + contentProducer.setInterceptor(new GzipHttpInputInterceptor(inflaterPool, new ArrayByteBufferPool(1, 1, 2), 1)); + + Throwable error = readAndAssertContent(totalContentBytesCount, originalContentString, contentProducer, totalContentBytesCount + buffers.length + 2, 25, 4, barrier); + assertThat(error, nullValue()); + } + + @Test + public void testBlockingContentProducerGzipInterceptorWithError() throws Exception + { + ByteBuffer[] uncompressedBuffers = new ByteBuffer[3]; + uncompressedBuffers[0] = ByteBuffer.wrap("1 hello 1".getBytes(StandardCharsets.ISO_8859_1)); + uncompressedBuffers[1] = ByteBuffer.wrap("2 howdy 2".getBytes(StandardCharsets.ISO_8859_1)); + uncompressedBuffers[2] = ByteBuffer.wrap("3 hey ya 3".getBytes(StandardCharsets.ISO_8859_1)); + final int totalContentBytesCount = countRemaining(uncompressedBuffers); + final String originalContentString = asString(uncompressedBuffers); + final Throwable expectedError = new Throwable("HttpInput idle timeout"); + + ByteBuffer[] buffers = new ByteBuffer[3]; + buffers[0] = gzipByteBuffer(uncompressedBuffers[0]); + buffers[1] = gzipByteBuffer(uncompressedBuffers[1]); + buffers[2] = gzipByteBuffer(uncompressedBuffers[2]); + + CyclicBarrier barrier = new CyclicBarrier(2); + + ContentProducer contentProducer = new AsyncContentProducer(new ArrayDelayedHttpChannel(buffers, new HttpInput.ErrorContent(expectedError), scheduledExecutorService, barrier)); + contentProducer.setInterceptor(new GzipHttpInputInterceptor(inflaterPool, new ArrayByteBufferPool(1, 1, 2), 32)); + + Throwable error = readAndAssertContent(totalContentBytesCount, originalContentString, contentProducer, (buffers.length + 1) * 2, 0, 4, barrier); + assertThat(error, Is.is(expectedError)); + } + + private Throwable readAndAssertContent(int totalContentBytesCount, String originalContentString, ContentProducer contentProducer, int totalContentCount, int readyCount, int notReadyCount, CyclicBarrier barrier) throws InterruptedException, BrokenBarrierException, TimeoutException + { + int readBytes = 0; + String consumedString = ""; + int nextContentCount = 0; + int isReadyFalseCount = 0; + int isReadyTrueCount = 0; + Throwable error = null; + + while (true) + { + if (contentProducer.isReady()) + isReadyTrueCount++; + else + isReadyFalseCount++; + + HttpInput.Content content = contentProducer.nextContent(); + nextContentCount++; + if (content == null) + { + barrier.await(5, TimeUnit.SECONDS); + content = contentProducer.nextContent(); + nextContentCount++; + } + assertThat(content, notNullValue()); + + if (content.isSpecial()) + { + if (content.isEof()) + break; + error = content.getError(); + break; + } + + byte[] b = new byte[content.remaining()]; + readBytes += b.length; + content.getByteBuffer().get(b); + consumedString += new String(b, StandardCharsets.ISO_8859_1); + content.skip(content.remaining()); + } + + assertThat(nextContentCount, is(totalContentCount)); + assertThat(readBytes, is(totalContentBytesCount)); + assertThat(consumedString, is(originalContentString)); + assertThat(isReadyFalseCount, is(notReadyCount)); + assertThat(isReadyTrueCount, is(readyCount)); + return error; + } + + private static int countRemaining(ByteBuffer[] byteBuffers) + { + int total = 0; + for (ByteBuffer byteBuffer : byteBuffers) + { + total += byteBuffer.remaining(); + } + return total; + } + + private static String asString(ByteBuffer[] buffers) + { + StringBuilder sb = new StringBuilder(); + for (ByteBuffer buffer : buffers) + { + byte[] b = new byte[buffer.remaining()]; + buffer.duplicate().get(b); + sb.append(new String(b, StandardCharsets.ISO_8859_1)); + } + return sb.toString(); + } + + private static ByteBuffer gzipByteBuffer(ByteBuffer uncompressedBuffer) + { + try + { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + GZIPOutputStream output = new GZIPOutputStream(baos); + + byte[] b = new byte[uncompressedBuffer.remaining()]; + uncompressedBuffer.get(b); + output.write(b); + + output.close(); + return ByteBuffer.wrap(baos.toByteArray()); + } + catch (IOException e) + { + throw new RuntimeException(e); + } + } + + private static class ArrayDelayedHttpChannel extends HttpChannel + { + private final ByteBuffer[] byteBuffers; + private final HttpInput.Content finalContent; + private final ScheduledExecutorService scheduledExecutorService; + private final CyclicBarrier barrier; + private int counter; + private volatile HttpInput.Content nextContent; + + public ArrayDelayedHttpChannel(ByteBuffer[] byteBuffers, HttpInput.Content finalContent, ScheduledExecutorService scheduledExecutorService, CyclicBarrier barrier) + { + super(new MockConnector(), new HttpConfiguration(), null, null); + this.byteBuffers = new ByteBuffer[byteBuffers.length]; + this.finalContent = finalContent; + this.scheduledExecutorService = scheduledExecutorService; + this.barrier = barrier; + for (int i = 0; i < byteBuffers.length; i++) + { + this.byteBuffers[i] = byteBuffers[i].duplicate(); + } + } + + @Override + public boolean needContent() + { + if (nextContent != null) + return true; + scheduledExecutorService.schedule(() -> + { + if (byteBuffers.length > counter) + nextContent = new HttpInput.Content(byteBuffers[counter++]); + else + nextContent = finalContent; + try + { + barrier.await(5, TimeUnit.SECONDS); + } + catch (Exception e) + { + throw new AssertionError(e); + } + }, 50, TimeUnit.MILLISECONDS); + return false; + } + + @Override + public HttpInput.Content produceContent() + { + HttpInput.Content result = nextContent; + nextContent = null; + return result; + } + + @Override + public boolean failAllContent(Throwable failure) + { + nextContent = null; + counter = byteBuffers.length; + return false; + } + + @Override + public boolean failed(Throwable x) + { + return false; + } + + @Override + protected boolean eof() + { + return false; + } + } +} diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/BlockingContentProducerTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/BlockingContentProducerTest.java new file mode 100644 index 00000000000..a8f8fdb7dfc --- /dev/null +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/BlockingContentProducerTest.java @@ -0,0 +1,320 @@ +// +// ======================================================================== +// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under +// the terms of the Eclipse Public License 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0 +// +// This Source Code may also be made available under the following +// Secondary Licenses when the conditions for such availability set +// forth in the Eclipse Public License, v. 2.0 are satisfied: +// the Apache License v2.0 which is available at +// https://www.apache.org/licenses/LICENSE-2.0 +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +package org.eclipse.jetty.server; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; +import java.util.zip.GZIPOutputStream; + +import org.eclipse.jetty.io.ArrayByteBufferPool; +import org.eclipse.jetty.io.EofException; +import org.eclipse.jetty.server.handler.gzip.GzipHttpInputInterceptor; +import org.eclipse.jetty.util.compression.InflaterPool; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.nullValue; +import static org.hamcrest.core.Is.is; + +public class BlockingContentProducerTest +{ + private ScheduledExecutorService scheduledExecutorService; + private InflaterPool inflaterPool; + + @BeforeEach + public void setUp() + { + scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(); + inflaterPool = new InflaterPool(-1, true); + } + + @AfterEach + public void tearDown() + { + scheduledExecutorService.shutdownNow(); + } + + @Test + public void testBlockingContentProducerNoInterceptor() + { + ByteBuffer[] buffers = new ByteBuffer[3]; + buffers[0] = ByteBuffer.wrap("1 hello 1".getBytes(StandardCharsets.ISO_8859_1)); + buffers[1] = ByteBuffer.wrap("2 howdy 2".getBytes(StandardCharsets.ISO_8859_1)); + buffers[2] = ByteBuffer.wrap("3 hey ya 3".getBytes(StandardCharsets.ISO_8859_1)); + final int totalContentBytesCount = countRemaining(buffers); + final String originalContentString = asString(buffers); + + AtomicReference ref = new AtomicReference<>(); + ArrayDelayedHttpChannel httpChannel = new ArrayDelayedHttpChannel(buffers, new HttpInput.EofContent(), scheduledExecutorService, () -> ref.get().onContentProducible()); + ContentProducer contentProducer = new BlockingContentProducer(new AsyncContentProducer(httpChannel)); + ref.set(contentProducer); + + Throwable error = readAndAssertContent(totalContentBytesCount, originalContentString, buffers.length + 1, contentProducer); + assertThat(error, nullValue()); + } + + @Test + public void testBlockingContentProducerNoInterceptorWithError() + { + ByteBuffer[] buffers = new ByteBuffer[3]; + buffers[0] = ByteBuffer.wrap("1 hello 1".getBytes(StandardCharsets.ISO_8859_1)); + buffers[1] = ByteBuffer.wrap("2 howdy 2".getBytes(StandardCharsets.ISO_8859_1)); + buffers[2] = ByteBuffer.wrap("3 hey ya 3".getBytes(StandardCharsets.ISO_8859_1)); + final int totalContentBytesCount = countRemaining(buffers); + final String originalContentString = asString(buffers); + final Throwable expectedError = new EofException("Early EOF"); + + AtomicReference ref = new AtomicReference<>(); + ArrayDelayedHttpChannel httpChannel = new ArrayDelayedHttpChannel(buffers, new HttpInput.ErrorContent(expectedError), scheduledExecutorService, () -> ref.get().onContentProducible()); + ContentProducer contentProducer = new BlockingContentProducer(new AsyncContentProducer(httpChannel)); + ref.set(contentProducer); + + Throwable error = readAndAssertContent(totalContentBytesCount, originalContentString, buffers.length + 1, contentProducer); + assertThat(error, is(expectedError)); + } + + @Test + public void testBlockingContentProducerGzipInterceptor() + { + ByteBuffer[] uncompressedBuffers = new ByteBuffer[3]; + uncompressedBuffers[0] = ByteBuffer.wrap("1 hello 1".getBytes(StandardCharsets.ISO_8859_1)); + uncompressedBuffers[1] = ByteBuffer.wrap("2 howdy 2".getBytes(StandardCharsets.ISO_8859_1)); + uncompressedBuffers[2] = ByteBuffer.wrap("3 hey ya 3".getBytes(StandardCharsets.ISO_8859_1)); + final int totalContentBytesCount = countRemaining(uncompressedBuffers); + final String originalContentString = asString(uncompressedBuffers); + + ByteBuffer[] buffers = new ByteBuffer[3]; + buffers[0] = gzipByteBuffer(uncompressedBuffers[0]); + buffers[1] = gzipByteBuffer(uncompressedBuffers[1]); + buffers[2] = gzipByteBuffer(uncompressedBuffers[2]); + + AtomicReference ref = new AtomicReference<>(); + ArrayDelayedHttpChannel httpChannel = new ArrayDelayedHttpChannel(buffers, new HttpInput.EofContent(), scheduledExecutorService, () -> ref.get().onContentProducible()); + ContentProducer contentProducer = new BlockingContentProducer(new AsyncContentProducer(httpChannel)); + ref.set(contentProducer); + contentProducer.setInterceptor(new GzipHttpInputInterceptor(inflaterPool, new ArrayByteBufferPool(1, 1, 2), 32)); + + Throwable error = readAndAssertContent(totalContentBytesCount, originalContentString, buffers.length + 1, contentProducer); + assertThat(error, nullValue()); + } + + @Test + public void testBlockingContentProducerGzipInterceptorWithTinyBuffers() + { + ByteBuffer[] uncompressedBuffers = new ByteBuffer[3]; + uncompressedBuffers[0] = ByteBuffer.wrap("1 hello 1".getBytes(StandardCharsets.ISO_8859_1)); + uncompressedBuffers[1] = ByteBuffer.wrap("2 howdy 2".getBytes(StandardCharsets.ISO_8859_1)); + uncompressedBuffers[2] = ByteBuffer.wrap("3 hey ya 3".getBytes(StandardCharsets.ISO_8859_1)); + final int totalContentBytesCount = countRemaining(uncompressedBuffers); + final String originalContentString = asString(uncompressedBuffers); + + ByteBuffer[] buffers = new ByteBuffer[3]; + buffers[0] = gzipByteBuffer(uncompressedBuffers[0]); + buffers[1] = gzipByteBuffer(uncompressedBuffers[1]); + buffers[2] = gzipByteBuffer(uncompressedBuffers[2]); + + AtomicReference ref = new AtomicReference<>(); + ArrayDelayedHttpChannel httpChannel = new ArrayDelayedHttpChannel(buffers, new HttpInput.EofContent(), scheduledExecutorService, () -> ref.get().onContentProducible()); + ContentProducer contentProducer = new BlockingContentProducer(new AsyncContentProducer(httpChannel)); + ref.set(contentProducer); + contentProducer.setInterceptor(new GzipHttpInputInterceptor(inflaterPool, new ArrayByteBufferPool(1, 1, 2), 1)); + + Throwable error = readAndAssertContent(totalContentBytesCount, originalContentString, totalContentBytesCount + 1, contentProducer); + assertThat(error, nullValue()); + } + + @Test + public void testBlockingContentProducerGzipInterceptorWithError() + { + ByteBuffer[] uncompressedBuffers = new ByteBuffer[3]; + uncompressedBuffers[0] = ByteBuffer.wrap("1 hello 1".getBytes(StandardCharsets.ISO_8859_1)); + uncompressedBuffers[1] = ByteBuffer.wrap("2 howdy 2".getBytes(StandardCharsets.ISO_8859_1)); + uncompressedBuffers[2] = ByteBuffer.wrap("3 hey ya 3".getBytes(StandardCharsets.ISO_8859_1)); + final int totalContentBytesCount = countRemaining(uncompressedBuffers); + final String originalContentString = asString(uncompressedBuffers); + final Throwable expectedError = new Throwable("HttpInput idle timeout"); + + ByteBuffer[] buffers = new ByteBuffer[3]; + buffers[0] = gzipByteBuffer(uncompressedBuffers[0]); + buffers[1] = gzipByteBuffer(uncompressedBuffers[1]); + buffers[2] = gzipByteBuffer(uncompressedBuffers[2]); + + AtomicReference ref = new AtomicReference<>(); + ArrayDelayedHttpChannel httpChannel = new ArrayDelayedHttpChannel(buffers, new HttpInput.ErrorContent(expectedError), scheduledExecutorService, () -> ref.get().onContentProducible()); + ContentProducer contentProducer = new BlockingContentProducer(new AsyncContentProducer(httpChannel)); + ref.set(contentProducer); + contentProducer.setInterceptor(new GzipHttpInputInterceptor(inflaterPool, new ArrayByteBufferPool(1, 1, 2), 32)); + + Throwable error = readAndAssertContent(totalContentBytesCount, originalContentString, buffers.length + 1, contentProducer); + assertThat(error, is(expectedError)); + } + + private Throwable readAndAssertContent(int totalContentBytesCount, String originalContentString, int totalContentCount, ContentProducer contentProducer) + { + int readBytes = 0; + int nextContentCount = 0; + String consumedString = ""; + Throwable error = null; + while (true) + { + HttpInput.Content content = contentProducer.nextContent(); + nextContentCount++; + + if (content.isSpecial()) + { + if (content.isEof()) + break; + error = content.getError(); + break; + } + + byte[] b = new byte[content.remaining()]; + content.getByteBuffer().get(b); + consumedString += new String(b, StandardCharsets.ISO_8859_1); + + readBytes += b.length; + } + assertThat(readBytes, is(totalContentBytesCount)); + assertThat(nextContentCount, is(totalContentCount)); + assertThat(consumedString, is(originalContentString)); + return error; + } + + private static int countRemaining(ByteBuffer[] byteBuffers) + { + int total = 0; + for (ByteBuffer byteBuffer : byteBuffers) + { + total += byteBuffer.remaining(); + } + return total; + } + + private static String asString(ByteBuffer[] buffers) + { + StringBuilder sb = new StringBuilder(); + for (ByteBuffer buffer : buffers) + { + byte[] b = new byte[buffer.remaining()]; + buffer.duplicate().get(b); + sb.append(new String(b, StandardCharsets.ISO_8859_1)); + } + return sb.toString(); + } + + private static ByteBuffer gzipByteBuffer(ByteBuffer uncompressedBuffer) + { + try + { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + GZIPOutputStream output = new GZIPOutputStream(baos); + + byte[] b = new byte[uncompressedBuffer.remaining()]; + uncompressedBuffer.get(b); + output.write(b); + + output.close(); + return ByteBuffer.wrap(baos.toByteArray()); + } + catch (IOException e) + { + throw new RuntimeException(e); + } + } + + private interface ContentListener + { + void onContent(); + } + + private static class ArrayDelayedHttpChannel extends HttpChannel + { + private final ByteBuffer[] byteBuffers; + private final HttpInput.Content finalContent; + private final ScheduledExecutorService scheduledExecutorService; + private final ContentListener contentListener; + private int counter; + private volatile HttpInput.Content nextContent; + + public ArrayDelayedHttpChannel(ByteBuffer[] byteBuffers, HttpInput.Content finalContent, ScheduledExecutorService scheduledExecutorService, ContentListener contentListener) + { + super(new MockConnector(), new HttpConfiguration(), null, null); + this.byteBuffers = new ByteBuffer[byteBuffers.length]; + this.finalContent = finalContent; + this.scheduledExecutorService = scheduledExecutorService; + this.contentListener = contentListener; + for (int i = 0; i < byteBuffers.length; i++) + { + this.byteBuffers[i] = byteBuffers[i].duplicate(); + } + } + + @Override + public boolean needContent() + { + if (nextContent != null) + return true; + scheduledExecutorService.schedule(() -> + { + if (byteBuffers.length > counter) + nextContent = new HttpInput.Content(byteBuffers[counter++]); + else + nextContent = finalContent; + contentListener.onContent(); + }, 50, TimeUnit.MILLISECONDS); + return false; + } + + @Override + public HttpInput.Content produceContent() + { + HttpInput.Content result = nextContent; + nextContent = null; + return result; + } + + @Override + public boolean failAllContent(Throwable failure) + { + nextContent = null; + counter = byteBuffers.length; + return false; + } + + @Override + public boolean failed(Throwable x) + { + return false; + } + + @Override + protected boolean eof() + { + return false; + } + } +} diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/HttpWriterTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/HttpWriterTest.java index d584037a1c2..c40e3d89109 100644 --- a/jetty-server/src/test/java/org/eclipse/jetty/server/HttpWriterTest.java +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/HttpWriterTest.java @@ -49,13 +49,21 @@ public class HttpWriterTest HttpChannel channel = new HttpChannel(new MockConnector(), new HttpConfiguration(), null, null) { @Override - public void produceContent() + public boolean needContent() { + return false; } @Override - public void failContent(Throwable failure) + public HttpInput.Content produceContent() { + return null; + } + + @Override + public boolean failAllContent(Throwable failure) + { + return false; } @Override @@ -63,6 +71,18 @@ public class HttpWriterTest { return pool; } + + @Override + public boolean failed(Throwable x) + { + return false; + } + + @Override + protected boolean eof() + { + return false; + } }; _httpOut = new HttpOutput(channel) diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/ResponseTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/ResponseTest.java index fb17e86da8e..afb9d8210ad 100644 --- a/jetty-server/src/test/java/org/eclipse/jetty/server/ResponseTest.java +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/ResponseTest.java @@ -178,13 +178,33 @@ public class ResponseTest }) { @Override - public void produceContent() + public boolean needContent() { + return false; } @Override - public void failContent(Throwable failure) + public HttpInput.Content produceContent() { + return null; + } + + @Override + public boolean failAllContent(Throwable failure) + { + return false; + } + + @Override + public boolean failed(Throwable x) + { + return false; + } + + @Override + protected boolean eof() + { + return false; } }; } diff --git a/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/AsyncIOServletTest.java b/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/AsyncIOServletTest.java index cbc3bd800a6..098cf5bd729 100644 --- a/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/AsyncIOServletTest.java +++ b/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/AsyncIOServletTest.java @@ -36,6 +36,7 @@ import java.util.concurrent.LinkedBlockingDeque; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; import java.util.zip.GZIPOutputStream; import javax.servlet.AsyncContext; import javax.servlet.DispatcherType; @@ -75,6 +76,8 @@ import org.eclipse.jetty.server.handler.ContextHandler.Context; import org.eclipse.jetty.server.handler.gzip.GzipHttpInputInterceptor; import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.FuturePromise; +import org.eclipse.jetty.util.compression.CompressionPool; +import org.eclipse.jetty.util.compression.InflaterPool; import org.hamcrest.Matchers; import org.junit.jupiter.api.Assumptions; import org.junit.jupiter.api.Disabled; @@ -89,6 +92,7 @@ import static org.eclipse.jetty.http.client.Transport.HTTP; import static org.eclipse.jetty.util.BufferUtil.toArray; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.empty; import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.is; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -778,10 +782,18 @@ public class AsyncIOServletTest extends AbstractTest= 0) + { + // System.err.printf("0x%2x %s %n", b, Character.isISOControl(b)?"?":(""+(char)b)); + response.getOutputStream().write(b); + } + else + return; + } + } + + @Override + public void onAllDataRead() throws IOException + { + asyncContext.complete(); + } + + @Override + public void onError(Throwable x) + { + } + }); + } + }); + + AsyncRequestContent contentProvider = new AsyncRequestContent(); + CountDownLatch clientLatch = new CountDownLatch(1); + + AtomicReference resultRef = new AtomicReference<>(); + scenario.client.newRequest(scenario.newURI()) + .method(HttpMethod.POST) + .path(scenario.servletPath) + .body(contentProvider) + .send(new BufferingResponseListener(16 * 1024 * 1024) + { + @Override + public void onComplete(Result result) + { + resultRef.set(result); + clientLatch.countDown(); + } + }); + + for (int i = 0; i < 1_000_000; i++) + { + contentProvider.offer(BufferUtil.toBuffer("S" + i)); + } + contentProvider.close(); + + assertTrue(clientLatch.await(30, TimeUnit.SECONDS)); + assertThat(resultRef.get().isSucceeded(), Matchers.is(true)); + assertThat(resultRef.get().getResponse().getStatus(), Matchers.equalTo(HttpStatus.OK_200)); + } + @ParameterizedTest @ArgumentsSource(TransportProvider.class) public void testAsyncInterceptedTwice(Transport transport) throws Exception @@ -1359,7 +1446,7 @@ public class AsyncIOServletTest extends AbstractTest { ByteBuffer byteBuffer = content.getByteBuffer(); @@ -1406,7 +1493,7 @@ public class AsyncIOServletTest extends AbstractTest Date: Tue, 3 Nov 2020 14:08:58 -0600 Subject: [PATCH 20/25] Bump protostream from 4.2.2.Final to 4.3.4.Final Signed-off-by: Joakim Erdfelt --- jetty-infinispan/infinispan-common/pom.xml | 2 +- jetty-infinispan/infinispan-remote/pom.xml | 2 +- pom.xml | 1 + tests/test-sessions/test-infinispan-sessions/pom.xml | 2 +- 4 files changed, 4 insertions(+), 3 deletions(-) diff --git a/jetty-infinispan/infinispan-common/pom.xml b/jetty-infinispan/infinispan-common/pom.xml index 5abc71fead1..1c86da6f07e 100644 --- a/jetty-infinispan/infinispan-common/pom.xml +++ b/jetty-infinispan/infinispan-common/pom.xml @@ -43,7 +43,7 @@ org.infinispan.protostream protostream - 4.2.2.Final + ${infinispan.protostream.version} true provided diff --git a/jetty-infinispan/infinispan-remote/pom.xml b/jetty-infinispan/infinispan-remote/pom.xml index 4a0d6fcaee1..66e9b5640d1 100644 --- a/jetty-infinispan/infinispan-remote/pom.xml +++ b/jetty-infinispan/infinispan-remote/pom.xml @@ -111,7 +111,7 @@ org.infinispan.protostream protostream - 4.2.2.Final + ${infinispan.protostream.version} provided diff --git a/pom.xml b/pom.xml index 6ba95f15ebc..1733a21c290 100644 --- a/pom.xml +++ b/pom.xml @@ -26,6 +26,7 @@ 1.1.3.v20160715 8.5.54 9.4.8.Final + 4.3.4.Final 2.0.10 2.5.1 9.0 diff --git a/tests/test-sessions/test-infinispan-sessions/pom.xml b/tests/test-sessions/test-infinispan-sessions/pom.xml index a76f441b583..1b583db656f 100644 --- a/tests/test-sessions/test-infinispan-sessions/pom.xml +++ b/tests/test-sessions/test-infinispan-sessions/pom.xml @@ -126,7 +126,7 @@ org.infinispan.protostream protostream - 4.2.2.Final + ${infinispan.protostream.version} test From 739397546c61eb2f18b9b3dc2e12cdf82dfa9950 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 3 Nov 2020 20:20:10 +0000 Subject: [PATCH 21/25] Bump jaxws-rt from 2.3.0.2 to 2.3.3 Bumps jaxws-rt from 2.3.0.2 to 2.3.3. Signed-off-by: dependabot[bot] --- jetty-http-spi/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jetty-http-spi/pom.xml b/jetty-http-spi/pom.xml index 0fea1098871..9f2572a9d1a 100644 --- a/jetty-http-spi/pom.xml +++ b/jetty-http-spi/pom.xml @@ -53,7 +53,7 @@ com.sun.xml.ws jaxws-rt - 2.3.0.2 + 2.3.3 test From f857ac97560e8765dadb4df501a5a75dbac56fc7 Mon Sep 17 00:00:00 2001 From: Lachlan Roberts Date: Wed, 4 Nov 2020 15:13:59 +1100 Subject: [PATCH 22/25] fix failures from incorrectly configured WebSocketUpgradeFilters Signed-off-by: Lachlan Roberts --- .../PerMessageDeflaterBufferSizeTest.java | 21 ++++++++++++++++++- .../src/test/resources/alt-filter-web.xml | 4 ++++ .../jetty/websocket/tests/ErrorCloseTest.java | 17 ++++++--------- .../tests/client/ClientConnectTest.java | 7 +------ .../tests/client/WebSocketClientTest.java | 6 ------ 5 files changed, 31 insertions(+), 24 deletions(-) diff --git a/jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/extensions/PerMessageDeflaterBufferSizeTest.java b/jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/extensions/PerMessageDeflaterBufferSizeTest.java index ccdbbb8329d..445249b9e9b 100644 --- a/jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/extensions/PerMessageDeflaterBufferSizeTest.java +++ b/jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/extensions/PerMessageDeflaterBufferSizeTest.java @@ -20,6 +20,7 @@ package org.eclipse.jetty.websocket.core.extensions; import java.io.IOException; import java.net.URI; +import java.nio.ByteBuffer; import java.util.Objects; import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; @@ -28,10 +29,13 @@ import org.eclipse.jetty.client.HttpRequest; import org.eclipse.jetty.client.HttpResponse; import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.HttpHeader; +import org.eclipse.jetty.util.BufferUtil; +import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.websocket.core.CoreSession; import org.eclipse.jetty.websocket.core.ExtensionConfig; import org.eclipse.jetty.websocket.core.Frame; import org.eclipse.jetty.websocket.core.FrameHandler; +import org.eclipse.jetty.websocket.core.OpCode; import org.eclipse.jetty.websocket.core.TestFrameHandler; import org.eclipse.jetty.websocket.core.WebSocketServer; import org.eclipse.jetty.websocket.core.client.CoreClientUpgradeRequest; @@ -291,7 +295,8 @@ public class PerMessageDeflaterBufferSizeTest // We should now only be able to send this message in multiple frames as it exceeds deflate_buffer_size. String message = "0123456789"; - clientHandler.sendText(message); + ByteBuffer payload = toBuffer(message, true); + clientHandler.getCoreSession().sendFrame(new Frame(OpCode.TEXT, payload), Callback.NOOP, false); // Verify the frame has been fragmented into multiple parts. int numFrames = 0; @@ -315,4 +320,18 @@ public class PerMessageDeflaterBufferSizeTest assertNull(serverHandler.getError()); assertNull(clientHandler.getError()); } + + public ByteBuffer toBuffer(String string, boolean direct) + { + ByteBuffer buffer = BufferUtil.allocate(string.length(), direct); + BufferUtil.clearToFill(buffer); + BufferUtil.put(BufferUtil.toBuffer(string), buffer); + BufferUtil.flipToFlush(buffer, 0); + + // Sanity checks. + assertThat(buffer.hasArray(), is(!direct)); + assertThat(BufferUtil.toString(buffer), is(string)); + + return buffer; + } } diff --git a/jetty-websocket/websocket-javax-tests/src/test/resources/alt-filter-web.xml b/jetty-websocket/websocket-javax-tests/src/test/resources/alt-filter-web.xml index 986106295e3..ccf98c85030 100644 --- a/jetty-websocket/websocket-javax-tests/src/test/resources/alt-filter-web.xml +++ b/jetty-websocket/websocket-javax-tests/src/test/resources/alt-filter-web.xml @@ -14,6 +14,10 @@ wsuf-test org.eclipse.jetty.websocket.util.server.WebSocketUpgradeFilter + + jetty.websocket.WebSocketMapping + jetty.websocket.defaultMapping + diff --git a/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/ErrorCloseTest.java b/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/ErrorCloseTest.java index 86dc8240721..b4a850bb5ca 100644 --- a/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/ErrorCloseTest.java +++ b/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/ErrorCloseTest.java @@ -22,11 +22,9 @@ import java.io.IOException; import java.net.URI; import java.time.Duration; import java.util.ArrayList; -import java.util.EnumSet; import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; -import javax.servlet.DispatcherType; import org.eclipse.jetty.logging.StacklessLogging; import org.eclipse.jetty.server.Server; @@ -39,7 +37,6 @@ import org.eclipse.jetty.websocket.client.WebSocketClient; import org.eclipse.jetty.websocket.common.WebSocketSession; import org.eclipse.jetty.websocket.core.internal.WebSocketCoreSession; import org.eclipse.jetty.websocket.server.config.JettyWebSocketServletContainerInitializer; -import org.eclipse.jetty.websocket.util.server.WebSocketUpgradeFilter; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -54,22 +51,20 @@ import static org.junit.jupiter.api.Assertions.assertTrue; public class ErrorCloseTest { - private Server server = new Server(); - private WebSocketClient client = new WebSocketClient(); - private ThrowingSocket serverSocket = new ThrowingSocket(); - private CountDownLatch serverCloseListener = new CountDownLatch(1); - private ServerConnector connector; + private final Server server = new Server(); + private final WebSocketClient client = new WebSocketClient(); + private final ThrowingSocket serverSocket = new ThrowingSocket(); + private final CountDownLatch serverCloseListener = new CountDownLatch(1); private URI serverUri; @BeforeEach public void start() throws Exception { - connector = new ServerConnector(server); + ServerConnector connector = new ServerConnector(server); server.addConnector(connector); ServletContextHandler contextHandler = new ServletContextHandler(ServletContextHandler.SESSIONS); contextHandler.setContextPath("/"); - contextHandler.addFilter(WebSocketUpgradeFilter.class, "/*", EnumSet.of(DispatcherType.REQUEST)); JettyWebSocketServletContainerInitializer.configure(contextHandler, (context, container) -> { container.addMapping("/", (req, resp) -> serverSocket); @@ -140,7 +135,7 @@ public class ErrorCloseTest serverSocket.methodsToThrow.add("onOpen"); EventSocket clientSocket = new EventSocket(); - try (StacklessLogging stacklessLogging = new StacklessLogging(WebSocketCoreSession.class)) + try (StacklessLogging ignored = new StacklessLogging(WebSocketCoreSession.class)) { client.connect(clientSocket, serverUri).get(5, TimeUnit.SECONDS); assertTrue(serverSocket.closeLatch.await(5, TimeUnit.SECONDS)); diff --git a/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/client/ClientConnectTest.java b/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/client/ClientConnectTest.java index 787c2c83261..72e0af5d204 100644 --- a/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/client/ClientConnectTest.java +++ b/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/client/ClientConnectTest.java @@ -25,7 +25,6 @@ import java.net.ServerSocket; import java.net.SocketTimeoutException; import java.net.URI; import java.time.Duration; -import java.util.EnumSet; import java.util.List; import java.util.Map; import java.util.concurrent.CountDownLatch; @@ -33,7 +32,6 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; -import javax.servlet.DispatcherType; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; @@ -49,7 +47,6 @@ import org.eclipse.jetty.websocket.tests.CloseTrackingEndpoint; import org.eclipse.jetty.websocket.tests.EchoSocket; import org.eclipse.jetty.websocket.tests.GetAuthHeaderEndpoint; import org.eclipse.jetty.websocket.tests.SimpleStatusServlet; -import org.eclipse.jetty.websocket.util.server.WebSocketUpgradeFilter; import org.hamcrest.Matcher; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; @@ -73,7 +70,7 @@ public class ClientConnectTest { private Server server; private WebSocketClient client; - private CountDownLatch serverLatch = new CountDownLatch(1); + private final CountDownLatch serverLatch = new CountDownLatch(1); @SuppressWarnings("unchecked") private E assertExpectedError(ExecutionException e, CloseTrackingEndpoint wsocket, Matcher errorMatcher) @@ -143,8 +140,6 @@ public class ClientConnectTest }); }); - context.addFilter(WebSocketUpgradeFilter.class, "/*", EnumSet.of(DispatcherType.REQUEST)); - context.addServlet(new ServletHolder(new SimpleStatusServlet(404)), "/bogus"); context.addServlet(new ServletHolder(new SimpleStatusServlet(200)), "/a-okay"); context.addServlet(new ServletHolder(new InvalidUpgradeServlet()), "/invalid-upgrade/*"); diff --git a/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/client/WebSocketClientTest.java b/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/client/WebSocketClientTest.java index 3725abbb25b..ee783272084 100644 --- a/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/client/WebSocketClientTest.java +++ b/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/client/WebSocketClientTest.java @@ -25,12 +25,10 @@ import java.nio.ByteBuffer; import java.time.Duration; import java.util.Arrays; import java.util.Collection; -import java.util.EnumSet; import java.util.List; import java.util.Map; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; -import javax.servlet.DispatcherType; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; @@ -50,7 +48,6 @@ import org.eclipse.jetty.websocket.tests.ConnectMessageEndpoint; import org.eclipse.jetty.websocket.tests.EchoSocket; import org.eclipse.jetty.websocket.tests.ParamsEndpoint; import org.eclipse.jetty.websocket.tests.util.FutureWriteCallback; -import org.eclipse.jetty.websocket.util.server.WebSocketUpgradeFilter; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; @@ -104,10 +101,7 @@ public class WebSocketClientTest configuration.addMapping("/get-params", (req, resp) -> new ParamsEndpoint()); }); - context.addFilter(WebSocketUpgradeFilter.class, "/*", EnumSet.of(DispatcherType.REQUEST)); - server.setHandler(context); - server.start(); } From 1904d326fc58fa1775e6ce20f29c87aec77064e4 Mon Sep 17 00:00:00 2001 From: Greg Wilkins Date: Wed, 4 Nov 2020 08:56:17 +0100 Subject: [PATCH 23/25] Fixes #5521 ResourceCollection NPE (#5527) * Fixes #5521 ResourceCollection NPE Fix constructor and addPath so that all resources in a RC must exist when created. * Fixes #5521 ResourceCollection NPE Cleanup the vestiges of non existent directories detected by resource ending in / * Fixes #5521 ResourceCollection NPE Revert adding paths ending in / as jar:file resource needs them * feedback from review improved javadoc. --- .../jetty/util/resource/JarFileResource.java | 9 +-- .../eclipse/jetty/util/resource/Resource.java | 4 +- .../util/resource/ResourceCollection.java | 58 +++++++++++-------- .../jetty/util/resource/URLResource.java | 5 -- .../util/resource/ResourceCollectionTest.java | 3 +- .../jetty/util/resource/ResourceTest.java | 6 +- .../eclipse/jetty/webapp/WebAppContext.java | 1 - 7 files changed, 40 insertions(+), 46 deletions(-) diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/resource/JarFileResource.java b/jetty-util/src/main/java/org/eclipse/jetty/util/resource/JarFileResource.java index 736f605df70..43576a25c0f 100644 --- a/jetty-util/src/main/java/org/eclipse/jetty/util/resource/JarFileResource.java +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/resource/JarFileResource.java @@ -144,7 +144,7 @@ public class JarFileResource extends JarResource String fileUrl = _urlString.substring(4, _urlString.length() - 2); try { - return newResource(fileUrl).exists(); + return _directory = newResource(fileUrl).exists(); } catch (Exception e) { @@ -236,15 +236,10 @@ public class JarFileResource extends JarResource return _exists; } - /** - * Returns true if the represented resource is a container/directory. - * If the resource is not a file, resources ending with "/" are - * considered directories. - */ @Override public boolean isDirectory() { - return _urlString.endsWith("/") || exists() && _directory; + return exists() && _directory; } /** diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/resource/Resource.java b/jetty-util/src/main/java/org/eclipse/jetty/util/resource/Resource.java index 4aa95771539..657c7998eaa 100644 --- a/jetty-util/src/main/java/org/eclipse/jetty/util/resource/Resource.java +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/resource/Resource.java @@ -320,8 +320,6 @@ public abstract class Resource implements ResourceFactory, Closeable /** * @return true if the represented resource is a container/directory. - * if the resource is not a file, resources ending with "/" are - * considered directories. */ public abstract boolean isDirectory(); @@ -412,7 +410,7 @@ public abstract class Resource implements ResourceFactory, Closeable /** * Returns the resource contained inside the current resource with the - * given name. + * given name, which may or may not exist. * * @param path The path segment to add, which is not encoded * @return the Resource for the resolved path within this Resource, never null diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/resource/ResourceCollection.java b/jetty-util/src/main/java/org/eclipse/jetty/util/resource/ResourceCollection.java index 4b7484081f1..4ef8d68c7a8 100644 --- a/jetty-util/src/main/java/org/eclipse/jetty/util/resource/ResourceCollection.java +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/resource/ResourceCollection.java @@ -62,8 +62,19 @@ public class ResourceCollection extends Resource * @param resources the resources to be added to collection */ public ResourceCollection(Resource... resources) + { + this(Arrays.asList(resources)); + } + + /** + * Instantiates a new resource collection. + * + * @param resources the resources to be added to collection + */ + public ResourceCollection(Collection resources) { _resources = new ArrayList<>(); + for (Resource r : resources) { if (r == null) @@ -82,17 +93,6 @@ public class ResourceCollection extends Resource } } - /** - * Instantiates a new resource collection. - * - * @param resources the resources to be added to collection - */ - public ResourceCollection(Collection resources) - { - _resources = new ArrayList<>(); - _resources.addAll(resources); - } - /** * Instantiates a new resource collection. * @@ -226,8 +226,16 @@ public class ResourceCollection extends Resource } /** + * Add a path to the resource collection. * @param path The path segment to add - * @return The contained resource (found first) in the collection of resources + * @return The resulting resource(s) : + *

    + *
  • is a file that exists in at least one of the collection, then the first one found is returned
  • + *
  • is a directory that exists in at exactly one of the collection, then that directory resource is returned
  • + *
  • is a directory that exists in several of the collection, then a ResourceCollection of those directories is returned
  • + *
  • do not exist in any of the collection, then a new non existent resource relative to the first in the collection is returned.
  • + *
+ * @throws MalformedURLException if the resolution of the path fails because the input path parameter is malformed against any of the collection */ @Override public Resource addPath(String path) throws IOException @@ -247,27 +255,28 @@ public class ResourceCollection extends Resource ArrayList resources = null; // Attempt a simple (single) Resource lookup that exists + Resource addedResource = null; for (Resource res : _resources) { - Resource r = res.addPath(path); - if (!r.isDirectory() && r.exists()) - { - // Return simple (non-directory) Resource - return r; - } - + addedResource = res.addPath(path); + if (!addedResource.exists()) + continue; + if (!addedResource.isDirectory()) + return addedResource; // Return simple (non-directory) Resource if (resources == null) - { resources = new ArrayList<>(); - } + resources.add(addedResource); + } - resources.add(r); + if (resources == null) + { + if (addedResource != null) + return addedResource; // This will not exist + return EmptyResource.INSTANCE; } if (resources.size() == 1) - { return resources.get(0); - } return new ResourceCollection(resources); } @@ -384,7 +393,6 @@ public class ResourceCollection extends Resource public boolean isDirectory() { assertResourcesSet(); - return true; } diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/resource/URLResource.java b/jetty-util/src/main/java/org/eclipse/jetty/util/resource/URLResource.java index 3dbd48ffa02..557bf2fbfbb 100644 --- a/jetty-util/src/main/java/org/eclipse/jetty/util/resource/URLResource.java +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/resource/URLResource.java @@ -128,11 +128,6 @@ public class URLResource extends Resource return _in != null; } - /** - * Returns true if the represented resource is a container/directory. - * If the resource is not a file, resources ending with "/" are - * considered directories. - */ @Override public boolean isDirectory() { diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/resource/ResourceCollectionTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/resource/ResourceCollectionTest.java index fc39c573f47..cb5313fe8d0 100644 --- a/jetty-util/src/test/java/org/eclipse/jetty/util/resource/ResourceCollectionTest.java +++ b/jetty-util/src/test/java/org/eclipse/jetty/util/resource/ResourceCollectionTest.java @@ -35,7 +35,6 @@ import org.junit.jupiter.api.extension.ExtendWith; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.contains; -import static org.hamcrest.Matchers.emptyArray; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.nullValue; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -189,7 +188,7 @@ public class ResourceCollectionTest assertThat(Arrays.asList(rc1.list()), contains("1.txt", "2.txt", "3.txt", "dir/")); assertThat(Arrays.asList(rc1.addPath("dir").list()), contains("1.txt", "2.txt", "3.txt")); - assertThat(rc1.addPath("unknown").list(), emptyArray()); + assertThat(rc1.addPath("unknown").list(), nullValue()); } @Test diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/resource/ResourceTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/resource/ResourceTest.java index ddb5153ff6b..d2a639ab411 100644 --- a/jetty-util/src/test/java/org/eclipse/jetty/util/resource/ResourceTest.java +++ b/jetty-util/src/test/java/org/eclipse/jetty/util/resource/ResourceTest.java @@ -215,15 +215,15 @@ public class ResourceTest cases.addCase(new Scenario(tdata1, "alphabet.txt", EXISTS, !DIR, "ABCDEFGHIJKLMNOPQRSTUVWXYZ")); cases.addCase(new Scenario(tdata2, "alphabet.txt", EXISTS, !DIR, "ABCDEFGHIJKLMNOPQRSTUVWXYZ")); - cases.addCase(new Scenario("jar:file:/somejar.jar!/content/", !EXISTS, DIR)); - cases.addCase(new Scenario("jar:file:/somejar.jar!/", !EXISTS, DIR)); + cases.addCase(new Scenario("jar:file:/somejar.jar!/content/", !EXISTS, !DIR)); + cases.addCase(new Scenario("jar:file:/somejar.jar!/", !EXISTS, !DIR)); String urlRef = cases.uriRef.toASCIIString(); Scenario zdata = new Scenario("jar:" + urlRef + "TestData/test.zip!/", EXISTS, DIR); cases.addCase(zdata); cases.addCase(new Scenario(zdata, "Unknown", !EXISTS, !DIR)); - cases.addCase(new Scenario(zdata, "/Unknown/", !EXISTS, DIR)); + cases.addCase(new Scenario(zdata, "/Unknown/", !EXISTS, !DIR)); cases.addCase(new Scenario(zdata, "subdir", EXISTS, DIR)); cases.addCase(new Scenario(zdata, "/subdir/", EXISTS, DIR)); diff --git a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/WebAppContext.java b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/WebAppContext.java index dafd33f0a08..b2a6e553a4f 100644 --- a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/WebAppContext.java +++ b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/WebAppContext.java @@ -31,7 +31,6 @@ import java.util.Collections; import java.util.EventListener; import java.util.HashMap; import java.util.HashSet; -import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; From 4f8707390e94825cad56d0272dcfc625b757d3f7 Mon Sep 17 00:00:00 2001 From: Joakim Erdfelt Date: Wed, 4 Nov 2020 10:09:15 -0600 Subject: [PATCH 24/25] Adding required gson dependency to test-infinispan-sessions Signed-off-by: Joakim Erdfelt --- pom.xml | 1 + tests/test-sessions/test-infinispan-sessions/pom.xml | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/pom.xml b/pom.xml index 1733a21c290..d90705f9118 100644 --- a/pom.xml +++ b/pom.xml @@ -27,6 +27,7 @@ 8.5.54 9.4.8.Final 4.3.4.Final + 2.8.6 2.0.10 2.5.1 9.0 diff --git a/tests/test-sessions/test-infinispan-sessions/pom.xml b/tests/test-sessions/test-infinispan-sessions/pom.xml index 1b583db656f..4aeb7ef0929 100644 --- a/tests/test-sessions/test-infinispan-sessions/pom.xml +++ b/tests/test-sessions/test-infinispan-sessions/pom.xml @@ -129,6 +129,12 @@ ${infinispan.protostream.version} test
+ + com.google.code.gson + gson + ${gson.version} + test + org.slf4j slf4j-simple From de98d7b3962c7f944b3ad20aadb0be4b5be53cf7 Mon Sep 17 00:00:00 2001 From: Chris Walker Date: Fri, 6 Nov 2020 14:18:07 -0600 Subject: [PATCH 25/25] Minor changes to documentation --- .../operations-guide/begin/architecture.adoc | 18 +++++++++--------- .../operations-guide/begin/download.adoc | 2 +- .../operations-guide/begin/install.adoc | 4 ++-- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/jetty-documentation/src/main/asciidoc/operations-guide/begin/architecture.adoc b/jetty-documentation/src/main/asciidoc/operations-guide/begin/architecture.adoc index f816db219a3..a521be56079 100644 --- a/jetty-documentation/src/main/asciidoc/operations-guide/begin/architecture.adoc +++ b/jetty-documentation/src/main/asciidoc/operations-guide/begin/architecture.adoc @@ -21,10 +21,10 @@ There are two main concepts on which the Eclipse Jetty standalone server is based: -* the xref:og-begin-arch-modules[Jetty _module_ system], that provides the Jetty features -* the xref:og-begin-arch-jetty-base[`$JETTY_BASE` directory], that provides a place where you configure the modules, and therefore the features, you need for your web applications +* The xref:og-begin-arch-modules[Jetty _module_ system], that provides the Jetty features +* The xref:og-begin-arch-jetty-base[`$JETTY_BASE` directory], that provides a place where you configure the modules, and therefore the features you need for your web applications -After installing Jetty, you want to setup a xref:og-begin-arch-jetty-base[`$JETTY_BASE` directory] where you configure xref:og-begin-arch-modules[Jetty modules]. +After installing Jetty, you will want to set up a xref:og-begin-arch-jetty-base[`$JETTY_BASE` directory] where you configure xref:og-begin-arch-modules[Jetty modules]. [[og-begin-arch-modules]] ===== Eclipse Jetty Architecture: Modules @@ -33,19 +33,19 @@ The Jetty standalone server is made of components that are assembled together, c A Jetty _module_ is made of one or more components that work together to provide typically one feature, although they may provide more than one feature. -A Jetty module is nothing more than Jetty components assembled together like you would do using Java APIs, just done in a declarative way using configuration files rather than using Java APIs. -What you can do in Java code to assemble Jetty components, it can be done using Jetty modules. +A Jetty module is nothing more than Jetty components assembled together like you would do using Java APIs, just done in a declarative way using configuration files. +What you can do in Java code to assemble Jetty components can be done using Jetty modules. -A Jetty module may be dependent on other Jetty modules: for example, the `http` Jetty module depends on the `server` Jetty module, that in turn depends on the `threadpool` and `logging` Jetty modules. +A Jetty module may be dependent on other Jetty modules: for example, the `http` Jetty module depends on the `server` Jetty module which in turn depends on the `threadpool` and `logging` Jetty modules. -Every feature in a Jetty server is enabled by enabling correspondent Jetty modules. +Every feature in a Jetty server is enabled by enabling the corresponding Jetty module(s). For example, if you enable only the `http` Jetty module, then your Jetty standalone server will only be able to listen to a network port for clear-text HTTP requests. It will not be able to process secure HTTP (i.e. `https`) requests, it will not be able to process WebSocket, or HTTP/2 or any other protocol because the correspondent modules have not been enabled. You can even start a Jetty server _without_ listening on a network port -- for example because you have enabled a custom module you wrote that provides the features you need. -This allows the Jetty standalone server to be as small as necessary: modules that are not enabled are not loaded, don't waste memory, and you don't risk that client use a module that you did not know was even there. +This allows the Jetty standalone server to be as small as necessary: modules that are not enabled are not loaded, don't waste memory, and you don't risk a client using a module that you did not know was even there. For more detailed information about the Jetty module system, see xref:og-modules[this section]. @@ -60,7 +60,7 @@ This separation between `$JETTY_HOME` and `$JETTY_BASE` allows upgrades without `$JETTY_HOME` contains the Jetty runtime and libraries and the default configuration, while a `$JETTY_BASE` contains your web applications and any override of the default configuration. For example, with the `$JETTY_HOME` installation the default value for the network port for clear-text HTTP is `8080`. -However, you want that port to be `6060`, for example because you are behind a load balancer that is configured to forward to the backend on port `6060`. +However, you want that port to be `6060`, because you are behind a load balancer that is configured to forward to the backend on port `6060`. Instead, you want to configure the clear-text HTTP port in your `$JETTY_BASE`. When you upgrade Jetty, you will upgrade only files in `$JETTY_HOME`, and all the configuration in `$JETTY_BASE` will remain unchanged. diff --git a/jetty-documentation/src/main/asciidoc/operations-guide/begin/download.adoc b/jetty-documentation/src/main/asciidoc/operations-guide/begin/download.adoc index 963dd601407..4a867a364ed 100644 --- a/jetty-documentation/src/main/asciidoc/operations-guide/begin/download.adoc +++ b/jetty-documentation/src/main/asciidoc/operations-guide/begin/download.adoc @@ -21,5 +21,5 @@ The Eclipse Jetty distribution is available for download from link:https://www.eclipse.org/jetty/download.html[] -The Eclipse Jetty distribution is available in both `zip` and `gzip` formats; download the one most appropriate for your system, typically `zip` for Windows and `gzip` for other operative systems. +The Eclipse Jetty distribution is available in both `zip` and `gzip` formats; download the one most appropriate for your system, typically `zip` for Windows and `gzip` for other operating systems. diff --git a/jetty-documentation/src/main/asciidoc/operations-guide/begin/install.adoc b/jetty-documentation/src/main/asciidoc/operations-guide/begin/install.adoc index 55a498062f5..cfa1d8ecf90 100644 --- a/jetty-documentation/src/main/asciidoc/operations-guide/begin/install.adoc +++ b/jetty-documentation/src/main/asciidoc/operations-guide/begin/install.adoc @@ -30,6 +30,6 @@ The rest of the instructions in this documentation will refer to this location a IMPORTANT: It is important that *only* stable release versions are used in production environments. Versions that have been deprecated or are released as Milestones (M), Alpha, Beta or Release Candidates (RC) are *not* suitable for production as they may contain security flaws or incomplete/non-functioning feature sets. -If you are new to Jetty, read the xref:og-begin-arch[Jetty architecture short section] to become familiar with the terms used in this document. -Otherwise, you can jump to the xref:og-begin-start[start Jetty section]. +If you are new to Jetty, you should read the xref:og-begin-arch[Jetty architecture section below] to become familiar with the terms used in this documentation. +Otherwise, you can jump to the xref:og-begin-start[section on starting Jetty].