From 82338660a8fbd8392defde92fa5130e8719c51b5 Mon Sep 17 00:00:00 2001 From: Olivier Lamy Date: Mon, 25 Jul 2022 19:54:18 +1000 Subject: [PATCH 1/4] add jetty-ee8-proxy (#8332) * restore ee8 proxy Signed-off-by: Olivier Lamy --- .github/workflows/codeql-analysis.yml | 6 +- jetty-ee10/jetty-ee10-proxy/pom.xml | 2 +- jetty-ee8/jetty-ee8-proxy/pom.xml | 79 +++++++++++++++++++ .../src/main/config/etc/jetty-ee8-proxy.xml | 35 ++++++++ .../src/main/config/modules/ee8-proxy.mod | 27 +++++++ jetty-ee8/pom.xml | 1 + .../src/main/config/modules/ee9-proxy.mod | 2 +- .../jetty/ee9/proxy/ClientAuthProxyTest.java | 14 ++-- .../ee9/proxy/ConnectHandlerSSLTest.java | 2 +- .../ee9/proxy/ForwardProxyServerTest.java | 2 +- .../ee9/proxy/ForwardProxyTLSServerTest.java | 10 +-- .../jetty/ee9/proxy/ProxyServletTest.java | 2 +- 12 files changed, 164 insertions(+), 18 deletions(-) create mode 100644 jetty-ee8/jetty-ee8-proxy/pom.xml create mode 100644 jetty-ee8/jetty-ee8-proxy/src/main/config/etc/jetty-ee8-proxy.xml create mode 100644 jetty-ee8/jetty-ee8-proxy/src/main/config/modules/ee8-proxy.mod diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index ca71a3ce28a..46013d167c4 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -67,8 +67,12 @@ jobs: # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs # queries: security-extended,security-and-quality + - name: Set up Maven + run: + mvn -e -B -V --show-version org.apache.maven.plugins:maven-wrapper-plugin:3.1.0:wrapper "-Dmaven=3.8.6" + - name: Clean install dependencies and build - run: mvn clean install -DskipTests + run: ./mvnw clean install -DskipTests -V -B # ℹī¸ Command-line programs to run using the OS shell. # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun diff --git a/jetty-ee10/jetty-ee10-proxy/pom.xml b/jetty-ee10/jetty-ee10-proxy/pom.xml index 808869d39a2..6f2ab96b61e 100644 --- a/jetty-ee10/jetty-ee10-proxy/pom.xml +++ b/jetty-ee10/jetty-ee10-proxy/pom.xml @@ -12,7 +12,7 @@ ${project.groupId}.proxy - org.eclipse.jetty.proxy.* + org.eclipse.jetty.ee10.proxy.* diff --git a/jetty-ee8/jetty-ee8-proxy/pom.xml b/jetty-ee8/jetty-ee8-proxy/pom.xml new file mode 100644 index 00000000000..8ddb2323f6f --- /dev/null +++ b/jetty-ee8/jetty-ee8-proxy/pom.xml @@ -0,0 +1,79 @@ + + + org.eclipse.jetty.ee8 + jetty-ee8 + 12.0.0-SNAPSHOT + + + 4.0.0 + jetty-ee8-proxy + EE8 :: Jetty :: Proxy + Jetty Proxy + + + jetty-ee9-proxy + ${project.groupId}.proxy + org.eclipse.jetty.ee8.proxy.* + + + + + maven-surefire-plugin + + + @{argLine} ${jetty.surefire.argLine} --add-reads org.eclipse.jetty.ee8.proxy=org.eclipse.jetty.logging + + + + + + + + org.slf4j + slf4j-api + + + org.eclipse.jetty.toolchain + jetty-servlet-api + provided + + + org.eclipse.jetty.ee8 + jetty-ee8-servlet + provided + + + org.eclipse.jetty + jetty-util + + + org.eclipse.jetty + jetty-client + + + org.eclipse.jetty + jetty-util-ajax + test + + + org.eclipse.jetty + jetty-http-tools + test + + + org.eclipse.jetty + jetty-slf4j-impl + test + + + org.eclipse.jetty + jetty-rewrite + test + + + org.eclipse.jetty.toolchain + jetty-test-helper + test + + + diff --git a/jetty-ee8/jetty-ee8-proxy/src/main/config/etc/jetty-ee8-proxy.xml b/jetty-ee8/jetty-ee8-proxy/src/main/config/etc/jetty-ee8-proxy.xml new file mode 100644 index 00000000000..5ed1d5837eb --- /dev/null +++ b/jetty-ee8/jetty-ee8-proxy/src/main/config/etc/jetty-ee8-proxy.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + maxThreads + + + + maxConnections + + + + idleTimeout + + + + timeout + + + + + + + + + diff --git a/jetty-ee8/jetty-ee8-proxy/src/main/config/modules/ee8-proxy.mod b/jetty-ee8/jetty-ee8-proxy/src/main/config/modules/ee8-proxy.mod new file mode 100644 index 00000000000..d2d675af92b --- /dev/null +++ b/jetty-ee8/jetty-ee8-proxy/src/main/config/modules/ee8-proxy.mod @@ -0,0 +1,27 @@ +# DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-modules.html + +[description] +Enables the Jetty Proxy service. +Allows the server to act as a non-transparent proxy for browsers. + +[depend] +ee8-servlet +client + +[environment] +ee8 + +[lib] +lib/jetty-ee8-proxy-${jetty.version}.jar + +[xml] +etc/jetty-ee8-proxy.xml + +[ini-template] +## Proxy Configuration +# jetty.proxy.servletClass=org.eclipse.jetty.ee8.proxy.ProxyServlet +# jetty.proxy.servletMapping=/* +# jetty.proxy.maxThreads=128 +# jetty.proxy.maxConnections=256 +# jetty.proxy.idleTimeout=30000 +# jetty.proxy.timeout=60000 diff --git a/jetty-ee8/pom.xml b/jetty-ee8/pom.xml index ca8d955742b..a04983010b4 100644 --- a/jetty-ee8/pom.xml +++ b/jetty-ee8/pom.xml @@ -34,6 +34,7 @@ jetty-ee8-jaas jetty-ee8-plus jetty-ee8-jndi + jetty-ee8-proxy jetty-ee8-annotations jetty-ee8-websocket jetty-ee8-quickstart diff --git a/jetty-ee9/jetty-ee9-proxy/src/main/config/modules/ee9-proxy.mod b/jetty-ee9/jetty-ee9-proxy/src/main/config/modules/ee9-proxy.mod index 82a6ac58589..8243766f95b 100644 --- a/jetty-ee9/jetty-ee9-proxy/src/main/config/modules/ee9-proxy.mod +++ b/jetty-ee9/jetty-ee9-proxy/src/main/config/modules/ee9-proxy.mod @@ -19,7 +19,7 @@ etc/jetty-ee9-proxy.xml [ini-template] ## Proxy Configuration -# jetty.proxy.servletClass=org.eclipse.jetty.proxy.ProxyServlet +# jetty.proxy.servletClass=org.eclipse.jetty.ee9.proxy.ProxyServlet # jetty.proxy.servletMapping=/* # jetty.proxy.maxThreads=128 # jetty.proxy.maxConnections=256 diff --git a/jetty-ee9/jetty-ee9-proxy/src/test/java/org/eclipse/jetty/ee9/proxy/ClientAuthProxyTest.java b/jetty-ee9/jetty-ee9-proxy/src/test/java/org/eclipse/jetty/ee9/proxy/ClientAuthProxyTest.java index 256e8e01bab..c6fbb941fe6 100644 --- a/jetty-ee9/jetty-ee9-proxy/src/test/java/org/eclipse/jetty/ee9/proxy/ClientAuthProxyTest.java +++ b/jetty-ee9/jetty-ee9-proxy/src/test/java/org/eclipse/jetty/ee9/proxy/ClientAuthProxyTest.java @@ -117,7 +117,7 @@ public class ClientAuthProxyTest serverTLS.setSniRequired(false); serverTLS.setNeedClientAuth(true); // The KeyStore is also a TrustStore. - serverTLS.setKeyStorePath(MavenTestingUtils.getTestResourceFile("client_auth/server_keystore.p12").getAbsolutePath()); + serverTLS.setKeyStorePath(MavenTestingUtils.getTargetFile("test-classes/client_auth/server_keystore.p12").getAbsolutePath()); serverTLS.setKeyStorePassword("storepwd"); serverTLS.setKeyStoreType("PKCS12"); @@ -166,7 +166,7 @@ public class ClientAuthProxyTest proxyTLS.setSniRequired(false); proxyTLS.setNeedClientAuth(true); // The KeyStore is also a TrustStore. - proxyTLS.setKeyStorePath(MavenTestingUtils.getTestResourceFile("client_auth/proxy_keystore.p12").getAbsolutePath()); + proxyTLS.setKeyStorePath(MavenTestingUtils.getTargetFile("test-classes/client_auth/proxy_keystore.p12").getAbsolutePath()); proxyTLS.setKeyStorePassword("storepwd"); proxyTLS.setKeyStoreType("PKCS12"); @@ -188,7 +188,7 @@ public class ClientAuthProxyTest SslContextFactory.Client clientTLS = new SslContextFactory.Client(); // Disable TLS-level hostname verification. clientTLS.setEndpointIdentificationAlgorithm(null); - clientTLS.setKeyStorePath(MavenTestingUtils.getTestResourceFile("client_auth/client_keystore.p12").getAbsolutePath()); + clientTLS.setKeyStorePath(MavenTestingUtils.getTargetFile("test-classes/client_auth/client_keystore.p12").getAbsolutePath()); clientTLS.setKeyStorePassword("storepwd"); clientTLS.setKeyStoreType("PKCS12"); ClientConnector connector = new ClientConnector(); @@ -261,7 +261,7 @@ public class ClientAuthProxyTest SslContextFactory.Client clientTLS = new SslContextFactory.Client(); // Disable TLS-level hostname verification for this test. clientTLS.setEndpointIdentificationAlgorithm(null); - clientTLS.setKeyStorePath(MavenTestingUtils.getTestResourceFile("client_auth/proxy_keystore.p12").getAbsolutePath()); + clientTLS.setKeyStorePath(MavenTestingUtils.getTargetFile("test-classes/client_auth/proxy_keystore.p12").getAbsolutePath()); clientTLS.setKeyStorePassword("storepwd"); clientTLS.setKeyStoreType("PKCS12"); clientTLS.setCertAlias(key + "_proxy"); @@ -327,7 +327,7 @@ public class ClientAuthProxyTest }; // Disable TLS-level hostname verification for this test. clientTLS.setEndpointIdentificationAlgorithm(null); - clientTLS.setKeyStorePath(MavenTestingUtils.getTestResourceFile("client_auth/proxy_keystore.p12").getAbsolutePath()); + clientTLS.setKeyStorePath(MavenTestingUtils.getTargetFile("test-classes/client_auth/proxy_keystore.p12").getAbsolutePath()); clientTLS.setKeyStorePassword("storepwd"); clientTLS.setKeyStoreType("PKCS12"); ClientConnector connector = new ClientConnector(); @@ -390,7 +390,7 @@ public class ClientAuthProxyTest }; // Disable hostname verification is required. clientTLS.setEndpointIdentificationAlgorithm(null); - clientTLS.setKeyStorePath(MavenTestingUtils.getTestResourceFile("client_auth/proxy_keystore.p12").getAbsolutePath()); + clientTLS.setKeyStorePath(MavenTestingUtils.getTargetFile("test-classes/client_auth/proxy_keystore.p12").getAbsolutePath()); clientTLS.setKeyStorePassword("storepwd"); clientTLS.setKeyStoreType("PKCS12"); ClientConnector connector = new ClientConnector(); @@ -440,7 +440,7 @@ public class ClientAuthProxyTest { // Disable TLS-level hostname verification for this test. tls.setEndpointIdentificationAlgorithm(null); - tls.setKeyStorePath(MavenTestingUtils.getTestResourceFile("client_auth/proxy_keystore.p12").getAbsolutePath()); + tls.setKeyStorePath(MavenTestingUtils.getTargetFile("test-classes/client_auth/proxy_keystore.p12").getAbsolutePath()); tls.setKeyStorePassword("storepwd"); tls.setKeyStoreType("PKCS12"); if (user != null) diff --git a/jetty-ee9/jetty-ee9-proxy/src/test/java/org/eclipse/jetty/ee9/proxy/ConnectHandlerSSLTest.java b/jetty-ee9/jetty-ee9-proxy/src/test/java/org/eclipse/jetty/ee9/proxy/ConnectHandlerSSLTest.java index 5b93a6a8a89..623638df23e 100644 --- a/jetty-ee9/jetty-ee9-proxy/src/test/java/org/eclipse/jetty/ee9/proxy/ConnectHandlerSSLTest.java +++ b/jetty-ee9/jetty-ee9-proxy/src/test/java/org/eclipse/jetty/ee9/proxy/ConnectHandlerSSLTest.java @@ -48,7 +48,7 @@ public class ConnectHandlerSSLTest extends AbstractConnectHandlerTest public void prepare() throws Exception { sslContextFactory = new SslContextFactory.Server(); - String keyStorePath = MavenTestingUtils.getTestResourceFile("server_keystore.p12").getAbsolutePath(); + String keyStorePath = MavenTestingUtils.getTargetFile("test-classes/server_keystore.p12").getAbsolutePath(); sslContextFactory.setKeyStorePath(keyStorePath); sslContextFactory.setKeyStorePassword("storepwd"); server = new Server(); diff --git a/jetty-ee9/jetty-ee9-proxy/src/test/java/org/eclipse/jetty/ee9/proxy/ForwardProxyServerTest.java b/jetty-ee9/jetty-ee9-proxy/src/test/java/org/eclipse/jetty/ee9/proxy/ForwardProxyServerTest.java index 562fa6adc7a..3fdee000488 100644 --- a/jetty-ee9/jetty-ee9-proxy/src/test/java/org/eclipse/jetty/ee9/proxy/ForwardProxyServerTest.java +++ b/jetty-ee9/jetty-ee9-proxy/src/test/java/org/eclipse/jetty/ee9/proxy/ForwardProxyServerTest.java @@ -72,7 +72,7 @@ public class ForwardProxyServerTest private static SslContextFactory.Server newServerSslContextFactory() { SslContextFactory.Server serverTLS = new SslContextFactory.Server(); - String keyStorePath = MavenTestingUtils.getTestResourceFile("server_keystore.p12").getAbsolutePath(); + String keyStorePath = MavenTestingUtils.getTargetFile("test-classes/server_keystore.p12").getAbsolutePath(); serverTLS.setKeyStorePath(keyStorePath); serverTLS.setKeyStorePassword("storepwd"); return serverTLS; diff --git a/jetty-ee9/jetty-ee9-proxy/src/test/java/org/eclipse/jetty/ee9/proxy/ForwardProxyTLSServerTest.java b/jetty-ee9/jetty-ee9-proxy/src/test/java/org/eclipse/jetty/ee9/proxy/ForwardProxyTLSServerTest.java index 7f6dd16f66f..b806d09a9d9 100644 --- a/jetty-ee9/jetty-ee9-proxy/src/test/java/org/eclipse/jetty/ee9/proxy/ForwardProxyTLSServerTest.java +++ b/jetty-ee9/jetty-ee9-proxy/src/test/java/org/eclipse/jetty/ee9/proxy/ForwardProxyTLSServerTest.java @@ -144,7 +144,7 @@ public class ForwardProxyTLSServerTest private static SslContextFactory.Server newServerSslContextFactory() { SslContextFactory.Server sslContextFactory = new SslContextFactory.Server(); - String keyStorePath = MavenTestingUtils.getTestResourceFile("server_keystore.p12").getAbsolutePath(); + String keyStorePath = MavenTestingUtils.getTargetFile("test-classes/server_keystore.p12").getAbsolutePath(); sslContextFactory.setKeyStorePath(keyStorePath); sslContextFactory.setKeyStorePassword("storepwd"); return sslContextFactory; @@ -153,7 +153,7 @@ public class ForwardProxyTLSServerTest private static SslContextFactory.Server newProxySslContextFactory() { SslContextFactory.Server proxyTLS = new SslContextFactory.Server(); - String keyStorePath = MavenTestingUtils.getTestResourceFile("proxy_keystore.p12").getAbsolutePath(); + String keyStorePath = MavenTestingUtils.getTargetFile("test-classes/proxy_keystore.p12").getAbsolutePath(); proxyTLS.setKeyStorePath(keyStorePath); proxyTLS.setKeyStorePassword("storepwd"); return proxyTLS; @@ -692,7 +692,7 @@ public class ForwardProxyTLSServerTest return keyManagers; } }; - clientSslContextFactory.setKeyStorePath(MavenTestingUtils.getTestResourceFile("client_keystore.p12").getAbsolutePath()); + clientSslContextFactory.setKeyStorePath(MavenTestingUtils.getTargetFile("test-classes/client_keystore.p12").getAbsolutePath()); clientSslContextFactory.setKeyStorePassword("storepwd"); clientSslContextFactory.setEndpointIdentificationAlgorithm(null); ClientConnector clientConnector = new ClientConnector(); @@ -747,7 +747,7 @@ public class ForwardProxyTLSServerTest return super.newSSLEngine(host, port); } }; - clientTLS.setKeyStorePath(MavenTestingUtils.getTestResourceFile("client_server_keystore.p12").getAbsolutePath()); + clientTLS.setKeyStorePath(MavenTestingUtils.getTargetFile("test-classes/client_server_keystore.p12").getAbsolutePath()); clientTLS.setKeyStorePassword("storepwd"); clientTLS.setEndpointIdentificationAlgorithm(null); ClientConnector clientConnector = new ClientConnector(); @@ -765,7 +765,7 @@ public class ForwardProxyTLSServerTest return super.newSSLEngine(host, port); } }; - proxyClientTLS.setKeyStorePath(MavenTestingUtils.getTestResourceFile("client_proxy_keystore.p12").getAbsolutePath()); + proxyClientTLS.setKeyStorePath(MavenTestingUtils.getTargetFile("test-classes/client_proxy_keystore.p12").getAbsolutePath()); proxyClientTLS.setKeyStorePassword("storepwd"); proxyClientTLS.setEndpointIdentificationAlgorithm(null); proxyClientTLS.start(); diff --git a/jetty-ee9/jetty-ee9-proxy/src/test/java/org/eclipse/jetty/ee9/proxy/ProxyServletTest.java b/jetty-ee9/jetty-ee9-proxy/src/test/java/org/eclipse/jetty/ee9/proxy/ProxyServletTest.java index 21f7f859fa3..ac731694235 100644 --- a/jetty-ee9/jetty-ee9-proxy/src/test/java/org/eclipse/jetty/ee9/proxy/ProxyServletTest.java +++ b/jetty-ee9/jetty-ee9-proxy/src/test/java/org/eclipse/jetty/ee9/proxy/ProxyServletTest.java @@ -140,7 +140,7 @@ public class ProxyServletTest server.addConnector(serverConnector); SslContextFactory.Server sslContextFactory = new SslContextFactory.Server(); - String keyStorePath = MavenTestingUtils.getTestResourceFile("server_keystore.p12").getAbsolutePath(); + String keyStorePath = MavenTestingUtils.getTargetFile("test-classes/server_keystore.p12").getAbsolutePath(); sslContextFactory.setKeyStorePath(keyStorePath); sslContextFactory.setKeyStorePassword("storepwd"); tlsServerConnector = new ServerConnector(server, new SslConnectionFactory( From 02324c35e2e3aea4ed62d92e8e3770f0607ef265 Mon Sep 17 00:00:00 2001 From: Olivier Lamy Date: Mon, 25 Jul 2022 19:57:44 +1000 Subject: [PATCH 2/4] remove redundant option Signed-off-by: Olivier Lamy --- .github/workflows/codeql-analysis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 46013d167c4..43b384206f8 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -69,7 +69,7 @@ jobs: - name: Set up Maven run: - mvn -e -B -V --show-version org.apache.maven.plugins:maven-wrapper-plugin:3.1.0:wrapper "-Dmaven=3.8.6" + mvn -e -B -V org.apache.maven.plugins:maven-wrapper-plugin:3.1.0:wrapper "-Dmaven=3.8.6" - name: Clean install dependencies and build run: ./mvnw clean install -DskipTests -V -B From 69a7b06f065378dab1d53371e0f2f71a74b6e20e Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Mon, 25 Jul 2022 13:22:14 +0200 Subject: [PATCH 3/4] Jetty 12.0.x retainable chunk (#8325) Made Chunk implement Retainable, and protocol implementations to link the RetainableByteBuffer all the way to the Chunk. Extended Retainable to have both a retain() and a release() methods. Removed HTTP/2's ISession and IStream, now just using HTTP2Session and HTTP2Stream. Removed *.Adapter classes in favor of interfaces with default methods. Javadoc additions and clarifications. Signed-off-by: Simone Bordet --- .../asciidoc/programming-guide/http2.adoc | 43 +- .../server/http2/server-http2.adoc | 2 +- .../jetty/docs/programming/HTTP2Docs.java | 41 +- .../client/http2/HTTP2ClientDocs.java | 84 ++-- .../client/http3/HTTP3ClientDocs.java | 10 +- .../server/http2/HTTP2ServerDocs.java | 80 +++- .../server/http3/HTTP3ServerDocs.java | 10 +- .../java/client/ConscryptHTTP2ClientTest.java | 17 +- .../alpn/java/client/JDK9HTTP2ClientTest.java | 17 +- .../eclipse/jetty/client/HttpReceiver.java | 19 +- .../util/InputStreamRequestContent.java | 8 +- .../jetty/client/util/PathRequestContent.java | 18 +- .../server/internal/ServerFCGIConnection.java | 2 +- .../java/org/eclipse/jetty/http/Trailers.java | 17 +- .../jetty/http2/client/HTTP2Client.java | 6 +- .../client/HTTP2ClientConnectionFactory.java | 9 +- .../client/internal/HTTP2ClientSession.java | 10 +- .../http2/AbstractFlowControlStrategy.java | 83 ++-- .../http2/BufferingFlowControlStrategy.java | 28 +- .../jetty/http2/FlowControlStrategy.java | 20 +- .../org/eclipse/jetty/http2/ISession.java | 169 -------- .../java/org/eclipse/jetty/http2/IStream.java | 228 ---------- .../http2/SimpleFlowControlStrategy.java | 14 +- .../org/eclipse/jetty/http2/api/Session.java | 125 ++---- .../org/eclipse/jetty/http2/api/Stream.java | 249 +++++++---- .../api/server/ServerSessionListener.java | 11 +- .../jetty/http2/internal/HTTP2Channel.java | 5 +- .../jetty/http2/internal/HTTP2Connection.java | 92 ++-- .../jetty/http2/internal/HTTP2Flusher.java | 29 +- .../jetty/http2/internal/HTTP2Session.java | 382 ++++++++--------- .../jetty/http2/internal/HTTP2Stream.java | 399 ++++++++---------- .../http2/internal/HTTP2StreamEndPoint.java | 212 +++------- .../internal/ClientHTTP2StreamEndPoint.java | 9 +- .../internal/HTTPSessionListenerPromise.java | 31 +- .../http/internal/HttpChannelOverHTTP2.java | 20 +- .../http/internal/HttpReceiverOverHTTP2.java | 381 +++++------------ .../http/internal/HttpSenderOverHTTP2.java | 8 +- .../AbstractHTTP2ServerConnectionFactory.java | 10 +- .../server/HTTP2ServerConnectionFactory.java | 30 +- .../RawHTTP2ServerConnectionFactory.java | 9 +- .../internal/HTTP2ServerConnection.java | 45 +- .../server/internal/HTTP2ServerSession.java | 19 +- .../server/internal/HttpStreamOverHTTP2.java | 38 +- .../internal/HttpTransportOverHTTP2.java | 2 +- .../internal/ServerHTTP2StreamEndPoint.java | 13 +- .../jetty-http2/jetty-http2-tests/pom.xml | 3 +- .../jetty/http2/tests/AsyncIOTest.java | 16 +- .../jetty/http2/tests/AsyncServletTest.java | 20 +- .../BlockedWritesWithSmallThreadPoolTest.java | 33 +- .../eclipse/jetty/http2/tests/CloseTest.java | 6 +- .../tests/ConcurrentStreamCreationTest.java | 6 +- .../jetty/http2/tests/ConnectTimeoutTest.java | 4 +- .../jetty/http2/tests/ConnectTunnelTest.java | 43 +- .../jetty/http2/tests/ContentLengthTest.java | 40 ++ .../jetty/http2/tests/DataDemandTest.java | 189 ++++----- .../http2/tests/FlowControlStalledTest.java | 76 ++-- .../http2/tests/FlowControlStrategyTest.java | 310 ++++++++------ .../http2/tests/FlowControlWindowsTest.java | 34 +- .../eclipse/jetty/http2/tests/GoAwayTest.java | 201 +++++---- .../jetty/http2/tests/H2SpecServer.java | 19 + .../jetty/http2/tests/HTTP2ServerTest.java | 2 +- .../eclipse/jetty/http2/tests/HTTP2Test.java | 214 +++++----- .../HttpClientTransportOverHTTP2Test.java | 100 ++++- .../jetty/http2/tests/IdleTimeoutTest.java | 157 +++---- .../jetty/http2/tests/InterleavingTest.java | 41 +- .../http2/tests/MaxConcurrentStreamsTest.java | 15 +- .../http2/tests/MaxPushedStreamsTest.java | 13 +- .../eclipse/jetty/http2/tests/PingTest.java | 4 +- .../jetty/http2/tests/PrefaceTest.java | 34 +- .../tests/PriorKnowledgeHTTP2OverTLSTest.java | 4 +- .../jetty/http2/tests/PriorityTest.java | 18 +- .../jetty/http2/tests/ProxyProtocolTest.java | 8 +- .../eclipse/jetty/http2/tests/ProxyTest.java | 8 +- .../http2/tests/PushCacheFilterTest.java | 76 ++-- .../http2/tests/PushedResourcesTest.java | 7 +- .../jetty/http2/tests/RawHTTP2ProxyTest.java | 88 ++-- .../http2/tests/RequestTrailersTest.java | 30 +- .../http2/tests/ResponseTrailerTest.java | 13 +- .../jetty/http2/tests/SessionFailureTest.java | 15 +- .../http2/tests/SmallThreadPoolLoadTest.java | 26 +- .../jetty/http2/tests/StreamCloseTest.java | 112 ++--- .../jetty/http2/tests/StreamCountTest.java | 50 ++- .../jetty/http2/tests/StreamResetTest.java | 159 +++---- .../jetty/http2/tests/TrailersTest.java | 45 +- .../test/resources/jetty-logging.properties | 1 + .../org/eclipse/jetty/http3/api/Stream.java | 30 +- .../http3/internal/HTTP3StreamConnection.java | 32 +- .../http/internal/HttpReceiverOverHTTP3.java | 67 ++- .../server/internal/HttpStreamOverHTTP3.java | 17 +- .../jetty/http3/tests/ClientServerTest.java | 8 +- .../jetty/http3/tests/DataDemandTest.java | 4 +- .../jetty/http3/tests/ExternalServerTest.java | 2 +- .../eclipse/jetty/http3/tests/GoAwayTest.java | 6 +- .../http3/tests/HandlerClientServerTest.java | 2 +- .../java/org/eclipse/jetty/io/Content.java | 106 +++-- .../java/org/eclipse/jetty/io/Retainable.java | 146 +++++++ .../jetty/io/RetainableByteBuffer.java | 55 +-- .../io/content/ContentSourceTransformer.java | 57 +-- .../io/content/InputStreamContentSource.java | 18 +- .../jetty/io/content/PathContentSource.java | 40 +- .../jetty/io/internal/ByteBufferChunk.java | 92 +++- .../eclipse/jetty/io/ContentSourceTest.java | 5 +- .../io/ContentSourceTransformerTest.java | 3 +- .../jetty/server/handler/DelayedHandler.java | 2 + .../server/handler/gzip/GzipRequest.java | 72 ++-- .../jetty/server/internal/HttpConnection.java | 29 +- .../org/eclipse/jetty/util/Retainable.java | 19 - .../jetty/ee10/servlet/ServletChannel.java | 1 + .../client/ProxyWithDynamicTransportTest.java | 8 +- .../jetty/test/webapp/HTTP1Servlet.java | 4 +- .../ee9/nested/AsyncContentProducerTest.java | 12 +- .../nested/BlockingContentProducerTest.java | 10 +- .../client/ProxyWithDynamicTransportTest.java | 8 +- 113 files changed, 3039 insertions(+), 3110 deletions(-) delete mode 100644 jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/ISession.java delete mode 100644 jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/IStream.java create mode 100644 jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/Retainable.java delete mode 100644 jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/Retainable.java diff --git a/documentation/jetty-documentation/src/main/asciidoc/programming-guide/http2.adoc b/documentation/jetty-documentation/src/main/asciidoc/programming-guide/http2.adoc index 0b819a53d6f..aff3ee71df1 100644 --- a/documentation/jetty-documentation/src/main/asciidoc/programming-guide/http2.adoc +++ b/documentation/jetty-documentation/src/main/asciidoc/programming-guide/http2.adoc @@ -26,9 +26,11 @@ This means that a sender and a receiver maintain a _flow control window_ that tr When a sender sends data bytes, it reduces its flow control window. When a receiver receives data bytes, it also reduces its flow control window, and then passes the received data bytes to the application. The application consumes the data bytes and tells back the receiver that it has consumed the data bytes. -The receiver then enlarges the flow control window, and arranges to send a message to the sender with the number of bytes consumed, so that the sender can enlarge its flow control window. +The receiver then enlarges the flow control window, and the implementation arranges to send a message to the sender with the number of bytes consumed, so that the sender can enlarge its flow control window. -A sender can send data bytes up to its whole flow control window, then it must stop sending until it receives a message from the receiver that the data bytes have been consumed, which enlarges the flow control window, which allows the sender to send more data bytes. +A sender can send data bytes up to its whole flow control window, then it must stop sending. +The sender may resume sending data bytes when it receives a message from the receiver that the data bytes sent previously have been consumed. +This message enlarges the sender flow control window, which allows the sender to send more data bytes. HTTP/2 defines _two_ flow control windows: one for each _session_, and one for each _stream_. Let's see with an example how they interact, assuming that in this example the session flow control window is 120 bytes and the stream flow control window is 100 bytes. @@ -37,33 +39,46 @@ The sender opens a session, and then opens `stream_1` on that session, and sends At this point the session flow control window is `40` bytes (`120 - 80`), and ``stream_1``'s flow control window is `20` bytes (`100 - 80`). The sender now opens `stream_2` on the same session and sends `40` data bytes. At this point, the session flow control window is `0` bytes (`40 - 40`), while ``stream_2``'s flow control window is `60` (`100 - 40`). -Since now the session flow control window is `0`, the sender cannot send more data bytes, neither on `stream_1` nor on `stream_2` despite both have their stream flow control windows greater than `0`. +Since now the session flow control window is `0`, the sender cannot send more data bytes, neither on `stream_1` nor on `stream_2`, nor on other streams, despite all the streams having their stream flow control windows greater than `0`. The receiver consumes ``stream_2``'s `40` data bytes and sends a message to the sender with this information. -At this point, the session flow control window is `40` (`0 40`), ``stream_1``'s flow control window is still `20` and ``stream_2``'s flow control window is `100` (`60 40`). -If the sender opens `stream_3` and would like to send 50 data bytes, it would only be able to send `40` because that is the maximum allowed by the session flow control window at this point. +At this point, the session flow control window is `40` (``0 + 40``), ``stream_1``'s flow control window is still `20` and ``stream_2``'s flow control window is `100` (``60 + 40``). +If the sender opens `stream_3` and would like to send `50` data bytes, it would only be able to send `40` because that is the maximum allowed by the session flow control window at this point. It is therefore very important that applications notify the fact that they have consumed data bytes as soon as possible, so that the implementation (the receiver) can send a message to the sender (in the form of a `WINDOW_UPDATE` frame) with the information to enlarge the flow control window, therefore reducing the possibility that sender stalls due to the flow control windows being reduced to `0`. end::flowControl[] tag::apiFlowControl[] -NOTE: Returning from the `onData(...)` method implicitly demands for more `DATA` frames (unless the one just delivered was the last). -Additional `DATA` frames may be delivered immediately if they are available or later, asynchronously, when they arrive. +[NOTE] +==== +When `onDataAvailable(Stream stream)` is invoked, the demand is implicitly cancelled. -Applications that consume the content buffer within `onData(...)` (for example, writing it to a file, or copying the bytes to another storage) should succeed the callback as soon as they have consumed the content buffer. +Just returning from the `onDataAvailable(Stream stream)` method does _not_ implicitly demand for more `DATA` frames. + +Applications must call `Stream.demand()` to explicitly require that `onDataAvailable(Stream stream)` is invoked again when more `DATA` frames are available. +==== + +Applications that consume the content buffer within `onDataAvailable(Stream stream)` (for example, writing it to a file, or copying the bytes to another storage) should call `Data.release()` as soon as they have consumed the content buffer. This allows the implementation to reuse the buffer, reducing the memory requirements needed to handle the content buffers. -Alternatively, a client application may store away _both_ the buffer and the callback to consume the buffer bytes later, or pass _both_ the buffer and the callback to another asynchronous API (this is typical in proxy applications). +Alternatively, a client application may store away the `Data` object to consume the buffer bytes later, or pass the `Data` object to another asynchronous API (this is typical in proxy applications). -IMPORTANT: Completing the `Callback` is very important not only to allow the implementation to reuse the buffer, but also tells the implementation to enlarge the stream and session flow control windows so that the sender will be able to send more `DATA` frames without stalling. +[IMPORTANT] +==== +Calling `Data.release()` is very important not only to allow the implementation to reuse the buffer, but also tells the implementation to enlarge the stream and session flow control windows so that the sender will be able to send more `DATA` frames without stalling. +==== -Applications can also precisely control _when_ to demand more `DATA` frames, by implementing the `onDataDemanded(...)` method instead of `onData(...)`: +Applications can unwrap the `Data` object into some other object that may be used later, provided that the _release_ semantic is maintained: [source,java,indent=0] ---- -include::{doc_code}/org/eclipse/jetty/docs/programming/HTTP2Docs.java[tags=dataDemanded] +include::{doc_code}/org/eclipse/jetty/docs/programming/HTTP2Docs.java[tags=dataUnwrap] ---- -IMPORTANT: Applications that implement `onDataDemanded(...)` must remember to call `Stream.demand(...)`. -If they don't, the implementation will not deliver `DATA` frames and the application will stall threadlessly until an idle timeout fires to close the stream or the session. +[IMPORTANT] +==== +Applications that implement `onDataAvailable(Stream stream)` must remember to call `Stream.demand()` eventually. + +If they do not call `Stream.demand()`, the implementation will not invoke `onDataAvailable(Stream stream)` to deliver more `DATA` frames and the application will stall threadlessly until an idle timeout fires to close the stream or the session. +==== end::apiFlowControl[] diff --git a/documentation/jetty-documentation/src/main/asciidoc/programming-guide/server/http2/server-http2.adoc b/documentation/jetty-documentation/src/main/asciidoc/programming-guide/server/http2/server-http2.adoc index 07afeaa2b20..6c2f8e146b3 100644 --- a/documentation/jetty-documentation/src/main/asciidoc/programming-guide/server/http2/server-http2.adoc +++ b/documentation/jetty-documentation/src/main/asciidoc/programming-guide/server/http2/server-http2.adoc @@ -89,7 +89,7 @@ Receiving the `HEADERS` frame opens the `Stream`: include::../../{doc_code}/org/eclipse/jetty/docs/programming/server/http2/HTTP2ServerDocs.java[tags=request] ---- -Server applications should return a `Stream.Listener` implementation from `onNewStream(...)` to be notified of events generated by the client, such as `DATA` frames carrying request content, or a `RST_STREAM` frame indicating that the client wants to _reset_ the request, or an idle timeout event indicating that the client was supposed to send more frames but it did not. +Server applications should return a `Stream.Listener` implementation from `onNewStream(\...)` to be notified of events generated by the client, such as `DATA` frames carrying request content, or a `RST_STREAM` frame indicating that the client wants to _reset_ the request, or an idle timeout event indicating that the client was supposed to send more frames but it did not. The example below shows how to receive request content: diff --git a/documentation/jetty-documentation/src/main/java/org/eclipse/jetty/docs/programming/HTTP2Docs.java b/documentation/jetty-documentation/src/main/java/org/eclipse/jetty/docs/programming/HTTP2Docs.java index 80386ea2f4e..0c5b57f5020 100644 --- a/documentation/jetty-documentation/src/main/java/org/eclipse/jetty/docs/programming/HTTP2Docs.java +++ b/documentation/jetty-documentation/src/main/java/org/eclipse/jetty/docs/programming/HTTP2Docs.java @@ -28,7 +28,6 @@ import org.eclipse.jetty.http.MetaData; import org.eclipse.jetty.http2.api.Session; import org.eclipse.jetty.http2.api.Stream; import org.eclipse.jetty.http2.client.HTTP2Client; -import org.eclipse.jetty.http2.frames.DataFrame; import org.eclipse.jetty.http2.frames.HeadersFrame; import org.eclipse.jetty.util.Callback; @@ -40,7 +39,7 @@ public class HTTP2Docs HTTP2Client http2Client = new HTTP2Client(); http2Client.start(); SocketAddress serverAddress = new InetSocketAddress("localhost", 8080); - CompletableFuture sessionCF = http2Client.connect(serverAddress, new Session.Listener.Adapter()); + CompletableFuture sessionCF = http2Client.connect(serverAddress, new Session.Listener() {}); Session session = sessionCF.get(); HttpFields requestHeaders = HttpFields.build() @@ -48,7 +47,7 @@ public class HTTP2Docs MetaData.Request request = new MetaData.Request("GET", HttpURI.from("http://localhost:8080/path"), HttpVersion.HTTP_2, requestHeaders); HeadersFrame headersFrame = new HeadersFrame(request, null, true); - // tag::dataDemanded[] + // tag::dataUnwrap[] class Chunk { private final ByteBuffer buffer; @@ -64,29 +63,39 @@ public class HTTP2Docs // A queue that consumers poll to consume content asynchronously. Queue dataQueue = new ConcurrentLinkedQueue<>(); - // Implementation of Stream.Listener.onDataDemanded(...) - // in case of asynchronous content consumption and demand. - Stream.Listener listener = new Stream.Listener.Adapter() + // Implementation of Stream.Listener.onDataAvailable(Stream stream) + // in case of unwrapping of the Data object for asynchronous content + // consumption and demand. + Stream.Listener listener = new Stream.Listener() { @Override - public void onDataDemanded(Stream stream, DataFrame frame, Callback callback) + public void onDataAvailable(Stream stream) { - // Get the content buffer. - ByteBuffer buffer = frame.getData(); + Stream.Data data = stream.readData(); - // Store buffer to consume it asynchronously, and wrap the callback. + if (data == null) + { + stream.demand(); + return; + } + + // Get the content buffer. + ByteBuffer buffer = data.frame().getData(); + + // Unwrap the Data object, converting it to a Chunk. + // The Data.release() semantic is maintained in the completion of the Callback. dataQueue.offer(new Chunk(buffer, Callback.from(() -> { // When the buffer has been consumed, then: - // A) succeed the nested callback. - callback.succeeded(); + // A) release the Data object. + data.release(); // B) demand more DATA frames. - stream.demand(1); - }, callback::failed))); + stream.demand(); + }))); - // Do not demand more content here, to avoid to overflow the queue. + // Do not demand more data here, to avoid to overflow the queue. } }; - // end::dataDemanded[] + // end::dataUnwrap[] } } diff --git a/documentation/jetty-documentation/src/main/java/org/eclipse/jetty/docs/programming/client/http2/HTTP2ClientDocs.java b/documentation/jetty-documentation/src/main/java/org/eclipse/jetty/docs/programming/client/http2/HTTP2ClientDocs.java index dfbf13d2e25..4f8ec17f66f 100644 --- a/documentation/jetty-documentation/src/main/java/org/eclipse/jetty/docs/programming/client/http2/HTTP2ClientDocs.java +++ b/documentation/jetty-documentation/src/main/java/org/eclipse/jetty/docs/programming/client/http2/HTTP2ClientDocs.java @@ -77,7 +77,7 @@ public class HTTP2ClientDocs // Connect to the server, the CompletableFuture will be // notified when the connection is succeeded (or failed). - CompletableFuture sessionCF = http2Client.connect(serverAddress, new Session.Listener.Adapter()); + CompletableFuture sessionCF = http2Client.connect(serverAddress, new Session.Listener() {}); // Block to obtain the Session. // Alternatively you can use the CompletableFuture APIs to avoid blocking. @@ -98,7 +98,7 @@ public class HTTP2ClientDocs // Connect to the server, the CompletableFuture will be // notified when the connection is succeeded (or failed). - CompletableFuture sessionCF = http2Client.connect(connector.getSslContextFactory(), serverAddress, new Session.Listener.Adapter()); + CompletableFuture sessionCF = http2Client.connect(connector.getSslContextFactory(), serverAddress, new Session.Listener() {}); // Block to obtain the Session. // Alternatively you can use the CompletableFuture APIs to avoid blocking. @@ -113,7 +113,7 @@ public class HTTP2ClientDocs // tag::configure[] SocketAddress serverAddress = new InetSocketAddress("localhost", 8080); - http2Client.connect(serverAddress, new Session.Listener.Adapter() + http2Client.connect(serverAddress, new Session.Listener() { @Override public Map onPreface(Session session) @@ -138,7 +138,7 @@ public class HTTP2ClientDocs http2Client.start(); // tag::newStream[] SocketAddress serverAddress = new InetSocketAddress("localhost", 8080); - CompletableFuture sessionCF = http2Client.connect(serverAddress, new Session.Listener.Adapter()); + CompletableFuture sessionCF = http2Client.connect(serverAddress, new Session.Listener() {}); Session session = sessionCF.get(); // Configure the request headers. @@ -153,7 +153,7 @@ public class HTTP2ClientDocs HeadersFrame headersFrame = new HeadersFrame(request, null, true); // Open a Stream by sending the HEADERS frame. - session.newStream(headersFrame, new Stream.Listener.Adapter()); + session.newStream(headersFrame, null); // end::newStream[] } @@ -163,7 +163,7 @@ public class HTTP2ClientDocs http2Client.start(); // tag::newStreamWithData[] SocketAddress serverAddress = new InetSocketAddress("localhost", 8080); - CompletableFuture sessionCF = http2Client.connect(serverAddress, new Session.Listener.Adapter()); + CompletableFuture sessionCF = http2Client.connect(serverAddress, new Session.Listener() {}); Session session = sessionCF.get(); // Configure the request headers. @@ -178,7 +178,7 @@ public class HTTP2ClientDocs HeadersFrame headersFrame = new HeadersFrame(request, null, false); // Open a Stream by sending the HEADERS frame. - CompletableFuture streamCF = session.newStream(headersFrame, new Stream.Listener.Adapter()); + CompletableFuture streamCF = session.newStream(headersFrame, null); // Block to obtain the Stream. // Alternatively you can use the CompletableFuture APIs to avoid blocking. @@ -205,7 +205,7 @@ public class HTTP2ClientDocs HTTP2Client http2Client = new HTTP2Client(); http2Client.start(); SocketAddress serverAddress = new InetSocketAddress("localhost", 8080); - CompletableFuture sessionCF = http2Client.connect(serverAddress, new Session.Listener.Adapter()); + CompletableFuture sessionCF = http2Client.connect(serverAddress, new Session.Listener() {}); Session session = sessionCF.get(); HttpFields requestHeaders = HttpFields.build() @@ -215,7 +215,7 @@ public class HTTP2ClientDocs // tag::responseListener[] // Open a Stream by sending the HEADERS frame. - session.newStream(headersFrame, new Stream.Listener.Adapter() + session.newStream(headersFrame, new Stream.Listener() { @Override public void onHeaders(Stream stream, HeadersFrame frame) @@ -227,6 +227,12 @@ public class HTTP2ClientDocs { MetaData.Response response = (MetaData.Response)metaData; System.getLogger("http2").log(INFO, "Received response {0}", response); + if (!frame.isEndStream()) + { + // Demand for DATA frames, so that onDataAvailable() + // below will be called when they are available. + stream.demand(); + } } else { @@ -235,19 +241,29 @@ public class HTTP2ClientDocs } @Override - public void onData(Stream stream, DataFrame frame, Callback callback) + public void onDataAvailable(Stream stream) { + // Read a Data object. + Stream.Data data = stream.readData(); + + if (data == null) + { + // Demand more DATA frames. + stream.demand(); + return; + } + // Get the content buffer. - ByteBuffer buffer = frame.getData(); + ByteBuffer buffer = data.frame().getData(); // Consume the buffer, here - as an example - just log it. System.getLogger("http2").log(INFO, "Consuming buffer {0}", buffer); // Tell the implementation that the buffer has been consumed. - callback.succeeded(); + data.release(); - // By returning from the method, implicitly tell the implementation - // to deliver to this method more DATA frames when they are available. + // Demand more DATA frames when they are available. + stream.demand(); } }); // end::responseListener[] @@ -258,7 +274,7 @@ public class HTTP2ClientDocs HTTP2Client http2Client = new HTTP2Client(); http2Client.start(); SocketAddress serverAddress = new InetSocketAddress("localhost", 8080); - CompletableFuture sessionCF = http2Client.connect(serverAddress, new Session.Listener.Adapter()); + CompletableFuture sessionCF = http2Client.connect(serverAddress, new Session.Listener() {}); Session session = sessionCF.get(); HttpFields requestHeaders = HttpFields.build() @@ -268,12 +284,15 @@ public class HTTP2ClientDocs // tag::reset[] // Open a Stream by sending the HEADERS frame. - CompletableFuture streamCF = session.newStream(headersFrame, new Stream.Listener.Adapter() + CompletableFuture streamCF = session.newStream(headersFrame, new Stream.Listener() { @Override - public void onReset(Stream stream, ResetFrame frame) + public void onReset(Stream stream, ResetFrame frame, Callback callback) { // The server reset this stream. + + // Succeed the callback to signal that the reset event has been handled. + callback.succeeded(); } }); Stream stream = streamCF.get(); @@ -288,7 +307,7 @@ public class HTTP2ClientDocs HTTP2Client http2Client = new HTTP2Client(); http2Client.start(); SocketAddress serverAddress = new InetSocketAddress("localhost", 8080); - CompletableFuture sessionCF = http2Client.connect(serverAddress, new Session.Listener.Adapter()); + CompletableFuture sessionCF = http2Client.connect(serverAddress, new Session.Listener() {}); Session session = sessionCF.get(); HttpFields requestHeaders = HttpFields.build() @@ -298,7 +317,7 @@ public class HTTP2ClientDocs // tag::push[] // Open a Stream by sending the HEADERS frame. - CompletableFuture streamCF = session.newStream(headersFrame, new Stream.Listener.Adapter() + CompletableFuture streamCF = session.newStream(headersFrame, new Stream.Listener() { @Override public Stream.Listener onPush(Stream pushedStream, PushPromiseFrame frame) @@ -314,7 +333,7 @@ public class HTTP2ClientDocs Stream primaryStream = pushedStream.getSession().getStream(frame.getStreamId()); // Return a Stream.Listener to listen for the pushed "response" events. - return new Stream.Listener.Adapter() + return new Stream.Listener() { @Override public void onHeaders(Stream stream, HeadersFrame frame) @@ -326,18 +345,29 @@ public class HTTP2ClientDocs { // The pushed "response" headers. HttpFields pushedResponseHeaders = metaData.getFields(); + + // Typically a pushed stream has data, so demand for data. + stream.demand(); } } @Override - public void onData(Stream stream, DataFrame frame, Callback callback) + public void onDataAvailable(Stream stream) { // Handle the pushed stream "response" content. + Stream.Data data = stream.readData(); + + if (data == null) + { + stream.demand(); + return; + } + // The pushed stream "response" content bytes. - ByteBuffer buffer = frame.getData(); - // Consume the buffer and complete the callback. - callback.succeeded(); + ByteBuffer buffer = data.frame().getData(); + // Consume the buffer and release the Data object. + data.release(); } }; } @@ -350,7 +380,7 @@ public class HTTP2ClientDocs HTTP2Client http2Client = new HTTP2Client(); http2Client.start(); SocketAddress serverAddress = new InetSocketAddress("localhost", 8080); - CompletableFuture sessionCF = http2Client.connect(serverAddress, new Session.Listener.Adapter()); + CompletableFuture sessionCF = http2Client.connect(serverAddress, new Session.Listener() {}); Session session = sessionCF.get(); HttpFields requestHeaders = HttpFields.build() @@ -360,12 +390,12 @@ public class HTTP2ClientDocs // tag::pushReset[] // Open a Stream by sending the HEADERS frame. - CompletableFuture streamCF = session.newStream(headersFrame, new Stream.Listener.Adapter() + CompletableFuture streamCF = session.newStream(headersFrame, new Stream.Listener() { @Override public Stream.Listener onPush(Stream pushedStream, PushPromiseFrame frame) { - // Reset the pushed stream to tell the server we are not interested. + // Reset the pushed stream to tell the server you are not interested. pushedStream.reset(new ResetFrame(pushedStream.getId(), ErrorCode.CANCEL_STREAM_ERROR.code), Callback.NOOP); // Not interested in listening to pushed response events. diff --git a/documentation/jetty-documentation/src/main/java/org/eclipse/jetty/docs/programming/client/http3/HTTP3ClientDocs.java b/documentation/jetty-documentation/src/main/java/org/eclipse/jetty/docs/programming/client/http3/HTTP3ClientDocs.java index e54d5bf5da3..66229040988 100644 --- a/documentation/jetty-documentation/src/main/java/org/eclipse/jetty/docs/programming/client/http3/HTTP3ClientDocs.java +++ b/documentation/jetty-documentation/src/main/java/org/eclipse/jetty/docs/programming/client/http3/HTTP3ClientDocs.java @@ -212,7 +212,7 @@ public class HTTP3ClientDocs process(data.getByteBuffer()); // Notify the implementation that the content has been consumed. - data.complete(); + data.release(); if (!data.isLast()) { @@ -266,7 +266,7 @@ public class HTTP3ClientDocs HTTP3Client http3Client = new HTTP3Client(); http3Client.start(); SocketAddress serverAddress = new InetSocketAddress("localhost", 8444); - CompletableFuture sessionCF = http3Client.connect(serverAddress, new Session.Listener.Adapter()); + CompletableFuture sessionCF = http3Client.connect(serverAddress, new Session.Listener()); Session session = sessionCF.get(); HttpFields requestHeaders = HttpFields.build() @@ -276,7 +276,7 @@ public class HTTP3ClientDocs // tag::push[] // Open a Stream by sending the HEADERS frame. - CompletableFuture streamCF = session.newStream(headersFrame, new Stream.Listener.Adapter() + CompletableFuture streamCF = session.newStream(headersFrame, new Stream.Listener() { @Override public Stream.Listener onPush(Stream pushedStream, PushPromiseFrame frame) @@ -328,7 +328,7 @@ public class HTTP3ClientDocs HTTP3Client http3Client = new HTTP3Client(); http3Client.start(); SocketAddress serverAddress = new InetSocketAddress("localhost", 8444); - CompletableFuture sessionCF = http3Client.connect(serverAddress, new Session.Listener.Adapter()); + CompletableFuture sessionCF = http3Client.connect(serverAddress, new Session.Listener()); Session session = sessionCF.get(); HttpFields requestHeaders = HttpFields.build() @@ -338,7 +338,7 @@ public class HTTP3ClientDocs // tag::pushReset[] // Open a Stream by sending the HEADERS frame. - CompletableFuture streamCF = session.newStream(headersFrame, new Stream.Listener.Adapter() + CompletableFuture streamCF = session.newStream(headersFrame, new Stream.Listener() { @Override public Stream.Listener onPush(Stream pushedStream, PushPromiseFrame frame) diff --git a/documentation/jetty-documentation/src/main/java/org/eclipse/jetty/docs/programming/server/http2/HTTP2ServerDocs.java b/documentation/jetty-documentation/src/main/java/org/eclipse/jetty/docs/programming/server/http2/HTTP2ServerDocs.java index a3dac9ec95c..94753853aa7 100644 --- a/documentation/jetty-documentation/src/main/java/org/eclipse/jetty/docs/programming/server/http2/HTTP2ServerDocs.java +++ b/documentation/jetty-documentation/src/main/java/org/eclipse/jetty/docs/programming/server/http2/HTTP2ServerDocs.java @@ -51,7 +51,7 @@ public class HTTP2ServerDocs // Create a Server instance. Server server = new Server(); - ServerSessionListener sessionListener = new ServerSessionListener.Adapter(); + ServerSessionListener sessionListener = new ServerSessionListener() {}; // Create a ServerConnector with RawHTTP2ServerConnectionFactory. RawHTTP2ServerConnectionFactory http2 = new RawHTTP2ServerConnectionFactory(sessionListener); @@ -77,7 +77,7 @@ public class HTTP2ServerDocs public void accept() { // tag::accept[] - ServerSessionListener sessionListener = new ServerSessionListener.Adapter() + ServerSessionListener sessionListener = new ServerSessionListener() { @Override public void onAccept(Session session) @@ -92,7 +92,7 @@ public class HTTP2ServerDocs public void preface() { // tag::preface[] - ServerSessionListener sessionListener = new ServerSessionListener.Adapter() + ServerSessionListener sessionListener = new ServerSessionListener() { @Override public Map onPreface(Session session) @@ -112,7 +112,7 @@ public class HTTP2ServerDocs public void request() { // tag::request[] - ServerSessionListener sessionListener = new ServerSessionListener.Adapter() + ServerSessionListener sessionListener = new ServerSessionListener() { @Override public Stream.Listener onNewStream(Stream stream, HeadersFrame frame) @@ -122,7 +122,10 @@ public class HTTP2ServerDocs // Return a Stream.Listener to handle the request events, // for example request content events or a request reset. - return new Stream.Listener.Adapter(); + return new Stream.Listener() + { + // Override callback methods for events you are interested in. + }; } }; // end::request[] @@ -131,29 +134,41 @@ public class HTTP2ServerDocs public void requestContent() { // tag::requestContent[] - ServerSessionListener sessionListener = new ServerSessionListener.Adapter() + ServerSessionListener sessionListener = new ServerSessionListener() { @Override public Stream.Listener onNewStream(Stream stream, HeadersFrame frame) { MetaData.Request request = (MetaData.Request)frame.getMetaData(); + + // Demand for request data content. + stream.demand(); + // Return a Stream.Listener to handle the request events. - return new Stream.Listener.Adapter() + return new Stream.Listener() { @Override - public void onData(Stream stream, DataFrame frame, Callback callback) + public void onDataAvailable(Stream stream) { + Stream.Data data = stream.readData(); + + if (data == null) + { + stream.demand(); + return; + } + // Get the content buffer. - ByteBuffer buffer = frame.getData(); + ByteBuffer buffer = data.frame().getData(); // Consume the buffer, here - as an example - just log it. System.getLogger("http2").log(INFO, "Consuming buffer {0}", buffer); // Tell the implementation that the buffer has been consumed. - callback.succeeded(); + data.release(); - // By returning from the method, implicitly tell the implementation - // to deliver to this method more DATA frames when they are available. + // Demand more DATA frames when they are available. + stream.demand(); } }; } @@ -164,7 +179,7 @@ public class HTTP2ServerDocs public void response() { // tag::response[] - ServerSessionListener sessionListener = new ServerSessionListener.Adapter() + ServerSessionListener sessionListener = new ServerSessionListener() { @Override public Stream.Listener onNewStream(Stream stream, HeadersFrame frame) @@ -178,15 +193,30 @@ public class HTTP2ServerDocs } else { - return new Stream.Listener.Adapter() + // Demand for request data. + stream.demand(); + + // Return a listener to handle the request events. + return new Stream.Listener() { @Override - public void onData(Stream stream, DataFrame frame, Callback callback) + public void onDataAvailable(Stream stream) { + Stream.Data data = stream.readData(); + + if (data == null) + { + stream.demand(); + return; + } + // Consume the request content. - callback.succeeded(); - if (frame.isEndStream()) + data.release(); + + if (data.frame().isEndStream()) respond(stream, request); + else + stream.demand(); } }; } @@ -230,7 +260,7 @@ public class HTTP2ServerDocs { float maxRequestRate = 0F; // tag::reset[] - ServerSessionListener sessionListener = new ServerSessionListener.Adapter() + ServerSessionListener sessionListener = new ServerSessionListener() { @Override public Stream.Listener onNewStream(Stream stream, HeadersFrame frame) @@ -247,7 +277,10 @@ public class HTTP2ServerDocs // The request is accepted. MetaData.Request request = (MetaData.Request)frame.getMetaData(); // Return a Stream.Listener to handle the request events. - return new Stream.Listener.Adapter(); + return new Stream.Listener() + { + // Override callback methods for events you are interested in. + }; } } // tag::exclude[] @@ -267,7 +300,7 @@ public class HTTP2ServerDocs // The favicon bytes. ByteBuffer faviconBuffer = BufferUtil.toBuffer(Resource.newResource("/path/to/favicon.ico"), true); - ServerSessionListener sessionListener = new ServerSessionListener.Adapter() + ServerSessionListener sessionListener = new ServerSessionListener() { // By default, push is enabled. private boolean pushEnabled = true; @@ -292,7 +325,7 @@ public class HTTP2ServerDocs HttpURI pushedURI = HttpURI.build(request.getURI()).path("/favicon.ico"); MetaData.Request pushedRequest = new MetaData.Request("GET", pushedURI, HttpVersion.HTTP_2, HttpFields.EMPTY); PushPromiseFrame promiseFrame = new PushPromiseFrame(stream.getId(), 0, pushedRequest); - stream.push(promiseFrame, new Stream.Listener.Adapter()) + stream.push(promiseFrame, null) .thenCompose(pushedStream -> { // Send the favicon "response". @@ -302,7 +335,10 @@ public class HTTP2ServerDocs }); } // Return a Stream.Listener to handle the request events. - return new Stream.Listener.Adapter(); + return new Stream.Listener() + { + // Override callback methods for events you are interested in. + }; } }; // end::push[] diff --git a/documentation/jetty-documentation/src/main/java/org/eclipse/jetty/docs/programming/server/http3/HTTP3ServerDocs.java b/documentation/jetty-documentation/src/main/java/org/eclipse/jetty/docs/programming/server/http3/HTTP3ServerDocs.java index 588b4affd78..960a7f2561b 100644 --- a/documentation/jetty-documentation/src/main/java/org/eclipse/jetty/docs/programming/server/http3/HTTP3ServerDocs.java +++ b/documentation/jetty-documentation/src/main/java/org/eclipse/jetty/docs/programming/server/http3/HTTP3ServerDocs.java @@ -157,7 +157,7 @@ public class HTTP3ServerDocs System.getLogger("http3").log(INFO, "Consuming buffer {0}", buffer); // Tell the implementation that the buffer has been consumed. - data.complete(); + data.release(); if (!data.isLast()) { @@ -204,7 +204,7 @@ public class HTTP3ServerDocs else { // Consume the request content. - data.complete(); + data.release(); if (data.isLast()) respond(stream, request); } @@ -290,7 +290,7 @@ public class HTTP3ServerDocs // The favicon bytes. ByteBuffer faviconBuffer = BufferUtil.toBuffer(Resource.newResource("/path/to/favicon.ico"), true); - ServerSessionListener sessionListener = new ServerSessionListener.Adapter() + ServerSessionListener sessionListener = new ServerSessionListener() { // By default, push is enabled. private boolean pushEnabled = true; @@ -315,7 +315,7 @@ public class HTTP3ServerDocs HttpURI pushedURI = HttpURI.build(request.getURI()).path("/favicon.ico"); MetaData.Request pushedRequest = new MetaData.Request("GET", pushedURI, HttpVersion.HTTP_2, HttpFields.EMPTY); PushPromiseFrame promiseFrame = new PushPromiseFrame(stream.getId(), 0, pushedRequest); - stream.push(promiseFrame, new Stream.Listener.Adapter()) + stream.push(promiseFrame, null) .thenCompose(pushedStream -> { // Send the favicon "response". @@ -325,7 +325,7 @@ public class HTTP3ServerDocs }); } // Return a Stream.Listener to handle the request events. - return new Stream.Listener.Adapter(); + return new Stream.Listener(); } }; // end::push[] diff --git a/jetty-core/jetty-alpn/jetty-alpn-conscrypt-client/src/test/java/org/eclipse/jetty/alpn/java/client/ConscryptHTTP2ClientTest.java b/jetty-core/jetty-alpn/jetty-alpn-conscrypt-client/src/test/java/org/eclipse/jetty/alpn/java/client/ConscryptHTTP2ClientTest.java index ce12386b3b4..2acbb0983db 100644 --- a/jetty-core/jetty-alpn/jetty-alpn-conscrypt-client/src/test/java/org/eclipse/jetty/alpn/java/client/ConscryptHTTP2ClientTest.java +++ b/jetty-core/jetty-alpn/jetty-alpn-conscrypt-client/src/test/java/org/eclipse/jetty/alpn/java/client/ConscryptHTTP2ClientTest.java @@ -28,9 +28,7 @@ import org.eclipse.jetty.http.MetaData; import org.eclipse.jetty.http2.api.Session; import org.eclipse.jetty.http2.api.Stream; import org.eclipse.jetty.http2.client.HTTP2Client; -import org.eclipse.jetty.http2.frames.DataFrame; import org.eclipse.jetty.http2.frames.HeadersFrame; -import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.FuturePromise; import org.eclipse.jetty.util.Jetty; import org.eclipse.jetty.util.Promise; @@ -69,14 +67,14 @@ public class ConscryptHTTP2ClientTest client.start(); FuturePromise sessionPromise = new FuturePromise<>(); - client.connect(sslContextFactory, new InetSocketAddress(host, port), new Session.Listener.Adapter(), sessionPromise); + client.connect(sslContextFactory, new InetSocketAddress(host, port), new Session.Listener() {}, sessionPromise); Session session = sessionPromise.get(15, TimeUnit.SECONDS); HttpFields requestFields = HttpFields.build().put("User-Agent", client.getClass().getName() + "/" + Jetty.VERSION); MetaData.Request metaData = new MetaData.Request("GET", HttpURI.from("https://" + host + ":" + port + "/"), HttpVersion.HTTP_2, requestFields); HeadersFrame headersFrame = new HeadersFrame(metaData, null, true); CountDownLatch latch = new CountDownLatch(1); - session.newStream(headersFrame, new Promise.Adapter<>(), new Stream.Listener.Adapter() + session.newStream(headersFrame, new Promise.Adapter<>(), new Stream.Listener() { @Override public void onHeaders(Stream stream, HeadersFrame frame) @@ -84,14 +82,17 @@ public class ConscryptHTTP2ClientTest System.err.println(frame); if (frame.isEndStream()) latch.countDown(); + stream.demand(); } @Override - public void onData(Stream stream, DataFrame frame, Callback callback) + public void onDataAvailable(Stream stream) { - System.err.println(frame); - callback.succeeded(); - if (frame.isEndStream()) + Stream.Data data = stream.readData(); + System.err.println(data); + data.release(); + stream.demand(); + if (data.frame().isEndStream()) latch.countDown(); } }); diff --git a/jetty-core/jetty-alpn/jetty-alpn-java-client/src/test/java/org/eclipse/jetty/alpn/java/client/JDK9HTTP2ClientTest.java b/jetty-core/jetty-alpn/jetty-alpn-java-client/src/test/java/org/eclipse/jetty/alpn/java/client/JDK9HTTP2ClientTest.java index 63e4e38667a..fb4decbc420 100644 --- a/jetty-core/jetty-alpn/jetty-alpn-java-client/src/test/java/org/eclipse/jetty/alpn/java/client/JDK9HTTP2ClientTest.java +++ b/jetty-core/jetty-alpn/jetty-alpn-java-client/src/test/java/org/eclipse/jetty/alpn/java/client/JDK9HTTP2ClientTest.java @@ -25,9 +25,7 @@ import org.eclipse.jetty.http.MetaData; import org.eclipse.jetty.http2.api.Session; import org.eclipse.jetty.http2.api.Stream; import org.eclipse.jetty.http2.client.HTTP2Client; -import org.eclipse.jetty.http2.frames.DataFrame; import org.eclipse.jetty.http2.frames.HeadersFrame; -import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.FuturePromise; import org.eclipse.jetty.util.Jetty; import org.eclipse.jetty.util.Promise; @@ -55,7 +53,7 @@ public class JDK9HTTP2ClientTest client.start(); FuturePromise sessionPromise = new FuturePromise<>(); - client.connect(sslContextFactory, new InetSocketAddress(host, port), new Session.Listener.Adapter(), sessionPromise); + client.connect(sslContextFactory, new InetSocketAddress(host, port), new Session.Listener() {}, sessionPromise); Session session = sessionPromise.get(15, TimeUnit.SECONDS); HttpFields.Mutable requestFields = HttpFields.build(); @@ -63,7 +61,7 @@ public class JDK9HTTP2ClientTest MetaData.Request metaData = new MetaData.Request("GET", HttpURI.from("https://" + host + ":" + port + "/"), HttpVersion.HTTP_2, requestFields); HeadersFrame headersFrame = new HeadersFrame(metaData, null, true); CountDownLatch latch = new CountDownLatch(1); - session.newStream(headersFrame, new Promise.Adapter<>(), new Stream.Listener.Adapter() + session.newStream(headersFrame, new Promise.Adapter<>(), new Stream.Listener() { @Override public void onHeaders(Stream stream, HeadersFrame frame) @@ -71,14 +69,17 @@ public class JDK9HTTP2ClientTest System.err.println(frame); if (frame.isEndStream()) latch.countDown(); + stream.demand(); } @Override - public void onData(Stream stream, DataFrame frame, Callback callback) + public void onDataAvailable(Stream stream) { - System.err.println(frame); - callback.succeeded(); - if (frame.isEndStream()) + Stream.Data data = stream.readData(); + System.err.println(data); + data.release(); + stream.demand(); + if (data.frame().isEndStream()) latch.countDown(); } }); diff --git a/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/HttpReceiver.java b/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/HttpReceiver.java index 8315e6b48cd..fd6532ab648 100644 --- a/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/HttpReceiver.java +++ b/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/HttpReceiver.java @@ -95,7 +95,7 @@ public abstract class HttpReceiver throw new IllegalArgumentException("Invalid demand " + n); boolean resume = false; - try (AutoLock l = lock.lock()) + try (AutoLock ignored = lock.lock()) { demand = MathUtils.cappedAdd(demand, n); if (stalled) @@ -104,7 +104,7 @@ public abstract class HttpReceiver resume = true; } if (LOG.isDebugEnabled()) - LOG.debug("Response demand={}/{}, resume={}", n, demand, resume); + LOG.debug("Response demand={}/{}, resume={} on {}", n, demand, resume, this); } if (resume) @@ -123,7 +123,7 @@ public abstract class HttpReceiver private long demand(LongUnaryOperator operator) { - try (AutoLock l = lock.lock()) + try (AutoLock ignored = lock.lock()) { return demand = operator.applyAsLong(demand); } @@ -131,7 +131,7 @@ public abstract class HttpReceiver protected boolean hasDemandOrStall() { - try (AutoLock l = lock.lock()) + try (AutoLock ignored = lock.lock()) { stalled = demand <= 0; return !stalled; @@ -229,17 +229,11 @@ public abstract class HttpReceiver { switch (fieldHeader) { - case SET_COOKIE: - case SET_COOKIE2: + case SET_COOKIE, SET_COOKIE2 -> { URI uri = exchange.getRequest().getURI(); if (uri != null) storeCookie(uri, field); - break; - } - default: - { - break; } } } @@ -602,10 +596,11 @@ public abstract class HttpReceiver @Override public String toString() { - return String.format("%s@%x(rsp=%s,failure=%s)", + return String.format("%s@%x(rsp=%s,demand=%d,failure=%s)", getClass().getSimpleName(), hashCode(), responseState, + demand(), failure); } diff --git a/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/util/InputStreamRequestContent.java b/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/util/InputStreamRequestContent.java index 17b721a27f6..860dad43d8b 100644 --- a/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/util/InputStreamRequestContent.java +++ b/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/util/InputStreamRequestContent.java @@ -16,7 +16,7 @@ package org.eclipse.jetty.client.util; import java.io.InputStream; import org.eclipse.jetty.client.api.Request; -import org.eclipse.jetty.io.ByteBufferPool; +import org.eclipse.jetty.io.RetainableByteBufferPool; import org.eclipse.jetty.io.content.InputStreamContentSource; /** @@ -33,7 +33,7 @@ public class InputStreamRequestContent extends InputStreamContentSource implemen public InputStreamRequestContent(InputStream stream) { - this("application/octet-stream", stream, null); + this(stream, 4096); } public InputStreamRequestContent(InputStream stream, int bufferSize) @@ -43,7 +43,7 @@ public class InputStreamRequestContent extends InputStreamContentSource implemen public InputStreamRequestContent(String contentType, InputStream stream, int bufferSize) { - this(contentType, stream, null); + this(contentType, stream); setBufferSize(bufferSize); } @@ -52,7 +52,7 @@ public class InputStreamRequestContent extends InputStreamContentSource implemen this(contentType, stream, null); } - public InputStreamRequestContent(String contentType, InputStream stream, ByteBufferPool bufferPool) + public InputStreamRequestContent(String contentType, InputStream stream, RetainableByteBufferPool bufferPool) { super(stream, bufferPool); this.contentType = contentType; diff --git a/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/util/PathRequestContent.java b/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/util/PathRequestContent.java index ae9cb33ede1..35d2dbcdcc4 100644 --- a/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/util/PathRequestContent.java +++ b/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/util/PathRequestContent.java @@ -17,16 +17,15 @@ import java.io.IOException; import java.nio.file.Path; import org.eclipse.jetty.client.api.Request; -import org.eclipse.jetty.io.ByteBufferPool; +import org.eclipse.jetty.io.RetainableByteBufferPool; import org.eclipse.jetty.io.content.PathContentSource; /** *

A {@link Request.Content} for files using JDK 7's {@code java.nio.file} APIs.

*

It is possible to specify a buffer size used to read content from the stream, - * by default 4096 bytes, and whether the buffer should be direct or not.

- *

If a {@link ByteBufferPool} is provided via {@link #setByteBufferPool(ByteBufferPool)}, - * the buffer will be allocated from that pool, otherwise one buffer will be - * allocated and used to read the file.

+ * by default 4096 bytes, whether the buffer should be direct or not, and a + * {@link RetainableByteBufferPool} from which {@code ByteBuffer}s will be acquired + * to read from the {@code Path}.

*/ public class PathRequestContent extends PathContentSource implements Request.Content { @@ -49,11 +48,16 @@ public class PathRequestContent extends PathContentSource implements Request.Con public PathRequestContent(String contentType, Path filePath, int bufferSize) throws IOException { - super(filePath); - this.contentType = contentType; + this(contentType, filePath, null); setBufferSize(bufferSize); } + public PathRequestContent(String contentType, Path filePath, RetainableByteBufferPool bufferPool) throws IOException + { + super(filePath, bufferPool); + this.contentType = contentType; + } + @Override public String getContentType() { diff --git a/jetty-core/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/internal/ServerFCGIConnection.java b/jetty-core/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/internal/ServerFCGIConnection.java index d1cf32f45bd..f1ca41530f0 100644 --- a/jetty-core/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/internal/ServerFCGIConnection.java +++ b/jetty-core/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/internal/ServerFCGIConnection.java @@ -372,7 +372,7 @@ public class ServerFCGIConnection extends AbstractConnection implements Connecti if (stream != null) { networkBuffer.retain(); - stream.onContent(Content.Chunk.from(buffer, false, networkBuffer::release)); + stream.onContent(Content.Chunk.from(buffer, false, networkBuffer)); // Signal that the content is processed asynchronously, to ensure backpressure. return true; } diff --git a/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/Trailers.java b/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/Trailers.java index 9f4d1e61a15..a8acb78d956 100644 --- a/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/Trailers.java +++ b/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/Trailers.java @@ -39,13 +39,20 @@ public class Trailers implements Content.Chunk return true; } - @Override - public void release() - { - } - public HttpFields getTrailers() { return trailers; } + + @Override + public void retain() + { + throw new UnsupportedOperationException(); + } + + @Override + public boolean release() + { + return true; + } } diff --git a/jetty-core/jetty-http2/http2-client/src/main/java/org/eclipse/jetty/http2/client/HTTP2Client.java b/jetty-core/jetty-http2/http2-client/src/main/java/org/eclipse/jetty/http2/client/HTTP2Client.java index 93cdebf1286..2eb6d438ff0 100644 --- a/jetty-core/jetty-http2/http2-client/src/main/java/org/eclipse/jetty/http2/client/HTTP2Client.java +++ b/jetty-core/jetty-http2/http2-client/src/main/java/org/eclipse/jetty/http2/client/HTTP2Client.java @@ -54,13 +54,13 @@ import org.eclipse.jetty.util.thread.Scheduler; * int port = 443; * * FuturePromise<Session> sessionPromise = new FuturePromise<>(); - * client.connect(sslContextFactory, new InetSocketAddress(host, port), new ServerSessionListener.Adapter(), sessionPromise); + * client.connect(sslContextFactory, new InetSocketAddress(host, port), new ServerSessionListener() {}, sessionPromise); * * // Obtain the client Session object. * Session session = sessionPromise.get(5, TimeUnit.SECONDS); * * // Prepare the HTTP request headers. - * HttpFields requestFields = new HttpFields(); + * HttpFields requestFields = HttpFields.build(); * requestFields.put("User-Agent", client.getClass().getName() + "/" + Jetty.VERSION); * // Prepare the HTTP request object. * MetaData.Request request = new MetaData.Request("PUT", HttpURI.from("https://" + host + ":" + port + "/"), HttpVersion.HTTP_2, requestFields); @@ -68,7 +68,7 @@ import org.eclipse.jetty.util.thread.Scheduler; * HeadersFrame headersFrame = new HeadersFrame(request, null, false); * * // Prepare the listener to receive the HTTP response frames. - * Stream.Listener responseListener = new new Stream.Listener.Adapter() + * Stream.Listener responseListener = new new Stream.Listener() * { * @Override * public void onHeaders(Stream stream, HeadersFrame frame) diff --git a/jetty-core/jetty-http2/http2-client/src/main/java/org/eclipse/jetty/http2/client/HTTP2ClientConnectionFactory.java b/jetty-core/jetty-http2/http2-client/src/main/java/org/eclipse/jetty/http2/client/HTTP2ClientConnectionFactory.java index fb50fabed5b..3d665b1575f 100644 --- a/jetty-core/jetty-http2/http2-client/src/main/java/org/eclipse/jetty/http2/client/HTTP2ClientConnectionFactory.java +++ b/jetty-core/jetty-http2/http2-client/src/main/java/org/eclipse/jetty/http2/client/HTTP2ClientConnectionFactory.java @@ -19,13 +19,13 @@ import java.util.Map; import java.util.concurrent.Executor; import org.eclipse.jetty.http2.FlowControlStrategy; -import org.eclipse.jetty.http2.ISession; import org.eclipse.jetty.http2.api.Session; import org.eclipse.jetty.http2.client.internal.HTTP2ClientSession; import org.eclipse.jetty.http2.frames.PrefaceFrame; import org.eclipse.jetty.http2.frames.SettingsFrame; import org.eclipse.jetty.http2.frames.WindowUpdateFrame; import org.eclipse.jetty.http2.internal.HTTP2Connection; +import org.eclipse.jetty.http2.internal.HTTP2Session; import org.eclipse.jetty.http2.internal.generator.Generator; import org.eclipse.jetty.http2.internal.parser.Parser; import org.eclipse.jetty.io.ByteBufferPool; @@ -35,7 +35,6 @@ import org.eclipse.jetty.io.EndPoint; import org.eclipse.jetty.io.RetainableByteBufferPool; import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.Promise; -import org.eclipse.jetty.util.component.LifeCycle; import org.eclipse.jetty.util.thread.Scheduler; public class HTTP2ClientConnectionFactory implements ClientConnectionFactory @@ -85,7 +84,7 @@ public class HTTP2ClientConnectionFactory implements ClientConnectionFactory private final Promise promise; private final Session.Listener listener; - private HTTP2ClientConnection(HTTP2Client client, RetainableByteBufferPool retainableByteBufferPool, Executor executor, EndPoint endpoint, Parser parser, ISession session, int bufferSize, Promise promise, Session.Listener listener) + private HTTP2ClientConnection(HTTP2Client client, RetainableByteBufferPool retainableByteBufferPool, Executor executor, EndPoint endpoint, Parser parser, HTTP2Session session, int bufferSize, Promise promise, Session.Listener listener) { super(retainableByteBufferPool, executor, endpoint, parser, session, bufferSize); this.client = client; @@ -109,7 +108,7 @@ public class HTTP2ClientConnectionFactory implements ClientConnectionFactory PrefaceFrame prefaceFrame = new PrefaceFrame(); SettingsFrame settingsFrame = new SettingsFrame(settings, false); - ISession session = getSession(); + HTTP2Session session = getSession(); int windowDelta = client.getInitialSessionRecvWindow() - FlowControlStrategy.DEFAULT_WINDOW_SIZE; session.updateRecvWindow(windowDelta); @@ -150,7 +149,7 @@ public class HTTP2ClientConnectionFactory implements ClientConnectionFactory public void onOpened(Connection connection) { HTTP2ClientConnection http2Connection = (HTTP2ClientConnection)connection; - http2Connection.client.addManaged((LifeCycle)http2Connection.getSession()); + http2Connection.client.addManaged(http2Connection.getSession()); } @Override diff --git a/jetty-core/jetty-http2/http2-client/src/main/java/org/eclipse/jetty/http2/client/internal/HTTP2ClientSession.java b/jetty-core/jetty-http2/http2-client/src/main/java/org/eclipse/jetty/http2/client/internal/HTTP2ClientSession.java index 51da678a07f..525dd58cce2 100644 --- a/jetty-core/jetty-http2/http2-client/src/main/java/org/eclipse/jetty/http2/client/internal/HTTP2ClientSession.java +++ b/jetty-core/jetty-http2/http2-client/src/main/java/org/eclipse/jetty/http2/client/internal/HTTP2ClientSession.java @@ -16,13 +16,13 @@ package org.eclipse.jetty.http2.client.internal; import org.eclipse.jetty.http.MetaData; import org.eclipse.jetty.http2.CloseState; import org.eclipse.jetty.http2.FlowControlStrategy; -import org.eclipse.jetty.http2.IStream; import org.eclipse.jetty.http2.api.Session; import org.eclipse.jetty.http2.api.Stream; import org.eclipse.jetty.http2.frames.HeadersFrame; import org.eclipse.jetty.http2.frames.PushPromiseFrame; import org.eclipse.jetty.http2.internal.ErrorCode; import org.eclipse.jetty.http2.internal.HTTP2Session; +import org.eclipse.jetty.http2.internal.HTTP2Stream; import org.eclipse.jetty.http2.internal.generator.Generator; import org.eclipse.jetty.io.EndPoint; import org.eclipse.jetty.util.Callback; @@ -47,7 +47,7 @@ public class HTTP2ClientSession extends HTTP2Session // HEADERS can be received for normal and pushed responses. int streamId = frame.getStreamId(); - IStream stream = getStream(streamId); + HTTP2Stream stream = getStream(streamId); if (stream != null) { MetaData metaData = frame.getMetaData(); @@ -94,7 +94,7 @@ public class HTTP2ClientSession extends HTTP2Session int streamId = frame.getStreamId(); int pushStreamId = frame.getPromisedStreamId(); - IStream stream = getStream(streamId); + HTTP2Stream stream = getStream(streamId); if (stream == null) { if (LOG.isDebugEnabled()) @@ -102,7 +102,7 @@ public class HTTP2ClientSession extends HTTP2Session } else { - IStream pushStream = createRemoteStream(pushStreamId, frame.getMetaData()); + HTTP2Stream pushStream = createRemoteStream(pushStreamId, frame.getMetaData()); if (pushStream != null) { pushStream.process(frame, Callback.NOOP); @@ -112,7 +112,7 @@ public class HTTP2ClientSession extends HTTP2Session } } - private Stream.Listener notifyPush(IStream stream, IStream pushStream, PushPromiseFrame frame) + private Stream.Listener notifyPush(HTTP2Stream stream, HTTP2Stream pushStream, PushPromiseFrame frame) { Stream.Listener listener = stream.getListener(); if (listener == null) diff --git a/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/AbstractFlowControlStrategy.java b/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/AbstractFlowControlStrategy.java index ccfbe1a3be3..2230fe7415b 100644 --- a/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/AbstractFlowControlStrategy.java +++ b/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/AbstractFlowControlStrategy.java @@ -14,13 +14,18 @@ package org.eclipse.jetty.http2; import java.io.IOException; +import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; +import org.eclipse.jetty.http2.api.Session; import org.eclipse.jetty.http2.api.Stream; import org.eclipse.jetty.http2.frames.WindowUpdateFrame; +import org.eclipse.jetty.http2.internal.HTTP2Session; +import org.eclipse.jetty.http2.internal.HTTP2Stream; +import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.annotation.ManagedAttribute; import org.eclipse.jetty.util.annotation.ManagedObject; import org.eclipse.jetty.util.annotation.ManagedOperation; @@ -35,7 +40,7 @@ public abstract class AbstractFlowControlStrategy implements FlowControlStrategy private final AtomicLong sessionStall = new AtomicLong(); private final AtomicLong sessionStallTime = new AtomicLong(); - private final Map streamsStalls = new ConcurrentHashMap<>(); + private final Map streamsStalls = new ConcurrentHashMap<>(); private final AtomicLong streamsStallTime = new AtomicLong(); private int initialStreamSendWindow; private int initialStreamRecvWindow; @@ -59,20 +64,20 @@ public abstract class AbstractFlowControlStrategy implements FlowControlStrategy } @Override - public void onStreamCreated(IStream stream) + public void onStreamCreated(Stream stream) { - stream.updateSendWindow(initialStreamSendWindow); - stream.updateRecvWindow(initialStreamRecvWindow); + updateSendWindow(stream, getInitialStreamSendWindow()); + updateRecvWindow(stream, getInitialStreamRecvWindow()); } @Override - public void onStreamDestroyed(IStream stream) + public void onStreamDestroyed(Stream stream) { streamsStalls.remove(stream); } @Override - public void updateInitialStreamWindow(ISession session, int initialStreamWindow, boolean local) + public void updateInitialStreamWindow(Session session, int initialStreamWindow, boolean local) { int previousInitialStreamWindow; if (local) @@ -94,19 +99,19 @@ public abstract class AbstractFlowControlStrategy implements FlowControlStrategy { if (local) { - ((IStream)stream).updateRecvWindow(delta); + updateRecvWindow(stream, delta); if (LOG.isDebugEnabled()) LOG.debug("Updated initial stream recv window {} -> {} for {}", previousInitialStreamWindow, initialStreamWindow, stream); } else { - session.onWindowUpdate((IStream)stream, new WindowUpdateFrame(stream.getId(), delta)); + updateWindow(session, stream, new WindowUpdateFrame(stream.getId(), delta)); } } } @Override - public void onWindowUpdate(ISession session, IStream stream, WindowUpdateFrame frame) + public void onWindowUpdate(Session session, Stream stream, WindowUpdateFrame frame) { int delta = frame.getWindowDelta(); if (frame.getStreamId() > 0) @@ -114,7 +119,7 @@ public abstract class AbstractFlowControlStrategy implements FlowControlStrategy // The stream may have been removed concurrently. if (stream != null) { - int oldSize = stream.updateSendWindow(delta); + int oldSize = updateSendWindow(stream, delta); if (LOG.isDebugEnabled()) LOG.debug("Updated stream send window {} -> {} for {}", oldSize, oldSize + delta, stream); if (oldSize <= 0) @@ -123,7 +128,7 @@ public abstract class AbstractFlowControlStrategy implements FlowControlStrategy } else { - int oldSize = session.updateSendWindow(delta); + int oldSize = updateSendWindow(session, delta); if (LOG.isDebugEnabled()) LOG.debug("Updated session send window {} -> {} for {}", oldSize, oldSize + delta, session); if (oldSize <= 0) @@ -132,40 +137,40 @@ public abstract class AbstractFlowControlStrategy implements FlowControlStrategy } @Override - public void onDataReceived(ISession session, IStream stream, int length) + public void onDataReceived(Session session, Stream stream, int length) { - int oldSize = session.updateRecvWindow(-length); + int oldSize = updateRecvWindow(session, -length); if (LOG.isDebugEnabled()) LOG.debug("Data received, {} bytes, updated session recv window {} -> {} for {}", length, oldSize, oldSize - length, session); if (stream != null) { - oldSize = stream.updateRecvWindow(-length); + oldSize = updateRecvWindow(stream, -length); if (LOG.isDebugEnabled()) LOG.debug("Data received, {} bytes, updated stream recv window {} -> {} for {}", length, oldSize, oldSize - length, stream); } } @Override - public void windowUpdate(ISession session, IStream stream, WindowUpdateFrame frame) + public void windowUpdate(Session session, Stream stream, WindowUpdateFrame frame) { } @Override - public void onDataSending(IStream stream, int length) + public void onDataSending(Stream stream, int length) { if (length == 0) return; - ISession session = stream.getSession(); - int oldSessionWindow = session.updateSendWindow(-length); + Session session = stream.getSession(); + int oldSessionWindow = updateSendWindow(session, -length); int newSessionWindow = oldSessionWindow - length; if (LOG.isDebugEnabled()) LOG.debug("Sending, session send window {} -> {} for {}", oldSessionWindow, newSessionWindow, session); if (newSessionWindow <= 0) onSessionStalled(session); - int oldStreamWindow = stream.updateSendWindow(-length); + int oldStreamWindow = updateSendWindow(stream, -length); int newStreamWindow = oldStreamWindow - length; if (LOG.isDebugEnabled()) LOG.debug("Sending, stream send window {} -> {} for {}", oldStreamWindow, newStreamWindow, stream); @@ -174,25 +179,55 @@ public abstract class AbstractFlowControlStrategy implements FlowControlStrategy } @Override - public void onDataSent(IStream stream, int length) + public void onDataSent(Stream stream, int length) { } - protected void onSessionStalled(ISession session) + protected void updateWindow(Session session, Stream stream, WindowUpdateFrame frame) + { + ((HTTP2Session)session).onWindowUpdate((HTTP2Stream)stream, frame); + } + + protected int updateRecvWindow(Session session, int value) + { + return ((HTTP2Session)session).updateRecvWindow(value); + } + + protected int updateSendWindow(Session session, int value) + { + return ((HTTP2Session)session).updateSendWindow(value); + } + + protected int updateRecvWindow(Stream stream, int value) + { + return ((HTTP2Stream)stream).updateRecvWindow(value); + } + + protected int updateSendWindow(Stream stream, int value) + { + return ((HTTP2Stream)stream).updateSendWindow(value); + } + + protected void sendWindowUpdate(Session session, Stream stream, List frames) + { + ((HTTP2Session)session).frames((HTTP2Stream)stream, frames, Callback.NOOP); + } + + protected void onSessionStalled(Session session) { sessionStall.set(System.nanoTime()); if (LOG.isDebugEnabled()) LOG.debug("Session stalled {}", session); } - protected void onStreamStalled(IStream stream) + protected void onStreamStalled(Stream stream) { streamsStalls.put(stream, System.nanoTime()); if (LOG.isDebugEnabled()) LOG.debug("Stream stalled {}", stream); } - protected void onSessionUnstalled(ISession session) + protected void onSessionUnstalled(Session session) { long stallTime = System.nanoTime() - sessionStall.getAndSet(0); sessionStallTime.addAndGet(stallTime); @@ -200,7 +235,7 @@ public abstract class AbstractFlowControlStrategy implements FlowControlStrategy LOG.debug("Session unstalled after {} ms {}", TimeUnit.NANOSECONDS.toMillis(stallTime), session); } - protected void onStreamUnstalled(IStream stream) + protected void onStreamUnstalled(Stream stream) { Long time = streamsStalls.remove(stream); if (time != null) diff --git a/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/BufferingFlowControlStrategy.java b/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/BufferingFlowControlStrategy.java index 39d105af54e..1c224bf3703 100644 --- a/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/BufferingFlowControlStrategy.java +++ b/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/BufferingFlowControlStrategy.java @@ -18,9 +18,10 @@ import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicInteger; +import org.eclipse.jetty.http2.api.Session; +import org.eclipse.jetty.http2.api.Stream; import org.eclipse.jetty.http2.frames.WindowUpdateFrame; import org.eclipse.jetty.util.Atomics; -import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.annotation.ManagedAttribute; import org.eclipse.jetty.util.annotation.ManagedObject; import org.slf4j.Logger; @@ -66,7 +67,7 @@ public class BufferingFlowControlStrategy extends AbstractFlowControlStrategy private final AtomicInteger maxSessionRecvWindow = new AtomicInteger(DEFAULT_WINDOW_SIZE); private final AtomicInteger sessionLevel = new AtomicInteger(); - private final Map streamLevels = new ConcurrentHashMap<>(); + private final Map streamLevels = new ConcurrentHashMap<>(); private float bufferRatio; public BufferingFlowControlStrategy(float bufferRatio) @@ -92,21 +93,21 @@ public class BufferingFlowControlStrategy extends AbstractFlowControlStrategy } @Override - public void onStreamCreated(IStream stream) + public void onStreamCreated(Stream stream) { super.onStreamCreated(stream); streamLevels.put(stream, new AtomicInteger()); } @Override - public void onStreamDestroyed(IStream stream) + public void onStreamDestroyed(Stream stream) { streamLevels.remove(stream); super.onStreamDestroyed(stream); } @Override - public void onDataConsumed(ISession session, IStream stream, int length) + public void onDataConsumed(Session session, Stream stream, int length) { if (length <= 0) return; @@ -119,10 +120,10 @@ public class BufferingFlowControlStrategy extends AbstractFlowControlStrategy { if (sessionLevel.compareAndSet(level, 0)) { - session.updateRecvWindow(level); + updateRecvWindow(session, level); if (LOG.isDebugEnabled()) LOG.debug("Data consumed, {} bytes, updated session recv window by {}/{} for {}", length, level, maxLevel, session); - sendWindowUpdate(null, session, new WindowUpdateFrame(0, level)); + sendWindowUpdate(session, null, List.of(new WindowUpdateFrame(0, level))); } else { @@ -153,10 +154,10 @@ public class BufferingFlowControlStrategy extends AbstractFlowControlStrategy if (level > maxLevel) { level = streamLevel.getAndSet(0); - stream.updateRecvWindow(level); + updateRecvWindow(stream, level); if (LOG.isDebugEnabled()) LOG.debug("Data consumed, {} bytes, updated stream recv window by {}/{} for {}", length, level, maxLevel, stream); - sendWindowUpdate(stream, session, new WindowUpdateFrame(stream.getId(), level)); + sendWindowUpdate(session, stream, List.of(new WindowUpdateFrame(stream.getId(), level))); } else { @@ -168,13 +169,8 @@ public class BufferingFlowControlStrategy extends AbstractFlowControlStrategy } } - protected void sendWindowUpdate(IStream stream, ISession session, WindowUpdateFrame frame) - { - session.frames(stream, List.of(frame), Callback.NOOP); - } - @Override - public void windowUpdate(ISession session, IStream stream, WindowUpdateFrame frame) + public void windowUpdate(Session session, Stream stream, WindowUpdateFrame frame) { super.windowUpdate(session, stream, frame); @@ -207,7 +203,7 @@ public class BufferingFlowControlStrategy extends AbstractFlowControlStrategy if (frame.getStreamId() == 0) { - int sessionWindow = session.updateRecvWindow(0); + int sessionWindow = updateRecvWindow(session, 0); Atomics.updateMax(maxSessionRecvWindow, sessionWindow); } } diff --git a/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/FlowControlStrategy.java b/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/FlowControlStrategy.java index df29a933bef..4502e93bced 100644 --- a/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/FlowControlStrategy.java +++ b/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/FlowControlStrategy.java @@ -13,29 +13,31 @@ package org.eclipse.jetty.http2; +import org.eclipse.jetty.http2.api.Session; +import org.eclipse.jetty.http2.api.Stream; import org.eclipse.jetty.http2.frames.WindowUpdateFrame; public interface FlowControlStrategy { public static int DEFAULT_WINDOW_SIZE = 65535; - public void onStreamCreated(IStream stream); + public void onStreamCreated(Stream stream); - public void onStreamDestroyed(IStream stream); + public void onStreamDestroyed(Stream stream); - public void updateInitialStreamWindow(ISession session, int initialStreamWindow, boolean local); + public void updateInitialStreamWindow(Session session, int initialStreamWindow, boolean local); - public void onWindowUpdate(ISession session, IStream stream, WindowUpdateFrame frame); + public void onWindowUpdate(Session session, Stream stream, WindowUpdateFrame frame); - public void onDataReceived(ISession session, IStream stream, int length); + public void onDataReceived(Session session, Stream stream, int length); - public void onDataConsumed(ISession session, IStream stream, int length); + public void onDataConsumed(Session session, Stream stream, int length); - public void windowUpdate(ISession session, IStream stream, WindowUpdateFrame frame); + public void windowUpdate(Session session, Stream stream, WindowUpdateFrame frame); - public void onDataSending(IStream stream, int length); + public void onDataSending(Stream stream, int length); - public void onDataSent(IStream stream, int length); + public void onDataSent(Stream stream, int length); public interface Factory { diff --git a/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/ISession.java b/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/ISession.java deleted file mode 100644 index 8c957c1016e..00000000000 --- a/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/ISession.java +++ /dev/null @@ -1,169 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995-2022 Mort Bay Consulting Pty Ltd and others. -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License v. 2.0 which is available at -// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.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; - -import java.io.IOException; -import java.util.List; -import java.util.concurrent.CompletableFuture; - -import org.eclipse.jetty.http2.api.Session; -import org.eclipse.jetty.http2.api.Stream; -import org.eclipse.jetty.http2.frames.DataFrame; -import org.eclipse.jetty.http2.frames.Frame; -import org.eclipse.jetty.http2.frames.PushPromiseFrame; -import org.eclipse.jetty.http2.frames.WindowUpdateFrame; -import org.eclipse.jetty.util.Callback; -import org.eclipse.jetty.util.Promise; - -/** - *

The SPI interface for implementing an HTTP/2 session.

- *

This class extends {@link Session} by adding the methods required to - * implement the HTTP/2 session functionalities.

- */ -public interface ISession extends Session -{ - @Override - public IStream getStream(int streamId); - - /** - *

Removes the given {@code stream}.

- * - * @param stream the stream to remove - * @return whether the stream was removed - */ - public boolean removeStream(IStream stream); - - /** - *

Sends the given list of frames to create a new {@link Stream}.

- * - * @param frames the list of frames to send - * @param promise the promise that gets notified of the stream creation - * @param listener the listener that gets notified of stream events - */ - public void newStream(IStream.FrameList frames, Promise promise, Stream.Listener listener); - - /** - *

Enqueues the given frames to be written to the connection.

- * @param stream the stream the frames belong to - * @param frames the frames to enqueue - * @param callback the callback that gets notified when the frames have been sent - */ - public void frames(IStream stream, List frames, Callback callback); - - /** - *

Enqueues the given PUSH_PROMISE frame to be written to the connection.

- *

Differently from {@link #frames(IStream, List, Callback)}, this method - * generates atomically the stream id for the pushed stream.

- * - * @param stream the stream associated to the pushed stream - * @param promise the promise that gets notified of the pushed stream creation - * @param frame the PUSH_PROMISE frame to enqueue - * @param listener the listener that gets notified of pushed stream events - */ - public void push(IStream stream, Promise promise, PushPromiseFrame frame, Stream.Listener listener); - - /** - *

Enqueues the given DATA frame to be written to the connection.

- * - * @param stream the stream the data frame belongs to - * @param callback the callback that gets notified when the frame has been sent - * @param frame the DATA frame to send - */ - public void data(IStream stream, Callback callback, DataFrame frame); - - /** - *

Updates the session send window by the given {@code delta}.

- * - * @param delta the delta value (positive or negative) to add to the session send window - * @return the previous value of the session send window - */ - public int updateSendWindow(int delta); - - /** - *

Updates the session receive window by the given {@code delta}.

- * - * @param delta the delta value (positive or negative) to add to the session receive window - * @return the previous value of the session receive window - */ - public int updateRecvWindow(int delta); - - /** - *

Callback method invoked when a WINDOW_UPDATE frame has been received.

- * - * @param stream the stream the window update belongs to, or null if the window update belongs to the session - * @param frame the WINDOW_UPDATE frame received - */ - public void onWindowUpdate(IStream stream, WindowUpdateFrame frame); - - /** - * @return whether the push functionality is enabled - */ - public boolean isPushEnabled(); - - /** - *

Callback invoked when the connection reads -1.

- * - * @see #onIdleTimeout() - * @see #close(int, String, Callback) - */ - public void onShutdown(); - - /** - *

Callback invoked when the idle timeout expires.

- * - * @return {@code true} if the session has expired - * @see #onShutdown() - * @see #close(int, String, Callback) - */ - public boolean onIdleTimeout(); - - /** - *

Callback method invoked during an HTTP/1.1 to HTTP/2 upgrade requests - * to process the given synthetic frame.

- * - * @param frame the synthetic frame to process - */ - public void onFrame(Frame frame); - - /** - *

Callback method invoked when bytes are flushed to the network.

- * - * @param bytes the number of bytes flushed to the network - * @throws IOException if the flush should fail - */ - public void onFlushed(long bytes) throws IOException; - - /** - * @return the number of bytes written by this session - */ - public long getBytesWritten(); - - /** - *

Callback method invoked when a DATA frame is received.

- * - * @param frame the DATA frame received - * @param callback the callback to notify when the frame has been processed - */ - public void onData(DataFrame frame, Callback callback); - - /** - *

Gracefully closes the session, returning a {@code CompletableFuture} that - * is completed when all the streams currently being processed are completed.

- *

Implementation is idempotent, i.e. calling this method a second time - * or concurrently results in a no-operation.

- * - * @return a {@code CompletableFuture} that is completed when all the streams are completed - */ - public CompletableFuture shutdown(); -} diff --git a/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/IStream.java b/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/IStream.java deleted file mode 100644 index f28eb5f533e..00000000000 --- a/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/IStream.java +++ /dev/null @@ -1,228 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995-2022 Mort Bay Consulting Pty Ltd and others. -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License v. 2.0 which is available at -// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.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; - -import java.io.Closeable; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Objects; - -import org.eclipse.jetty.http2.api.Stream; -import org.eclipse.jetty.http2.frames.DataFrame; -import org.eclipse.jetty.http2.frames.Frame; -import org.eclipse.jetty.http2.frames.HeadersFrame; -import org.eclipse.jetty.http2.frames.StreamFrame; -import org.eclipse.jetty.util.Attachable; -import org.eclipse.jetty.util.Callback; - -/** - *

The SPI interface for implementing an HTTP/2 stream.

- *

This class extends {@link Stream} by adding the methods required to - * implement the HTTP/2 stream functionalities.

- */ -public interface IStream extends Stream, Attachable, Closeable -{ - /** - * @return whether this stream is local or remote - */ - public boolean isLocal(); - - @Override - public ISession getSession(); - - /** - * @return the {@link org.eclipse.jetty.http2.api.Stream.Listener} associated with this stream - * @see #setListener(Stream.Listener) - */ - public Listener getListener(); - - // TODO: move this to Stream. - public Data readData(); - - /** - * @param listener the {@link org.eclipse.jetty.http2.api.Stream.Listener} associated with this stream - * @see #getListener() - */ - public void setListener(Listener listener); - - /** - *

Sends the given list of frames.

- *

Typically used to send HTTP headers along with content and possibly trailers.

- * - * @param frameList the list of frames to send - * @param callback the callback that gets notified when the frames have been sent - */ - void send(FrameList frameList, Callback callback); - - /** - *

Processes the given {@code frame}, belonging to this stream.

- * - * @param frame the frame to process - * @param callback the callback to complete when frame has been processed - */ - public void process(Frame frame, Callback callback); - - /** - *

Updates the close state of this stream.

- * - * @param update whether to update the close state - * @param event the event that caused the close state update - * @return whether the stream has been fully closed by this invocation - */ - public boolean updateClose(boolean update, CloseState.Event event); - - /** - *

Forcibly closes this stream.

- */ - @Override - public void close(); - - /** - *

Updates the stream send window by the given {@code delta}.

- * - * @param delta the delta value (positive or negative) to add to the stream send window - * @return the previous value of the stream send window - */ - public int updateSendWindow(int delta); - - /** - *

Updates the stream receive window by the given {@code delta}.

- * - * @param delta the delta value (positive or negative) to add to the stream receive window - * @return the previous value of the stream receive window - */ - public int updateRecvWindow(int delta); - - /** - *

Marks this stream as not idle so that the - * {@link #getIdleTimeout() idle timeout} is postponed.

- */ - public void notIdle(); - - /** - * @return whether the stream is closed remotely. - * @see #isClosed() - */ - 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() - * @see Listener#onFailure(Stream, int, String, Throwable, Callback) - */ - boolean isResetOrFailed(); - - /** - * Marks this stream as committed. - * - * @see #isCommitted() - */ - void commit(); - - /** - * @return whether bytes for this stream have been sent to the remote peer. - * @see #commit() - */ - boolean isCommitted(); - - /** - *

An ordered list of frames belonging to the same stream.

- */ - public static class FrameList - { - private final List frames; - - /** - *

Creates a frame list of just the given HEADERS frame.

- * - * @param headers the HEADERS frame - */ - public FrameList(HeadersFrame headers) - { - Objects.requireNonNull(headers); - this.frames = List.of(headers); - } - - /** - *

Creates a frame list of the given frames.

- * - * @param headers the HEADERS frame for the headers - * @param data the DATA frame for the content, or null if there is no content - * @param trailers the HEADERS frame for the trailers, or null if there are no trailers - */ - public FrameList(HeadersFrame headers, DataFrame data, HeadersFrame trailers) - { - Objects.requireNonNull(headers); - ArrayList frames = new ArrayList<>(3); - int streamId = headers.getStreamId(); - if (data != null && data.getStreamId() != streamId) - throw new IllegalArgumentException("Invalid stream ID for DATA frame " + data); - if (trailers != null && trailers.getStreamId() != streamId) - throw new IllegalArgumentException("Invalid stream ID for HEADERS frame " + trailers); - frames.add(headers); - if (data != null) - frames.add(data); - if (trailers != null) - frames.add(trailers); - this.frames = Collections.unmodifiableList(frames); - } - - /** - * @return the stream ID of the frames in this list - */ - public int getStreamId() - { - return frames.get(0).getStreamId(); - } - - /** - * @return a List of non-null frames - */ - public List getFrames() - { - return frames; - } - } - - public static final class Data - { - private final DataFrame frame; - private final Runnable complete; - - public Data(DataFrame frame, Runnable complete) - { - this.frame = frame; - this.complete = complete; - } - - public DataFrame frame() - { - return frame; - } - - public void complete() - { - complete.run(); - } - } -} diff --git a/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/SimpleFlowControlStrategy.java b/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/SimpleFlowControlStrategy.java index 576a14cc1ae..3d2b3cadc62 100644 --- a/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/SimpleFlowControlStrategy.java +++ b/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/SimpleFlowControlStrategy.java @@ -16,9 +16,9 @@ package org.eclipse.jetty.http2; import java.util.ArrayList; import java.util.List; -import org.eclipse.jetty.http2.frames.Frame; +import org.eclipse.jetty.http2.api.Session; +import org.eclipse.jetty.http2.api.Stream; import org.eclipse.jetty.http2.frames.WindowUpdateFrame; -import org.eclipse.jetty.util.Callback; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -37,7 +37,7 @@ public class SimpleFlowControlStrategy extends AbstractFlowControlStrategy } @Override - public void onDataConsumed(ISession session, IStream stream, int length) + public void onDataConsumed(Session session, Stream stream, int length) { if (length <= 0) return; @@ -46,10 +46,10 @@ public class SimpleFlowControlStrategy extends AbstractFlowControlStrategy // This method is called when a whole flow controlled frame has been consumed. // We send a WindowUpdate every time, even if the frame was very small. - List frames = new ArrayList<>(2); + List frames = new ArrayList<>(2); WindowUpdateFrame sessionFrame = new WindowUpdateFrame(0, length); frames.add(sessionFrame); - session.updateRecvWindow(length); + updateRecvWindow(session, length); if (LOG.isDebugEnabled()) LOG.debug("Data consumed, increased session recv window by {} for {}", length, session); @@ -64,12 +64,12 @@ public class SimpleFlowControlStrategy extends AbstractFlowControlStrategy { WindowUpdateFrame streamFrame = new WindowUpdateFrame(stream.getId(), length); frames.add(streamFrame); - stream.updateRecvWindow(length); + updateRecvWindow(stream, length); if (LOG.isDebugEnabled()) LOG.debug("Data consumed, increased stream recv window by {} for {}", length, stream); } } - session.frames(stream, frames, Callback.NOOP); + sendWindowUpdate(session, stream, frames); } } diff --git a/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/api/Session.java b/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/api/Session.java index d0ad3c218a2..ce3efde79df 100644 --- a/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/api/Session.java +++ b/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/api/Session.java @@ -19,7 +19,6 @@ import java.util.Collection; import java.util.Map; import java.util.concurrent.CompletableFuture; -import org.eclipse.jetty.http2.frames.DataFrame; import org.eclipse.jetty.http2.frames.GoAwayFrame; import org.eclipse.jetty.http2.frames.HeadersFrame; import org.eclipse.jetty.http2.frames.PingFrame; @@ -36,7 +35,7 @@ import org.eclipse.jetty.util.Promise; * Session session = ...; * HeadersFrame frame = ...; * Promise<Stream> promise = ... - * session.newStream(frame, promise, new Stream.Listener.Adapter() + * session.newStream(frame, promise, new Stream.Listener() * { * public void onHeaders(Stream stream, HeadersFrame frame) * { @@ -122,6 +121,11 @@ public interface Session */ public boolean isClosed(); + /** + * @return whether the push functionality is enabled + */ + public boolean isPushEnabled(); + /** * @return a snapshot of all the streams currently belonging to this session */ @@ -169,6 +173,16 @@ public interface Session return getRemoteAddress(); } + /** + *

Gracefully closes the session, returning a {@code CompletableFuture} that + * is completed when all the streams currently being processed are completed.

+ *

Implementation is idempotent, i.e. calling this method a second time + * or concurrently results in a no-operation.

+ * + * @return a {@code CompletableFuture} that is completed when all the streams are completed + */ + public CompletableFuture shutdown(); + /** *

A {@link Listener} is the passive counterpart of a {@link Session} and * receives events happening on an HTTP/2 connection.

@@ -191,7 +205,10 @@ public interface Session * @return a (possibly empty or null) map containing SETTINGS configuration * options to send. */ - public Map onPreface(Session session); + public default Map onPreface(Session session) + { + return null; + } /** *

Callback method invoked when a new stream is being created upon @@ -201,15 +218,19 @@ public interface Session * {@link Stream#headers(HeadersFrame, Callback)}.

*

Applications can detect whether request DATA frames will be arriving * by testing {@link HeadersFrame#isEndStream()}. If the application is - * interested in processing the DATA frames, it must return a + * interested in processing the DATA frames, it must demand for DATA + * frames using {@link Stream#demand()} and return a * {@link Stream.Listener} implementation that overrides - * {@link Stream.Listener#onData(Stream, DataFrame, Callback)}.

+ * {@link Stream.Listener#onDataAvailable(Stream)}.

* * @param stream the newly created stream * @param frame the HEADERS frame received * @return a {@link Stream.Listener} that will be notified of stream events */ - public Stream.Listener onNewStream(Stream stream, HeadersFrame frame); + public default Stream.Listener onNewStream(Stream stream, HeadersFrame frame) + { + return null; + } /** *

Callback method invoked when a SETTINGS frame has been received.

@@ -217,7 +238,9 @@ public interface Session * @param session the session * @param frame the SETTINGS frame received */ - public void onSettings(Session session, SettingsFrame frame); + public default void onSettings(Session session, SettingsFrame frame) + { + } /** *

Callback method invoked when a PING frame has been received.

@@ -225,16 +248,20 @@ public interface Session * @param session the session * @param frame the PING frame received */ - public void onPing(Session session, PingFrame frame); + public default void onPing(Session session, PingFrame frame) + { + } /** *

Callback method invoked when a RST_STREAM frame has been received for an unknown stream.

* * @param session the session * @param frame the RST_STREAM frame received - * @see Stream.Listener#onReset(Stream, ResetFrame) + * @see Stream.Listener#onReset(Stream, ResetFrame, Callback) */ - public void onReset(Session session, ResetFrame frame); + public default void onReset(Session session, ResetFrame frame) + { + } /** *

Callback method invoked when a GOAWAY frame has been received.

@@ -255,26 +282,19 @@ public interface Session */ public default void onClose(Session session, GoAwayFrame frame, Callback callback) { - try - { - onClose(session, frame); - callback.succeeded(); - } - catch (Throwable x) - { - callback.failed(x); - } + callback.succeeded(); } - public void onClose(Session session, GoAwayFrame frame); - /** *

Callback method invoked when the idle timeout expired.

* * @param session the session * @return whether the session should be closed */ - public boolean onIdleTimeout(Session session); + public default boolean onIdleTimeout(Session session) + { + return true; + } /** *

Callback method invoked when a failure has been detected for this session.

@@ -285,66 +305,7 @@ public interface Session */ public default void onFailure(Session session, Throwable failure, Callback callback) { - try - { - onFailure(session, failure); - callback.succeeded(); - } - catch (Throwable x) - { - callback.failed(x); - } - } - - public void onFailure(Session session, Throwable failure); - - /** - *

Empty implementation of {@link Stream.Listener}.

- */ - public static class Adapter implements Session.Listener - { - @Override - public Map onPreface(Session session) - { - return null; - } - - @Override - public Stream.Listener onNewStream(Stream stream, HeadersFrame frame) - { - return null; - } - - @Override - public void onSettings(Session session, SettingsFrame frame) - { - } - - @Override - public void onPing(Session session, PingFrame frame) - { - } - - @Override - public void onReset(Session session, ResetFrame frame) - { - } - - @Override - public void onClose(Session session, GoAwayFrame frame) - { - } - - @Override - public boolean onIdleTimeout(Session session) - { - return true; - } - - @Override - public void onFailure(Session session, Throwable failure) - { - } + callback.succeeded(); } } } diff --git a/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/api/Stream.java b/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/api/Stream.java index 23638134935..b9c21abae86 100644 --- a/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/api/Stream.java +++ b/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/api/Stream.java @@ -19,6 +19,8 @@ import org.eclipse.jetty.http2.frames.DataFrame; import org.eclipse.jetty.http2.frames.HeadersFrame; import org.eclipse.jetty.http2.frames.PushPromiseFrame; import org.eclipse.jetty.http2.frames.ResetFrame; +import org.eclipse.jetty.io.Retainable; +import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.Promise; @@ -42,6 +44,11 @@ public interface Stream */ public int getId(); + /** + * @return the {@link org.eclipse.jetty.http2.api.Stream.Listener} associated with this stream + */ + public Listener getListener(); + /** * @return the session this stream is associated to */ @@ -92,6 +99,34 @@ public interface Stream */ public void push(PushPromiseFrame frame, Promise promise, Listener listener); + /** + *

Reads DATA frames from this stream, wrapping them in retainable {@link Data} + * objects.

+ *

The returned {@link Stream.Data} object may be {@code null}, indicating + * that the end of the read side of the stream has not yet been reached, which + * may happen in these cases:

+ *
    + *
  • not all the bytes have been received so far, for example the remote + * peer did not send them yet, or they are in-flight
  • + *
  • all the bytes have been received, but there is a trailer HEADERS + * frame to be received to indicate the end of the read side of the + * stream
  • + *
+ *

When the returned {@link Stream.Data} object is not {@code null}, + * applications must call, either immediately or later (possibly + * asynchronously) {@link Stream.Data#release()} to notify the + * implementation that the bytes have been processed.

+ *

{@link Stream.Data} objects may be stored away for later, asynchronous, + * processing (for example, to process them only when all of them have been + * received).

+ * + * @return a {@link Stream.Data} object containing the DATA frame, + * or null if no DATA frame is available + * @see #demand() + * @see Listener#onDataAvailable(Stream) + */ + public Data readData(); + /** *

Sends the given DATA {@code frame}.

* @@ -144,11 +179,22 @@ public interface Stream */ public Object removeAttribute(String key); + /** + * @return whether this stream is local or remote + */ + public boolean isLocal(); + /** * @return whether this stream has been reset */ public boolean isReset(); + /** + * @return whether the stream is closed remotely. + * @see #isClosed() + */ + boolean isRemotelyClosed(); + /** * @return whether this stream is closed, both locally and remotely. */ @@ -168,12 +214,26 @@ public interface Stream public void setIdleTimeout(long idleTimeout); /** - *

Demands {@code n} more {@code DATA} frames for this stream.

+ *

Demands more {@code DATA} frames for this stream, causing + * {@link Listener#onDataAvailable(Stream)} to be invoked, possibly at a later time, + * when the stream has data to be read.

+ *

This method is idempotent: calling it when there already is an + * outstanding demand to invoke {@link Listener#onDataAvailable(Stream)} + * is a no-operation.

+ *

The thread invoking this method may invoke directly + * {@link Listener#onDataAvailable(Stream)}, unless another thread + * that must invoke {@link Listener#onDataAvailable(Stream)} + * notices the outstanding demand first.

+ *

When all bytes have been read (via {@link #readData()}), further + * invocations of this method are a no-operation.

+ *

It is always guaranteed that invoking this method from within + * {@code onDataAvailable(Stream)} will not cause a + * {@link StackOverflowError}.

* - * @param n the increment of the demand, must be greater than zero - * @see Listener#onDataDemanded(Stream, DataFrame, Callback) + * @see #readData() + * @see Listener#onDataAvailable(Stream) */ - public void demand(long n); + public void demand(); /** *

A {@link Stream.Listener} is the passive counterpart of a {@link Stream} and receives @@ -181,7 +241,7 @@ public interface Stream *

HTTP/2 data is flow controlled - this means that only a finite number of data events * are delivered, until the flow control window is exhausted.

*

Applications control the delivery of data events by requesting them via - * {@link Stream#demand(long)}; the first event is always delivered, while subsequent + * {@link Stream#demand()}; the first event is always delivered, while subsequent * events must be explicitly demanded.

*

Applications control the HTTP/2 flow control by completing the callback associated * with data events - this allows the implementation to recycle the data buffer and @@ -207,7 +267,10 @@ public interface Stream * @param stream the stream * @param frame the HEADERS frame received */ - public void onHeaders(Stream stream, HeadersFrame frame); + public default void onHeaders(Stream stream, HeadersFrame frame) + { + stream.demand(); + } /** *

Callback method invoked when a PUSH_PROMISE frame has been received.

@@ -216,45 +279,66 @@ public interface Stream * @param frame the PUSH_PROMISE frame received * @return a Stream.Listener that will be notified of pushed stream events */ - public Listener onPush(Stream stream, PushPromiseFrame frame); - - /** - *

Callback method invoked before notifying the first DATA frame.

- *

The default implementation initializes the demand for DATA frames.

- * - * @param stream the stream - */ - public default void onBeforeData(Stream stream) + public default Listener onPush(Stream stream, PushPromiseFrame frame) { - stream.demand(1); + return null; } /** - *

Callback method invoked when a DATA frame has been received.

+ *

Callback method invoked if the application has expressed + * {@link Stream#demand() demand} for DATA frames, and if there + * may be content available.

+ *

Applications that wish to handle DATA frames should call + * {@link Stream#demand()} for this method to be invoked when + * the data is available.

+ *

Server applications should typically demand from {@link #onNewStream(Stream)} + * (upon receiving an HTTP request), while client applications + * should typically demand from {@link #onHeaders(Stream, HeadersFrame)} + * (upon receiving an HTTP response).

+ *

Just prior calling this method, the outstanding demand is + * cancelled; applications that implement this method should read + * content calling {@link Stream#readData()}, and call + * {@link Stream#demand()} to signal to the implementation to call + * again this method when there may be more content available.

+ *

Only one thread at a time invokes this method, although it + * may not be the same thread across different invocations.

+ *

It is always guaranteed that invoking {@link Stream#demand()} + * from within this method will not cause a {@link StackOverflowError}.

+ *

Typical usage:

+ *
+         * class MyStreamListener implements Stream.Listener
+         * {
+         *     @Override
+         *     public void onDataAvailable(Stream stream)
+         *     {
+         *         // Read a chunk of the content.
+         *         Stream.Data data = stream.readData();
+         *         if (data == null)
+         *         {
+         *             // No data available now, demand to be called back.
+         *             stream.demand();
+         *         }
+         *         else
+         *         {
+         *             // Process the content.
+         *             process(data.getByteBuffer());
+         *             // Notify that the content has been consumed.
+         *             data.release();
+         *             if (!data.frame().isEndStream())
+         *             {
+         *                 // Demand to be called back.
+         *                 stream.demand();
+         *             }
+         *         }
+         *     }
+         * }
+         * 
* * @param stream the stream - * @param frame the DATA frame received - * @param callback the callback to complete when the bytes of the DATA frame have been consumed - * @see #onDataDemanded(Stream, DataFrame, Callback) + * @see Stream#demand() */ - public default void onData(Stream stream, DataFrame frame, Callback callback) + public default void onDataAvailable(Stream stream) { - callback.succeeded(); - } - - /** - *

Callback method invoked when a DATA frame has been demanded.

- *

Implementations of this method must arrange to call (within the - * method or otherwise asynchronously) {@link #demand(long)}.

- * - * @param stream the stream - * @param frame the DATA frame received - * @param callback the callback to complete when the bytes of the DATA frame have been consumed - */ - public default void onDataDemanded(Stream stream, DataFrame frame, Callback callback) - { - onData(stream, frame, callback); - stream.demand(1); } /** @@ -266,26 +350,7 @@ public interface Stream */ public default void onReset(Stream stream, ResetFrame frame, Callback callback) { - try - { - onReset(stream, frame); - callback.succeeded(); - } - catch (Throwable x) - { - callback.failed(x); - } - } - - /** - *

Callback method invoked when a RST_STREAM frame has been received for this stream.

- * - * @param stream the stream - * @param frame the RST_FRAME received - * @see Session.Listener#onReset(Session, ResetFrame) - */ - public default void onReset(Stream stream, ResetFrame frame) - { + callback.succeeded(); } /** @@ -296,7 +361,10 @@ public interface Stream * @return true to reset the stream, false to ignore the idle timeout * @see #getIdleTimeout() */ - public boolean onIdleTimeout(Stream stream, Throwable x); + public default boolean onIdleTimeout(Stream stream, Throwable x) + { + return true; + } /** *

Callback method invoked when the stream failed.

@@ -320,38 +388,53 @@ public interface Stream public default void onClosed(Stream stream) { } + } - /** - *

Empty implementation of {@link Listener}

- */ - public static class Adapter implements Listener + /** + *

A {@link Retainable} wrapper of a {@link DataFrame}.

+ */ + public abstract static class Data implements Retainable + { + public static Data eof(int streamId) { - @Override - public void onHeaders(Stream stream, HeadersFrame frame) - { - } + return new Data.EOF(streamId); + } - @Override - public Listener onPush(Stream stream, PushPromiseFrame frame) - { - return null; - } + private final DataFrame frame; - @Override - public void onData(Stream stream, DataFrame frame, Callback callback) - { - callback.succeeded(); - } + public Data(DataFrame frame) + { + this.frame = frame; + } - @Override - public void onReset(Stream stream, ResetFrame frame) - { - } + public DataFrame frame() + { + return frame; + } - @Override - public boolean onIdleTimeout(Stream stream, Throwable x) + @Override + public void retain() + { + throw new UnsupportedOperationException(); + } + + @Override + public boolean release() + { + return true; + } + + @Override + public String toString() + { + return "%s@%x[%s]".formatted(getClass().getSimpleName(), hashCode(), frame()); + } + + private static class EOF extends Data + { + public EOF(int streamId) { - return true; + super(new DataFrame(streamId, BufferUtil.EMPTY_BUFFER, true)); } } } diff --git a/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/api/server/ServerSessionListener.java b/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/api/server/ServerSessionListener.java index 6a6b2976d22..7600dbcdbfc 100644 --- a/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/api/server/ServerSessionListener.java +++ b/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/api/server/ServerSessionListener.java @@ -25,16 +25,7 @@ public interface ServerSessionListener extends Session.Listener * * @param session the session */ - public void onAccept(Session session); - - /** - *

Empty implementation of {@link ServerSessionListener}

- */ - public static class Adapter extends Session.Listener.Adapter implements ServerSessionListener + public default void onAccept(Session session) { - @Override - public void onAccept(Session session) - { - } } } diff --git a/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/HTTP2Channel.java b/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/HTTP2Channel.java index 4abce8dafdb..f3dcae40b06 100644 --- a/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/HTTP2Channel.java +++ b/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/HTTP2Channel.java @@ -15,7 +15,6 @@ package org.eclipse.jetty.http2.internal; import java.util.function.Consumer; -import org.eclipse.jetty.http2.frames.DataFrame; import org.eclipse.jetty.http2.frames.HeadersFrame; import org.eclipse.jetty.util.Callback; @@ -32,7 +31,7 @@ public interface HTTP2Channel */ public interface Client { - public void onData(DataFrame frame, Callback callback); + public void onDataAvailable(); public boolean onTimeout(Throwable failure); @@ -47,7 +46,7 @@ public interface HTTP2Channel */ public interface Server { - public Runnable onData(DataFrame frame, Callback callback); + public Runnable onDataAvailable(); public Runnable onTrailer(HeadersFrame frame); diff --git a/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/HTTP2Connection.java b/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/HTTP2Connection.java index 9c46af3f63f..87e7d09fe9a 100644 --- a/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/HTTP2Connection.java +++ b/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/HTTP2Connection.java @@ -20,12 +20,13 @@ import java.util.Queue; import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicLong; -import org.eclipse.jetty.http2.ISession; +import org.eclipse.jetty.http2.api.Stream; import org.eclipse.jetty.http2.frames.DataFrame; import org.eclipse.jetty.http2.internal.parser.Parser; import org.eclipse.jetty.io.AbstractConnection; import org.eclipse.jetty.io.Connection; import org.eclipse.jetty.io.EndPoint; +import org.eclipse.jetty.io.Retainable; import org.eclipse.jetty.io.RetainableByteBuffer; import org.eclipse.jetty.io.RetainableByteBufferPool; import org.eclipse.jetty.io.WriteFlusher; @@ -48,13 +49,13 @@ public class HTTP2Connection extends AbstractConnection implements WriteFlusher. private final AtomicLong bytesIn = new AtomicLong(); private final RetainableByteBufferPool retainableByteBufferPool; private final Parser parser; - private final ISession session; + private final HTTP2Session session; private final int bufferSize; private final ExecutionStrategy strategy; private boolean useInputDirectByteBuffers; private boolean useOutputDirectByteBuffers; - protected HTTP2Connection(RetainableByteBufferPool retainableByteBufferPool, Executor executor, EndPoint endPoint, Parser parser, ISession session, int bufferSize) + protected HTTP2Connection(RetainableByteBufferPool retainableByteBufferPool, Executor executor, EndPoint endPoint, Parser parser, HTTP2Session session, int bufferSize) { super(endPoint, executor); this.retainableByteBufferPool = retainableByteBufferPool; @@ -69,14 +70,14 @@ public class HTTP2Connection extends AbstractConnection implements WriteFlusher. @Override public long getMessagesIn() { - HTTP2Session session = (HTTP2Session)getSession(); + HTTP2Session session = getSession(); return session.getStreamsOpened(); } @Override public long getMessagesOut() { - HTTP2Session session = (HTTP2Session)getSession(); + HTTP2Session session = getSession(); return session.getStreamsClosed(); } @@ -92,7 +93,7 @@ public class HTTP2Connection extends AbstractConnection implements WriteFlusher. return session.getBytesWritten(); } - public ISession getSession() + public HTTP2Session getSession() { return session; } @@ -196,7 +197,7 @@ public class HTTP2Connection extends AbstractConnection implements WriteFlusher. private void offerTask(Runnable task) { - try (AutoLock l = lock.lock()) + try (AutoLock ignored = lock.lock()) { tasks.offer(task); } @@ -226,7 +227,7 @@ public class HTTP2Connection extends AbstractConnection implements WriteFlusher. private Runnable pollTask() { - try (AutoLock l = lock.lock()) + try (AutoLock ignored = lock.lock()) { return tasks.poll(); } @@ -257,7 +258,7 @@ public class HTTP2Connection extends AbstractConnection implements WriteFlusher. { Runnable task = pollTask(); if (LOG.isDebugEnabled()) - LOG.debug("Dequeued task {}", String.valueOf(task)); + LOG.debug("Dequeued task {}", task); if (task != null) return task; @@ -404,8 +405,7 @@ public class HTTP2Connection extends AbstractConnection implements WriteFlusher. { NetworkBuffer networkBuffer = producer.networkBuffer; networkBuffer.retain(); - Callback callback = networkBuffer; - session.onData(frame, callback); + session.onData(new StreamData(frame, networkBuffer)); } @Override @@ -416,7 +416,30 @@ public class HTTP2Connection extends AbstractConnection implements WriteFlusher. } } - private class NetworkBuffer implements Callback + private static class StreamData extends Stream.Data + { + private final Retainable retainable; + + private StreamData(DataFrame frame, Retainable retainable) + { + super(frame); + this.retainable = retainable; + } + + @Override + public void retain() + { + retainable.retain(); + } + + @Override + public boolean release() + { + return retainable.release(); + } + } + + private class NetworkBuffer implements Retainable { private final RetainableByteBuffer delegate; @@ -440,46 +463,27 @@ public class HTTP2Connection extends AbstractConnection implements WriteFlusher. return delegate.hasRemaining(); } - public boolean release() - { - return delegate.release(); - } - + @Override public void retain() { delegate.retain(); } + @Override + public boolean release() + { + if (delegate.release()) + { + if (LOG.isDebugEnabled()) + LOG.debug("Released retained {}", this); + return true; + } + return false; + } + private void put(ByteBuffer source) { BufferUtil.append(delegate.getBuffer(), source); } - - @Override - public void succeeded() - { - completed(null); - } - - @Override - public void failed(Throwable failure) - { - completed(failure); - } - - private void completed(Throwable failure) - { - if (delegate.release()) - { - if (LOG.isDebugEnabled()) - LOG.debug("Released retained {}", this, failure); - } - } - - @Override - public InvocationType getInvocationType() - { - return InvocationType.NON_BLOCKING; - } } } diff --git a/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/HTTP2Flusher.java b/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/HTTP2Flusher.java index 66212b9f181..dc960759ca4 100644 --- a/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/HTTP2Flusher.java +++ b/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/HTTP2Flusher.java @@ -26,7 +26,6 @@ import java.util.Queue; import java.util.Set; import org.eclipse.jetty.http2.FlowControlStrategy; -import org.eclipse.jetty.http2.IStream; import org.eclipse.jetty.http2.frames.Frame; import org.eclipse.jetty.http2.frames.FrameType; import org.eclipse.jetty.http2.frames.WindowUpdateFrame; @@ -69,10 +68,10 @@ public class HTTP2Flusher extends IteratingCallback implements Dumpable return invocationType; } - public void window(IStream stream, WindowUpdateFrame frame) + public void window(HTTP2Stream stream, WindowUpdateFrame frame) { Throwable closed; - try (AutoLock l = lock.lock()) + try (AutoLock ignored = lock.lock()) { closed = terminated; if (closed == null) @@ -86,7 +85,7 @@ public class HTTP2Flusher extends IteratingCallback implements Dumpable public boolean prepend(Entry entry) { Throwable closed; - try (AutoLock l = lock.lock()) + try (AutoLock ignored = lock.lock()) { closed = terminated; if (closed == null) @@ -105,7 +104,7 @@ public class HTTP2Flusher extends IteratingCallback implements Dumpable public boolean append(Entry entry) { Throwable closed; - try (AutoLock l = lock.lock()) + try (AutoLock ignored = lock.lock()) { closed = terminated; if (closed == null) @@ -124,7 +123,7 @@ public class HTTP2Flusher extends IteratingCallback implements Dumpable public boolean append(List list) { Throwable closed; - try (AutoLock l = lock.lock()) + try (AutoLock ignored = lock.lock()) { closed = terminated; if (closed == null) @@ -142,7 +141,7 @@ public class HTTP2Flusher extends IteratingCallback implements Dumpable private int getWindowQueueSize() { - try (AutoLock l = lock.lock()) + try (AutoLock ignored = lock.lock()) { return windows.size(); } @@ -150,7 +149,7 @@ public class HTTP2Flusher extends IteratingCallback implements Dumpable public int getFrameQueueSize() { - try (AutoLock l = lock.lock()) + try (AutoLock ignored = lock.lock()) { return entries.size(); } @@ -162,7 +161,7 @@ public class HTTP2Flusher extends IteratingCallback implements Dumpable if (LOG.isDebugEnabled()) LOG.debug("Flushing {}", session); - try (AutoLock l = lock.lock()) + try (AutoLock ignored = lock.lock()) { if (terminated != null) throw terminated; @@ -354,7 +353,7 @@ public class HTTP2Flusher extends IteratingCallback implements Dumpable Throwable closed; Set allEntries; - try (AutoLock l = lock.lock()) + try (AutoLock ignored = lock.lock()) { closed = terminated; terminated = x; @@ -383,7 +382,7 @@ public class HTTP2Flusher extends IteratingCallback implements Dumpable void terminate(Throwable cause) { Throwable closed; - try (AutoLock l = lock.lock()) + try (AutoLock ignored = lock.lock()) { closed = terminated; terminated = cause; @@ -425,9 +424,9 @@ public class HTTP2Flusher extends IteratingCallback implements Dumpable public abstract static class Entry extends Callback.Nested { protected final Frame frame; - protected final IStream stream; + protected final HTTP2Stream stream; - protected Entry(Frame frame, IStream stream, Callback callback) + protected Entry(Frame frame, HTTP2Stream stream, Callback callback) { super(callback); this.frame = frame; @@ -514,10 +513,10 @@ public class HTTP2Flusher extends IteratingCallback implements Dumpable private class WindowEntry { - private final IStream stream; + private final HTTP2Stream stream; private final WindowUpdateFrame frame; - public WindowEntry(IStream stream, WindowUpdateFrame frame) + public WindowEntry(HTTP2Stream stream, WindowUpdateFrame frame) { this.stream = stream; this.frame = frame; diff --git a/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/HTTP2Session.java b/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/HTTP2Session.java index 2d5d464d25b..8a23f69c6d0 100644 --- a/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/HTTP2Session.java +++ b/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/HTTP2Session.java @@ -40,8 +40,6 @@ import java.util.stream.Collectors; import org.eclipse.jetty.http.MetaData; import org.eclipse.jetty.http2.CloseState; import org.eclipse.jetty.http2.FlowControlStrategy; -import org.eclipse.jetty.http2.ISession; -import org.eclipse.jetty.http2.IStream; import org.eclipse.jetty.http2.api.Session; import org.eclipse.jetty.http2.api.Stream; import org.eclipse.jetty.http2.frames.DataFrame; @@ -82,11 +80,11 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; @ManagedObject -public abstract class HTTP2Session extends ContainerLifeCycle implements ISession, Parser.Listener +public abstract class HTTP2Session extends ContainerLifeCycle implements Session, Parser.Listener { private static final Logger LOG = LoggerFactory.getLogger(HTTP2Session.class); - private final ConcurrentMap streams = new ConcurrentHashMap<>(); + private final ConcurrentMap streams = new ConcurrentHashMap<>(); private final AtomicLong streamsOpened = new AtomicLong(); private final AtomicLong streamsClosed = new AtomicLong(); private final StreamsState streamsState = new StreamsState(); @@ -221,7 +219,6 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio return generator; } - @Override public long getBytesWritten() { return bytesWritten.get(); @@ -230,17 +227,18 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio @Override public void onData(DataFrame frame) { - onData(frame, Callback.NOOP); + // This method should never be called, the one below should. + throw new UnsupportedOperationException(); } - @Override - public void onData(DataFrame frame, Callback callback) + public void onData(Stream.Data data) { if (LOG.isDebugEnabled()) - LOG.debug("Received {} on {}", frame, this); + LOG.debug("Received {} on {}", data, this); + DataFrame frame = data.frame(); int streamId = frame.getStreamId(); - IStream stream = getStream(streamId); + HTTP2Stream stream = getStream(streamId); // SPEC: the session window must be updated even if the stream is null. // The flow control length includes the padding bytes. @@ -251,7 +249,7 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio { if (getRecvWindow() < 0) { - onSessionFailure(ErrorCode.FLOW_CONTROL_ERROR.code, "session_window_exceeded", callback); + onSessionFailure(ErrorCode.FLOW_CONTROL_ERROR.code, "session_window_exceeded", toCallback(data)); } else { @@ -259,11 +257,11 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio { // It's a bad client, it does not deserve to be // treated gently by just resetting the stream. - onSessionFailure(ErrorCode.FLOW_CONTROL_ERROR.code, "stream_window_exceeded", callback); + onSessionFailure(ErrorCode.FLOW_CONTROL_ERROR.code, "stream_window_exceeded", toCallback(data)); } else { - stream.process(frame, new DataCallback(callback, stream, flowControlLength)); + stream.process(new StreamData(data, stream, flowControlLength)); } } } @@ -275,12 +273,36 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio // otherwise other requests will be stalled. flowControl.onDataConsumed(this, null, flowControlLength); if (isStreamClosed(streamId)) - reset(null, new ResetFrame(streamId, ErrorCode.STREAM_CLOSED_ERROR.code), callback); + reset(null, new ResetFrame(streamId, ErrorCode.STREAM_CLOSED_ERROR.code), toCallback(data)); else - onSessionFailure(ErrorCode.PROTOCOL_ERROR.code, "unexpected_data_frame", callback); + onSessionFailure(ErrorCode.PROTOCOL_ERROR.code, "unexpected_data_frame", toCallback(data)); } } + private Callback toCallback(Stream.Data data) + { + return new Callback() + { + @Override + public void succeeded() + { + data.release(); + } + + @Override + public void failed(Throwable x) + { + data.release(); + } + + @Override + public InvocationType getInvocationType() + { + return InvocationType.NON_BLOCKING; + } + }; + } + private boolean isStreamClosed(int streamId) { return isLocalStream(streamId) ? isLocalStreamClosed(streamId) : isRemoteStreamClosed(streamId); @@ -318,7 +340,7 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio LOG.debug("Received {} on {}", frame, this); int streamId = frame.getStreamId(); - IStream stream = getStream(streamId); + HTTP2Stream stream = getStream(streamId); if (stream != null) { stream.process(frame, new OnResetCallback()); @@ -359,62 +381,54 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio int value = entry.getValue(); switch (key) { - case SettingsFrame.HEADER_TABLE_SIZE: + case SettingsFrame.HEADER_TABLE_SIZE -> { if (LOG.isDebugEnabled()) LOG.debug("Updating HPACK header table size to {} for {}", value, this); generator.setHeaderTableSize(value); - break; } - case SettingsFrame.ENABLE_PUSH: + case SettingsFrame.ENABLE_PUSH -> { boolean enabled = value == 1; if (LOG.isDebugEnabled()) LOG.debug("{} push for {}", enabled ? "Enabling" : "Disabling", this); pushEnabled = enabled; - break; } - case SettingsFrame.MAX_CONCURRENT_STREAMS: + case SettingsFrame.MAX_CONCURRENT_STREAMS -> { if (LOG.isDebugEnabled()) LOG.debug("Updating max local concurrent streams to {} for {}", value, this); maxLocalStreams = value; - break; } - case SettingsFrame.INITIAL_WINDOW_SIZE: + case SettingsFrame.INITIAL_WINDOW_SIZE -> { if (LOG.isDebugEnabled()) LOG.debug("Updating initial stream window size to {} for {}", value, this); flowControl.updateInitialStreamWindow(this, value, false); - break; } - case SettingsFrame.MAX_FRAME_SIZE: + case SettingsFrame.MAX_FRAME_SIZE -> { if (LOG.isDebugEnabled()) LOG.debug("Updating max frame size to {} for {}", value, this); generator.setMaxFrameSize(value); - break; } - case SettingsFrame.MAX_HEADER_LIST_SIZE: + case SettingsFrame.MAX_HEADER_LIST_SIZE -> { if (LOG.isDebugEnabled()) LOG.debug("Updating max header list size to {} for {}", value, this); generator.setMaxHeaderListSize(value); - break; } - case SettingsFrame.ENABLE_CONNECT_PROTOCOL: + case SettingsFrame.ENABLE_CONNECT_PROTOCOL -> { boolean enabled = value == 1; if (LOG.isDebugEnabled()) LOG.debug("{} CONNECT protocol for {}", enabled ? "Enabling" : "Disabling", this); connectProtocolEnabled = enabled; - break; } - default: + default -> { if (LOG.isDebugEnabled()) LOG.debug("Unknown setting {}:{} for {}", key, value, this); - break; } } } @@ -470,7 +484,7 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio int windowDelta = frame.getWindowDelta(); if (streamId > 0) { - IStream stream = getStream(streamId); + HTTP2Stream stream = getStream(streamId); if (stream != null) { int streamSendWindow = stream.updateSendWindow(0); @@ -500,8 +514,7 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio } } - @Override - public void onWindowUpdate(IStream stream, WindowUpdateFrame frame) + public void onWindowUpdate(HTTP2Stream stream, WindowUpdateFrame frame) { // WindowUpdateFrames arrive concurrently with writes. // Increasing (or reducing) the window size concurrently @@ -522,7 +535,7 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio Throwable failure = toFailure(error, reason); if (LOG.isDebugEnabled()) LOG.debug("Stream #{} failure {}", streamId, this, failure); - IStream stream = getStream(streamId); + HTTP2Stream stream = getStream(streamId); if (stream != null) failStream(stream, error, reason, failure, callback); else @@ -567,7 +580,7 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio notifyFailure(this, failure, countCallback); } - private void failStreams(Predicate matcher, String reason, boolean reset) + private void failStreams(Predicate matcher, String reason, boolean reset) { int error = ErrorCode.CANCEL_STREAM_ERROR.code; Throwable failure = toFailure(error, reason); @@ -575,7 +588,7 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio { if (stream.isClosed()) continue; - if (!matcher.test((IStream)stream)) + if (!matcher.test(stream)) continue; if (LOG.isDebugEnabled()) LOG.debug("Failing stream {} of {}", stream, this); @@ -587,7 +600,7 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio private void failStream(Stream stream, int error, String reason, Throwable failure, Callback callback) { - ((IStream)stream).process(new FailureFrame(error, reason, failure), callback); + ((HTTP2Stream)stream).process(new FailureFrame(error, reason, failure), callback); } private Throwable toFailure(int error, String reason) @@ -598,11 +611,10 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio @Override public void newStream(HeadersFrame frame, Promise promise, Stream.Listener listener) { - newStream(new IStream.FrameList(frame), promise, listener); + newStream(new HTTP2Stream.FrameList(frame), promise, listener); } - @Override - public void newStream(IStream.FrameList frames, Promise promise, Stream.Listener listener) + public void newStream(HTTP2Stream.FrameList frames, Promise promise, Stream.Listener listener) { streamsState.newLocalStream(frames, promise, listener); } @@ -619,7 +631,7 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio return streamsState.newUpgradeStream(frame, listener, failFn); } - protected IStream newStream(int streamId, MetaData.Request request, boolean local) + protected HTTP2Stream newStream(int streamId, MetaData.Request request, boolean local) { return new HTTP2Stream(this, streamId, request, local); } @@ -630,8 +642,7 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio return streamsState.priority(frame, callback); } - @Override - public void push(IStream stream, Promise promise, PushPromiseFrame frame, Stream.Listener listener) + public void push(Stream stream, Promise promise, PushPromiseFrame frame, Stream.Listener listener) { streamsState.push(frame, new Promise.Wrapper<>(promise) { @@ -640,7 +651,7 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio { // Pushed streams are implicitly remotely closed. // They are closed when sending an end-stream DATA frame. - ((IStream)pushedStream).updateClose(true, CloseState.Event.RECEIVED); + ((HTTP2Stream)pushedStream).updateClose(true, CloseState.Event.RECEIVED); super.succeeded(pushedStream); } }, listener); @@ -661,7 +672,7 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio control(null, callback, frame); } - void reset(IStream stream, ResetFrame frame, Callback callback) + void reset(HTTP2Stream stream, ResetFrame frame, Callback callback) { control(stream, Callback.from(() -> { @@ -730,13 +741,12 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio return streamsState.getCloseState(); } - private void control(IStream stream, Callback callback, Frame frame) + private void control(HTTP2Stream stream, Callback callback, Frame frame) { frames(stream, List.of(frame), callback); } - @Override - public void frames(IStream stream, List frames, Callback callback) + public void frames(HTTP2Stream stream, List frames, Callback callback) { // We want to generate as late as possible to allow re-prioritization; // generation will happen while processing the entries. @@ -754,15 +764,14 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio } } - private HTTP2Flusher.Entry newEntry(Frame frame, IStream stream, Callback callback) + private HTTP2Flusher.Entry newEntry(Frame frame, HTTP2Stream stream, Callback callback) { return frame.getType() == FrameType.DATA ? new DataEntry((DataFrame)frame, stream, callback) : new ControlEntry(frame, stream, callback); } - @Override - public void data(IStream stream, Callback callback, DataFrame frame) + public void data(HTTP2Stream stream, DataFrame frame, Callback callback) { // We want to generate as late as possible to allow re-prioritization. frame(newEntry(frame, stream, callback), true); @@ -782,7 +791,7 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio } } - protected IStream createLocalStream(int streamId, MetaData.Request request, Consumer failFn) + protected HTTP2Stream createLocalStream(int streamId, MetaData.Request request, Consumer failFn) { while (true) { @@ -800,7 +809,7 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio break; } - IStream stream = newStream(streamId, request, true); + HTTP2Stream stream = newStream(streamId, request, true); if (streams.putIfAbsent(streamId, stream) == null) { stream.setIdleTimeout(getStreamIdleTimeout()); @@ -817,7 +826,7 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio } } - protected IStream createRemoteStream(int streamId, MetaData.Request request) + protected HTTP2Stream createRemoteStream(int streamId, MetaData.Request request) { // This stream has been seen the server. // Even if the stream cannot be created because this peer is closing, @@ -851,7 +860,7 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio break; } - IStream stream = newStream(streamId, request, false); + HTTP2Stream stream = newStream(streamId, request, false); if (streams.putIfAbsent(streamId, stream) == null) { stream.setIdleTimeout(getStreamIdleTimeout()); @@ -878,11 +887,10 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio remoteStreamCount.add(deltaStreams, deltaClosing); } - @Override - public boolean removeStream(IStream stream) + public boolean removeStream(Stream stream) { int streamId = stream.getId(); - IStream removed = streams.remove(streamId); + HTTP2Stream removed = streams.remove(streamId); if (removed == null) return false; if (LOG.isDebugEnabled()) @@ -906,7 +914,7 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio } @Override - public IStream getStream(int streamId) + public HTTP2Stream getStream(int streamId) { return streams.get(streamId); } @@ -953,13 +961,11 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio return recvWindow.get(); } - @Override public int updateSendWindow(int delta) { return sendWindow.getAndAdd(delta); } - @Override public int updateRecvWindow(int delta) { return recvWindow.getAndAdd(delta); @@ -990,7 +996,6 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio * @see #close(int, String, Callback) * @see #onIdleTimeout() */ - @Override public void onShutdown() { streamsState.onShutdown(); @@ -1004,7 +1009,6 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio * @see #close(int, String, Callback) * @see #onShutdown() */ - @Override public boolean onIdleTimeout() { return streamsState.onIdleTimeout(); @@ -1015,7 +1019,6 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio streamsState.idleTime = System.nanoTime(); } - @Override public void onFrame(Frame frame) { onConnectionFailure(ErrorCode.PROTOCOL_ERROR.code, "upgrade"); @@ -1033,14 +1036,14 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio streamsState.onStreamCreated(); } - protected final void onStreamOpened(IStream stream) + protected final void onStreamOpened(Stream stream) { if (LOG.isDebugEnabled()) LOG.debug("Opened stream {} for {}", stream, this); streamsOpened.incrementAndGet(); } - private void onStreamClosed(IStream stream) + private void onStreamClosed(Stream stream) { if (LOG.isDebugEnabled()) LOG.debug("Closed stream {} for {}", stream, this); @@ -1054,7 +1057,6 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio streamsState.onStreamDestroyed(); } - @Override public void onFlushed(long bytes) throws IOException { flusher.onFlushed(bytes); @@ -1187,7 +1189,7 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio } } - protected void notifyHeaders(IStream stream, HeadersFrame frame) + protected void notifyHeaders(Stream stream, HeadersFrame frame) { Stream.Listener listener = stream.getListener(); if (listener == null) @@ -1232,7 +1234,7 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio { private int frameBytes; - private ControlEntry(Frame frame, IStream stream, Callback callback) + private ControlEntry(Frame frame, HTTP2Stream stream, Callback callback) { super(frame, stream, callback); } @@ -1278,23 +1280,17 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio { switch (frame.getType()) { - case HEADERS: + case HEADERS -> { HeadersFrame headersFrame = (HeadersFrame)frame; stream.updateClose(headersFrame.isEndStream(), CloseState.Event.BEFORE_SEND); - break; } - case SETTINGS: + case SETTINGS -> { SettingsFrame settingsFrame = (SettingsFrame)frame; Integer initialWindow = settingsFrame.getSettings().get(SettingsFrame.INITIAL_WINDOW_SIZE); if (initialWindow != null) flowControl.updateInitialStreamWindow(HTTP2Session.this, initialWindow, true); - break; - } - default: - { - break; } } } @@ -1315,23 +1311,17 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio switch (frame.getType()) { - case HEADERS: + case HEADERS -> { HeadersFrame headersFrame = (HeadersFrame)frame; if (headersFrame.getMetaData().isRequest()) onStreamOpened(stream); if (stream.updateClose(headersFrame.isEndStream(), CloseState.Event.AFTER_SEND)) removeStream(stream); - break; } - case WINDOW_UPDATE: + case WINDOW_UPDATE -> { flowControl.windowUpdate(HTTP2Session.this, stream, (WindowUpdateFrame)frame); - break; - } - default: - { - break; } } @@ -1346,7 +1336,7 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio private int dataBytes; private int dataRemaining; - private DataEntry(DataFrame frame, IStream stream, Callback callback) + private DataEntry(DataFrame frame, HTTP2Stream stream, Callback callback) { super(frame, stream, callback); // We don't do any padding, so the flow control length is @@ -1439,42 +1429,6 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio } } - private class DataCallback extends Callback.Nested - { - private final IStream stream; - private final int flowControlLength; - - public DataCallback(Callback callback, IStream stream, int flowControlLength) - { - super(callback); - this.stream = stream; - this.flowControlLength = flowControlLength; - } - - @Override - public void succeeded() - { - complete(); - super.succeeded(); - } - - @Override - public void failed(Throwable x) - { - // Consume also in case of failures, to free the - // session flow control window for other streams. - complete(); - super.failed(x); - } - - private void complete() - { - notIdle(); - stream.notIdle(); - flowControl.onDataConsumed(HTTP2Session.this, stream, flowControlLength); - } - } - private class OnResetCallback implements Callback { @Override @@ -1531,7 +1485,7 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio private CloseState getCloseState() { - try (AutoLock l = lock.lock()) + try (AutoLock ignored = lock.lock()) { return closed; } @@ -1540,7 +1494,7 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio private CompletableFuture shutdown() { CompletableFuture future; - try (AutoLock l = lock.lock()) + try (AutoLock ignored = lock.lock()) { if (shutdownCallback != null) return shutdownCallback; @@ -1554,11 +1508,11 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio { boolean sendGoAway = false; boolean tryRunZeroStreamsAction = false; - try (AutoLock l = lock.lock()) + try (AutoLock ignored = lock.lock()) { switch (closed) { - case NOT_CLOSED: + case NOT_CLOSED -> { goAwaySent = frame; closed = CloseState.LOCALLY_CLOSED; @@ -1574,9 +1528,8 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio }; tryRunZeroStreamsAction = streamCount.get() == 0; } - break; } - case LOCALLY_CLOSED: + case LOCALLY_CLOSED -> { if (frame.isGraceful()) { @@ -1601,9 +1554,8 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio LOG.debug("Already sent, ignored GOAWAY {} for {}", frame, HTTP2Session.this); } } - break; } - case REMOTELY_CLOSED: + case REMOTELY_CLOSED -> { goAwaySent = frame; sendGoAway = true; @@ -1632,14 +1584,12 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio tryRunZeroStreamsAction = streamCount.get() == 0; } } - break; } - default: + default -> { // Already closing or closed, ignore it. if (LOG.isDebugEnabled()) LOG.debug("Already closed, ignored {} for {}", frame, HTTP2Session.this); - break; } } } @@ -1665,14 +1615,11 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio LOG.debug("Halting ({}) for {}", reason, HTTP2Session.this); GoAwayFrame goAwayFrame = null; GoAwayFrame goAwayFrameEvent; - try (AutoLock l = lock.lock()) + try (AutoLock ignored = lock.lock()) { switch (closed) { - case NOT_CLOSED: - case REMOTELY_CLOSED: - case LOCALLY_CLOSED: - case CLOSING: + case NOT_CLOSED, REMOTELY_CLOSED, LOCALLY_CLOSED, CLOSING -> { if (goAwaySent == null || goAwaySent.isGraceful()) goAwaySent = goAwayFrame = newGoAwayFrame(ErrorCode.NO_ERROR.code, reason); @@ -1681,9 +1628,8 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio zeroStreamsAction = null; if (failure != null) failure = toFailure(ErrorCode.NO_ERROR.code, reason); - break; } - default: + default -> { return; } @@ -1700,11 +1646,11 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio { boolean failStreams = false; boolean tryRunZeroStreamsAction = false; - try (AutoLock l = lock.lock()) + try (AutoLock ignored = lock.lock()) { switch (closed) { - case NOT_CLOSED: + case NOT_CLOSED -> { goAwayRecv = frame; if (frame.isGraceful()) @@ -1722,9 +1668,8 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio tryRunZeroStreamsAction = streamCount.get() == 0; failStreams = true; } - break; } - case LOCALLY_CLOSED: + case LOCALLY_CLOSED -> { goAwayRecv = frame; if (frame.isGraceful()) @@ -1750,9 +1695,8 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio failStreams = true; } } - break; } - case REMOTELY_CLOSED: + case REMOTELY_CLOSED -> { if (frame.isGraceful()) { @@ -1777,14 +1721,12 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio tryRunZeroStreamsAction = streamCount.get() == 0; failStreams = true; } - break; } - default: + default -> { // Already closing or closed, ignore it. if (LOG.isDebugEnabled()) LOG.debug("Already closed, ignored {} for {}", frame, HTTP2Session.this); - break; } } } @@ -1797,7 +1739,7 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio // For example, a client that sent request with streamId=137 may send a GOAWAY(4), // where streamId=4 is the last stream pushed by the server to the client. // The server must not compare its local streamId=4 with remote streamId=137. - Predicate failIf = stream -> stream.isLocal() && stream.getId() > frame.getLastStreamId(); + Predicate failIf = stream -> stream.isLocal() && stream.getId() > frame.getLastStreamId(); failStreams(failIf, "closing", false); } @@ -1810,36 +1752,32 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio String reason = "input_shutdown"; Throwable cause = null; boolean failStreams = false; - try (AutoLock l = lock.lock()) + try (AutoLock ignored = lock.lock()) { switch (closed) { - case NOT_CLOSED: - case LOCALLY_CLOSED: + case NOT_CLOSED, LOCALLY_CLOSED -> { if (LOG.isDebugEnabled()) LOG.debug("Unexpected ISHUT for {}", HTTP2Session.this); closed = CloseState.CLOSING; failure = cause = new ClosedChannelException(); - break; } - case REMOTELY_CLOSED: + case REMOTELY_CLOSED -> { closed = CloseState.CLOSING; GoAwayFrame goAwayFrame = newGoAwayFrame(0, ErrorCode.NO_ERROR.code, reason); zeroStreamsAction = () -> terminate(goAwayFrame); failure = new ClosedChannelException(); failStreams = true; - break; } - case CLOSING: + case CLOSING -> { if (failure == null) failure = new ClosedChannelException(); failStreams = true; - break; } - default: + default -> { if (LOG.isDebugEnabled()) LOG.debug("Already closed, ignoring ISHUT for {}", HTTP2Session.this); @@ -1852,7 +1790,7 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio { // Since nothing else will arrive from the other peer, reset // the streams for which the other peer did not send all frames. - Predicate failIf = stream -> !stream.isRemotelyClosed(); + Predicate failIf = stream -> !stream.isRemotelyClosed(); failStreams(failIf, reason, false); tryRunZeroStreamsAction(); } @@ -1870,20 +1808,20 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio boolean sendGoAway = false; GoAwayFrame goAwayFrame = null; Throwable cause = null; - try (AutoLock l = lock.lock()) + try (AutoLock ignored = lock.lock()) { switch (closed) { - case NOT_CLOSED: + case NOT_CLOSED -> { long elapsed = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - idleTime); if (elapsed < endPoint.getIdleTimeout()) return false; notify = true; - break; } + // Timed out while waiting for closing events, fail all the streams. - case LOCALLY_CLOSED: + case LOCALLY_CLOSED -> { if (goAwaySent.isGraceful()) { @@ -1894,9 +1832,8 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio closed = CloseState.CLOSING; zeroStreamsAction = null; failure = cause = new TimeoutException("Session idle timeout expired"); - break; } - case REMOTELY_CLOSED: + case REMOTELY_CLOSED -> { goAwaySent = newGoAwayFrame(ErrorCode.NO_ERROR.code, reason); sendGoAway = true; @@ -1904,9 +1841,8 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio closed = CloseState.CLOSING; zeroStreamsAction = null; failure = cause = new TimeoutException("Session idle timeout expired"); - break; } - default: + default -> { if (LOG.isDebugEnabled()) LOG.debug("Already closed, ignored idle timeout for {}", HTTP2Session.this); @@ -1937,22 +1873,19 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio { GoAwayFrame goAwayFrame; Throwable cause; - try (AutoLock l = lock.lock()) + try (AutoLock ignored = lock.lock()) { switch (closed) { - case NOT_CLOSED: - case LOCALLY_CLOSED: - case REMOTELY_CLOSED: + case NOT_CLOSED, LOCALLY_CLOSED, REMOTELY_CLOSED -> { // Send another GOAWAY with the error code. goAwaySent = goAwayFrame = newGoAwayFrame(error, reason); closed = CloseState.CLOSING; zeroStreamsAction = null; failure = cause = toFailure(error, reason); - break; } - default: + default -> { if (LOG.isDebugEnabled()) LOG.debug("Already closed, ignored session failure {}", HTTP2Session.this, failure); @@ -1974,13 +1907,11 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio private void onWriteFailure(Throwable x) { String reason = "write_failure"; - try (AutoLock l = lock.lock()) + try (AutoLock ignored = lock.lock()) { switch (closed) { - case NOT_CLOSED: - case LOCALLY_CLOSED: - case REMOTELY_CLOSED: + case NOT_CLOSED, LOCALLY_CLOSED, REMOTELY_CLOSED -> { closed = CloseState.CLOSING; zeroStreamsAction = () -> @@ -1989,9 +1920,8 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio terminate(goAwayFrame); }; failure = x; - break; } - default: + default -> { return; } @@ -2032,7 +1962,7 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio // but only one moves to CLOSED and runs the action. Runnable action = null; CompletableFuture future; - try (AutoLock l = lock.lock()) + try (AutoLock ignored = lock.lock()) { long count = streamCount.get(); if (count > 0) @@ -2046,34 +1976,27 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio switch (closed) { - case LOCALLY_CLOSED: + case LOCALLY_CLOSED -> { if (goAwaySent.isGraceful()) { action = zeroStreamsAction; zeroStreamsAction = null; } - break; } - case REMOTELY_CLOSED: + case REMOTELY_CLOSED -> { if (goAwaySent != null && goAwaySent.isGraceful()) { action = zeroStreamsAction; zeroStreamsAction = null; } - break; } - case CLOSING: + case CLOSING -> { closed = CloseState.CLOSED; action = zeroStreamsAction; zeroStreamsAction = null; - break; - } - default: - { - break; } } } @@ -2114,7 +2037,7 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio return streamId; } - private void newLocalStream(IStream.FrameList frameList, Promise promise, Stream.Listener listener) + private void newLocalStream(HTTP2Stream.FrameList frameList, Promise promise, Stream.Listener listener) { Slot slot = new Slot(); int currentStreamId = frameList.getStreamId(); @@ -2137,12 +2060,12 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio private Stream newUpgradeStream(HeadersFrame frame, Stream.Listener listener, Consumer failFn) { int streamId; - try (AutoLock l = lock.lock()) + try (AutoLock ignored = lock.lock()) { streamId = localStreamIds.getAndAdd(2); HTTP2Session.this.onStreamCreated(streamId); } - IStream stream = HTTP2Session.this.createLocalStream(streamId, (MetaData.Request)frame.getMetaData(), x -> + HTTP2Stream stream = HTTP2Session.this.createLocalStream(streamId, (MetaData.Request)frame.getMetaData(), x -> { HTTP2Session.this.onStreamDestroyed(streamId); failFn.accept(x); @@ -2157,7 +2080,7 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio private boolean newRemoteStream(int streamId) { - try (AutoLock l = lock.lock()) + try (AutoLock ignored = lock.lock()) { switch (closed) { @@ -2201,7 +2124,7 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio MetaData.Request request = extractMetaDataRequest(frames.get(0)); if (request == null) return false; - IStream stream = HTTP2Session.this.createLocalStream(streamId, request, promise::failed); + HTTP2Stream stream = HTTP2Session.this.createLocalStream(streamId, request, promise::failed); if (stream == null) return false; @@ -2241,7 +2164,7 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio private int reserveSlot(Slot slot, int streamId, Consumer fail) { Throwable failure = null; - try (AutoLock l = lock.lock()) + try (AutoLock ignored = lock.lock()) { if (closed == CloseState.NOT_CLOSED) { @@ -2267,7 +2190,7 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio private void freeSlot(Slot slot, int streamId) { - try (AutoLock l = lock.lock()) + try (AutoLock ignored = lock.lock()) { slots.remove(slot); } @@ -2296,7 +2219,7 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio while (true) { List entries; - try (AutoLock l = lock.lock()) + try (AutoLock ignored = lock.lock()) { if (flushing == null) flushing = thread; @@ -2324,7 +2247,7 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio @Override public String toString() { - try (AutoLock l = lock.lock()) + try (AutoLock ignored = lock.lock()) { return String.format("state=[streams=%d,%s,goAwayRecv=%s,goAwaySent=%s,failure=%s]", streamCount.get(), @@ -2362,4 +2285,59 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio return false; } } + + /** + * @implNote This class needs an extra reference counter because it needs to + * open the flow control window when the application releases this instance. + * Imagine a network buffer with 2 DATA frames: this will create 2 Data + * objects, which will be passed to the application. The network buffer is + * now retained 3 times (1 time for the network usage, and 1 time for each + * Data object). + * When the application releases the first Data object, the flow control + * window should be opened immediately for the length of that Data object, + * so the implementation cannot rely on delegating the call to release() + * to the network buffer, because the network buffer will still be retained. + * Furthermore, the flow control logic must be executed only once, while + * the Data object release() method may be invoked multiple times (since + * it may be additionally retained, for example when converted to a Chunk). + * The solution is to have an additional reference counter for the objects + * of this class, that allows to invoke the flow control logic only once, + * and only when all retains performed on an instance have been released. + */ + private class StreamData extends Stream.Data + { + private final ReferenceCounter counter = new ReferenceCounter(); + private final Stream.Data data; + private final HTTP2Stream stream; + private final int flowControlLength; + + private StreamData(Stream.Data data, HTTP2Stream stream, int flowControlLength) + { + super(data.frame()); + this.data = data; + this.stream = stream; + this.flowControlLength = flowControlLength; + } + + @Override + public void retain() + { + counter.retain(); + data.retain(); + } + + @Override + public boolean release() + { + data.release(); + boolean result = counter.release(); + if (result) + { + notIdle(); + stream.notIdle(); + flowControl.onDataConsumed(HTTP2Session.this, stream, flowControlLength); + } + return result; + } + } } diff --git a/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/HTTP2Stream.java b/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/HTTP2Stream.java index ed32f9066f4..07eb4803f2a 100644 --- a/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/HTTP2Stream.java +++ b/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/HTTP2Stream.java @@ -13,11 +13,16 @@ package org.eclipse.jetty.http2.internal; +import java.io.Closeable; import java.io.EOFException; import java.io.IOException; import java.nio.channels.WritePendingException; import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Collections; import java.util.Deque; +import java.util.List; +import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.TimeUnit; @@ -30,8 +35,6 @@ import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpMethod; import org.eclipse.jetty.http.MetaData; import org.eclipse.jetty.http2.CloseState; -import org.eclipse.jetty.http2.ISession; -import org.eclipse.jetty.http2.IStream; import org.eclipse.jetty.http2.api.Stream; import org.eclipse.jetty.http2.frames.DataFrame; import org.eclipse.jetty.http2.frames.FailureFrame; @@ -39,31 +42,31 @@ import org.eclipse.jetty.http2.frames.Frame; import org.eclipse.jetty.http2.frames.HeadersFrame; import org.eclipse.jetty.http2.frames.PushPromiseFrame; import org.eclipse.jetty.http2.frames.ResetFrame; +import org.eclipse.jetty.http2.frames.StreamFrame; import org.eclipse.jetty.http2.frames.WindowUpdateFrame; import org.eclipse.jetty.io.CyclicTimeouts; import org.eclipse.jetty.io.EofException; +import org.eclipse.jetty.util.Attachable; import org.eclipse.jetty.util.Callback; -import org.eclipse.jetty.util.MathUtils; import org.eclipse.jetty.util.Promise; import org.eclipse.jetty.util.component.Dumpable; import org.eclipse.jetty.util.thread.AutoLock; -import org.eclipse.jetty.util.thread.Scheduler; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class HTTP2Stream implements IStream, Callback, Dumpable, CyclicTimeouts.Expirable +public class HTTP2Stream implements Stream, Attachable, Closeable, Callback, Dumpable, CyclicTimeouts.Expirable { private static final Logger LOG = LoggerFactory.getLogger(HTTP2Stream.class); private final AutoLock lock = new AutoLock(); - private Deque dataQueue; + private Deque dataQueue; private final AtomicReference attachment = new AtomicReference<>(); private final AtomicReference> attributes = new AtomicReference<>(); private final AtomicReference closeState = new AtomicReference<>(CloseState.NOT_CLOSED); private final AtomicInteger sendWindow = new AtomicInteger(); private final AtomicInteger recvWindow = new AtomicInteger(); private final long timeStamp = System.nanoTime(); - private final ISession session; + private final HTTP2Session session; private final int streamId; private final MetaData.Request request; private final boolean local; @@ -73,27 +76,21 @@ public class HTTP2Stream implements IStream, Callback, Dumpable, CyclicTimeouts. private boolean remoteReset; private Listener listener; private long dataLength; - private long dataDemand; - private boolean dataInitial; - private boolean dataProcess; + private boolean dataEOF; + private boolean dataDemand; + private boolean dataStalled; private boolean committed; private long idleTimeout; private long expireNanoTime = Long.MAX_VALUE; - public HTTP2Stream(ISession session, int streamId, MetaData.Request request, boolean local) + public HTTP2Stream(HTTP2Session session, int streamId, MetaData.Request request, boolean local) { this.session = session; this.streamId = streamId; this.request = request; this.local = local; this.dataLength = Long.MIN_VALUE; - this.dataInitial = true; - } - - @Deprecated - public HTTP2Stream(Scheduler scheduler, ISession session, int streamId, MetaData.Request request, boolean local) - { - this(session, streamId, request, local); + this.dataStalled = true; } @Override @@ -121,7 +118,7 @@ public class HTTP2Stream implements IStream, Callback, Dumpable, CyclicTimeouts. } @Override - public ISession getSession() + public HTTP2Session getSession() { return session; } @@ -132,7 +129,6 @@ public class HTTP2Stream implements IStream, Callback, Dumpable, CyclicTimeouts. send(new FrameList(frame), callback); } - @Override public void send(FrameList frameList, Callback callback) { if (startWrite(callback)) @@ -149,14 +145,14 @@ public class HTTP2Stream implements IStream, Callback, Dumpable, CyclicTimeouts. public void data(DataFrame frame, Callback callback) { if (startWrite(callback)) - session.data(this, this, frame); + session.data(this, frame, this); } @Override public void reset(ResetFrame frame, Callback callback) { Throwable resetFailure = null; - try (AutoLock l = lock.lock()) + try (AutoLock ignored = lock.lock()) { if (isReset()) { @@ -171,13 +167,13 @@ public class HTTP2Stream implements IStream, Callback, Dumpable, CyclicTimeouts. if (resetFailure != null) callback.failed(resetFailure); else - ((HTTP2Session)session).reset(this, frame, callback); + session.reset(this, frame, callback); } private boolean startWrite(Callback callback) { Throwable failure; - try (AutoLock l = lock.lock()) + try (AutoLock ignored = lock.lock()) { failure = this.failure; if (failure == null && sendCallback == null) @@ -213,7 +209,7 @@ public class HTTP2Stream implements IStream, Callback, Dumpable, CyclicTimeouts. @Override public boolean isReset() { - try (AutoLock l = lock.lock()) + try (AutoLock ignored = lock.lock()) { return localReset || remoteReset; } @@ -221,16 +217,15 @@ public class HTTP2Stream implements IStream, Callback, Dumpable, CyclicTimeouts. private boolean isFailed() { - try (AutoLock l = lock.lock()) + try (AutoLock ignored = lock.lock()) { return failure != null; } } - @Override public boolean isResetOrFailed() { - try (AutoLock l = lock.lock()) + try (AutoLock ignored = lock.lock()) { return isReset() || isFailed(); } @@ -249,39 +244,16 @@ public class HTTP2Stream implements IStream, Callback, Dumpable, CyclicTimeouts. return state == CloseState.REMOTELY_CLOSED || state == CloseState.CLOSING || state == CloseState.CLOSED; } - @Override - public boolean failAllData(Throwable x) - { - Deque copy; - try (AutoLock l = lock.lock()) - { - dataDemand = 0; - copy = dataQueue; - dataQueue = null; - } - DataEntry lastDataEntry = null; - if (copy != null) - { - copy.forEach(dataEntry -> dataEntry.callback.failed(x)); - lastDataEntry = copy.isEmpty() ? null : copy.peekLast(); - } - if (lastDataEntry == null) - return isRemotelyClosed(); - return lastDataEntry.frame.isEndStream(); - } - public boolean isLocallyClosed() { return closeState.get() == CloseState.LOCALLY_CLOSED; } - @Override public void commit() { committed = true; } - @Override public boolean isCommitted() { return committed; @@ -292,7 +264,6 @@ public class HTTP2Stream implements IStream, Callback, Dumpable, CyclicTimeouts. return !isClosed(); } - @Override public void notIdle() { long idleTimeout = getIdleTimeout(); @@ -317,7 +288,7 @@ public class HTTP2Stream implements IStream, Callback, Dumpable, CyclicTimeouts. { this.idleTimeout = idleTimeout; notIdle(); - ((HTTP2Session)session).scheduleTimeout(this); + session.scheduleTimeout(this); } protected void onIdleExpired(TimeoutException timeout) @@ -356,73 +327,53 @@ public class HTTP2Stream implements IStream, Callback, Dumpable, CyclicTimeouts. @Override public Data readData() { - DataEntry dataEntry; - try (AutoLock l = lock.lock()) + Data data; + try (AutoLock ignored = lock.lock()) { if (dataQueue == null || dataQueue.isEmpty()) - return null; - dataEntry = dataQueue.poll(); + { + if (dataEOF) + return Data.eof(getId()); + else + return null; + } + else + { + data = dataQueue.poll(); + dataEOF = data.frame().isEndStream(); + } } - DataFrame frame = dataEntry.frame; - if (updateClose(frame.isEndStream(), CloseState.Event.RECEIVED)) + if (updateClose(data.frame().isEndStream(), CloseState.Event.RECEIVED)) session.removeStream(this); - return new Data(frame, () -> dataEntry.callback.succeeded()); + return data; } - @Override public void setListener(Listener listener) { this.listener = listener; } - @Override public void process(Frame frame, Callback callback) { notIdle(); switch (frame.getType()) { - case PREFACE: - { - onNewStream(callback); - break; - } - case HEADERS: - { - onHeaders((HeadersFrame)frame, callback); - break; - } - case DATA: - { - onData((DataFrame)frame, callback); - break; - } - case RST_STREAM: - { - onReset((ResetFrame)frame, callback); - break; - } - case PUSH_PROMISE: - { - onPush((PushPromiseFrame)frame, callback); - break; - } - case WINDOW_UPDATE: - { - onWindowUpdate((WindowUpdateFrame)frame, callback); - break; - } - case FAILURE: - { - onFailure((FailureFrame)frame, callback); - break; - } - default: - { - throw new UnsupportedOperationException(); - } + case PREFACE -> onNewStream(callback); + case HEADERS -> onHeaders((HeadersFrame)frame, callback); + case RST_STREAM -> onReset((ResetFrame)frame, callback); + case PUSH_PROMISE -> onPush((PushPromiseFrame)frame, callback); + case WINDOW_UPDATE -> onWindowUpdate((WindowUpdateFrame)frame, callback); + case FAILURE -> onFailure((FailureFrame)frame, callback); + default -> throw new UnsupportedOperationException(); } } + public void process(Data data) + { + notIdle(); + onData(data); + } + private void onNewStream(Callback callback) { notifyNewStream(this); @@ -440,120 +391,105 @@ public class HTTP2Stream implements IStream, Callback, Dumpable, CyclicTimeouts. length = fields.getLongField(HttpHeader.CONTENT_LENGTH); dataLength = length >= 0 ? length : Long.MIN_VALUE; } + // Set EOF for either the request, the response or the trailers. + try (AutoLock ignored = lock.lock()) + { + dataEOF = frame.isEndStream(); + } callback.succeeded(); } - private void onData(DataFrame frame, Callback callback) + private void onData(Data data) { // SPEC: remotely closed streams must be replied with a reset. if (isRemotelyClosed()) { + if (LOG.isDebugEnabled()) + LOG.debug("Data {} for already closed {}", data, this); + data.release(); reset(new ResetFrame(streamId, ErrorCode.STREAM_CLOSED_ERROR.code), Callback.NOOP); - callback.failed(new EOFException("stream_closed")); return; } if (isReset()) { // Just drop the frame. - callback.failed(new IOException("stream_reset")); + if (LOG.isDebugEnabled()) + LOG.debug("Data {} for already reset {}", data, this); + data.release(); return; } if (dataLength != Long.MIN_VALUE) { + DataFrame frame = data.frame(); dataLength -= frame.remaining(); if (dataLength < 0 || (frame.isEndStream() && dataLength != 0)) { + if (LOG.isDebugEnabled()) + LOG.debug("Invalid data length {} for {}", data, this); + data.release(); reset(new ResetFrame(streamId, ErrorCode.PROTOCOL_ERROR.code), Callback.NOOP); - callback.failed(new IOException("invalid_data_length")); return; } } - boolean initial; - boolean proceed = false; - DataEntry entry = new DataEntry(frame, callback); - try (AutoLock l = lock.lock()) + boolean process; + try (AutoLock ignored = lock.lock()) { if (dataQueue == null) dataQueue = new ArrayDeque<>(); - dataQueue.offer(entry); - initial = dataInitial; - if (initial) - { - dataInitial = false; - // Fake that we are processing data so we return - // from onBeforeData() before calling onData(). - dataProcess = true; - } - else if (!dataProcess) - { - dataProcess = proceed = dataDemand > 0; - } - } - if (initial) - { - if (LOG.isDebugEnabled()) - LOG.debug("Starting data processing of {} for {}", frame, this); - notifyBeforeData(this); - try (AutoLock l = lock.lock()) - { - dataProcess = proceed = dataDemand > 0; - } + process = dataQueue.isEmpty() && dataDemand; + dataQueue.offer(data); } if (LOG.isDebugEnabled()) - LOG.debug("{} data processing of {} for {}", proceed ? "Proceeding" : "Stalling", frame, this); - if (proceed) + LOG.debug("Data {} notifying onDataAvailable() {} for {}", data, process, this); + if (process) processData(); } @Override - public void demand(long n) + public void demand() { - if (n <= 0) - throw new IllegalArgumentException("Invalid demand " + n); - long demand; - boolean proceed = false; - try (AutoLock l = lock.lock()) + boolean process = false; + try (AutoLock ignored = lock.lock()) { - demand = dataDemand = MathUtils.cappedAdd(dataDemand, n); - if (!dataProcess) - dataProcess = proceed = dataQueue != null && !dataQueue.isEmpty(); + dataDemand = true; + if (dataStalled && dataQueue != null && !dataQueue.isEmpty()) + { + dataStalled = false; + process = true; + } } if (LOG.isDebugEnabled()) - LOG.debug("Demand {}/{}, {} data processing for {}", n, demand, proceed ? "proceeding" : "stalling", this); - if (proceed) + LOG.debug("Demand, {} data processing for {}", process ? "proceeding" : "stalling", this); + if (process) processData(); } - private void processData() + public void processData() { while (true) { - DataEntry dataEntry; - try (AutoLock l = lock.lock()) + try (AutoLock ignored = lock.lock()) { - if (dataQueue == null || dataQueue.isEmpty() || dataDemand == 0) + if (dataQueue == null || dataQueue.isEmpty() || !dataDemand) { if (LOG.isDebugEnabled()) LOG.debug("Stalling data processing for {}", this); - dataProcess = false; + dataStalled = true; return; } - --dataDemand; - dataEntry = dataQueue.poll(); + dataDemand = false; + dataStalled = false; } - DataFrame frame = dataEntry.frame; - if (updateClose(frame.isEndStream(), CloseState.Event.RECEIVED)) - session.removeStream(this); - notifyDataDemanded(this, frame, dataEntry.callback); + notifyDataAvailable(this); } } - private long demand() + private boolean hasDemand() { - try (AutoLock l = lock.lock()) + try (AutoLock ignored = lock.lock()) { return dataDemand; } @@ -561,7 +497,7 @@ public class HTTP2Stream implements IStream, Callback, Dumpable, CyclicTimeouts. private void onReset(ResetFrame frame, Callback callback) { - try (AutoLock l = lock.lock()) + try (AutoLock ignored = lock.lock()) { remoteReset = true; failure = new EofException("reset"); @@ -588,7 +524,7 @@ public class HTTP2Stream implements IStream, Callback, Dumpable, CyclicTimeouts. private void onFailure(FailureFrame frame, Callback callback) { - try (AutoLock l = lock.lock()) + try (AutoLock ignored = lock.lock()) { failure = frame.getFailure(); } @@ -599,7 +535,6 @@ public class HTTP2Stream implements IStream, Callback, Dumpable, CyclicTimeouts. callback.succeeded(); } - @Override public boolean updateClose(boolean update, CloseState.Event event) { if (LOG.isDebugEnabled()) @@ -608,17 +543,12 @@ public class HTTP2Stream implements IStream, Callback, Dumpable, CyclicTimeouts. if (!update) return false; - switch (event) + return switch (event) { - case RECEIVED: - return updateCloseAfterReceived(); - case BEFORE_SEND: - return updateCloseBeforeSend(); - case AFTER_SEND: - return updateCloseAfterSend(); - default: - return false; - } + case RECEIVED -> updateCloseAfterReceived(); + case BEFORE_SEND -> updateCloseBeforeSend(); + case AFTER_SEND -> updateCloseAfterSend(); + }; } private boolean updateCloseAfterReceived() @@ -628,27 +558,25 @@ public class HTTP2Stream implements IStream, Callback, Dumpable, CyclicTimeouts. CloseState current = closeState.get(); switch (current) { - case NOT_CLOSED: + case NOT_CLOSED -> { if (closeState.compareAndSet(current, CloseState.REMOTELY_CLOSED)) return false; - break; } - case LOCALLY_CLOSING: + case LOCALLY_CLOSING -> { if (closeState.compareAndSet(current, CloseState.CLOSING)) { updateStreamCount(0, 1); return false; } - break; } - case LOCALLY_CLOSED: + case LOCALLY_CLOSED -> { close(); return true; } - default: + default -> { return false; } @@ -663,22 +591,20 @@ public class HTTP2Stream implements IStream, Callback, Dumpable, CyclicTimeouts. CloseState current = closeState.get(); switch (current) { - case NOT_CLOSED: + case NOT_CLOSED -> { if (closeState.compareAndSet(current, CloseState.LOCALLY_CLOSING)) return false; - break; } - case REMOTELY_CLOSED: + case REMOTELY_CLOSED -> { if (closeState.compareAndSet(current, CloseState.CLOSING)) { updateStreamCount(0, 1); return false; } - break; } - default: + default -> { return false; } @@ -693,20 +619,17 @@ public class HTTP2Stream implements IStream, Callback, Dumpable, CyclicTimeouts. CloseState current = closeState.get(); switch (current) { - case NOT_CLOSED: - case LOCALLY_CLOSING: + case NOT_CLOSED, LOCALLY_CLOSING -> { if (closeState.compareAndSet(current, CloseState.LOCALLY_CLOSED)) return false; - break; } - case REMOTELY_CLOSED: - case CLOSING: + case REMOTELY_CLOSED, CLOSING -> { close(); return true; } - default: + default -> { return false; } @@ -724,13 +647,11 @@ public class HTTP2Stream implements IStream, Callback, Dumpable, CyclicTimeouts. return recvWindow.get(); } - @Override public int updateSendWindow(int delta) { return sendWindow.getAndAdd(delta); } - @Override public int updateRecvWindow(int delta) { return recvWindow.getAndAdd(delta); @@ -755,7 +676,7 @@ public class HTTP2Stream implements IStream, Callback, Dumpable, CyclicTimeouts. private void updateStreamCount(int deltaStream, int deltaClosing) { - ((HTTP2Session)session).updateStreamCount(isLocal(), deltaStream, deltaClosing); + session.updateStreamCount(isLocal(), deltaStream, deltaClosing); } @Override @@ -785,7 +706,7 @@ public class HTTP2Stream implements IStream, Callback, Dumpable, CyclicTimeouts. private Callback endWrite() { - try (AutoLock l = lock.lock()) + try (AutoLock ignored = lock.lock()) { Callback callback = sendCallback; sendCallback = null; @@ -809,14 +730,14 @@ public class HTTP2Stream implements IStream, Callback, Dumpable, CyclicTimeouts. } } - private void notifyBeforeData(Stream stream) + private void notifyDataAvailable(Stream stream) { Listener listener = this.listener; if (listener != null) { try { - listener.onBeforeData(stream); + listener.onDataAvailable(stream); } catch (Throwable x) { @@ -825,29 +746,10 @@ public class HTTP2Stream implements IStream, Callback, Dumpable, CyclicTimeouts. } else { - stream.demand(1); - } - } - - private void notifyDataDemanded(Stream stream, DataFrame frame, Callback callback) - { - Listener listener = this.listener; - if (listener != null) - { - try - { - listener.onDataDemanded(stream, frame, callback); - } - catch (Throwable x) - { - LOG.info("Failure while notifying listener {}", listener, x); - callback.failed(x); - } - } - else - { - callback.succeeded(); - stream.demand(1); + Data data = readData(); + if (data != null) + data.release(); + stream.demand(); } } @@ -939,14 +841,14 @@ public class HTTP2Stream implements IStream, Callback, Dumpable, CyclicTimeouts. @Override public String toString() { - return String.format("%s@%x#%d@%x{sendWindow=%s,recvWindow=%s,demand=%d,reset=%b/%b,%s,age=%d,attachment=%s}", + return String.format("%s@%x#%d@%x{sendWindow=%s,recvWindow=%s,demand=%b,reset=%b/%b,%s,age=%d,attachment=%s}", getClass().getSimpleName(), hashCode(), getId(), session.hashCode(), sendWindow, recvWindow, - demand(), + hasDemand(), localReset, remoteReset, closeState, @@ -954,15 +856,62 @@ public class HTTP2Stream implements IStream, Callback, Dumpable, CyclicTimeouts. attachment); } - private static class DataEntry + /** + *

An ordered list of frames belonging to the same stream.

+ */ + public static class FrameList { - private final DataFrame frame; - private final Callback callback; + private final List frames; - private DataEntry(DataFrame frame, Callback callback) + /** + *

Creates a frame list of just the given HEADERS frame.

+ * + * @param headers the HEADERS frame + */ + public FrameList(HeadersFrame headers) { - this.frame = frame; - this.callback = callback; + Objects.requireNonNull(headers); + this.frames = List.of(headers); + } + + /** + *

Creates a frame list of the given frames.

+ * + * @param headers the HEADERS frame for the headers + * @param data the DATA frame for the content, or null if there is no content + * @param trailers the HEADERS frame for the trailers, or null if there are no trailers + */ + public FrameList(HeadersFrame headers, DataFrame data, HeadersFrame trailers) + { + Objects.requireNonNull(headers); + ArrayList frames = new ArrayList<>(3); + int streamId = headers.getStreamId(); + if (data != null && data.getStreamId() != streamId) + throw new IllegalArgumentException("Invalid stream ID for DATA frame " + data); + if (trailers != null && trailers.getStreamId() != streamId) + throw new IllegalArgumentException("Invalid stream ID for HEADERS frame " + trailers); + frames.add(headers); + if (data != null) + frames.add(data); + if (trailers != null) + frames.add(trailers); + this.frames = Collections.unmodifiableList(frames); + } + + /** + * @return the stream ID of the frames in this list + */ + public int getStreamId() + { + return frames.get(0).getStreamId(); + } + + /** + * @return a List of non-null frames + */ + public List getFrames() + { + return frames; } } } diff --git a/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/HTTP2StreamEndPoint.java b/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/HTTP2StreamEndPoint.java index 1b43a8dc3a8..4fd0d8c7773 100644 --- a/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/HTTP2StreamEndPoint.java +++ b/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/HTTP2StreamEndPoint.java @@ -20,19 +20,17 @@ import java.nio.BufferOverflowException; import java.nio.ByteBuffer; import java.nio.channels.ReadPendingException; import java.nio.channels.WritePendingException; -import java.util.ArrayDeque; -import java.util.Deque; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; -import org.eclipse.jetty.http2.IStream; +import org.eclipse.jetty.http2.api.Stream; import org.eclipse.jetty.http2.frames.DataFrame; import org.eclipse.jetty.io.Connection; import org.eclipse.jetty.io.EndPoint; import org.eclipse.jetty.io.EofException; import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.Callback; -import org.eclipse.jetty.util.thread.AutoLock; +import org.eclipse.jetty.util.IO; import org.eclipse.jetty.util.thread.Invocable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -41,17 +39,17 @@ public abstract class HTTP2StreamEndPoint implements EndPoint { private static final Logger LOG = LoggerFactory.getLogger(HTTP2StreamEndPoint.class); - private final AutoLock lock = new AutoLock(); - private final Deque dataQueue = new ArrayDeque<>(); private final AtomicReference writeState = new AtomicReference<>(WriteState.IDLE); private final AtomicReference readCallback = new AtomicReference<>(); private final long created = System.currentTimeMillis(); + private final HTTP2Stream stream; private final AtomicBoolean eof = new AtomicBoolean(); private final AtomicBoolean closed = new AtomicBoolean(); - private final IStream stream; + private final AtomicReference failure = new AtomicReference<>(); + private Stream.Data data; private Connection connection; - public HTTP2StreamEndPoint(IStream stream) + public HTTP2StreamEndPoint(HTTP2Stream stream) { this.stream = stream; } @@ -188,31 +186,31 @@ public abstract class HTTP2StreamEndPoint implements EndPoint @Override public int fill(ByteBuffer sink) throws IOException { - Entry entry; - try (AutoLock l = lock.lock()) - { - entry = dataQueue.poll(); - } + if (data != null) + return fillFromData(sink); + + Throwable failure = this.failure.get(); + if (failure != null) + throw IO.rethrow(failure); + + if (eof.get()) + return -1; + + Stream.Data data = this.data = stream.readData(); if (LOG.isDebugEnabled()) - LOG.debug("filled {} on {}", entry, this); + LOG.debug("filled {} on {}", data, this); - if (entry == null) + if (data == null) return 0; - if (entry.isEOF()) - { - entry.succeed(); - return shutdownInput(); - } - IOException failure = entry.ioFailure(); - if (failure != null) - { - entry.fail(failure); - throw failure; - } + return fillFromData(sink); + } + + private int fillFromData(ByteBuffer sink) + { int sinkPosition = BufferUtil.flipToFill(sink); - ByteBuffer source = entry.buffer; + ByteBuffer source = data.frame().getData(); int sourceLength = source.remaining(); int length = Math.min(sourceLength, sink.remaining()); int sourceLimit = source.limit(); @@ -221,27 +219,15 @@ public abstract class HTTP2StreamEndPoint implements EndPoint source.limit(sourceLimit); BufferUtil.flipToFlush(sink, sinkPosition); - if (source.hasRemaining()) + if (!source.hasRemaining()) { - try (AutoLock l = lock.lock()) - { - dataQueue.offerFirst(entry); - } + eof.set(data.frame().isEndStream()); + data.release(); + data = null; + stream.demand(); } - 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; - } - private int shutdownInput() - { - eof.set(true); - return -1; + return length; } @Override @@ -394,24 +380,19 @@ public abstract class HTTP2StreamEndPoint implements EndPoint WriteState current = writeState.get(); switch (current.state) { - case IDLE: + case IDLE -> + { if (!writeState.compareAndSet(current, WriteState.PENDING)) - break; + continue; // TODO: we really need a Stream primitive to write multiple frames. ByteBuffer result = coalesce(buffers, false); stream.data(new DataFrame(stream.getId(), result, false), Callback.from(() -> writeSuccess(callback), x -> writeFailure(x, callback))); - return; - case PENDING: - callback.failed(new WritePendingException()); - return; - case OSHUTTING: - case OSHUT: - callback.failed(new EofException("Output shutdown")); - return; - case FAILED: - callback.failed(current.failure); - return; + } + case PENDING -> callback.failed(new WritePendingException()); + case OSHUTTING, OSHUT -> callback.failed(new EofException("Output shutdown")); + case FAILED -> callback.failed(current.failure); } + return; } } } @@ -423,23 +404,21 @@ public abstract class HTTP2StreamEndPoint implements EndPoint WriteState current = writeState.get(); switch (current.state) { - case IDLE: - case OSHUT: - callback.failed(new IllegalStateException()); - return; - case PENDING: + case IDLE, OSHUT -> callback.failed(new IllegalStateException()); + case PENDING -> + { if (!writeState.compareAndSet(current, WriteState.IDLE)) - break; + continue; callback.succeeded(); - return; - case OSHUTTING: + } + case OSHUTTING -> + { callback.succeeded(); shutdownOutput(); - return; - case FAILED: - callback.failed(current.failure); - return; + } + case FAILED -> callback.failed(current.failure); } + return; } } @@ -539,54 +518,22 @@ public abstract class HTTP2StreamEndPoint implements EndPoint newConnection.onOpen(); } - protected void offerData(DataFrame frame, Callback callback) + protected void processDataAvailable() { - ByteBuffer buffer = frame.getData(); - if (LOG.isDebugEnabled()) - LOG.debug("offering {} on {}", frame, this); - if (frame.isEndStream()) - { - if (buffer.hasRemaining()) - offer(buffer, Callback.from(Callback.NOOP::succeeded, callback::failed), null); - offer(BufferUtil.EMPTY_BUFFER, callback, Entry.EOF); - } - else - { - if (buffer.hasRemaining()) - offer(buffer, callback, null); - else - callback.succeeded(); - } process(); } - protected void offerFailure(Throwable failure) + protected void processFailure(Throwable failure) { - offer(BufferUtil.EMPTY_BUFFER, Callback.NOOP, failure); - process(); + if (this.failure.compareAndSet(null, failure)) + process(); } - private void offer(ByteBuffer buffer, Callback callback, Throwable failure) + private void process() { - try (AutoLock l = lock.lock()) - { - dataQueue.offer(new Entry(buffer, callback, failure)); - } - } - - protected void process() - { - boolean empty; - try (AutoLock l = lock.lock()) - { - empty = dataQueue.isEmpty(); - } - if (!empty) - { - Callback callback = readCallback.getAndSet(null); - if (callback != null) - callback.succeeded(); - } + Callback callback = readCallback.getAndSet(null); + if (callback != null) + callback.succeeded(); } @Override @@ -599,51 +546,6 @@ public abstract class HTTP2StreamEndPoint implements EndPoint writeState); } - private static class Entry - { - private static final Throwable EOF = new Throwable(); - - private final ByteBuffer buffer; - private final Callback callback; - private final Throwable failure; - - private Entry(ByteBuffer buffer, Callback callback, Throwable failure) - { - this.buffer = buffer; - this.callback = callback; - this.failure = failure; - } - - private boolean isEOF() - { - return failure == EOF; - } - - private IOException ioFailure() - { - if (failure == null || isEOF()) - return null; - return failure instanceof IOException ? (IOException)failure : new IOException(failure); - } - - private void succeed() - { - callback.succeeded(); - } - - private void fail(Throwable failure) - { - callback.failed(failure); - } - - @Override - public String toString() - { - return String.format("%s@%x[b=%s,eof=%b,f=%s]", getClass().getSimpleName(), hashCode(), - BufferUtil.toDetailString(buffer), isEOF(), isEOF() ? null : failure); - } - } - private static class WriteState { public static final WriteState IDLE = new WriteState(State.IDLE); diff --git a/jetty-core/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/internal/ClientHTTP2StreamEndPoint.java b/jetty-core/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/internal/ClientHTTP2StreamEndPoint.java index 1692df2a5d7..56a241c70fd 100644 --- a/jetty-core/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/internal/ClientHTTP2StreamEndPoint.java +++ b/jetty-core/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/internal/ClientHTTP2StreamEndPoint.java @@ -13,9 +13,8 @@ package org.eclipse.jetty.http2.client.http.internal; -import org.eclipse.jetty.http2.IStream; -import org.eclipse.jetty.http2.frames.DataFrame; import org.eclipse.jetty.http2.internal.HTTP2Channel; +import org.eclipse.jetty.http2.internal.HTTP2Stream; import org.eclipse.jetty.http2.internal.HTTP2StreamEndPoint; import org.eclipse.jetty.io.Connection; import org.eclipse.jetty.util.Callback; @@ -26,15 +25,15 @@ public class ClientHTTP2StreamEndPoint extends HTTP2StreamEndPoint implements HT { private static final Logger LOG = LoggerFactory.getLogger(ClientHTTP2StreamEndPoint.class); - public ClientHTTP2StreamEndPoint(IStream stream) + public ClientHTTP2StreamEndPoint(HTTP2Stream stream) { super(stream); } @Override - public void onData(DataFrame frame, Callback callback) + public void onDataAvailable() { - offerData(frame, callback); + processDataAvailable(); } @Override diff --git a/jetty-core/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/internal/HTTPSessionListenerPromise.java b/jetty-core/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/internal/HTTPSessionListenerPromise.java index 1a906e9cbe5..e799ee538ba 100644 --- a/jetty-core/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/internal/HTTPSessionListenerPromise.java +++ b/jetty-core/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/internal/HTTPSessionListenerPromise.java @@ -25,9 +25,10 @@ import org.eclipse.jetty.http2.api.Session; import org.eclipse.jetty.http2.frames.GoAwayFrame; import org.eclipse.jetty.http2.frames.SettingsFrame; import org.eclipse.jetty.http2.internal.HTTP2Session; +import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.Promise; -public class HTTPSessionListenerPromise extends Session.Listener.Adapter implements Promise +public class HTTPSessionListenerPromise implements Session.Listener, Promise { private final AtomicMarkableReference connection = new AtomicMarkableReference<>(null, false); private final Map context; @@ -82,13 +83,15 @@ public class HTTPSessionListenerPromise extends Session.Listener.Adapter impleme } @Override - public void onClose(Session session, GoAwayFrame frame) + public void onClose(Session session, GoAwayFrame frame, Callback callback) { - if (failConnectionPromise(new ClosedChannelException())) - return; - HttpConnectionOverHTTP2 connection = this.connection.getReference(); - if (connection != null) - onClose(connection, frame); + if (!failConnectionPromise(new ClosedChannelException())) + { + HttpConnectionOverHTTP2 connection = this.connection.getReference(); + if (connection != null) + onClose(connection, frame); + } + callback.succeeded(); } public void onClose(HttpConnectionOverHTTP2 connection, GoAwayFrame frame) @@ -110,13 +113,15 @@ public class HTTPSessionListenerPromise extends Session.Listener.Adapter impleme } @Override - public void onFailure(Session session, Throwable failure) + public void onFailure(Session session, Throwable failure, Callback callback) { - if (failConnectionPromise(failure)) - return; - HttpConnectionOverHTTP2 connection = this.connection.getReference(); - if (connection != null) - connection.close(failure); + if (!failConnectionPromise(failure)) + { + HttpConnectionOverHTTP2 connection = this.connection.getReference(); + if (connection != null) + connection.close(failure); + } + callback.succeeded(); } private boolean failConnectionPromise(Throwable failure) diff --git a/jetty-core/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/internal/HttpChannelOverHTTP2.java b/jetty-core/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/internal/HttpChannelOverHTTP2.java index 9785262c536..9ec56da4f8d 100644 --- a/jetty-core/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/internal/HttpChannelOverHTTP2.java +++ b/jetty-core/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/internal/HttpChannelOverHTTP2.java @@ -19,15 +19,14 @@ import org.eclipse.jetty.client.HttpExchange; import org.eclipse.jetty.client.HttpReceiver; import org.eclipse.jetty.client.HttpSender; import org.eclipse.jetty.client.api.Result; -import org.eclipse.jetty.http2.IStream; import org.eclipse.jetty.http2.api.Session; import org.eclipse.jetty.http2.api.Stream; -import org.eclipse.jetty.http2.frames.DataFrame; import org.eclipse.jetty.http2.frames.HeadersFrame; import org.eclipse.jetty.http2.frames.PushPromiseFrame; import org.eclipse.jetty.http2.frames.ResetFrame; import org.eclipse.jetty.http2.internal.ErrorCode; import org.eclipse.jetty.http2.internal.HTTP2Channel; +import org.eclipse.jetty.http2.internal.HTTP2Stream; import org.eclipse.jetty.util.Callback; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -88,7 +87,7 @@ public class HttpChannelOverHTTP2 extends HttpChannel { this.stream = stream; if (stream != null) - ((IStream)stream).setAttachment(receiver); + ((HTTP2Stream)stream).setAttachment(receiver); } public boolean isFailed() @@ -186,30 +185,31 @@ public class HttpChannelOverHTTP2 extends HttpChannel } @Override - public void onData(Stream stream, DataFrame frame, Callback callback) + public void onDataAvailable(Stream stream) { - HTTP2Channel.Client channel = (HTTP2Channel.Client)((IStream)stream).getAttachment(); - channel.onData(frame, callback); + HTTP2Channel.Client channel = (HTTP2Channel.Client)((HTTP2Stream)stream).getAttachment(); + channel.onDataAvailable(); } @Override - public void onReset(Stream stream, ResetFrame frame) + public void onReset(Stream stream, ResetFrame frame, Callback callback) { // TODO: needs to call HTTP2Channel? - receiver.onReset(stream, frame); + receiver.onReset(frame); + callback.succeeded(); } @Override public boolean onIdleTimeout(Stream stream, Throwable x) { - HTTP2Channel.Client channel = (HTTP2Channel.Client)((IStream)stream).getAttachment(); + HTTP2Channel.Client channel = (HTTP2Channel.Client)((HTTP2Stream)stream).getAttachment(); return channel.onTimeout(x); } @Override public void onFailure(Stream stream, int error, String reason, Throwable failure, Callback callback) { - HTTP2Channel.Client channel = (HTTP2Channel.Client)((IStream)stream).getAttachment(); + HTTP2Channel.Client channel = (HTTP2Channel.Client)((HTTP2Stream)stream).getAttachment(); channel.onFailure(failure, callback); } } diff --git a/jetty-core/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/internal/HttpReceiverOverHTTP2.java b/jetty-core/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/internal/HttpReceiverOverHTTP2.java index 3cbdbebca76..aafb2aa44f6 100644 --- a/jetty-core/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/internal/HttpReceiverOverHTTP2.java +++ b/jetty-core/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/internal/HttpReceiverOverHTTP2.java @@ -15,9 +15,8 @@ package org.eclipse.jetty.http2.client.http.internal; import java.io.IOException; import java.nio.ByteBuffer; -import java.util.ArrayDeque; import java.util.List; -import java.util.Queue; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.BiFunction; import org.eclipse.jetty.client.HttpChannel; @@ -33,18 +32,16 @@ import org.eclipse.jetty.http.HttpField; import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.http.MetaData; -import org.eclipse.jetty.http2.IStream; import org.eclipse.jetty.http2.api.Stream; -import org.eclipse.jetty.http2.frames.DataFrame; import org.eclipse.jetty.http2.frames.HeadersFrame; import org.eclipse.jetty.http2.frames.PushPromiseFrame; import org.eclipse.jetty.http2.frames.ResetFrame; import org.eclipse.jetty.http2.internal.ErrorCode; import org.eclipse.jetty.http2.internal.HTTP2Channel; +import org.eclipse.jetty.http2.internal.HTTP2Stream; import org.eclipse.jetty.io.EndPoint; -import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.Callback; -import org.eclipse.jetty.util.thread.AutoLock; +import org.eclipse.jetty.util.thread.Invocable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -52,7 +49,7 @@ public class HttpReceiverOverHTTP2 extends HttpReceiver implements HTTP2Channel. { private static final Logger LOG = LoggerFactory.getLogger(HttpReceiverOverHTTP2.class); - private final ContentNotifier contentNotifier = new ContentNotifier(this); + private final AtomicBoolean notifySuccess = new AtomicBoolean(); public HttpReceiverOverHTTP2(HttpChannel channel) { @@ -68,81 +65,91 @@ public class HttpReceiverOverHTTP2 extends HttpReceiver implements HTTP2Channel. @Override protected void receive() { - contentNotifier.process(true); - } + // Called when the application resumes demand of content. + if (LOG.isDebugEnabled()) + LOG.debug("Resuming response processing on {}", this); - @Override - protected void reset() - { - super.reset(); - contentNotifier.reset(); + HttpExchange exchange = getHttpExchange(); + if (exchange == null) + return; + + if (notifySuccess.get()) + responseSuccess(exchange); + else + getHttpChannel().getStream().demand(); } void onHeaders(Stream stream, HeadersFrame frame) + { + MetaData metaData = frame.getMetaData(); + if (metaData.isResponse()) + onResponse(stream, frame); + else + onTrailer(frame); + } + + private void onResponse(Stream stream, HeadersFrame frame) { HttpExchange exchange = getHttpExchange(); if (exchange == null) return; + MetaData.Response response = (MetaData.Response)frame.getMetaData(); HttpResponse httpResponse = exchange.getResponse(); - MetaData metaData = frame.getMetaData(); - if (metaData.isResponse()) + httpResponse.version(response.getHttpVersion()).status(response.getStatus()).reason(response.getReason()); + + if (responseBegin(exchange)) { - MetaData.Response response = (MetaData.Response)frame.getMetaData(); - httpResponse.version(response.getHttpVersion()).status(response.getStatus()).reason(response.getReason()); - - if (responseBegin(exchange)) + HttpFields headers = response.getFields(); + for (HttpField header : headers) { - HttpFields headers = response.getFields(); - for (HttpField header : headers) - { - if (!responseHeader(exchange, header)) - return; - } + if (!responseHeader(exchange, header)) + return; + } - HttpRequest httpRequest = exchange.getRequest(); - if (MetaData.isTunnel(httpRequest.getMethod(), httpResponse.getStatus())) - { - ClientHTTP2StreamEndPoint endPoint = new ClientHTTP2StreamEndPoint((IStream)stream); - long idleTimeout = httpRequest.getIdleTimeout(); - if (idleTimeout > 0) - endPoint.setIdleTimeout(idleTimeout); - if (LOG.isDebugEnabled()) - LOG.debug("Successful HTTP2 tunnel on {} via {}", stream, endPoint); - ((IStream)stream).setAttachment(endPoint); - HttpConversation conversation = httpRequest.getConversation(); - conversation.setAttribute(EndPoint.class.getName(), endPoint); - HttpUpgrader upgrader = (HttpUpgrader)conversation.getAttribute(HttpUpgrader.class.getName()); - if (upgrader != null) - upgrade(upgrader, httpResponse, endPoint); - } + HttpRequest httpRequest = exchange.getRequest(); + if (MetaData.isTunnel(httpRequest.getMethod(), httpResponse.getStatus())) + { + ClientHTTP2StreamEndPoint endPoint = new ClientHTTP2StreamEndPoint((HTTP2Stream)stream); + long idleTimeout = httpRequest.getIdleTimeout(); + if (idleTimeout > 0) + endPoint.setIdleTimeout(idleTimeout); + if (LOG.isDebugEnabled()) + LOG.debug("Successful HTTP2 tunnel on {} via {}", stream, endPoint); + ((HTTP2Stream)stream).setAttachment(endPoint); + HttpConversation conversation = httpRequest.getConversation(); + conversation.setAttribute(EndPoint.class.getName(), endPoint); + HttpUpgrader upgrader = (HttpUpgrader)conversation.getAttribute(HttpUpgrader.class.getName()); + if (upgrader != null) + upgrade(upgrader, httpResponse, endPoint); + } - if (responseHeaders(exchange)) - { - int status = response.getStatus(); - if (frame.isEndStream() || HttpStatus.isInterim(status)) - responseSuccess(exchange); - } + notifySuccess.set(frame.isEndStream()); + if (responseHeaders(exchange)) + { + int status = response.getStatus(); + if (frame.isEndStream() || HttpStatus.isInterim(status)) + responseSuccess(exchange); else - { - if (frame.isEndStream()) - { - // There is no demand to trigger response success, so add - // a poison pill to trigger it when there will be demand. - notifyContent(exchange, new DataFrame(stream.getId(), BufferUtil.EMPTY_BUFFER, true), Callback.NOOP); - } - } + stream.demand(); + } + else + { + if (LOG.isDebugEnabled()) + LOG.debug("Stalling response processing, no demand after headers on {}", this); } } - else // Response trailers. - { - HttpFields trailers = metaData.getFields(); - trailers.forEach(httpResponse::trailer); - // Previous DataFrames had endStream=false, so - // add a poison pill to trigger response success - // after all normal DataFrames have been consumed. - notifyContent(exchange, new DataFrame(stream.getId(), BufferUtil.EMPTY_BUFFER, true), Callback.NOOP); - } + } + + private void onTrailer(HeadersFrame frame) + { + HttpExchange exchange = getHttpExchange(); + if (exchange == null) + return; + + HttpFields trailers = frame.getMetaData().getFields(); + trailers.forEach(exchange.getResponse()::trailer); + responseSuccess(exchange); } private void upgrade(HttpUpgrader upgrader, HttpResponse response, EndPoint endPoint) @@ -190,20 +197,59 @@ public class HttpReceiverOverHTTP2 extends HttpReceiver implements HTTP2Channel. } @Override - public void onData(DataFrame frame, Callback callback) + public void onDataAvailable() { HttpExchange exchange = getHttpExchange(); if (exchange == null) + return; + + Stream stream = getHttpChannel().getStream(); + if (stream == null) + return; + + Stream.Data data = stream.readData(); + if (data != null) { - callback.failed(new IOException("terminated")); + ByteBuffer byteBuffer = data.frame().getData(); + if (byteBuffer.hasRemaining()) + { + notifySuccess.set(data.frame().isEndStream()); + Callback callback = Callback.from(Invocable.InvocationType.NON_BLOCKING, data::release, x -> + { + data.release(); + if (responseFailure(x)) + stream.reset(new ResetFrame(stream.getId(), ErrorCode.CANCEL_STREAM_ERROR.code), Callback.NOOP); + }); + boolean proceed = responseContent(exchange, byteBuffer, callback); + if (proceed) + { + if (data.frame().isEndStream()) + responseSuccess(exchange); + else + stream.demand(); + } + else + { + if (LOG.isDebugEnabled()) + LOG.debug("Stalling response processing, no demand after {} on {}", data, this); + } + } + else + { + data.release(); + if (data.frame().isEndStream()) + responseSuccess(exchange); + else + stream.demand(); + } } else { - notifyContent(exchange, frame, callback); + stream.demand(); } } - void onReset(Stream stream, ResetFrame frame) + void onReset(ResetFrame frame) { HttpExchange exchange = getHttpExchange(); if (exchange == null) @@ -227,201 +273,4 @@ public class HttpReceiverOverHTTP2 extends HttpReceiver implements HTTP2Channel. responseFailure(failure); callback.succeeded(); } - - private void notifyContent(HttpExchange exchange, DataFrame frame, Callback callback) - { - contentNotifier.offer(exchange, frame, callback); - } - - private class ContentNotifier - { - private final AutoLock lock = new AutoLock(); - private final Queue queue = new ArrayDeque<>(); - private final HttpReceiverOverHTTP2 receiver; - private DataInfo dataInfo; - private boolean active; - private boolean resume; - private boolean stalled; - - private ContentNotifier(HttpReceiverOverHTTP2 receiver) - { - this.receiver = receiver; - } - - private void offer(HttpExchange exchange, DataFrame frame, Callback callback) - { - DataInfo dataInfo = new DataInfo(exchange, frame, callback); - if (LOG.isDebugEnabled()) - LOG.debug("Queueing content {}", dataInfo); - enqueue(dataInfo); - process(false); - } - - private void enqueue(DataInfo dataInfo) - { - try (AutoLock l = lock.lock()) - { - queue.offer(dataInfo); - } - } - - private void process(boolean resume) - { - // Allow only one thread at a time. - boolean busy = active(resume); - if (LOG.isDebugEnabled()) - LOG.debug("Resuming({}) processing({}) of content", resume, !busy); - if (busy) - return; - - // Process only if there is demand. - try (AutoLock l = lock.lock()) - { - if (!resume && demand() <= 0) - { - if (LOG.isDebugEnabled()) - LOG.debug("Stalling processing, content available but no demand"); - active = false; - stalled = true; - return; - } - } - - while (true) - { - if (dataInfo != null) - { - if (dataInfo.frame.isEndStream()) - { - receiver.responseSuccess(dataInfo.exchange); - // Return even if active, as reset() will be called later. - return; - } - } - - try (AutoLock l = lock.lock()) - { - dataInfo = queue.poll(); - if (LOG.isDebugEnabled()) - LOG.debug("Processing content {}", dataInfo); - if (dataInfo == null) - { - active = false; - return; - } - } - - ByteBuffer buffer = dataInfo.frame.getData(); - Callback callback = dataInfo.callback; - if (buffer.hasRemaining()) - { - boolean proceed = receiver.responseContent(dataInfo.exchange, buffer, Callback.from(callback::succeeded, x -> fail(callback, x))); - if (!proceed) - { - // The call to responseContent() said we should - // stall, but another thread may have just resumed. - boolean stall = stall(); - if (LOG.isDebugEnabled()) - LOG.debug("Stalling({}) processing", stall); - if (stall) - return; - } - } - else - { - callback.succeeded(); - } - } - } - - private boolean active(boolean resume) - { - try (AutoLock l = lock.lock()) - { - if (active) - { - // There is a thread in process(), - // but it may be about to exit, so - // remember "resume" to signal the - // processing thread to continue. - if (resume) - this.resume = true; - return true; - } - - // If there is no demand (i.e. stalled - // and not resuming) then don't process. - if (stalled && !resume) - return true; - - // Start processing. - active = true; - stalled = false; - return false; - } - } - - /** - * Called when there is no demand, this method checks whether - * the processing should really stop or it should continue. - * - * @return true to stop processing, false to continue processing - */ - private boolean stall() - { - try (AutoLock l = lock.lock()) - { - if (resume) - { - // There was no demand, but another thread - // just demanded, continue processing. - resume = false; - return false; - } - - // There is no demand, stop processing. - active = false; - stalled = true; - return true; - } - } - - private void reset() - { - dataInfo = null; - try (AutoLock l = lock.lock()) - { - queue.clear(); - active = false; - resume = false; - stalled = false; - } - } - - private void fail(Callback callback, Throwable failure) - { - callback.failed(failure); - receiver.responseFailure(failure); - } - - private class DataInfo - { - private final HttpExchange exchange; - private final DataFrame frame; - private final Callback callback; - - private DataInfo(HttpExchange exchange, DataFrame frame, Callback callback) - { - this.exchange = exchange; - this.frame = frame; - this.callback = callback; - } - - @Override - public String toString() - { - return String.format("%s@%x[%s]", getClass().getSimpleName(), hashCode(), frame); - } - } - } } diff --git a/jetty-core/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/internal/HttpSenderOverHTTP2.java b/jetty-core/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/internal/HttpSenderOverHTTP2.java index 0f4adf2dc0f..e99defbc041 100644 --- a/jetty-core/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/internal/HttpSenderOverHTTP2.java +++ b/jetty-core/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/internal/HttpSenderOverHTTP2.java @@ -25,11 +25,11 @@ import org.eclipse.jetty.http.HttpMethod; import org.eclipse.jetty.http.HttpURI; import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.http.MetaData; -import org.eclipse.jetty.http2.ISession; -import org.eclipse.jetty.http2.IStream; import org.eclipse.jetty.http2.api.Stream; import org.eclipse.jetty.http2.frames.DataFrame; import org.eclipse.jetty.http2.frames.HeadersFrame; +import org.eclipse.jetty.http2.internal.HTTP2Session; +import org.eclipse.jetty.http2.internal.HTTP2Stream; import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.Promise; @@ -123,8 +123,8 @@ public class HttpSenderOverHTTP2 extends HttpSender } HttpChannelOverHTTP2 channel = getHttpChannel(); - IStream.FrameList frameList = new IStream.FrameList(headersFrame, dataFrame, trailersFrame); - ((ISession)channel.getSession()).newStream(frameList, new HeadersPromise(request, callback), channel.getStreamListener()); + HTTP2Stream.FrameList frameList = new HTTP2Stream.FrameList(headersFrame, dataFrame, trailersFrame); + ((HTTP2Session)channel.getSession()).newStream(frameList, new HeadersPromise(request, callback), channel.getStreamListener()); } private HttpFields retrieveTrailers(HttpRequest request) diff --git a/jetty-core/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/AbstractHTTP2ServerConnectionFactory.java b/jetty-core/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/AbstractHTTP2ServerConnectionFactory.java index f55be43d8dc..b49b9cccaed 100644 --- a/jetty-core/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/AbstractHTTP2ServerConnectionFactory.java +++ b/jetty-core/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/AbstractHTTP2ServerConnectionFactory.java @@ -25,7 +25,6 @@ import java.util.concurrent.atomic.AtomicReference; import org.eclipse.jetty.http2.BufferingFlowControlStrategy; import org.eclipse.jetty.http2.FlowControlStrategy; -import org.eclipse.jetty.http2.ISession; import org.eclipse.jetty.http2.RateControl; import org.eclipse.jetty.http2.WindowRateControl; import org.eclipse.jetty.http2.api.Session; @@ -33,6 +32,7 @@ import org.eclipse.jetty.http2.api.server.ServerSessionListener; import org.eclipse.jetty.http2.frames.Frame; import org.eclipse.jetty.http2.frames.SettingsFrame; import org.eclipse.jetty.http2.internal.HTTP2Connection; +import org.eclipse.jetty.http2.internal.HTTP2Session; import org.eclipse.jetty.http2.internal.generator.Generator; import org.eclipse.jetty.http2.internal.parser.ServerParser; import org.eclipse.jetty.http2.server.internal.HTTP2ServerConnection; @@ -311,13 +311,13 @@ public abstract class AbstractHTTP2ServerConnectionFactory extends AbstractConne @ManagedObject("The container of HTTP/2 sessions") public static class HTTP2SessionContainer implements Connection.Listener, Graceful, Dumpable { - private final Set sessions = ConcurrentHashMap.newKeySet(); + private final Set sessions = ConcurrentHashMap.newKeySet(); private final AtomicReference> shutdown = new AtomicReference<>(); @Override public void onOpened(Connection connection) { - ISession session = ((HTTP2Connection)connection).getSession(); + HTTP2Session session = ((HTTP2Connection)connection).getSession(); sessions.add(session); LifeCycle.start(session); if (isShutdown()) @@ -327,7 +327,7 @@ public abstract class AbstractHTTP2ServerConnectionFactory extends AbstractConne @Override public void onClosed(Connection connection) { - ISession session = ((HTTP2Connection)connection).getSession(); + HTTP2Session session = ((HTTP2Connection)connection).getSession(); if (sessions.remove(session)) LifeCycle.stop(session); } @@ -371,7 +371,7 @@ public abstract class AbstractHTTP2ServerConnectionFactory extends AbstractConne return shutdown.get() != null; } - private CompletableFuture shutdown(ISession session) + private CompletableFuture shutdown(HTTP2Session session) { return session.shutdown(); } diff --git a/jetty-core/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HTTP2ServerConnectionFactory.java b/jetty-core/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HTTP2ServerConnectionFactory.java index 725794b5744..15c45e8fb77 100644 --- a/jetty-core/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HTTP2ServerConnectionFactory.java +++ b/jetty-core/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HTTP2ServerConnectionFactory.java @@ -17,16 +17,15 @@ import java.util.Map; import java.util.concurrent.TimeoutException; import org.eclipse.jetty.http2.HTTP2Cipher; -import org.eclipse.jetty.http2.IStream; import org.eclipse.jetty.http2.api.Session; import org.eclipse.jetty.http2.api.Stream; import org.eclipse.jetty.http2.api.server.ServerSessionListener; -import org.eclipse.jetty.http2.frames.DataFrame; import org.eclipse.jetty.http2.frames.GoAwayFrame; import org.eclipse.jetty.http2.frames.HeadersFrame; import org.eclipse.jetty.http2.frames.PushPromiseFrame; import org.eclipse.jetty.http2.frames.ResetFrame; import org.eclipse.jetty.http2.internal.ErrorCode; +import org.eclipse.jetty.http2.internal.HTTP2Stream; import org.eclipse.jetty.http2.server.internal.HTTP2ServerConnection; import org.eclipse.jetty.io.EndPoint; import org.eclipse.jetty.io.EofException; @@ -70,7 +69,7 @@ public class HTTP2ServerConnectionFactory extends AbstractHTTP2ServerConnectionF return acceptable; } - protected class HTTPServerSessionListener extends ServerSessionListener.Adapter implements Stream.Listener + protected class HTTPServerSessionListener implements ServerSessionListener, Stream.Listener { private final EndPoint endPoint; @@ -93,26 +92,17 @@ public class HTTP2ServerConnectionFactory extends AbstractHTTP2ServerConnectionF @Override public Stream.Listener onNewStream(Stream stream, HeadersFrame frame) { - getConnection().onNewStream((IStream)stream, frame); - return this; - } - - @Override - public void onBeforeData(Stream stream) - { - // Do not notify DATA frame listeners until demanded. + getConnection().onNewStream((HTTP2Stream)stream, frame); + // Do not demand for DATA frames. // This allows CONNECT requests with pseudo header :protocol // (e.g. WebSocket over HTTP/2) to buffer DATA frames // until they upgrade and are ready to process them. + return this; } @Override public boolean onIdleTimeout(Session session) { - boolean close = super.onIdleTimeout(session); - if (!close) - return false; - long idleTimeout = getConnection().getEndPoint().getIdleTimeout(); return getConnection().onSessionTimeout(new TimeoutException("Session idle timeout " + idleTimeout + " ms")); } @@ -137,7 +127,7 @@ public class HTTP2ServerConnectionFactory extends AbstractHTTP2ServerConnectionF public void onHeaders(Stream stream, HeadersFrame frame) { if (frame.isEndStream()) - getConnection().onTrailers((IStream)stream, frame); + getConnection().onTrailers(stream, frame); else close(stream, "invalid_trailers"); } @@ -151,9 +141,9 @@ public class HTTP2ServerConnectionFactory extends AbstractHTTP2ServerConnectionF } @Override - public void onDataDemanded(Stream stream, DataFrame frame, Callback callback) + public void onDataAvailable(Stream stream) { - getConnection().onData((IStream)stream, frame, callback); + getConnection().onDataAvailable(stream); } @Override @@ -173,13 +163,13 @@ public class HTTP2ServerConnectionFactory extends AbstractHTTP2ServerConnectionF private void onFailure(Stream stream, Throwable failure, Callback callback) { - getConnection().onStreamFailure((IStream)stream, failure, callback); + getConnection().onStreamFailure(stream, failure, callback); } @Override public boolean onIdleTimeout(Stream stream, Throwable x) { - return getConnection().onStreamTimeout((IStream)stream, x); + return getConnection().onStreamTimeout(stream, x); } private void close(Stream stream, String reason) diff --git a/jetty-core/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/RawHTTP2ServerConnectionFactory.java b/jetty-core/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/RawHTTP2ServerConnectionFactory.java index 2939b750855..0a2994ab887 100644 --- a/jetty-core/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/RawHTTP2ServerConnectionFactory.java +++ b/jetty-core/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/RawHTTP2ServerConnectionFactory.java @@ -27,6 +27,7 @@ import org.eclipse.jetty.http2.frames.SettingsFrame; import org.eclipse.jetty.io.EndPoint; import org.eclipse.jetty.server.Connector; import org.eclipse.jetty.server.HttpConfiguration; +import org.eclipse.jetty.util.Callback; public class RawHTTP2ServerConnectionFactory extends AbstractHTTP2ServerConnectionFactory { @@ -111,9 +112,9 @@ public class RawHTTP2ServerConnectionFactory extends AbstractHTTP2ServerConnecti } @Override - public void onClose(Session session, GoAwayFrame frame) + public void onClose(Session session, GoAwayFrame frame, Callback callback) { - delegate.onClose(session, frame); + delegate.onClose(session, frame, callback); } @Override @@ -123,9 +124,9 @@ public class RawHTTP2ServerConnectionFactory extends AbstractHTTP2ServerConnecti } @Override - public void onFailure(Session session, Throwable failure) + public void onFailure(Session session, Throwable failure, Callback callback) { - delegate.onFailure(session, failure); + delegate.onFailure(session, failure, callback); } } } diff --git a/jetty-core/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/internal/HTTP2ServerConnection.java b/jetty-core/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/internal/HTTP2ServerConnection.java index 2a3e64724bc..b77a99011ee 100644 --- a/jetty-core/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/internal/HTTP2ServerConnection.java +++ b/jetty-core/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/internal/HTTP2ServerConnection.java @@ -13,7 +13,6 @@ package org.eclipse.jetty.http2.server.internal; -import java.io.IOException; import java.net.SocketAddress; import java.util.ArrayList; import java.util.Base64; @@ -29,16 +28,16 @@ import org.eclipse.jetty.http.HttpMethod; import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.http.MetaData; import org.eclipse.jetty.http.MetaData.Request; -import org.eclipse.jetty.http2.ISession; -import org.eclipse.jetty.http2.IStream; +import org.eclipse.jetty.http2.api.Stream; import org.eclipse.jetty.http2.api.server.ServerSessionListener; -import org.eclipse.jetty.http2.frames.DataFrame; import org.eclipse.jetty.http2.frames.Frame; import org.eclipse.jetty.http2.frames.HeadersFrame; import org.eclipse.jetty.http2.frames.PrefaceFrame; import org.eclipse.jetty.http2.frames.SettingsFrame; import org.eclipse.jetty.http2.internal.HTTP2Channel; import org.eclipse.jetty.http2.internal.HTTP2Connection; +import org.eclipse.jetty.http2.internal.HTTP2Session; +import org.eclipse.jetty.http2.internal.HTTP2Stream; import org.eclipse.jetty.http2.internal.parser.ServerParser; import org.eclipse.jetty.http2.internal.parser.SettingsBodyParser; import org.eclipse.jetty.io.Connection; @@ -65,7 +64,7 @@ public class HTTP2ServerConnection extends HTTP2Connection implements Connection private final HttpConfiguration httpConfig; private final String id; - public HTTP2ServerConnection(RetainableByteBufferPool retainableByteBufferPool, Connector connector, EndPoint endPoint, HttpConfiguration httpConfig, ServerParser parser, ISession session, int inputBufferSize, ServerSessionListener listener) + public HTTP2ServerConnection(RetainableByteBufferPool retainableByteBufferPool, Connector connector, EndPoint endPoint, HttpConfiguration httpConfig, ServerParser parser, HTTP2Session session, int inputBufferSize, ServerSessionListener listener) { super(retainableByteBufferPool, connector.getExecutor(), endPoint, parser, session, inputBufferSize); this.connector = connector; @@ -83,7 +82,7 @@ public class HTTP2ServerConnection extends HTTP2Connection implements Connection @Override public void onOpen() { - ISession session = getSession(); + HTTP2Session session = getSession(); notifyAccept(session); for (Frame frame : upgradeFrames) { @@ -93,7 +92,7 @@ public class HTTP2ServerConnection extends HTTP2Connection implements Connection produce(); } - private void notifyAccept(ISession session) + private void notifyAccept(HTTP2Session session) { try { @@ -105,7 +104,7 @@ public class HTTP2ServerConnection extends HTTP2Connection implements Connection } } - public void onNewStream(IStream stream, HeadersFrame frame) + public void onNewStream(HTTP2Stream stream, HeadersFrame frame) { if (LOG.isDebugEnabled()) LOG.debug("Processing {} on {}", frame, stream); @@ -119,29 +118,25 @@ public class HTTP2ServerConnection extends HTTP2Connection implements Connection offerTask(task, false); } - public void onData(IStream stream, DataFrame frame, Callback callback) + public void onDataAvailable(Stream stream) { if (LOG.isDebugEnabled()) - LOG.debug("Processing {} on {}", frame, stream); + LOG.debug("Processing data available on {}", stream); - HTTP2Channel.Server channel = (HTTP2Channel.Server)stream.getAttachment(); + HTTP2Channel.Server channel = (HTTP2Channel.Server)((HTTP2Stream)stream).getAttachment(); if (channel != null) { - Runnable task = channel.onData(frame, callback); + Runnable task = channel.onDataAvailable(); if (task != null) offerTask(task, false); } - else - { - callback.failed(new IOException("channel_not_found")); - } } - public void onTrailers(IStream stream, HeadersFrame frame) + public void onTrailers(Stream stream, HeadersFrame frame) { if (LOG.isDebugEnabled()) LOG.debug("Processing trailers {} on {}", frame, stream); - HTTP2Channel.Server channel = (HTTP2Channel.Server)stream.getAttachment(); + HTTP2Channel.Server channel = (HTTP2Channel.Server)((HTTP2Stream)stream).getAttachment(); if (channel != null) { Runnable task = channel.onTrailer(frame); @@ -150,20 +145,20 @@ public class HTTP2ServerConnection extends HTTP2Connection implements Connection } } - public boolean onStreamTimeout(IStream stream, Throwable failure) + public boolean onStreamTimeout(Stream stream, Throwable failure) { - HTTP2Channel.Server channel = (HTTP2Channel.Server)stream.getAttachment(); + HTTP2Channel.Server channel = (HTTP2Channel.Server)((HTTP2Stream)stream).getAttachment(); boolean result = channel != null && channel.onTimeout(failure, task -> offerTask(task, true)); if (LOG.isDebugEnabled()) LOG.debug("{} idle timeout on {}: {}", result ? "Processed" : "Ignored", stream, failure); return result; } - public void onStreamFailure(IStream stream, Throwable failure, Callback callback) + public void onStreamFailure(Stream stream, Throwable failure, Callback callback) { if (LOG.isDebugEnabled()) LOG.debug("Processing stream failure on {}", stream, failure); - HTTP2Channel.Server channel = (HTTP2Channel.Server)stream.getAttachment(); + HTTP2Channel.Server channel = (HTTP2Channel.Server)((HTTP2Stream)stream).getAttachment(); if (channel != null) { Runnable task = channel.onFailure(failure, callback); @@ -182,10 +177,10 @@ public class HTTP2ServerConnection extends HTTP2Connection implements Connection public boolean onSessionTimeout(Throwable failure) { - ISession session = getSession(); + HTTP2Session session = getSession(); // Compute whether all requests are idle. boolean result = session.getStreams().stream() - .map(stream -> (IStream)stream) + .map(stream -> (HTTP2Stream)stream) .map(stream -> (HTTP2Channel.Server)stream.getAttachment()) .filter(Objects::nonNull) .map(HTTP2Channel.Server::isIdle) @@ -203,7 +198,7 @@ public class HTTP2ServerConnection extends HTTP2Connection implements Connection callback.succeeded(); } - public void push(IStream stream, MetaData.Request request) + public void push(HTTP2Stream stream, MetaData.Request request) { if (LOG.isDebugEnabled()) LOG.debug("Processing push {} on {}", request, stream); diff --git a/jetty-core/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/internal/HTTP2ServerSession.java b/jetty-core/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/internal/HTTP2ServerSession.java index d0777ec7758..cea8e777add 100644 --- a/jetty-core/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/internal/HTTP2ServerSession.java +++ b/jetty-core/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/internal/HTTP2ServerSession.java @@ -20,7 +20,6 @@ import java.util.Map; import org.eclipse.jetty.http.MetaData; import org.eclipse.jetty.http2.CloseState; import org.eclipse.jetty.http2.FlowControlStrategy; -import org.eclipse.jetty.http2.IStream; import org.eclipse.jetty.http2.api.Session; import org.eclipse.jetty.http2.api.Stream; import org.eclipse.jetty.http2.api.server.ServerSessionListener; @@ -32,6 +31,7 @@ import org.eclipse.jetty.http2.frames.SettingsFrame; import org.eclipse.jetty.http2.frames.WindowUpdateFrame; import org.eclipse.jetty.http2.internal.ErrorCode; import org.eclipse.jetty.http2.internal.HTTP2Session; +import org.eclipse.jetty.http2.internal.HTTP2Stream; import org.eclipse.jetty.http2.internal.generator.Generator; import org.eclipse.jetty.http2.internal.parser.ServerParser; import org.eclipse.jetty.io.EndPoint; @@ -82,7 +82,7 @@ public class HTTP2ServerSession extends HTTP2Session implements ServerParser.Lis return; } - IStream stream = getStream(streamId); + HTTP2Stream stream = getStream(streamId); MetaData metaData = frame.getMetaData(); if (metaData.isRequest()) @@ -169,19 +169,12 @@ public class HTTP2ServerSession extends HTTP2Session implements ServerParser.Lis { switch (frame.getType()) { - case PREFACE: - onPreface(); - break; - case SETTINGS: + case PREFACE -> onPreface(); + case SETTINGS -> // SPEC: the required reply to this SETTINGS frame is the 101 response. onSettings((SettingsFrame)frame, false); - break; - case HEADERS: - onHeaders((HeadersFrame)frame); - break; - default: - super.onFrame(frame); - break; + case HEADERS -> onHeaders((HeadersFrame)frame); + default -> super.onFrame(frame); } } } diff --git a/jetty-core/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/internal/HttpStreamOverHTTP2.java b/jetty-core/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/internal/HttpStreamOverHTTP2.java index 5fc3081e18d..c6d678cbb76 100644 --- a/jetty-core/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/internal/HttpStreamOverHTTP2.java +++ b/jetty-core/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/internal/HttpStreamOverHTTP2.java @@ -24,7 +24,6 @@ import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.http.MetaData; import org.eclipse.jetty.http.Trailers; -import org.eclipse.jetty.http2.IStream; import org.eclipse.jetty.http2.api.Stream; import org.eclipse.jetty.http2.frames.DataFrame; import org.eclipse.jetty.http2.frames.HeadersFrame; @@ -32,6 +31,7 @@ import org.eclipse.jetty.http2.frames.PushPromiseFrame; import org.eclipse.jetty.http2.frames.ResetFrame; import org.eclipse.jetty.http2.internal.ErrorCode; import org.eclipse.jetty.http2.internal.HTTP2Channel; +import org.eclipse.jetty.http2.internal.HTTP2Stream; import org.eclipse.jetty.io.Connection; import org.eclipse.jetty.io.Content; import org.eclipse.jetty.server.HttpChannel; @@ -50,14 +50,14 @@ public class HttpStreamOverHTTP2 implements HttpStream, HTTP2Channel.Server private final AutoLock lock = new AutoLock(); private final HTTP2ServerConnection _connection; private final HttpChannel _httpChannel; - private final IStream _stream; + private final HTTP2Stream _stream; private final long _nanoTimeStamp; private Content.Chunk _chunk; private MetaData.Response _metaData; private boolean committed; private boolean _demand; - public HttpStreamOverHTTP2(HTTP2ServerConnection connection, HttpChannel httpChannel, IStream stream) + public HttpStreamOverHTTP2(HTTP2ServerConnection connection, HttpChannel httpChannel, HTTP2Stream stream) { _connection = connection; _httpChannel = httpChannel; @@ -139,13 +139,16 @@ public class HttpStreamOverHTTP2 implements HttpStream, HTTP2Channel.Server if (chunk != null) return chunk; - IStream.Data data = _stream.readData(); + Stream.Data data = _stream.readData(); if (data == null) return null; + chunk = createChunk(data); + data.release(); + try (AutoLock ignored = lock.lock()) { - _chunk = newChunk(data.frame(), data::complete); + _chunk = chunk; } } } @@ -172,27 +175,23 @@ public class HttpStreamOverHTTP2 implements HttpStream, HTTP2Channel.Server } else if (demand) { - _stream.demand(1); + _stream.demand(); } } @Override - public Runnable onData(DataFrame frame, Callback callback) + public Runnable onDataAvailable() { - Content.Chunk chunk = newChunk(frame, callback::succeeded); try (AutoLock ignored = lock.lock()) { _demand = false; - _chunk = chunk; } if (LOG.isDebugEnabled()) { - LOG.debug("HTTP2 Request #{}/{}: {} bytes of {} content", + LOG.debug("HTTP2 Request #{}/{}: data available", _stream.getId(), - Integer.toHexString(_stream.getSession().hashCode()), - frame.remaining(), - frame.isEndStream() ? "last" : "some"); + Integer.toHexString(_stream.getSession().hashCode())); } return _httpChannel.onContentAvailable(); @@ -218,9 +217,12 @@ public class HttpStreamOverHTTP2 implements HttpStream, HTTP2Channel.Server return _httpChannel.onContentAvailable(); } - private Content.Chunk newChunk(DataFrame frame, Runnable complete) + private Content.Chunk createChunk(Stream.Data data) { - return Content.Chunk.from(frame.getData(), frame.isEndStream(), complete); + // As we are passing the ByteBuffer to the Chunk we need to retain. + data.retain(); + DataFrame frame = data.frame(); + return Content.Chunk.from(frame.getData(), frame.isEndStream(), data); } @Override @@ -342,7 +344,7 @@ public class HttpStreamOverHTTP2 implements HttpStream, HTTP2Channel.Server System.lineSeparator(), HttpVersion.HTTP_2, _metaData.getStatus(), System.lineSeparator(), _metaData.getFields()); } - _stream.send(new IStream.FrameList(headersFrame, dataFrame, trailersFrame), callback); + _stream.send(new HTTP2Stream.FrameList(headersFrame, dataFrame, trailersFrame), callback); } private void sendContent(MetaData.Request request, ByteBuffer content, boolean last, Callback callback) @@ -422,7 +424,7 @@ public class HttpStreamOverHTTP2 implements HttpStream, HTTP2Channel.Server @Override public void succeeded(Stream pushStream) { - _connection.push((IStream)pushStream, request); + _connection.push((HTTP2Stream)pushStream, request); } @Override @@ -431,7 +433,7 @@ public class HttpStreamOverHTTP2 implements HttpStream, HTTP2Channel.Server if (LOG.isDebugEnabled()) LOG.debug("Could not HTTP/2 push {}", request, x); } - }, new Stream.Listener.Adapter()); // TODO: handle reset from the client ? + }, null); // TODO: handle reset from the client ? } public Runnable onPushRequest(MetaData.Request request) diff --git a/jetty-core/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/internal/HttpTransportOverHTTP2.java b/jetty-core/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/internal/HttpTransportOverHTTP2.java index a7a5d435441..6b658779c3f 100644 --- a/jetty-core/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/internal/HttpTransportOverHTTP2.java +++ b/jetty-core/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/internal/HttpTransportOverHTTP2.java @@ -269,7 +269,7 @@ public class HttpTransportOverHTTP2 // if (LOG.isDebugEnabled()) // LOG.debug("Could not push {}", request, x); // } -// }, new Stream.Listener.Adapter()); // TODO: handle reset from the client ? +// }, null); // TODO: handle reset from the client ? // } // // private void sendDataFrame(ByteBuffer content, boolean lastContent, boolean endStream, Callback callback) diff --git a/jetty-core/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/internal/ServerHTTP2StreamEndPoint.java b/jetty-core/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/internal/ServerHTTP2StreamEndPoint.java index 95e685bcfce..0e69deee65b 100644 --- a/jetty-core/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/internal/ServerHTTP2StreamEndPoint.java +++ b/jetty-core/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/internal/ServerHTTP2StreamEndPoint.java @@ -15,10 +15,9 @@ package org.eclipse.jetty.http2.server.internal; import java.util.function.Consumer; -import org.eclipse.jetty.http2.IStream; -import org.eclipse.jetty.http2.frames.DataFrame; import org.eclipse.jetty.http2.frames.HeadersFrame; import org.eclipse.jetty.http2.internal.HTTP2Channel; +import org.eclipse.jetty.http2.internal.HTTP2Stream; import org.eclipse.jetty.http2.internal.HTTP2StreamEndPoint; import org.eclipse.jetty.io.Connection; import org.eclipse.jetty.util.Callback; @@ -29,15 +28,15 @@ public class ServerHTTP2StreamEndPoint extends HTTP2StreamEndPoint implements HT { private static final Logger LOG = LoggerFactory.getLogger(ServerHTTP2StreamEndPoint.class); - public ServerHTTP2StreamEndPoint(IStream stream) + public ServerHTTP2StreamEndPoint(HTTP2Stream stream) { super(stream); } @Override - public Runnable onData(DataFrame frame, Callback callback) + public Runnable onDataAvailable() { - offerData(frame, callback); + processDataAvailable(); return null; } @@ -59,7 +58,7 @@ public class ServerHTTP2StreamEndPoint extends HTTP2StreamEndPoint implements HT result = connection.onIdleExpired(); if (result) { - offerFailure(failure); + processFailure(failure); consumer.accept(() -> close(failure)); } return result; @@ -70,7 +69,7 @@ public class ServerHTTP2StreamEndPoint extends HTTP2StreamEndPoint implements HT { if (LOG.isDebugEnabled()) LOG.debug("failure on {}: {}", this, failure); - offerFailure(failure); + processFailure(failure); close(failure); return callback::succeeded; } diff --git a/jetty-core/jetty-http2/jetty-http2-tests/pom.xml b/jetty-core/jetty-http2/jetty-http2-tests/pom.xml index 2b9d125078b..f90841cc4d9 100644 --- a/jetty-core/jetty-http2/jetty-http2-tests/pom.xml +++ b/jetty-core/jetty-http2/jetty-http2-tests/pom.xml @@ -15,7 +15,7 @@ org.mortbay.jetty h2spec-maven-plugin - 1.0.10-SNAPSHOT + 1.0.10 org.eclipse.jetty.http2.tests.H2SpecServer ${skipTests} @@ -24,7 +24,6 @@ ${project.build.directory}/h2spec-reports 3.5 - Sends invalid connection preface - 8.1.2.3 - 8.1.2.6. Malformed Requests and Responses diff --git a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/AsyncIOTest.java b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/AsyncIOTest.java index dcbb46b0fc9..cf20d5174db 100644 --- a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/AsyncIOTest.java +++ b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/AsyncIOTest.java @@ -53,13 +53,13 @@ public class AsyncIOTest extends AbstractTest } }); - Session session = newClientSession(new Session.Listener.Adapter()); + Session session = newClientSession(new Session.Listener() {}); MetaData.Request metaData = newRequest("GET", HttpFields.EMPTY); HeadersFrame frame = new HeadersFrame(metaData, null, false); final CountDownLatch latch = new CountDownLatch(1); FuturePromise promise = new FuturePromise<>(); - session.newStream(frame, promise, new Stream.Listener.Adapter() + session.newStream(frame, promise, new Stream.Listener() { @Override public void onHeaders(Stream stream, HeadersFrame frame) @@ -87,13 +87,13 @@ public class AsyncIOTest extends AbstractTest } }); - Session session = newClientSession(new Session.Listener.Adapter()); + Session session = newClientSession(new Session.Listener() {}); MetaData.Request metaData = newRequest("GET", HttpFields.EMPTY); HeadersFrame frame = new HeadersFrame(metaData, null, false); final CountDownLatch latch = new CountDownLatch(1); FuturePromise promise = new FuturePromise<>(); - session.newStream(frame, promise, new Stream.Listener.Adapter() + session.newStream(frame, promise, new Stream.Listener() { @Override public void onHeaders(Stream stream, HeadersFrame frame) @@ -145,13 +145,13 @@ public class AsyncIOTest extends AbstractTest } }); - Session session = newClient(new Session.Listener.Adapter()); + Session session = newClient(new Session.Listener() {}); MetaData.Request metaData = newRequest("GET", HttpFields.EMPTY); HeadersFrame frame = new HeadersFrame(metaData, null, false); final CountDownLatch latch = new CountDownLatch(1); FuturePromise promise = new FuturePromise<>(); - session.newStream(frame, promise, new Stream.Listener.Adapter() + session.newStream(frame, promise, new Stream.Listener() { @Override public void onHeaders(Stream stream, HeadersFrame frame) @@ -212,7 +212,7 @@ public class AsyncIOTest extends AbstractTest } }); - Session session = newClient(new Session.Listener.Adapter() + Session session = newClient(new Session.Listener() { @Override public Map onPreface(Session session) @@ -227,7 +227,7 @@ public class AsyncIOTest extends AbstractTest HeadersFrame frame = new HeadersFrame(metaData, null, true); CountDownLatch latch = new CountDownLatch(1); FuturePromise promise = new FuturePromise<>(); - session.newStream(frame, promise, new Stream.Listener.Adapter() + session.newStream(frame, promise, new Stream.Listener() { @Override public void onClosed(Stream stream) diff --git a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/AsyncServletTest.java b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/AsyncServletTest.java index 8103f27ef0d..192aad3f3db 100644 --- a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/AsyncServletTest.java +++ b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/AsyncServletTest.java @@ -56,13 +56,13 @@ public class AsyncServletTest extends AbstractTest // } // }); // -// Session session = newClient(new Session.Listener.Adapter()); +// Session session = newClient(new Session.Listener() {}); // // MetaData.Request metaData = newRequest("GET", HttpFields.EMPTY); // HeadersFrame frame = new HeadersFrame(metaData, null, true); // ByteArrayOutputStream buffer = new ByteArrayOutputStream(); // CountDownLatch latch = new CountDownLatch(1); -// session.newStream(frame, new Promise.Adapter<>(), new Stream.Listener.Adapter() +// session.newStream(frame, new Promise.Adapter<>(), new Stream.Listener() // { // @Override // public void onData(Stream stream, DataFrame frame, Callback callback) @@ -93,13 +93,13 @@ public class AsyncServletTest extends AbstractTest // long idleTimeout = 1000; // client.setIdleTimeout(idleTimeout); // -// Session session = newClient(new Session.Listener.Adapter()); +// Session session = newClient(new Session.Listener() {}); // MetaData.Request metaData = newRequest("GET", HttpFields.EMPTY); // HeadersFrame frame = new HeadersFrame(metaData, null, true); // FuturePromise promise = new FuturePromise<>(); // CountDownLatch responseLatch = new CountDownLatch(1); // CountDownLatch failLatch = new CountDownLatch(1); -// session.newStream(frame, promise, new Stream.Listener.Adapter() +// session.newStream(frame, promise, new Stream.Listener() // { // @Override // public void onHeaders(Stream stream, HeadersFrame frame) @@ -130,12 +130,12 @@ public class AsyncServletTest extends AbstractTest // long idleTimeout = 1000; // client.setIdleTimeout(10 * idleTimeout); // -// Session session = newClient(new Session.Listener.Adapter()); +// Session session = newClient(new Session.Listener() {}); // MetaData.Request metaData = newRequest("GET", HttpFields.EMPTY); // HeadersFrame frame = new HeadersFrame(metaData, null, true); // FuturePromise promise = new FuturePromise<>(); // CountDownLatch clientLatch = new CountDownLatch(1); -// session.newStream(frame, promise, new Stream.Listener.Adapter() +// session.newStream(frame, promise, new Stream.Listener() // { // @Override // public boolean onIdleTimeout(Stream stream, Throwable x) @@ -178,11 +178,11 @@ public class AsyncServletTest extends AbstractTest // // prepareClient(); // client.start(); -// Session session = newClient(new Session.Listener.Adapter()); +// Session session = newClient(new Session.Listener() {}); // MetaData.Request metaData = newRequest("GET", HttpFields.EMPTY); // HeadersFrame frame = new HeadersFrame(metaData, null, true); // FuturePromise promise = new FuturePromise<>(); -// session.newStream(frame, promise, new Stream.Listener.Adapter()); +// session.newStream(frame, promise, null); // Stream stream = promise.get(5, TimeUnit.SECONDS); // // // Wait for the server to be in ASYNC_WAIT. @@ -288,11 +288,11 @@ public class AsyncServletTest extends AbstractTest // prepareClient(); // client.start(); // -// Session session = newClient(new Session.Listener.Adapter()); +// Session session = newClient(new Session.Listener() {}); // MetaData.Request metaData = newRequest("GET", HttpFields.EMPTY); // HeadersFrame frame = new HeadersFrame(metaData, null, true); // CountDownLatch clientLatch = new CountDownLatch(1); -// session.newStream(frame, new Promise.Adapter<>(), new Stream.Listener.Adapter() +// session.newStream(frame, new Promise.Adapter<>(), new Stream.Listener() // { // @Override // public void onHeaders(Stream stream, HeadersFrame frame) diff --git a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/BlockedWritesWithSmallThreadPoolTest.java b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/BlockedWritesWithSmallThreadPoolTest.java index 2dd8f3ba793..2d2229bdc16 100644 --- a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/BlockedWritesWithSmallThreadPoolTest.java +++ b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/BlockedWritesWithSmallThreadPoolTest.java @@ -122,7 +122,7 @@ public class BlockedWritesWithSmallThreadPoolTest client.start(); FuturePromise promise = new FuturePromise<>(); - client.connect(new InetSocketAddress("localhost", connector.getLocalPort()), new Session.Listener.Adapter(), promise); + client.connect(new InetSocketAddress("localhost", connector.getLocalPort()), new Session.Listener() {}, promise); Session session = promise.get(5, SECONDS); CountDownLatch clientBlockLatch = new CountDownLatch(1); @@ -130,23 +130,25 @@ public class BlockedWritesWithSmallThreadPoolTest // Send a request to TCP congest the server. HttpURI uri = HttpURI.build("http://localhost:" + connector.getLocalPort() + "/congest"); MetaData.Request request = new MetaData.Request("GET", uri, HttpVersion.HTTP_2, HttpFields.EMPTY); - session.newStream(new HeadersFrame(request, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter() + session.newStream(new HeadersFrame(request, null, true), new Promise.Adapter<>(), new Stream.Listener() { @Override - public void onData(Stream stream, DataFrame frame, Callback callback) + public void onDataAvailable(Stream stream) { + Stream.Data data = stream.readData(); try { // Block here to stop reading from the network // to cause the server to TCP congest. clientBlockLatch.await(5, SECONDS); - callback.succeeded(); - if (frame.isEndStream()) + data.release(); + stream.demand(); + if (data.frame().isEndStream()) clientDataLatch.countDown(); } catch (InterruptedException x) { - callback.failed(x); + data.release(); } } }); @@ -183,23 +185,26 @@ public class BlockedWritesWithSmallThreadPoolTest { int contentLength = 16 * 1024 * 1024; CountDownLatch serverBlockLatch = new CountDownLatch(1); - RawHTTP2ServerConnectionFactory http2 = new RawHTTP2ServerConnectionFactory(new HttpConfiguration(), new ServerSessionListener.Adapter() + RawHTTP2ServerConnectionFactory http2 = new RawHTTP2ServerConnectionFactory(new HttpConfiguration(), new ServerSessionListener() { @Override public Stream.Listener onNewStream(Stream stream, HeadersFrame frame) { - return new Stream.Listener.Adapter() + stream.demand(); + return new Stream.Listener() { @Override - public void onData(Stream stream, DataFrame frame, Callback callback) + public void onDataAvailable(Stream stream) { + Stream.Data data = stream.readData(); try { // Block here to stop reading from the network // to cause the client to TCP congest. serverBlockLatch.await(5, SECONDS); - callback.succeeded(); - if (frame.isEndStream()) + data.release(); + stream.demand(); + if (data.frame().isEndStream()) { MetaData.Response response = new MetaData.Response(HttpVersion.HTTP_2, HttpStatus.OK_200, HttpFields.EMPTY); stream.headers(new HeadersFrame(stream.getId(), response, null, true), Callback.NOOP); @@ -207,7 +212,7 @@ public class BlockedWritesWithSmallThreadPoolTest } catch (InterruptedException x) { - callback.failed(x); + data.release(); } } }; @@ -226,7 +231,7 @@ public class BlockedWritesWithSmallThreadPoolTest client.start(); FuturePromise promise = new FuturePromise<>(); - client.connect(new InetSocketAddress("localhost", connector.getLocalPort()), new Session.Listener.Adapter(), promise); + client.connect(new InetSocketAddress("localhost", connector.getLocalPort()), new Session.Listener() {}, promise); Session session = promise.get(5, SECONDS); // Send a request to TCP congest the client. @@ -234,7 +239,7 @@ public class BlockedWritesWithSmallThreadPoolTest MetaData.Request request = new MetaData.Request("GET", uri, HttpVersion.HTTP_2, HttpFields.EMPTY); FuturePromise streamPromise = new FuturePromise<>(); CountDownLatch latch = new CountDownLatch(1); - session.newStream(new HeadersFrame(request, null, false), streamPromise, new Stream.Listener.Adapter() + session.newStream(new HeadersFrame(request, null, false), streamPromise, new Stream.Listener() { @Override public void onHeaders(Stream stream, HeadersFrame frame) diff --git a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/CloseTest.java b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/CloseTest.java index 3ceeff39780..f82498cf7f0 100644 --- a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/CloseTest.java +++ b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/CloseTest.java @@ -53,7 +53,7 @@ public class CloseTest extends AbstractServerTest { final CountDownLatch closeLatch = new CountDownLatch(1); final AtomicReference sessionRef = new AtomicReference<>(); - startServer(new ServerSessionListener.Adapter() + startServer(new ServerSessionListener() { @Override public Stream.Listener onNewStream(Stream stream, HeadersFrame frame) @@ -123,7 +123,7 @@ public class CloseTest extends AbstractServerTest public void testClientSendsGoAwayButDoesNotCloseConnectionServerCloses() throws Exception { final AtomicReference sessionRef = new AtomicReference<>(); - startServer(new ServerSessionListener.Adapter() + startServer(new ServerSessionListener() { @Override public Stream.Listener onNewStream(Stream stream, HeadersFrame frame) @@ -187,7 +187,7 @@ public class CloseTest extends AbstractServerTest { final long idleTimeout = 1000; final AtomicReference sessionRef = new AtomicReference<>(); - startServer(new ServerSessionListener.Adapter() + startServer(new ServerSessionListener() { @Override public Stream.Listener onNewStream(Stream stream, HeadersFrame frame) diff --git a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/ConcurrentStreamCreationTest.java b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/ConcurrentStreamCreationTest.java index a993f72f2d1..36fc21f9e0d 100644 --- a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/ConcurrentStreamCreationTest.java +++ b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/ConcurrentStreamCreationTest.java @@ -45,7 +45,7 @@ public class ConcurrentStreamCreationTest extends AbstractTest int iterations = 1024; int total = threads * runs * iterations; CountDownLatch serverLatch = new CountDownLatch(total); - start(new ServerSessionListener.Adapter() + start(new ServerSessionListener() { @Override public Stream.Listener onNewStream(Stream stream, HeadersFrame frame) @@ -58,7 +58,7 @@ public class ConcurrentStreamCreationTest extends AbstractTest } }, h2 -> h2.setMaxConcurrentStreams(total)); - Session session = newClientSession(new Session.Listener.Adapter()); + Session session = newClientSession(new Session.Listener() {}); CyclicBarrier barrier = new CyclicBarrier(threads); CountDownLatch clientLatch = new CountDownLatch(total); @@ -81,7 +81,7 @@ public class ConcurrentStreamCreationTest extends AbstractTest { MetaData.Request request = newRequest("GET", HttpFields.EMPTY); HeadersFrame requestFrame = new HeadersFrame(request, null, true); - session.newStream(requestFrame, promise, new Stream.Listener.Adapter() + session.newStream(requestFrame, promise, new Stream.Listener() { @Override public void onHeaders(Stream stream, HeadersFrame frame) diff --git a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/ConnectTimeoutTest.java b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/ConnectTimeoutTest.java index c5c2266b1df..231ed3f4003 100644 --- a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/ConnectTimeoutTest.java +++ b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/ConnectTimeoutTest.java @@ -39,12 +39,12 @@ public class ConnectTimeoutTest extends AbstractTest int connectTimeout = 1000; assumeConnectTimeout(host, port, connectTimeout); - start(new ServerSessionListener.Adapter()); + start(new ServerSessionListener() {}); http2Client.setConnectTimeout(connectTimeout); InetSocketAddress address = new InetSocketAddress(host, port); final CountDownLatch latch = new CountDownLatch(1); - http2Client.connect(address, new Session.Listener.Adapter(), new Promise.Adapter<>() + http2Client.connect(address, new Session.Listener() {}, new Promise.Adapter<>() { @Override public void failed(Throwable x) diff --git a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/ConnectTunnelTest.java b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/ConnectTunnelTest.java index 38047d5850a..a3e4d843e04 100644 --- a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/ConnectTunnelTest.java +++ b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/ConnectTunnelTest.java @@ -22,6 +22,7 @@ import org.eclipse.jetty.http.HostPortHttpField; import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.HttpMethod; import org.eclipse.jetty.http.HttpScheme; +import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.http.HttpURI; import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.http.MetaData; @@ -44,7 +45,7 @@ public class ConnectTunnelTest extends AbstractTest @Test public void testCONNECT() throws Exception { - start(new ServerSessionListener.Adapter() + start(new ServerSessionListener() { @Override public Stream.Listener onNewStream(Stream stream, HeadersFrame frame) @@ -56,18 +57,21 @@ public class ConnectTunnelTest extends AbstractTest assertNull(uri.getScheme()); assertNull(uri.getPath()); assertNotNull(uri.getAuthority()); - return new Stream.Listener.Adapter() + MetaData.Response response = new MetaData.Response(HttpVersion.HTTP_2, HttpStatus.OK_200, HttpFields.EMPTY); + stream.headers(new HeadersFrame(stream.getId(), response, null, false), Callback.from(stream::demand)); + return new Stream.Listener() { @Override - public void onData(Stream stream, DataFrame frame, Callback callback) + public void onDataAvailable(Stream stream) { - stream.data(frame, callback); + Stream.Data data = stream.readData(); + stream.data(data.frame(), Callback.from(data::release)); } }; } }); - Session client = newClientSession(new Session.Listener.Adapter()); + Session client = newClientSession(new Session.Listener() {}); CountDownLatch latch = new CountDownLatch(1); byte[] bytes = "HELLO".getBytes(StandardCharsets.UTF_8); @@ -76,12 +80,14 @@ public class ConnectTunnelTest extends AbstractTest String authority = host + ":" + port; MetaData.Request request = new MetaData.Request(HttpMethod.CONNECT.asString(), null, new HostPortHttpField(authority), null, HttpVersion.HTTP_2, HttpFields.EMPTY, -1); FuturePromise streamPromise = new FuturePromise<>(); - client.newStream(new HeadersFrame(request, null, false), streamPromise, new Stream.Listener.Adapter() + client.newStream(new HeadersFrame(request, null, false), streamPromise, new Stream.Listener() { @Override - public void onData(Stream stream, DataFrame frame, Callback callback) + public void onDataAvailable(Stream stream) { - if (frame.isEndStream()) + Stream.Data data = stream.readData(); + data.release(); + if (data.frame().isEndStream()) latch.countDown(); } }); @@ -95,7 +101,7 @@ public class ConnectTunnelTest extends AbstractTest @Test public void testCONNECTWithProtocol() throws Exception { - start(new ServerSessionListener.Adapter() + start(new ServerSessionListener() { @Override public Stream.Listener onNewStream(Stream stream, HeadersFrame frame) @@ -108,18 +114,21 @@ public class ConnectTunnelTest extends AbstractTest assertNotNull(uri.getPath()); assertNotNull(uri.getAuthority()); assertNotNull(request.getProtocol()); - return new Stream.Listener.Adapter() + MetaData.Response response = new MetaData.Response(HttpVersion.HTTP_2, HttpStatus.OK_200, HttpFields.EMPTY); + stream.headers(new HeadersFrame(stream.getId(), response, null, false), Callback.from(stream::demand)); + return new Stream.Listener() { @Override - public void onData(Stream stream, DataFrame frame, Callback callback) + public void onDataAvailable(Stream stream) { - stream.data(frame, callback); + Stream.Data data = stream.readData(); + stream.data(data.frame(), Callback.from(data::release)); } }; } }); - Session client = newClientSession(new Session.Listener.Adapter()); + Session client = newClientSession(new Session.Listener() {}); CountDownLatch latch = new CountDownLatch(1); byte[] bytes = "HELLO".getBytes(StandardCharsets.UTF_8); @@ -128,12 +137,14 @@ public class ConnectTunnelTest extends AbstractTest String authority = host + ":" + port; MetaData.Request request = new MetaData.ConnectRequest(HttpScheme.HTTP, new HostPortHttpField(authority), "/", HttpFields.EMPTY, "websocket"); FuturePromise streamPromise = new FuturePromise<>(); - client.newStream(new HeadersFrame(request, null, false), streamPromise, new Stream.Listener.Adapter() + client.newStream(new HeadersFrame(request, null, false), streamPromise, new Stream.Listener() { @Override - public void onData(Stream stream, DataFrame frame, Callback callback) + public void onDataAvailable(Stream stream) { - if (frame.isEndStream()) + Stream.Data data = stream.readData(); + data.release(); + if (data.frame().isEndStream()) latch.countDown(); } }); diff --git a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/ContentLengthTest.java b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/ContentLengthTest.java index fdd3e2b789f..02a0f9fada8 100644 --- a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/ContentLengthTest.java +++ b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/ContentLengthTest.java @@ -14,10 +14,18 @@ package org.eclipse.jetty.http2.tests; import java.nio.ByteBuffer; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; import org.eclipse.jetty.client.api.ContentResponse; import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.HttpHeader; +import org.eclipse.jetty.http2.api.Session; +import org.eclipse.jetty.http2.api.Stream; +import org.eclipse.jetty.http2.frames.DataFrame; +import org.eclipse.jetty.http2.frames.HeadersFrame; +import org.eclipse.jetty.http2.frames.ResetFrame; +import org.eclipse.jetty.io.Content; import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.Response; @@ -27,6 +35,7 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; public class ContentLengthTest extends AbstractTest @@ -76,6 +85,37 @@ public class ContentLengthTest extends AbstractTest assertEquals(data.length, contentLength); } + @ParameterizedTest + @ValueSource(strings = {"GET", "HEAD", "POST", "PUT"}) + public void testClientContentLengthMismatch(String method) throws Exception + { + byte[] data = new byte[512]; + start(new Handler.Processor() + { + @Override + public void process(Request request, Response response, Callback callback) + { + Content.Source.consumeAll(request, callback); + } + }); + + Session clientSession = newClientSession(new Session.Listener() {}); + CountDownLatch resetLatch = new CountDownLatch(1); + // Set a wrong Content-Length header. + HttpFields requestHeaders = HttpFields.build().put(HttpHeader.CONTENT_LENGTH, String.valueOf(data.length + 1)); + clientSession.newStream(new HeadersFrame(newRequest(method, requestHeaders), null, false), new Stream.Listener() + { + @Override + public void onReset(Stream stream, ResetFrame frame, Callback callback) + { + resetLatch.countDown(); + callback.succeeded(); + } + }).thenAccept(stream -> stream.data(new DataFrame(stream.getId(), ByteBuffer.wrap(data), true))); + + assertTrue(resetLatch.await(5, TimeUnit.SECONDS)); + } + // TODO @ParameterizedTest @ValueSource(strings = {"GET", "HEAD", "POST", "PUT"}) diff --git a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/DataDemandTest.java b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/DataDemandTest.java index 2c6c1409091..bf47db276f8 100644 --- a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/DataDemandTest.java +++ b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/DataDemandTest.java @@ -14,10 +14,13 @@ package org.eclipse.jetty.http2.tests; import java.nio.ByteBuffer; +import java.util.Deque; import java.util.Queue; +import java.util.concurrent.ConcurrentLinkedDeque; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import org.eclipse.jetty.http.HttpFields; @@ -25,7 +28,6 @@ import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.http.MetaData; import org.eclipse.jetty.http2.FlowControlStrategy; -import org.eclipse.jetty.http2.ISession; import org.eclipse.jetty.http2.api.Session; import org.eclipse.jetty.http2.api.Stream; import org.eclipse.jetty.http2.api.server.ServerSessionListener; @@ -40,12 +42,13 @@ import org.eclipse.jetty.util.FuturePromise; import org.eclipse.jetty.util.Promise; import org.junit.jupiter.api.Test; +import static org.awaitility.Awaitility.await; +import static org.hamcrest.Matchers.is; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.fail; public class DataDemandTest extends AbstractTest { @@ -54,35 +57,40 @@ public class DataDemandTest extends AbstractTest { int length = FlowControlStrategy.DEFAULT_WINDOW_SIZE - 1; AtomicReference serverStreamRef = new AtomicReference<>(); - Queue serverQueue = new ConcurrentLinkedQueue<>(); - start(new ServerSessionListener.Adapter() + Deque serverQueue = new ConcurrentLinkedDeque<>(); + start(new ServerSessionListener() { @Override public Stream.Listener onNewStream(Stream stream, HeadersFrame frame) { serverStreamRef.set(stream); - return new Stream.Listener.Adapter() + MetaData.Response response = new MetaData.Response(HttpVersion.HTTP_2, HttpStatus.OK_200, HttpFields.EMPTY); + stream.headers(new HeadersFrame(stream.getId(), response, null, false), Callback.from(stream::demand)); + return new Stream.Listener() { @Override - public void onDataDemanded(Stream stream, DataFrame frame, Callback callback) + public void onDataAvailable(Stream stream) { - // Don't demand and don't complete callbacks. - serverQueue.offer(frame); + Stream.Data data = stream.readData(); + // Don't demand and don't release. + serverQueue.offer(data); } }; } }); - Session client = newClientSession(new Session.Listener.Adapter()); + Session client = newClientSession(new Session.Listener() {}); MetaData.Request post = newRequest("POST", HttpFields.EMPTY); FuturePromise promise = new FuturePromise<>(); - Queue clientQueue = new ConcurrentLinkedQueue<>(); - client.newStream(new HeadersFrame(post, null, false), promise, new Stream.Listener.Adapter() + Queue clientQueue = new ConcurrentLinkedQueue<>(); + client.newStream(new HeadersFrame(post, null, false), promise, new Stream.Listener() { @Override - public void onDataDemanded(Stream stream, DataFrame frame, Callback callback) + public void onDataAvailable(Stream stream) { - clientQueue.offer(frame); + Stream.Data data = stream.readData(); + // Don't demand and don't release. + clientQueue.offer(data); } }); Stream clientStream = promise.get(5, TimeUnit.SECONDS); @@ -90,7 +98,7 @@ public class DataDemandTest extends AbstractTest // so that it will be split on the server in multiple frames. clientStream.data(new DataFrame(clientStream.getId(), ByteBuffer.allocate(length), true), Callback.NOOP); - // The server should receive only 1 DATA frame because it does explicit demand. + // The server should receive only 1 DATA frame because it does 1 explicit demand. // Wait a bit more to be sure it only receives 1 DATA frame. Thread.sleep(1000); assertEquals(1, serverQueue.size()); @@ -98,25 +106,20 @@ public class DataDemandTest extends AbstractTest Stream serverStream = serverStreamRef.get(); assertNotNull(serverStream); - // Demand more DATA frames. - int count = 2; - serverStream.demand(count); - Thread.sleep(1000); - // The server should have received `count` more DATA frames. - assertEquals(1 + count, serverQueue.size()); + // Demand 1 more DATA frames. + serverStream.demand(); + // The server should have received 1 more DATA frame. + await().atMost(1, TimeUnit.SECONDS).until(serverQueue::size, is(2)); // Demand all the rest. - serverStream.demand(Long.MAX_VALUE); - int loops = 0; + AtomicInteger count = new AtomicInteger(serverQueue.size()); while (true) { - if (++loops > 100) - fail(); - - Thread.sleep(100); - + serverStream.demand(); + await().atMost(1, TimeUnit.SECONDS).until(() -> serverQueue.size() == count.get() + 1); + count.incrementAndGet(); long sum = serverQueue.stream() - .mapToLong(frame -> frame.getData().remaining()) + .mapToLong(data -> data.frame().getData().remaining()) .sum(); if (sum == length) break; @@ -124,50 +127,52 @@ public class DataDemandTest extends AbstractTest // Even if demanded, the flow control window should not have // decreased because the callbacks have not been completed. - int recvWindow = ((ISession)serverStream.getSession()).updateRecvWindow(0); + int recvWindow = ((HTTP2Session)serverStream.getSession()).updateRecvWindow(0); assertEquals(FlowControlStrategy.DEFAULT_WINDOW_SIZE - length, recvWindow); + // Release them all. + serverQueue.forEach(Stream.Data::release); + // Send a large DATA frame to the client. serverStream.data(new DataFrame(serverStream.getId(), ByteBuffer.allocate(length), true), Callback.NOOP); - // The client should receive only 1 DATA frame because it does explicit demand. // Wait a bit more to be sure it only receives 1 DATA frame. Thread.sleep(1000); assertEquals(1, clientQueue.size()); - // Demand more DATA frames. - clientStream.demand(count); + // Demand 1 more DATA frames. + clientStream.demand(); Thread.sleep(1000); - // The client should have received `count` more DATA frames. - assertEquals(1 + count, clientQueue.size()); + // The client should have received 1 more DATA frame. + assertEquals(2, clientQueue.size()); // Demand all the rest. - clientStream.demand(Long.MAX_VALUE); - loops = 0; + count.set(clientQueue.size()); while (true) { - if (++loops > 100) - fail(); - - Thread.sleep(100); - + clientStream.demand(); + await().atMost(1, TimeUnit.SECONDS).until(() -> clientQueue.size() == count.get() + 1); + count.incrementAndGet(); long sum = clientQueue.stream() - .mapToLong(frame -> frame.getData().remaining()) + .mapToLong(data -> data.frame().getData().remaining()) .sum(); if (sum == length) break; } + // Release them all. + clientQueue.forEach(Stream.Data::release); + // Both the client and server streams should be gone now. assertNull(clientStream.getSession().getStream(clientStream.getId())); assertNull(serverStream.getSession().getStream(serverStream.getId())); } @Test - public void testOnBeforeData() throws Exception + public void testNoDemandNoOnDataAvailable() throws Exception { - start(new ServerSessionListener.Adapter() + start(new ServerSessionListener() { @Override public Stream.Listener onNewStream(Stream stream, HeadersFrame frame) @@ -183,13 +188,12 @@ public class DataDemandTest extends AbstractTest } }); - Session client = newClientSession(new Session.Listener.Adapter()); + Session client = newClientSession(new Session.Listener() {}); MetaData.Request post = newRequest("GET", HttpFields.EMPTY); FuturePromise promise = new FuturePromise<>(); CountDownLatch responseLatch = new CountDownLatch(1); - CountDownLatch beforeDataLatch = new CountDownLatch(1); CountDownLatch latch = new CountDownLatch(1); - client.newStream(new HeadersFrame(post, null, true), promise, new Stream.Listener.Adapter() + client.newStream(new HeadersFrame(post, null, true), promise, new Stream.Listener() { @Override public void onHeaders(Stream stream, HeadersFrame frame) @@ -197,37 +201,33 @@ public class DataDemandTest extends AbstractTest MetaData.Response response = (MetaData.Response)frame.getMetaData(); assertEquals(HttpStatus.OK_200, response.getStatus()); responseLatch.countDown(); - } - - @Override - public void onBeforeData(Stream stream) - { - beforeDataLatch.countDown(); // Don't demand. } @Override - public void onData(Stream stream, DataFrame frame, Callback callback) + public void onDataAvailable(Stream stream) { - callback.succeeded(); - if (frame.isEndStream()) + Stream.Data data = stream.readData(); + assertNotNull(data); + data.release(); + stream.demand(); + if (data.frame().isEndStream()) latch.countDown(); } }); Stream clientStream = promise.get(5, TimeUnit.SECONDS); assertTrue(responseLatch.await(5, TimeUnit.SECONDS)); - assertTrue(beforeDataLatch.await(5, TimeUnit.SECONDS)); // Should not receive DATA frames until demanded. assertFalse(latch.await(1, TimeUnit.SECONDS)); // Now demand the first DATA frame. - clientStream.demand(1); + clientStream.demand(); assertTrue(latch.await(5, TimeUnit.SECONDS)); } @Test public void testDemandFromOnHeaders() throws Exception { - start(new ServerSessionListener.Adapter() + start(new ServerSessionListener() { @Override public Stream.Listener onNewStream(Stream stream, HeadersFrame frame) @@ -243,28 +243,19 @@ public class DataDemandTest extends AbstractTest } }); - Session client = newClientSession(new Session.Listener.Adapter()); + Session client = newClientSession(new Session.Listener() {}); MetaData.Request post = newRequest("GET", HttpFields.EMPTY); CountDownLatch latch = new CountDownLatch(1); - client.newStream(new HeadersFrame(post, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter() + client.newStream(new HeadersFrame(post, null, true), new Promise.Adapter<>(), new Stream.Listener() { @Override - public void onHeaders(Stream stream, HeadersFrame frame) + public void onDataAvailable(Stream stream) { - stream.demand(1); - } - - @Override - public void onBeforeData(Stream stream) - { - // Do not demand from here, we have already demanded in onHeaders(). - } - - @Override - public void onData(Stream stream, DataFrame frame, Callback callback) - { - callback.succeeded(); - if (frame.isEndStream()) + Stream.Data data = stream.readData(); + assertNotNull(data); + data.release(); + stream.demand(); + if (data.frame().isEndStream()) latch.countDown(); } }); @@ -272,9 +263,9 @@ public class DataDemandTest extends AbstractTest } @Test - public void testOnBeforeDataDoesNotReenter() throws Exception + public void testDemandFromOnHeadersDoesNotInvokeOnDataAvailable() throws Exception { - start(new ServerSessionListener.Adapter() + start(new ServerSessionListener() { @Override public Stream.Listener onNewStream(Stream stream, HeadersFrame frame) @@ -290,27 +281,29 @@ public class DataDemandTest extends AbstractTest } }); - Session client = newClientSession(new Session.Listener.Adapter()); + Session client = newClientSession(new Session.Listener() {}); MetaData.Request post = newRequest("GET", HttpFields.EMPTY); CountDownLatch latch = new CountDownLatch(1); - client.newStream(new HeadersFrame(post, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter() + client.newStream(new HeadersFrame(post, null, true), new Promise.Adapter<>(), new Stream.Listener() { - private boolean inBeforeData; + private boolean inHeaders; @Override - public void onBeforeData(Stream stream) + public void onHeaders(Stream stream, HeadersFrame frame) { - inBeforeData = true; - stream.demand(1); - inBeforeData = false; + inHeaders = true; + stream.demand(); + inHeaders = false; } @Override - public void onData(Stream stream, DataFrame frame, Callback callback) + public void onDataAvailable(Stream stream) { - assertFalse(inBeforeData); - callback.succeeded(); - if (frame.isEndStream()) + assertFalse(inHeaders); + Stream.Data data = stream.readData(); + data.release(); + stream.demand(); + if (data.frame().isEndStream()) latch.countDown(); } }); @@ -320,19 +313,21 @@ public class DataDemandTest extends AbstractTest @Test public void testSynchronousDemandDoesNotStackOverflow() throws Exception { - start(new ServerSessionListener.Adapter() + start(new ServerSessionListener() { @Override public Stream.Listener onNewStream(Stream stream, HeadersFrame frame) { - return new Stream.Listener.Adapter() + stream.demand(); + return new Stream.Listener() { @Override - public void onDataDemanded(Stream stream, DataFrame frame, Callback callback) + public void onDataAvailable(Stream stream) { - callback.succeeded(); - stream.demand(1); - if (frame.isEndStream()) + Stream.Data data = stream.readData(); + data.release(); + stream.demand(); + if (data.frame().isEndStream()) { MetaData.Response response = new MetaData.Response(HttpVersion.HTTP_2, HttpStatus.OK_200, HttpFields.EMPTY); stream.headers(new HeadersFrame(stream.getId(), response, null, true), Callback.NOOP); @@ -342,11 +337,11 @@ public class DataDemandTest extends AbstractTest } }); - Session client = newClientSession(new Session.Listener.Adapter()); + Session client = newClientSession(new Session.Listener() {}); MetaData.Request post = newRequest("POST", HttpFields.EMPTY); FuturePromise promise = new FuturePromise<>(); CountDownLatch latch = new CountDownLatch(1); - client.newStream(new HeadersFrame(post, null, false), promise, new Stream.Listener.Adapter() + client.newStream(new HeadersFrame(post, null, false), promise, new Stream.Listener() { @Override public void onHeaders(Stream stream, HeadersFrame frame) diff --git a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/FlowControlStalledTest.java b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/FlowControlStalledTest.java index 0be4c9b40ec..23fafc2be1e 100644 --- a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/FlowControlStalledTest.java +++ b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/FlowControlStalledTest.java @@ -31,8 +31,6 @@ import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.http.MetaData; import org.eclipse.jetty.http2.BufferingFlowControlStrategy; import org.eclipse.jetty.http2.FlowControlStrategy; -import org.eclipse.jetty.http2.ISession; -import org.eclipse.jetty.http2.IStream; import org.eclipse.jetty.http2.api.Session; import org.eclipse.jetty.http2.api.Stream; import org.eclipse.jetty.http2.api.server.ServerSessionListener; @@ -56,11 +54,11 @@ import static org.junit.jupiter.api.Assertions.assertTrue; public class FlowControlStalledTest { - protected ServerConnector connector; - protected HTTP2Client client; - protected Server server; + private ServerConnector connector; + private HTTP2Client client; + private Server server; - protected void start(FlowControlStrategy.Factory flowControlFactory, ServerSessionListener listener) throws Exception + private void start(FlowControlStrategy.Factory flowControlFactory, ServerSessionListener listener) throws Exception { QueuedThreadPool serverExecutor = new QueuedThreadPool(); serverExecutor.setName("server"); @@ -83,7 +81,7 @@ public class FlowControlStalledTest client.start(); } - protected Session newClient(Session.Listener listener) throws Exception + private Session newClient(Session.Listener listener) throws Exception { String host = "localhost"; int port = connector.getLocalPort(); @@ -93,7 +91,7 @@ public class FlowControlStalledTest return promise.get(5, TimeUnit.SECONDS); } - protected MetaData.Request newRequest(String method, String target, HttpFields fields) + private MetaData.Request newRequest(String method, String target, HttpFields fields) { String host = "localhost"; int port = connector.getLocalPort(); @@ -118,19 +116,19 @@ public class FlowControlStalledTest start(() -> new BufferingFlowControlStrategy(0.5f) { @Override - public void onStreamStalled(IStream stream) + public void onStreamStalled(Stream stream) { super.onStreamStalled(stream); stallLatch.get().countDown(); } @Override - protected void onStreamUnstalled(IStream stream) + protected void onStreamUnstalled(Stream stream) { super.onStreamUnstalled(stream); unstallLatch.countDown(); } - }, new ServerSessionListener.Adapter() + }, new ServerSessionListener() { @Override public Stream.Listener onNewStream(Stream stream, HeadersFrame frame) @@ -156,24 +154,28 @@ public class FlowControlStalledTest stream.headers(new HeadersFrame(stream.getId(), response, null, true), Callback.NOOP); } + stream.demand(); return null; } }); // Use a large session window so that only the stream gets stalled. client.setInitialSessionRecvWindow(5 * FlowControlStrategy.DEFAULT_WINDOW_SIZE); - Session client = newClient(new Session.Listener.Adapter()); + Session client = newClient(new Session.Listener() {}); CountDownLatch latch = new CountDownLatch(1); - Queue callbacks = new ArrayDeque<>(); + Queue dataQueue = new ArrayDeque<>(); MetaData.Request request = newRequest("GET", "/stall", HttpFields.EMPTY); - client.newStream(new HeadersFrame(request, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter() + client.newStream(new HeadersFrame(request, null, true), new Promise.Adapter<>(), new Stream.Listener() { @Override - public void onData(Stream stream, DataFrame frame, Callback callback) + public void onDataAvailable(Stream stream) { - callbacks.offer(callback); - if (frame.isEndStream()) + Stream.Data data = stream.readData(); + // Do not release. + dataQueue.offer(data); + stream.demand(); + if (data.frame().isEndStream()) latch.countDown(); } }); @@ -185,16 +187,16 @@ public class FlowControlStalledTest stallLatch.set(new CountDownLatch(1)); request = newRequest("GET", "/", HttpFields.EMPTY); - client.newStream(new HeadersFrame(request, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter()); + client.newStream(new HeadersFrame(request, null, true), new Promise.Adapter<>(), null); assertFalse(stallLatch.get().await(1, TimeUnit.SECONDS)); // Consume all data. while (!latch.await(10, TimeUnit.MILLISECONDS)) { - Callback callback = callbacks.poll(); - if (callback != null) - callback.succeeded(); + Stream.Data data = dataQueue.poll(); + if (data != null) + data.release(); } // Make sure the unstall callback is invoked. @@ -209,19 +211,19 @@ public class FlowControlStalledTest start(() -> new BufferingFlowControlStrategy(0.5f) { @Override - public void onSessionStalled(ISession session) + public void onSessionStalled(Session session) { super.onSessionStalled(session); stallLatch.get().countDown(); } @Override - protected void onSessionUnstalled(ISession session) + protected void onSessionUnstalled(Session session) { super.onSessionUnstalled(session); unstallLatch.countDown(); } - }, new ServerSessionListener.Adapter() + }, new ServerSessionListener() { @Override public Stream.Listener onNewStream(Stream stream, HeadersFrame frame) @@ -247,13 +249,14 @@ public class FlowControlStalledTest stream.headers(new HeadersFrame(stream.getId(), response, null, true), Callback.NOOP); } + stream.demand(); return null; } }); // Use a large stream window so that only the session gets stalled. client.setInitialStreamRecvWindow(5 * FlowControlStrategy.DEFAULT_WINDOW_SIZE); - Session session = newClient(new Session.Listener.Adapter() + Session session = newClient(new Session.Listener() { @Override public Map onPreface(Session session) @@ -265,15 +268,18 @@ public class FlowControlStalledTest }); CountDownLatch latch = new CountDownLatch(1); - Queue callbacks = new ArrayDeque<>(); + Queue dataQueue = new ArrayDeque<>(); MetaData.Request request = newRequest("GET", "/stall", HttpFields.EMPTY); - session.newStream(new HeadersFrame(request, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter() + session.newStream(new HeadersFrame(request, null, true), new Promise.Adapter<>(), new Stream.Listener() { @Override - public void onData(Stream stream, DataFrame frame, Callback callback) + public void onDataAvailable(Stream stream) { - callbacks.offer(callback); - if (frame.isEndStream()) + Stream.Data data = stream.readData(); + // Do not release. + dataQueue.offer(data); + stream.demand(); + if (data.frame().isEndStream()) latch.countDown(); } }); @@ -285,16 +291,16 @@ public class FlowControlStalledTest stallLatch.set(new CountDownLatch(1)); request = newRequest("GET", "/", HttpFields.EMPTY); - session.newStream(new HeadersFrame(request, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter()); + session.newStream(new HeadersFrame(request, null, true), new Promise.Adapter<>(), null); assertFalse(stallLatch.get().await(1, TimeUnit.SECONDS)); - // Consume all data. + // Release all data. while (!latch.await(10, TimeUnit.MILLISECONDS)) { - Callback callback = callbacks.poll(); - if (callback != null) - callback.succeeded(); + Stream.Data data = dataQueue.poll(); + if (data != null) + data.release(); } // Make sure the unstall callback is invoked. diff --git a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/FlowControlStrategyTest.java b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/FlowControlStrategyTest.java index 613ff8bd32b..ba1f88a6b20 100644 --- a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/FlowControlStrategyTest.java +++ b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/FlowControlStrategyTest.java @@ -37,8 +37,6 @@ import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.http.MetaData; import org.eclipse.jetty.http2.BufferingFlowControlStrategy; import org.eclipse.jetty.http2.FlowControlStrategy; -import org.eclipse.jetty.http2.ISession; -import org.eclipse.jetty.http2.IStream; import org.eclipse.jetty.http2.api.Session; import org.eclipse.jetty.http2.api.Stream; import org.eclipse.jetty.http2.api.server.ServerSessionListener; @@ -137,7 +135,7 @@ public abstract class FlowControlStrategyTest CountDownLatch stream1Latch = new CountDownLatch(1); CountDownLatch stream2Latch = new CountDownLatch(1); CountDownLatch settingsLatch = new CountDownLatch(1); - start(new ServerSessionListener.Adapter() + start(new ServerSessionListener() { @Override public Map onPreface(Session session) @@ -182,7 +180,7 @@ public abstract class FlowControlStrategyTest } }); - HTTP2Session clientSession = (HTTP2Session)newClient(new Session.Listener.Adapter()); + HTTP2Session clientSession = (HTTP2Session)newClient(new Session.Listener() {}); assertEquals(FlowControlStrategy.DEFAULT_WINDOW_SIZE, clientSession.getSendWindow()); assertEquals(FlowControlStrategy.DEFAULT_WINDOW_SIZE, clientSession.getRecvWindow()); @@ -190,7 +188,7 @@ public abstract class FlowControlStrategyTest MetaData.Request request1 = newRequest("GET", HttpFields.EMPTY); FuturePromise promise1 = new FuturePromise<>(); - clientSession.newStream(new HeadersFrame(request1, null, true), promise1, new Stream.Listener.Adapter()); + clientSession.newStream(new HeadersFrame(request1, null, true), promise1, null); HTTP2Stream clientStream1 = (HTTP2Stream)promise1.get(5, TimeUnit.SECONDS); assertEquals(FlowControlStrategy.DEFAULT_WINDOW_SIZE, clientStream1.getSendWindow()); @@ -214,7 +212,7 @@ public abstract class FlowControlStrategyTest // Now create a new stream, it must pick up the new value. MetaData.Request request2 = newRequest("POST", HttpFields.EMPTY); FuturePromise promise2 = new FuturePromise<>(); - clientSession.newStream(new HeadersFrame(request2, null, true), promise2, new Stream.Listener.Adapter()); + clientSession.newStream(new HeadersFrame(request2, null, true), promise2, null); HTTP2Stream clientStream2 = (HTTP2Stream)promise2.get(5, TimeUnit.SECONDS); assertEquals(FlowControlStrategy.DEFAULT_WINDOW_SIZE, clientStream2.getSendWindow()); @@ -233,8 +231,8 @@ public abstract class FlowControlStrategyTest // We get 3 data frames: the first of 1024 and 2 of 512 each // after the flow control window has been reduced. CountDownLatch dataLatch = new CountDownLatch(3); - AtomicReference callbackRef = new AtomicReference<>(); - start(new ServerSessionListener.Adapter() + AtomicReference dataRef = new AtomicReference<>(); + start(new ServerSessionListener() { @Override public Stream.Listener onNewStream(Stream stream, HeadersFrame requestFrame) @@ -242,29 +240,31 @@ public abstract class FlowControlStrategyTest MetaData.Response response = new MetaData.Response(HttpVersion.HTTP_2, 200, HttpFields.EMPTY); HeadersFrame responseFrame = new HeadersFrame(stream.getId(), response, null, true); stream.headers(responseFrame, Callback.NOOP); - - return new Stream.Listener.Adapter() + stream.demand(); + return new Stream.Listener() { private final AtomicInteger dataFrames = new AtomicInteger(); @Override - public void onData(Stream stream, DataFrame frame, Callback callback) + public void onDataAvailable(Stream stream) { + Stream.Data data = stream.readData(); dataLatch.countDown(); int dataFrameCount = dataFrames.incrementAndGet(); if (dataFrameCount == 1) { - callbackRef.set(callback); + dataRef.set(data); Map settings = new HashMap<>(); settings.put(SettingsFrame.INITIAL_WINDOW_SIZE, size); stream.getSession().settings(new SettingsFrame(settings, false), Callback.NOOP); - // Do not succeed the callback here. + // Do not release the data here. } else if (dataFrameCount > 1) { - // Consume the data. - callback.succeeded(); + // Release the data. + data.release(); } + stream.demand(); } }; } @@ -272,7 +272,7 @@ public abstract class FlowControlStrategyTest // Two SETTINGS frames, the initial one and the one we send from the server. CountDownLatch settingsLatch = new CountDownLatch(2); - Session session = newClient(new Session.Listener.Adapter() + Session session = newClient(new Session.Listener() { @Override public void onSettings(Session session, SettingsFrame frame) @@ -283,7 +283,7 @@ public abstract class FlowControlStrategyTest MetaData.Request request = newRequest("POST", HttpFields.EMPTY); FuturePromise promise = new FuturePromise<>(); - session.newStream(new HeadersFrame(request, null, false), promise, new Stream.Listener.Adapter()); + session.newStream(new HeadersFrame(request, null, false), promise, null); Stream stream = promise.get(5, TimeUnit.SECONDS); // Send first chunk that exceeds the window. @@ -299,8 +299,8 @@ public abstract class FlowControlStrategyTest assertFalse(dataLatch.await(1, TimeUnit.SECONDS)); - // Consume the data arrived to server, this will resume flow control on the client. - callbackRef.get().succeeded(); + // Release the data arrived to server, this will resume flow control on the client. + dataRef.get().release(); assertTrue(dataLatch.await(5, TimeUnit.SECONDS)); } @@ -311,7 +311,7 @@ public abstract class FlowControlStrategyTest int windowSize = 1536; int length = 5 * windowSize; CountDownLatch settingsLatch = new CountDownLatch(2); - start(new ServerSessionListener.Adapter() + start(new ServerSessionListener() { @Override public void onSettings(Session session, SettingsFrame frame) @@ -335,7 +335,7 @@ public abstract class FlowControlStrategyTest } }); - Session session = newClient(new Session.Listener.Adapter()); + Session session = newClient(new Session.Listener() {}); Map settings = new HashMap<>(); settings.put(SettingsFrame.INITIAL_WINDOW_SIZE, windowSize); @@ -346,30 +346,33 @@ public abstract class FlowControlStrategyTest assertTrue(settingsLatch.await(5, TimeUnit.SECONDS)); CountDownLatch dataLatch = new CountDownLatch(1); - Exchanger exchanger = new Exchanger<>(); + Exchanger exchanger = new Exchanger<>(); MetaData.Request metaData = newRequest("GET", HttpFields.EMPTY); HeadersFrame requestFrame = new HeadersFrame(metaData, null, true); - session.newStream(requestFrame, new Promise.Adapter<>(), new Stream.Listener.Adapter() + session.newStream(requestFrame, new Promise.Adapter<>(), new Stream.Listener() { private final AtomicInteger dataFrames = new AtomicInteger(); @Override - public void onData(Stream stream, DataFrame frame, Callback callback) + public void onDataAvailable(Stream stream) { + Stream.Data data = stream.readData(); try { int dataFrames = this.dataFrames.incrementAndGet(); if (dataFrames == 1 || dataFrames == 2) { - // Do not consume the data frame. + // Do not release the Data. // We should then be flow-control stalled. - exchanger.exchange(callback); + exchanger.exchange(data); + stream.demand(); } else if (dataFrames == 3 || dataFrames == 4 || dataFrames == 5) { // Consume totally. - callback.succeeded(); - if (frame.isEndStream()) + data.release(); + stream.demand(); + if (data.frame().isEndStream()) dataLatch.countDown(); } else @@ -379,22 +382,22 @@ public abstract class FlowControlStrategyTest } catch (InterruptedException x) { - callback.failed(x); + data.release(); } } }); - Callback callback = exchanger.exchange(null, 5, TimeUnit.SECONDS); + Stream.Data data = exchanger.exchange(null, 5, TimeUnit.SECONDS); checkThatWeAreFlowControlStalled(exchanger); - // Consume the first chunk. - callback.succeeded(); + // Release the first chunk. + data.release(); - callback = exchanger.exchange(null, 5, TimeUnit.SECONDS); + data = exchanger.exchange(null, 5, TimeUnit.SECONDS); checkThatWeAreFlowControlStalled(exchanger); - // Consume the second chunk. - callback.succeeded(); + // Release the second chunk. + data.release(); assertTrue(dataLatch.await(5, TimeUnit.SECONDS)); } @@ -403,10 +406,10 @@ public abstract class FlowControlStrategyTest public void testClientFlowControlOneBigWrite() throws Exception { int windowSize = 1536; - Exchanger exchanger = new Exchanger<>(); + Exchanger exchanger = new Exchanger<>(); CountDownLatch settingsLatch = new CountDownLatch(1); CountDownLatch dataLatch = new CountDownLatch(1); - start(new ServerSessionListener.Adapter() + start(new ServerSessionListener() { @Override public Map onPreface(Session session) @@ -422,13 +425,15 @@ public abstract class FlowControlStrategyTest MetaData.Response metaData = new MetaData.Response(HttpVersion.HTTP_2, 200, HttpFields.EMPTY); HeadersFrame responseFrame = new HeadersFrame(stream.getId(), metaData, null, true); stream.headers(responseFrame, Callback.NOOP); - return new Stream.Listener.Adapter() + stream.demand(); + return new Stream.Listener() { private final AtomicInteger dataFrames = new AtomicInteger(); @Override - public void onData(Stream stream, DataFrame frame, Callback callback) + public void onDataAvailable(Stream stream) { + Stream.Data data = stream.readData(); try { int dataFrames = this.dataFrames.incrementAndGet(); @@ -436,13 +441,15 @@ public abstract class FlowControlStrategyTest { // Do not consume the data frame. // We should then be flow-control stalled. - exchanger.exchange(callback); + exchanger.exchange(data); + stream.demand(); } else if (dataFrames == 3 || dataFrames == 4 || dataFrames == 5) { // Consume totally. - callback.succeeded(); - if (frame.isEndStream()) + data.release(); + stream.demand(); + if (data.frame().isEndStream()) dataLatch.countDown(); } else @@ -452,14 +459,14 @@ public abstract class FlowControlStrategyTest } catch (InterruptedException x) { - callback.failed(x); + data.release(); } } }; } }); - Session session = newClient(new Session.Listener.Adapter() + Session session = newClient(new Session.Listener() { @Override public void onSettings(Session session, SettingsFrame frame) @@ -480,22 +487,22 @@ public abstract class FlowControlStrategyTest DataFrame dataFrame = new DataFrame(stream.getId(), ByteBuffer.allocate(length), true); stream.data(dataFrame, Callback.NOOP); - Callback callback = exchanger.exchange(null, 5, TimeUnit.SECONDS); + Stream.Data data = exchanger.exchange(null, 5, TimeUnit.SECONDS); checkThatWeAreFlowControlStalled(exchanger); // Consume the first chunk. - callback.succeeded(); + data.release(); - callback = exchanger.exchange(null, 5, TimeUnit.SECONDS); + data = exchanger.exchange(null, 5, TimeUnit.SECONDS); checkThatWeAreFlowControlStalled(exchanger); // Consume the second chunk. - callback.succeeded(); + data.release(); assertTrue(dataLatch.await(5, TimeUnit.SECONDS)); } - private void checkThatWeAreFlowControlStalled(Exchanger exchanger) + private void checkThatWeAreFlowControlStalled(Exchanger exchanger) { assertThrows(TimeoutException.class, () -> exchanger.exchange(null, 1, TimeUnit.SECONDS)); @@ -505,51 +512,56 @@ public abstract class FlowControlStrategyTest public void testSessionStalledStallsNewStreams() throws Exception { int windowSize = 1024; - start(new ServerSessionListener.Adapter() + start(new ServerSessionListener() { @Override public Stream.Listener onNewStream(Stream stream, HeadersFrame requestFrame) { MetaData.Request request = (MetaData.Request)requestFrame.getMetaData(); - if ("POST".equalsIgnoreCase(request.getMethod())) + if (HttpMethod.POST.is(request.getMethod())) { - // Send data to consume most of the session window. - ByteBuffer data = ByteBuffer.allocate(FlowControlStrategy.DEFAULT_WINDOW_SIZE - windowSize); - DataFrame dataFrame = new DataFrame(stream.getId(), data, true); - stream.data(dataFrame, Callback.NOOP); + MetaData.Response metaData = new MetaData.Response(HttpVersion.HTTP_2, 200, HttpFields.EMPTY); + stream.headers(new HeadersFrame(stream.getId(), metaData, null, false)) + .thenCompose(s -> + { + // Send data to consume most of the session window. + ByteBuffer data = ByteBuffer.allocate(FlowControlStrategy.DEFAULT_WINDOW_SIZE - windowSize); + DataFrame dataFrame = new DataFrame(s.getId(), data, true); + return s.data(dataFrame); + }); return null; } else { // For every stream, send down half the window size of data. MetaData.Response metaData = new MetaData.Response(HttpVersion.HTTP_2, 200, HttpFields.EMPTY); - HeadersFrame responseFrame = new HeadersFrame(stream.getId(), metaData, null, false); - Callback.Completable completable = new Callback.Completable(); - stream.headers(responseFrame, completable); - completable.thenRun(() -> - { - DataFrame dataFrame = new DataFrame(stream.getId(), ByteBuffer.allocate(windowSize / 2), true); - stream.data(dataFrame, Callback.NOOP); - }); + stream.headers(new HeadersFrame(stream.getId(), metaData, null, false)) + .thenCompose(s -> + { + DataFrame dataFrame = new DataFrame(s.getId(), ByteBuffer.allocate(windowSize / 2), true); + return s.data(dataFrame); + }); return null; } } }); - Session session = newClient(new Session.Listener.Adapter()); + Session session = newClient(new Session.Listener() {}); // First request is just to consume most of the session window. - List callbacks1 = new ArrayList<>(); + List dataList1 = new ArrayList<>(); CountDownLatch prepareLatch = new CountDownLatch(1); MetaData.Request request1 = newRequest("POST", HttpFields.EMPTY); - session.newStream(new HeadersFrame(request1, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter() + session.newStream(new HeadersFrame(request1, null, true), new Promise.Adapter<>(), new Stream.Listener() { @Override - public void onData(Stream stream, DataFrame frame, Callback callback) + public void onDataAvailable(Stream stream) { + Stream.Data data = stream.readData(); // Do not consume the data to reduce the session window. - callbacks1.add(callback); - if (frame.isEndStream()) + dataList1.add(data); + stream.demand(); + if (data.frame().isEndStream()) prepareLatch.countDown(); } }); @@ -557,37 +569,43 @@ public abstract class FlowControlStrategyTest // Second request will consume half of the remaining the session window. MetaData.Request request2 = newRequest("GET", HttpFields.EMPTY); - session.newStream(new HeadersFrame(request2, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter() + session.newStream(new HeadersFrame(request2, null, true), new Promise.Adapter<>(), new Stream.Listener() { @Override - public void onData(Stream stream, DataFrame frame, Callback callback) + public void onDataAvailable(Stream stream) { - // Do not consume it to stall flow control. + stream.readData(); + stream.demand(); + // Do not release it to stall flow control. } }); // Third request will consume the whole session window, which is now stalled. // A fourth request will not be able to receive data. MetaData.Request request3 = newRequest("GET", HttpFields.EMPTY); - session.newStream(new HeadersFrame(request3, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter() + session.newStream(new HeadersFrame(request3, null, true), new Promise.Adapter<>(), new Stream.Listener() { @Override - public void onData(Stream stream, DataFrame frame, Callback callback) + public void onDataAvailable(Stream stream) { - // Do not consume it to stall flow control. + stream.readData(); + stream.demand(); + // Do not release it to stall flow control. } }); // Fourth request is now stalled. CountDownLatch latch = new CountDownLatch(1); MetaData.Request request4 = newRequest("GET", HttpFields.EMPTY); - session.newStream(new HeadersFrame(request4, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter() + session.newStream(new HeadersFrame(request4, null, true), new Promise.Adapter<>(), new Stream.Listener() { @Override - public void onData(Stream stream, DataFrame frame, Callback callback) + public void onDataAvailable(Stream stream) { - callback.succeeded(); - if (frame.isEndStream()) + Stream.Data data = stream.readData(); + data.release(); + stream.demand(); + if (data.frame().isEndStream()) latch.countDown(); } }); @@ -597,10 +615,7 @@ public abstract class FlowControlStrategyTest // Consume the data of the first response. // This will open up the session window, allowing the fourth stream to send data. - for (Callback callback : callbacks1) - { - callback.succeeded(); - } + dataList1.forEach(Stream.Data::release); assertTrue(latch.await(5, TimeUnit.SECONDS)); } @@ -611,7 +626,7 @@ public abstract class FlowControlStrategyTest byte[] data = new byte[1024 * 1024]; new Random().nextBytes(data); - start(new ServerSessionListener.Adapter() + start(new ServerSessionListener() { @Override public Stream.Listener onNewStream(Stream stream, HeadersFrame requestFrame) @@ -629,22 +644,25 @@ public abstract class FlowControlStrategyTest } }); - Session session = newClient(new Session.Listener.Adapter()); + Session session = newClient(new Session.Listener() {}); MetaData.Request metaData = newRequest("GET", HttpFields.EMPTY); HeadersFrame requestFrame = new HeadersFrame(metaData, null, true); byte[] bytes = new byte[data.length]; CountDownLatch latch = new CountDownLatch(1); - session.newStream(requestFrame, new Promise.Adapter<>(), new Stream.Listener.Adapter() + session.newStream(requestFrame, new Promise.Adapter<>(), new Stream.Listener() { private int received; @Override - public void onData(Stream stream, DataFrame frame, Callback callback) + public void onDataAvailable(Stream stream) { + Stream.Data data = stream.readData(); + DataFrame frame = data.frame(); int remaining = frame.remaining(); frame.getData().get(bytes, received, remaining); this.received += remaining; - callback.succeeded(); + data.release(); + stream.demand(); if (frame.isEndStream()) latch.countDown(); } @@ -657,7 +675,7 @@ public abstract class FlowControlStrategyTest @Test public void testClientSendingInitialSmallWindow() throws Exception { - start(new ServerSessionListener.Adapter() + start(new ServerSessionListener() { @Override public Stream.Listener onNewStream(Stream stream, HeadersFrame frame) @@ -666,19 +684,21 @@ public abstract class FlowControlStrategyTest HeadersFrame responseFrame = new HeadersFrame(stream.getId(), metaData, null, false); Callback.Completable completable = new Callback.Completable(); stream.headers(responseFrame, completable); - return new Stream.Listener.Adapter() + stream.demand(); + return new Stream.Listener() { @Override - public void onData(Stream stream, DataFrame frame, Callback callback) + public void onDataAvailable(Stream stream) { - completable.thenRun(() -> stream.data(frame, callback)); + Stream.Data data = stream.readData(); + completable.thenRun(() -> stream.data(data.frame(), Callback.from(Callback.from(data::release), stream::demand))); } }; } }); int initialWindow = 16; - Session session = newClient(new Session.Listener.Adapter() + Session session = newClient(new Session.Listener() { @Override public Map onPreface(Session session) @@ -698,14 +718,16 @@ public abstract class FlowControlStrategyTest HeadersFrame requestFrame = new HeadersFrame(metaData, null, false); Promise.Completable completable = new Promise.Completable<>(); CountDownLatch latch = new CountDownLatch(1); - session.newStream(requestFrame, completable, new Stream.Listener.Adapter() + session.newStream(requestFrame, completable, new Stream.Listener() { @Override - public void onData(Stream stream, DataFrame frame, Callback callback) + public void onDataAvailable(Stream stream) { - responseContent.put(frame.getData()); - callback.succeeded(); - if (frame.isEndStream()) + Stream.Data data = stream.readData(); + responseContent.put(data.frame().getData()); + data.release(); + stream.demand(); + if (data.frame().isEndStream()) latch.countDown(); } }); @@ -727,31 +749,34 @@ public abstract class FlowControlStrategyTest { // On server, we don't consume the data. CountDownLatch serverCloseLatch = new CountDownLatch(1); - start(new ServerSessionListener.Adapter() + start(new ServerSessionListener() { @Override public Stream.Listener onNewStream(Stream stream, HeadersFrame frame) { - return new Stream.Listener.Adapter() + return new Stream.Listener() { @Override - public void onData(Stream stream, DataFrame frame, Callback callback) + public void onDataAvailable(Stream stream) { - // Do not succeed the callback. + // Read but do not release the Data. + stream.readData(); + stream.demand(); } }; } @Override - public void onClose(Session session, GoAwayFrame frame) + public void onClose(Session session, GoAwayFrame frame, Callback callback) { serverCloseLatch.countDown(); + callback.succeeded(); } }); CountDownLatch clientGoAwayLatch = new CountDownLatch(1); CountDownLatch clientCloseLatch = new CountDownLatch(1); - Session session = newClient(new Session.Listener.Adapter() + Session session = newClient(new Session.Listener() { @Override public void onGoAway(Session session, GoAwayFrame frame) @@ -761,9 +786,10 @@ public abstract class FlowControlStrategyTest } @Override - public void onClose(Session session, GoAwayFrame frame) + public void onClose(Session session, GoAwayFrame frame, Callback callback) { clientCloseLatch.countDown(); + callback.succeeded(); } }); @@ -771,7 +797,7 @@ public abstract class FlowControlStrategyTest MetaData.Request metaData = newRequest("POST", HttpFields.EMPTY); HeadersFrame requestFrame = new HeadersFrame(metaData, null, false); CompletableFuture completable = new CompletableFuture<>(); - session.newStream(requestFrame, Promise.from(completable), new Stream.Listener.Adapter()); + session.newStream(requestFrame, Promise.from(completable), null); Stream stream = completable.get(5, TimeUnit.SECONDS); ByteBuffer data = ByteBuffer.allocate(FlowControlStrategy.DEFAULT_WINDOW_SIZE); CountDownLatch dataLatch = new CountDownLatch(1); @@ -819,39 +845,42 @@ public abstract class FlowControlStrategyTest { // On server, we don't consume the data. CountDownLatch serverCloseLatch = new CountDownLatch(1); - start(new ServerSessionListener.Adapter() + start(new ServerSessionListener() { @Override public Map onPreface(Session session) { // Enlarge the session window. - ((ISession)session).updateRecvWindow(FlowControlStrategy.DEFAULT_WINDOW_SIZE); - return super.onPreface(session); + ((HTTP2Session)session).updateRecvWindow(FlowControlStrategy.DEFAULT_WINDOW_SIZE); + return null; } @Override public Stream.Listener onNewStream(Stream stream, HeadersFrame frame) { - return new Stream.Listener.Adapter() + return new Stream.Listener() { @Override - public void onData(Stream stream, DataFrame frame, Callback callback) + public void onDataAvailable(Stream stream) { - // Do not succeed the callback. + // Read but do not release the Data. + stream.readData(); + stream.demand(); } }; } @Override - public void onClose(Session session, GoAwayFrame frame) + public void onClose(Session session, GoAwayFrame frame, Callback callback) { serverCloseLatch.countDown(); + callback.succeeded(); } }); CountDownLatch clientGoAwayLatch = new CountDownLatch(1); CountDownLatch clientCloseLatch = new CountDownLatch(1); - Session session = newClient(new Session.Listener.Adapter() + Session session = newClient(new Session.Listener() { @Override public void onGoAway(Session session, GoAwayFrame frame) @@ -861,9 +890,10 @@ public abstract class FlowControlStrategyTest } @Override - public void onClose(Session session, GoAwayFrame frame) + public void onClose(Session session, GoAwayFrame frame, Callback callback) { clientCloseLatch.countDown(); + callback.succeeded(); } }); @@ -871,7 +901,7 @@ public abstract class FlowControlStrategyTest MetaData.Request metaData = newRequest("POST", HttpFields.EMPTY); HeadersFrame requestFrame = new HeadersFrame(metaData, null, false); FuturePromise streamPromise = new FuturePromise<>(); - session.newStream(requestFrame, streamPromise, new Stream.Listener.Adapter()); + session.newStream(requestFrame, streamPromise, null); Stream stream = streamPromise.get(5, TimeUnit.SECONDS); ByteBuffer data = ByteBuffer.allocate(FlowControlStrategy.DEFAULT_WINDOW_SIZE); CountDownLatch dataLatch = new CountDownLatch(1); @@ -914,43 +944,44 @@ public abstract class FlowControlStrategyTest public void testFlowControlWhenServerResetsStream() throws Exception { // On server, don't consume the data and immediately reset. - start(new ServerSessionListener.Adapter() + start(new ServerSessionListener() { @Override public Stream.Listener onNewStream(Stream stream, HeadersFrame frame) { MetaData.Request request = (MetaData.Request)frame.getMetaData(); - if (HttpMethod.GET.is(request.getMethod())) - return new Stream.Listener.Adapter(); - - return new Stream.Listener.Adapter() + return null; + stream.demand(); + return new Stream.Listener() { @Override - public void onData(Stream stream, DataFrame frame, Callback callback) + public void onDataAvailable(Stream stream) { - // Fail the callback to enlarge the session window. + Stream.Data data = stream.readData(); + // Release the data to enlarge the session window. // More data frames will be discarded because the // stream is reset, and automatically consumed to // keep the session window large for other streams. - callback.failed(new Throwable()); + data.release(); stream.reset(new ResetFrame(stream.getId(), ErrorCode.CANCEL_STREAM_ERROR.code), Callback.NOOP); } }; } }); - Session session = newClient(new Session.Listener.Adapter()); + Session session = newClient(new Session.Listener() {}); MetaData.Request metaData = newRequest("POST", HttpFields.EMPTY); HeadersFrame frame = new HeadersFrame(metaData, null, false); FuturePromise streamPromise = new FuturePromise<>(); CountDownLatch resetLatch = new CountDownLatch(1); - session.newStream(frame, streamPromise, new Stream.Listener.Adapter() + session.newStream(frame, streamPromise, new Stream.Listener() { @Override - public void onReset(Stream stream, ResetFrame frame) + public void onReset(Stream stream, ResetFrame frame, Callback callback) { resetLatch.countDown(); + callback.succeeded(); } }); Stream stream = streamPromise.get(5, TimeUnit.SECONDS); @@ -980,22 +1011,25 @@ public abstract class FlowControlStrategyTest @Test public void testNoWindowUpdateForRemotelyClosedStream() throws Exception { - List callbacks = new ArrayList<>(); - start(new ServerSessionListener.Adapter() + List dataList = new ArrayList<>(); + start(new ServerSessionListener() { @Override public Stream.Listener onNewStream(Stream stream, HeadersFrame frame) { - return new Stream.Listener.Adapter() + stream.demand(); + return new Stream.Listener() { @Override - public void onData(Stream stream, DataFrame frame, Callback callback) + public void onDataAvailable(Stream stream) { - callbacks.add(callback); - if (frame.isEndStream()) + Stream.Data data = stream.readData(); + dataList.add(data); + stream.demand(); + if (data.frame().isEndStream()) { - // Succeed the callbacks when the stream is already remotely closed. - callbacks.forEach(Callback::succeeded); + // Release the Data when the stream is already remotely closed. + dataList.forEach(Stream.Data::release); MetaData.Response response = new MetaData.Response(HttpVersion.HTTP_2, HttpStatus.OK_200, HttpFields.EMPTY); stream.headers(new HeadersFrame(stream.getId(), response, null, true), Callback.NOOP); } @@ -1009,7 +1043,7 @@ public abstract class FlowControlStrategyTest client.setFlowControlStrategyFactory(() -> new BufferingFlowControlStrategy(0.5F) { @Override - public void onWindowUpdate(ISession session, IStream stream, WindowUpdateFrame frame) + public void onWindowUpdate(Session session, Stream stream, WindowUpdateFrame frame) { if (frame.getStreamId() == 0) sessionWindowUpdates.add(frame); @@ -1019,12 +1053,12 @@ public abstract class FlowControlStrategyTest } }); - Session session = newClient(new Session.Listener.Adapter()); + Session session = newClient(new Session.Listener() {}); MetaData.Request metaData = newRequest("POST", HttpFields.EMPTY); HeadersFrame frame = new HeadersFrame(metaData, null, false); FuturePromise streamPromise = new FuturePromise<>(); CountDownLatch latch = new CountDownLatch(1); - session.newStream(frame, streamPromise, new Stream.Listener.Adapter() + session.newStream(frame, streamPromise, new Stream.Listener() { @Override public void onHeaders(Stream stream, HeadersFrame frame) diff --git a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/FlowControlWindowsTest.java b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/FlowControlWindowsTest.java index b989a8ddcc6..16f35147d5b 100644 --- a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/FlowControlWindowsTest.java +++ b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/FlowControlWindowsTest.java @@ -24,13 +24,13 @@ import org.eclipse.jetty.http.HttpMethod; import org.eclipse.jetty.http.HttpScheme; import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.http.MetaData; -import org.eclipse.jetty.http2.ISession; -import org.eclipse.jetty.http2.IStream; import org.eclipse.jetty.http2.api.Session; import org.eclipse.jetty.http2.api.Stream; import org.eclipse.jetty.http2.api.server.ServerSessionListener; import org.eclipse.jetty.http2.client.HTTP2Client; import org.eclipse.jetty.http2.frames.HeadersFrame; +import org.eclipse.jetty.http2.internal.HTTP2Session; +import org.eclipse.jetty.http2.internal.HTTP2Stream; import org.eclipse.jetty.http2.server.RawHTTP2ServerConnectionFactory; import org.eclipse.jetty.server.HttpConfiguration; import org.eclipse.jetty.server.Server; @@ -84,22 +84,22 @@ public class FlowControlWindowsTest server.stop(); } - protected ISession newClient(Session.Listener listener) throws Exception + protected HTTP2Session newClient(Session.Listener listener) throws Exception { String host = "localhost"; int port = connector.getLocalPort(); InetSocketAddress address = new InetSocketAddress(host, port); FuturePromise promise = new FuturePromise<>(); client.connect(address, listener, promise); - return (ISession)promise.get(5, TimeUnit.SECONDS); + return (HTTP2Session)promise.get(5, TimeUnit.SECONDS); } @Test public void testClientFlowControlWindows() throws Exception { - start(new ServerSessionListener.Adapter()); + start(new ServerSessionListener() {}); - ISession clientSession = newClient(new Session.Listener.Adapter()); + HTTP2Session clientSession = newClient(new Session.Listener() {}); // Wait while client and server exchange SETTINGS and WINDOW_UPDATE frames. Thread.sleep(1000); @@ -112,8 +112,8 @@ public class FlowControlWindowsTest MetaData.Request request = new MetaData.Request(HttpMethod.GET.asString(), HttpScheme.HTTP.asString(), hostPort, "/", HttpVersion.HTTP_2, HttpFields.EMPTY, -1); HeadersFrame frame = new HeadersFrame(request, null, true); FuturePromise promise = new FuturePromise<>(); - clientSession.newStream(frame, promise, new Stream.Listener.Adapter()); - IStream clientStream = (IStream)promise.get(5, TimeUnit.SECONDS); + clientSession.newStream(frame, promise, null); + HTTP2Stream clientStream = (HTTP2Stream)promise.get(5, TimeUnit.SECONDS); int streamSendWindow = clientStream.updateSendWindow(0); assertEquals(serverStreamRecvWindow, streamSendWindow); @@ -124,32 +124,32 @@ public class FlowControlWindowsTest @Test public void testServerFlowControlWindows() throws Exception { - AtomicReference sessionRef = new AtomicReference<>(); + AtomicReference sessionRef = new AtomicReference<>(); CountDownLatch sessionLatch = new CountDownLatch(1); - AtomicReference streamRef = new AtomicReference<>(); + AtomicReference streamRef = new AtomicReference<>(); CountDownLatch streamLatch = new CountDownLatch(1); - start(new ServerSessionListener.Adapter() + start(new ServerSessionListener() { @Override public void onAccept(Session session) { - sessionRef.set((ISession)session); + sessionRef.set((HTTP2Session)session); sessionLatch.countDown(); } @Override public Stream.Listener onNewStream(Stream stream, HeadersFrame frame) { - streamRef.set((IStream)stream); + streamRef.set((HTTP2Stream)stream); streamLatch.countDown(); return null; } }); - ISession clientSession = newClient(new Session.Listener.Adapter()); + HTTP2Session clientSession = newClient(new Session.Listener() {}); assertTrue(sessionLatch.await(5, TimeUnit.SECONDS)); - ISession serverSession = sessionRef.get(); + HTTP2Session serverSession = sessionRef.get(); // Wait while client and server exchange SETTINGS and WINDOW_UPDATE frames. Thread.sleep(1000); @@ -161,10 +161,10 @@ public class FlowControlWindowsTest HostPortHttpField hostPort = new HostPortHttpField("localhost:" + connector.getLocalPort()); MetaData.Request request = new MetaData.Request(HttpMethod.GET.asString(), HttpScheme.HTTP.asString(), hostPort, "/", HttpVersion.HTTP_2, HttpFields.EMPTY, -1); HeadersFrame frame = new HeadersFrame(request, null, true); - clientSession.newStream(frame, new Promise.Adapter<>(), new Stream.Listener.Adapter()); + clientSession.newStream(frame, new Promise.Adapter<>(), null); assertTrue(streamLatch.await(5, TimeUnit.SECONDS)); - IStream serverStream = streamRef.get(); + HTTP2Stream serverStream = streamRef.get(); int streamSendWindow = serverStream.updateSendWindow(0); assertEquals(clientStreamRecvWindow, streamSendWindow); diff --git a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/GoAwayTest.java b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/GoAwayTest.java index 95c3eb8c488..09f1c44fb5c 100644 --- a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/GoAwayTest.java +++ b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/GoAwayTest.java @@ -27,8 +27,6 @@ import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.http.MetaData; import org.eclipse.jetty.http2.CloseState; import org.eclipse.jetty.http2.FlowControlStrategy; -import org.eclipse.jetty.http2.ISession; -import org.eclipse.jetty.http2.IStream; import org.eclipse.jetty.http2.SimpleFlowControlStrategy; import org.eclipse.jetty.http2.api.Session; import org.eclipse.jetty.http2.api.Stream; @@ -40,6 +38,7 @@ import org.eclipse.jetty.http2.frames.ResetFrame; import org.eclipse.jetty.http2.frames.SettingsFrame; import org.eclipse.jetty.http2.internal.ErrorCode; import org.eclipse.jetty.http2.internal.HTTP2Session; +import org.eclipse.jetty.http2.internal.HTTP2Stream; import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.FuturePromise; @@ -55,7 +54,7 @@ public class GoAwayTest extends AbstractTest { CountDownLatch serverLatch = new CountDownLatch(1); AtomicReference serverSessionRef = new AtomicReference<>(); - start(new ServerSessionListener.Adapter() + start(new ServerSessionListener() { @Override public Stream.Listener onNewStream(Stream stream, HeadersFrame frame) @@ -67,23 +66,25 @@ public class GoAwayTest extends AbstractTest } @Override - public void onClose(Session session, GoAwayFrame frame) + public void onClose(Session session, GoAwayFrame frame, Callback callback) { serverLatch.countDown(); + callback.succeeded(); } }); CountDownLatch clientLatch = new CountDownLatch(1); - Session clientSession = newClientSession(new Session.Listener.Adapter() + Session clientSession = newClientSession(new Session.Listener() { @Override - public void onClose(Session session, GoAwayFrame frame) + public void onClose(Session session, GoAwayFrame frame, Callback callback) { clientLatch.countDown(); + callback.succeeded(); } }); MetaData.Request request = newRequest(HttpMethod.GET.asString(), HttpFields.EMPTY); - clientSession.newStream(new HeadersFrame(request, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter() + clientSession.newStream(new HeadersFrame(request, null, true), new Promise.Adapter<>(), new Stream.Listener() { @Override public void onHeaders(Stream stream, HeadersFrame frame) @@ -108,7 +109,7 @@ public class GoAwayTest extends AbstractTest AtomicReference serverSessionRef = new AtomicReference<>(); CountDownLatch serverGoAwayLatch = new CountDownLatch(1); CountDownLatch serverCloseLatch = new CountDownLatch(1); - start(new ServerSessionListener.Adapter() + start(new ServerSessionListener() { @Override public Stream.Listener onNewStream(Stream stream, HeadersFrame frame) @@ -126,15 +127,16 @@ public class GoAwayTest extends AbstractTest } @Override - public void onClose(Session session, GoAwayFrame frame) + public void onClose(Session session, GoAwayFrame frame, Callback callback) { serverCloseLatch.countDown(); + callback.succeeded(); } }); CountDownLatch clientGoAwayLatch = new CountDownLatch(1); CountDownLatch clientCloseLatch = new CountDownLatch(1); - Session clientSession = newClientSession(new Session.Listener.Adapter() + Session clientSession = newClientSession(new Session.Listener() { @Override public void onGoAway(Session session, GoAwayFrame frame) @@ -143,15 +145,16 @@ public class GoAwayTest extends AbstractTest } @Override - public void onClose(Session session, GoAwayFrame frame) + public void onClose(Session session, GoAwayFrame frame, Callback callback) { clientCloseLatch.countDown(); + callback.succeeded(); } }); MetaData.Request request1 = newRequest(HttpMethod.GET.asString(), HttpFields.EMPTY); CountDownLatch streamFailureLatch = new CountDownLatch(1); - clientSession.newStream(new HeadersFrame(request1, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter() + clientSession.newStream(new HeadersFrame(request1, null, true), new Promise.Adapter<>(), new Stream.Listener() { @Override public void onHeaders(Stream stream, HeadersFrame frame) @@ -162,7 +165,7 @@ public class GoAwayTest extends AbstractTest // The client sends the second request and should eventually fail it // locally since it has a larger streamId, and the server discarded it. MetaData.Request request2 = newRequest(HttpMethod.GET.asString(), HttpFields.EMPTY); - clientSession.newStream(new HeadersFrame(request2, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter() + clientSession.newStream(new HeadersFrame(request2, null, true), new Promise.Adapter<>(), new Stream.Listener() { @Override public void onFailure(Stream stream, int error, String reason, Throwable failure, Callback callback) @@ -190,7 +193,7 @@ public class GoAwayTest extends AbstractTest CountDownLatch serverGoAwayLatch = new CountDownLatch(1); CountDownLatch serverCloseLatch = new CountDownLatch(1); AtomicReference serverSessionRef = new AtomicReference<>(); - start(new ServerSessionListener.Adapter() + start(new ServerSessionListener() { @Override public Stream.Listener onNewStream(Stream stream, HeadersFrame frame) @@ -208,17 +211,18 @@ public class GoAwayTest extends AbstractTest } @Override - public void onClose(Session session, GoAwayFrame frame) + public void onClose(Session session, GoAwayFrame frame, Callback callback) { if (!frame.isGraceful()) serverCloseLatch.countDown(); + callback.succeeded(); } }); CountDownLatch clientGracefulGoAwayLatch = new CountDownLatch(1); CountDownLatch clientGoAwayLatch = new CountDownLatch(1); CountDownLatch clientCloseLatch = new CountDownLatch(1); - Session clientSession = newClientSession(new Session.Listener.Adapter() + Session clientSession = newClientSession(new Session.Listener() { @Override public void onGoAway(Session session, GoAwayFrame frame) @@ -230,15 +234,16 @@ public class GoAwayTest extends AbstractTest } @Override - public void onClose(Session session, GoAwayFrame frame) + public void onClose(Session session, GoAwayFrame frame, Callback callback) { if (!frame.isGraceful()) clientCloseLatch.countDown(); + callback.succeeded(); } }); CountDownLatch clientLatch = new CountDownLatch(1); MetaData.Request request = newRequest(HttpMethod.GET.asString(), HttpFields.EMPTY); - clientSession.newStream(new HeadersFrame(request, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter() + clientSession.newStream(new HeadersFrame(request, null, true), new Promise.Adapter<>(), new Stream.Listener() { @Override public void onHeaders(Stream stream, HeadersFrame frame) @@ -272,7 +277,7 @@ public class GoAwayTest extends AbstractTest CountDownLatch serverCloseLatch = new CountDownLatch(1); AtomicReference serverSessionRef = new AtomicReference<>(); AtomicReference serverStreamRef = new AtomicReference<>(); - start(new ServerSessionListener.Adapter() + start(new ServerSessionListener() { @Override public Stream.Listener onNewStream(Stream stream, HeadersFrame frame) @@ -294,17 +299,18 @@ public class GoAwayTest extends AbstractTest } @Override - public void onClose(Session session, GoAwayFrame frame) + public void onClose(Session session, GoAwayFrame frame, Callback callback) { if (!frame.isGraceful()) serverCloseLatch.countDown(); + callback.succeeded(); } }); CountDownLatch clientGracefulGoAwayLatch = new CountDownLatch(1); CountDownLatch clientGoAwayLatch = new CountDownLatch(1); CountDownLatch clientCloseLatch = new CountDownLatch(1); - Session clientSession = newClientSession(new Session.Listener.Adapter() + Session clientSession = newClientSession(new Session.Listener() { @Override public void onGoAway(Session session, GoAwayFrame frame) @@ -316,15 +322,16 @@ public class GoAwayTest extends AbstractTest } @Override - public void onClose(Session session, GoAwayFrame frame) + public void onClose(Session session, GoAwayFrame frame, Callback callback) { if (!frame.isGraceful()) clientCloseLatch.countDown(); + callback.succeeded(); } }); CountDownLatch clientLatch = new CountDownLatch(1); MetaData.Request request = newRequest(HttpMethod.GET.asString(), HttpFields.EMPTY); - clientSession.newStream(new HeadersFrame(request, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter() + clientSession.newStream(new HeadersFrame(request, null, true), new Promise.Adapter<>(), new Stream.Listener() { @Override public void onHeaders(Stream stream, HeadersFrame frame) @@ -372,7 +379,7 @@ public class GoAwayTest extends AbstractTest AtomicReference serverSessionRef = new AtomicReference<>(); CountDownLatch serverGoAwayLatch = new CountDownLatch(1); CountDownLatch serverCloseLatch = new CountDownLatch(1); - start(new ServerSessionListener.Adapter() + start(new ServerSessionListener() { @Override public void onAccept(Session session) @@ -383,14 +390,16 @@ public class GoAwayTest extends AbstractTest @Override public Stream.Listener onNewStream(Stream stream, HeadersFrame frame) { + stream.demand(); AtomicInteger dataFrames = new AtomicInteger(); - return new Stream.Listener.Adapter() + return new Stream.Listener() { @Override - public void onData(Stream stream, DataFrame frame, Callback callback) + public void onDataAvailable(Stream stream) { - // Do not consume the data for this stream (i.e. don't succeed the callback). - // Only send the response when receiving the first DATA frame. + stream.readData(); + // Do not release the Data for this stream. + // Only send the response after reading the first DATA frame. if (dataFrames.incrementAndGet() == 1) { MetaData.Response response = new MetaData.Response(HttpVersion.HTTP_2, HttpStatus.OK_200, HttpFields.EMPTY); @@ -407,9 +416,10 @@ public class GoAwayTest extends AbstractTest } @Override - public void onClose(Session session, GoAwayFrame frame) + public void onClose(Session session, GoAwayFrame frame, Callback callback) { serverCloseLatch.countDown(); + callback.succeeded(); } }, h2 -> { @@ -422,7 +432,7 @@ public class GoAwayTest extends AbstractTest CountDownLatch clientSettingsLatch = new CountDownLatch(1); CountDownLatch clientGoAwayLatch = new CountDownLatch(1); CountDownLatch clientCloseLatch = new CountDownLatch(1); - Session clientSession = newClientSession(new Session.Listener.Adapter() + Session clientSession = newClientSession(new Session.Listener() { @Override public void onSettings(Session session, SettingsFrame frame) @@ -437,9 +447,10 @@ public class GoAwayTest extends AbstractTest } @Override - public void onClose(Session session, GoAwayFrame frame) + public void onClose(Session session, GoAwayFrame frame, Callback callback) { clientCloseLatch.countDown(); + callback.succeeded(); } }); @@ -450,12 +461,12 @@ public class GoAwayTest extends AbstractTest // This is necessary because the server session window is smaller than the // default and the server cannot send a WINDOW_UPDATE with a negative value. - ((ISession)clientSession).updateSendWindow(flowControlWindow - FlowControlStrategy.DEFAULT_WINDOW_SIZE); + ((HTTP2Session)clientSession).updateSendWindow(flowControlWindow - FlowControlStrategy.DEFAULT_WINDOW_SIZE); MetaData.Request request1 = newRequest("GET", HttpFields.EMPTY); HeadersFrame headersFrame1 = new HeadersFrame(request1, null, false); DataFrame dataFrame1 = new DataFrame(ByteBuffer.allocate(flowControlWindow / 2), false); - ((ISession)clientSession).newStream(new IStream.FrameList(headersFrame1, dataFrame1, null), new Promise.Adapter<>(), new Stream.Listener.Adapter() + ((HTTP2Session)clientSession).newStream(new HTTP2Stream.FrameList(headersFrame1, dataFrame1, null), new Promise.Adapter<>(), new Stream.Listener() { @Override public void onHeaders(Stream clientStream1, HeadersFrame frame) @@ -470,7 +481,7 @@ public class GoAwayTest extends AbstractTest MetaData.Request request2 = newRequest("POST", HttpFields.EMPTY); HeadersFrame headersFrame2 = new HeadersFrame(request2, null, false); DataFrame dataFrame2 = new DataFrame(ByteBuffer.allocate(flowControlWindow / 2), true); - ((ISession)clientStream1.getSession()).newStream(new IStream.FrameList(headersFrame2, dataFrame2, null), new Promise.Adapter<>() + ((HTTP2Session)clientStream1.getSession()).newStream(new HTTP2Stream.FrameList(headersFrame2, dataFrame2, null), new Promise.Adapter<>() { @Override public void succeeded(Stream clientStream2) @@ -480,7 +491,7 @@ public class GoAwayTest extends AbstractTest // the server and be able to complete this stream. clientStream1.data(new DataFrame(clientStream1.getId(), ByteBuffer.allocate(flowControlWindow / 2), true), Callback.NOOP); } - }, new Adapter()); + }, new Stream.Listener() {}); } }); @@ -500,7 +511,7 @@ public class GoAwayTest extends AbstractTest CountDownLatch serverStreamLatch = new CountDownLatch(1); CountDownLatch serverGoAwayLatch = new CountDownLatch(1); CountDownLatch serverCloseLatch = new CountDownLatch(1); - start(new ServerSessionListener.Adapter() + start(new ServerSessionListener() { @Override public Stream.Listener onNewStream(Stream stream, HeadersFrame frame) @@ -517,15 +528,16 @@ public class GoAwayTest extends AbstractTest } @Override - public void onClose(Session session, GoAwayFrame frame) + public void onClose(Session session, GoAwayFrame frame, Callback callback) { serverCloseLatch.countDown(); + callback.succeeded(); } }); CountDownLatch clientGoAwayLatch = new CountDownLatch(1); CountDownLatch clientCloseLatch = new CountDownLatch(1); - Session clientSession = newClientSession(new Session.Listener.Adapter() + Session clientSession = newClientSession(new Session.Listener() { @Override public void onGoAway(Session session, GoAwayFrame frame) @@ -534,15 +546,16 @@ public class GoAwayTest extends AbstractTest } @Override - public void onClose(Session session, GoAwayFrame frame) + public void onClose(Session session, GoAwayFrame frame, Callback callback) { clientCloseLatch.countDown(); + callback.succeeded(); } }); CountDownLatch clientLatch = new CountDownLatch(1); MetaData.Request request = newRequest(HttpMethod.GET.asString(), HttpFields.EMPTY); - clientSession.newStream(new HeadersFrame(request, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter() + clientSession.newStream(new HeadersFrame(request, null, true), new Promise.Adapter<>(), new Stream.Listener() { @Override public void onHeaders(Stream stream, HeadersFrame frame) @@ -584,7 +597,7 @@ public class GoAwayTest extends AbstractTest AtomicReference serverStreamRef = new AtomicReference<>(); CountDownLatch serverStreamLatch = new CountDownLatch(1); CountDownLatch serverCloseLatch = new CountDownLatch(1); - start(new ServerSessionListener.Adapter() + start(new ServerSessionListener() { @Override public Stream.Listener onNewStream(Stream stream, HeadersFrame frame) @@ -599,15 +612,16 @@ public class GoAwayTest extends AbstractTest } @Override - public void onClose(Session session, GoAwayFrame frame) + public void onClose(Session session, GoAwayFrame frame, Callback callback) { serverCloseLatch.countDown(); + callback.succeeded(); } }); CountDownLatch clientGoAwayLatch = new CountDownLatch(1); CountDownLatch clientCloseLatch = new CountDownLatch(1); - Session clientSession = newClientSession(new Session.Listener.Adapter() + Session clientSession = newClientSession(new Session.Listener() { @Override public void onGoAway(Session session, GoAwayFrame frame) @@ -624,15 +638,16 @@ public class GoAwayTest extends AbstractTest } @Override - public void onClose(Session session, GoAwayFrame frame) + public void onClose(Session session, GoAwayFrame frame, Callback callback) { clientCloseLatch.countDown(); + callback.succeeded(); } }); CountDownLatch clientLatch = new CountDownLatch(1); MetaData.Request request = newRequest(HttpMethod.GET.asString(), HttpFields.EMPTY); - clientSession.newStream(new HeadersFrame(request, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter() + clientSession.newStream(new HeadersFrame(request, null, true), new Promise.Adapter<>(), new Stream.Listener() { @Override public void onHeaders(Stream stream, HeadersFrame frame) @@ -669,25 +684,24 @@ public class GoAwayTest extends AbstractTest AtomicReference serverStreamRef = new AtomicReference<>(); CountDownLatch serverGoAwayLatch = new CountDownLatch(1); CountDownLatch serverCloseLatch = new CountDownLatch(1); - start(new ServerSessionListener.Adapter() + start(new ServerSessionListener() { @Override public Stream.Listener onNewStream(Stream stream, HeadersFrame frame) { serverStreamRef.set(stream); - return new Stream.Listener.Adapter() + stream.demand(); + return new Stream.Listener() { @Override - public void onData(Stream stream, DataFrame frame, Callback callback) + public void onDataAvailable(Stream stream) { - if (frame.isEndStream()) + Stream.Data data = stream.readData(); + data.release(); + if (data.frame().isEndStream()) { MetaData.Response response = new MetaData.Response(HttpVersion.HTTP_2, HttpStatus.OK_200, HttpFields.EMPTY); - stream.headers(new HeadersFrame(stream.getId(), response, null, true), callback); - } - else - { - callback.succeeded(); + stream.headers(new HeadersFrame(stream.getId(), response, null, true), Callback.NOOP); } } }; @@ -708,16 +722,17 @@ public class GoAwayTest extends AbstractTest } @Override - public void onClose(Session session, GoAwayFrame frame) + public void onClose(Session session, GoAwayFrame frame, Callback callback) { serverCloseLatch.countDown(); + callback.succeeded(); } }); CountDownLatch clientGracefulGoAwayLatch = new CountDownLatch(1); CountDownLatch clientGoAwayLatch = new CountDownLatch(1); CountDownLatch clientCloseLatch = new CountDownLatch(1); - Session clientSession = newClientSession(new Session.Listener.Adapter() + Session clientSession = newClientSession(new Session.Listener() { @Override public void onGoAway(Session session, GoAwayFrame frame) @@ -729,14 +744,15 @@ public class GoAwayTest extends AbstractTest } @Override - public void onClose(Session session, GoAwayFrame frame) + public void onClose(Session session, GoAwayFrame frame, Callback callback) { clientCloseLatch.countDown(); + callback.succeeded(); } }); MetaData.Request request = newRequest(HttpMethod.GET.asString(), HttpFields.EMPTY); FuturePromise promise = new FuturePromise<>(); - clientSession.newStream(new HeadersFrame(request, null, false), promise, new Stream.Listener.Adapter()); + clientSession.newStream(new HeadersFrame(request, null, false), promise, null); Stream clientStream = promise.get(5, TimeUnit.SECONDS); // Send a graceful GOAWAY from the client. @@ -763,17 +779,18 @@ public class GoAwayTest extends AbstractTest { AtomicReference serverSessionRef = new AtomicReference<>(); CountDownLatch serverCloseLatch = new CountDownLatch(1); - start(new ServerSessionListener.Adapter() + start(new ServerSessionListener() { @Override - public void onClose(Session session, GoAwayFrame frame) + public void onClose(Session session, GoAwayFrame frame, Callback callback) { serverSessionRef.set(session); serverCloseLatch.countDown(); + callback.succeeded(); } }); - Session clientSession = newClientSession(new Session.Listener.Adapter()); + Session clientSession = newClientSession(new Session.Listener() {}); // TODO: get rid of sleep! // Wait for the SETTINGS frames to be exchanged. Thread.sleep(500); @@ -789,7 +806,7 @@ public class GoAwayTest extends AbstractTest { AtomicReference serverSessionRef = new AtomicReference<>(); CountDownLatch serverCloseLatch = new CountDownLatch(1); - start(new ServerSessionListener.Adapter() + start(new ServerSessionListener() { @Override public void onAccept(Session session) @@ -798,13 +815,14 @@ public class GoAwayTest extends AbstractTest } @Override - public void onClose(Session session, GoAwayFrame frame) + public void onClose(Session session, GoAwayFrame frame, Callback callback) { serverCloseLatch.countDown(); + callback.succeeded(); } }); - newClientSession(new Session.Listener.Adapter() + newClientSession(new Session.Listener() { @Override public void onGoAway(Session session, GoAwayFrame frame) @@ -833,7 +851,7 @@ public class GoAwayTest extends AbstractTest AtomicReference serverSessionRef = new AtomicReference<>(); CountDownLatch serverIdleTimeoutLatch = new CountDownLatch(1); CountDownLatch serverCloseLatch = new CountDownLatch(1); - start(new ServerSessionListener.Adapter() + start(new ServerSessionListener() { @Override public void onAccept(Session session) @@ -850,15 +868,16 @@ public class GoAwayTest extends AbstractTest } @Override - public void onClose(Session session, GoAwayFrame frame) + public void onClose(Session session, GoAwayFrame frame, Callback callback) { serverCloseLatch.countDown(); + callback.succeeded(); } }); CountDownLatch clientGoAwayLatch = new CountDownLatch(1); CountDownLatch clientCloseLatch = new CountDownLatch(1); - Session clientSession = newClientSession(new Session.Listener.Adapter() + Session clientSession = newClientSession(new Session.Listener() { @Override public void onGoAway(Session session, GoAwayFrame frame) @@ -868,9 +887,10 @@ public class GoAwayTest extends AbstractTest } @Override - public void onClose(Session session, GoAwayFrame frame) + public void onClose(Session session, GoAwayFrame frame, Callback callback) { clientCloseLatch.countDown(); + callback.succeeded(); } }); @@ -892,7 +912,7 @@ public class GoAwayTest extends AbstractTest AtomicReference serverSessionRef = new AtomicReference<>(); CountDownLatch serverCloseLatch = new CountDownLatch(1); - start(new ServerSessionListener.Adapter() + start(new ServerSessionListener() { @Override public void onAccept(Session session) @@ -911,16 +931,17 @@ public class GoAwayTest extends AbstractTest } @Override - public void onClose(Session session, GoAwayFrame frame) + public void onClose(Session session, GoAwayFrame frame, Callback callback) { serverCloseLatch.countDown(); + callback.succeeded(); } }); CountDownLatch clientGracefulGoAwayLatch = new CountDownLatch(1); CountDownLatch clientGoAwayLatch = new CountDownLatch(1); CountDownLatch clientCloseLatch = new CountDownLatch(1); - Session clientSession = newClientSession(new Session.Listener.Adapter() + Session clientSession = newClientSession(new Session.Listener() { @Override public void onGoAway(Session session, GoAwayFrame frame) @@ -932,20 +953,22 @@ public class GoAwayTest extends AbstractTest } @Override - public void onClose(Session session, GoAwayFrame frame) + public void onClose(Session session, GoAwayFrame frame, Callback callback) { clientCloseLatch.countDown(); + callback.succeeded(); } }); CountDownLatch clientResetLatch = new CountDownLatch(1); MetaData.Request request = newRequest(HttpMethod.GET.asString(), HttpFields.EMPTY); // Send request headers but not data. - clientSession.newStream(new HeadersFrame(request, null, false), new Promise.Adapter<>(), new Stream.Listener.Adapter() + clientSession.newStream(new HeadersFrame(request, null, false), new Promise.Adapter<>(), new Stream.Listener() { @Override - public void onReset(Stream stream, ResetFrame frame) + public void onReset(Stream stream, ResetFrame frame, Callback callback) { clientResetLatch.countDown(); + callback.succeeded(); } }); @@ -968,7 +991,7 @@ public class GoAwayTest extends AbstractTest AtomicReference serverSessionRef = new AtomicReference<>(); CountDownLatch serverGracefulGoAwayLatch = new CountDownLatch(1); CountDownLatch serverCloseLatch = new CountDownLatch(1); - start(new ServerSessionListener.Adapter() + start(new ServerSessionListener() { @Override public void onAccept(Session session) @@ -992,15 +1015,16 @@ public class GoAwayTest extends AbstractTest } @Override - public void onClose(Session session, GoAwayFrame frame) + public void onClose(Session session, GoAwayFrame frame, Callback callback) { serverCloseLatch.countDown(); + callback.succeeded(); } }); CountDownLatch clientGoAwayLatch = new CountDownLatch(1); CountDownLatch clientCloseLatch = new CountDownLatch(1); - Session clientSession = newClientSession(new Session.Listener.Adapter() + Session clientSession = newClientSession(new Session.Listener() { @Override public void onGoAway(Session session, GoAwayFrame frame) @@ -1009,19 +1033,21 @@ public class GoAwayTest extends AbstractTest } @Override - public void onClose(Session session, GoAwayFrame frame) + public void onClose(Session session, GoAwayFrame frame, Callback callback) { clientCloseLatch.countDown(); + callback.succeeded(); } }); MetaData.Request request = newRequest(HttpMethod.GET.asString(), HttpFields.EMPTY); CountDownLatch streamResetLatch = new CountDownLatch(1); - clientSession.newStream(new HeadersFrame(request, null, false), new Promise.Adapter<>(), new Stream.Listener.Adapter() + clientSession.newStream(new HeadersFrame(request, null, false), new Promise.Adapter<>(), new Stream.Listener() { @Override - public void onReset(Stream stream, ResetFrame frame) + public void onReset(Stream stream, ResetFrame frame, Callback callback) { streamResetLatch.countDown(); + callback.succeeded(); } }); @@ -1043,7 +1069,7 @@ public class GoAwayTest extends AbstractTest { AtomicReference serverSessionRef = new AtomicReference<>(); CountDownLatch serverCloseLatch = new CountDownLatch(1); - start(new ServerSessionListener.Adapter() + start(new ServerSessionListener() { @Override public Stream.Listener onNewStream(Stream stream, HeadersFrame frame) @@ -1055,15 +1081,16 @@ public class GoAwayTest extends AbstractTest } @Override - public void onClose(Session session, GoAwayFrame frame) + public void onClose(Session session, GoAwayFrame frame, Callback callback) { serverCloseLatch.countDown(); + callback.succeeded(); } }); CountDownLatch clientGoAwayLatch = new CountDownLatch(1); CountDownLatch clientCloseLatch = new CountDownLatch(1); - Session clientSession = newClientSession(new Session.Listener.Adapter() + Session clientSession = newClientSession(new Session.Listener() { @Override public void onGoAway(Session session, GoAwayFrame frame) @@ -1072,20 +1099,22 @@ public class GoAwayTest extends AbstractTest } @Override - public void onClose(Session session, GoAwayFrame frame) + public void onClose(Session session, GoAwayFrame frame, Callback callback) { clientCloseLatch.countDown(); + callback.succeeded(); } }); MetaData.Request request = newRequest(HttpMethod.GET.asString(), HttpFields.EMPTY); CountDownLatch clientResetLatch = new CountDownLatch(1); - clientSession.newStream(new HeadersFrame(request, null, false), new Promise.Adapter<>(), new Stream.Listener.Adapter() + clientSession.newStream(new HeadersFrame(request, null, false), new Promise.Adapter<>(), new Stream.Listener() { @Override - public void onReset(Stream stream, ResetFrame frame) + public void onReset(Stream stream, ResetFrame frame, Callback callback) { clientResetLatch.countDown(); + callback.succeeded(); } }); diff --git a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/H2SpecServer.java b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/H2SpecServer.java index 231a61e4841..f703255cc5c 100644 --- a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/H2SpecServer.java +++ b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/H2SpecServer.java @@ -14,10 +14,17 @@ package org.eclipse.jetty.http2.tests; import org.eclipse.jetty.http2.server.HTTP2CServerConnectionFactory; +import org.eclipse.jetty.io.Content; +import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.HttpConfiguration; import org.eclipse.jetty.server.HttpConnectionFactory; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Response; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.util.Callback; + +import static java.nio.charset.StandardCharsets.UTF_8; /** * HTTP/2 server to run the 'h2spec' tool against. @@ -39,6 +46,18 @@ public class H2SpecServer connector.setPort(port); server.addConnector(connector); + // H2Spec requires the server to read the request + // content and respond with 200 and some content. + server.setHandler(new Handler.Processor() + { + @Override + public void process(Request request, Response response, Callback callback) + { + Content.Source.consumeAll(request, Callback.NOOP); + response.write(true, UTF_8.encode("hello"), callback); + } + }); + server.start(); } } diff --git a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/HTTP2ServerTest.java b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/HTTP2ServerTest.java index 9c59d4585a0..3cb7ecd8803 100644 --- a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/HTTP2ServerTest.java +++ b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/HTTP2ServerTest.java @@ -555,7 +555,7 @@ public class HTTP2ServerTest extends AbstractServerTest private void testRequestWithContinuationFrames(PriorityFrame priorityFrame, Callable frames) throws Exception { final CountDownLatch serverLatch = new CountDownLatch(1); - startServer(new ServerSessionListener.Adapter() + startServer(new ServerSessionListener() { @Override public Stream.Listener onNewStream(Stream stream, HeadersFrame frame) diff --git a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/HTTP2Test.java b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/HTTP2Test.java index 1e18878082f..2a0f0ab8fee 100644 --- a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/HTTP2Test.java +++ b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/HTTP2Test.java @@ -78,12 +78,12 @@ public class HTTP2Test extends AbstractTest } }); - Session session = newClientSession(new Session.Listener.Adapter()); + Session session = newClientSession(new Session.Listener() {}); MetaData.Request metaData = newRequest("GET", HttpFields.EMPTY); HeadersFrame frame = new HeadersFrame(metaData, null, true); - final CountDownLatch latch = new CountDownLatch(1); - session.newStream(frame, new Promise.Adapter<>(), new Stream.Listener.Adapter() + CountDownLatch latch = new CountDownLatch(1); + session.newStream(frame, new Promise.Adapter<>(), new Stream.Listener() { @Override public void onHeaders(Stream stream, HeadersFrame frame) @@ -106,7 +106,7 @@ public class HTTP2Test extends AbstractTest @Test public void testRequestNoContentResponseEmptyContent() throws Exception { - start(new ServerSessionListener.Adapter() + start(new ServerSessionListener() { @Override public Stream.Listener onNewStream(Stream stream, HeadersFrame frame) @@ -124,12 +124,12 @@ public class HTTP2Test extends AbstractTest } }); - Session session = newClientSession(new Session.Listener.Adapter()); + Session session = newClientSession(new Session.Listener() {}); MetaData.Request metaData = newRequest("GET", HttpFields.EMPTY); HeadersFrame frame = new HeadersFrame(metaData, null, true); - final CountDownLatch latch = new CountDownLatch(1); - session.newStream(frame, new Promise.Adapter<>(), new Stream.Listener.Adapter() + CountDownLatch latch = new CountDownLatch(1); + session.newStream(frame, new Promise.Adapter<>(), new Stream.Listener() { @Override public void onHeaders(Stream stream, HeadersFrame frame) @@ -138,13 +138,15 @@ public class HTTP2Test extends AbstractTest assertEquals(stream.getId(), frame.getStreamId()); MetaData.Response response = (MetaData.Response)frame.getMetaData(); assertEquals(200, response.getStatus()); + stream.demand(); } @Override - public void onData(Stream stream, DataFrame frame, Callback callback) + public void onDataAvailable(Stream stream) { - assertTrue(frame.isEndStream()); - callback.succeeded(); + Stream.Data data = stream.readData(); + assertTrue(data.frame().isEndStream()); + data.release(); latch.countDown(); } }); @@ -155,7 +157,7 @@ public class HTTP2Test extends AbstractTest @Test public void testRequestNoContentResponseContent() throws Exception { - final byte[] content = "Hello World!".getBytes(StandardCharsets.UTF_8); + byte[] content = "Hello World!".getBytes(StandardCharsets.UTF_8); start(new Handler.Processor() { @Override @@ -165,12 +167,12 @@ public class HTTP2Test extends AbstractTest } }); - Session session = newClientSession(new Session.Listener.Adapter()); + Session session = newClientSession(new Session.Listener() {}); MetaData.Request metaData = newRequest("GET", HttpFields.EMPTY); HeadersFrame frame = new HeadersFrame(metaData, null, true); - final CountDownLatch latch = new CountDownLatch(2); - session.newStream(frame, new Promise.Adapter<>(), new Stream.Listener.Adapter() + CountDownLatch latch = new CountDownLatch(2); + session.newStream(frame, new Promise.Adapter<>(), new Stream.Listener() { @Override public void onHeaders(Stream stream, HeadersFrame frame) @@ -183,16 +185,19 @@ public class HTTP2Test extends AbstractTest MetaData.Response response = (MetaData.Response)frame.getMetaData(); assertEquals(200, response.getStatus()); + stream.demand(); + latch.countDown(); } @Override - public void onData(Stream stream, DataFrame frame, Callback callback) + public void onDataAvailable(Stream stream) { + Stream.Data data = stream.readData(); + DataFrame frame = data.frame(); assertTrue(frame.isEndStream()); assertEquals(ByteBuffer.wrap(content), frame.getData()); - - callback.succeeded(); + data.release(); latch.countDown(); } }); @@ -212,25 +217,27 @@ public class HTTP2Test extends AbstractTest } }); - Session session = newClientSession(new Session.Listener.Adapter()); + Session session = newClientSession(new Session.Listener() {}); CountDownLatch latch = new CountDownLatch(1); MetaData.Request metaData = newRequest("POST", HttpFields.EMPTY); HeadersFrame frame = new HeadersFrame(metaData, null, false); Promise.Completable streamCompletable = new Promise.Completable<>(); - session.newStream(frame, streamCompletable, new Stream.Listener.Adapter() + session.newStream(frame, streamCompletable, new Stream.Listener() { @Override - public void onData(Stream stream, DataFrame frame, Callback callback) + public void onDataAvailable(Stream stream) { - callback.succeeded(); - if (frame.isEndStream()) + Stream.Data data = stream.readData(); + data.release(); + stream.demand(); + if (data.frame().isEndStream()) latch.countDown(); } }); streamCompletable.thenCompose(stream -> { - DataFrame dataFrame = new DataFrame(stream.getId(), ByteBuffer.allocate(1024), false); + DataFrame dataFrame = new DataFrame(stream.getId(), ByteBuffer.allocate(512), false); Callback.Completable dataCompletable = new Callback.Completable(); stream.data(dataFrame, dataCompletable); return dataCompletable.thenApply(y -> stream); @@ -246,7 +253,7 @@ public class HTTP2Test extends AbstractTest @Test public void testMultipleRequests() throws Exception { - final String downloadBytes = "X-Download"; + String downloadBytes = "X-Download"; start(new Handler.Processor() { @Override @@ -260,7 +267,7 @@ public class HTTP2Test extends AbstractTest }); int requests = 20; - Session session = newClientSession(new Session.Listener.Adapter()); + Session session = newClientSession(new Session.Listener() {}); Random random = new Random(); HttpFields fields = HttpFields.build() @@ -268,16 +275,18 @@ public class HTTP2Test extends AbstractTest .put("User-Agent", "HTTP2Client/" + Jetty.VERSION); MetaData.Request metaData = newRequest("GET", fields); HeadersFrame frame = new HeadersFrame(metaData, null, true); - final CountDownLatch latch = new CountDownLatch(requests); + CountDownLatch latch = new CountDownLatch(requests); for (int i = 0; i < requests; ++i) { - session.newStream(frame, new Promise.Adapter<>(), new Stream.Listener.Adapter() + session.newStream(frame, new Promise.Adapter<>(), new Stream.Listener() { @Override - public void onData(Stream stream, DataFrame frame, Callback callback) + public void onDataAvailable(Stream stream) { - callback.succeeded(); - if (frame.isEndStream()) + Stream.Data data = stream.readData(); + data.release(); + stream.demand(); + if (data.frame().isEndStream()) latch.countDown(); } }); @@ -289,7 +298,7 @@ public class HTTP2Test extends AbstractTest @Test public void testCustomResponseCode() throws Exception { - final int status = 475; + int status = 475; start(new Handler.Processor() { @Override @@ -300,11 +309,11 @@ public class HTTP2Test extends AbstractTest } }); - Session session = newClientSession(new Session.Listener.Adapter()); + Session session = newClientSession(new Session.Listener() {}); MetaData.Request metaData = newRequest("GET", HttpFields.EMPTY); HeadersFrame frame = new HeadersFrame(metaData, null, true); - final CountDownLatch latch = new CountDownLatch(1); - session.newStream(frame, new Promise.Adapter<>(), new Stream.Listener.Adapter() + CountDownLatch latch = new CountDownLatch(1); + session.newStream(frame, new Promise.Adapter<>(), new Stream.Listener() { @Override public void onHeaders(Stream stream, HeadersFrame frame) @@ -322,9 +331,9 @@ public class HTTP2Test extends AbstractTest @Test public void testHostHeader() throws Exception { - final String host = "fooBar"; - final int port = 1313; - final String authority = host + ":" + port; + String host = "fooBar"; + int port = 1313; + String authority = host + ":" + port; start(new Handler.Processor() { @Override @@ -336,12 +345,12 @@ public class HTTP2Test extends AbstractTest } }); - Session session = newClientSession(new Session.Listener.Adapter()); + Session session = newClientSession(new Session.Listener() {}); HostPortHttpField hostHeader = new HostPortHttpField(authority); MetaData.Request metaData = new MetaData.Request("GET", HttpScheme.HTTP.asString(), hostHeader, "/", HttpVersion.HTTP_2, HttpFields.EMPTY, -1); HeadersFrame frame = new HeadersFrame(metaData, null, true); - final CountDownLatch latch = new CountDownLatch(1); - session.newStream(frame, new Promise.Adapter<>(), new Stream.Listener.Adapter() + CountDownLatch latch = new CountDownLatch(1); + session.newStream(frame, new Promise.Adapter<>(), new Stream.Listener() { @Override public void onHeaders(Stream stream, HeadersFrame frame) @@ -359,15 +368,16 @@ public class HTTP2Test extends AbstractTest @Test public void testServerSendsGoAwayOnStop() throws Exception { - start(new ServerSessionListener.Adapter()); + start(new ServerSessionListener() {}); CountDownLatch closeLatch = new CountDownLatch(1); - newClientSession(new Session.Listener.Adapter() + newClientSession(new Session.Listener() { @Override - public void onClose(Session session, GoAwayFrame frame) + public void onClose(Session session, GoAwayFrame frame, Callback callback) { closeLatch.countDown(); + callback.succeeded(); } }); @@ -382,16 +392,17 @@ public class HTTP2Test extends AbstractTest public void testClientSendsGoAwayOnStop() throws Exception { CountDownLatch closeLatch = new CountDownLatch(1); - start(new ServerSessionListener.Adapter() + start(new ServerSessionListener() { @Override - public void onClose(Session session, GoAwayFrame frame) + public void onClose(Session session, GoAwayFrame frame, Callback callback) { closeLatch.countDown(); + callback.succeeded(); } }); - newClientSession(new Session.Listener.Adapter()); + newClientSession(new Session.Listener() {}); sleep(1000); @@ -404,7 +415,7 @@ public class HTTP2Test extends AbstractTest public void testMaxConcurrentStreams() throws Exception { int maxStreams = 2; - start(new ServerSessionListener.Adapter() + start(new ServerSessionListener() { @Override public Map onPreface(Session session) @@ -424,7 +435,7 @@ public class HTTP2Test extends AbstractTest }); CountDownLatch settingsLatch = new CountDownLatch(1); - Session session = newClientSession(new Session.Listener.Adapter() + Session session = newClientSession(new Session.Listener() { @Override public void onSettings(Session session, SettingsFrame frame) @@ -437,7 +448,7 @@ public class HTTP2Test extends AbstractTest MetaData.Request request1 = newRequest("GET", HttpFields.EMPTY); FuturePromise promise1 = new FuturePromise<>(); CountDownLatch exchangeLatch1 = new CountDownLatch(2); - session.newStream(new HeadersFrame(request1, null, false), promise1, new Stream.Listener.Adapter() + session.newStream(new HeadersFrame(request1, null, false), promise1, new Stream.Listener() { @Override public void onHeaders(Stream stream, HeadersFrame frame) @@ -451,7 +462,7 @@ public class HTTP2Test extends AbstractTest MetaData.Request request2 = newRequest("GET", HttpFields.EMPTY); FuturePromise promise2 = new FuturePromise<>(); CountDownLatch exchangeLatch2 = new CountDownLatch(2); - session.newStream(new HeadersFrame(request2, null, false), promise2, new Stream.Listener.Adapter() + session.newStream(new HeadersFrame(request2, null, false), promise2, new Stream.Listener() { @Override public void onHeaders(Stream stream, HeadersFrame frame) @@ -473,7 +484,7 @@ public class HTTP2Test extends AbstractTest if (x instanceof IllegalStateException) maxStreamsLatch.countDown(); } - }, new Stream.Listener.Adapter()); + }, null); assertTrue(maxStreamsLatch.await(5, TimeUnit.SECONDS)); assertEquals(2, session.getStreams().size()); @@ -500,7 +511,7 @@ public class HTTP2Test extends AbstractTest { exchangeLatch4.countDown(); } - }, new Stream.Listener.Adapter() + }, new Stream.Listener() { @Override public void onHeaders(Stream stream, HeadersFrame frame) @@ -528,7 +539,7 @@ public class HTTP2Test extends AbstractTest @Test public void testInvalidAPIUsageOnClient() throws Exception { - start(new ServerSessionListener.Adapter() + start(new ServerSessionListener() { @Override public Stream.Listener onNewStream(Stream stream, HeadersFrame frame) @@ -536,13 +547,16 @@ public class HTTP2Test extends AbstractTest Callback.Completable completable = new Callback.Completable(); MetaData.Response response = new MetaData.Response(HttpVersion.HTTP_2, HttpStatus.OK_200, HttpFields.EMPTY); stream.headers(new HeadersFrame(stream.getId(), response, null, false), completable); - return new Stream.Listener.Adapter() + stream.demand(); + return new Stream.Listener() { @Override - public void onData(Stream stream, DataFrame frame, Callback callback) + public void onDataAvailable(Stream stream) { - callback.succeeded(); - if (frame.isEndStream()) + Stream.Data data = stream.readData(); + data.release(); + stream.demand(); + if (data.frame().isEndStream()) { completable.thenRun(() -> { @@ -555,19 +569,21 @@ public class HTTP2Test extends AbstractTest } }); - Session session = newClientSession(new Session.Listener.Adapter()); + Session session = newClientSession(new Session.Listener() {}); MetaData.Request metaData = newRequest("GET", HttpFields.EMPTY); HeadersFrame frame = new HeadersFrame(metaData, null, false); Promise.Completable completable = new Promise.Completable<>(); CountDownLatch completeLatch = new CountDownLatch(2); - session.newStream(frame, completable, new Stream.Listener.Adapter() + session.newStream(frame, completable, new Stream.Listener() { @Override - public void onData(Stream stream, DataFrame frame, Callback callback) + public void onDataAvailable(Stream stream) { - callback.succeeded(); - if (frame.isEndStream()) + Stream.Data data = stream.readData(); + data.release(); + stream.demand(); + if (data.frame().isEndStream()) completeLatch.countDown(); } }); @@ -624,7 +640,7 @@ public class HTTP2Test extends AbstractTest { long sleep = 1000; CountDownLatch completeLatch = new CountDownLatch(2); - start(new ServerSessionListener.Adapter() + start(new ServerSessionListener() { @Override public Stream.Listener onNewStream(Stream stream, HeadersFrame frame) @@ -642,14 +658,7 @@ public class HTTP2Test extends AbstractTest sleep(2 * sleep); return super.getMetaData(); } - }, new Callback() - { - @Override - public void succeeded() - { - stream.data(dataFrame, NOOP); - } - }); + }, Callback.from(() -> stream.data(dataFrame, Callback.NOOP), Throwable::printStackTrace)); }).start(); // Wait for the headers() call to happen. @@ -674,17 +683,18 @@ public class HTTP2Test extends AbstractTest } }); - Session session = newClientSession(new Session.Listener.Adapter()); + Session session = newClientSession(new Session.Listener() {}); MetaData.Request metaData = newRequest("GET", HttpFields.EMPTY); HeadersFrame frame = new HeadersFrame(metaData, null, true); - session.newStream(frame, new Promise.Adapter<>(), new Stream.Listener.Adapter() + session.newStream(frame, new Promise.Adapter<>(), new Stream.Listener() { @Override - public void onData(Stream stream, DataFrame frame, Callback callback) + public void onDataAvailable(Stream stream) { - callback.succeeded(); - if (frame.isEndStream()) + Stream.Data data = stream.readData(); + data.release(); + if (data.frame().isEndStream()) completeLatch.countDown(); } }); @@ -695,7 +705,7 @@ public class HTTP2Test extends AbstractTest @Test public void testCleanGoAwayDoesNotTriggerFailureNotification() throws Exception { - start(new ServerSessionListener.Adapter() + start(new ServerSessionListener() { @Override public Stream.Listener onNewStream(Stream stream, HeadersFrame frame) @@ -711,23 +721,25 @@ public class HTTP2Test extends AbstractTest CountDownLatch closeLatch = new CountDownLatch(1); CountDownLatch failureLatch = new CountDownLatch(1); - Session session = newClientSession(new Session.Listener.Adapter() + Session session = newClientSession(new Session.Listener() { @Override - public void onClose(Session session, GoAwayFrame frame) + public void onClose(Session session, GoAwayFrame frame, Callback callback) { closeLatch.countDown(); + callback.succeeded(); } @Override - public void onFailure(Session session, Throwable failure) + public void onFailure(Session session, Throwable failure, Callback callback) { failureLatch.countDown(); + callback.succeeded(); } }); MetaData.Request metaData = newRequest("GET", HttpFields.EMPTY); HeadersFrame request = new HeadersFrame(metaData, null, true); - session.newStream(request, new Promise.Adapter<>(), new Stream.Listener.Adapter()); + session.newStream(request, new Promise.Adapter<>(), null); // Make sure onClose() is called. assertTrue(closeLatch.await(5, TimeUnit.SECONDS)); @@ -747,13 +759,13 @@ public class HTTP2Test extends AbstractTest }); // A bad header in the request should fail on the client. - Session session = newClientSession(new Session.Listener.Adapter()); + Session session = newClientSession(new Session.Listener() {}); HttpFields requestFields = HttpFields.build() .put(":custom", "special"); MetaData.Request metaData = newRequest("GET", requestFields); HeadersFrame request = new HeadersFrame(metaData, null, true); FuturePromise promise = new FuturePromise<>(); - session.newStream(request, promise, new Stream.Listener.Adapter()); + session.newStream(request, promise, null); ExecutionException x = assertThrows(ExecutionException.class, () -> promise.get(5, TimeUnit.SECONDS)); assertThat(x.getCause(), instanceOf(HpackException.StreamException.class)); } @@ -772,17 +784,18 @@ public class HTTP2Test extends AbstractTest }); // Good request with bad header in the response. - Session session = newClientSession(new Session.Listener.Adapter()); + Session session = newClientSession(new Session.Listener() {}); MetaData.Request metaData = newRequest("GET", HttpFields.EMPTY); HeadersFrame request = new HeadersFrame(metaData, null, true); FuturePromise promise = new FuturePromise<>(); CountDownLatch resetLatch = new CountDownLatch(1); - session.newStream(request, promise, new Stream.Listener.Adapter() + session.newStream(request, promise, new Stream.Listener() { @Override - public void onReset(Stream stream, ResetFrame frame) + public void onReset(Stream stream, ResetFrame frame, Callback callback) { resetLatch.countDown(); + callback.succeeded(); } }); Stream stream = promise.get(5, TimeUnit.SECONDS); @@ -815,19 +828,20 @@ public class HTTP2Test extends AbstractTest }); // Good request with bad header in the response. - Session session = newClientSession(new Session.Listener.Adapter()); + Session session = newClientSession(new Session.Listener() {}); MetaData.Request metaData = newRequest("GET", "/flush", HttpFields.EMPTY); HeadersFrame request = new HeadersFrame(metaData, null, true); FuturePromise promise = new FuturePromise<>(); CountDownLatch resetLatch = new CountDownLatch(1); - session.newStream(request, promise, new Stream.Listener.Adapter() + session.newStream(request, promise, new Stream.Listener() { @Override - public void onReset(Stream stream, ResetFrame frame) + public void onReset(Stream stream, ResetFrame frame, Callback callback) { // Cannot receive a 500 because we force the flush on the server, so // the response is committed even if the server was not able to write it. resetLatch.countDown(); + callback.succeeded(); } }); Stream stream = promise.get(5, TimeUnit.SECONDS); @@ -842,7 +856,7 @@ public class HTTP2Test extends AbstractTest AtomicReference serverSessionRef = new AtomicReference<>(); CountDownLatch serverSessionLatch = new CountDownLatch(1); CountDownLatch dataLatch = new CountDownLatch(2); - start(new ServerSessionListener.Adapter() + start(new ServerSessionListener() { @Override public void onAccept(Session session) @@ -854,14 +868,17 @@ public class HTTP2Test extends AbstractTest @Override public Stream.Listener onNewStream(Stream stream, HeadersFrame frame) { - return new Stream.Listener.Adapter() + stream.demand(); + return new Stream.Listener() { @Override - public void onData(Stream stream, DataFrame frame, Callback callback) + public void onDataAvailable(Stream stream) { - callback.succeeded(); + Stream.Data data = stream.readData(); + data.release(); + stream.demand(); dataLatch.countDown(); - if (frame.isEndStream()) + if (data.frame().isEndStream()) { MetaData.Response response = new MetaData.Response(HttpVersion.HTTP_2, HttpStatus.OK_200, HttpFields.EMPTY); stream.headers(new HeadersFrame(stream.getId(), response, null, true), Callback.NOOP); @@ -876,7 +893,7 @@ public class HTTP2Test extends AbstractTest CountDownLatch clientGracefulGoAwayLatch = new CountDownLatch(1); CountDownLatch clientGoAwayLatch = new CountDownLatch(1); CountDownLatch clientCloseLatch = new CountDownLatch(1); - Session clientSession = newClientSession(new Session.Listener.Adapter() + Session clientSession = newClientSession(new Session.Listener() { @Override public void onGoAway(Session session, GoAwayFrame frame) @@ -888,9 +905,10 @@ public class HTTP2Test extends AbstractTest } @Override - public void onClose(Session session, GoAwayFrame frame) + public void onClose(Session session, GoAwayFrame frame, Callback callback) { clientCloseLatch.countDown(); + callback.succeeded(); } }); assertTrue(serverSessionLatch.await(5, TimeUnit.SECONDS)); @@ -901,7 +919,7 @@ public class HTTP2Test extends AbstractTest MetaData.Request metaData1 = newRequest("GET", HttpFields.EMPTY); HeadersFrame request1 = new HeadersFrame(metaData1, null, false); FuturePromise promise1 = new FuturePromise<>(); - Stream.Listener.Adapter listener = new Stream.Listener.Adapter() + Stream.Listener listener = new Stream.Listener() { @Override public void onHeaders(Stream stream, HeadersFrame frame) @@ -943,7 +961,7 @@ public class HTTP2Test extends AbstractTest MetaData.Request metaData3 = new MetaData.Request("GET", HttpScheme.HTTP.asString(), authority3, "/", HttpVersion.HTTP_2, HttpFields.EMPTY, -1); HeadersFrame request3 = new HeadersFrame(metaData3, null, true); FuturePromise promise3 = new FuturePromise<>(); - clientSession.newStream(request3, promise3, new Stream.Listener.Adapter()); + clientSession.newStream(request3, promise3, null); assertThrows(ExecutionException.class, () -> promise3.get(5, TimeUnit.SECONDS)); // Finish the previous requests and expect the responses. diff --git a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/HttpClientTransportOverHTTP2Test.java b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/HttpClientTransportOverHTTP2Test.java index 46ce6d9ea15..637c1bd3a0f 100644 --- a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/HttpClientTransportOverHTTP2Test.java +++ b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/HttpClientTransportOverHTTP2Test.java @@ -31,6 +31,7 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; +import java.util.function.LongConsumer; import java.util.function.UnaryOperator; import org.eclipse.jetty.client.HttpClient; @@ -83,6 +84,7 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.greaterThanOrEqualTo; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -122,17 +124,18 @@ public class HttpClientTransportOverHTTP2Test extends AbstractTest public void testRequestAbortSendsResetFrame() throws Exception { CountDownLatch resetLatch = new CountDownLatch(1); - start(new ServerSessionListener.Adapter() + start(new ServerSessionListener() { @Override public Stream.Listener onNewStream(Stream stream, HeadersFrame frame) { - return new Stream.Listener.Adapter() + return new Stream.Listener() { @Override - public void onReset(Stream stream, ResetFrame frame) + public void onReset(Stream stream, ResetFrame frame, Callback callback) { resetLatch.countDown(); + callback.succeeded(); } }; } @@ -149,7 +152,7 @@ public class HttpClientTransportOverHTTP2Test extends AbstractTest public void testResponseAbortSendsResetFrame() throws Exception { CountDownLatch resetLatch = new CountDownLatch(1); - start(new ServerSessionListener.Adapter() + start(new ServerSessionListener() { @Override public Stream.Listener onNewStream(Stream stream, HeadersFrame frame) @@ -165,12 +168,13 @@ public class HttpClientTransportOverHTTP2Test extends AbstractTest } }); - return new Stream.Listener.Adapter() + return new Stream.Listener() { @Override - public void onReset(Stream stream, ResetFrame frame) + public void onReset(Stream stream, ResetFrame frame, Callback callback) { resetLatch.countDown(); + callback.succeeded(); } }; } @@ -208,10 +212,74 @@ public class HttpClientTransportOverHTTP2Test extends AbstractTest assertEquals(HttpStatus.OK_200, response.getStatus()); } + @Test + public void testDelayDemandAfterHeaders() throws Exception + { + start(new Handler.Processor() + { + @Override + public void process(Request request, org.eclipse.jetty.server.Response response, Callback callback) + { + Callback.Completable completable = new Callback.Completable(); + response.write(false, ByteBuffer.allocate(1), completable); + completable.whenComplete((r, x) -> + { + if (x != null) + callback.failed(x); + else + response.write(true, ByteBuffer.allocate(2), callback); + }); + } + }); + + AtomicReference demandRef = new AtomicReference<>(); + CountDownLatch beforeContentLatch = new CountDownLatch(1); + AtomicInteger contentCount = new AtomicInteger(); + CountDownLatch latch = new CountDownLatch(1); + httpClient.newRequest("localhost", connector.getLocalPort()) + .onResponseContentDemanded(new org.eclipse.jetty.client.api.Response.DemandedContentListener() + { + @Override + public void onBeforeContent(org.eclipse.jetty.client.api.Response response, LongConsumer demand) + { + // Do not demand. + demandRef.set(demand); + beforeContentLatch.countDown(); + } + + @Override + public void onContent(org.eclipse.jetty.client.api.Response response, LongConsumer demand, ByteBuffer content, Callback callback) + { + contentCount.incrementAndGet(); + callback.succeeded(); + demand.accept(1); + } + }) + .timeout(5, TimeUnit.SECONDS) + .send(result -> + { + assertTrue(result.isSucceeded()); + assertEquals(HttpStatus.OK_200, result.getResponse().getStatus()); + latch.countDown(); + }); + + assertTrue(beforeContentLatch.await(5, TimeUnit.SECONDS)); + + // Verify that the response is not completed yet. + assertFalse(latch.await(1, TimeUnit.SECONDS)); + assertEquals(0, contentCount.get()); + + // Demand to receive the content. + demandRef.get().accept(1); + + assertTrue(latch.await(5, TimeUnit.SECONDS)); + assertEquals(2, contentCount.get()); + } + @Test public void testLastStreamId() throws Exception { - prepareServer(new RawHTTP2ServerConnectionFactory(new HttpConfiguration(), new ServerSessionListener.Adapter() + prepareServer(new RawHTTP2ServerConnectionFactory(new HttpConfiguration(), new ServerSessionListener() { @Override public Map onPreface(Session session) @@ -370,17 +438,18 @@ public class HttpClientTransportOverHTTP2Test extends AbstractTest long idleTimeout = 1000; CountDownLatch resetLatch = new CountDownLatch(1); - start(new ServerSessionListener.Adapter() + start(new ServerSessionListener() { @Override public Stream.Listener onNewStream(Stream stream, HeadersFrame frame) { - return new Stream.Listener.Adapter() + return new Stream.Listener() { @Override - public void onReset(Stream stream, ResetFrame frame) + public void onReset(Stream stream, ResetFrame frame, Callback callback) { resetLatch.countDown(); + callback.succeeded(); } }; } @@ -402,17 +471,18 @@ public class HttpClientTransportOverHTTP2Test extends AbstractTest public void testRequestIdleTimeoutSendsResetFrame() throws Exception { CountDownLatch resetLatch = new CountDownLatch(1); - start(new ServerSessionListener.Adapter() + start(new ServerSessionListener() { @Override public Stream.Listener onNewStream(Stream stream, HeadersFrame frame) { - return new Stream.Listener.Adapter() + return new Stream.Listener() { @Override - public void onReset(Stream stream, ResetFrame frame) + public void onReset(Stream stream, ResetFrame frame, Callback callback) { resetLatch.countDown(); + callback.succeeded(); } }; } @@ -561,7 +631,7 @@ public class HttpClientTransportOverHTTP2Test extends AbstractTest public void test204WithContent() throws Exception { byte[] bytes = "No Content".getBytes(StandardCharsets.UTF_8); - start(new ServerSessionListener.Adapter() + start(new ServerSessionListener() { @Override public Stream.Listener onNewStream(Stream stream, HeadersFrame frame) @@ -588,7 +658,7 @@ public class HttpClientTransportOverHTTP2Test extends AbstractTest @Test public void testInvalidResponseHPack() throws Exception { - start(new ServerSessionListener.Adapter() + start(new ServerSessionListener() { @Override public Stream.Listener onNewStream(Stream stream, HeadersFrame frame) diff --git a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/IdleTimeoutTest.java b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/IdleTimeoutTest.java index 814e1f8dd0e..654f6e2d0f8 100644 --- a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/IdleTimeoutTest.java +++ b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/IdleTimeoutTest.java @@ -23,7 +23,6 @@ import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.http.MetaData; import org.eclipse.jetty.http2.FlowControlStrategy; -import org.eclipse.jetty.http2.ISession; import org.eclipse.jetty.http2.api.Session; import org.eclipse.jetty.http2.api.Stream; import org.eclipse.jetty.http2.api.server.ServerSessionListener; @@ -60,7 +59,7 @@ public class IdleTimeoutTest extends AbstractTest @Test public void testServerEnforcingIdleTimeout() throws Exception { - start(new ServerSessionListener.Adapter() + start(new ServerSessionListener() { @Override public Stream.Listener onNewStream(Stream stream, HeadersFrame requestFrame) @@ -74,13 +73,14 @@ public class IdleTimeoutTest extends AbstractTest }); connector.setIdleTimeout(idleTimeout); - final CountDownLatch latch = new CountDownLatch(1); - Session session = newClientSession(new Session.Listener.Adapter() + CountDownLatch latch = new CountDownLatch(1); + Session session = newClientSession(new Session.Listener() { @Override - public void onClose(Session session, GoAwayFrame frame) + public void onClose(Session session, GoAwayFrame frame, Callback callback) { latch.countDown(); + callback.succeeded(); } }); @@ -93,7 +93,7 @@ public class IdleTimeoutTest extends AbstractTest { stream.setIdleTimeout(10 * idleTimeout); } - }, new Stream.Listener.Adapter()); + }, null); assertTrue(latch.await(5 * idleTimeout, TimeUnit.MILLISECONDS)); } @@ -101,7 +101,7 @@ public class IdleTimeoutTest extends AbstractTest @Test public void testServerEnforcingIdleTimeoutWithUnrespondedStream() throws Exception { - start(new ServerSessionListener.Adapter() + start(new ServerSessionListener() { @Override public Stream.Listener onNewStream(Stream stream, HeadersFrame frame) @@ -112,13 +112,14 @@ public class IdleTimeoutTest extends AbstractTest }); connector.setIdleTimeout(idleTimeout); - final CountDownLatch latch = new CountDownLatch(1); - Session session = newClientSession(new Session.Listener.Adapter() + CountDownLatch latch = new CountDownLatch(1); + Session session = newClientSession(new Session.Listener() { @Override - public void onClose(Session session, GoAwayFrame frame) + public void onClose(Session session, GoAwayFrame frame, Callback callback) { latch.countDown(); + callback.succeeded(); } }); @@ -132,7 +133,7 @@ public class IdleTimeoutTest extends AbstractTest { stream.setIdleTimeout(10 * idleTimeout); } - }, new Stream.Listener.Adapter()); + }, null); assertTrue(latch.await(5 * idleTimeout, TimeUnit.MILLISECONDS)); } @@ -140,7 +141,7 @@ public class IdleTimeoutTest extends AbstractTest @Test public void testServerNotEnforcingIdleTimeoutWithinCallback() throws Exception { - start(new ServerSessionListener.Adapter() + start(new ServerSessionListener() { @Override public Stream.Listener onNewStream(Stream stream, HeadersFrame frame) @@ -159,17 +160,18 @@ public class IdleTimeoutTest extends AbstractTest }); connector.setIdleTimeout(idleTimeout); - final CountDownLatch closeLatch = new CountDownLatch(1); - Session session = newClientSession(new ServerSessionListener.Adapter() + CountDownLatch closeLatch = new CountDownLatch(1); + Session session = newClientSession(new ServerSessionListener() { @Override - public void onClose(Session session, GoAwayFrame frame) + public void onClose(Session session, GoAwayFrame frame, Callback callback) { closeLatch.countDown(); + callback.succeeded(); } }); - final CountDownLatch replyLatch = new CountDownLatch(1); + CountDownLatch replyLatch = new CountDownLatch(1); MetaData.Request metaData = newRequest("GET", HttpFields.EMPTY); HeadersFrame requestFrame = new HeadersFrame(metaData, null, true); session.newStream(requestFrame, new Promise.Adapter<>() @@ -179,7 +181,7 @@ public class IdleTimeoutTest extends AbstractTest { stream.setIdleTimeout(10 * idleTimeout); } - }, new Stream.Listener.Adapter() + }, new Stream.Listener() { @Override public void onHeaders(Stream stream, HeadersFrame frame) @@ -197,8 +199,8 @@ public class IdleTimeoutTest extends AbstractTest @Test public void testClientEnforcingIdleTimeout() throws Exception { - final CountDownLatch closeLatch = new CountDownLatch(1); - start(new ServerSessionListener.Adapter() + CountDownLatch closeLatch = new CountDownLatch(1); + start(new ServerSessionListener() { @Override public Stream.Listener onNewStream(Stream stream, HeadersFrame frame) @@ -211,14 +213,15 @@ public class IdleTimeoutTest extends AbstractTest } @Override - public void onClose(Session session, GoAwayFrame frame) + public void onClose(Session session, GoAwayFrame frame, Callback callback) { closeLatch.countDown(); + callback.succeeded(); } }); http2Client.setIdleTimeout(idleTimeout); - Session session = newClientSession(new Session.Listener.Adapter()); + Session session = newClientSession(new Session.Listener() {}); MetaData.Request metaData = newRequest("GET", HttpFields.EMPTY); HeadersFrame requestFrame = new HeadersFrame(metaData, null, true); session.newStream(requestFrame, new Promise.Adapter<>() @@ -228,7 +231,7 @@ public class IdleTimeoutTest extends AbstractTest { stream.setIdleTimeout(10 * idleTimeout); } - }, new Stream.Listener.Adapter()); + }, null); assertTrue(closeLatch.await(5 * idleTimeout, TimeUnit.MILLISECONDS)); assertTrue(session.isClosed()); @@ -237,8 +240,8 @@ public class IdleTimeoutTest extends AbstractTest @Test public void testClientEnforcingIdleTimeoutWithUnrespondedStream() throws Exception { - final CountDownLatch closeLatch = new CountDownLatch(1); - start(new ServerSessionListener.Adapter() + CountDownLatch closeLatch = new CountDownLatch(1); + start(new ServerSessionListener() { @Override public Stream.Listener onNewStream(Stream stream, HeadersFrame frame) @@ -248,14 +251,15 @@ public class IdleTimeoutTest extends AbstractTest } @Override - public void onClose(Session session, GoAwayFrame frame) + public void onClose(Session session, GoAwayFrame frame, Callback callback) { closeLatch.countDown(); + callback.succeeded(); } }); http2Client.setIdleTimeout(idleTimeout); - Session session = newClientSession(new Session.Listener.Adapter()); + Session session = newClientSession(new Session.Listener() {}); MetaData.Request metaData = newRequest("GET", HttpFields.EMPTY); HeadersFrame requestFrame = new HeadersFrame(metaData, null, true); session.newStream(requestFrame, new Promise.Adapter<>() @@ -265,7 +269,7 @@ public class IdleTimeoutTest extends AbstractTest { stream.setIdleTimeout(10 * idleTimeout); } - }, new Stream.Listener.Adapter()); + }, null); assertTrue(closeLatch.await(5 * idleTimeout, TimeUnit.MILLISECONDS)); } @@ -273,8 +277,8 @@ public class IdleTimeoutTest extends AbstractTest @Test public void testClientNotEnforcingIdleTimeoutWithinCallback() throws Exception { - final CountDownLatch closeLatch = new CountDownLatch(1); - start(new ServerSessionListener.Adapter() + CountDownLatch closeLatch = new CountDownLatch(1); + start(new ServerSessionListener() { @Override public Stream.Listener onNewStream(Stream stream, HeadersFrame frame) @@ -287,16 +291,17 @@ public class IdleTimeoutTest extends AbstractTest } @Override - public void onClose(Session session, GoAwayFrame frame) + public void onClose(Session session, GoAwayFrame frame, Callback callback) { closeLatch.countDown(); + callback.succeeded(); } }); http2Client.setIdleTimeout(idleTimeout); - Session session = newClientSession(new Session.Listener.Adapter()); + Session session = newClientSession(new Session.Listener() {}); - final CountDownLatch replyLatch = new CountDownLatch(1); + CountDownLatch replyLatch = new CountDownLatch(1); MetaData.Request metaData = newRequest("GET", HttpFields.EMPTY); HeadersFrame requestFrame = new HeadersFrame(metaData, null, true); session.newStream(requestFrame, new Promise.Adapter<>() @@ -306,7 +311,7 @@ public class IdleTimeoutTest extends AbstractTest { stream.setIdleTimeout(10 * idleTimeout); } - }, new Stream.Listener.Adapter() + }, new Stream.Listener() { @Override public void onHeaders(Stream stream, HeadersFrame frame) @@ -326,7 +331,7 @@ public class IdleTimeoutTest extends AbstractTest @Test public void testClientEnforcingStreamIdleTimeout() throws Exception { - final int idleTimeout = 1000; + int idleTimeout = 1000; start(new Handler.Processor() { @Override @@ -337,10 +342,10 @@ public class IdleTimeoutTest extends AbstractTest } }); - Session session = newClientSession(new Session.Listener.Adapter()); + Session session = newClientSession(new Session.Listener() {}); - final CountDownLatch dataLatch = new CountDownLatch(1); - final CountDownLatch timeoutLatch = new CountDownLatch(1); + CountDownLatch dataLatch = new CountDownLatch(1); + CountDownLatch timeoutLatch = new CountDownLatch(1); MetaData.Request metaData = newRequest("GET", HttpFields.EMPTY); HeadersFrame requestFrame = new HeadersFrame(metaData, null, true); session.newStream(requestFrame, new Promise.Adapter<>() @@ -350,12 +355,13 @@ public class IdleTimeoutTest extends AbstractTest { stream.setIdleTimeout(idleTimeout); } - }, new Stream.Listener.Adapter() + }, new Stream.Listener() { @Override - public void onData(Stream stream, DataFrame frame, Callback callback) + public void onDataAvailable(Stream stream) { - callback.succeeded(); + Stream.Data data = stream.readData(); + data.release(); dataLatch.countDown(); } @@ -381,14 +387,14 @@ public class IdleTimeoutTest extends AbstractTest @Test public void testServerEnforcingStreamIdleTimeout() throws Exception { - final CountDownLatch timeoutLatch = new CountDownLatch(1); - start(new ServerSessionListener.Adapter() + CountDownLatch timeoutLatch = new CountDownLatch(1); + start(new ServerSessionListener() { @Override public Stream.Listener onNewStream(Stream stream, HeadersFrame frame) { stream.setIdleTimeout(idleTimeout); - return new Stream.Listener.Adapter() + return new Stream.Listener() { @Override public boolean onIdleTimeout(Stream stream, Throwable x) @@ -400,17 +406,18 @@ public class IdleTimeoutTest extends AbstractTest } }); - final CountDownLatch resetLatch = new CountDownLatch(1); - Session session = newClientSession(new Session.Listener.Adapter()); + CountDownLatch resetLatch = new CountDownLatch(1); + Session session = newClientSession(new Session.Listener() {}); MetaData.Request metaData = newRequest("GET", HttpFields.EMPTY); // Stream does not end here, but we won't send any DATA frame. HeadersFrame requestFrame = new HeadersFrame(metaData, null, false); - session.newStream(requestFrame, new Promise.Adapter<>(), new Stream.Listener.Adapter() + session.newStream(requestFrame, new Promise.Adapter<>(), new Stream.Listener() { @Override - public void onReset(Stream stream, ResetFrame frame) + public void onReset(Stream stream, ResetFrame frame, Callback callback) { resetLatch.countDown(); + callback.succeeded(); } }); @@ -426,14 +433,15 @@ public class IdleTimeoutTest extends AbstractTest @Test public void testServerStreamIdleTimeoutIsNotEnforcedWhenReceiving() throws Exception { - final CountDownLatch timeoutLatch = new CountDownLatch(1); - start(new ServerSessionListener.Adapter() + CountDownLatch timeoutLatch = new CountDownLatch(1); + start(new ServerSessionListener() { @Override public Stream.Listener onNewStream(Stream stream, HeadersFrame frame) { stream.setIdleTimeout(idleTimeout); - return new Stream.Listener.Adapter() + stream.demand(); + return new Stream.Listener() { @Override public boolean onIdleTimeout(Stream stream, Throwable x) @@ -445,15 +453,15 @@ public class IdleTimeoutTest extends AbstractTest } }); - Session session = newClientSession(new Session.Listener.Adapter()); + Session session = newClientSession(new Session.Listener() {}); MetaData.Request metaData = newRequest("GET", HttpFields.EMPTY); HeadersFrame requestFrame = new HeadersFrame(metaData, null, false); FuturePromise promise = new FuturePromise<>(); - session.newStream(requestFrame, promise, new Stream.Listener.Adapter()); - final Stream stream = promise.get(5, TimeUnit.SECONDS); + session.newStream(requestFrame, promise, null); + Stream stream = promise.get(5, TimeUnit.SECONDS); sleep(idleTimeout / 2); - final CountDownLatch dataLatch = new CountDownLatch(1); + CountDownLatch dataLatch = new CountDownLatch(1); stream.data(new DataFrame(stream.getId(), ByteBuffer.allocate(1), false), new Callback() { private int sends; @@ -462,15 +470,9 @@ public class IdleTimeoutTest extends AbstractTest public void succeeded() { sleep(idleTimeout / 2); - final boolean last = ++sends == 2; + boolean last = ++sends == 2; stream.data(new DataFrame(stream.getId(), ByteBuffer.allocate(1), last), !last ? this : new Callback() { - @Override - public InvocationType getInvocationType() - { - return InvocationType.NON_BLOCKING; - } - @Override public void succeeded() { @@ -478,6 +480,12 @@ public class IdleTimeoutTest extends AbstractTest assertEquals(1, timeoutLatch.getCount()); dataLatch.countDown(); } + + @Override + public InvocationType getInvocationType() + { + return InvocationType.NON_BLOCKING; + } }); } }); @@ -490,8 +498,8 @@ public class IdleTimeoutTest extends AbstractTest @Test public void testClientStreamIdleTimeoutIsNotEnforcedWhenSending() throws Exception { - final CountDownLatch resetLatch = new CountDownLatch(1); - start(new ServerSessionListener.Adapter() + CountDownLatch resetLatch = new CountDownLatch(1); + start(new ServerSessionListener() { @Override public Stream.Listener onNewStream(Stream stream, HeadersFrame frame) @@ -508,7 +516,7 @@ public class IdleTimeoutTest extends AbstractTest } }); - Session session = newClientSession(new Session.Listener.Adapter()); + Session session = newClientSession(new Session.Listener() {}); MetaData.Request metaData = newRequest("GET", HttpFields.EMPTY); HeadersFrame requestFrame = new HeadersFrame(metaData, null, false); FuturePromise promise = new FuturePromise<>() @@ -520,8 +528,8 @@ public class IdleTimeoutTest extends AbstractTest super.succeeded(stream); } }; - session.newStream(requestFrame, promise, new Stream.Listener.Adapter()); - final Stream stream = promise.get(5, TimeUnit.SECONDS); + session.newStream(requestFrame, promise, null); + Stream stream = promise.get(5, TimeUnit.SECONDS); Callback.Completable completable1 = new Callback.Completable(); sleep(idleTimeout / 2); @@ -589,12 +597,12 @@ public class IdleTimeoutTest extends AbstractTest // to make sure it does not fire spuriously. connector.setIdleTimeout(3 * delay); - Session session = newClientSession(new Session.Listener.Adapter()); + Session session = newClientSession(new Session.Listener() {}); MetaData.Request metaData = newRequest("POST", HttpFields.EMPTY); HeadersFrame requestFrame = new HeadersFrame(metaData, null, false); FuturePromise promise = new FuturePromise<>(); CountDownLatch latch = new CountDownLatch(1); - session.newStream(requestFrame, promise, new Stream.Listener.Adapter() + session.newStream(requestFrame, promise, new Stream.Listener() { @Override public void onHeaders(Stream stream, HeadersFrame frame) @@ -649,7 +657,7 @@ public class IdleTimeoutTest extends AbstractTest prepareClient(); http2Client.start(); - Session client = newClientSession(new Session.Listener.Adapter()); + Session client = newClientSession(new Session.Listener() {}); // Send requests until one is queued on the server but not dispatched. int count = 0; @@ -661,7 +669,7 @@ public class IdleTimeoutTest extends AbstractTest MetaData.Request request = newRequest("GET", "/" + count, HttpFields.EMPTY); HeadersFrame frame = new HeadersFrame(request, null, false); FuturePromise promise = new FuturePromise<>(); - client.newStream(frame, promise, new Stream.Listener.Adapter()); + client.newStream(frame, promise, null); Stream stream = promise.get(5, TimeUnit.SECONDS); ByteBuffer data = ByteBuffer.allocate(10); stream.data(new DataFrame(stream.getId(), data, true), Callback.NOOP); @@ -675,16 +683,17 @@ public class IdleTimeoutTest extends AbstractTest MetaData.Request request = newRequest("GET", HttpFields.EMPTY); HeadersFrame frame = new HeadersFrame(request, null, false); FuturePromise promise = new FuturePromise<>(); - client.newStream(frame, promise, new Stream.Listener.Adapter() + client.newStream(frame, promise, new Stream.Listener() { @Override - public void onReset(Stream stream, ResetFrame frame) + public void onReset(Stream stream, ResetFrame frame, Callback callback) { + callback.succeeded(); resetLatch.countDown(); } }); Stream stream = promise.get(5, TimeUnit.SECONDS); - ByteBuffer data = ByteBuffer.allocate(((ISession)client).updateSendWindow(0)); + ByteBuffer data = ByteBuffer.allocate(((HTTP2Session)client).updateSendWindow(0)); stream.data(new DataFrame(stream.getId(), data, true), Callback.NOOP); assertTrue(resetLatch.await(2 * idleTimeout, TimeUnit.MILLISECONDS)); @@ -692,7 +701,7 @@ public class IdleTimeoutTest extends AbstractTest // Wait for WINDOW_UPDATEs to be processed by the client. sleep(1000); - assertThat(((ISession)client).updateSendWindow(0), Matchers.greaterThan(0)); + assertThat(((HTTP2Session)client).updateSendWindow(0), Matchers.greaterThan(0)); } private void sleep(long value) diff --git a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/InterleavingTest.java b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/InterleavingTest.java index 1bf567a5121..6876569476e 100644 --- a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/InterleavingTest.java +++ b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/InterleavingTest.java @@ -29,7 +29,6 @@ import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.http.MetaData; -import org.eclipse.jetty.http2.ISession; import org.eclipse.jetty.http2.api.Session; import org.eclipse.jetty.http2.api.Stream; import org.eclipse.jetty.http2.api.server.ServerSessionListener; @@ -37,6 +36,7 @@ import org.eclipse.jetty.http2.frames.DataFrame; import org.eclipse.jetty.http2.frames.Frame; import org.eclipse.jetty.http2.frames.HeadersFrame; import org.eclipse.jetty.http2.frames.SettingsFrame; +import org.eclipse.jetty.http2.internal.HTTP2Session; import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.FuturePromise; @@ -57,7 +57,7 @@ public class InterleavingTest extends AbstractTest { CountDownLatch serverStreamsLatch = new CountDownLatch(2); List serverStreams = new ArrayList<>(); - start(new ServerSessionListener.Adapter() + start(new ServerSessionListener() { @Override public Stream.Listener onNewStream(Stream stream, HeadersFrame frame) @@ -69,7 +69,7 @@ public class InterleavingTest extends AbstractTest }); int maxFrameSize = Frame.DEFAULT_MAX_LENGTH + 1; - Session session = newClientSession(new Session.Listener.Adapter() + Session session = newClientSession(new Session.Listener() { @Override public Map onPreface(Session session) @@ -80,13 +80,16 @@ public class InterleavingTest extends AbstractTest } }); - BlockingQueue dataFrames = new LinkedBlockingDeque<>(); - Stream.Listener streamListener = new Stream.Listener.Adapter() + BlockingQueue dataQueue = new LinkedBlockingDeque<>(); + Stream.Listener streamListener = new Stream.Listener() { @Override - public void onData(Stream stream, DataFrame frame, Callback callback) + public void onDataAvailable(Stream stream) { - dataFrames.offer(new DataFrameCallback(frame, callback)); + Stream.Data data = stream.readData(); + // Do not release. + dataQueue.offer(data); + stream.demand(); } }; @@ -110,9 +113,9 @@ public class InterleavingTest extends AbstractTest serverStream1.headers(new HeadersFrame(serverStream1.getId(), response1, null, false), Callback.NOOP); Random random = new Random(); - byte[] content1 = new byte[2 * ((ISession)serverStream1.getSession()).updateSendWindow(0)]; + byte[] content1 = new byte[2 * ((HTTP2Session)serverStream1.getSession()).updateSendWindow(0)]; random.nextBytes(content1); - byte[] content2 = new byte[2 * ((ISession)serverStream2.getSession()).updateSendWindow(0)]; + byte[] content2 = new byte[2 * ((HTTP2Session)serverStream2.getSession()).updateSendWindow(0)]; random.nextBytes(content2); MetaData.Response response2 = new MetaData.Response(HttpVersion.HTTP_2, HttpStatus.OK_200, HttpFields.EMPTY); @@ -143,11 +146,11 @@ public class InterleavingTest extends AbstractTest int finished = 0; while (finished < 2) { - DataFrameCallback dataFrameCallback = dataFrames.poll(5, TimeUnit.SECONDS); - if (dataFrameCallback == null) + Stream.Data data = dataQueue.poll(5, TimeUnit.SECONDS); + if (data == null) fail(); - DataFrame dataFrame = dataFrameCallback.frame; + DataFrame dataFrame = data.frame(); int streamId = dataFrame.getStreamId(); int length = dataFrame.remaining(); streamLengths.add(new StreamLength(streamId, length)); @@ -156,7 +159,7 @@ public class InterleavingTest extends AbstractTest BufferUtil.writeTo(dataFrame.getData(), contents.get(streamId)); - dataFrameCallback.callback.succeeded(); + data.release(); } // Verify that the content has been sent properly. @@ -196,18 +199,6 @@ public class InterleavingTest extends AbstractTest }); } - private static class DataFrameCallback - { - private final DataFrame frame; - private final Callback callback; - - private DataFrameCallback(DataFrame frame, Callback callback) - { - this.frame = frame; - this.callback = callback; - } - } - private static class StreamLength { private final int stream; diff --git a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/MaxConcurrentStreamsTest.java b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/MaxConcurrentStreamsTest.java index d33f354a9f5..45d36ace7ec 100644 --- a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/MaxConcurrentStreamsTest.java +++ b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/MaxConcurrentStreamsTest.java @@ -449,7 +449,7 @@ public class MaxConcurrentStreamsTest extends AbstractTest public void testTCPCongestedStreamTimesOut() throws Exception { CountDownLatch request1Latch = new CountDownLatch(1); - RawHTTP2ServerConnectionFactory http2 = new RawHTTP2ServerConnectionFactory(new HttpConfiguration(), new ServerSessionListener.Adapter() + RawHTTP2ServerConnectionFactory http2 = new RawHTTP2ServerConnectionFactory(new HttpConfiguration(), new ServerSessionListener() { @Override public Stream.Listener onNewStream(Stream stream, HeadersFrame frame) @@ -475,8 +475,7 @@ public class MaxConcurrentStreamsTest extends AbstractTest stream.headers(new HeadersFrame(stream.getId(), response, null, true), Callback.NOOP); } } - // Return a Stream listener that consumes the content. - return new Stream.Listener.Adapter(); + return null; } }); http2.setMaxConcurrentStreams(2); @@ -559,7 +558,7 @@ public class MaxConcurrentStreamsTest extends AbstractTest public void testDifferentMaxConcurrentStreamsForDifferentConnections() throws Exception { long processing = 125; - RawHTTP2ServerConnectionFactory http2 = new RawHTTP2ServerConnectionFactory(new HttpConfiguration(), new ServerSessionListener.Adapter() + RawHTTP2ServerConnectionFactory http2 = new RawHTTP2ServerConnectionFactory(new HttpConfiguration(), new ServerSessionListener() { private Session session1; private Session session2; @@ -728,9 +727,9 @@ public class MaxConcurrentStreamsTest extends AbstractTest } @Override - public void onClose(Session session, GoAwayFrame frame) + public void onClose(Session session, GoAwayFrame frame, Callback callback) { - listener.onClose(session, frame); + listener.onClose(session, frame, callback); } @Override @@ -740,9 +739,9 @@ public class MaxConcurrentStreamsTest extends AbstractTest } @Override - public void onFailure(Session session, Throwable failure) + public void onFailure(Session session, Throwable failure, Callback callback) { - listener.onFailure(session, failure); + listener.onFailure(session, failure, callback); } } } diff --git a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/MaxPushedStreamsTest.java b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/MaxPushedStreamsTest.java index 25e3132da99..0d194f65455 100644 --- a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/MaxPushedStreamsTest.java +++ b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/MaxPushedStreamsTest.java @@ -52,7 +52,7 @@ public class MaxPushedStreamsTest extends AbstractTest int maxPushed = 2; CountDownLatch resetLatch = new CountDownLatch(1); - start(new ServerSessionListener.Adapter() + start(new ServerSessionListener() { @Override public Stream.Listener onNewStream(Stream stream, HeadersFrame frame) @@ -77,7 +77,7 @@ public class MaxPushedStreamsTest extends AbstractTest .map(pushFrame -> { Promise.Completable promise = new Promise.Completable<>(); - stream.push(pushFrame, promise, new Stream.Listener.Adapter()); + stream.push(pushFrame, promise, null); return promise; }) // ... wait for the pushed streams... @@ -88,13 +88,14 @@ public class MaxPushedStreamsTest extends AbstractTest { PushPromiseFrame extraPushFrame = new PushPromiseFrame(stream.getId(), 0, newRequest("GET", "/push_extra", HttpFields.EMPTY)); FuturePromise extraPromise = new FuturePromise<>(); - stream.push(extraPushFrame, extraPromise, new Stream.Listener.Adapter() + stream.push(extraPushFrame, extraPromise, new Stream.Listener() { @Override - public void onReset(Stream stream, ResetFrame frame) + public void onReset(Stream stream, ResetFrame frame, Callback callback) { assertEquals(ErrorCode.REFUSED_STREAM_ERROR.code, frame.getError()); resetLatch.countDown(); + callback.succeeded(); } }); return streams; @@ -116,10 +117,10 @@ public class MaxPushedStreamsTest extends AbstractTest }); http2Client.setMaxConcurrentPushedStreams(maxPushed); - Session session = newClientSession(new Session.Listener.Adapter()); + Session session = newClientSession(new Session.Listener() {}); MetaData.Request request = newRequest("GET", HttpFields.EMPTY); CountDownLatch responseLatch = new CountDownLatch(1); - session.newStream(new HeadersFrame(request, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter() + session.newStream(new HeadersFrame(request, null, true), new Promise.Adapter<>(), new Stream.Listener() { @Override public void onHeaders(Stream stream, HeadersFrame frame) diff --git a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/PingTest.java b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/PingTest.java index 4dcc18b3f4d..379cc68fbf6 100644 --- a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/PingTest.java +++ b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/PingTest.java @@ -31,12 +31,12 @@ public class PingTest extends AbstractTest @Test public void testPing() throws Exception { - start(new ServerSessionListener.Adapter()); + start(new ServerSessionListener() {}); final byte[] payload = new byte[8]; new Random().nextBytes(payload); final CountDownLatch latch = new CountDownLatch(1); - Session session = newClientSession(new Session.Listener.Adapter() + Session session = newClientSession(new Session.Listener() { @Override public void onPing(Session session, PingFrame frame) diff --git a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/PrefaceTest.java b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/PrefaceTest.java index e4a968458f9..b4a3f0e18f8 100644 --- a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/PrefaceTest.java +++ b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/PrefaceTest.java @@ -72,7 +72,7 @@ public class PrefaceTest extends AbstractTest @Test public void testServerPrefaceReplySentAfterClientPreface() throws Exception { - start(new ServerSessionListener.Adapter() + start(new ServerSessionListener() { @Override public void onAccept(Session session) @@ -91,7 +91,7 @@ public class PrefaceTest extends AbstractTest } }); - Session session = newClientSession(new Session.Listener.Adapter() + Session session = newClientSession(new Session.Listener() { @Override public Map onPreface(Session session) @@ -114,7 +114,7 @@ public class PrefaceTest extends AbstractTest CountDownLatch latch = new CountDownLatch(1); MetaData.Request metaData = newRequest("GET", HttpFields.EMPTY); HeadersFrame requestFrame = new HeadersFrame(metaData, null, true); - session.newStream(requestFrame, new Promise.Adapter<>(), new Stream.Listener.Adapter() + session.newStream(requestFrame, new Promise.Adapter<>(), new Stream.Listener() { @Override public void onHeaders(Stream stream, HeadersFrame frame) @@ -130,7 +130,7 @@ public class PrefaceTest extends AbstractTest @Test public void testClientPrefaceReplySentAfterServerPreface() throws Exception { - start(new ServerSessionListener.Adapter() + start(new ServerSessionListener() { @Override public Map onPreface(Session session) @@ -197,8 +197,10 @@ public class PrefaceTest extends AbstractTest assertEquals(2, settings.size()); SettingsFrame frame1 = settings.poll(); + assertNotNull(frame1); assertFalse(frame1.isReply()); SettingsFrame frame2 = settings.poll(); + assertNotNull(frame2); assertTrue(frame2.isReply()); } } @@ -215,7 +217,7 @@ public class PrefaceTest extends AbstractTest @Override protected ServerSessionListener newSessionListener(Connector connector, EndPoint endPoint) { - return new ServerSessionListener.Adapter() + return new ServerSessionListener() { @Override public Map onPreface(Session session) @@ -249,13 +251,14 @@ public class PrefaceTest extends AbstractTest { socket.connect(new InetSocketAddress("localhost", connector.getLocalPort())); - String upgradeRequest = - "GET /one HTTP/1.1\r\n" + - "Host: localhost\r\n" + - "Connection: Upgrade, HTTP2-Settings\r\n" + - "Upgrade: h2c\r\n" + - "HTTP2-Settings: \r\n" + - "\r\n"; + String upgradeRequest = """ + GET /one HTTP/1.1\r + Host: localhost\r + Connection: Upgrade, HTTP2-Settings\r + Upgrade: h2c\r + HTTP2-Settings: \r + \r + """; ByteBuffer upgradeBuffer = ByteBuffer.wrap(upgradeRequest.getBytes(StandardCharsets.ISO_8859_1)); socket.write(upgradeBuffer); @@ -355,12 +358,13 @@ public class PrefaceTest extends AbstractTest CountDownLatch failureLatch = new CountDownLatch(1); Promise.Completable promise = new Promise.Completable<>(); InetSocketAddress address = new InetSocketAddress("localhost", server.getLocalPort()); - http2Client.connect(address, new Session.Listener.Adapter() + http2Client.connect(address, new Session.Listener() { @Override - public void onFailure(Session session, Throwable failure) + public void onFailure(Session session, Throwable failure, Callback callback) { failureLatch.countDown(); + callback.succeeded(); } }, promise); @@ -389,7 +393,7 @@ public class PrefaceTest extends AbstractTest @Test public void testInvalidClientPreface() throws Exception { - start(new ServerSessionListener.Adapter()); + start(new ServerSessionListener() {}); try (Socket client = new Socket("localhost", connector.getLocalPort())) { diff --git a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/PriorKnowledgeHTTP2OverTLSTest.java b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/PriorKnowledgeHTTP2OverTLSTest.java index 90e9c214fb3..51632ed1248 100644 --- a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/PriorKnowledgeHTTP2OverTLSTest.java +++ b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/PriorKnowledgeHTTP2OverTLSTest.java @@ -139,13 +139,13 @@ public class PriorKnowledgeHTTP2OverTLSTest }); int port = connector.getLocalPort(); - MetaData.Response response = http2Client.connect(http2Client.getClientConnector().getSslContextFactory(), new InetSocketAddress("localhost", port), new Session.Listener.Adapter()) + MetaData.Response response = http2Client.connect(http2Client.getClientConnector().getSslContextFactory(), new InetSocketAddress("localhost", port), new Session.Listener() {}) .thenCompose(session -> { CompletableFuture responsePromise = new CompletableFuture<>(); HttpURI.Mutable uri = HttpURI.build("https://localhost:" + port + "/path"); MetaData.Request request = new MetaData.Request("GET", uri, HttpVersion.HTTP_2, HttpFields.EMPTY); - return session.newStream(new HeadersFrame(request, null, true), new Stream.Listener.Adapter() + return session.newStream(new HeadersFrame(request, null, true), new Stream.Listener() { @Override public void onHeaders(Stream stream, HeadersFrame frame) diff --git a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/PriorityTest.java b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/PriorityTest.java index 3b044689c20..2e4ea87f7d0 100644 --- a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/PriorityTest.java +++ b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/PriorityTest.java @@ -38,7 +38,7 @@ public class PriorityTest extends AbstractTest @Test public void testPriorityBeforeHeaders() throws Exception { - start(new ServerSessionListener.Adapter() + start(new ServerSessionListener() { @Override public Stream.Listener onNewStream(Stream stream, HeadersFrame frame) @@ -50,7 +50,7 @@ public class PriorityTest extends AbstractTest } }); - Session session = newClientSession(new Session.Listener.Adapter()); + Session session = newClientSession(new Session.Listener() {}); int streamId = session.priority(new PriorityFrame(0, 13, false), Callback.NOOP); assertTrue(streamId > 0); @@ -65,7 +65,7 @@ public class PriorityTest extends AbstractTest assertEquals(streamId, result.getId()); latch.countDown(); } - }, new Stream.Listener.Adapter() + }, new Stream.Listener() { @Override public void onHeaders(Stream stream, HeadersFrame frame) @@ -83,7 +83,7 @@ public class PriorityTest extends AbstractTest { CountDownLatch beforeRequests = new CountDownLatch(1); CountDownLatch afterRequests = new CountDownLatch(2); - start(new ServerSessionListener.Adapter() + start(new ServerSessionListener() { @Override public Stream.Listener onNewStream(Stream stream, HeadersFrame frame) @@ -106,7 +106,7 @@ public class PriorityTest extends AbstractTest }); CountDownLatch responses = new CountDownLatch(2); - Stream.Listener.Adapter listener = new Stream.Listener.Adapter() + Stream.Listener listener = new Stream.Listener() { @Override public void onHeaders(Stream stream, HeadersFrame frame) @@ -116,7 +116,7 @@ public class PriorityTest extends AbstractTest } }; - Session session = newClientSession(new Session.Listener.Adapter()); + Session session = newClientSession(new Session.Listener() {}); MetaData metaData1 = newRequest("GET", "/one", HttpFields.EMPTY); HeadersFrame headersFrame1 = new HeadersFrame(metaData1, null, true); FuturePromise promise1 = new FuturePromise<>(); @@ -145,7 +145,7 @@ public class PriorityTest extends AbstractTest { PriorityFrame priorityFrame = new PriorityFrame(13, 200, true); CountDownLatch latch = new CountDownLatch(2); - start(new ServerSessionListener.Adapter() + start(new ServerSessionListener() { @Override public Stream.Listener onNewStream(Stream stream, HeadersFrame frame) @@ -164,10 +164,10 @@ public class PriorityTest extends AbstractTest } }); - Session session = newClientSession(new Session.Listener.Adapter()); + Session session = newClientSession(new Session.Listener() {}); MetaData metaData = newRequest("GET", "/one", HttpFields.EMPTY); HeadersFrame headersFrame = new HeadersFrame(metaData, priorityFrame, true); - session.newStream(headersFrame, new Promise.Adapter<>(), new Stream.Listener.Adapter() + session.newStream(headersFrame, new Promise.Adapter<>(), new Stream.Listener() { @Override public void onHeaders(Stream stream, HeadersFrame frame) diff --git a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/ProxyProtocolTest.java b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/ProxyProtocolTest.java index 01d54776889..5296ce91717 100644 --- a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/ProxyProtocolTest.java +++ b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/ProxyProtocolTest.java @@ -100,14 +100,14 @@ public class ProxyProtocolTest channel.write(ByteBuffer.wrap(request1.getBytes(StandardCharsets.UTF_8))); FuturePromise promise = new FuturePromise<>(); - client.accept(null, channel, new Session.Listener.Adapter(), promise); + client.accept(null, channel, new Session.Listener() {}, promise); Session session = promise.get(5, TimeUnit.SECONDS); String uri = "http://localhost:" + connector.getLocalPort() + "/"; MetaData.Request metaData = new MetaData.Request("GET", HttpURI.from(uri), HttpVersion.HTTP_2, HttpFields.EMPTY); HeadersFrame frame = new HeadersFrame(metaData, null, true); CountDownLatch latch = new CountDownLatch(1); - session.newStream(frame, new Promise.Adapter<>(), new Stream.Listener.Adapter() + session.newStream(frame, new Promise.Adapter<>(), new Stream.Listener() { @Override public void onHeaders(Stream stream, HeadersFrame frame) @@ -149,14 +149,14 @@ public class ProxyProtocolTest channel.write(ByteBuffer.wrap(StringUtil.fromHexString(request1))); FuturePromise promise = new FuturePromise<>(); - client.accept(null, channel, new Session.Listener.Adapter(), promise); + client.accept(null, channel, new Session.Listener() {}, promise); Session session = promise.get(5, TimeUnit.SECONDS); String uri = "http://localhost:" + connector.getLocalPort() + "/"; MetaData.Request metaData = new MetaData.Request("GET", HttpURI.from(uri), HttpVersion.HTTP_2, HttpFields.EMPTY); HeadersFrame frame = new HeadersFrame(metaData, null, true); CountDownLatch latch = new CountDownLatch(1); - session.newStream(frame, new Promise.Adapter<>(), new Stream.Listener.Adapter() + session.newStream(frame, new Promise.Adapter<>(), new Stream.Listener() { @Override public void onHeaders(Stream stream, HeadersFrame frame) diff --git a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/ProxyTest.java b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/ProxyTest.java index 06aa001f630..4d15b0295b5 100644 --- a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/ProxyTest.java +++ b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/ProxyTest.java @@ -124,10 +124,10 @@ public class ProxyTest // startClient(); // // CountDownLatch clientLatch = new CountDownLatch(1); -// Session session = newClient(new Session.Listener.Adapter()); +// Session session = newClient(new Session.Listener() {}); // MetaData.Request metaData = newRequest("GET", "/", HttpFields.EMPTY); // HeadersFrame frame = new HeadersFrame(metaData, null, true); -// session.newStream(frame, new Promise.Adapter<>(), new Stream.Listener.Adapter() +// session.newStream(frame, new Promise.Adapter<>(), new Stream.Listener() // { // @Override // public void onHeaders(Stream stream, HeadersFrame frame) @@ -162,10 +162,10 @@ public class ProxyTest // startClient(); // // CountDownLatch clientLatch = new CountDownLatch(1); -// Session session = newClient(new Session.Listener.Adapter()); +// Session session = newClient(new Session.Listener() {}); // MetaData.Request metaData = newRequest("GET", "/", HttpFields.EMPTY); // HeadersFrame frame = new HeadersFrame(metaData, null, true); -// session.newStream(frame, new Promise.Adapter<>(), new Stream.Listener.Adapter() +// session.newStream(frame, new Promise.Adapter<>(), new Stream.Listener() // { // @Override // public void onData(Stream stream, DataFrame frame, Callback callback) diff --git a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/PushCacheFilterTest.java b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/PushCacheFilterTest.java index e97b7106ca0..ccdf82cced8 100644 --- a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/PushCacheFilterTest.java +++ b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/PushCacheFilterTest.java @@ -68,13 +68,13 @@ public class PushCacheFilterTest extends AbstractTest // } // }); // -// final Session session = newClient(new Session.Listener.Adapter()); +// final Session session = newClient(new Session.Listener() {}); // // // Request for the primary and secondary resource to build the cache. // final String referrerURI = newURI(primaryResource); // MetaData.Request primaryRequest = newRequest("GET", primaryResource, HttpFields.EMPTY); // final CountDownLatch warmupLatch = new CountDownLatch(1); -// session.newStream(new HeadersFrame(primaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter() +// session.newStream(new HeadersFrame(primaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener() // { // @Override // public void onData(Stream stream, DataFrame frame, Callback callback) @@ -86,7 +86,7 @@ public class PushCacheFilterTest extends AbstractTest // HttpFields.Mutable secondaryFields = HttpFields.build() // .put(HttpHeader.REFERER, referrerURI); // MetaData.Request secondaryRequest = newRequest("GET", secondaryResource, secondaryFields); -// session.newStream(new HeadersFrame(secondaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter() +// session.newStream(new HeadersFrame(secondaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener() // { // @Override // public void onData(Stream stream, DataFrame frame, Callback callback) @@ -104,7 +104,7 @@ public class PushCacheFilterTest extends AbstractTest // primaryRequest = newRequest("GET", primaryResource, HttpFields.EMPTY); // final CountDownLatch primaryResponseLatch = new CountDownLatch(2); // final CountDownLatch pushLatch = new CountDownLatch(2); -// session.newStream(new HeadersFrame(primaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter() +// session.newStream(new HeadersFrame(primaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener() // { // @Override // public void onHeaders(Stream stream, HeadersFrame frame) @@ -169,7 +169,7 @@ public class PushCacheFilterTest extends AbstractTest // } // }); // -// final Session session = newClient(new Session.Listener.Adapter()); +// final Session session = newClient(new Session.Listener() {}); // // // Request for the primary and secondary resource to build the cache. // // The referrerURI does not point to the primary resource, so there will be no @@ -178,7 +178,7 @@ public class PushCacheFilterTest extends AbstractTest // HttpFields.Mutable primaryFields = HttpFields.build(); // MetaData.Request primaryRequest = newRequest("GET", primaryResource, primaryFields); // final CountDownLatch warmupLatch = new CountDownLatch(1); -// session.newStream(new HeadersFrame(primaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter() +// session.newStream(new HeadersFrame(primaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener() // { // @Override // public void onData(Stream stream, DataFrame frame, Callback callback) @@ -190,7 +190,7 @@ public class PushCacheFilterTest extends AbstractTest // HttpFields.Mutable secondaryFields = HttpFields.build() // .put(HttpHeader.REFERER, referrerURI); // MetaData.Request secondaryRequest = newRequest("GET", secondaryResource, secondaryFields); -// session.newStream(new HeadersFrame(secondaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter() +// session.newStream(new HeadersFrame(secondaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener() // { // @Override // public void onData(Stream stream, DataFrame frame, Callback callback) @@ -208,7 +208,7 @@ public class PushCacheFilterTest extends AbstractTest // primaryRequest = newRequest("GET", primaryResource, primaryFields); // final CountDownLatch primaryResponseLatch = new CountDownLatch(1); // final CountDownLatch pushLatch = new CountDownLatch(1); -// session.newStream(new HeadersFrame(primaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter() +// session.newStream(new HeadersFrame(primaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener() // { // @Override // public Stream.Listener onPush(Stream stream, PushPromiseFrame frame) @@ -257,14 +257,14 @@ public class PushCacheFilterTest extends AbstractTest // } // }); // -// final Session session = newClient(new Session.Listener.Adapter()); +// final Session session = newClient(new Session.Listener() {}); // // // Request for the primary and secondary resource to build the cache. // final String primaryURI = newURI(primaryResource); // HttpFields.Mutable primaryFields = HttpFields.build(); // MetaData.Request primaryRequest = newRequest("GET", primaryResource, primaryFields); // final CountDownLatch warmupLatch = new CountDownLatch(1); -// session.newStream(new HeadersFrame(primaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter() +// session.newStream(new HeadersFrame(primaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener() // { // @Override // public void onData(Stream stream, DataFrame frame, Callback callback) @@ -276,7 +276,7 @@ public class PushCacheFilterTest extends AbstractTest // HttpFields.Mutable secondaryFields = HttpFields.build() // .put(HttpHeader.REFERER, primaryURI); // MetaData.Request secondaryRequest = newRequest("GET", secondaryResource, secondaryFields); -// session.newStream(new HeadersFrame(secondaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter() +// session.newStream(new HeadersFrame(secondaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener() // { // @Override // public void onData(Stream stream, DataFrame frame, Callback callback) @@ -294,7 +294,7 @@ public class PushCacheFilterTest extends AbstractTest // primaryRequest = newRequest("GET", primaryResource, primaryFields); // final CountDownLatch primaryResponseLatch = new CountDownLatch(1); // final CountDownLatch pushLatch = new CountDownLatch(1); -// session.newStream(new HeadersFrame(primaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter() +// session.newStream(new HeadersFrame(primaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener() // { // @Override // public Stream.Listener onPush(Stream stream, PushPromiseFrame frame) @@ -330,7 +330,7 @@ public class PushCacheFilterTest extends AbstractTest // secondaryFields.put(HttpHeader.REFERER, primaryURI); // MetaData.Request secondaryRequest = newRequest("GET", secondaryResource, secondaryFields); // final CountDownLatch secondaryResponseLatch = new CountDownLatch(1); -// session.newStream(new HeadersFrame(secondaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter() +// session.newStream(new HeadersFrame(secondaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener() // { // @Override // public void onData(Stream stream, DataFrame frame, Callback callback) @@ -360,14 +360,14 @@ public class PushCacheFilterTest extends AbstractTest // } // }); // -// final Session session = newClient(new Session.Listener.Adapter()); +// final Session session = newClient(new Session.Listener() {}); // // // Request for the primary and secondary resource to build the cache. // final String primaryURI = newURI(primaryResource); // HttpFields.Mutable primaryFields = HttpFields.build(); // MetaData.Request primaryRequest = newRequest("GET", primaryResource, primaryFields); // final CountDownLatch warmupLatch = new CountDownLatch(1); -// session.newStream(new HeadersFrame(primaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter() +// session.newStream(new HeadersFrame(primaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener() // { // @Override // public void onHeaders(Stream stream, HeadersFrame frame) @@ -378,7 +378,7 @@ public class PushCacheFilterTest extends AbstractTest // HttpFields.Mutable secondaryFields = HttpFields.build(); // secondaryFields.put(HttpHeader.REFERER, primaryURI); // MetaData.Request secondaryRequest = newRequest("GET", secondaryResource, secondaryFields); -// session.newStream(new HeadersFrame(secondaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter() +// session.newStream(new HeadersFrame(secondaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener() // { // @Override // public void onData(Stream stream, DataFrame frame, Callback callback) @@ -398,7 +398,7 @@ public class PushCacheFilterTest extends AbstractTest // primaryRequest = newRequest("GET", primaryResource, primaryFields); // final CountDownLatch primaryResponseLatch = new CountDownLatch(1); // final CountDownLatch pushLatch = new CountDownLatch(1); -// session.newStream(new HeadersFrame(primaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter() +// session.newStream(new HeadersFrame(primaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener() // { // @Override // public void onHeaders(Stream stream, HeadersFrame frame) @@ -451,14 +451,14 @@ public class PushCacheFilterTest extends AbstractTest // } // }); // -// final Session session = newClient(new Session.Listener.Adapter()); +// final Session session = newClient(new Session.Listener() {}); // // // Request for the primary, secondary and tertiary resource to build the cache. // final String primaryURI = newURI(primaryResource); // HttpFields.Mutable primaryFields = HttpFields.build(); // MetaData.Request primaryRequest = newRequest("GET", primaryResource, primaryFields); // final CountDownLatch warmupLatch = new CountDownLatch(2); -// session.newStream(new HeadersFrame(primaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter() +// session.newStream(new HeadersFrame(primaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener() // { // @Override // public void onData(Stream stream, DataFrame frame, Callback callback) @@ -471,7 +471,7 @@ public class PushCacheFilterTest extends AbstractTest // HttpFields.Mutable secondaryFields1 = HttpFields.build() // .put(HttpHeader.REFERER, primaryURI); // MetaData.Request secondaryRequest1 = newRequest("GET", secondaryResource1, secondaryFields1); -// session.newStream(new HeadersFrame(secondaryRequest1, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter() +// session.newStream(new HeadersFrame(secondaryRequest1, null, true), new Promise.Adapter<>(), new Stream.Listener() // { // @Override // public void onData(Stream stream, DataFrame frame, Callback callback) @@ -500,7 +500,7 @@ public class PushCacheFilterTest extends AbstractTest // HttpFields.Mutable secondaryFields2 = HttpFields.build() // .put(HttpHeader.REFERER, primaryURI); // MetaData.Request secondaryRequest2 = newRequest("GET", secondaryResource2, secondaryFields2); -// session.newStream(new HeadersFrame(secondaryRequest2, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter() +// session.newStream(new HeadersFrame(secondaryRequest2, null, true), new Promise.Adapter<>(), new Stream.Listener() // { // @Override // public void onData(Stream stream, DataFrame frame, Callback callback) @@ -522,7 +522,7 @@ public class PushCacheFilterTest extends AbstractTest // final CountDownLatch primaryResponseLatch = new CountDownLatch(1); // final CountDownLatch primaryPushesLatch = new CountDownLatch(3); // final CountDownLatch recursiveLatch = new CountDownLatch(1); -// session.newStream(new HeadersFrame(primaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter() +// session.newStream(new HeadersFrame(primaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener() // { // @Override // public void onData(Stream stream, DataFrame frame, Callback callback) @@ -574,7 +574,7 @@ public class PushCacheFilterTest extends AbstractTest // CountDownLatch secondaryResponseLatch = new CountDownLatch(1); // CountDownLatch secondaryPushLatch = new CountDownLatch(1); // MetaData.Request secondaryRequest = newRequest("GET", secondaryResource1, HttpFields.EMPTY); -// session.newStream(new HeadersFrame(secondaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter() +// session.newStream(new HeadersFrame(secondaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener() // { // @Override // public void onData(Stream stream, DataFrame frame, Callback callback) @@ -641,13 +641,13 @@ public class PushCacheFilterTest extends AbstractTest // }); // final String primaryURI = newURI(primaryResource); // -// final Session session = newClient(new Session.Listener.Adapter()); +// final Session session = newClient(new Session.Listener() {}); // // // Login with the wrong credentials, causing a redirect to self. // HttpFields.Mutable primaryFields = HttpFields.build(); // MetaData.Request primaryRequest = newRequest("GET", primaryResource + "?credentials=wrong", primaryFields); // final CountDownLatch warmupLatch = new CountDownLatch(1); -// session.newStream(new HeadersFrame(primaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter() +// session.newStream(new HeadersFrame(primaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener() // { // @Override // public void onHeaders(Stream stream, HeadersFrame frame) @@ -684,7 +684,7 @@ public class PushCacheFilterTest extends AbstractTest // primaryRequest = newRequest("GET", primaryResource + "?credentials=secret", primaryFields); // final CountDownLatch primaryResponseLatch = new CountDownLatch(1); // final CountDownLatch pushLatch = new CountDownLatch(1); -// session.newStream(new HeadersFrame(primaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter() +// session.newStream(new HeadersFrame(primaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener() // { // @Override // public void onData(Stream stream, DataFrame frame, Callback callback) @@ -733,14 +733,14 @@ public class PushCacheFilterTest extends AbstractTest // } // }); // -// final Session session = newClient(new Session.Listener.Adapter()); +// final Session session = newClient(new Session.Listener() {}); // // // Request for the primary and secondary resource to build the cache. // final String primaryURI = newURI(primaryResource); // HttpFields.Mutable primaryFields = HttpFields.build(); // MetaData.Request primaryRequest = newRequest("GET", primaryResource, primaryFields); // final CountDownLatch warmupLatch = new CountDownLatch(1); -// session.newStream(new HeadersFrame(primaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter() +// session.newStream(new HeadersFrame(primaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener() // { // @Override // public void onHeaders(Stream stream, HeadersFrame frame) @@ -751,7 +751,7 @@ public class PushCacheFilterTest extends AbstractTest // HttpFields.Mutable secondaryFields = HttpFields.build(); // secondaryFields.put(HttpHeader.REFERER, primaryURI); // MetaData.Request secondaryRequest = newRequest("GET", secondaryResource, secondaryFields); -// session.newStream(new HeadersFrame(secondaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter() +// session.newStream(new HeadersFrame(secondaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener() // { // @Override // public void onHeaders(Stream stream, HeadersFrame frame) @@ -771,7 +771,7 @@ public class PushCacheFilterTest extends AbstractTest // primaryRequest = newRequest("GET", primaryResource, primaryFields); // final CountDownLatch primaryResponseLatch = new CountDownLatch(1); // final CountDownLatch pushLatch = new CountDownLatch(1); -// session.newStream(new HeadersFrame(primaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter() +// session.newStream(new HeadersFrame(primaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener() // { // @Override // public Stream.Listener onPush(Stream stream, PushPromiseFrame frame) @@ -826,14 +826,14 @@ public class PushCacheFilterTest extends AbstractTest // } // }); // -// final Session session = newClient(new Session.Listener.Adapter()); +// final Session session = newClient(new Session.Listener() {}); // // // Request for the primary and secondary resource to build the cache. // final String referrerURI = newURI(primaryResource); // HttpFields.Mutable primaryFields = HttpFields.build(); // MetaData.Request primaryRequest = newRequest("GET", primaryResource, primaryFields); // final CountDownLatch warmupLatch = new CountDownLatch(1); -// session.newStream(new HeadersFrame(primaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter() +// session.newStream(new HeadersFrame(primaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener() // { // @Override // public void onData(Stream stream, DataFrame frame, Callback callback) @@ -845,7 +845,7 @@ public class PushCacheFilterTest extends AbstractTest // HttpFields.Mutable secondaryFields = HttpFields.build(); // secondaryFields.put(HttpHeader.REFERER, referrerURI); // MetaData.Request secondaryRequest = newRequest("GET", secondaryResource, secondaryFields); -// session.newStream(new HeadersFrame(secondaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter() +// session.newStream(new HeadersFrame(secondaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener() // { // @Override // public void onData(Stream stream, DataFrame frame, Callback callback) @@ -863,7 +863,7 @@ public class PushCacheFilterTest extends AbstractTest // primaryRequest = newRequest("POST", primaryResource, primaryFields); // final CountDownLatch primaryResponseLatch = new CountDownLatch(1); // final CountDownLatch pushLatch = new CountDownLatch(1); -// session.newStream(new HeadersFrame(primaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter() +// session.newStream(new HeadersFrame(primaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener() // { // @Override // public Stream.Listener onPush(Stream stream, PushPromiseFrame frame) @@ -912,7 +912,7 @@ public class PushCacheFilterTest extends AbstractTest // } // }); // -// final Session session = newClient(new Session.Listener.Adapter() +// final Session session = newClient(new Session.Listener() // { // @Override // public Map onPreface(Session session) @@ -928,7 +928,7 @@ public class PushCacheFilterTest extends AbstractTest // HttpFields.Mutable primaryFields = HttpFields.build(); // MetaData.Request primaryRequest = newRequest("GET", primaryResource, primaryFields); // final CountDownLatch warmupLatch = new CountDownLatch(1); -// session.newStream(new HeadersFrame(primaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter() +// session.newStream(new HeadersFrame(primaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener() // { // @Override // public void onData(Stream stream, DataFrame frame, Callback callback) @@ -940,7 +940,7 @@ public class PushCacheFilterTest extends AbstractTest // HttpFields.Mutable secondaryFields = HttpFields.build(); // secondaryFields.put(HttpHeader.REFERER, referrerURI); // MetaData.Request secondaryRequest = newRequest("GET", secondaryResource, secondaryFields); -// session.newStream(new HeadersFrame(secondaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter() +// session.newStream(new HeadersFrame(secondaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener() // { // @Override // public void onData(Stream stream, DataFrame frame, Callback callback) @@ -958,7 +958,7 @@ public class PushCacheFilterTest extends AbstractTest // primaryRequest = newRequest("GET", primaryResource, primaryFields); // final CountDownLatch primaryResponseLatch = new CountDownLatch(1); // final CountDownLatch pushLatch = new CountDownLatch(1); -// session.newStream(new HeadersFrame(primaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter() +// session.newStream(new HeadersFrame(primaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener() // { // @Override // public Stream.Listener onPush(Stream stream, PushPromiseFrame frame) diff --git a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/PushedResourcesTest.java b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/PushedResourcesTest.java index 9b714db1546..569b178e537 100644 --- a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/PushedResourcesTest.java +++ b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/PushedResourcesTest.java @@ -51,7 +51,7 @@ public class PushedResourcesTest extends AbstractTest { String pushPath = "/secondary"; CountDownLatch latch = new CountDownLatch(1); - start(new ServerSessionListener.Adapter() + start(new ServerSessionListener() { @Override public Stream.Listener onNewStream(Stream stream, HeadersFrame frame) @@ -67,12 +67,13 @@ public class PushedResourcesTest extends AbstractTest MetaData.Response response = new MetaData.Response(HttpVersion.HTTP_2, HttpStatus.OK_200, HttpFields.EMPTY); stream.headers(new HeadersFrame(stream.getId(), response, null, true), Callback.NOOP); } - }, new Stream.Listener.Adapter() + }, new Stream.Listener() { @Override - public void onReset(Stream stream, ResetFrame frame) + public void onReset(Stream stream, ResetFrame frame, Callback callback) { latch.countDown(); + callback.succeeded(); } }); return null; diff --git a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/RawHTTP2ProxyTest.java b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/RawHTTP2ProxyTest.java index a12ac03bc7c..1730d095e4a 100644 --- a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/RawHTTP2ProxyTest.java +++ b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/RawHTTP2ProxyTest.java @@ -112,14 +112,14 @@ public class RawHTTP2ProxyTest byte[] data1 = new byte[1024]; new Random().nextBytes(data1); ByteBuffer buffer1 = ByteBuffer.wrap(data1); - Server server1 = startServer("server1", new ServerSessionListener.Adapter() + Server server1 = startServer("server1", new ServerSessionListener() { @Override public Stream.Listener onNewStream(Stream stream, HeadersFrame frame) { if (LOGGER.isDebugEnabled()) LOGGER.debug("SERVER1 received {}", frame); - return new Stream.Listener.Adapter() + return new Stream.Listener() { @Override public void onHeaders(Stream stream, HeadersFrame frame) @@ -149,21 +149,23 @@ public class RawHTTP2ProxyTest } }); ServerConnector connector1 = (ServerConnector)server1.getAttribute("connector"); - Server server2 = startServer("server2", new ServerSessionListener.Adapter() + Server server2 = startServer("server2", new ServerSessionListener() { @Override public Stream.Listener onNewStream(Stream stream, HeadersFrame frame) { if (LOGGER.isDebugEnabled()) LOGGER.debug("SERVER2 received {}", frame); - return new Stream.Listener.Adapter() + stream.demand(); + return new Stream.Listener() { @Override - public void onData(Stream stream, DataFrame frame, Callback callback) + public void onDataAvailable(Stream stream) { + Stream.Data data = stream.readData(); if (LOGGER.isDebugEnabled()) - LOGGER.debug("SERVER2 received {}", frame); - callback.succeeded(); + LOGGER.debug("SERVER2 received {}", data); + data.release(); MetaData.Response response = new MetaData.Response(HttpVersion.HTTP_2, HttpStatus.OK_200, HttpFields.EMPTY); Callback.Completable completable1 = new Callback.Completable(); HeadersFrame reply = new HeadersFrame(stream.getId(), response, null, false); @@ -173,10 +175,10 @@ public class RawHTTP2ProxyTest completable1.thenCompose(ignored -> { Callback.Completable completable2 = new Callback.Completable(); - DataFrame data = new DataFrame(stream.getId(), buffer1.slice(), false); + DataFrame dataFrame = new DataFrame(stream.getId(), buffer1.slice(), false); if (LOGGER.isDebugEnabled()) - LOGGER.debug("SERVER2 sending {}", data); - stream.data(data, completable2); + LOGGER.debug("SERVER2 sending {}", dataFrame); + stream.data(dataFrame, completable2); return completable2; }).thenRun(() -> { @@ -198,7 +200,7 @@ public class RawHTTP2ProxyTest HTTP2Client client = startClient("client"); FuturePromise clientPromise = new FuturePromise<>(); - client.connect(proxyAddress, new Session.Listener.Adapter(), clientPromise); + client.connect(proxyAddress, new Session.Listener() {}, clientPromise); Session clientSession = clientPromise.get(5, TimeUnit.SECONDS); // Send a request with trailers for server1. @@ -207,23 +209,27 @@ public class RawHTTP2ProxyTest MetaData.Request request1 = new MetaData.Request("GET", HttpURI.from("http://localhost/server1"), HttpVersion.HTTP_2, fields1); FuturePromise streamPromise1 = new FuturePromise<>(); CountDownLatch latch1 = new CountDownLatch(1); - clientSession.newStream(new HeadersFrame(request1, null, false), streamPromise1, new Stream.Listener.Adapter() + clientSession.newStream(new HeadersFrame(request1, null, false), streamPromise1, new Stream.Listener() { @Override public void onHeaders(Stream stream, HeadersFrame frame) { if (LOGGER.isDebugEnabled()) LOGGER.debug("CLIENT received {}", frame); + stream.demand(); } @Override - public void onData(Stream stream, DataFrame frame, Callback callback) + public void onDataAvailable(Stream stream) { + Stream.Data data = stream.readData(); + DataFrame frame = data.frame(); if (LOGGER.isDebugEnabled()) LOGGER.debug("CLIENT received {}", frame); assertEquals(buffer1.slice(), frame.getData()); - callback.succeeded(); + data.release(); latch1.countDown(); + stream.demand(); } }); Stream stream1 = streamPromise1.get(5, TimeUnit.SECONDS); @@ -235,7 +241,7 @@ public class RawHTTP2ProxyTest MetaData.Request request2 = new MetaData.Request("GET", HttpURI.from("http://localhost/server1"), HttpVersion.HTTP_2, fields2); FuturePromise streamPromise2 = new FuturePromise<>(); CountDownLatch latch2 = new CountDownLatch(1); - clientSession.newStream(new HeadersFrame(request2, null, false), streamPromise2, new Stream.Listener.Adapter() + clientSession.newStream(new HeadersFrame(request2, null, false), streamPromise2, new Stream.Listener() { @Override public void onHeaders(Stream stream, HeadersFrame frame) @@ -244,14 +250,17 @@ public class RawHTTP2ProxyTest LOGGER.debug("CLIENT received {}", frame); if (frame.isEndStream()) latch2.countDown(); + stream.demand(); } @Override - public void onData(Stream stream, DataFrame frame, Callback callback) + public void onDataAvailable(Stream stream) { + Stream.Data data = stream.readData(); if (LOGGER.isDebugEnabled()) - LOGGER.debug("CLIENT received {}", frame); - callback.succeeded(); + LOGGER.debug("CLIENT received {}", data.frame()); + data.release(); + stream.demand(); } }); Stream stream2 = streamPromise2.get(5, TimeUnit.SECONDS); @@ -261,7 +270,7 @@ public class RawHTTP2ProxyTest assertTrue(latch2.await(5, TimeUnit.SECONDS)); } - private static class ClientToProxySessionListener extends ServerSessionListener.Adapter + private static class ClientToProxySessionListener implements ServerSessionListener { private final Map forwarders = new ConcurrentHashMap<>(); private final HTTP2Client client; @@ -282,15 +291,17 @@ public class RawHTTP2ProxyTest int port = Integer.parseInt(fields.get("X-Target")); ClientToProxyToServer clientToProxyToServer = forwarders.computeIfAbsent(port, p -> new ClientToProxyToServer("localhost", p, client)); clientToProxyToServer.offer(stream, frame, Callback.NOOP); + stream.demand(); return clientToProxyToServer; } @Override - public void onClose(Session session, GoAwayFrame frame) + public void onClose(Session session, GoAwayFrame frame, Callback callback) { if (LOGGER.isDebugEnabled()) LOGGER.debug("Received {} on {}", frame, session); // TODO + callback.succeeded(); } @Override @@ -303,11 +314,12 @@ public class RawHTTP2ProxyTest } @Override - public void onFailure(Session session, Throwable failure) + public void onFailure(Session session, Throwable failure, Callback callback) { if (LOGGER.isDebugEnabled()) LOGGER.debug("Failure on " + session, failure); // TODO + callback.succeeded(); } } @@ -478,6 +490,7 @@ public class RawHTTP2ProxyTest if (LOGGER.isDebugEnabled()) LOGGER.debug("CPS received {} on {}", frame, stream); offer(stream, frame, NOOP); + stream.demand(); } @Override @@ -488,19 +501,22 @@ public class RawHTTP2ProxyTest } @Override - public void onData(Stream stream, DataFrame frame, Callback callback) + public void onDataAvailable(Stream stream) { + Stream.Data data = stream.readData(); if (LOGGER.isDebugEnabled()) - LOGGER.debug("CPS received {} on {}", frame, stream); - offer(stream, frame, callback); + LOGGER.debug("CPS read {} on {}", data, stream); + offer(stream, data.frame(), Callback.from(data::release)); + stream.demand(); } @Override - public void onReset(Stream stream, ResetFrame frame) + public void onReset(Stream stream, ResetFrame frame, Callback callback) { if (LOGGER.isDebugEnabled()) LOGGER.debug("CPS received {} on {}", frame, stream); // TODO: drain the queue for that stream, and notify server. + callback.succeeded(); } @Override @@ -513,14 +529,15 @@ public class RawHTTP2ProxyTest } } - private static class ServerToProxySessionListener extends Session.Listener.Adapter + private static class ServerToProxySessionListener implements Session.Listener { @Override - public void onClose(Session session, GoAwayFrame frame) + public void onClose(Session session, GoAwayFrame frame, Callback callback) { if (LOGGER.isDebugEnabled()) LOGGER.debug("Received {} on {}", frame, session); // TODO + callback.succeeded(); } @Override @@ -533,11 +550,12 @@ public class RawHTTP2ProxyTest } @Override - public void onFailure(Session session, Throwable failure) + public void onFailure(Session session, Throwable failure, Callback callback) { if (LOGGER.isDebugEnabled()) LOGGER.debug("Failure on " + session, failure); // TODO + callback.succeeded(); } } @@ -632,6 +650,7 @@ public class RawHTTP2ProxyTest if (LOGGER.isDebugEnabled()) LOGGER.debug("SPC received {} on {}", frame, stream); offer(stream, frame, NOOP); + stream.demand(); } @Override @@ -644,19 +663,22 @@ public class RawHTTP2ProxyTest } @Override - public void onData(Stream stream, DataFrame frame, Callback callback) + public void onDataAvailable(Stream stream) { + Stream.Data data = stream.readData(); if (LOGGER.isDebugEnabled()) - LOGGER.debug("SPC received {} on {}", frame, stream); - offer(stream, frame, callback); + LOGGER.debug("SPC read {} on {}", data, stream); + offer(stream, data.frame(), Callback.from(data::release)); + stream.demand(); } @Override - public void onReset(Stream stream, ResetFrame frame) + public void onReset(Stream stream, ResetFrame frame, Callback callback) { if (LOGGER.isDebugEnabled()) LOGGER.debug("SPC received {} on {}", frame, stream); // TODO: drain queue, reset client stream. + callback.succeeded(); } @Override @@ -665,7 +687,7 @@ public class RawHTTP2ProxyTest if (LOGGER.isDebugEnabled()) LOGGER.debug("SPC idle timeout for {}", stream); // TODO: - return false; + return true; } private void link(Stream proxyToServerStream, Stream clientToProxyStream) diff --git a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/RequestTrailersTest.java b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/RequestTrailersTest.java index f54f2f05387..949ad554207 100644 --- a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/RequestTrailersTest.java +++ b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/RequestTrailersTest.java @@ -28,7 +28,6 @@ import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.http.MetaData; import org.eclipse.jetty.http2.api.Stream; import org.eclipse.jetty.http2.api.server.ServerSessionListener; -import org.eclipse.jetty.http2.frames.DataFrame; import org.eclipse.jetty.http2.frames.HeadersFrame; import org.eclipse.jetty.util.Callback; import org.junit.jupiter.api.Test; @@ -54,7 +53,7 @@ public class RequestTrailersTest extends AbstractTest private void testEmptyTrailers(String content) throws Exception { CountDownLatch trailersLatch = new CountDownLatch(1); - start(new ServerSessionListener.Adapter() + start(new ServerSessionListener() { @Override public Stream.Listener onNewStream(Stream stream, HeadersFrame frame) @@ -62,7 +61,7 @@ public class RequestTrailersTest extends AbstractTest MetaData.Response response = new MetaData.Response(HttpVersion.HTTP_2, HttpStatus.OK_200, HttpFields.EMPTY); HeadersFrame responseFrame = new HeadersFrame(stream.getId(), response, null, true); stream.headers(responseFrame, Callback.NOOP); - return new Stream.Listener.Adapter() + return new Stream.Listener() { @Override public void onHeaders(Stream stream, HeadersFrame frame) @@ -89,20 +88,23 @@ public class RequestTrailersTest extends AbstractTest @Test public void testEmptyTrailersWithAsyncContent() throws Exception { - start(new ServerSessionListener.Adapter() + start(new ServerSessionListener() { @Override public Stream.Listener onNewStream(Stream stream, HeadersFrame frame) { - return new Stream.Listener.Adapter() + stream.demand(); + return new Stream.Listener() { @Override - public void onData(Stream stream, DataFrame dataFrame, Callback callback) + public void onDataAvailable(Stream stream) { - callback.succeeded(); + Stream.Data data = stream.readData(); + data.release(); + stream.demand(); // We should not receive an empty HEADERS frame for the // trailers, but instead a DATA frame with endStream=true. - if (dataFrame.isEndStream()) + if (data.frame().isEndStream()) { MetaData.Response response = new MetaData.Response(HttpVersion.HTTP_2, HttpStatus.OK_200, HttpFields.EMPTY); HeadersFrame responseFrame = new HeadersFrame(stream.getId(), response, null, true); @@ -138,20 +140,22 @@ public class RequestTrailersTest extends AbstractTest @Test public void testEmptyTrailersWithEmptyAsyncContent() throws Exception { - start(new ServerSessionListener.Adapter() + start(new ServerSessionListener() { @Override public Stream.Listener onNewStream(Stream stream, HeadersFrame frame) { - return new Stream.Listener.Adapter() + stream.demand(); + return new Stream.Listener() { @Override - public void onData(Stream stream, DataFrame dataFrame, Callback callback) + public void onDataAvailable(Stream stream) { - callback.succeeded(); + Stream.Data data = stream.readData(); + data.release(); // We should not receive an empty HEADERS frame for the // trailers, but instead a DATA frame with endStream=true. - if (dataFrame.isEndStream()) + if (data.frame().isEndStream()) { MetaData.Response response = new MetaData.Response(HttpVersion.HTTP_2, HttpStatus.OK_200, HttpFields.EMPTY); HeadersFrame responseFrame = new HeadersFrame(stream.getId(), response, null, true); diff --git a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/ResponseTrailerTest.java b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/ResponseTrailerTest.java index 31332e9373a..85e3440eafc 100644 --- a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/ResponseTrailerTest.java +++ b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/ResponseTrailerTest.java @@ -29,7 +29,6 @@ import org.eclipse.jetty.http.MetaData; import org.eclipse.jetty.http2.api.Session; import org.eclipse.jetty.http2.api.Stream; import org.eclipse.jetty.http2.client.HTTP2Client; -import org.eclipse.jetty.http2.frames.DataFrame; import org.eclipse.jetty.http2.frames.HeadersFrame; import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.Request; @@ -81,7 +80,7 @@ public class ResponseTrailerTest extends AbstractTest int port = connector.getLocalPort(); InetSocketAddress address = new InetSocketAddress(host, port); FuturePromise sessionPromise = new FuturePromise<>(); - http2Client.connect(address, new Session.Listener.Adapter(), sessionPromise); + http2Client.connect(address, new Session.Listener() {}, sessionPromise); Session session = sessionPromise.get(5, TimeUnit.SECONDS); HttpURI uri = HttpURI.from("http://" + host + ":" + port + "/"); @@ -89,7 +88,7 @@ public class ResponseTrailerTest extends AbstractTest HeadersFrame frame = new HeadersFrame(request, null, true); BlockingQueue headers = new LinkedBlockingQueue<>(); CountDownLatch latch = new CountDownLatch(1); - session.newStream(frame, new Promise.Adapter<>(), new Stream.Listener.Adapter() + session.newStream(frame, new Promise.Adapter<>(), new Stream.Listener() { @Override public void onHeaders(Stream stream, HeadersFrame frame) @@ -97,13 +96,15 @@ public class ResponseTrailerTest extends AbstractTest headers.offer(frame); if (frame.isEndStream()) latch.countDown(); + stream.demand(); } @Override - public void onData(Stream stream, DataFrame frame, Callback callback) + public void onDataAvailable(Stream stream) { - super.onData(stream, frame, callback); - if (frame.isEndStream()) + Stream.Data data = stream.readData(); + data.release(); + if (data.frame().isEndStream()) latch.countDown(); } }); diff --git a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/SessionFailureTest.java b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/SessionFailureTest.java index bb6fa5bfae5..62309d5c3e5 100644 --- a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/SessionFailureTest.java +++ b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/SessionFailureTest.java @@ -39,12 +39,13 @@ public class SessionFailureTest extends AbstractTest public void testWrongPreface() throws Exception { final CountDownLatch latch = new CountDownLatch(1); - start(new ServerSessionListener.Adapter() + start(new ServerSessionListener() { @Override - public void onFailure(Session session, Throwable failure) + public void onFailure(Session session, Throwable failure, Callback callback) { latch.countDown(); + callback.succeeded(); } }); @@ -74,7 +75,7 @@ public class SessionFailureTest extends AbstractTest { final CountDownLatch writeLatch = new CountDownLatch(1); final CountDownLatch serverFailureLatch = new CountDownLatch(1); - start(new ServerSessionListener.Adapter() + start(new ServerSessionListener() { @Override public Stream.Listener onNewStream(Stream stream, HeadersFrame frame) @@ -94,19 +95,21 @@ public class SessionFailureTest extends AbstractTest } @Override - public void onFailure(Session session, Throwable failure) + public void onFailure(Session session, Throwable failure, Callback callback) { serverFailureLatch.countDown(); + callback.succeeded(); } }); final CountDownLatch clientFailureLatch = new CountDownLatch(1); - Session session = newClientSession(new Session.Listener.Adapter() + Session session = newClientSession(new Session.Listener() { @Override - public void onFailure(Session session, Throwable failure) + public void onFailure(Session session, Throwable failure, Callback callback) { clientFailureLatch.countDown(); + callback.succeeded(); } }); HeadersFrame frame = new HeadersFrame(newRequest("GET", HttpFields.EMPTY), null, true); diff --git a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/SmallThreadPoolLoadTest.java b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/SmallThreadPoolLoadTest.java index 24074808ead..11d1454131a 100644 --- a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/SmallThreadPoolLoadTest.java +++ b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/SmallThreadPoolLoadTest.java @@ -70,7 +70,7 @@ public class SmallThreadPoolLoadTest extends AbstractTest start(new LoadHandler()); // Only one connection to the server. - Session session = newClientSession(new Session.Listener.Adapter()); + Session session = newClientSession(new Session.Listener() {}); int runs = 10; int iterations = 512; @@ -143,30 +143,34 @@ public class SmallThreadPoolLoadTest extends AbstractTest HeadersFrame requestFrame = new HeadersFrame(request, null, download); FuturePromise promise = new FuturePromise<>(); - CountDownLatch requestLatch = new CountDownLatch(1); + CountDownLatch responseLatch = new CountDownLatch(1); AtomicBoolean reset = new AtomicBoolean(); - session.newStream(requestFrame, promise, new Stream.Listener.Adapter() + session.newStream(requestFrame, promise, new Stream.Listener() { @Override public void onHeaders(Stream stream, HeadersFrame frame) { if (frame.isEndStream()) - requestLatch.countDown(); + responseLatch.countDown(); + stream.demand(); } @Override - public void onData(Stream stream, DataFrame frame, Callback callback) + public void onDataAvailable(Stream stream) { - callback.succeeded(); - if (frame.isEndStream()) - requestLatch.countDown(); + Stream.Data data = stream.readData(); + data.release(); + stream.demand(); + if (data.frame().isEndStream()) + responseLatch.countDown(); } @Override - public void onReset(Stream stream, ResetFrame frame) + public void onReset(Stream stream, ResetFrame frame, Callback callback) { reset.set(true); - requestLatch.countDown(); + responseLatch.countDown(); + callback.succeeded(); } }); if (!download) @@ -175,7 +179,7 @@ public class SmallThreadPoolLoadTest extends AbstractTest stream.data(new DataFrame(stream.getId(), ByteBuffer.allocate(contentLength), true), Callback.NOOP); } - boolean success = requestLatch.await(5, TimeUnit.SECONDS); + boolean success = responseLatch.await(5, TimeUnit.SECONDS); if (success) latch.countDown(); else diff --git a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/StreamCloseTest.java b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/StreamCloseTest.java index 78b0bc10bca..4b995280a88 100644 --- a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/StreamCloseTest.java +++ b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/StreamCloseTest.java @@ -16,6 +16,7 @@ package org.eclipse.jetty.http2.tests; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; @@ -47,19 +48,19 @@ public class StreamCloseTest extends AbstractTest @Test public void testRequestClosedRemotelyClosesStream() throws Exception { - final CountDownLatch latch = new CountDownLatch(1); - start(new ServerSessionListener.Adapter() + CountDownLatch latch = new CountDownLatch(1); + start(new ServerSessionListener() { @Override public Stream.Listener onNewStream(Stream stream, HeadersFrame frame) { - assertTrue(((HTTP2Stream)stream).isRemotelyClosed()); + assertTrue(stream.isRemotelyClosed()); latch.countDown(); return null; } }); - Session session = newClientSession(new Session.Listener.Adapter()); + Session session = newClientSession(new Session.Listener() {}); HeadersFrame frame = new HeadersFrame(newRequest("GET", HttpFields.EMPTY), null, true); FuturePromise promise = new FuturePromise<>(); session.newStream(frame, promise, null); @@ -71,11 +72,11 @@ public class StreamCloseTest extends AbstractTest @Test public void testRequestClosedResponseClosedClosesStream() throws Exception { - final CountDownLatch latch = new CountDownLatch(2); - start(new ServerSessionListener.Adapter() + CountDownLatch latch = new CountDownLatch(2); + start(new ServerSessionListener() { @Override - public Stream.Listener onNewStream(final Stream stream, HeadersFrame frame) + public Stream.Listener onNewStream(Stream stream, HeadersFrame frame) { MetaData.Response metaData = new MetaData.Response(HttpVersion.HTTP_2, 200, HttpFields.EMPTY); HeadersFrame response = new HeadersFrame(stream.getId(), metaData, null, true); @@ -93,10 +94,10 @@ public class StreamCloseTest extends AbstractTest } }); - Session session = newClientSession(new Session.Listener.Adapter()); + Session session = newClientSession(new Session.Listener() {}); HeadersFrame frame = new HeadersFrame(newRequest("GET", HttpFields.EMPTY), null, true); FuturePromise promise = new FuturePromise<>(); - session.newStream(frame, promise, new Stream.Listener.Adapter() + session.newStream(frame, promise, new Stream.Listener() { @Override public void onHeaders(Stream stream, HeadersFrame frame) @@ -113,31 +114,33 @@ public class StreamCloseTest extends AbstractTest @Test public void testRequestDataClosedResponseDataClosedClosesStream() throws Exception { - final CountDownLatch serverDataLatch = new CountDownLatch(1); - start(new ServerSessionListener.Adapter() + CountDownLatch serverDataLatch = new CountDownLatch(1); + start(new ServerSessionListener() { @Override public Stream.Listener onNewStream(Stream stream, HeadersFrame frame) { MetaData.Response metaData = new MetaData.Response(HttpVersion.HTTP_2, 200, HttpFields.EMPTY); HeadersFrame response = new HeadersFrame(stream.getId(), metaData, null, false); - Callback.Completable completable = new Callback.Completable(); - stream.headers(response, completable); - return new Stream.Listener.Adapter() + CompletableFuture completable = stream.headers(response); + stream.demand(); + return new Stream.Listener() { @Override - public void onData(final Stream stream, DataFrame frame, final Callback callback) + public void onDataAvailable(Stream stream) { - assertTrue(((HTTP2Stream)stream).isRemotelyClosed()); + Stream.Data data = stream.readData(); - completable.thenRun(() -> stream.data(frame, new Callback() + assertTrue(stream.isRemotelyClosed()); + + completable.thenRun(() -> stream.data(data.frame(), new Callback() { @Override public void succeeded() { assertTrue(stream.isClosed()); assertEquals(0, stream.getSession().getStreams().size()); - callback.succeeded(); + data.release(); serverDataLatch.countDown(); } })); @@ -146,25 +149,26 @@ public class StreamCloseTest extends AbstractTest } }); - final CountDownLatch completeLatch = new CountDownLatch(1); - Session session = newClientSession(new Session.Listener.Adapter()); + CountDownLatch completeLatch = new CountDownLatch(1); + Session session = newClientSession(new Session.Listener() {}); HeadersFrame frame = new HeadersFrame(newRequest("GET", HttpFields.EMPTY), null, false); FuturePromise promise = new FuturePromise<>(); - session.newStream(frame, promise, new Stream.Listener.Adapter() + session.newStream(frame, promise, new Stream.Listener() { @Override - public void onData(Stream stream, DataFrame frame, Callback callback) + public void onDataAvailable(Stream stream) { + Stream.Data data = stream.readData(); // The sent data callback may not be notified yet here. - callback.succeeded(); + data.release(); completeLatch.countDown(); } }); - final Stream stream = promise.get(5, TimeUnit.SECONDS); + Stream stream = promise.get(5, TimeUnit.SECONDS); assertFalse(stream.isClosed()); assertFalse(((HTTP2Stream)stream).isLocallyClosed()); - final CountDownLatch clientDataLatch = new CountDownLatch(1); + CountDownLatch clientDataLatch = new CountDownLatch(1); stream.data(new DataFrame(stream.getId(), ByteBuffer.wrap(new byte[512]), true), new Callback() { @Override @@ -185,8 +189,8 @@ public class StreamCloseTest extends AbstractTest @Test public void testPushedStreamIsClosed() throws Exception { - final CountDownLatch serverLatch = new CountDownLatch(1); - start(new ServerSessionListener.Adapter() + CountDownLatch serverLatch = new CountDownLatch(1); + start(new ServerSessionListener() { @Override public Stream.Listener onNewStream(Stream stream, HeadersFrame frame) @@ -195,10 +199,10 @@ public class StreamCloseTest extends AbstractTest stream.push(pushFrame, new Promise.Adapter<>() { @Override - public void succeeded(final Stream pushedStream) + public void succeeded(Stream pushedStream) { // When created, pushed stream must be implicitly remotely closed. - assertTrue(((HTTP2Stream)pushedStream).isRemotelyClosed()); + assertTrue(pushedStream.isRemotelyClosed()); // Send some data with endStream = true. pushedStream.data(new DataFrame(pushedStream.getId(), ByteBuffer.allocate(16), true), new Callback() { @@ -210,29 +214,31 @@ public class StreamCloseTest extends AbstractTest } }); } - }, new Stream.Listener.Adapter()); + }, null); HeadersFrame response = new HeadersFrame(stream.getId(), new MetaData.Response(HttpVersion.HTTP_2, 200, HttpFields.EMPTY), null, true); stream.headers(response, Callback.NOOP); return null; } }); - Session session = newClientSession(new Session.Listener.Adapter()); + Session session = newClientSession(new Session.Listener() {}); HeadersFrame frame = new HeadersFrame(newRequest("GET", HttpFields.EMPTY), null, true); - final CountDownLatch clientLatch = new CountDownLatch(1); - session.newStream(frame, new Promise.Adapter<>(), new Stream.Listener.Adapter() + CountDownLatch clientLatch = new CountDownLatch(1); + session.newStream(frame, new Promise.Adapter<>(), new Stream.Listener() { @Override public Stream.Listener onPush(Stream pushedStream, PushPromiseFrame frame) { assertTrue(((HTTP2Stream)pushedStream).isLocallyClosed()); - return new Adapter() + pushedStream.demand(); + return new Stream.Listener() { @Override - public void onData(Stream pushedStream, DataFrame frame, Callback callback) + public void onDataAvailable(Stream pushedStream) { + Stream.Data data = pushedStream.readData(); assertTrue(pushedStream.isClosed()); - callback.succeeded(); + data.release(); clientLatch.countDown(); } }; @@ -246,36 +252,37 @@ public class StreamCloseTest extends AbstractTest @Test public void testPushedStreamResetIsClosed() throws Exception { - final CountDownLatch serverLatch = new CountDownLatch(1); - start(new ServerSessionListener.Adapter() + CountDownLatch serverLatch = new CountDownLatch(1); + start(new ServerSessionListener() { @Override - public Stream.Listener onNewStream(final Stream stream, HeadersFrame frame) + public Stream.Listener onNewStream(Stream stream, HeadersFrame frame) { PushPromiseFrame pushFrame = new PushPromiseFrame(stream.getId(), 0, newRequest("GET", HttpFields.EMPTY)); - stream.push(pushFrame, new Promise.Adapter<>(), new Stream.Listener.Adapter() + stream.push(pushFrame, new Promise.Adapter<>(), new Stream.Listener() { @Override - public void onReset(Stream pushedStream, ResetFrame frame) + public void onReset(Stream pushedStream, ResetFrame frame, Callback callback) { assertTrue(pushedStream.isReset()); assertTrue(pushedStream.isClosed()); HeadersFrame response = new HeadersFrame(stream.getId(), new MetaData.Response(HttpVersion.HTTP_2, 200, HttpFields.EMPTY), null, true); stream.headers(response, Callback.NOOP); serverLatch.countDown(); + callback.succeeded(); } }); return null; } }); - Session session = newClientSession(new Session.Listener.Adapter()); + Session session = newClientSession(new Session.Listener() {}); HeadersFrame frame = new HeadersFrame(newRequest("GET", HttpFields.EMPTY), null, true); - final CountDownLatch clientLatch = new CountDownLatch(2); - session.newStream(frame, new Promise.Adapter<>(), new Stream.Listener.Adapter() + CountDownLatch clientLatch = new CountDownLatch(2); + session.newStream(frame, new Promise.Adapter<>(), new Stream.Listener() { @Override - public Stream.Listener onPush(final Stream pushedStream, PushPromiseFrame frame) + public Stream.Listener onPush(Stream pushedStream, PushPromiseFrame frame) { pushedStream.reset(new ResetFrame(pushedStream.getId(), ErrorCode.REFUSED_STREAM_ERROR.code), new Callback() { @@ -305,9 +312,9 @@ public class StreamCloseTest extends AbstractTest public void testFailedSessionClosesIdleStream() throws Exception { AtomicReference sessionRef = new AtomicReference<>(); - final CountDownLatch latch = new CountDownLatch(1); - final List streams = new ArrayList<>(); - start(new ServerSessionListener.Adapter() + CountDownLatch latch = new CountDownLatch(1); + List streams = new ArrayList<>(); + start(new ServerSessionListener() { @Override public Stream.Listener onNewStream(Stream stream, HeadersFrame frame) @@ -326,22 +333,23 @@ public class StreamCloseTest extends AbstractTest } @Override - public void onFailure(Session session, Throwable failure) + public void onFailure(Session session, Throwable failure, Callback callback) { sessionRef.set(session); latch.countDown(); + callback.succeeded(); } }); - Session session = newClientSession(new Session.Listener.Adapter()); + Session session = newClientSession(new Session.Listener() {}); // First stream will be idle on server. HeadersFrame request1 = new HeadersFrame(newRequest("HEAD", HttpFields.EMPTY), null, true); - session.newStream(request1, new Promise.Adapter<>(), new Stream.Listener.Adapter()); + session.newStream(request1, new Promise.Adapter<>(), null); // Second stream will fail on server. HeadersFrame request2 = new HeadersFrame(newRequest("GET", HttpFields.EMPTY), null, true); - session.newStream(request2, new Promise.Adapter<>(), new Stream.Listener.Adapter()); + session.newStream(request2, new Promise.Adapter<>(), null); assertTrue(latch.await(5, TimeUnit.SECONDS)); Session serverSession = sessionRef.get(); diff --git a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/StreamCountTest.java b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/StreamCountTest.java index 9f1ef4820cc..82e5220abf2 100644 --- a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/StreamCountTest.java +++ b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/StreamCountTest.java @@ -48,7 +48,7 @@ public class StreamCountTest extends AbstractTest @Test public void testServerAllowsOneStreamEnforcedByClient() throws Exception { - start(new ServerSessionListener.Adapter() + start(new ServerSessionListener() { @Override public Map onPreface(Session session) @@ -61,19 +61,18 @@ public class StreamCountTest extends AbstractTest @Override public Stream.Listener onNewStream(Stream stream, HeadersFrame frame) { - return new Stream.Listener.Adapter() + stream.demand(); + return new Stream.Listener() { @Override - public void onData(Stream stream, DataFrame frame, Callback callback) + public void onDataAvailable(Stream stream) { - if (frame.isEndStream()) + Stream.Data data = stream.readData(); + data.release(); + if (data.frame().isEndStream()) { MetaData.Response metaData = new MetaData.Response(HttpVersion.HTTP_2, 200, HttpFields.EMPTY); - stream.headers(new HeadersFrame(stream.getId(), metaData, null, true), callback); - } - else - { - callback.succeeded(); + stream.headers(new HeadersFrame(stream.getId(), metaData, null, true), Callback.NOOP); } } }; @@ -81,7 +80,7 @@ public class StreamCountTest extends AbstractTest }); CountDownLatch settingsLatch = new CountDownLatch(1); - Session session = newClientSession(new Session.Listener.Adapter() + Session session = newClientSession(new Session.Listener() { @Override public void onSettings(Session session, SettingsFrame frame) @@ -96,7 +95,7 @@ public class StreamCountTest extends AbstractTest HeadersFrame frame1 = new HeadersFrame(metaData, null, false); FuturePromise streamPromise1 = new FuturePromise<>(); CountDownLatch responseLatch = new CountDownLatch(1); - session.newStream(frame1, streamPromise1, new Stream.Listener.Adapter() + session.newStream(frame1, streamPromise1, new Stream.Listener() { @Override public void onHeaders(Stream stream, HeadersFrame frame) @@ -109,7 +108,7 @@ public class StreamCountTest extends AbstractTest HeadersFrame frame2 = new HeadersFrame(metaData, null, false); FuturePromise streamPromise2 = new FuturePromise<>(); - session.newStream(frame2, streamPromise2, new Stream.Listener.Adapter()); + session.newStream(frame2, streamPromise2, null); assertThrows(ExecutionException.class, () -> streamPromise2.get(5, TimeUnit.SECONDS)); @@ -121,27 +120,25 @@ public class StreamCountTest extends AbstractTest @Test public void testServerAllowsOneStreamEnforcedByServer() throws Exception { - start(new ServerSessionListener.Adapter() + start(new ServerSessionListener() { @Override public Stream.Listener onNewStream(Stream stream, HeadersFrame frame) { HTTP2Session session = (HTTP2Session)stream.getSession(); session.setMaxRemoteStreams(1); - - return new Stream.Listener.Adapter() + stream.demand(); + return new Stream.Listener() { @Override - public void onData(Stream stream, DataFrame frame, Callback callback) + public void onDataAvailable(Stream stream) { - if (frame.isEndStream()) + Stream.Data data = stream.readData(); + data.release(); + if (data.frame().isEndStream()) { MetaData.Response metaData = new MetaData.Response(HttpVersion.HTTP_2, 200, HttpFields.EMPTY); - stream.headers(new HeadersFrame(stream.getId(), metaData, null, true), callback); - } - else - { - callback.succeeded(); + stream.headers(new HeadersFrame(stream.getId(), metaData, null, true), Callback.NOOP); } } }; @@ -149,7 +146,7 @@ public class StreamCountTest extends AbstractTest }); CountDownLatch sessionResetLatch = new CountDownLatch(2); - Session session = newClientSession(new Session.Listener.Adapter() + Session session = newClientSession(new Session.Listener() { @Override public void onReset(Session session, ResetFrame frame) @@ -162,7 +159,7 @@ public class StreamCountTest extends AbstractTest HeadersFrame frame1 = new HeadersFrame(metaData, null, false); FuturePromise streamPromise1 = new FuturePromise<>(); CountDownLatch responseLatch = new CountDownLatch(1); - session.newStream(frame1, streamPromise1, new Stream.Listener.Adapter() + session.newStream(frame1, streamPromise1, new Stream.Listener() { @Override public void onHeaders(Stream stream, HeadersFrame frame) @@ -177,12 +174,13 @@ public class StreamCountTest extends AbstractTest HeadersFrame frame2 = new HeadersFrame(metaData, null, false); FuturePromise streamPromise2 = new FuturePromise<>(); AtomicReference resetLatch = new AtomicReference<>(new CountDownLatch(1)); - session.newStream(frame2, streamPromise2, new Stream.Listener.Adapter() + session.newStream(frame2, streamPromise2, new Stream.Listener() { @Override - public void onReset(Stream stream, ResetFrame frame) + public void onReset(Stream stream, ResetFrame frame, Callback callback) { resetLatch.get().countDown(); + callback.succeeded(); } }); diff --git a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/StreamResetTest.java b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/StreamResetTest.java index fb4717248b9..aae30aca322 100644 --- a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/StreamResetTest.java +++ b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/StreamResetTest.java @@ -45,8 +45,6 @@ import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.http.MetaData; import org.eclipse.jetty.http2.BufferingFlowControlStrategy; import org.eclipse.jetty.http2.FlowControlStrategy; -import org.eclipse.jetty.http2.ISession; -import org.eclipse.jetty.http2.IStream; import org.eclipse.jetty.http2.api.Session; import org.eclipse.jetty.http2.api.Stream; import org.eclipse.jetty.http2.api.server.ServerSessionListener; @@ -60,6 +58,7 @@ import org.eclipse.jetty.http2.frames.WindowUpdateFrame; import org.eclipse.jetty.http2.internal.ErrorCode; import org.eclipse.jetty.http2.internal.HTTP2Flusher; import org.eclipse.jetty.http2.internal.HTTP2Session; +import org.eclipse.jetty.http2.internal.HTTP2Stream; import org.eclipse.jetty.http2.internal.generator.Generator; import org.eclipse.jetty.http2.server.AbstractHTTP2ServerConnectionFactory; import org.eclipse.jetty.http2.server.HTTP2ServerConnectionFactory; @@ -96,13 +95,13 @@ public class StreamResetTest extends AbstractTest @Test public void testStreamSendingResetIsRemoved() throws Exception { - start(new ServerSessionListener.Adapter()); + start(new ServerSessionListener() {}); - Session client = newClientSession(new Session.Listener.Adapter()); + Session client = newClientSession(new Session.Listener() {}); MetaData.Request request = newRequest("GET", HttpFields.EMPTY); HeadersFrame requestFrame = new HeadersFrame(request, null, false); FuturePromise promise = new FuturePromise<>(); - client.newStream(requestFrame, promise, new Stream.Listener.Adapter()); + client.newStream(requestFrame, promise, null); Stream stream = promise.get(5, TimeUnit.SECONDS); ResetFrame resetFrame = new ResetFrame(stream.getId(), ErrorCode.CANCEL_STREAM_ERROR.code); FutureCallback resetCallback = new FutureCallback(); @@ -117,30 +116,31 @@ public class StreamResetTest extends AbstractTest { final AtomicReference streamRef = new AtomicReference<>(); final CountDownLatch resetLatch = new CountDownLatch(1); - start(new ServerSessionListener.Adapter() + start(new ServerSessionListener() { @Override public Stream.Listener onNewStream(Stream stream, HeadersFrame frame) { - return new Stream.Listener.Adapter() + return new Stream.Listener() { @Override - public void onReset(Stream stream, ResetFrame frame) + public void onReset(Stream stream, ResetFrame frame, Callback callback) { assertNotNull(stream); assertTrue(stream.isReset()); streamRef.set(stream); resetLatch.countDown(); + callback.succeeded(); } }; } }); - Session client = newClientSession(new Session.Listener.Adapter()); + Session client = newClientSession(new Session.Listener() {}); MetaData.Request request = newRequest("GET", HttpFields.EMPTY); HeadersFrame requestFrame = new HeadersFrame(request, null, false); FuturePromise promise = new FuturePromise<>(); - client.newStream(requestFrame, promise, new Stream.Listener.Adapter()); + client.newStream(requestFrame, promise, null); Stream stream = promise.get(5, TimeUnit.SECONDS); ResetFrame resetFrame = new ResetFrame(stream.getId(), ErrorCode.CANCEL_STREAM_ERROR.code); stream.reset(resetFrame, Callback.NOOP); @@ -160,7 +160,7 @@ public class StreamResetTest extends AbstractTest { final CountDownLatch serverResetLatch = new CountDownLatch(1); final CountDownLatch serverDataLatch = new CountDownLatch(1); - start(new ServerSessionListener.Adapter() + start(new ServerSessionListener() { @Override public Stream.Listener onNewStream(Stream stream, HeadersFrame requestFrame) @@ -169,12 +169,14 @@ public class StreamResetTest extends AbstractTest HeadersFrame responseFrame = new HeadersFrame(stream.getId(), response, null, false); Callback.Completable completable = new Callback.Completable(); stream.headers(responseFrame, completable); - return new Stream.Listener.Adapter() + stream.demand(); + return new Stream.Listener() { @Override - public void onData(Stream stream, DataFrame frame, Callback callback) + public void onDataAvailable(Stream stream) { - callback.succeeded(); + Stream.Data data = stream.readData(); + data.release(); completable.thenRun(() -> stream.data(new DataFrame(stream.getId(), ByteBuffer.allocate(16), true), new Callback() { @@ -187,10 +189,10 @@ public class StreamResetTest extends AbstractTest } @Override - public void onReset(Stream s, ResetFrame frame) + public void onReset(Stream s, ResetFrame frame, Callback callback) { // Simulate that there is pending data to send. - IStream stream = (IStream)s; + HTTP2Stream stream = (HTTP2Stream)s; List frames = List.of(new DataFrame(s.getId(), ByteBuffer.allocate(16), true)); stream.getSession().frames(stream, frames, new Callback() { @@ -200,29 +202,32 @@ public class StreamResetTest extends AbstractTest serverResetLatch.countDown(); } }); + callback.succeeded(); } }; } }); - Session client = newClientSession(new Session.Listener.Adapter()); + Session client = newClientSession(new Session.Listener() {}); MetaData.Request request1 = newRequest("GET", HttpFields.EMPTY); HeadersFrame requestFrame1 = new HeadersFrame(request1, null, false); FuturePromise promise1 = new FuturePromise<>(); final CountDownLatch stream1HeadersLatch = new CountDownLatch(1); final CountDownLatch stream1DataLatch = new CountDownLatch(1); - client.newStream(requestFrame1, promise1, new Stream.Listener.Adapter() + client.newStream(requestFrame1, promise1, new Stream.Listener() { @Override public void onHeaders(Stream stream, HeadersFrame frame) { stream1HeadersLatch.countDown(); + stream.demand(); } @Override - public void onData(Stream stream, DataFrame frame, Callback callback) + public void onDataAvailable(Stream stream) { - callback.succeeded(); + Stream.Data data = stream.readData(); + data.release(); stream1DataLatch.countDown(); } }); @@ -233,12 +238,13 @@ public class StreamResetTest extends AbstractTest HeadersFrame requestFrame2 = new HeadersFrame(request2, null, false); FuturePromise promise2 = new FuturePromise<>(); final CountDownLatch stream2DataLatch = new CountDownLatch(1); - client.newStream(requestFrame2, promise2, new Stream.Listener.Adapter() + client.newStream(requestFrame2, promise2, new Stream.Listener() { @Override - public void onData(Stream stream, DataFrame frame, Callback callback) + public void onDataAvailable(Stream stream) { - callback.succeeded(); + Stream.Data data = stream.readData(); + data.release(); stream2DataLatch.countDown(); } }); @@ -311,10 +317,10 @@ public class StreamResetTest extends AbstractTest } }); - Session client = newClientSession(new Session.Listener.Adapter()); + Session client = newClientSession(new Session.Listener() {}); MetaData.Request request = newRequest("GET", HttpFields.EMPTY); HeadersFrame frame = new HeadersFrame(request, null, true); - client.newStream(frame, new FuturePromise<>(), new Stream.Listener.Adapter() + client.newStream(frame, new FuturePromise<>(), new Stream.Listener() { @Override public void onHeaders(Stream stream, HeadersFrame frame) @@ -398,10 +404,10 @@ public class StreamResetTest extends AbstractTest } }); - Session client = newClientSession(new Session.Listener.Adapter()); + Session client = newClientSession(new Session.Listener() {}); MetaData.Request request = newRequest("GET", HttpFields.EMPTY); HeadersFrame frame = new HeadersFrame(request, null, true); - client.newStream(frame, new FuturePromise<>(), new Stream.Listener.Adapter() + client.newStream(frame, new FuturePromise<>(), new Stream.Listener() { @Override public void onHeaders(Stream stream, HeadersFrame frame) @@ -438,17 +444,17 @@ public class StreamResetTest extends AbstractTest } }); - Session client = newClientSession(new Session.Listener.Adapter()); + Session client = newClientSession(new Session.Listener() {}); MetaData.Request request = newRequest("GET", HttpFields.EMPTY); HeadersFrame frame = new HeadersFrame(request, null, false); FuturePromise promise = new FuturePromise<>(); - client.newStream(frame, promise, new Stream.Listener.Adapter()); + client.newStream(frame, promise, null); Stream stream = promise.get(5, TimeUnit.SECONDS); ByteBuffer data = ByteBuffer.allocate(FlowControlStrategy.DEFAULT_WINDOW_SIZE); stream.data(new DataFrame(stream.getId(), data, false), Callback.from(dataLatch::countDown)); // The server does not read the data, so the flow control window should be zero. assertTrue(dataLatch.await(5, TimeUnit.SECONDS)); - assertEquals(0, ((ISession)client).updateSendWindow(0)); + assertEquals(0, ((HTTP2Session)client).updateSendWindow(0)); // Now reset the stream. stream.reset(new ResetFrame(stream.getId(), ErrorCode.CANCEL_STREAM_ERROR.code), Callback.NOOP); @@ -457,7 +463,7 @@ public class StreamResetTest extends AbstractTest // it, and for the client to process the window updates. Thread.sleep(1000); - assertThat(((ISession)client).updateSendWindow(0), Matchers.greaterThan(0)); + assertThat(((HTTP2Session)client).updateSendWindow(0), Matchers.greaterThan(0)); } @Test @@ -500,7 +506,7 @@ public class StreamResetTest extends AbstractTest prepareClient(); httpClient.start(); - Session client = newClientSession(new Session.Listener.Adapter()); + Session client = newClientSession(new Session.Listener() {}); // Send requests until one is queued on the server but not dispatched. AtomicReference latch = new AtomicReference<>(); @@ -514,7 +520,7 @@ public class StreamResetTest extends AbstractTest MetaData.Request request = newRequest("GET", "/" + count, HttpFields.EMPTY); HeadersFrame frame = new HeadersFrame(request, null, false); FuturePromise promise = new FuturePromise<>(); - client.newStream(frame, promise, new Stream.Listener.Adapter() + client.newStream(frame, promise, new Stream.Listener() { @Override public void onHeaders(Stream stream, HeadersFrame frame) @@ -522,13 +528,16 @@ public class StreamResetTest extends AbstractTest MetaData.Response response = (MetaData.Response)frame.getMetaData(); if (response.getStatus() == HttpStatus.OK_200) latch.get().countDown(); + stream.demand(); } @Override - public void onData(Stream stream, DataFrame frame, Callback callback) + public void onDataAvailable(Stream stream) { - callback.succeeded(); - if (frame.isEndStream()) + Stream.Data data = stream.readData(); + data.release(); + stream.demand(); + if (data.frame().isEndStream()) latch.get().countDown(); } }); @@ -547,9 +556,9 @@ public class StreamResetTest extends AbstractTest HeadersFrame frame = new HeadersFrame(request, null, false); FuturePromise promise = new FuturePromise<>(); // This request will get no event from the server since it's reset by the client. - client.newStream(frame, promise, new Stream.Listener.Adapter()); + client.newStream(frame, promise, null); Stream stream = promise.get(5, TimeUnit.SECONDS); - ByteBuffer data = ByteBuffer.allocate(((ISession)client).updateSendWindow(0)); + ByteBuffer data = ByteBuffer.allocate(((HTTP2Session)client).updateSendWindow(0)); stream.data(new DataFrame(stream.getId(), data, false), new Callback() { @Override @@ -560,7 +569,7 @@ public class StreamResetTest extends AbstractTest }); // Wait for WINDOW_UPDATEs to be processed by the client. - await().atMost(1000, TimeUnit.SECONDS).until(() -> ((ISession)client).updateSendWindow(0), Matchers.greaterThan(0)); + await().atMost(1000, TimeUnit.SECONDS).until(() -> ((HTTP2Session)client).updateSendWindow(0), Matchers.greaterThan(0)); latch.set(new CountDownLatch(2 * streams.size())); // Notify all blocked threads to wakeup. @@ -591,12 +600,12 @@ public class StreamResetTest extends AbstractTest } }); - Session client = newClientSession(new Session.Listener.Adapter()); + Session client = newClientSession(new Session.Listener() {}); MetaData.Request request = newRequest("GET", HttpFields.EMPTY); HeadersFrame frame = new HeadersFrame(request, null, false); FuturePromise promise = new FuturePromise<>(); - client.newStream(frame, promise, new Stream.Listener.Adapter()); + client.newStream(frame, promise, null); Stream stream = promise.get(5, TimeUnit.SECONDS); ByteBuffer data = ByteBuffer.allocate(FlowControlStrategy.DEFAULT_WINDOW_SIZE); CountDownLatch dataLatch = new CountDownLatch(1); @@ -610,13 +619,13 @@ public class StreamResetTest extends AbstractTest }); // The server does not read the data, so the flow control window should be zero. assertTrue(dataLatch.await(5, TimeUnit.SECONDS)); - assertEquals(0, ((ISession)client).updateSendWindow(0)); + assertEquals(0, ((HTTP2Session)client).updateSendWindow(0)); // Wait for the server process the exception, and // for the client to process the window updates. Thread.sleep(2000); - assertThat(((ISession)client).updateSendWindow(0), Matchers.greaterThan(0)); + assertThat(((HTTP2Session)client).updateSendWindow(0), Matchers.greaterThan(0)); } } @@ -650,21 +659,23 @@ public class StreamResetTest extends AbstractTest } }); - Deque dataQueue = new ArrayDeque<>(); + Deque dataQueue = new ArrayDeque<>(); AtomicLong received = new AtomicLong(); CountDownLatch latch = new CountDownLatch(1); - Session client = newClientSession(new Session.Listener.Adapter()); + Session client = newClientSession(new Session.Listener() {}); MetaData.Request request = newRequest("GET", HttpFields.EMPTY); HeadersFrame frame = new HeadersFrame(request, null, true); FuturePromise promise = new FuturePromise<>(); - client.newStream(frame, promise, new Stream.Listener.Adapter() + client.newStream(frame, promise, new Stream.Listener() { @Override - public void onData(Stream stream, DataFrame frame, Callback callback) + public void onDataAvailable(Stream stream) { - dataQueue.offer(callback); + Stream.Data data = stream.readData(); + dataQueue.offer(data); // Do not consume the data yet. - if (received.addAndGet(frame.getData().remaining()) == windowSize) + stream.demand(); + if (received.addAndGet(data.frame().getData().remaining()) == windowSize) latch.countDown(); } }); @@ -673,7 +684,7 @@ public class StreamResetTest extends AbstractTest // Reset and consume. stream.reset(new ResetFrame(stream.getId(), ErrorCode.CANCEL_STREAM_ERROR.code), Callback.NOOP); - dataQueue.forEach(Callback::succeeded); + dataQueue.forEach(Stream.Data::release); assertTrue(writeLatch.await(5, TimeUnit.SECONDS)); } @@ -701,17 +712,21 @@ public class StreamResetTest extends AbstractTest AtomicLong received = new AtomicLong(); CountDownLatch latch = new CountDownLatch(1); - Session client = newClientSession(new Session.Listener.Adapter()); + Session client = newClientSession(new Session.Listener() {}); MetaData.Request request = newRequest("GET", HttpFields.EMPTY); HeadersFrame frame = new HeadersFrame(request, null, true); FuturePromise promise = new FuturePromise<>(); - client.newStream(frame, promise, new Stream.Listener.Adapter() + client.newStream(frame, promise, new Stream.Listener() { @Override - public void onData(Stream stream, DataFrame frame, Callback callback) + public void onDataAvailable(Stream stream) { - if (received.addAndGet(frame.getData().remaining()) == windowSize) + Stream.Data data = stream.readData(); + // Do not release to stall the flow control window. + if (received.addAndGet(data.frame().getData().remaining()) == windowSize) latch.countDown(); + else + stream.demand(); } }); Stream stream = promise.get(5, TimeUnit.SECONDS); @@ -750,21 +765,23 @@ public class StreamResetTest extends AbstractTest } }); - Deque dataQueue = new ArrayDeque<>(); + Deque dataQueue = new ArrayDeque<>(); AtomicLong received = new AtomicLong(); CountDownLatch latch = new CountDownLatch(1); - Session client = newClientSession(new Session.Listener.Adapter()); + Session client = newClientSession(new Session.Listener() {}); MetaData.Request request = newRequest("GET", HttpFields.EMPTY); HeadersFrame frame = new HeadersFrame(request, null, true); FuturePromise promise = new FuturePromise<>(); - client.newStream(frame, promise, new Stream.Listener.Adapter() + client.newStream(frame, promise, new Stream.Listener() { @Override - public void onData(Stream stream, DataFrame frame, Callback callback) + public void onDataAvailable(Stream stream) { - dataQueue.offer(callback); + Stream.Data data = stream.readData(); + dataQueue.offer(data); // Do not consume the data yet. - if (received.addAndGet(frame.getData().remaining()) == windowSize) + stream.demand(); + if (received.addAndGet(data.frame().getData().remaining()) == windowSize) latch.countDown(); } }); @@ -773,7 +790,7 @@ public class StreamResetTest extends AbstractTest // Reset and consume. stream.reset(new ResetFrame(stream.getId(), ErrorCode.CANCEL_STREAM_ERROR.code), Callback.NOOP); - dataQueue.forEach(Callback::succeeded); + dataQueue.forEach(Stream.Data::release); assertTrue(writeLatch.await(5, TimeUnit.SECONDS)); } @@ -804,12 +821,12 @@ public class StreamResetTest extends AbstractTest } }); - Session client = newClientSession(new Session.Listener.Adapter()); + Session client = newClientSession(new Session.Listener() {}); MetaData.Request request = newRequest("GET", HttpFields.EMPTY); HeadersFrame frame = new HeadersFrame(request, null, false); FuturePromise promise = new FuturePromise<>(); - client.newStream(frame, promise, new Stream.Listener.Adapter()); + client.newStream(frame, promise, null); Stream stream = promise.get(5, TimeUnit.SECONDS); ByteBuffer content = ByteBuffer.wrap(new byte[1024]); stream.data(new DataFrame(stream.getId(), content, true), Callback.NOOP); @@ -1016,16 +1033,16 @@ public class StreamResetTest extends AbstractTest http2.setFlowControlStrategyFactory(() -> new BufferingFlowControlStrategy(ratio) { @Override - protected void sendWindowUpdate(IStream stream, ISession session, WindowUpdateFrame frame) + protected void sendWindowUpdate(Session session, Stream stream, List frames) { // Before sending the window update, reset from the client side. if (stream != null) streamRef.get().reset(new ResetFrame(stream.getId(), ErrorCode.CANCEL_STREAM_ERROR.code), Callback.NOOP); - super.sendWindowUpdate(stream, session, frame); + super.sendWindowUpdate(session, stream, frames); } }); }; - start(new ServerSessionListener.Adapter() + start(new ServerSessionListener() { @Override public Stream.Listener onNewStream(Stream stream, HeadersFrame frame) @@ -1034,24 +1051,24 @@ public class StreamResetTest extends AbstractTest HeadersFrame responseFrame = new HeadersFrame(stream.getId(), response, null, false); Callback.Completable completable = new Callback.Completable(); stream.headers(responseFrame, completable); - // Consume the request content as it arrives. - return new Stream.Listener.Adapter(); + return null; } }, http2Factory); CountDownLatch failureLatch = new CountDownLatch(1); - Session client = newClientSession(new Session.Listener.Adapter() + Session client = newClientSession(new Session.Listener() { @Override - public void onFailure(Session session, Throwable failure) + public void onFailure(Session session, Throwable failure, Callback callback) { failureLatch.countDown(); + callback.succeeded(); } }); MetaData.Request request = newRequest("GET", HttpFields.EMPTY); HeadersFrame requestFrame = new HeadersFrame(request, null, false); FuturePromise promise = new FuturePromise<>(); - client.newStream(requestFrame, promise, new Stream.Listener.Adapter()); + client.newStream(requestFrame, promise, null); Stream stream = promise.get(5, TimeUnit.SECONDS); streamRef.set(stream); // Send enough bytes to trigger the server to send a window update. diff --git a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/TrailersTest.java b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/TrailersTest.java index d651b136910..009128a7f6b 100644 --- a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/TrailersTest.java +++ b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/TrailersTest.java @@ -60,7 +60,7 @@ public class TrailersTest extends AbstractTest public void testTrailersSentByClient() throws Exception { CountDownLatch latch = new CountDownLatch(1); - start(new ServerSessionListener.Adapter() + start(new ServerSessionListener() { @Override public Stream.Listener onNewStream(Stream stream, HeadersFrame frame) @@ -68,7 +68,7 @@ public class TrailersTest extends AbstractTest MetaData.Request request = (MetaData.Request)frame.getMetaData(); assertFalse(frame.isEndStream()); assertTrue(request.getFields().contains("X-Request")); - return new Stream.Listener.Adapter() + return new Stream.Listener() { @Override public void onHeaders(Stream stream, HeadersFrame frame) @@ -82,14 +82,14 @@ public class TrailersTest extends AbstractTest } }); - Session session = newClientSession(new Session.Listener.Adapter()); + Session session = newClientSession(new Session.Listener() {}); HttpFields.Mutable requestFields = HttpFields.build(); requestFields.put("X-Request", "true"); MetaData.Request request = newRequest("GET", requestFields); HeadersFrame requestFrame = new HeadersFrame(request, null, false); FuturePromise streamPromise = new FuturePromise<>(); - session.newStream(requestFrame, streamPromise, new Stream.Listener.Adapter()); + session.newStream(requestFrame, streamPromise, null); Stream stream = streamPromise.get(5, TimeUnit.SECONDS); // Send the trailers. @@ -151,7 +151,7 @@ public class TrailersTest extends AbstractTest } }); - Session session = newClientSession(new Session.Listener.Adapter()); + Session session = newClientSession(new Session.Listener() {}); HttpFields.Mutable requestFields = HttpFields.build(); requestFields.put("X-Request", "true"); @@ -159,7 +159,7 @@ public class TrailersTest extends AbstractTest HeadersFrame requestFrame = new HeadersFrame(request, null, false); FuturePromise streamPromise = new FuturePromise<>(); CountDownLatch latch = new CountDownLatch(1); - session.newStream(requestFrame, streamPromise, new Stream.Listener.Adapter() + session.newStream(requestFrame, streamPromise, new Stream.Listener() { @Override public void onHeaders(Stream stream, HeadersFrame frame) @@ -194,7 +194,7 @@ public class TrailersTest extends AbstractTest @Test public void testTrailersSentByServer() throws Exception { - start(new ServerSessionListener.Adapter() + start(new ServerSessionListener() { @Override public Stream.Listener onNewStream(Stream stream, HeadersFrame frame) @@ -219,11 +219,11 @@ public class TrailersTest extends AbstractTest } }); - Session session = newClientSession(new Session.Listener.Adapter()); + Session session = newClientSession(new Session.Listener() {}); MetaData.Request request = newRequest("GET", HttpFields.EMPTY); HeadersFrame requestFrame = new HeadersFrame(request, null, true); CountDownLatch latch = new CountDownLatch(1); - session.newStream(requestFrame, new Promise.Adapter<>(), new Stream.Listener.Adapter() + session.newStream(requestFrame, new Promise.Adapter<>(), new Stream.Listener() { private boolean responded; @@ -269,12 +269,12 @@ public class TrailersTest extends AbstractTest } }); - Session session = newClientSession(new Session.Listener.Adapter()); + Session session = newClientSession(new Session.Listener() {}); MetaData.Request request = newRequest("GET", HttpFields.EMPTY); HeadersFrame requestFrame = new HeadersFrame(request, null, true); CountDownLatch latch = new CountDownLatch(1); List frames = new ArrayList<>(); - session.newStream(requestFrame, new Promise.Adapter<>(), new Stream.Listener.Adapter() + session.newStream(requestFrame, new Promise.Adapter<>(), new Stream.Listener() { @Override public void onHeaders(Stream stream, HeadersFrame frame) @@ -282,13 +282,15 @@ public class TrailersTest extends AbstractTest frames.add(frame); if (frame.isEndStream()) latch.countDown(); + stream.demand(); } @Override - public void onData(Stream stream, DataFrame frame, Callback callback) + public void onDataAvailable(Stream stream) { - frames.add(frame); - callback.succeeded(); + Stream.Data data = stream.readData(); + frames.add(data.frame()); + data.release(); } }); @@ -318,11 +320,11 @@ public class TrailersTest extends AbstractTest } }); - Session session = newClientSession(new Session.Listener.Adapter()); + Session session = newClientSession(new Session.Listener() {}); MetaData.Request request = newRequest("POST", HttpFields.EMPTY); HeadersFrame requestFrame = new HeadersFrame(request, null, false); FuturePromise promise = new FuturePromise<>(); - session.newStream(requestFrame, promise, new Stream.Listener.Adapter()); + session.newStream(requestFrame, promise, null); Stream stream = promise.get(5, TimeUnit.SECONDS); ByteBuffer data = ByteBuffer.wrap(StringUtil.getUtf8Bytes("hello")); Callback.Completable completable = new Callback.Completable(); @@ -362,16 +364,17 @@ public class TrailersTest extends AbstractTest }); CountDownLatch clientLatch = new CountDownLatch(1); - Session session = newClientSession(new Session.Listener.Adapter()); + Session session = newClientSession(new Session.Listener() {}); MetaData.Request request = newRequest("POST", HttpFields.EMPTY); HeadersFrame requestFrame = new HeadersFrame(request, null, false); FuturePromise promise = new FuturePromise<>(); - session.newStream(requestFrame, promise, new Stream.Listener.Adapter() + session.newStream(requestFrame, promise, new Stream.Listener() { @Override - public void onReset(Stream stream, ResetFrame frame) + public void onReset(Stream stream, ResetFrame frame, Callback callback) { clientLatch.countDown(); + callback.succeeded(); } }); Stream stream = promise.get(5, TimeUnit.SECONDS); @@ -409,11 +412,11 @@ public class TrailersTest extends AbstractTest AtomicReference responseRef = new AtomicReference<>(); AtomicReference trailersRef = new AtomicReference<>(); CountDownLatch clientLatch = new CountDownLatch(2); - Session session = newClientSession(new Session.Listener.Adapter()); + Session session = newClientSession(new Session.Listener() {}); MetaData.Request request = newRequest("POST", HttpFields.EMPTY); HeadersFrame requestFrame = new HeadersFrame(request, null, false); FuturePromise promise = new FuturePromise<>(); - session.newStream(requestFrame, promise, new Stream.Listener.Adapter() + session.newStream(requestFrame, promise, new Stream.Listener() { @Override public void onHeaders(Stream stream, HeadersFrame frame) diff --git a/jetty-core/jetty-http2/jetty-http2-tests/src/test/resources/jetty-logging.properties b/jetty-core/jetty-http2/jetty-http2-tests/src/test/resources/jetty-logging.properties index d74b419ac50..0e6467e2dcf 100644 --- a/jetty-core/jetty-http2/jetty-http2-tests/src/test/resources/jetty-logging.properties +++ b/jetty-core/jetty-http2/jetty-http2-tests/src/test/resources/jetty-logging.properties @@ -1,3 +1,4 @@ #org.eclipse.jetty.LEVEL=DEBUG +#org.eclipse.jetty.client.LEVEL=DEBUG #org.eclipse.jetty.http2.LEVEL=DEBUG org.eclipse.jetty.http2.hpack.LEVEL=INFO diff --git a/jetty-core/jetty-http3/http3-common/src/main/java/org/eclipse/jetty/http3/api/Stream.java b/jetty-core/jetty-http3/http3-common/src/main/java/org/eclipse/jetty/http3/api/Stream.java index 558bd33fe62..ceae8fb0fd2 100644 --- a/jetty-core/jetty-http3/http3-common/src/main/java/org/eclipse/jetty/http3/api/Stream.java +++ b/jetty-core/jetty-http3/http3-common/src/main/java/org/eclipse/jetty/http3/api/Stream.java @@ -20,6 +20,7 @@ import java.util.concurrent.CompletableFuture; import org.eclipse.jetty.http.MetaData; import org.eclipse.jetty.http3.frames.DataFrame; import org.eclipse.jetty.http3.frames.HeadersFrame; +import org.eclipse.jetty.io.Retainable; /** *

A {@link Stream} represents a bidirectional exchange of data within a {@link Session}.

@@ -72,7 +73,7 @@ public interface Stream * *

When the returned {@link Stream.Data} object is not {@code null}, * applications must call, either immediately or later (possibly - * asynchronously) {@link Stream.Data#complete()} to notify the + * asynchronously) {@link Stream.Data#release()} to notify the * implementation that the bytes have been processed.

*

{@link Stream.Data} objects may be stored away for later, asynchronous, * processing (for example, to process them only when all of them have been @@ -190,7 +191,7 @@ public interface Stream * // Process the content. * process(data.getByteBuffer()); * // Notify that the content has been consumed. - * data.complete(); + * data.release(); * if (!data.isLast()) * { * // Demand to be called back. @@ -305,7 +306,7 @@ public interface Stream * // Process the content. * process(data.getByteBuffer()); * // Notify that the content has been consumed. - * data.complete(); + * data.release(); * if (!data.isLast()) * { * // Demand to be called back. @@ -362,27 +363,21 @@ public interface Stream /** *

A {@link Stream.Data} instance associates a {@link ByteBuffer} - * containing request bytes or response bytes with a completion event - * that applications must trigger when the bytes have been - * processed.

+ * containing request bytes or response bytes.

* * @see Stream#readData() */ - public static class Data + public abstract static class Data implements Retainable { private final DataFrame frame; - private final Runnable complete; - public Data(DataFrame frame, Runnable complete) + public Data(DataFrame frame) { this.frame = Objects.requireNonNull(frame); - this.complete = Objects.requireNonNull(complete); } /** * @return the {@link ByteBuffer} containing the data bytes - * - * @see #complete() */ public ByteBuffer getByteBuffer() { @@ -398,17 +393,6 @@ public interface Stream return frame.isLast(); } - /** - *

The method that applications must invoke to - * signal that the data bytes have been processed.

- * - * @see #getByteBuffer() - */ - public void complete() - { - complete.run(); - } - @Override public String toString() { diff --git a/jetty-core/jetty-http3/http3-common/src/main/java/org/eclipse/jetty/http3/internal/HTTP3StreamConnection.java b/jetty-core/jetty-http3/http3-common/src/main/java/org/eclipse/jetty/http3/internal/HTTP3StreamConnection.java index 790660607c8..c7b66ac228a 100644 --- a/jetty-core/jetty-http3/http3-common/src/main/java/org/eclipse/jetty/http3/internal/HTTP3StreamConnection.java +++ b/jetty-core/jetty-http3/http3-common/src/main/java/org/eclipse/jetty/http3/internal/HTTP3StreamConnection.java @@ -209,7 +209,7 @@ public abstract class HTTP3StreamConnection extends AbstractConnection // Release the network buffer here (if empty), since the application may // not be reading more bytes, to avoid to keep around a consumed buffer. tryReleaseBuffer(false); - return new Stream.Data(frame, () -> completeReadData(current)); + return new StreamData(frame, current); } else { @@ -251,13 +251,6 @@ public abstract class HTTP3StreamConnection extends AbstractConnection } } - private void completeReadData(RetainableByteBuffer buffer) - { - buffer.release(); - if (LOG.isDebugEnabled()) - LOG.debug("released retained {}", buffer); - } - public void demand() { boolean hasData; @@ -452,6 +445,29 @@ public abstract class HTTP3StreamConnection extends AbstractConnection return String.format("%s[demand=%b,stalled=%b,parserDataMode=%b]", super.toConnectionString(), hasDemand(), isStalled(), parserDataMode); } + private static class StreamData extends Stream.Data + { + private final RetainableByteBuffer retainable; + + public StreamData(DataFrame frame, RetainableByteBuffer retainable) + { + super(frame); + this.retainable = retainable; + } + + @Override + public void retain() + { + retainable.retain(); + } + + @Override + public boolean release() + { + return retainable.release(); + } + } + private class MessageListener extends ParserListener.Wrapper { private MessageListener(ParserListener listener) diff --git a/jetty-core/jetty-http3/http3-http-client-transport/src/main/java/org/eclipse/jetty/http3/client/http/internal/HttpReceiverOverHTTP3.java b/jetty-core/jetty-http3/http3-http-client-transport/src/main/java/org/eclipse/jetty/http3/client/http/internal/HttpReceiverOverHTTP3.java index d0957ad2149..867100b3768 100644 --- a/jetty-core/jetty-http3/http3-http-client-transport/src/main/java/org/eclipse/jetty/http3/client/http/internal/HttpReceiverOverHTTP3.java +++ b/jetty-core/jetty-http3/http3-http-client-transport/src/main/java/org/eclipse/jetty/http3/client/http/internal/HttpReceiverOverHTTP3.java @@ -13,8 +13,8 @@ package org.eclipse.jetty.http3.client.http.internal; -import java.io.UncheckedIOException; import java.nio.ByteBuffer; +import java.util.concurrent.atomic.AtomicBoolean; import org.eclipse.jetty.client.HttpExchange; import org.eclipse.jetty.client.HttpReceiver; @@ -34,7 +34,8 @@ import org.slf4j.LoggerFactory; public class HttpReceiverOverHTTP3 extends HttpReceiver implements Stream.Client.Listener { private static final Logger LOG = LoggerFactory.getLogger(HttpReceiverOverHTTP3.class); - private boolean notifySuccess; + + private final AtomicBoolean notifySuccess = new AtomicBoolean(); protected HttpReceiverOverHTTP3(HttpChannelOverHTTP3 channel) { @@ -58,7 +59,7 @@ public class HttpReceiverOverHTTP3 extends HttpReceiver implements Stream.Client if (exchange == null) return; - if (notifySuccess) + if (notifySuccess.get()) responseSuccess(exchange); else getHttpChannel().getStream().demand(); @@ -86,6 +87,7 @@ public class HttpReceiverOverHTTP3 extends HttpReceiver implements Stream.Client // TODO: add support for HttpMethod.CONNECT. + notifySuccess.set(frame.isLast()); if (responseHeaders(exchange)) { int status = response.getStatus(); @@ -98,7 +100,6 @@ public class HttpReceiverOverHTTP3 extends HttpReceiver implements Stream.Client { if (LOG.isDebugEnabled()) LOG.debug("stalling response processing, no demand after headers on {}", this); - notifySuccess = frame.isLast(); } } } @@ -110,55 +111,45 @@ public class HttpReceiverOverHTTP3 extends HttpReceiver implements Stream.Client if (exchange == null) return; - try + Stream.Data data = stream.readData(); + if (data != null) { - Stream.Data data = stream.readData(); - if (data != null) + ByteBuffer byteBuffer = data.getByteBuffer(); + if (byteBuffer.hasRemaining()) { - ByteBuffer byteBuffer = data.getByteBuffer(); - if (byteBuffer.hasRemaining()) + notifySuccess.set(data.isLast()); + Callback callback = Callback.from(Invocable.InvocationType.NON_BLOCKING, data::release, x -> { - Callback callback = Callback.from(Invocable.InvocationType.NON_BLOCKING, data::complete, x -> - { - data.complete(); - if (responseFailure(x)) - stream.reset(HTTP3ErrorCode.REQUEST_CANCELLED_ERROR.code(), x); - }); - boolean proceed = responseContent(exchange, byteBuffer, callback); - if (proceed) - { - if (data.isLast()) - responseSuccess(exchange); - else - stream.demand(); - } - else - { - if (LOG.isDebugEnabled()) - LOG.debug("stalling response processing, no demand after {} on {}", data, this); - notifySuccess = data.isLast(); - } - } - else + data.release(); + if (responseFailure(x)) + stream.reset(HTTP3ErrorCode.REQUEST_CANCELLED_ERROR.code(), x); + }); + boolean proceed = responseContent(exchange, byteBuffer, callback); + if (proceed) { - data.complete(); if (data.isLast()) responseSuccess(exchange); else stream.demand(); } + else + { + if (LOG.isDebugEnabled()) + LOG.debug("stalling response processing, no demand after {} on {}", data, this); + } } else { - stream.demand(); + data.release(); + if (data.isLast()) + responseSuccess(exchange); + else + stream.demand(); } } - catch (Throwable x) + else { - Throwable failure = x; - if (x instanceof UncheckedIOException) - failure = x.getCause(); - exchange.getRequest().abort(failure); + stream.demand(); } } diff --git a/jetty-core/jetty-http3/http3-server/src/main/java/org/eclipse/jetty/http3/server/internal/HttpStreamOverHTTP3.java b/jetty-core/jetty-http3/http3-server/src/main/java/org/eclipse/jetty/http3/server/internal/HttpStreamOverHTTP3.java index 736595f8b37..35eb4b0b430 100644 --- a/jetty-core/jetty-http3/http3-server/src/main/java/org/eclipse/jetty/http3/server/internal/HttpStreamOverHTTP3.java +++ b/jetty-core/jetty-http3/http3-server/src/main/java/org/eclipse/jetty/http3/server/internal/HttpStreamOverHTTP3.java @@ -143,9 +143,12 @@ public class HttpStreamOverHTTP3 implements HttpStream if (data == null) return null; + chunk = createChunk(data); + data.release(); + try (AutoLock ignored = lock.lock()) { - this.chunk = newChunk(data); + this.chunk = chunk; } } } @@ -186,10 +189,14 @@ public class HttpStreamOverHTTP3 implements HttpStream return null; } + Content.Chunk chunk = createChunk(data); + data.release(); + try (AutoLock ignored = lock.lock()) { - chunk = newChunk(data); + this.chunk = chunk; } + return httpChannel.onContentAvailable(); } @@ -209,9 +216,11 @@ public class HttpStreamOverHTTP3 implements HttpStream return httpChannel.onContentAvailable(); } - private Content.Chunk newChunk(Stream.Data data) + private Content.Chunk createChunk(Stream.Data data) { - return Content.Chunk.from(data.getByteBuffer(), data.isLast(), data::complete); + // As we are passing the ByteBuffer to the Chunk we need to retain. + data.retain(); + return Content.Chunk.from(data.getByteBuffer(), data.isLast(), data); } @Override diff --git a/jetty-core/jetty-http3/http3-tests/src/test/java/org/eclipse/jetty/http3/tests/ClientServerTest.java b/jetty-core/jetty-http3/http3-tests/src/test/java/org/eclipse/jetty/http3/tests/ClientServerTest.java index b94f10ec627..a176c79da43 100644 --- a/jetty-core/jetty-http3/http3-tests/src/test/java/org/eclipse/jetty/http3/tests/ClientServerTest.java +++ b/jetty-core/jetty-http3/http3-tests/src/test/java/org/eclipse/jetty/http3/tests/ClientServerTest.java @@ -224,7 +224,7 @@ public class ClientServerTest extends AbstractClientServerTest return; } // Recycle the ByteBuffer in data.frame. - data.complete(); + data.release(); // Call me again immediately. stream.demand(); if (data.isLast()) @@ -294,8 +294,8 @@ public class ClientServerTest extends AbstractClientServerTest } // Echo it back, then demand only when the write is finished. stream.data(new DataFrame(data.getByteBuffer(), data.isLast())) - // Always complete. - .whenComplete((s, x) -> data.complete()) + // Always release. + .whenComplete((s, x) -> data.release()) // Demand only if successful. .thenRun(stream::demand); } @@ -330,7 +330,7 @@ public class ClientServerTest extends AbstractClientServerTest { // Consume data. byteBuffer.put(data.getByteBuffer()); - data.complete(); + data.release(); if (data.isLast()) clientDataLatch.countDown(); } diff --git a/jetty-core/jetty-http3/http3-tests/src/test/java/org/eclipse/jetty/http3/tests/DataDemandTest.java b/jetty-core/jetty-http3/http3-tests/src/test/java/org/eclipse/jetty/http3/tests/DataDemandTest.java index 749a7bd4fa1..637605dfde2 100644 --- a/jetty-core/jetty-http3/http3-tests/src/test/java/org/eclipse/jetty/http3/tests/DataDemandTest.java +++ b/jetty-core/jetty-http3/http3-tests/src/test/java/org/eclipse/jetty/http3/tests/DataDemandTest.java @@ -462,7 +462,7 @@ public class DataDemandTest extends AbstractClientServerTest if (data != null) { // Consume the data. - data.complete(); + data.release(); if (data.isLast()) { dataLatch.countDown(); @@ -558,7 +558,7 @@ public class DataDemandTest extends AbstractClientServerTest } else { - data.complete(); + data.release(); if (data.isLast()) lastDataLatch.countDown(); else diff --git a/jetty-core/jetty-http3/http3-tests/src/test/java/org/eclipse/jetty/http3/tests/ExternalServerTest.java b/jetty-core/jetty-http3/http3-tests/src/test/java/org/eclipse/jetty/http3/tests/ExternalServerTest.java index 42737e2bf3f..1d24a853b67 100644 --- a/jetty-core/jetty-http3/http3-tests/src/test/java/org/eclipse/jetty/http3/tests/ExternalServerTest.java +++ b/jetty-core/jetty-http3/http3-tests/src/test/java/org/eclipse/jetty/http3/tests/ExternalServerTest.java @@ -76,7 +76,7 @@ public class ExternalServerTest System.err.println("RESPONSE DATA = " + data); if (data != null) { - data.complete(); + data.release(); if (data.isLast()) { requestLatch.countDown(); diff --git a/jetty-core/jetty-http3/http3-tests/src/test/java/org/eclipse/jetty/http3/tests/GoAwayTest.java b/jetty-core/jetty-http3/http3-tests/src/test/java/org/eclipse/jetty/http3/tests/GoAwayTest.java index ce80957a1d6..552dda9544d 100644 --- a/jetty-core/jetty-http3/http3-tests/src/test/java/org/eclipse/jetty/http3/tests/GoAwayTest.java +++ b/jetty-core/jetty-http3/http3-tests/src/test/java/org/eclipse/jetty/http3/tests/GoAwayTest.java @@ -578,7 +578,7 @@ public class GoAwayTest extends AbstractClientServerTest { Stream.Data data = stream.readData(); if (data != null) - data.complete(); + data.release(); if (data != null && data.isLast()) { MetaData.Response response = new MetaData.Response(HttpVersion.HTTP_3, HttpStatus.OK_200, HttpFields.EMPTY); @@ -1277,7 +1277,7 @@ public class GoAwayTest extends AbstractClientServerTest Stream.Data data = stream.readData(); if (data != null) { - data.complete(); + data.release(); if (data.isLast()) dataLatch.countDown(); } @@ -1332,7 +1332,7 @@ public class GoAwayTest extends AbstractClientServerTest Stream.Data data = stream.readData(); if (data != null) { - data.complete(); + data.release(); if (data.isLast()) dataLatch.countDown(); } diff --git a/jetty-core/jetty-http3/http3-tests/src/test/java/org/eclipse/jetty/http3/tests/HandlerClientServerTest.java b/jetty-core/jetty-http3/http3-tests/src/test/java/org/eclipse/jetty/http3/tests/HandlerClientServerTest.java index ec429f01ed0..479b839ccbf 100644 --- a/jetty-core/jetty-http3/http3-tests/src/test/java/org/eclipse/jetty/http3/tests/HandlerClientServerTest.java +++ b/jetty-core/jetty-http3/http3-tests/src/test/java/org/eclipse/jetty/http3/tests/HandlerClientServerTest.java @@ -123,7 +123,7 @@ public class HandlerClientServerTest extends AbstractClientServerTest copy.put(byteBuffer); copy.flip(); clientReceivedBuffers.add(copy); - data.complete(); + data.release(); if (data.isLast()) { diff --git a/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/Content.java b/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/Content.java index 82c645eb690..059ccf00c69 100644 --- a/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/Content.java +++ b/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/Content.java @@ -28,7 +28,6 @@ import org.eclipse.jetty.io.content.ContentSinkOutputStream; import org.eclipse.jetty.io.content.ContentSinkSubscriber; import org.eclipse.jetty.io.content.ContentSourceInputStream; import org.eclipse.jetty.io.content.ContentSourcePublisher; -import org.eclipse.jetty.io.content.ContentSourceTransformer; import org.eclipse.jetty.io.internal.ByteBufferChunk; import org.eclipse.jetty.io.internal.ContentCopier; import org.eclipse.jetty.io.internal.ContentSourceByteBuffer; @@ -269,8 +268,11 @@ public class Content * will continue to return the same error instance.

*

Once a read returns a {@link Chunk#isLast() last chunk}, further reads will * continue to return a last chunk (although the instance may be different).

- *

Chunks of content that have been consumed by the content reader code must - * be {@link Chunk#release() released}.

+ *

The content reader code must ultimately arrange for a call to + * {@link Chunk#release()} on the returned {@link Chunk}.

+ *

Additionally, prior to the ultimate call to {@link Chunk#release()}, the reader + * code may make additional calls to {@link Chunk#retain()}, that must ultimately + * be matched by a correspondent number of calls to {@link Chunk#release()}.

*

Concurrent reads from different threads are not recommended, as they are * inherently in a race condition.

*

Reads performed outside the invocation context of a @@ -281,6 +283,7 @@ public class Content * * @return a chunk of content, possibly an error instance, or {@code null} * @see #demand(Runnable) + * @see Retainable */ Chunk read(); @@ -316,41 +319,6 @@ public class Content * @param failure the cause of the failure */ void fail(Throwable failure); - - /** - *

A wrapper of a nested source of content, that may transform the chunks obtained from - * the nested source.

- *

Typical implementations may split/coalesce the chunks read from the nested source, - * or encode/decode (for example gzip) them.

- *

Implementations should override {@link #transform(Chunk)} with the transformation - * logic.

- */ - abstract class Transformer extends ContentSourceTransformer - { - public Transformer(Content.Source rawSource) - { - super(rawSource); - } - - /** - *

Transforms the input chunk parameter into an output chunk.

- *

The input chunk parameter may be {@code null}, a signal to implementations - * to try to produce an output chunk, if possible, from previous input chunks. - * For example, a single compressed input chunk may be transformed into multiple - * uncompressed output chunks.

- *

Implementations should return an {@link Chunk.Error error chunk} in case - * of transformation errors.

- *

Exceptions thrown by this method are equivalent to returning an error chunk.

- *

Implementations of this method must arrange to {@link Chunk#release() release} - * the input chunk, unless they return it as is. - * The output chunk is released by the code that uses this Transformer.

- * - * @param rawChunk the input chunk to transform - * @return the transformed output chunk - */ - @Override - protected abstract Chunk transform(Chunk rawChunk); - } } /** @@ -428,7 +396,7 @@ public class Content * to release the {@code ByteBuffer} back into a pool), or the * {@link #release()} method overridden.

*/ - public interface Chunk + public interface Chunk extends Retainable { /** *

An empty, non-last, chunk.

@@ -440,7 +408,8 @@ public class Content Content.Chunk EOF = ByteBufferChunk.EOF; /** - *

Creates a last/non-last Chunk with the given ByteBuffer.

+ *

Creates a Chunk with the given ByteBuffer.

+ *

The returned Chunk must be {@link #release() released}.

* * @param byteBuffer the ByteBuffer with the bytes of this Chunk * @param last whether the Chunk is the last one @@ -448,15 +417,15 @@ public class Content */ static Chunk from(ByteBuffer byteBuffer, boolean last) { - return new ByteBufferChunk(byteBuffer, last); + return new ByteBufferChunk.WithReferenceCount(byteBuffer, last); } /** - *

Creates a last/non-last Chunk with the given ByteBuffer.

+ *

Creates a Chunk with the given ByteBuffer.

+ *

The returned Chunk must be {@link #release() released}.

* * @param byteBuffer the ByteBuffer with the bytes of this Chunk * @param last whether the Chunk is the last one - * @param releaser the code to run when this Chunk is released * @return a new Chunk */ static Chunk from(ByteBuffer byteBuffer, boolean last, Runnable releaser) @@ -466,6 +435,7 @@ public class Content /** *

Creates a last/non-last Chunk with the given ByteBuffer.

+ *

The returned Chunk must be {@link #release() released}.

* * @param byteBuffer the ByteBuffer with the bytes of this Chunk * @param last whether the Chunk is the last one @@ -477,6 +447,21 @@ public class Content return new ByteBufferChunk.ReleasedByConsumer(byteBuffer, last, Objects.requireNonNull(releaser)); } + /** + *

Creates a last/non-last Chunk with the given ByteBuffer, linked to the given Retainable.

+ *

The {@link #retain()} and {@link #release()} methods of this Chunk will delegate to the + * given Retainable.

+ * + * @param byteBuffer the ByteBuffer with the bytes of this Chunk + * @param last whether the Chunk is the last one + * @param retainable the Retainable this Chunk links to + * @return a new Chunk + */ + static Chunk from(ByteBuffer byteBuffer, boolean last, Retainable retainable) + { + return new ByteBufferChunk.WithRetainable(byteBuffer, last, Objects.requireNonNull(retainable)); + } + /** *

Creates an {@link Error error chunk} with the given failure.

* @@ -537,9 +522,31 @@ public class Content boolean isLast(); /** - *

Releases the resources associated to this Chunk.

+ *

Returns a new {@code Chunk} whose {@code ByteBuffer} is a slice, with the given + * position and limit, of the {@code ByteBuffer} of the source {@code Chunk}.

+ *

The returned {@code Chunk} retains the source {@code Chunk} and it is linked + * to it via {@link #from(ByteBuffer, boolean, Retainable)}.

+ * + * @param source the original chunk + * @param position the position at which the slice begins + * @param limit the limit at which the slice ends + * @param last whether the new Chunk is last + * @return a new {@code Chunk} retained from the source {@code Chunk} with a slice + * of the source {@code Chunk}'s {@code ByteBuffer} */ - void release(); + default Chunk slice(Chunk source, int position, int limit, boolean last) + { + ByteBuffer sourceBuffer = source.getByteBuffer(); + int sourceLimit = sourceBuffer.limit(); + sourceBuffer.limit(limit); + int sourcePosition = sourceBuffer.position(); + sourceBuffer.position(position); + ByteBuffer slice = sourceBuffer.slice(); + sourceBuffer.limit(sourceLimit); + sourceBuffer.position(sourcePosition); + source.retain(); + return from(slice, last, source); + } /** * @return the number of bytes remaining in this Chunk @@ -635,8 +642,15 @@ public class Content } @Override - public void release() + public void retain() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean release() + { + return true; } } } diff --git a/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/Retainable.java b/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/Retainable.java new file mode 100644 index 00000000000..766e364caee --- /dev/null +++ b/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/Retainable.java @@ -0,0 +1,146 @@ +// +// ======================================================================== +// Copyright (c) 1995-2022 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License v. 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.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.io; + +import java.util.Objects; +import java.util.concurrent.atomic.AtomicInteger; + +/** + *

A reference counted resource, for example one that is borrowed from a pool, + * that may be retained an additional number of times, and released a correspondent + * number of times, over its lifecycle.

+ *

The resource is typically implicitly retained when it is first created. + * It may be retained more times (thus incrementing its reference count) and released + * (thus decrementing its reference count), until the reference count goes to zero.

+ */ +public interface Retainable +{ + /** + *

Retains this resource, incrementing the reference count.

+ */ + void retain(); + + /** + *

Releases this resource, decrementing the reference count.

+ *

This method returns {@code true} when the reference count goes to zero, + * {@code false} otherwise.

+ * + * @return whether the invocation of this method decremented the reference count to zero + */ + boolean release(); + + class Wrapper implements Retainable + { + private final Retainable wrapped; + + public Wrapper(Retainable wrapped) + { + this.wrapped = Objects.requireNonNull(wrapped); + } + + public Retainable getWrapped() + { + return wrapped; + } + + @Override + public void retain() + { + getWrapped().retain(); + } + + @Override + public boolean release() + { + return getWrapped().release(); + } + + @Override + public String toString() + { + return "%s@%x[%s]".formatted(getClass().getSimpleName(), hashCode(), getWrapped()); + } + } + + /** + *

A reference count implementation for a {@link Retainable} resource.

+ *

The reference count is initialized to 1 when the resource is created, + * and therefore it is implicitly retained and needs a call to {@link #release()}.

+ *

Additional calls to {@link #retain()} must be matched by correspondent + * calls to {@link #release()}.

+ *

When the reference count goes to zero, the resource may be pooled. + * When the resource is acquired from the pool, {@link #acquire()} should be + * called to set the reference count to {@code 1}.

+ */ + class ReferenceCounter implements Retainable + { + private final AtomicInteger references; + + public ReferenceCounter() + { + this(1); + } + + protected ReferenceCounter(int initialCount) + { + references = new AtomicInteger(initialCount); + } + + /** + *

Updates the reference count from {@code 0} to {@code 1}.

+ *

This method should only be used when this resource is acquired + * from a pool.

+ */ + protected void acquire() + { + if (references.getAndUpdate(c -> c == 0 ? 1 : c) != 0) + throw new IllegalStateException("acquired while in use " + this); + } + + @Override + public void retain() + { + if (references.getAndUpdate(c -> c == 0 ? 0 : c + 1) == 0) + throw new IllegalStateException("released " + this); + } + + @Override + public boolean release() + { + int ref = references.updateAndGet(c -> + { + if (c == 0) + throw new IllegalStateException("already released " + this); + return c - 1; + }); + return ref == 0; + } + + /** + *

Returns whether {@link #retain()} has been called at least one more time than {@link #release()}.

+ * + * @return whether this buffer is retained + */ + public boolean isRetained() + { + return references.get() > 1; + } + + @Override + public String toString() + { + return String.format("%s@%x[r=%d]", getClass().getSimpleName(), hashCode(), references.get()); + } + } +} diff --git a/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/RetainableByteBuffer.java b/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/RetainableByteBuffer.java index 84156df4015..876a0dd641d 100644 --- a/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/RetainableByteBuffer.java +++ b/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/RetainableByteBuffer.java @@ -14,12 +14,10 @@ package org.eclipse.jetty.io; import java.nio.ByteBuffer; -import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; import java.util.function.Consumer; import org.eclipse.jetty.util.BufferUtil; -import org.eclipse.jetty.util.Retainable; /** *

A pooled ByteBuffer which maintains a reference count that is @@ -33,15 +31,15 @@ import org.eclipse.jetty.util.Retainable; * *

Calling {@link #release()} on a out of pool and retained instance does not re-pool it while that re-pools it on a out of pool but not retained instance.

*/ -public class RetainableByteBuffer implements Retainable +public class RetainableByteBuffer extends Retainable.ReferenceCounter { private final ByteBuffer buffer; - private final AtomicInteger references = new AtomicInteger(); private final Consumer releaser; private final AtomicLong lastUpdate = new AtomicLong(System.nanoTime()); RetainableByteBuffer(ByteBuffer buffer, Consumer releaser) { + super(0); this.releaser = releaser; this.buffer = buffer; } @@ -61,61 +59,26 @@ public class RetainableByteBuffer implements Retainable return lastUpdate.getOpaque(); } - /** - * Checks if {@link #retain()} has been called at least one more time than {@link #release()}. - * @return true if this buffer is retained, false otherwise. - */ - public boolean isRetained() - { - return references.get() > 1; - } - public boolean isDirect() { return buffer.isDirect(); } - /** - * Increments the retained counter of this buffer. It must be done internally by - * the pool right after creation and after each un-pooling. - * The reason why this method exists on top of {@link #retain()} is to be able to - * have some safety checks that must know why the ref counter is being incremented. - */ - void acquire() + protected void acquire() { - if (references.getAndUpdate(c -> c == 0 ? 1 : c) != 0) - throw new IllegalStateException("re-pooled while still used " + this); + // Overridden for visibility. + super.acquire(); } - /** - * Increments the retained counter of this buffer. - */ - @Override - public void retain() - { - if (references.getAndUpdate(c -> c == 0 ? 0 : c + 1) == 0) - throw new IllegalStateException("released " + this); - } - - /** - * Decrements the retained counter of this buffer. - * @return true if the buffer was re-pooled, false otherwise. - */ public boolean release() { - int ref = references.updateAndGet(c -> - { - if (c == 0) - throw new IllegalStateException("already released " + this); - return c - 1; - }); - if (ref == 0) + boolean released = super.release(); + if (released) { lastUpdate.setOpaque(System.nanoTime()); releaser.accept(this); - return true; } - return false; + return released; } public int remaining() @@ -141,6 +104,6 @@ public class RetainableByteBuffer implements Retainable @Override public String toString() { - return String.format("%s@%x{%s,r=%d}", getClass().getSimpleName(), hashCode(), BufferUtil.toDetailString(buffer), references.get()); + return "%s[%s]".formatted(super.toString(), BufferUtil.toDetailString(buffer)); } } diff --git a/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/content/ContentSourceTransformer.java b/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/content/ContentSourceTransformer.java index 9ebcbfed98f..f1e98eee312 100644 --- a/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/content/ContentSourceTransformer.java +++ b/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/content/ContentSourceTransformer.java @@ -18,16 +18,13 @@ import java.util.Objects; import org.eclipse.jetty.io.Content; /** - *

- * This abstract {@link Content.Source} wraps another {@link Content.Source} and implementors need only to provide - * the {@link #transform(Content.Chunk)} method, which is used to transform {@link Content.Chunk} read from the - * wrapped source. - *

- *

- * The {@link #demand(Runnable)} conversation is passed directly to the wrapped {@link Content.Source}, which means - * that transformations that may fully consume bytes read can result in a null return from {@link Content.Source#read()} - * even after a callback to the demand {@link Runnable} (as per spurious invocation in {@link Content.Source#demand(Runnable)}. - *

+ *

This abstract {@link Content.Source} wraps another {@link Content.Source} and implementers need only + * to implement the {@link #transform(Content.Chunk)} method, which is used to transform {@link Content.Chunk} + * read from the wrapped source.

+ *

The {@link #demand(Runnable)} conversation is passed directly to the wrapped {@link Content.Source}, + * which means that transformations that may fully consume bytes read can result in a null return from + * {@link Content.Source#read()} even after a callback to the demand {@link Runnable} (as per spurious + * invocation in {@link Content.Source#demand(Runnable)}.

*/ public abstract class ContentSourceTransformer implements Content.Source { @@ -63,7 +60,8 @@ public abstract class ContentSourceTransformer implements Content.Source transformedChunk = process(rawChunk); - // Release of rawChunk must be done by transform(). + if (rawChunk != null && rawChunk != transformedChunk) + rawChunk.release(); rawChunk = null; if (transformedChunk != null) @@ -117,29 +115,34 @@ public abstract class ContentSourceTransformer implements Content.Source } catch (Throwable x) { - if (rawChunk != null) - rawChunk.release(); fail(x); return Content.Chunk.from(x); } } /** - * Content chunk transformation method. - *

- * This method is called during a {@link Content.Source#read()} to transform a raw chunk to a chunk that - * will be returned from the read call. The caller of {@link Content.Source#read()} method is always - * responsible for calling {@link Content.Chunk#release()} on the returned chunk, which may be: + *

Transforms the input chunk parameter into an output chunk.

+ *

When this method produces a non-{@code null}, non-last chunk, + * it is subsequently invoked with a {@code null} input chunk to try to + * produce more output chunks from the previous input chunk. + * For example, a single compressed input chunk may be transformed into + * multiple uncompressed output chunks.

+ *

The input chunk is released as soon as this method returns, so + * implementations that must hold onto the input chunk must arrange to call + * {@link Content.Chunk#retain()} and its correspondent {@link Content.Chunk#release()}.

+ *

Implementations should return an {@link Content.Chunk.Error error chunk} in case + * of transformation errors.

+ *

Exceptions thrown by this method are equivalent to returning an error chunk.

+ *

Implementations of this method may return:

*
    - *
  • the rawChunk. This is typically done for {@link Content.Chunk.Error}s, - * when {@link Content.Chunk#isLast()} is true, or if no transformation is required.
  • - *
  • a new (or predefined) {@link Content.Chunk} derived from the rawChunk. The transform is - * responsible for calling {@link Content.Chunk#release()} on the rawChunk, either during the call - * to {@link Content.Source#read()} or subsequently.
  • - *
  • null if the rawChunk is fully consumed and/or requires additional chunks to be transformed.
  • + *
  • {@code null}, if more input chunks are necessary to produce an output chunk
  • + *
  • the {@code inputChunk} itself, typically in case of {@link Content.Chunk.Error}s, + * or when no transformation is required
  • + *
  • a new {@link Content.Chunk} derived from {@code inputChunk}.
  • *
- * @param rawChunk A chunk read from the wrapped {@link Content.Source}. It is always non null. - * @return The transformed chunk or null. + * + * @param inputChunk a chunk read from the wrapped {@link Content.Source} + * @return a transformed chunk or {@code null} */ - protected abstract Content.Chunk transform(Content.Chunk rawChunk); + protected abstract Content.Chunk transform(Content.Chunk inputChunk); } diff --git a/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/content/InputStreamContentSource.java b/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/content/InputStreamContentSource.java index ad2e7df0312..e38970eddf3 100644 --- a/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/content/InputStreamContentSource.java +++ b/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/content/InputStreamContentSource.java @@ -19,6 +19,8 @@ import java.nio.ByteBuffer; import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.io.Content; import org.eclipse.jetty.io.NoopByteBufferPool; +import org.eclipse.jetty.io.RetainableByteBuffer; +import org.eclipse.jetty.io.RetainableByteBufferPool; import org.eclipse.jetty.util.IO; import org.eclipse.jetty.util.thread.AutoLock; import org.eclipse.jetty.util.thread.SerializedInvoker; @@ -37,7 +39,7 @@ public class InputStreamContentSource implements Content.Source private final AutoLock lock = new AutoLock(); private final SerializedInvoker invoker = new SerializedInvoker(); private final InputStream inputStream; - private final ByteBufferPool bufferPool; + private final RetainableByteBufferPool bufferPool; private int bufferSize = 4096; private Runnable demandCallback; private Content.Chunk.Error errorChunk; @@ -45,13 +47,18 @@ public class InputStreamContentSource implements Content.Source public InputStreamContentSource(InputStream inputStream) { - this(inputStream, null); + this(inputStream, (ByteBufferPool)null); } public InputStreamContentSource(InputStream inputStream, ByteBufferPool bufferPool) + { + this(inputStream, (bufferPool == null ? ByteBufferPool.NOOP : bufferPool).asRetainableByteBufferPool()); + } + + public InputStreamContentSource(InputStream inputStream, RetainableByteBufferPool bufferPool) { this.inputStream = inputStream; - this.bufferPool = bufferPool == null ? ByteBufferPool.NOOP : bufferPool; + this.bufferPool = bufferPool == null ? ByteBufferPool.NOOP.asRetainableByteBufferPool() : bufferPool; } public int getBufferSize() @@ -77,7 +84,8 @@ public class InputStreamContentSource implements Content.Source try { - ByteBuffer buffer = bufferPool.acquire(getBufferSize(), false); + RetainableByteBuffer streamBuffer = bufferPool.acquire(getBufferSize(), false); + ByteBuffer buffer = streamBuffer.getBuffer(); int read = inputStream.read(buffer.array(), buffer.arrayOffset(), buffer.capacity()); if (read < 0) { @@ -87,7 +95,7 @@ public class InputStreamContentSource implements Content.Source else { buffer.limit(read); - return Content.Chunk.from(buffer, false, bufferPool::release); + return Content.Chunk.from(buffer, false, streamBuffer); } } catch (Throwable x) diff --git a/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/content/PathContentSource.java b/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/content/PathContentSource.java index 799c01ee4fe..d54c6ae6e3c 100644 --- a/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/content/PathContentSource.java +++ b/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/content/PathContentSource.java @@ -24,13 +24,15 @@ import java.nio.file.StandardOpenOption; import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.io.Content; +import org.eclipse.jetty.io.RetainableByteBuffer; +import org.eclipse.jetty.io.RetainableByteBufferPool; import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.IO; import org.eclipse.jetty.util.thread.AutoLock; import org.eclipse.jetty.util.thread.SerializedInvoker; /** - *

A {@link Content.Source} that provides the file content of the passed {@link Path}

+ *

A {@link Content.Source} that provides the file content of the passed {@link Path}.

*/ public class PathContentSource implements Content.Source { @@ -38,8 +40,8 @@ public class PathContentSource implements Content.Source private final SerializedInvoker invoker = new SerializedInvoker(); private final Path path; private final long length; + private final RetainableByteBufferPool byteBufferPool; private int bufferSize = 4096; - private ByteBufferPool byteBufferPool; private boolean useDirectByteBuffers = true; private ReadableByteChannel channel; private long totalRead; @@ -47,6 +49,16 @@ public class PathContentSource implements Content.Source private Content.Chunk.Error errorChunk; public PathContentSource(Path path) throws IOException + { + this(path, (ByteBufferPool)null); + } + + public PathContentSource(Path path, ByteBufferPool byteBufferPool) throws IOException + { + this(path, (byteBufferPool == null ? ByteBufferPool.NOOP : byteBufferPool).asRetainableByteBufferPool()); + } + + public PathContentSource(Path path, RetainableByteBufferPool byteBufferPool) throws IOException { if (!Files.isRegularFile(path)) throw new NoSuchFileException(path.toString()); @@ -54,6 +66,7 @@ public class PathContentSource implements Content.Source throw new AccessDeniedException(path.toString()); this.path = path; this.length = Files.size(path); + this.byteBufferPool = byteBufferPool == null ? ByteBufferPool.NOOP.asRetainableByteBufferPool() : byteBufferPool; } public Path getPath() @@ -77,16 +90,6 @@ public class PathContentSource implements Content.Source this.bufferSize = bufferSize; } - public ByteBufferPool getByteBufferPool() - { - return byteBufferPool; - } - - public void setByteBufferPool(ByteBufferPool byteBufferPool) - { - this.byteBufferPool = byteBufferPool; - } - public boolean isUseDirectByteBuffers() { return useDirectByteBuffers; @@ -123,9 +126,8 @@ public class PathContentSource implements Content.Source if (!channel.isOpen()) return Content.Chunk.EOF; - ByteBuffer byteBuffer = byteBufferPool == null - ? BufferUtil.allocate(getBufferSize(), isUseDirectByteBuffers()) - : byteBufferPool.acquire(getBufferSize(), isUseDirectByteBuffers()); + RetainableByteBuffer retainableBuffer = byteBufferPool.acquire(getBufferSize(), isUseDirectByteBuffers()); + ByteBuffer byteBuffer = retainableBuffer.getBuffer(); int read; try @@ -146,7 +148,7 @@ public class PathContentSource implements Content.Source if (last) IO.close(channel); - return Content.Chunk.from(byteBuffer, last, this::release); + return Content.Chunk.from(byteBuffer, last, retainableBuffer); } @Override @@ -204,12 +206,6 @@ public class PathContentSource implements Content.Source } } - private void release(ByteBuffer byteBuffer) - { - if (byteBufferPool != null) - byteBufferPool.release(byteBuffer); - } - protected boolean rewind() { try (AutoLock ignored = lock.lock()) diff --git a/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/internal/ByteBufferChunk.java b/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/internal/ByteBufferChunk.java index 3e5cdc87cd6..03e216c5239 100644 --- a/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/internal/ByteBufferChunk.java +++ b/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/internal/ByteBufferChunk.java @@ -19,9 +19,10 @@ import java.util.concurrent.atomic.AtomicReference; import java.util.function.Consumer; import org.eclipse.jetty.io.Content; +import org.eclipse.jetty.io.Retainable; import org.eclipse.jetty.util.BufferUtil; -public class ByteBufferChunk implements Content.Chunk +public abstract class ByteBufferChunk implements Content.Chunk { public static final ByteBufferChunk EMPTY = new ByteBufferChunk(BufferUtil.EMPTY_BUFFER, false) { @@ -49,18 +50,28 @@ public class ByteBufferChunk implements Content.Chunk this.last = last; } + @Override public ByteBuffer getByteBuffer() { return byteBuffer; } + @Override public boolean isLast() { return last; } - public void release() + @Override + public void retain() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean release() + { + return true; } @Override @@ -74,7 +85,29 @@ public class ByteBufferChunk implements Content.Chunk ); } - public static class ReleasedByRunnable extends ByteBufferChunk + public static class WithReferenceCount extends ByteBufferChunk + { + private final ReferenceCounter references = new ReferenceCounter(); + + public WithReferenceCount(ByteBuffer byteBuffer, boolean last) + { + super(byteBuffer, last); + } + + @Override + public void retain() + { + references.retain(); + } + + @Override + public boolean release() + { + return references.release(); + } + } + + public static class ReleasedByRunnable extends ByteBufferChunk.WithReferenceCount { private final AtomicReference releaser; @@ -84,15 +117,21 @@ public class ByteBufferChunk implements Content.Chunk this.releaser = new AtomicReference<>(releaser); } - public void release() + @Override + public boolean release() { - Runnable runnable = releaser.getAndSet(null); - if (runnable != null) - runnable.run(); + boolean released = super.release(); + if (released) + { + Runnable runnable = releaser.getAndSet(null); + if (runnable != null) + runnable.run(); + } + return released; } } - public static class ReleasedByConsumer extends ByteBufferChunk + public static class ReleasedByConsumer extends ByteBufferChunk.WithReferenceCount { private final AtomicReference> releaser; @@ -102,11 +141,40 @@ public class ByteBufferChunk implements Content.Chunk this.releaser = new AtomicReference<>(releaser); } - public void release() + @Override + public boolean release() { - Consumer consumer = releaser.getAndSet(null); - if (consumer != null) - consumer.accept(getByteBuffer()); + boolean released = super.release(); + if (released) + { + Consumer consumer = releaser.getAndSet(null); + if (consumer != null) + consumer.accept(getByteBuffer()); + } + return released; + } + } + + public static class WithRetainable extends ByteBufferChunk + { + private final Retainable retainable; + + public WithRetainable(ByteBuffer byteBuffer, boolean last, Retainable retainable) + { + super(byteBuffer, last); + this.retainable = retainable; + } + + @Override + public void retain() + { + retainable.retain(); + } + + @Override + public boolean release() + { + return retainable.release(); } } } diff --git a/jetty-core/jetty-io/src/test/java/org/eclipse/jetty/io/ContentSourceTest.java b/jetty-core/jetty-io/src/test/java/org/eclipse/jetty/io/ContentSourceTest.java index 2d0da679ed2..239c459074d 100644 --- a/jetty-core/jetty-io/src/test/java/org/eclipse/jetty/io/ContentSourceTest.java +++ b/jetty-core/jetty-io/src/test/java/org/eclipse/jetty/io/ContentSourceTest.java @@ -33,6 +33,7 @@ import java.util.concurrent.atomic.AtomicReference; import org.eclipse.jetty.io.content.AsyncContent; import org.eclipse.jetty.io.content.ByteBufferContentSource; import org.eclipse.jetty.io.content.ContentSourceInputStream; +import org.eclipse.jetty.io.content.ContentSourceTransformer; import org.eclipse.jetty.io.content.InputStreamContentSource; import org.eclipse.jetty.io.content.PathContentSource; import org.eclipse.jetty.toolchain.test.MavenTestingUtils; @@ -69,7 +70,7 @@ public class ContentSourceTest ByteBufferContentSource byteBufferSource = new ByteBufferContentSource(UTF_8.encode("one"), UTF_8.encode("two")); - Content.Source.Transformer transformerSource = new Content.Source.Transformer(new ByteBufferContentSource(UTF_8.encode("one"), UTF_8.encode("two"))) + ContentSourceTransformer transformerSource = new ContentSourceTransformer(new ByteBufferContentSource(UTF_8.encode("one"), UTF_8.encode("two"))) { @Override protected Content.Chunk transform(Content.Chunk rawChunk) @@ -80,7 +81,7 @@ public class ContentSourceTest @Override public String toString() { - return "Content.Source.Transformer@%x".formatted(hashCode()); + return "%s@%x".formatted(ContentSourceTransformer.class.getSimpleName(), hashCode()); } }; diff --git a/jetty-core/jetty-io/src/test/java/org/eclipse/jetty/io/ContentSourceTransformerTest.java b/jetty-core/jetty-io/src/test/java/org/eclipse/jetty/io/ContentSourceTransformerTest.java index 8c85ca110f1..a2d73229cdc 100644 --- a/jetty-core/jetty-io/src/test/java/org/eclipse/jetty/io/ContentSourceTransformerTest.java +++ b/jetty-core/jetty-io/src/test/java/org/eclipse/jetty/io/ContentSourceTransformerTest.java @@ -22,6 +22,7 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import org.eclipse.jetty.io.content.AsyncContent; +import org.eclipse.jetty.io.content.ContentSourceTransformer; import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.FutureCallback; import org.junit.jupiter.api.Test; @@ -247,7 +248,7 @@ public class ContentSourceTransformerTest assertInstanceOf(Content.Chunk.Error.class, chunk); } - private static class WordSplitLowCaseTransformer extends Content.Source.Transformer + private static class WordSplitLowCaseTransformer extends ContentSourceTransformer { private final Queue chunks = new ArrayDeque<>(); diff --git a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/DelayedHandler.java b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/DelayedHandler.java index 90a445d0212..680392ac5bf 100644 --- a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/DelayedHandler.java +++ b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/DelayedHandler.java @@ -53,6 +53,8 @@ public abstract class DelayedHandler extends Handler.Wrapper return processor; if (request.getLength() <= 0 && !request.getHeaders().contains(HttpHeader.CONTENT_TYPE)) return processor; + // TODO: add logic to not delay if it's a CONNECT request. + // TODO: also add logic to not delay if it's a request that expects 100 Continue. return new UntilContentProcessor(request, processor); } diff --git a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/gzip/GzipRequest.java b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/gzip/GzipRequest.java index d0beeca24c5..af47456d516 100644 --- a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/gzip/GzipRequest.java +++ b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/gzip/GzipRequest.java @@ -19,6 +19,7 @@ import org.eclipse.jetty.http.GZIPContentDecoder; import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.io.Content; +import org.eclipse.jetty.io.content.ContentSourceTransformer; import org.eclipse.jetty.server.Components; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.Response; @@ -33,7 +34,7 @@ public class GzipRequest extends Request.WrapperProcessor private final boolean _inflateInput; private Decoder _decoder; - private GzipTransformer gzipContentProcessor; + private GzipTransformer gzipTransformer; private final int _inflateBufferSize; private final GzipHandler _gzipHandler; private final HttpFields _fields; @@ -62,7 +63,7 @@ public class GzipRequest extends Request.WrapperProcessor { Components components = request.getComponents(); _decoder = new Decoder(__inflaterPool, components.getByteBufferPool(), _inflateBufferSize); - gzipContentProcessor = new GzipTransformer(request); + gzipTransformer = new GzipTransformer(request); } int outputBufferSize = request.getConnectionMetaData().getHttpConfiguration().getOutputBufferSize(); @@ -75,7 +76,7 @@ public class GzipRequest extends Request.WrapperProcessor public Content.Chunk read() { if (_inflateInput) - return gzipContentProcessor.read(); + return gzipTransformer.read(); return super.read(); } @@ -83,7 +84,7 @@ public class GzipRequest extends Request.WrapperProcessor public void demand(Runnable demandCallback) { if (_inflateInput) - gzipContentProcessor.demand(demandCallback); + gzipTransformer.demand(demandCallback); else super.demand(demandCallback); } @@ -101,7 +102,7 @@ public class GzipRequest extends Request.WrapperProcessor } } - private class GzipTransformer extends Content.Source.Transformer + private class GzipTransformer extends ContentSourceTransformer { private Content.Chunk _chunk; @@ -111,38 +112,43 @@ public class GzipRequest extends Request.WrapperProcessor } @Override - protected Content.Chunk transform(Content.Chunk compressed) + protected Content.Chunk transform(Content.Chunk inputChunk) { - try - { - if (_chunk == null) - _chunk = compressed; - if (_chunk == null) - return null; - if (_chunk instanceof Content.Chunk.Error) - return _chunk; - if (_chunk.isLast() && !_chunk.hasRemaining()) - return Content.Chunk.EOF; + boolean retain = _chunk == null; + if (_chunk == null) + _chunk = inputChunk; + if (_chunk == null) + return null; + if (_chunk instanceof Content.Chunk.Error) + return _chunk; + if (_chunk.isLast() && !_chunk.hasRemaining()) + return Content.Chunk.EOF; - ByteBuffer decodedBuffer = _decoder.decode(_chunk); - if (BufferUtil.hasContent(decodedBuffer)) - return Content.Chunk.from(decodedBuffer, _chunk.isLast() && !_chunk.hasRemaining(), _decoder::release); - return _chunk.isLast() ? Content.Chunk.EOF : null; - } - finally + // Retain the input chunk because its ByteBuffer will be referenced by the Inflater. + if (retain) + _chunk.retain(); + ByteBuffer decodedBuffer = _decoder.decode(_chunk); + + if (BufferUtil.hasContent(decodedBuffer)) { - if (_chunk != null && !_chunk.hasRemaining()) - { - _chunk.release(); - _chunk = null; - } + // The decoded ByteBuffer is a transformed "copy" of the + // compressed one, so it has its own reference counter. + return Content.Chunk.from(decodedBuffer, _chunk.isLast() && !_chunk.hasRemaining(), _decoder::release); + } + else + { + // Could not decode more from this chunk, release it. + Content.Chunk result = _chunk.isLast() ? Content.Chunk.EOF : null; + _chunk.release(); + _chunk = null; + return result; } } } private static class Decoder extends GZIPContentDecoder { - private ByteBuffer _chunk; + private ByteBuffer _decoded; private Decoder(InflaterPool inflaterPool, ByteBufferPool bufferPool, int bufferSize) { @@ -152,22 +158,22 @@ public class GzipRequest extends Request.WrapperProcessor public ByteBuffer decode(Content.Chunk content) { decodeChunks(content.getByteBuffer()); - ByteBuffer chunk = _chunk; - _chunk = null; + ByteBuffer chunk = _decoded; + _decoded = null; return chunk; } @Override - protected boolean decodedChunk(final ByteBuffer chunk) + protected boolean decodedChunk(ByteBuffer decoded) { - _chunk = chunk; + _decoded = decoded; return true; } @Override public void decodeChunks(ByteBuffer compressed) { - _chunk = null; + _decoded = null; super.decodeChunks(compressed); } } diff --git a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/internal/HttpConnection.java b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/internal/HttpConnection.java index 67eab53c6a2..88461b2e84f 100644 --- a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/internal/HttpConnection.java +++ b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/internal/HttpConnection.java @@ -50,6 +50,7 @@ import org.eclipse.jetty.io.Connection; import org.eclipse.jetty.io.Content; import org.eclipse.jetty.io.EndPoint; import org.eclipse.jetty.io.EofException; +import org.eclipse.jetty.io.Retainable; import org.eclipse.jetty.io.RetainableByteBuffer; import org.eclipse.jetty.io.RetainableByteBufferPool; import org.eclipse.jetty.io.WriteFlusher; @@ -80,6 +81,7 @@ import static org.eclipse.jetty.http.HttpStatus.INTERNAL_SERVER_ERROR_500; public class HttpConnection extends AbstractConnection implements Runnable, WriteFlusher.Listener, Connection.UpgradeFrom, Connection.UpgradeTo, ConnectionMetaData { private static final Logger LOG = LoggerFactory.getLogger(HttpConnection.class); + private static final HttpField PREAMBLE_UPGRADE_H2C = new HttpField(HttpHeader.UPGRADE, "h2c"); private static final ThreadLocal __currentConnection = new ThreadLocal<>(); private static final AtomicLong __connectionIdGenerator = new AtomicLong(); @@ -1007,12 +1009,7 @@ public class HttpConnection extends AbstractConnection implements Runnable, Writ LOG.debug("content {}/{} for {}", BufferUtil.toDetailString(buffer), _retainableByteBuffer, HttpConnection.this); RetainableByteBuffer retainable = _retainableByteBuffer; - stream._chunk = Content.Chunk.from(buffer, false, () -> - { - retainable.release(); - if (LOG.isDebugEnabled()) - LOG.debug("release {}/{} for {}", BufferUtil.toDetailString(buffer), retainable, this); - }); + stream._chunk = Content.Chunk.from(buffer, false, new ChunkRetainable(retainable, buffer)); return true; } @@ -1137,7 +1134,25 @@ public class HttpConnection extends AbstractConnection implements Runnable, Writ } } - private static final HttpField PREAMBLE_UPGRADE_H2C = new HttpField(HttpHeader.UPGRADE, "h2c"); + private class ChunkRetainable extends Retainable.Wrapper + { + private final ByteBuffer buffer; + + private ChunkRetainable(Retainable retainable, ByteBuffer buffer) + { + super(retainable); + this.buffer = buffer; + } + + @Override + public boolean release() + { + boolean released = super.release(); + if (LOG.isDebugEnabled()) + LOG.debug("content released {} {}/{} for {}", released, BufferUtil.toDetailString(buffer), getWrapped(), HttpConnection.this); + return released; + } + } protected class HttpStreamOverHTTP1 implements HttpStream { diff --git a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/Retainable.java b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/Retainable.java deleted file mode 100644 index f3160945186..00000000000 --- a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/Retainable.java +++ /dev/null @@ -1,19 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995-2022 Mort Bay Consulting Pty Ltd and others. -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License v. 2.0 which is available at -// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.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.util; - -public interface Retainable -{ - public void retain(); -} diff --git a/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ServletChannel.java b/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ServletChannel.java index 2bd5055f913..73d26609e26 100644 --- a/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ServletChannel.java +++ b/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ServletChannel.java @@ -608,6 +608,7 @@ public class ServletChannel implements Runnable try { _servletContextApi.getContext().getServletContextHandler().requestInitialized(_request, _request.getHttpServletRequest()); + getHttpOutput().reopen(); _combinedListener.onBeforeDispatch(_request); dispatchable.dispatch(); } diff --git a/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-http-client-transport/src/test/java/org/eclipse/jetty/ee10/http/client/ProxyWithDynamicTransportTest.java b/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-http-client-transport/src/test/java/org/eclipse/jetty/ee10/http/client/ProxyWithDynamicTransportTest.java index 203b5755c8f..817d8fe2a27 100644 --- a/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-http-client-transport/src/test/java/org/eclipse/jetty/ee10/http/client/ProxyWithDynamicTransportTest.java +++ b/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-http-client-transport/src/test/java/org/eclipse/jetty/ee10/http/client/ProxyWithDynamicTransportTest.java @@ -434,7 +434,7 @@ public class ProxyWithDynamicTransportTest startClient(); FuturePromise sessionPromise = new FuturePromise<>(); - http2Client.connect(new InetSocketAddress("localhost", proxyConnector.getLocalPort()), new Session.Listener.Adapter(), sessionPromise); + http2Client.connect(new InetSocketAddress("localhost", proxyConnector.getLocalPort()), new Session.Listener(), sessionPromise); Session session = sessionPromise.get(5, TimeUnit.SECONDS); String serverAddress = "localhost:" + serverConnector.getLocalPort(); MetaData.ConnectRequest connect = new MetaData.ConnectRequest(HttpScheme.HTTP, new AuthorityHttpField(serverAddress), null, HttpFields.EMPTY, null); @@ -442,7 +442,7 @@ public class ProxyWithDynamicTransportTest FuturePromise streamPromise = new FuturePromise<>(); CountDownLatch tunnelLatch = new CountDownLatch(1); CountDownLatch responseLatch = new CountDownLatch(1); - session.newStream(frame, streamPromise, new Stream.Listener.Adapter() + session.newStream(frame, streamPromise, new Stream.Listener() { @Override public void onHeaders(Stream stream, HeadersFrame frame) @@ -519,7 +519,7 @@ public class ProxyWithDynamicTransportTest ((HTTP2CServerConnectionFactory)h2c).setStreamIdleTimeout(streamIdleTimeout); FuturePromise sessionPromise = new FuturePromise<>(); - http2Client.connect(new InetSocketAddress("localhost", proxyConnector.getLocalPort()), new Session.Listener.Adapter(), sessionPromise); + http2Client.connect(new InetSocketAddress("localhost", proxyConnector.getLocalPort()), new Session.Listener(), sessionPromise); Session session = sessionPromise.get(5, TimeUnit.SECONDS); String serverAddress = "localhost:" + serverConnector.getLocalPort(); MetaData.ConnectRequest connect = new MetaData.ConnectRequest(HttpScheme.HTTP, new AuthorityHttpField(serverAddress), null, HttpFields.EMPTY, null); @@ -528,7 +528,7 @@ public class ProxyWithDynamicTransportTest CountDownLatch tunnelLatch = new CountDownLatch(1); CountDownLatch responseLatch = new CountDownLatch(1); CountDownLatch resetLatch = new CountDownLatch(1); - session.newStream(frame, streamPromise, new Stream.Listener.Adapter() + session.newStream(frame, streamPromise, new Stream.Listener() { @Override public void onHeaders(Stream stream, HeadersFrame frame) diff --git a/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-http2-webapp/src/main/java/org/eclipse/jetty/test/webapp/HTTP1Servlet.java b/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-http2-webapp/src/main/java/org/eclipse/jetty/test/webapp/HTTP1Servlet.java index c7d6c662dcd..84eb26ca7c7 100644 --- a/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-http2-webapp/src/main/java/org/eclipse/jetty/test/webapp/HTTP1Servlet.java +++ b/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-http2-webapp/src/main/java/org/eclipse/jetty/test/webapp/HTTP1Servlet.java @@ -80,7 +80,7 @@ public class HTTP1Servlet extends HttpServlet String contextPath = request.getContextPath(); ServletOutputStream output = response.getOutputStream(); AsyncContext asyncContext = request.startAsync(); - http2Client.connect(sslContextFactory, new InetSocketAddress(host, port), new Session.Listener.Adapter(), new Promise() + http2Client.connect(sslContextFactory, new InetSocketAddress(host, port), new Session.Listener(), new Promise() { @Override public void succeeded(Session session) @@ -97,7 +97,7 @@ public class HTTP1Servlet extends HttpServlet response.setHeader("X-Failure", "stream"); asyncContext.complete(); } - }, new Stream.Listener.Adapter() + }, new Stream.Listener() { @Override public void onData(Stream stream, DataFrame frame, Callback callback) diff --git a/jetty-ee9/jetty-ee9-nested/src/test/java/org/eclipse/jetty/ee9/nested/AsyncContentProducerTest.java b/jetty-ee9/jetty-ee9-nested/src/test/java/org/eclipse/jetty/ee9/nested/AsyncContentProducerTest.java index 988ebdb19a0..08cbaa8c494 100644 --- a/jetty-ee9/jetty-ee9-nested/src/test/java/org/eclipse/jetty/ee9/nested/AsyncContentProducerTest.java +++ b/jetty-ee9/jetty-ee9-nested/src/test/java/org/eclipse/jetty/ee9/nested/AsyncContentProducerTest.java @@ -204,7 +204,7 @@ public class AsyncContentProducerTest public void testAsyncContentProducerInterceptorGeneratesError() { AtomicInteger contentReleasedCount = new AtomicInteger(); - ContentProducer contentProducer = new AsyncContentProducer(new ContentListHttpChannel(List.of(Content.Chunk.from(ByteBuffer.allocate(1), false, contentReleasedCount::incrementAndGet)), Content.Chunk.EOF)); + ContentProducer contentProducer = new AsyncContentProducer(new ContentListHttpChannel(List.of(Content.Chunk.from(ByteBuffer.allocate(1), false)), Content.Chunk.EOF)); try (AutoLock ignored = contentProducer.lock()) { contentProducer.setInterceptor(content -> Content.Chunk.from(new Throwable("testAsyncContentProducerInterceptorGeneratesError interceptor error"))); @@ -227,7 +227,7 @@ public class AsyncContentProducerTest public void testAsyncContentProducerInterceptorGeneratesEof() { AtomicInteger contentReleasedCount = new AtomicInteger(); - ContentProducer contentProducer = new AsyncContentProducer(new ContentListHttpChannel(List.of(Content.Chunk.from(ByteBuffer.allocate(1), false, contentReleasedCount::incrementAndGet)), Content.Chunk.from(new Throwable("should not reach this")))); + ContentProducer contentProducer = new AsyncContentProducer(new ContentListHttpChannel(List.of(Content.Chunk.from(ByteBuffer.allocate(1), false)), Content.Chunk.from(new Throwable("should not reach this")))); try (AutoLock ignored = contentProducer.lock()) { contentProducer.setInterceptor(content -> Content.Chunk.EOF); @@ -250,7 +250,7 @@ public class AsyncContentProducerTest public void testAsyncContentProducerInterceptorThrows() { AtomicInteger contentReleasedCount = new AtomicInteger(); - ContentProducer contentProducer = new AsyncContentProducer(new ContentListHttpChannel(List.of(Content.Chunk.from(ByteBuffer.allocate(1), false, contentReleasedCount::incrementAndGet)), Content.Chunk.EOF)); + ContentProducer contentProducer = new AsyncContentProducer(new ContentListHttpChannel(List.of(Content.Chunk.from(ByteBuffer.allocate(1), false)), Content.Chunk.EOF)); try (AutoLock ignored = contentProducer.lock()) { contentProducer.setInterceptor(content -> @@ -277,10 +277,10 @@ public class AsyncContentProducerTest { AtomicInteger contentReleasedCount = new AtomicInteger(); AtomicInteger interceptorContentReleasedCount = new AtomicInteger(); - ContentProducer contentProducer = new AsyncContentProducer(new ContentListHttpChannel(List.of(Content.Chunk.from(ByteBuffer.allocate(1), false, contentReleasedCount::incrementAndGet)), Content.Chunk.EOF)); + ContentProducer contentProducer = new AsyncContentProducer(new ContentListHttpChannel(List.of(Content.Chunk.from(ByteBuffer.allocate(1), false)), Content.Chunk.EOF)); try (AutoLock ignored = contentProducer.lock()) { - contentProducer.setInterceptor(content -> Content.Chunk.from(ByteBuffer.allocate(1), false, interceptorContentReleasedCount::incrementAndGet)); + contentProducer.setInterceptor(content -> Content.Chunk.from(ByteBuffer.allocate(1), false)); assertThat(contentProducer.isReady(), is(true)); @@ -302,7 +302,7 @@ public class AsyncContentProducerTest AtomicInteger contentReleasedCount = new AtomicInteger(); AtomicInteger specialContentInterceptedCount = new AtomicInteger(); AtomicInteger nullContentInterceptedCount = new AtomicInteger(); - ContentProducer contentProducer = new AsyncContentProducer(new ContentListHttpChannel(List.of(Content.Chunk.from(ByteBuffer.allocate(0), false, contentReleasedCount::incrementAndGet)), Content.Chunk.EOF)); + ContentProducer contentProducer = new AsyncContentProducer(new ContentListHttpChannel(List.of(Content.Chunk.from(ByteBuffer.allocate(0), false)), Content.Chunk.EOF)); try (AutoLock ignored = contentProducer.lock()) { contentProducer.setInterceptor(content -> diff --git a/jetty-ee9/jetty-ee9-nested/src/test/java/org/eclipse/jetty/ee9/nested/BlockingContentProducerTest.java b/jetty-ee9/jetty-ee9-nested/src/test/java/org/eclipse/jetty/ee9/nested/BlockingContentProducerTest.java index 5ca56b1060d..f98706061c2 100644 --- a/jetty-ee9/jetty-ee9-nested/src/test/java/org/eclipse/jetty/ee9/nested/BlockingContentProducerTest.java +++ b/jetty-ee9/jetty-ee9-nested/src/test/java/org/eclipse/jetty/ee9/nested/BlockingContentProducerTest.java @@ -193,7 +193,7 @@ public class BlockingContentProducerTest public void testBlockingContentProducerInterceptorGeneratesError() { AtomicInteger contentSucceededCount = new AtomicInteger(); - ContentProducer contentProducer = new BlockingContentProducer(new AsyncContentProducer(new StaticContentHttpChannel(Content.Chunk.from(ByteBuffer.allocate(1), false, contentSucceededCount::incrementAndGet)))); + ContentProducer contentProducer = new BlockingContentProducer(new AsyncContentProducer(new StaticContentHttpChannel(Content.Chunk.from(ByteBuffer.allocate(1), false)))); try (AutoLock ignored = contentProducer.lock()) { contentProducer.setInterceptor(content -> Content.Chunk.from(new Throwable("testBlockingContentProducerInterceptorGeneratesError interceptor error"))); @@ -215,7 +215,7 @@ public class BlockingContentProducerTest public void testBlockingContentProducerInterceptorGeneratesEof() { AtomicInteger contentSucceededCount = new AtomicInteger(); - ContentProducer contentProducer = new BlockingContentProducer(new AsyncContentProducer(new StaticContentHttpChannel(Content.Chunk.from(ByteBuffer.allocate(1), false, contentSucceededCount::incrementAndGet)))); + ContentProducer contentProducer = new BlockingContentProducer(new AsyncContentProducer(new StaticContentHttpChannel(Content.Chunk.from(ByteBuffer.allocate(1), false)))); try (AutoLock ignored = contentProducer.lock()) { contentProducer.setInterceptor(content -> Content.Chunk.EOF); @@ -237,7 +237,7 @@ public class BlockingContentProducerTest public void testBlockingContentProducerInterceptorThrows() { AtomicInteger contentFailedCount = new AtomicInteger(); - ContentProducer contentProducer = new BlockingContentProducer(new AsyncContentProducer(new StaticContentHttpChannel(Content.Chunk.from(ByteBuffer.allocate(1), false, contentFailedCount::incrementAndGet)))); + ContentProducer contentProducer = new BlockingContentProducer(new AsyncContentProducer(new StaticContentHttpChannel(Content.Chunk.from(ByteBuffer.allocate(1), false)))); try (AutoLock ignored = contentProducer.lock()) { contentProducer.setInterceptor(content -> @@ -262,7 +262,7 @@ public class BlockingContentProducerTest public void testBlockingContentProducerInterceptorDoesNotConsume() { AtomicInteger contentFailedCount = new AtomicInteger(); - ContentProducer contentProducer = new BlockingContentProducer(new AsyncContentProducer(new StaticContentHttpChannel(Content.Chunk.from(ByteBuffer.allocate(1), false, contentFailedCount::incrementAndGet)))); + ContentProducer contentProducer = new BlockingContentProducer(new AsyncContentProducer(new StaticContentHttpChannel(Content.Chunk.from(ByteBuffer.allocate(1), false)))); try (AutoLock ignored = contentProducer.lock()) { contentProducer.setInterceptor(content -> null); @@ -284,7 +284,7 @@ public class BlockingContentProducerTest AtomicInteger contentSucceededCount = new AtomicInteger(); AtomicInteger specialContentInterceptedCount = new AtomicInteger(); AtomicInteger nullContentInterceptedCount = new AtomicInteger(); - ContentProducer contentProducer = new BlockingContentProducer(new AsyncContentProducer(new StaticContentHttpChannel(Content.Chunk.from(ByteBuffer.allocate(0), false, contentSucceededCount::incrementAndGet)))); + ContentProducer contentProducer = new BlockingContentProducer(new AsyncContentProducer(new StaticContentHttpChannel(Content.Chunk.from(ByteBuffer.allocate(0), false)))); try (AutoLock ignored = contentProducer.lock()) { contentProducer.setInterceptor(content -> diff --git a/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-http-client-transport/src/test/java/org/eclipse/jetty/ee9/http/client/ProxyWithDynamicTransportTest.java b/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-http-client-transport/src/test/java/org/eclipse/jetty/ee9/http/client/ProxyWithDynamicTransportTest.java index 2036b1e4a94..308145b90eb 100644 --- a/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-http-client-transport/src/test/java/org/eclipse/jetty/ee9/http/client/ProxyWithDynamicTransportTest.java +++ b/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-http-client-transport/src/test/java/org/eclipse/jetty/ee9/http/client/ProxyWithDynamicTransportTest.java @@ -434,7 +434,7 @@ public class ProxyWithDynamicTransportTest startClient(); FuturePromise sessionPromise = new FuturePromise<>(); - http2Client.connect(new InetSocketAddress("localhost", proxyConnector.getLocalPort()), new Session.Listener.Adapter(), sessionPromise); + http2Client.connect(new InetSocketAddress("localhost", proxyConnector.getLocalPort()), new Session.Listener(), sessionPromise); Session session = sessionPromise.get(5, TimeUnit.SECONDS); String serverAddress = "localhost:" + serverConnector.getLocalPort(); MetaData.ConnectRequest connect = new MetaData.ConnectRequest(HttpScheme.HTTP, new AuthorityHttpField(serverAddress), null, HttpFields.EMPTY, null); @@ -442,7 +442,7 @@ public class ProxyWithDynamicTransportTest FuturePromise streamPromise = new FuturePromise<>(); CountDownLatch tunnelLatch = new CountDownLatch(1); CountDownLatch responseLatch = new CountDownLatch(1); - session.newStream(frame, streamPromise, new Stream.Listener.Adapter() + session.newStream(frame, streamPromise, new Stream.Listener() { @Override public void onHeaders(Stream stream, HeadersFrame frame) @@ -519,7 +519,7 @@ public class ProxyWithDynamicTransportTest ((HTTP2CServerConnectionFactory)h2c).setStreamIdleTimeout(streamIdleTimeout); FuturePromise sessionPromise = new FuturePromise<>(); - http2Client.connect(new InetSocketAddress("localhost", proxyConnector.getLocalPort()), new Session.Listener.Adapter(), sessionPromise); + http2Client.connect(new InetSocketAddress("localhost", proxyConnector.getLocalPort()), new Session.Listener(), sessionPromise); Session session = sessionPromise.get(5, TimeUnit.SECONDS); String serverAddress = "localhost:" + serverConnector.getLocalPort(); MetaData.ConnectRequest connect = new MetaData.ConnectRequest(HttpScheme.HTTP, new AuthorityHttpField(serverAddress), null, HttpFields.EMPTY, null); @@ -528,7 +528,7 @@ public class ProxyWithDynamicTransportTest CountDownLatch tunnelLatch = new CountDownLatch(1); CountDownLatch responseLatch = new CountDownLatch(1); CountDownLatch resetLatch = new CountDownLatch(1); - session.newStream(frame, streamPromise, new Stream.Listener.Adapter() + session.newStream(frame, streamPromise, new Stream.Listener() { @Override public void onHeaders(Stream stream, HeadersFrame frame) From 0d75a87886fa96ff08bf9a5da1cc458885644bcf Mon Sep 17 00:00:00 2001 From: Joakim Erdfelt Date: Mon, 25 Jul 2022 09:59:14 -0500 Subject: [PATCH 4/4] Adding comment about usage --- .../src/main/java/org/eclipse/jetty/server/ResourceService.java | 1 + .../java/org/eclipse/jetty/ee10/servlet/DefaultServlet.java | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/ResourceService.java b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/ResourceService.java index fd3e0efbd2e..c242f7b5d23 100644 --- a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/ResourceService.java +++ b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/ResourceService.java @@ -385,6 +385,7 @@ public class ResourceService uri.path(uri.getCanonicalPath() + "/"); uri.param(parameter); response.getHeaders().putLongField(HttpHeader.CONTENT_LENGTH, 0); + // TODO: can writeRedirect (override) also work for WelcomeActionType.REDIRECT? Response.sendRedirect(request, response, callback, uri.getPathQuery()); return; } diff --git a/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/DefaultServlet.java b/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/DefaultServlet.java index 8ebde8698a1..ee1700b58b2 100644 --- a/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/DefaultServlet.java +++ b/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/DefaultServlet.java @@ -983,7 +983,7 @@ public class DefaultServlet extends HttpServlet welcome = URIUtil.addPaths(servletPath, welcome); response.setContentLength(0); - response.sendRedirect(welcome); + response.sendRedirect(welcome); // Call API (might be overridden) callback.succeeded(); } }