From 97f9c1d0fbf1b3c2ccd6488c35b63e7265e6c6eb Mon Sep 17 00:00:00 2001 From: Ludovic Orban Date: Mon, 10 Jun 2024 13:36:31 +0200 Subject: [PATCH 01/22] fix missing notifyRemoteAsyncErrors http config wiring Signed-off-by: Ludovic Orban --- .../org/eclipse/jetty/ee10/servlet/ServletChannelState.java | 3 ++- .../java/org/eclipse/jetty/ee9/nested/HttpChannelState.java | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ServletChannelState.java b/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ServletChannelState.java index 0c419cba5598..46f1fe1050bb 100644 --- a/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ServletChannelState.java +++ b/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ServletChannelState.java @@ -572,7 +572,8 @@ public void startAsync(AsyncContextEvent event) if (!_failureListener) { _failureListener = true; - _servletChannel.getRequest().addFailureListener(this::asyncError); + if (_servletChannel.getHttpConfiguration().isNotifyRemoteAsyncErrors()) + _servletChannel.getRequest().addFailureListener(this::asyncError); } _requestState = RequestState.ASYNC; _event = event; diff --git a/jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/HttpChannelState.java b/jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/HttpChannelState.java index 7fdc4184b068..24659120a531 100644 --- a/jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/HttpChannelState.java +++ b/jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/HttpChannelState.java @@ -534,7 +534,8 @@ public void startAsync(AsyncContextEvent event) if (!_failureListener) { _failureListener = true; - getHttpChannel().getCoreRequest().addFailureListener(this::asyncError); + if (_channel.getHttpConfiguration().isNotifyRemoteAsyncErrors()) + getHttpChannel().getCoreRequest().addFailureListener(this::asyncError); } _requestState = RequestState.ASYNC; _event = event; From f10b2458920d09951f766a2dd7df9066709f97be Mon Sep 17 00:00:00 2001 From: Ludovic Orban Date: Tue, 11 Jun 2024 12:13:28 +0200 Subject: [PATCH 02/22] add tests Signed-off-by: Ludovic Orban --- .../jetty/http2/tests/AbstractTest.java | 7 +- .../jetty/http2/tests/AsyncIOTest.java | 71 +++++++++++++++ .../jetty/http2/tests/AsyncServletTest.java | 2 + .../server/internal/HttpChannelState.java | 2 +- .../jetty/ee10/servlet/HttpOutput.java | 4 +- .../test/client/transport/AbstractTest.java | 35 +++++++- .../client/transport/AsyncIOServletTest.java | 87 ++++++++++++++++++ .../test/client/transport/AbstractTest.java | 35 +++++++- .../client/transport/AsyncIOServletTest.java | 90 +++++++++++++++++++ 9 files changed, 328 insertions(+), 5 deletions(-) diff --git a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/AbstractTest.java b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/AbstractTest.java index 90bc346b4ce9..9a5f944f14da 100644 --- a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/AbstractTest.java +++ b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/AbstractTest.java @@ -58,7 +58,12 @@ public class AbstractTest protected void start(Handler handler) throws Exception { - HTTP2CServerConnectionFactory connectionFactory = new HTTP2CServerConnectionFactory(new HttpConfiguration()); + start(handler, new HttpConfiguration()); + } + + protected void start(Handler handler, HttpConfiguration httpConfiguration) throws Exception + { + HTTP2CServerConnectionFactory connectionFactory = new HTTP2CServerConnectionFactory(httpConfiguration); connectionFactory.setInitialSessionRecvWindow(FlowControlStrategy.DEFAULT_WINDOW_SIZE); connectionFactory.setInitialStreamRecvWindow(FlowControlStrategy.DEFAULT_WINDOW_SIZE); prepareServer(connectionFactory); 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 430d721bcf7c..0be119895252 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 @@ -17,22 +17,33 @@ import java.nio.ByteBuffer; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.MetaData; +import org.eclipse.jetty.http2.ErrorCode; 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.io.EofException; import org.eclipse.jetty.server.Handler; +import org.eclipse.jetty.server.HttpConfiguration; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.Response; +import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.FuturePromise; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import static org.awaitility.Awaitility.await; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.nullValue; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; @@ -241,6 +252,66 @@ public void onClosed(Stream stream) */ } + @ParameterizedTest + @ValueSource(booleans = {true, false}) + public void testClientResetWithoutRemoteErrorNotification(boolean notify) throws Exception + { + CountDownLatch latch = new CountDownLatch(1); + AtomicReference responseRef = new AtomicReference<>(); + AtomicReference failureRef = new AtomicReference<>(); + HttpConfiguration httpConfiguration = new HttpConfiguration(); + httpConfiguration.setNotifyRemoteAsyncErrors(notify); + start(new Handler.Abstract() { + @Override + public boolean handle(Request request, Response response, Callback callback) + { + request.addFailureListener(failureRef::set); + responseRef.set(response); + latch.countDown(); + return true; + } + }, httpConfiguration); + + Session session = newClientSession(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, null); + Stream stream = promise.get(5, TimeUnit.SECONDS); + + // Wait for the server to be idle. + assertTrue(latch.await(5, TimeUnit.SECONDS)); + sleep(500); + + stream.reset(new ResetFrame(stream.getId(), ErrorCode.CANCEL_STREAM_ERROR.code), Callback.NOOP); + + if (notify) + // Wait for the reset to be notified to the failure listener. + await().atMost(5, TimeUnit.SECONDS).until(failureRef::get, instanceOf(EofException.class)); + else + // Wait for the reset to NOT be notified to the failure listener. + await().atMost(5, TimeUnit.SECONDS).during(1, TimeUnit.SECONDS).until(failureRef::get, nullValue()); + + // Assert that writing to the response fails. + var cb = new Callback() + { + private Throwable failure = null; + + @Override + public void failed(Throwable x) + { + failure = x; + } + + Throwable failure() + { + return failure; + } + }; + responseRef.get().write(true, BufferUtil.EMPTY_BUFFER, cb); + await().atMost(5, TimeUnit.SECONDS).until(cb::failure, instanceOf(EofException.class)); + } + private static void sleep(long ms) throws InterruptedIOException { try 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 328e21571962..898987c72f7e 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 @@ -154,6 +154,7 @@ public void test() // assertTrue(clientLatch.await(2 * idleTimeout, TimeUnit.MILLISECONDS)); // } // + // @Test // public void testStartAsyncThenClientResetWithoutRemoteErrorNotification() throws Exception // { @@ -206,6 +207,7 @@ public void test() // output.flush(); // }); // } + // // @Test // public void testStartAsyncThenServerSessionIdleTimeout() throws Exception diff --git a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/internal/HttpChannelState.java b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/internal/HttpChannelState.java index dc2b3be55d62..7d7ab8cc380b 100644 --- a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/internal/HttpChannelState.java +++ b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/internal/HttpChannelState.java @@ -437,7 +437,7 @@ public Runnable onFailure(Throwable x) // Notify the failure listeners only once. Consumer onFailure = _onFailure; _onFailure = null; - Runnable invokeOnFailureListeners = onFailure == null ? null : () -> + Runnable invokeOnFailureListeners = onFailure == null || !getHttpConfiguration().isNotifyRemoteAsyncErrors() ? null : () -> { try { diff --git a/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/HttpOutput.java b/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/HttpOutput.java index 419fc0d9ebf5..b9e6270401d7 100644 --- a/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/HttpOutput.java +++ b/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/HttpOutput.java @@ -676,7 +676,9 @@ public void flush() throws IOException catch (Throwable t) { onWriteComplete(false, t); - throw t; + if (t instanceof IOException) + throw t; + throw new IOException(t); } } } diff --git a/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-client-transports/src/test/java/org/eclipse/jetty/ee10/test/client/transport/AbstractTest.java b/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-client-transports/src/test/java/org/eclipse/jetty/ee10/test/client/transport/AbstractTest.java index 806ada98f318..bf137ebe316f 100644 --- a/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-client-transports/src/test/java/org/eclipse/jetty/ee10/test/client/transport/AbstractTest.java +++ b/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-client-transports/src/test/java/org/eclipse/jetty/ee10/test/client/transport/AbstractTest.java @@ -14,6 +14,7 @@ package org.eclipse.jetty.ee10.test.client.transport; import java.io.InputStream; +import java.net.InetSocketAddress; import java.net.URI; import java.nio.file.Files; import java.nio.file.Path; @@ -21,6 +22,7 @@ import java.util.Collection; import java.util.EnumSet; import java.util.List; +import java.util.concurrent.TimeUnit; import java.util.function.Consumer; import jakarta.servlet.http.HttpServlet; @@ -32,7 +34,13 @@ import org.eclipse.jetty.ee10.servlet.ServletHolder; import org.eclipse.jetty.fcgi.client.transport.HttpClientTransportOverFCGI; import org.eclipse.jetty.fcgi.server.ServerFCGIConnectionFactory; +import org.eclipse.jetty.http.HostPortHttpField; +import org.eclipse.jetty.http.HttpFields; +import org.eclipse.jetty.http.HttpScheme; +import org.eclipse.jetty.http.HttpVersion; +import org.eclipse.jetty.http.MetaData; import org.eclipse.jetty.http2.HTTP2Cipher; +import org.eclipse.jetty.http2.api.Session; import org.eclipse.jetty.http2.client.HTTP2Client; import org.eclipse.jetty.http2.client.transport.HttpClientTransportOverHTTP2; import org.eclipse.jetty.http2.server.AbstractHTTP2ServerConnectionFactory; @@ -58,6 +66,7 @@ import org.eclipse.jetty.server.SslConnectionFactory; import org.eclipse.jetty.toolchain.test.jupiter.WorkDir; import org.eclipse.jetty.toolchain.test.jupiter.WorkDirExtension; +import org.eclipse.jetty.util.FuturePromise; import org.eclipse.jetty.util.SocketAddressResolver; import org.eclipse.jetty.util.component.LifeCycle; import org.eclipse.jetty.util.ssl.SslContextFactory; @@ -79,6 +88,7 @@ public class AbstractTest protected AbstractConnector connector; protected ServletContextHandler servletContextHandler; protected HttpClient client; + protected HTTP2Client http2Client; public static Collection transports() { @@ -189,6 +199,29 @@ protected void startClient(Transport transport, Consumer consumer) t client.start(); } + protected Session newHttp2ClientSession(Session.Listener listener) throws Exception + { + String host = "localhost"; + int port = ((NetworkConnector)connector).getLocalPort(); + InetSocketAddress address = new InetSocketAddress(host, port); + FuturePromise promise = new FuturePromise<>(); + http2Client.connect(address, listener, promise); + return promise.get(5, TimeUnit.SECONDS); + } + + protected MetaData.Request newRequest(String method, HttpFields fields) + { + return newRequest(method, "/", fields); + } + + protected MetaData.Request newRequest(String method, String path, HttpFields fields) + { + String host = "localhost"; + int port = ((NetworkConnector)connector).getLocalPort(); + String authority = host + ":" + port; + return new MetaData.Request(method, HttpScheme.HTTP.asString(), new HostPortHttpField(authority), path, HttpVersion.HTTP_2, fields, -1); + } + public AbstractConnector newConnector(Transport transport, Server server) { return switch (transport) @@ -262,7 +295,7 @@ protected HttpClientTransport newHttpClientTransport(Transport transport) throws ClientConnector clientConnector = new ClientConnector(); clientConnector.setSelectors(1); clientConnector.setSslContextFactory(newSslContextFactoryClient()); - HTTP2Client http2Client = new HTTP2Client(clientConnector); + http2Client = new HTTP2Client(clientConnector); yield new HttpClientTransportOverHTTP2(http2Client); } case H3 -> diff --git a/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-client-transports/src/test/java/org/eclipse/jetty/ee10/test/client/transport/AsyncIOServletTest.java b/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-client-transports/src/test/java/org/eclipse/jetty/ee10/test/client/transport/AsyncIOServletTest.java index 41472d1fd7c0..642bca937a3e 100644 --- a/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-client-transports/src/test/java/org/eclipse/jetty/ee10/test/client/transport/AsyncIOServletTest.java +++ b/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-client-transports/src/test/java/org/eclipse/jetty/ee10/test/client/transport/AsyncIOServletTest.java @@ -34,6 +34,8 @@ import java.util.concurrent.atomic.AtomicReference; import jakarta.servlet.AsyncContext; +import jakarta.servlet.AsyncEvent; +import jakarta.servlet.AsyncListener; import jakarta.servlet.DispatcherType; import jakarta.servlet.ReadListener; import jakarta.servlet.ServletInputStream; @@ -54,13 +56,19 @@ import org.eclipse.jetty.client.StringRequestContent; import org.eclipse.jetty.client.transport.internal.HttpConnectionOverHTTP; import org.eclipse.jetty.ee10.servlet.HttpOutput; +import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpHeaderValue; import org.eclipse.jetty.http.HttpMethod; import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.http.MetaData; +import org.eclipse.jetty.http2.ErrorCode; import org.eclipse.jetty.http2.HTTP2Session; import org.eclipse.jetty.http2.api.Session; +import org.eclipse.jetty.http2.api.Stream; import org.eclipse.jetty.http2.client.transport.internal.HttpConnectionOverHTTP2; +import org.eclipse.jetty.http2.frames.HeadersFrame; +import org.eclipse.jetty.http2.frames.ResetFrame; import org.eclipse.jetty.io.Connection; import org.eclipse.jetty.io.EofException; import org.eclipse.jetty.logging.StacklessLogging; @@ -76,6 +84,7 @@ import org.junit.jupiter.api.Disabled; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.params.provider.ValueSource; import static org.awaitility.Awaitility.await; import static org.hamcrest.MatcherAssert.assertThat; @@ -83,6 +92,7 @@ import static org.hamcrest.Matchers.empty; import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.nullValue; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -1534,6 +1544,83 @@ public void onError(Throwable x) assertEquals(HttpStatus.NO_CONTENT_204, response.getStatus()); } + @ParameterizedTest + @ValueSource(booleans = {true, false}) + public void testStartAsyncThenClientResetWithoutRemoteErrorNotification(boolean notify) throws Exception + { + httpConfig.setNotifyRemoteAsyncErrors(notify); + + AtomicReference errorAsyncEventRef = new AtomicReference<>(); + AtomicReference responseRef = new AtomicReference<>(); + CountDownLatch latch = new CountDownLatch(1); + start(Transport.H2C, new HttpServlet() { + @Override + protected void service(HttpServletRequest request, HttpServletResponse response) + { + AsyncContext asyncContext = request.startAsync(); + asyncContext.addListener(new AsyncListener() { + @Override + public void onComplete(AsyncEvent event) throws IOException + { + } + + @Override + public void onTimeout(AsyncEvent event) throws IOException + { + } + + @Override + public void onError(AsyncEvent event) throws IOException + { + errorAsyncEventRef.set(event); + } + + @Override + public void onStartAsync(AsyncEvent event) throws IOException + { + } + }); + asyncContext.setTimeout(0); + responseRef.set(response); + latch.countDown(); + } + }); + + Session session = newHttp2ClientSession(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, null); + Stream stream = promise.get(5, TimeUnit.SECONDS); + + // Wait for the server to be in ASYNC_WAIT. + assertTrue(latch.await(5, TimeUnit.SECONDS)); + sleep(500); + + stream.reset(new ResetFrame(stream.getId(), ErrorCode.CANCEL_STREAM_ERROR.code), Callback.NOOP); + + if (notify) + // Wait for the reset to be notified to the async context listener. + await().atMost(5, TimeUnit.SECONDS).until(() -> + { + AsyncEvent asyncEvent = errorAsyncEventRef.get(); + return asyncEvent == null ? null : asyncEvent.getThrowable(); + }, instanceOf(EofException.class)); + else + // Wait for the reset to NOT be notified to the failure listener. + await().atMost(5, TimeUnit.SECONDS).during(1, TimeUnit.SECONDS).until(errorAsyncEventRef::get, nullValue()); + + ServletOutputStream output = responseRef.get().getOutputStream(); + + assertThrows(IOException.class, + () -> + { + // Large writes or explicit flush() must + // fail because the stream has been reset. + output.flush(); + }); + } + private static class Listener implements ReadListener, WriteListener { private final Executor executor = Executors.newFixedThreadPool(32); diff --git a/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-client-transports/src/test/java/org/eclipse/jetty/ee9/test/client/transport/AbstractTest.java b/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-client-transports/src/test/java/org/eclipse/jetty/ee9/test/client/transport/AbstractTest.java index 63efe5877d06..e7e3b81d429e 100644 --- a/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-client-transports/src/test/java/org/eclipse/jetty/ee9/test/client/transport/AbstractTest.java +++ b/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-client-transports/src/test/java/org/eclipse/jetty/ee9/test/client/transport/AbstractTest.java @@ -15,6 +15,7 @@ import java.io.IOException; import java.io.InputStream; +import java.net.InetSocketAddress; import java.net.URI; import java.nio.file.Files; import java.nio.file.Path; @@ -26,6 +27,7 @@ import java.util.EnumSet; import java.util.List; import java.util.Objects; +import java.util.concurrent.TimeUnit; import jakarta.servlet.http.HttpServlet; import org.eclipse.jetty.alpn.server.ALPNServerConnectionFactory; @@ -36,7 +38,13 @@ import org.eclipse.jetty.ee9.servlet.ServletHolder; import org.eclipse.jetty.fcgi.client.transport.HttpClientTransportOverFCGI; import org.eclipse.jetty.fcgi.server.ServerFCGIConnectionFactory; +import org.eclipse.jetty.http.HostPortHttpField; +import org.eclipse.jetty.http.HttpFields; +import org.eclipse.jetty.http.HttpScheme; +import org.eclipse.jetty.http.HttpVersion; +import org.eclipse.jetty.http.MetaData; import org.eclipse.jetty.http2.HTTP2Cipher; +import org.eclipse.jetty.http2.api.Session; import org.eclipse.jetty.http2.client.HTTP2Client; import org.eclipse.jetty.http2.client.transport.HttpClientTransportOverHTTP2; import org.eclipse.jetty.http2.server.AbstractHTTP2ServerConnectionFactory; @@ -62,6 +70,7 @@ import org.eclipse.jetty.server.SslConnectionFactory; import org.eclipse.jetty.toolchain.test.jupiter.WorkDir; import org.eclipse.jetty.toolchain.test.jupiter.WorkDirExtension; +import org.eclipse.jetty.util.FuturePromise; import org.eclipse.jetty.util.SocketAddressResolver; import org.eclipse.jetty.util.component.LifeCycle; import org.eclipse.jetty.util.ssl.SslContextFactory; @@ -83,6 +92,7 @@ public class AbstractTest protected AbstractConnector connector; protected ServletContextHandler servletContextHandler; protected HttpClient client; + protected HTTP2Client http2Client; public static Collection transports() { @@ -196,6 +206,29 @@ protected void startClient(Transport transport) throws Exception client.start(); } + protected Session newHttp2ClientSession(Session.Listener listener) throws Exception + { + String host = "localhost"; + int port = ((NetworkConnector)connector).getLocalPort(); + InetSocketAddress address = new InetSocketAddress(host, port); + FuturePromise promise = new FuturePromise<>(); + http2Client.connect(address, listener, promise); + return promise.get(5, TimeUnit.SECONDS); + } + + protected MetaData.Request newRequest(String method, HttpFields fields) + { + return newRequest(method, "/", fields); + } + + protected MetaData.Request newRequest(String method, String path, HttpFields fields) + { + String host = "localhost"; + int port = ((NetworkConnector)connector).getLocalPort(); + String authority = host + ":" + port; + return new MetaData.Request(method, HttpScheme.HTTP.asString(), new HostPortHttpField(authority), path, HttpVersion.HTTP_2, fields, -1); + } + public AbstractConnector newConnector(Transport transport, Server server) { return switch (transport) @@ -268,7 +301,7 @@ protected HttpClientTransport newHttpClientTransport(Transport transport) throws ClientConnector clientConnector = new ClientConnector(); clientConnector.setSelectors(1); clientConnector.setSslContextFactory(newSslContextFactoryClient()); - HTTP2Client http2Client = new HTTP2Client(clientConnector); + http2Client = new HTTP2Client(clientConnector); yield new HttpClientTransportOverHTTP2(http2Client); } case H3 -> diff --git a/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-client-transports/src/test/java/org/eclipse/jetty/ee9/test/client/transport/AsyncIOServletTest.java b/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-client-transports/src/test/java/org/eclipse/jetty/ee9/test/client/transport/AsyncIOServletTest.java index 4a10ca2b01fe..08cfcbaec522 100644 --- a/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-client-transports/src/test/java/org/eclipse/jetty/ee9/test/client/transport/AsyncIOServletTest.java +++ b/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-client-transports/src/test/java/org/eclipse/jetty/ee9/test/client/transport/AsyncIOServletTest.java @@ -35,6 +35,8 @@ import java.util.concurrent.atomic.AtomicReference; import jakarta.servlet.AsyncContext; +import jakarta.servlet.AsyncEvent; +import jakarta.servlet.AsyncListener; import jakarta.servlet.DispatcherType; import jakarta.servlet.ReadListener; import jakarta.servlet.ServletInputStream; @@ -56,13 +58,19 @@ import org.eclipse.jetty.ee9.nested.ContextHandler; import org.eclipse.jetty.ee9.nested.HttpInput; import org.eclipse.jetty.ee9.nested.HttpOutput; +import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpHeaderValue; import org.eclipse.jetty.http.HttpMethod; import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.http.MetaData; +import org.eclipse.jetty.http2.ErrorCode; import org.eclipse.jetty.http2.HTTP2Session; import org.eclipse.jetty.http2.api.Session; +import org.eclipse.jetty.http2.api.Stream; import org.eclipse.jetty.http2.client.transport.internal.HttpConnectionOverHTTP2; +import org.eclipse.jetty.http2.frames.HeadersFrame; +import org.eclipse.jetty.http2.frames.ResetFrame; import org.eclipse.jetty.io.Connection; import org.eclipse.jetty.io.EofException; import org.eclipse.jetty.logging.StacklessLogging; @@ -76,6 +84,7 @@ import org.junit.jupiter.api.Disabled; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.params.provider.ValueSource; import static java.nio.ByteBuffer.wrap; import static org.awaitility.Awaitility.await; @@ -85,6 +94,7 @@ import static org.hamcrest.Matchers.empty; import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.nullValue; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -1861,6 +1871,86 @@ public void onError(Throwable x) assertEquals(1, allDataReadCount.get()); } + @ParameterizedTest + @ValueSource(booleans = {true, false}) + public void testStartAsyncThenClientResetWithoutRemoteErrorNotification(boolean notify) throws Exception + { + httpConfig.setNotifyRemoteAsyncErrors(notify); + + AtomicReference errorAsyncEventRef = new AtomicReference<>(); + AtomicReference responseRef = new AtomicReference<>(); + CountDownLatch latch = new CountDownLatch(1); + start(Transport.H2C, new HttpServlet() { + @Override + protected void service(HttpServletRequest request, HttpServletResponse response) + { + AsyncContext asyncContext = request.startAsync(); + asyncContext.addListener(new AsyncListener() { + @Override + public void onComplete(AsyncEvent event) throws IOException + { + } + + @Override + public void onTimeout(AsyncEvent event) throws IOException + { + } + + @Override + public void onError(AsyncEvent event) throws IOException + { + errorAsyncEventRef.set(event); + } + + @Override + public void onStartAsync(AsyncEvent event) throws IOException + { + } + }); + asyncContext.setTimeout(0); + responseRef.set(response); + latch.countDown(); + } + }); + + Session session = newHttp2ClientSession(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, null); + Stream stream = promise.get(5, TimeUnit.SECONDS); + + // Wait for the server to be in ASYNC_WAIT. + assertTrue(latch.await(5, TimeUnit.SECONDS)); + sleep(500); + + stream.reset(new ResetFrame(stream.getId(), ErrorCode.CANCEL_STREAM_ERROR.code), Callback.NOOP); + + if (notify) + // Wait for the reset to be notified to the async context listener. + await().atMost(5, TimeUnit.SECONDS).until(() -> + { + AsyncEvent asyncEvent = errorAsyncEventRef.get(); + return asyncEvent == null ? null : asyncEvent.getThrowable(); + }, instanceOf(EofException.class)); + else + // Wait for the reset to NOT be notified to the failure listener. + await().atMost(5, TimeUnit.SECONDS).during(1, TimeUnit.SECONDS).until(errorAsyncEventRef::get, nullValue()); + + ServletOutputStream output = responseRef.get().getOutputStream(); + + if (notify) + output.flush(); + else + assertThrows(IOException.class, + () -> + { + // Large writes or explicit flush() must + // fail because the stream has been reset. + output.flush(); + }); + } + private static class Listener implements ReadListener, WriteListener { private final Executor executor = Executors.newFixedThreadPool(32); From 294357716c51d8495353c7f4fda5b221840daeea Mon Sep 17 00:00:00 2001 From: Ludovic Orban Date: Tue, 11 Jun 2024 12:14:48 +0200 Subject: [PATCH 03/22] remove ported test Signed-off-by: Ludovic Orban --- .../jetty/http2/tests/AsyncServletTest.java | 54 ------------------- 1 file changed, 54 deletions(-) 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 898987c72f7e..5ca981b723b3 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 @@ -154,60 +154,6 @@ public void test() // assertTrue(clientLatch.await(2 * idleTimeout, TimeUnit.MILLISECONDS)); // } // - -// @Test -// public void testStartAsyncThenClientResetWithoutRemoteErrorNotification() throws Exception -// { -// HttpConfiguration httpConfiguration = new HttpConfiguration(); -// httpConfiguration.setNotifyRemoteAsyncErrors(false); -// prepareServer(new HTTP2ServerConnectionFactory(httpConfiguration)); -// ServletContextHandler context = new ServletContextHandler(server, "/"); -// AtomicReference asyncContextRef = new AtomicReference<>(); -// CountDownLatch latch = new CountDownLatch(1); -// context.addServlet(new ServletHolder(new HttpServlet() -// { -// @Override -// protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException -// { -// AsyncContext asyncContext = request.startAsync(); -// asyncContext.setTimeout(0); -// asyncContextRef.set(asyncContext); -// latch.countDown(); -// } -// }), servletPath + "/*"); -// server.start(); -// -// prepareClient(); -// client.start(); -// 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, null); -// Stream stream = promise.get(5, TimeUnit.SECONDS); -// -// // Wait for the server to be in ASYNC_WAIT. -// assertTrue(latch.await(5, TimeUnit.SECONDS)); -// sleep(500); -// -// stream.reset(new ResetFrame(stream.getId(), ErrorCode.CANCEL_STREAM_ERROR.code), Callback.NOOP); -// -// // Wait for the reset to be processed by the server. -// sleep(500); -// -// AsyncContext asyncContext = asyncContextRef.get(); -// ServletResponse response = asyncContext.getResponse(); -// ServletOutputStream output = response.getOutputStream(); -// -// assertThrows(IOException.class, -// () -> -// { -// // Large writes or explicit flush() must -// // fail because the stream has been reset. -// output.flush(); -// }); -// } - // // @Test // public void testStartAsyncThenServerSessionIdleTimeout() throws Exception From 9fbae2a8e33b7cc50cccccad5314581e53c04ebf Mon Sep 17 00:00:00 2001 From: Ludovic Orban Date: Tue, 11 Jun 2024 14:33:01 +0200 Subject: [PATCH 04/22] fix checkstyle Signed-off-by: Ludovic Orban --- .../org/eclipse/jetty/http2/tests/AsyncIOTest.java | 3 ++- .../test/client/transport/AsyncIOServletTest.java | 14 ++++++++------ .../test/client/transport/AsyncIOServletTest.java | 14 ++++++++------ 3 files changed, 18 insertions(+), 13 deletions(-) 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 0be119895252..0c45757bdb21 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 @@ -261,7 +261,8 @@ public void testClientResetWithoutRemoteErrorNotification(boolean notify) throws AtomicReference failureRef = new AtomicReference<>(); HttpConfiguration httpConfiguration = new HttpConfiguration(); httpConfiguration.setNotifyRemoteAsyncErrors(notify); - start(new Handler.Abstract() { + start(new Handler.Abstract() + { @Override public boolean handle(Request request, Response response, Callback callback) { diff --git a/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-client-transports/src/test/java/org/eclipse/jetty/ee10/test/client/transport/AsyncIOServletTest.java b/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-client-transports/src/test/java/org/eclipse/jetty/ee10/test/client/transport/AsyncIOServletTest.java index 642bca937a3e..34562151bae1 100644 --- a/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-client-transports/src/test/java/org/eclipse/jetty/ee10/test/client/transport/AsyncIOServletTest.java +++ b/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-client-transports/src/test/java/org/eclipse/jetty/ee10/test/client/transport/AsyncIOServletTest.java @@ -1553,30 +1553,32 @@ public void testStartAsyncThenClientResetWithoutRemoteErrorNotification(boolean AtomicReference errorAsyncEventRef = new AtomicReference<>(); AtomicReference responseRef = new AtomicReference<>(); CountDownLatch latch = new CountDownLatch(1); - start(Transport.H2C, new HttpServlet() { + start(Transport.H2C, new HttpServlet() + { @Override protected void service(HttpServletRequest request, HttpServletResponse response) { AsyncContext asyncContext = request.startAsync(); - asyncContext.addListener(new AsyncListener() { + asyncContext.addListener(new AsyncListener() + { @Override - public void onComplete(AsyncEvent event) throws IOException + public void onComplete(AsyncEvent event) { } @Override - public void onTimeout(AsyncEvent event) throws IOException + public void onTimeout(AsyncEvent event) { } @Override - public void onError(AsyncEvent event) throws IOException + public void onError(AsyncEvent event) { errorAsyncEventRef.set(event); } @Override - public void onStartAsync(AsyncEvent event) throws IOException + public void onStartAsync(AsyncEvent event) { } }); diff --git a/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-client-transports/src/test/java/org/eclipse/jetty/ee9/test/client/transport/AsyncIOServletTest.java b/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-client-transports/src/test/java/org/eclipse/jetty/ee9/test/client/transport/AsyncIOServletTest.java index 08cfcbaec522..7ebafca10ae6 100644 --- a/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-client-transports/src/test/java/org/eclipse/jetty/ee9/test/client/transport/AsyncIOServletTest.java +++ b/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-client-transports/src/test/java/org/eclipse/jetty/ee9/test/client/transport/AsyncIOServletTest.java @@ -1880,30 +1880,32 @@ public void testStartAsyncThenClientResetWithoutRemoteErrorNotification(boolean AtomicReference errorAsyncEventRef = new AtomicReference<>(); AtomicReference responseRef = new AtomicReference<>(); CountDownLatch latch = new CountDownLatch(1); - start(Transport.H2C, new HttpServlet() { + start(Transport.H2C, new HttpServlet() + { @Override protected void service(HttpServletRequest request, HttpServletResponse response) { AsyncContext asyncContext = request.startAsync(); - asyncContext.addListener(new AsyncListener() { + asyncContext.addListener(new AsyncListener() + { @Override - public void onComplete(AsyncEvent event) throws IOException + public void onComplete(AsyncEvent event) { } @Override - public void onTimeout(AsyncEvent event) throws IOException + public void onTimeout(AsyncEvent event) { } @Override - public void onError(AsyncEvent event) throws IOException + public void onError(AsyncEvent event) { errorAsyncEventRef.set(event); } @Override - public void onStartAsync(AsyncEvent event) throws IOException + public void onStartAsync(AsyncEvent event) { } }); From c2d4d2abf381af82670b62d4ffaa6f690cc2ca38 Mon Sep 17 00:00:00 2001 From: Ludovic Orban Date: Tue, 11 Jun 2024 17:02:41 +0200 Subject: [PATCH 05/22] rename tests Signed-off-by: Ludovic Orban --- .../test/java/org/eclipse/jetty/http2/tests/AsyncIOTest.java | 2 +- .../jetty/ee10/test/client/transport/AsyncIOServletTest.java | 2 +- .../jetty/ee9/test/client/transport/AsyncIOServletTest.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) 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 0c45757bdb21..c32a6969fcef 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 @@ -254,7 +254,7 @@ public void onClosed(Stream stream) @ParameterizedTest @ValueSource(booleans = {true, false}) - public void testClientResetWithoutRemoteErrorNotification(boolean notify) throws Exception + public void testClientResetRemoteErrorNotification(boolean notify) throws Exception { CountDownLatch latch = new CountDownLatch(1); AtomicReference responseRef = new AtomicReference<>(); diff --git a/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-client-transports/src/test/java/org/eclipse/jetty/ee10/test/client/transport/AsyncIOServletTest.java b/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-client-transports/src/test/java/org/eclipse/jetty/ee10/test/client/transport/AsyncIOServletTest.java index 34562151bae1..db0f3ebef975 100644 --- a/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-client-transports/src/test/java/org/eclipse/jetty/ee10/test/client/transport/AsyncIOServletTest.java +++ b/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-client-transports/src/test/java/org/eclipse/jetty/ee10/test/client/transport/AsyncIOServletTest.java @@ -1546,7 +1546,7 @@ public void onError(Throwable x) @ParameterizedTest @ValueSource(booleans = {true, false}) - public void testStartAsyncThenClientResetWithoutRemoteErrorNotification(boolean notify) throws Exception + public void testStartAsyncThenClientResetRemoteErrorNotification(boolean notify) throws Exception { httpConfig.setNotifyRemoteAsyncErrors(notify); diff --git a/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-client-transports/src/test/java/org/eclipse/jetty/ee9/test/client/transport/AsyncIOServletTest.java b/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-client-transports/src/test/java/org/eclipse/jetty/ee9/test/client/transport/AsyncIOServletTest.java index 7ebafca10ae6..11204d699bbc 100644 --- a/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-client-transports/src/test/java/org/eclipse/jetty/ee9/test/client/transport/AsyncIOServletTest.java +++ b/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-client-transports/src/test/java/org/eclipse/jetty/ee9/test/client/transport/AsyncIOServletTest.java @@ -1873,7 +1873,7 @@ public void onError(Throwable x) @ParameterizedTest @ValueSource(booleans = {true, false}) - public void testStartAsyncThenClientResetWithoutRemoteErrorNotification(boolean notify) throws Exception + public void testStartAsyncThenClientResetRemoteErrorNotification(boolean notify) throws Exception { httpConfig.setNotifyRemoteAsyncErrors(notify); From 366328154d2d527496fcb82f26b1034daad13a4e Mon Sep 17 00:00:00 2001 From: Ludovic Orban Date: Mon, 17 Jun 2024 10:27:08 +0200 Subject: [PATCH 06/22] handle review comments Signed-off-by: Ludovic Orban --- .../org/eclipse/jetty/ee10/servlet/ServletChannelState.java | 5 ++--- .../java/org/eclipse/jetty/ee9/nested/HttpChannelState.java | 5 ++--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ServletChannelState.java b/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ServletChannelState.java index 46f1fe1050bb..d4c298123ca0 100644 --- a/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ServletChannelState.java +++ b/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ServletChannelState.java @@ -569,11 +569,10 @@ public void startAsync(AsyncContextEvent event) if (_state != State.HANDLING || (_requestState != RequestState.BLOCKING && _requestState != RequestState.ERRORING)) throw new IllegalStateException(this.getStatusStringLocked()); - if (!_failureListener) + if (_servletChannel.getHttpConfiguration().isNotifyRemoteAsyncErrors() && !_failureListener) { _failureListener = true; - if (_servletChannel.getHttpConfiguration().isNotifyRemoteAsyncErrors()) - _servletChannel.getRequest().addFailureListener(this::asyncError); + _servletChannel.getRequest().addFailureListener(this::asyncError); } _requestState = RequestState.ASYNC; _event = event; diff --git a/jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/HttpChannelState.java b/jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/HttpChannelState.java index 24659120a531..cc04f5a5d6a3 100644 --- a/jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/HttpChannelState.java +++ b/jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/HttpChannelState.java @@ -531,11 +531,10 @@ public void startAsync(AsyncContextEvent event) if (_state != State.HANDLING || _requestState != RequestState.BLOCKING) throw new IllegalStateException(this.getStatusStringLocked()); - if (!_failureListener) + if (_channel.getHttpConfiguration().isNotifyRemoteAsyncErrors() && !_failureListener) { _failureListener = true; - if (_channel.getHttpConfiguration().isNotifyRemoteAsyncErrors()) - getHttpChannel().getCoreRequest().addFailureListener(this::asyncError); + getHttpChannel().getCoreRequest().addFailureListener(this::asyncError); } _requestState = RequestState.ASYNC; _event = event; From 8b35d6d7e15b8c7df7ed57b1d0d0b3ebb6c17741 Mon Sep 17 00:00:00 2001 From: Ludovic Orban Date: Mon, 17 Jun 2024 11:21:59 +0200 Subject: [PATCH 07/22] move notifyRemoteAsyncErrors logic to Core API Signed-off-by: Ludovic Orban --- .../org/eclipse/jetty/http2/HTTP2Channel.java | 2 +- .../server/HTTP2ServerConnectionFactory.java | 9 ++------- .../server/internal/HTTP2ServerConnection.java | 6 +++--- .../server/internal/HttpStreamOverHTTP2.java | 4 ++-- .../internal/ServerHTTP2StreamEndPoint.java | 4 ++-- .../org/eclipse/jetty/server/HttpChannel.java | 15 +++++++++++++-- .../jetty/server/internal/HttpChannelState.java | 15 ++++++++++++++- .../jetty/ee10/servlet/ServletChannelState.java | 2 +- .../jetty/ee9/nested/HttpChannelState.java | 2 +- 9 files changed, 39 insertions(+), 20 deletions(-) diff --git a/jetty-core/jetty-http2/jetty-http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Channel.java b/jetty-core/jetty-http2/jetty-http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Channel.java index ff4ef40c12e2..ef1ab7ea848f 100644 --- a/jetty-core/jetty-http2/jetty-http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Channel.java +++ b/jetty-core/jetty-http2/jetty-http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Channel.java @@ -63,7 +63,7 @@ public interface Server // TODO: can it be simplified? The callback seems to only be succeeded, which // means it can be converted into a Runnable which may just be the return type // so we can get rid of the Callback parameter. - public Runnable onFailure(Throwable failure, Callback callback); + public Runnable onFailure(Throwable failure, boolean remote, Callback callback); // TODO: is this needed? public boolean isIdle(); diff --git a/jetty-core/jetty-http2/jetty-http2-server/src/main/java/org/eclipse/jetty/http2/server/HTTP2ServerConnectionFactory.java b/jetty-core/jetty-http2/jetty-http2-server/src/main/java/org/eclipse/jetty/http2/server/HTTP2ServerConnectionFactory.java index ba2b27b520b4..fab196ee7e3a 100644 --- a/jetty-core/jetty-http2/jetty-http2-server/src/main/java/org/eclipse/jetty/http2/server/HTTP2ServerConnectionFactory.java +++ b/jetty-core/jetty-http2/jetty-http2-server/src/main/java/org/eclipse/jetty/http2/server/HTTP2ServerConnectionFactory.java @@ -156,7 +156,7 @@ public void onDataAvailable(Stream stream) public void onReset(Stream stream, ResetFrame frame, Callback callback) { EofException failure = new EofException("Reset " + ErrorCode.toString(frame.getError(), null)); - onFailure(stream, failure, callback); + getConnection().onStreamFailure(stream, failure, true, callback); } @Override @@ -164,12 +164,7 @@ public void onFailure(Stream stream, int error, String reason, Throwable failure { if (!(failure instanceof QuietException)) failure = new EofException(failure); - onFailure(stream, failure, callback); - } - - private void onFailure(Stream stream, Throwable failure, Callback callback) - { - getConnection().onStreamFailure(stream, failure, callback); + getConnection().onStreamFailure(stream, failure, false, callback); } @Override diff --git a/jetty-core/jetty-http2/jetty-http2-server/src/main/java/org/eclipse/jetty/http2/server/internal/HTTP2ServerConnection.java b/jetty-core/jetty-http2/jetty-http2-server/src/main/java/org/eclipse/jetty/http2/server/internal/HTTP2ServerConnection.java index 7a2a9299636b..ca0a237fd138 100644 --- a/jetty-core/jetty-http2/jetty-http2-server/src/main/java/org/eclipse/jetty/http2/server/internal/HTTP2ServerConnection.java +++ b/jetty-core/jetty-http2/jetty-http2-server/src/main/java/org/eclipse/jetty/http2/server/internal/HTTP2ServerConnection.java @@ -194,14 +194,14 @@ public void onStreamTimeout(Stream stream, TimeoutException timeout, Promise co } @Override - public Runnable onFailure(Throwable failure, Callback callback) + public Runnable onFailure(Throwable failure, boolean remote, Callback callback) { - Runnable runnable = _httpChannel.onFailure(failure); + Runnable runnable = remote ? _httpChannel.onRemoteFailure(failure) : _httpChannel.onFailure(failure); return () -> { if (runnable != null) diff --git a/jetty-core/jetty-http2/jetty-http2-server/src/main/java/org/eclipse/jetty/http2/server/internal/ServerHTTP2StreamEndPoint.java b/jetty-core/jetty-http2/jetty-http2-server/src/main/java/org/eclipse/jetty/http2/server/internal/ServerHTTP2StreamEndPoint.java index 8f285557f288..30f34ef43dc5 100644 --- a/jetty-core/jetty-http2/jetty-http2-server/src/main/java/org/eclipse/jetty/http2/server/internal/ServerHTTP2StreamEndPoint.java +++ b/jetty-core/jetty-http2/jetty-http2-server/src/main/java/org/eclipse/jetty/http2/server/internal/ServerHTTP2StreamEndPoint.java @@ -67,10 +67,10 @@ public void onTimeout(TimeoutException timeout, BiConsumer co } @Override - public Runnable onFailure(Throwable failure, Callback callback) + public Runnable onFailure(Throwable failure, boolean remote, Callback callback) { if (LOG.isDebugEnabled()) - LOG.debug("failure on {}", this, failure); + LOG.debug("{}failure on {}", remote ? "remote " : "", this, failure); processFailure(failure); close(failure); return callback::succeeded; diff --git a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannel.java b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannel.java index a565ccce5b59..61a157ebd9e8 100644 --- a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannel.java +++ b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannel.java @@ -88,8 +88,7 @@ public interface HttpChannel extends Invocable /** *

Notifies this {@code HttpChannel} that an asynchronous failure happened.

- *

Typical failure examples could be HTTP/2 resets or - * protocol failures (for example, invalid request bytes).

+ *

Typical failure examples could be protocol failures (for example, invalid request bytes).

* * @param failure the failure cause. * @return a {@code Runnable} that performs the failure action, or {@code null} @@ -98,6 +97,18 @@ public interface HttpChannel extends Invocable */ Runnable onFailure(Throwable failure); + /** + *

Notifies this {@code HttpChannel} that an asynchronous notification was received indicating + * a remote failure happened.

+ *

Typical failure examples could be HTTP/2 resets.

+ * + * @param failure the failure cause. + * @return a {@code Runnable} that performs the failure action, or {@code null} + * if no failure action needs be performed by the calling thread + * @see Request#addFailureListener(Consumer) + */ + Runnable onRemoteFailure(Throwable failure); + /** *

Notifies this {@code HttpChannel} that an asynchronous close happened.

* diff --git a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/internal/HttpChannelState.java b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/internal/HttpChannelState.java index 7d7ab8cc380b..e97331b573ae 100644 --- a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/internal/HttpChannelState.java +++ b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/internal/HttpChannelState.java @@ -392,6 +392,17 @@ public Runnable onIdleTimeout(TimeoutException t) @Override public Runnable onFailure(Throwable x) + { + return onFailure(x, false); + } + + @Override + public Runnable onRemoteFailure(Throwable x) + { + return onFailure(x, true); + } + + private Runnable onFailure(Throwable x, boolean remote) { HttpStream stream; Runnable task; @@ -437,7 +448,9 @@ public Runnable onFailure(Throwable x) // Notify the failure listeners only once. Consumer onFailure = _onFailure; _onFailure = null; - Runnable invokeOnFailureListeners = onFailure == null || !getHttpConfiguration().isNotifyRemoteAsyncErrors() ? null : () -> + + boolean skipListeners = remote && !getHttpConfiguration().isNotifyRemoteAsyncErrors(); + Runnable invokeOnFailureListeners = onFailure == null || skipListeners ? null : () -> { try { diff --git a/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ServletChannelState.java b/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ServletChannelState.java index d4c298123ca0..0c419cba5598 100644 --- a/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ServletChannelState.java +++ b/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ServletChannelState.java @@ -569,7 +569,7 @@ public void startAsync(AsyncContextEvent event) if (_state != State.HANDLING || (_requestState != RequestState.BLOCKING && _requestState != RequestState.ERRORING)) throw new IllegalStateException(this.getStatusStringLocked()); - if (_servletChannel.getHttpConfiguration().isNotifyRemoteAsyncErrors() && !_failureListener) + if (!_failureListener) { _failureListener = true; _servletChannel.getRequest().addFailureListener(this::asyncError); diff --git a/jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/HttpChannelState.java b/jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/HttpChannelState.java index cc04f5a5d6a3..7fdc4184b068 100644 --- a/jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/HttpChannelState.java +++ b/jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/HttpChannelState.java @@ -531,7 +531,7 @@ public void startAsync(AsyncContextEvent event) if (_state != State.HANDLING || _requestState != RequestState.BLOCKING) throw new IllegalStateException(this.getStatusStringLocked()); - if (_channel.getHttpConfiguration().isNotifyRemoteAsyncErrors() && !_failureListener) + if (!_failureListener) { _failureListener = true; getHttpChannel().getCoreRequest().addFailureListener(this::asyncError); From d375c8afa00b54b8fe9dacae01f799c321d34990 Mon Sep 17 00:00:00 2001 From: Ludovic Orban Date: Tue, 18 Jun 2024 10:44:41 +0200 Subject: [PATCH 08/22] wire h3 reset Signed-off-by: Ludovic Orban --- .../transport/internal/HttpReceiverOverHTTP3.java | 2 +- .../eclipse/jetty/http3/client/HTTP3StreamClient.java | 4 ++-- .../java/org/eclipse/jetty/http3/HTTP3Session.java | 6 +++--- .../main/java/org/eclipse/jetty/http3/HTTP3Stream.java | 6 +++--- .../org/eclipse/jetty/http3/HTTP3StreamConnection.java | 10 ++++++++-- .../main/java/org/eclipse/jetty/http3/api/Stream.java | 6 ++++-- .../org/eclipse/jetty/http3/parser/BodyParser.java | 4 ++-- .../eclipse/jetty/http3/parser/HeadersBodyParser.java | 2 +- .../org/eclipse/jetty/http3/parser/ParserListener.java | 6 +++--- .../http3/server/HTTP3ServerConnectionFactory.java | 6 +++--- .../jetty/http3/server/internal/HTTP3StreamServer.java | 4 ++-- .../http3/server/internal/HttpStreamOverHTTP3.java | 4 ++-- .../server/internal/ServerHTTP3StreamConnection.java | 4 ++-- .../java/org/eclipse/jetty/http3/tests/GoAwayTest.java | 8 ++++---- .../jetty/http3/tests/StreamIdleTimeoutTest.java | 4 ++-- .../quic/quiche/foreign/ForeignQuicheConnection.java | 3 +++ .../jetty/quic/quiche/jna/JnaQuicheConnection.java | 3 +++ 17 files changed, 48 insertions(+), 34 deletions(-) diff --git a/jetty-core/jetty-http3/jetty-http3-client-transport/src/main/java/org/eclipse/jetty/http3/client/transport/internal/HttpReceiverOverHTTP3.java b/jetty-core/jetty-http3/jetty-http3-client-transport/src/main/java/org/eclipse/jetty/http3/client/transport/internal/HttpReceiverOverHTTP3.java index 594de886793c..bf91417bb900 100644 --- a/jetty-core/jetty-http3/jetty-http3-client-transport/src/main/java/org/eclipse/jetty/http3/client/transport/internal/HttpReceiverOverHTTP3.java +++ b/jetty-core/jetty-http3/jetty-http3-client-transport/src/main/java/org/eclipse/jetty/http3/client/transport/internal/HttpReceiverOverHTTP3.java @@ -154,7 +154,7 @@ public void onIdleTimeout(Stream.Client stream, Throwable failure, Promise prom } @Override - protected void notifyFailure(long error, Throwable failure) + protected void notifyFailure(boolean remote, long error, Throwable failure) { Listener listener = getListener(); try { if (listener != null) - listener.onFailure(this, error, failure); + listener.onFailure(this, remote, error, failure); } catch (Throwable x) { diff --git a/jetty-core/jetty-http3/jetty-http3-common/src/main/java/org/eclipse/jetty/http3/HTTP3Session.java b/jetty-core/jetty-http3/jetty-http3-common/src/main/java/org/eclipse/jetty/http3/HTTP3Session.java index c6b1b9ce506a..577133e2bc84 100644 --- a/jetty-core/jetty-http3/jetty-http3-common/src/main/java/org/eclipse/jetty/http3/HTTP3Session.java +++ b/jetty-core/jetty-http3/jetty-http3-common/src/main/java/org/eclipse/jetty/http3/HTTP3Session.java @@ -682,7 +682,7 @@ private void failStreams(Predicate predicate, long error, String re stream.reset(error, failure); // Since the stream failure was generated // by a GOAWAY, notify the application. - stream.onFailure(error, failure); + stream.onFailure(false, error, failure); }); } @@ -814,13 +814,13 @@ private void notifyDisconnect(long error, String reason) } @Override - public void onStreamFailure(long streamId, long error, Throwable failure) + public void onStreamFailure(long streamId, boolean remote, long error, Throwable failure) { if (LOG.isDebugEnabled()) LOG.debug("stream failure 0x{}/{} for stream #{} on {}", Long.toHexString(error), failure.getMessage(), streamId, this); HTTP3Stream stream = getStream(streamId); if (stream != null) - stream.onFailure(error, failure); + stream.onFailure(remote, error, failure); } @Override diff --git a/jetty-core/jetty-http3/jetty-http3-common/src/main/java/org/eclipse/jetty/http3/HTTP3Stream.java b/jetty-core/jetty-http3/jetty-http3-common/src/main/java/org/eclipse/jetty/http3/HTTP3Stream.java index 2eadbe900122..aea014e7b6f7 100644 --- a/jetty-core/jetty-http3/jetty-http3-common/src/main/java/org/eclipse/jetty/http3/HTTP3Stream.java +++ b/jetty-core/jetty-http3/jetty-http3-common/src/main/java/org/eclipse/jetty/http3/HTTP3Stream.java @@ -370,13 +370,13 @@ public void onTrailer(HeadersFrame frame) protected abstract void notifyIdleTimeout(TimeoutException timeout, Promise promise); - public void onFailure(long error, Throwable failure) + public void onFailure(boolean remote, long error, Throwable failure) { - notifyFailure(error, failure); + notifyFailure(remote, error, failure); session.removeStream(this, failure); } - protected abstract void notifyFailure(long error, Throwable failure); + protected abstract void notifyFailure(boolean remote, long error, Throwable failure); protected boolean validateAndUpdate(EnumSet allowed, FrameState target) { diff --git a/jetty-core/jetty-http3/jetty-http3-common/src/main/java/org/eclipse/jetty/http3/HTTP3StreamConnection.java b/jetty-core/jetty-http3/jetty-http3-common/src/main/java/org/eclipse/jetty/http3/HTTP3StreamConnection.java index 29542da3d1eb..699e5eaf1c63 100644 --- a/jetty-core/jetty-http3/jetty-http3-common/src/main/java/org/eclipse/jetty/http3/HTTP3StreamConnection.java +++ b/jetty-core/jetty-http3/jetty-http3-common/src/main/java/org/eclipse/jetty/http3/HTTP3StreamConnection.java @@ -13,6 +13,7 @@ package org.eclipse.jetty.http3; +import java.io.EOFException; import java.io.IOException; import java.io.UncheckedIOException; import java.nio.ByteBuffer; @@ -150,10 +151,15 @@ private void processDataFrames(boolean setFillInterest) long error = HTTP3ErrorCode.REQUEST_CANCELLED_ERROR.code(); getEndPoint().close(error, x); // Notify the application that a failure happened. - parser.getListener().onStreamFailure(getEndPoint().getStreamId(), error, x); + parser.getListener().onStreamFailure(getEndPoint().getStreamId(), isReset(x), error, x); } } + private static boolean isReset(Throwable x) + { + return x.getCause() instanceof EOFException; + } + private void processNonDataFrames() { try @@ -238,7 +244,7 @@ private void processNonDataFrames() long error = HTTP3ErrorCode.REQUEST_CANCELLED_ERROR.code(); getEndPoint().close(error, x); // Notify the application that a failure happened. - parser.getListener().onStreamFailure(getEndPoint().getStreamId(), error, x); + parser.getListener().onStreamFailure(getEndPoint().getStreamId(), isReset(x), error, x); } } diff --git a/jetty-core/jetty-http3/jetty-http3-common/src/main/java/org/eclipse/jetty/http3/api/Stream.java b/jetty-core/jetty-http3/jetty-http3-common/src/main/java/org/eclipse/jetty/http3/api/Stream.java index d2e83791404a..3129c905fd56 100644 --- a/jetty-core/jetty-http3/jetty-http3-common/src/main/java/org/eclipse/jetty/http3/api/Stream.java +++ b/jetty-core/jetty-http3/jetty-http3-common/src/main/java/org/eclipse/jetty/http3/api/Stream.java @@ -253,10 +253,11 @@ public default void onIdleTimeout(Client stream, Throwable failure, Promise * * @param stream the stream + * @param remote true if the error comes from a remote notification * @param error the failure error * @param failure the cause of the failure */ - public default void onFailure(Stream.Client stream, long error, Throwable failure) + public default void onFailure(Stream.Client stream, boolean remote, long error, Throwable failure) { } } @@ -369,10 +370,11 @@ public default void onIdleTimeout(Server stream, TimeoutException failure, Promi * the stream has been reset.

* * @param stream the stream + * @param remote true if the error comes from a remote notification * @param error the failure error * @param failure the cause of the failure */ - public default void onFailure(Stream.Server stream, long error, Throwable failure) + public default void onFailure(Server stream, boolean remote, long error, Throwable failure) { } } diff --git a/jetty-core/jetty-http3/jetty-http3-common/src/main/java/org/eclipse/jetty/http3/parser/BodyParser.java b/jetty-core/jetty-http3/jetty-http3-common/src/main/java/org/eclipse/jetty/http3/parser/BodyParser.java index 9aa050d57936..2921d04906d6 100644 --- a/jetty-core/jetty-http3/jetty-http3-common/src/main/java/org/eclipse/jetty/http3/parser/BodyParser.java +++ b/jetty-core/jetty-http3/jetty-http3-common/src/main/java/org/eclipse/jetty/http3/parser/BodyParser.java @@ -87,11 +87,11 @@ protected void notifySessionFailure(long error, String reason, Throwable failure } } - protected void notifyStreamFailure(long streamId, long error, Throwable failure) + protected void notifyStreamFailure(long streamId, boolean remote, long error, Throwable failure) { try { - listener.onStreamFailure(streamId, error, failure); + listener.onStreamFailure(streamId, remote, error, failure); } catch (Throwable x) { diff --git a/jetty-core/jetty-http3/jetty-http3-common/src/main/java/org/eclipse/jetty/http3/parser/HeadersBodyParser.java b/jetty-core/jetty-http3/jetty-http3-common/src/main/java/org/eclipse/jetty/http3/parser/HeadersBodyParser.java index 686c20712ec2..28b9b8950e28 100644 --- a/jetty-core/jetty-http3/jetty-http3-common/src/main/java/org/eclipse/jetty/http3/parser/HeadersBodyParser.java +++ b/jetty-core/jetty-http3/jetty-http3-common/src/main/java/org/eclipse/jetty/http3/parser/HeadersBodyParser.java @@ -127,7 +127,7 @@ private boolean decode(ByteBuffer encoded, boolean last) { if (LOG.isDebugEnabled()) LOG.debug("decode failure", x); - notifyStreamFailure(streamId, x.getErrorCode(), x); + notifyStreamFailure(streamId, false, x.getErrorCode(), x); } catch (QpackException.SessionException x) { diff --git a/jetty-core/jetty-http3/jetty-http3-common/src/main/java/org/eclipse/jetty/http3/parser/ParserListener.java b/jetty-core/jetty-http3/jetty-http3-common/src/main/java/org/eclipse/jetty/http3/parser/ParserListener.java index 2db2e77a1357..93048c9624a5 100644 --- a/jetty-core/jetty-http3/jetty-http3-common/src/main/java/org/eclipse/jetty/http3/parser/ParserListener.java +++ b/jetty-core/jetty-http3/jetty-http3-common/src/main/java/org/eclipse/jetty/http3/parser/ParserListener.java @@ -36,7 +36,7 @@ public default void onGoAway(GoAwayFrame frame) { } - public default void onStreamFailure(long streamId, long error, Throwable failure) + public default void onStreamFailure(long streamId, boolean remote, long error, Throwable failure) { } @@ -72,9 +72,9 @@ public void onSettings(SettingsFrame frame) } @Override - public void onStreamFailure(long streamId, long error, Throwable failure) + public void onStreamFailure(long streamId, boolean remote, long error, Throwable failure) { - listener.onStreamFailure(streamId, error, failure); + listener.onStreamFailure(streamId, remote, error, failure); } @Override diff --git a/jetty-core/jetty-http3/jetty-http3-server/src/main/java/org/eclipse/jetty/http3/server/HTTP3ServerConnectionFactory.java b/jetty-core/jetty-http3/jetty-http3-server/src/main/java/org/eclipse/jetty/http3/server/HTTP3ServerConnectionFactory.java index fde8bb5dbfbe..75889de805d9 100644 --- a/jetty-core/jetty-http3/jetty-http3-server/src/main/java/org/eclipse/jetty/http3/server/HTTP3ServerConnectionFactory.java +++ b/jetty-core/jetty-http3/jetty-http3-server/src/main/java/org/eclipse/jetty/http3/server/HTTP3ServerConnectionFactory.java @@ -96,7 +96,7 @@ public void onFailure(Session session, long error, String reason, Throwable fail { session.getStreams().stream() .map(stream -> (HTTP3Stream)stream) - .forEach(stream -> stream.onFailure(error, failure)); + .forEach(stream -> stream.onFailure(false, error, failure)); } } @@ -165,10 +165,10 @@ public void onIdleTimeout(Stream.Server stream, TimeoutException timeout, Promis } @Override - public void onFailure(Stream.Server stream, long error, Throwable failure) + public void onFailure(Stream.Server stream, boolean remote, long error, Throwable failure) { HTTP3Stream http3Stream = (HTTP3Stream)stream; - Runnable task = getConnection().onFailure((HTTP3Stream)stream, failure); + Runnable task = getConnection().onFailure((HTTP3Stream)stream, remote, failure); if (task != null) { ServerHTTP3Session protocolSession = (ServerHTTP3Session)http3Stream.getSession().getProtocolSession(); diff --git a/jetty-core/jetty-http3/jetty-http3-server/src/main/java/org/eclipse/jetty/http3/server/internal/HTTP3StreamServer.java b/jetty-core/jetty-http3/jetty-http3-server/src/main/java/org/eclipse/jetty/http3/server/internal/HTTP3StreamServer.java index 9b994a93fce8..2f3a9702638a 100644 --- a/jetty-core/jetty-http3/jetty-http3-server/src/main/java/org/eclipse/jetty/http3/server/internal/HTTP3StreamServer.java +++ b/jetty-core/jetty-http3/jetty-http3-server/src/main/java/org/eclipse/jetty/http3/server/internal/HTTP3StreamServer.java @@ -120,13 +120,13 @@ protected void notifyIdleTimeout(TimeoutException timeout, Promise prom } @Override - protected void notifyFailure(long error, Throwable failure) + protected void notifyFailure(boolean remote, long error, Throwable failure) { Listener listener = this.listener; try { if (listener != null) - listener.onFailure(this, error, failure); + listener.onFailure(this, remote, error, failure); } catch (Throwable x) { diff --git a/jetty-core/jetty-http3/jetty-http3-server/src/main/java/org/eclipse/jetty/http3/server/internal/HttpStreamOverHTTP3.java b/jetty-core/jetty-http3/jetty-http3-server/src/main/java/org/eclipse/jetty/http3/server/internal/HttpStreamOverHTTP3.java index 1552659ad97d..d32cda87da5d 100644 --- a/jetty-core/jetty-http3/jetty-http3-server/src/main/java/org/eclipse/jetty/http3/server/internal/HttpStreamOverHTTP3.java +++ b/jetty-core/jetty-http3/jetty-http3-server/src/main/java/org/eclipse/jetty/http3/server/internal/HttpStreamOverHTTP3.java @@ -527,7 +527,7 @@ public void onIdleTimeout(TimeoutException failure, BiConsumer true, error, reason, false, failure); if (notifyFailure) - onSessionFailure(error, reason, failure); + onSessionFailure(error, false, reason, failure); notifyDisconnect(error, reason); } @@ -824,17 +824,17 @@ public void onStreamFailure(long streamId, boolean remote, long error, Throwable } @Override - public void onSessionFailure(long error, String reason, Throwable failure) + public void onSessionFailure(long error, boolean remote, String reason, Throwable failure) { - notifyFailure(error, reason, failure); + notifyFailure(error, remote, reason, failure); inwardClose(error, reason); } - private void notifyFailure(long error, String reason, Throwable failure) + private void notifyFailure(long error, boolean remote, String reason, Throwable failure) { try { - listener.onFailure(this, error, reason, failure); + listener.onFailure(this, remote, error, reason, failure); } catch (Throwable x) { diff --git a/jetty-core/jetty-http3/jetty-http3-common/src/main/java/org/eclipse/jetty/http3/HTTP3Stream.java b/jetty-core/jetty-http3/jetty-http3-common/src/main/java/org/eclipse/jetty/http3/HTTP3Stream.java index aea014e7b6f7..d2275927788b 100644 --- a/jetty-core/jetty-http3/jetty-http3-common/src/main/java/org/eclipse/jetty/http3/HTTP3Stream.java +++ b/jetty-core/jetty-http3/jetty-http3-common/src/main/java/org/eclipse/jetty/http3/HTTP3Stream.java @@ -392,7 +392,7 @@ protected boolean validateAndUpdate(EnumSet allowed, FrameState targ if (frameState == FrameState.FAILED) return false; frameState = FrameState.FAILED; - session.onSessionFailure(HTTP3ErrorCode.FRAME_UNEXPECTED_ERROR.code(), "invalid_frame_sequence", new IllegalStateException("invalid frame sequence")); + session.onSessionFailure(HTTP3ErrorCode.FRAME_UNEXPECTED_ERROR.code(), false, "invalid_frame_sequence", new IllegalStateException("invalid frame sequence")); return false; } } diff --git a/jetty-core/jetty-http3/jetty-http3-common/src/main/java/org/eclipse/jetty/http3/InstructionStreamConnection.java b/jetty-core/jetty-http3/jetty-http3-common/src/main/java/org/eclipse/jetty/http3/InstructionStreamConnection.java index 22f1a4e2d20d..5f0a74ba1776 100644 --- a/jetty-core/jetty-http3/jetty-http3-common/src/main/java/org/eclipse/jetty/http3/InstructionStreamConnection.java +++ b/jetty-core/jetty-http3/jetty-http3-common/src/main/java/org/eclipse/jetty/http3/InstructionStreamConnection.java @@ -109,28 +109,28 @@ else if (filled < 0) } catch (QpackException.SessionException x) { - fail(x.getErrorCode(), x.getMessage(), x); + fail(x.getErrorCode(), false, x.getMessage(), x); } catch (Throwable x) { - fail(HTTP3ErrorCode.INTERNAL_ERROR.code(), "internal_error", x); + fail(HTTP3ErrorCode.INTERNAL_ERROR.code(), false, "internal_error", x); } } - private void fail(long errorCode, String message, Throwable failure) + private void fail(long errorCode, boolean remote, String message, Throwable failure) { buffer.release(); buffer = null; if (LOG.isDebugEnabled()) LOG.debug("could not process instruction stream {}", getEndPoint(), failure); - notifySessionFailure(errorCode, message, failure); + notifySessionFailure(errorCode, remote, message, failure); } - protected void notifySessionFailure(long error, String reason, Throwable failure) + protected void notifySessionFailure(long error, boolean remote, String reason, Throwable failure) { try { - listener.onSessionFailure(error, reason, failure); + listener.onSessionFailure(error, remote, reason, failure); } catch (Throwable x) { diff --git a/jetty-core/jetty-http3/jetty-http3-common/src/main/java/org/eclipse/jetty/http3/api/Session.java b/jetty-core/jetty-http3/jetty-http3-common/src/main/java/org/eclipse/jetty/http3/api/Session.java index 1dccc54ba10f..015a22457c47 100644 --- a/jetty-core/jetty-http3/jetty-http3-common/src/main/java/org/eclipse/jetty/http3/api/Session.java +++ b/jetty-core/jetty-http3/jetty-http3-common/src/main/java/org/eclipse/jetty/http3/api/Session.java @@ -243,7 +243,7 @@ public default void onDisconnect(Session session, long error, String reason) * @param reason the failure reason * @param failure the failure */ - public default void onFailure(Session session, long error, String reason, Throwable failure) + public default void onFailure(Session session, boolean remote, long error, String reason, Throwable failure) { } } diff --git a/jetty-core/jetty-http3/jetty-http3-common/src/main/java/org/eclipse/jetty/http3/parser/BodyParser.java b/jetty-core/jetty-http3/jetty-http3-common/src/main/java/org/eclipse/jetty/http3/parser/BodyParser.java index 2921d04906d6..f8941b2c39cd 100644 --- a/jetty-core/jetty-http3/jetty-http3-common/src/main/java/org/eclipse/jetty/http3/parser/BodyParser.java +++ b/jetty-core/jetty-http3/jetty-http3-common/src/main/java/org/eclipse/jetty/http3/parser/BodyParser.java @@ -66,20 +66,20 @@ protected long getBodyLength() protected void emptyBody(ByteBuffer buffer) { - sessionFailure(buffer, HTTP3ErrorCode.PROTOCOL_ERROR.code(), "invalid_frame", new IOException("invalid empty body frame")); + sessionFailure(buffer, HTTP3ErrorCode.PROTOCOL_ERROR.code(), false, "invalid_frame", new IOException("invalid empty body frame")); } - protected void sessionFailure(ByteBuffer buffer, long error, String reason, Throwable failure) + protected void sessionFailure(ByteBuffer buffer, long error, boolean remote, String reason, Throwable failure) { BufferUtil.clear(buffer); - notifySessionFailure(error, reason, failure); + notifySessionFailure(error, remote, reason, failure); } - protected void notifySessionFailure(long error, String reason, Throwable failure) + protected void notifySessionFailure(long error, boolean remote, String reason, Throwable failure) { try { - listener.onSessionFailure(error, reason, failure); + listener.onSessionFailure(error, remote, reason, failure); } catch (Throwable x) { diff --git a/jetty-core/jetty-http3/jetty-http3-common/src/main/java/org/eclipse/jetty/http3/parser/ControlParser.java b/jetty-core/jetty-http3/jetty-http3-common/src/main/java/org/eclipse/jetty/http3/parser/ControlParser.java index 9ad1873fc29a..96f3d2091d09 100644 --- a/jetty-core/jetty-http3/jetty-http3-common/src/main/java/org/eclipse/jetty/http3/parser/ControlParser.java +++ b/jetty-core/jetty-http3/jetty-http3-common/src/main/java/org/eclipse/jetty/http3/parser/ControlParser.java @@ -88,7 +88,7 @@ public void parse(ByteBuffer buffer) // SPEC: message frames on the control stream are invalid. if (LOG.isDebugEnabled()) LOG.debug("invalid message frame type {} on control stream", Long.toHexString(frameType)); - sessionFailure(buffer, HTTP3ErrorCode.FRAME_UNEXPECTED_ERROR.code(), "invalid_frame_type", new IOException("invalid message frame on control stream")); + sessionFailure(buffer, false, HTTP3ErrorCode.FRAME_UNEXPECTED_ERROR.code(), "invalid_frame_type", new IOException("invalid message frame on control stream")); return; } @@ -135,13 +135,13 @@ public void parse(ByteBuffer buffer) { if (LOG.isDebugEnabled()) LOG.debug("parse failed", x); - sessionFailure(buffer, HTTP3ErrorCode.INTERNAL_ERROR.code(), "parser_error", x); + sessionFailure(buffer, false, HTTP3ErrorCode.INTERNAL_ERROR.code(), "parser_error", x); } } - private void sessionFailure(ByteBuffer buffer, long error, String reason, Throwable failure) + private void sessionFailure(ByteBuffer buffer, boolean remote, long error, String reason, Throwable failure) { - unknownBodyParser.sessionFailure(buffer, error, reason, failure); + unknownBodyParser.sessionFailure(buffer, error, remote, reason, failure); } private enum State diff --git a/jetty-core/jetty-http3/jetty-http3-common/src/main/java/org/eclipse/jetty/http3/parser/HeadersBodyParser.java b/jetty-core/jetty-http3/jetty-http3-common/src/main/java/org/eclipse/jetty/http3/parser/HeadersBodyParser.java index 28b9b8950e28..7d73a934ccab 100644 --- a/jetty-core/jetty-http3/jetty-http3-common/src/main/java/org/eclipse/jetty/http3/parser/HeadersBodyParser.java +++ b/jetty-core/jetty-http3/jetty-http3-common/src/main/java/org/eclipse/jetty/http3/parser/HeadersBodyParser.java @@ -133,13 +133,13 @@ private boolean decode(ByteBuffer encoded, boolean last) { if (LOG.isDebugEnabled()) LOG.debug("decode failure", x); - notifySessionFailure(x.getErrorCode(), x.getMessage(), x); + notifySessionFailure(x.getErrorCode(), false, x.getMessage(), x); } catch (Throwable x) { if (LOG.isDebugEnabled()) LOG.debug("decode failure", x); - notifySessionFailure(HTTP3ErrorCode.INTERNAL_ERROR.code(), "internal_error", x); + notifySessionFailure(HTTP3ErrorCode.INTERNAL_ERROR.code(), false, "internal_error", x); } return false; } diff --git a/jetty-core/jetty-http3/jetty-http3-common/src/main/java/org/eclipse/jetty/http3/parser/MessageParser.java b/jetty-core/jetty-http3/jetty-http3-common/src/main/java/org/eclipse/jetty/http3/parser/MessageParser.java index 2c364a187e25..3d144696b919 100644 --- a/jetty-core/jetty-http3/jetty-http3-common/src/main/java/org/eclipse/jetty/http3/parser/MessageParser.java +++ b/jetty-core/jetty-http3/jetty-http3-common/src/main/java/org/eclipse/jetty/http3/parser/MessageParser.java @@ -147,7 +147,7 @@ public Result parse(ByteBuffer buffer) // SPEC: control frames on a message stream are invalid. if (LOG.isDebugEnabled()) LOG.debug("invalid control frame type {} on message stream", Long.toHexString(frameType)); - sessionFailure(buffer, HTTP3ErrorCode.FRAME_UNEXPECTED_ERROR.code(), "invalid_frame_type", new IOException("invalid control frame in message stream")); + sessionFailure(buffer, false, HTTP3ErrorCode.FRAME_UNEXPECTED_ERROR.code(),"invalid_frame_type", new IOException("invalid control frame in message stream")); return Result.NO_FRAME; } @@ -207,14 +207,14 @@ public Result parse(ByteBuffer buffer) { if (LOG.isDebugEnabled()) LOG.debug("parse failed", x); - sessionFailure(buffer, HTTP3ErrorCode.INTERNAL_ERROR.code(), "parser_error", x); + sessionFailure(buffer, false, HTTP3ErrorCode.INTERNAL_ERROR.code(), "parser_error", x); return Result.NO_FRAME; } } - private void sessionFailure(ByteBuffer buffer, long error, String reason, Throwable failure) + private void sessionFailure(ByteBuffer buffer, boolean remote, long error, String reason, Throwable failure) { - unknownBodyParser.sessionFailure(buffer, error, reason, failure); + unknownBodyParser.sessionFailure(buffer, error, remote, reason, failure); } public enum Result diff --git a/jetty-core/jetty-http3/jetty-http3-common/src/main/java/org/eclipse/jetty/http3/parser/ParserListener.java b/jetty-core/jetty-http3/jetty-http3-common/src/main/java/org/eclipse/jetty/http3/parser/ParserListener.java index 93048c9624a5..d15ac9204d83 100644 --- a/jetty-core/jetty-http3/jetty-http3-common/src/main/java/org/eclipse/jetty/http3/parser/ParserListener.java +++ b/jetty-core/jetty-http3/jetty-http3-common/src/main/java/org/eclipse/jetty/http3/parser/ParserListener.java @@ -40,7 +40,7 @@ public default void onStreamFailure(long streamId, boolean remote, long error, T { } - public default void onSessionFailure(long error, String reason, Throwable failure) + public default void onSessionFailure(long error, boolean remote, String reason, Throwable failure) { } @@ -78,9 +78,9 @@ public void onStreamFailure(long streamId, boolean remote, long error, Throwable } @Override - public void onSessionFailure(long error, String reason, Throwable failure) + public void onSessionFailure(long error, boolean remote, String reason, Throwable failure) { - listener.onSessionFailure(error, reason, failure); + listener.onSessionFailure(error, remote, reason, failure); } } } diff --git a/jetty-core/jetty-http3/jetty-http3-common/src/main/java/org/eclipse/jetty/http3/parser/SettingsBodyParser.java b/jetty-core/jetty-http3/jetty-http3-common/src/main/java/org/eclipse/jetty/http3/parser/SettingsBodyParser.java index 8ca5569c8b11..fb467bb6d24a 100644 --- a/jetty-core/jetty-http3/jetty-http3-common/src/main/java/org/eclipse/jetty/http3/parser/SettingsBodyParser.java +++ b/jetty-core/jetty-http3/jetty-http3-common/src/main/java/org/eclipse/jetty/http3/parser/SettingsBodyParser.java @@ -73,12 +73,12 @@ public Result parse(ByteBuffer buffer) { if (settings.containsKey(key)) { - sessionFailure(buffer, HTTP3ErrorCode.SETTINGS_ERROR.code(), "settings_duplicate", new IllegalArgumentException("invalid duplicate setting")); + sessionFailure(buffer, HTTP3ErrorCode.SETTINGS_ERROR.code(), false, "settings_duplicate", new IllegalArgumentException("invalid duplicate setting")); return Result.NO_FRAME; } if (SettingsFrame.isReserved(key)) { - sessionFailure(buffer, HTTP3ErrorCode.SETTINGS_ERROR.code(), "settings_reserved", new IllegalArgumentException("invalid reserved setting")); + sessionFailure(buffer, HTTP3ErrorCode.SETTINGS_ERROR.code(), false, "settings_reserved", new IllegalArgumentException("invalid reserved setting")); return Result.NO_FRAME; } if (length > 0) @@ -87,7 +87,7 @@ public Result parse(ByteBuffer buffer) } else { - sessionFailure(buffer, HTTP3ErrorCode.FRAME_ERROR.code(), "settings_invalid_format", new IllegalArgumentException("invalid setting")); + sessionFailure(buffer, HTTP3ErrorCode.FRAME_ERROR.code(), false, "settings_invalid_format", new IllegalArgumentException("invalid setting")); return Result.NO_FRAME; } break; @@ -116,7 +116,7 @@ else if (length == 0) } else { - sessionFailure(buffer, HTTP3ErrorCode.FRAME_ERROR.code(), "settings_invalid_format", new IllegalArgumentException("invalid setting")); + sessionFailure(buffer, HTTP3ErrorCode.FRAME_ERROR.code(), false, "settings_invalid_format", new IllegalArgumentException("invalid setting")); return Result.NO_FRAME; } break; diff --git a/jetty-core/jetty-http3/jetty-http3-server/src/main/java/org/eclipse/jetty/http3/server/HTTP3ServerConnectionFactory.java b/jetty-core/jetty-http3/jetty-http3-server/src/main/java/org/eclipse/jetty/http3/server/HTTP3ServerConnectionFactory.java index 75889de805d9..8c624b173b29 100644 --- a/jetty-core/jetty-http3/jetty-http3-server/src/main/java/org/eclipse/jetty/http3/server/HTTP3ServerConnectionFactory.java +++ b/jetty-core/jetty-http3/jetty-http3-server/src/main/java/org/eclipse/jetty/http3/server/HTTP3ServerConnectionFactory.java @@ -92,11 +92,11 @@ public boolean onIdleTimeout(Session session) } @Override - public void onFailure(Session session, long error, String reason, Throwable failure) + public void onFailure(Session session, boolean remote, long error, String reason, Throwable failure) { session.getStreams().stream() .map(stream -> (HTTP3Stream)stream) - .forEach(stream -> stream.onFailure(false, error, failure)); + .forEach(stream -> stream.onFailure(remote, error, failure)); } } diff --git a/jetty-core/jetty-http3/jetty-http3-server/src/main/java/org/eclipse/jetty/http3/server/internal/ServerHTTP3Session.java b/jetty-core/jetty-http3/jetty-http3-server/src/main/java/org/eclipse/jetty/http3/server/internal/ServerHTTP3Session.java index 0bf47b12e260..8351191ebab6 100644 --- a/jetty-core/jetty-http3/jetty-http3-server/src/main/java/org/eclipse/jetty/http3/server/internal/ServerHTTP3Session.java +++ b/jetty-core/jetty-http3/jetty-http3-server/src/main/java/org/eclipse/jetty/http3/server/internal/ServerHTTP3Session.java @@ -203,7 +203,7 @@ else if (key == SettingsFrame.MAX_BLOCKED_STREAMS) private void failControlStream(Throwable failure) { long error = HTTP3ErrorCode.CLOSED_CRITICAL_STREAM_ERROR.code(); - onFailure(error, "control_stream_failure", failure); + onFailure(false, error, "control_stream_failure", failure); } private QuicStreamEndPoint openInstructionEndPoint(long streamId) @@ -246,9 +246,9 @@ protected boolean onIdleTimeout() } @Override - protected void onFailure(long error, String reason, Throwable failure) + protected void onFailure(boolean remote, long error, String reason, Throwable failure) { - session.onSessionFailure(HTTP3ErrorCode.NO_ERROR.code(), "failure", failure); + session.onSessionFailure(HTTP3ErrorCode.NO_ERROR.code(), remote, "failure", failure); } @Override diff --git a/jetty-core/jetty-http3/jetty-http3-tests/src/test/java/org/eclipse/jetty/http3/tests/UnexpectedFrameTest.java b/jetty-core/jetty-http3/jetty-http3-tests/src/test/java/org/eclipse/jetty/http3/tests/UnexpectedFrameTest.java index cf69cc3fca84..cd337c43b811 100644 --- a/jetty-core/jetty-http3/jetty-http3-tests/src/test/java/org/eclipse/jetty/http3/tests/UnexpectedFrameTest.java +++ b/jetty-core/jetty-http3/jetty-http3-tests/src/test/java/org/eclipse/jetty/http3/tests/UnexpectedFrameTest.java @@ -38,7 +38,7 @@ public void testDataBeforeHeaders() throws Exception start(new Session.Server.Listener() { @Override - public void onFailure(Session session, long error, String reason, Throwable failure) + public void onFailure(Session session, boolean remote, long error, String reason, Throwable failure) { assertEquals(HTTP3ErrorCode.FRAME_UNEXPECTED_ERROR.code(), error); serverFailureLatch.countDown(); diff --git a/jetty-core/jetty-quic/jetty-quic-client/src/main/java/org/eclipse/jetty/quic/client/ClientProtocolSession.java b/jetty-core/jetty-quic/jetty-quic-client/src/main/java/org/eclipse/jetty/quic/client/ClientProtocolSession.java index 56760671a9f1..2278fec78f26 100644 --- a/jetty-core/jetty-quic/jetty-quic-client/src/main/java/org/eclipse/jetty/quic/client/ClientProtocolSession.java +++ b/jetty-core/jetty-quic/jetty-quic-client/src/main/java/org/eclipse/jetty/quic/client/ClientProtocolSession.java @@ -93,7 +93,7 @@ protected boolean onReadable(long readableStreamId) } @Override - protected void onFailure(long error, String reason, Throwable failure) + protected void onFailure(boolean remote, long error, String reason, Throwable failure) { outwardClose(QuicErrorCode.NO_ERROR.code(), "failure"); } diff --git a/jetty-core/jetty-quic/jetty-quic-common/src/main/java/org/eclipse/jetty/quic/common/ProtocolSession.java b/jetty-core/jetty-quic/jetty-quic-common/src/main/java/org/eclipse/jetty/quic/common/ProtocolSession.java index 452fdcbacc02..e99d53600dc8 100644 --- a/jetty-core/jetty-quic/jetty-quic-common/src/main/java/org/eclipse/jetty/quic/common/ProtocolSession.java +++ b/jetty-core/jetty-quic/jetty-quic-common/src/main/java/org/eclipse/jetty/quic/common/ProtocolSession.java @@ -146,7 +146,7 @@ protected boolean onIdleTimeout() return true; } - protected void onFailure(long error, String reason, Throwable failure) + protected void onFailure(boolean remote, long error, String reason, Throwable failure) { } diff --git a/jetty-core/jetty-quic/jetty-quic-common/src/main/java/org/eclipse/jetty/quic/common/QuicConnection.java b/jetty-core/jetty-quic/jetty-quic-common/src/main/java/org/eclipse/jetty/quic/common/QuicConnection.java index c5ff003db1b0..d7dbc2c6cc67 100644 --- a/jetty-core/jetty-quic/jetty-quic-common/src/main/java/org/eclipse/jetty/quic/common/QuicConnection.java +++ b/jetty-core/jetty-quic/jetty-quic-common/src/main/java/org/eclipse/jetty/quic/common/QuicConnection.java @@ -323,7 +323,7 @@ protected Runnable process(QuicSession session, SocketAddress remoteAddress, Byt { if (LOG.isDebugEnabled()) LOG.debug("process failure for {}", session, x); - session.onFailure(x); + session.onFailure(false, x); return null; } } diff --git a/jetty-core/jetty-quic/jetty-quic-common/src/main/java/org/eclipse/jetty/quic/common/QuicSession.java b/jetty-core/jetty-quic/jetty-quic-common/src/main/java/org/eclipse/jetty/quic/common/QuicSession.java index 8f998fa8743b..5ec38ffd2df5 100644 --- a/jetty-core/jetty-quic/jetty-quic-common/src/main/java/org/eclipse/jetty/quic/common/QuicSession.java +++ b/jetty-core/jetty-quic/jetty-quic-common/src/main/java/org/eclipse/jetty/quic/common/QuicSession.java @@ -208,9 +208,9 @@ public boolean onIdleTimeout() return protocolSession.onIdleTimeout(); } - public void onFailure(Throwable failure) + public void onFailure(boolean remote, Throwable failure) { - protocolSession.onFailure(QuicErrorCode.NO_ERROR.code(), "failure", failure); + protocolSession.onFailure(remote, QuicErrorCode.NO_ERROR.code(), "failure", failure); } /** diff --git a/jetty-core/jetty-quic/jetty-quic-common/src/main/java/org/eclipse/jetty/quic/common/QuicStreamEndPoint.java b/jetty-core/jetty-quic/jetty-quic-common/src/main/java/org/eclipse/jetty/quic/common/QuicStreamEndPoint.java index b8a2a731a63e..ba35b851f2d9 100644 --- a/jetty-core/jetty-quic/jetty-quic-common/src/main/java/org/eclipse/jetty/quic/common/QuicStreamEndPoint.java +++ b/jetty-core/jetty-quic/jetty-quic-common/src/main/java/org/eclipse/jetty/quic/common/QuicStreamEndPoint.java @@ -13,6 +13,7 @@ package org.eclipse.jetty.quic.common; +import java.io.EOFException; import java.io.IOException; import java.net.SocketAddress; import java.nio.ByteBuffer; @@ -268,9 +269,20 @@ public boolean onReadable() QuicStreamEndPoint streamEndPoint = getQuicSession().getStreamEndPoint(streamId); if (streamEndPoint.isStreamFinished()) { + // Check if the stream was finished because of a reset frame. + boolean reset = false; + try + { + streamEndPoint.fill(BufferUtil.EMPTY_BUFFER); + } + catch (IOException x) + { + reset = x instanceof EOFException; + } + EofException e = new EofException(); streamEndPoint.getFillInterest().onFail(e); - streamEndPoint.getQuicSession().onFailure(e); + streamEndPoint.getQuicSession().onFailure(reset, e); } } return interested; diff --git a/jetty-core/jetty-quic/jetty-quic-quiche/jetty-quic-quiche-foreign/src/main/java/org/eclipse/jetty/quic/quiche/foreign/ForeignQuicheConnection.java b/jetty-core/jetty-quic/jetty-quic-quiche/jetty-quic-quiche-foreign/src/main/java/org/eclipse/jetty/quic/quiche/foreign/ForeignQuicheConnection.java index 78b6da1d6a1d..9aa018d0efe4 100644 --- a/jetty-core/jetty-quic/jetty-quic-quiche/jetty-quic-quiche-foreign/src/main/java/org/eclipse/jetty/quic/quiche/foreign/ForeignQuicheConnection.java +++ b/jetty-core/jetty-quic/jetty-quic-quiche/jetty-quic-quiche-foreign/src/main/java/org/eclipse/jetty/quic/quiche/foreign/ForeignQuicheConnection.java @@ -919,9 +919,12 @@ public int drainClearBytesForStream(long streamId, ByteBuffer buffer) throws IOE MemorySegment fin = scope.allocate(NativeHelper.C_CHAR); read = quiche_h.quiche_conn_stream_recv(quicheConn, streamId, bufferSegment, buffer.remaining(), fin); - int prevPosition = buffer.position(); - buffer.put(bufferSegment.asByteBuffer().limit((int)read)); - buffer.position(prevPosition); + if (read > 0) + { + int prevPosition = buffer.position(); + buffer.put(bufferSegment.asByteBuffer().limit((int)read)); + buffer.position(prevPosition); + } } } diff --git a/jetty-core/jetty-quic/jetty-quic-server/src/main/java/org/eclipse/jetty/quic/server/ServerProtocolSession.java b/jetty-core/jetty-quic/jetty-quic-server/src/main/java/org/eclipse/jetty/quic/server/ServerProtocolSession.java index 7bca0aa0fcac..fdd07eb6bb91 100644 --- a/jetty-core/jetty-quic/jetty-quic-server/src/main/java/org/eclipse/jetty/quic/server/ServerProtocolSession.java +++ b/jetty-core/jetty-quic/jetty-quic-server/src/main/java/org/eclipse/jetty/quic/server/ServerProtocolSession.java @@ -86,7 +86,7 @@ protected boolean onReadable(long readableStreamId) } @Override - protected void onFailure(long error, String reason, Throwable failure) + protected void onFailure(boolean remote, long error, String reason, Throwable failure) { // TODO: should probably reset the stream if it exists. } From ceaa74602eeadd31c741bfe36f251eebb5019c39 Mon Sep 17 00:00:00 2001 From: Ludovic Orban Date: Tue, 18 Jun 2024 11:54:17 +0200 Subject: [PATCH 10/22] add h3 test Signed-off-by: Ludovic Orban --- .../test/client/transport/AbstractTest.java | 102 +++++++++++++++++- .../client/transport/AsyncIOServletTest.java | 34 +++--- 2 files changed, 114 insertions(+), 22 deletions(-) diff --git a/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-client-transports/src/test/java/org/eclipse/jetty/ee10/test/client/transport/AbstractTest.java b/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-client-transports/src/test/java/org/eclipse/jetty/ee10/test/client/transport/AbstractTest.java index 25cc1d37d96c..104005b44619 100644 --- a/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-client-transports/src/test/java/org/eclipse/jetty/ee10/test/client/transport/AbstractTest.java +++ b/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-client-transports/src/test/java/org/eclipse/jetty/ee10/test/client/transport/AbstractTest.java @@ -21,7 +21,10 @@ import java.security.KeyStore; import java.util.Collection; import java.util.EnumSet; +import java.util.HashMap; import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; import java.util.function.Consumer; @@ -39,13 +42,16 @@ import org.eclipse.jetty.http.HttpScheme; import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.http.MetaData; +import org.eclipse.jetty.http2.ErrorCode; import org.eclipse.jetty.http2.HTTP2Cipher; -import org.eclipse.jetty.http2.api.Session; import org.eclipse.jetty.http2.client.HTTP2Client; import org.eclipse.jetty.http2.client.transport.HttpClientTransportOverHTTP2; +import org.eclipse.jetty.http2.frames.HeadersFrame; +import org.eclipse.jetty.http2.frames.ResetFrame; import org.eclipse.jetty.http2.server.AbstractHTTP2ServerConnectionFactory; import org.eclipse.jetty.http2.server.HTTP2CServerConnectionFactory; import org.eclipse.jetty.http2.server.HTTP2ServerConnectionFactory; +import org.eclipse.jetty.http3.HTTP3ErrorCode; import org.eclipse.jetty.http3.client.HTTP3Client; import org.eclipse.jetty.http3.client.transport.HttpClientTransportOverHTTP3; import org.eclipse.jetty.http3.server.AbstractHTTP3ServerConnectionFactory; @@ -66,6 +72,7 @@ import org.eclipse.jetty.server.SslConnectionFactory; import org.eclipse.jetty.toolchain.test.jupiter.WorkDir; import org.eclipse.jetty.toolchain.test.jupiter.WorkDirExtension; +import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.FuturePromise; import org.eclipse.jetty.util.SocketAddressResolver; import org.eclipse.jetty.util.component.LifeCycle; @@ -88,7 +95,11 @@ public class AbstractTest protected AbstractConnector connector; protected ServletContextHandler servletContextHandler; protected HttpClient client; - protected HTTP2Client http2Client; + + private HTTP2Client http2Client; + private final Map h2Streams = new HashMap<>(); + private HTTP3Client http3Client; + private final Map h3Streams = new HashMap<>(); public static Collection transports() { @@ -120,6 +131,14 @@ public static Collection transportsSecure() return transports; } + public static Collection transportsWithStreams() + { + EnumSet transports = EnumSet.of(Transport.H2C, Transport.H3); + if ("ci".equals(System.getProperty("env"))) + transports.remove(Transport.H3); + return transports; + } + @BeforeEach public void prepare() { @@ -129,6 +148,8 @@ public void prepare() @AfterEach public void dispose() { + h2Streams.clear(); + h3Streams.clear(); LifeCycle.stop(client); LifeCycle.stop(server); } @@ -207,16 +228,87 @@ protected void startClient(Transport transport, Consumer consumer) t client.start(); } - protected Session newHttp2ClientSession(Session.Listener listener) throws Exception + protected long newRequestOnStream(Transport transport) throws Exception + { + switch (transport) + { + case H2C, H2 -> + { + return sendHeadersWithNewH2Stream(); + } + case H3 -> + { + return sendHeadersWithNewH3Stream(); + } + default -> throw new IllegalArgumentException("Transport does not support streams: " + transport); + } + } + + private int sendHeadersWithNewH2Stream() throws Exception + { + org.eclipse.jetty.http2.api.Session session = newHttp2ClientSession(new org.eclipse.jetty.http2.api.Session.Listener() {}); + MetaData.Request metaData = newRequest("GET", HttpFields.EMPTY); + HeadersFrame frame = new HeadersFrame(metaData, null, false); + FuturePromise promise = new FuturePromise<>(); + session.newStream(frame, promise, null); + org.eclipse.jetty.http2.api.Stream stream = promise.get(5, TimeUnit.SECONDS); + int streamId = stream.getId(); + h2Streams.put(streamId, stream); + return streamId; + } + + private long sendHeadersWithNewH3Stream() throws Exception + { + org.eclipse.jetty.http3.api.Session.Client session = newHttp3ClientSession(new org.eclipse.jetty.http3.api.Session.Client.Listener() {}); + MetaData.Request metaData = newRequest("GET", HttpFields.EMPTY); + CompletableFuture cf = session.newRequest(new org.eclipse.jetty.http3.frames.HeadersFrame(metaData, false), null); + org.eclipse.jetty.http3.api.Stream stream = cf.get(5, TimeUnit.SECONDS); + long streamId = stream.getId(); + h3Streams.put(streamId, stream); + return streamId; + } + + protected void resetStream(Transport transport, long streamId) + { + switch (transport) + { + case H2C, H2 -> resetH2Stream((int)streamId); + case H3 -> resetH3Stream(streamId); + default -> throw new IllegalArgumentException("Transport does not support streams: " + transport); + } + } + + private void resetH2Stream(int streamId) + { + org.eclipse.jetty.http2.api.Stream stream = h2Streams.get(streamId); + stream.reset(new ResetFrame(streamId, ErrorCode.CANCEL_STREAM_ERROR.code), Callback.NOOP); + } + + private void resetH3Stream(long streamId) + { + org.eclipse.jetty.http3.api.Stream stream = h3Streams.get(streamId); + stream.reset(HTTP3ErrorCode.REQUEST_CANCELLED_ERROR.code(), new Exception(getClass().getSimpleName() + " reset")); + } + + private org.eclipse.jetty.http2.api.Session newHttp2ClientSession(org.eclipse.jetty.http2.api.Session.Listener listener) throws Exception { String host = "localhost"; int port = ((NetworkConnector)connector).getLocalPort(); InetSocketAddress address = new InetSocketAddress(host, port); - FuturePromise promise = new FuturePromise<>(); + FuturePromise promise = new FuturePromise<>(); http2Client.connect(address, listener, promise); return promise.get(5, TimeUnit.SECONDS); } + private org.eclipse.jetty.http3.api.Session.Client newHttp3ClientSession(org.eclipse.jetty.http3.api.Session.Client.Listener listener) throws Exception + { + String host = "localhost"; + int port = ((NetworkConnector)connector).getLocalPort(); + InetSocketAddress address = new InetSocketAddress(host, port); + CompletableFuture cf = http3Client.connect(address, listener); + return cf.get(5, TimeUnit.SECONDS); + } + protected MetaData.Request newRequest(String method, HttpFields fields) { return newRequest(method, "/", fields); @@ -313,7 +405,7 @@ protected HttpClientTransport newHttpClientTransport(Transport transport) throws SslContextFactory.Client sslContextFactory = newSslContextFactoryClient(); clientConnector.setSslContextFactory(sslContextFactory); Path clientPemDirectory = Files.createDirectories(pemDir.resolve("client")); - HTTP3Client http3Client = new HTTP3Client(new ClientQuicConfiguration(sslContextFactory, clientPemDirectory)); + http3Client = new HTTP3Client(new ClientQuicConfiguration(sslContextFactory, clientPemDirectory)); yield new HttpClientTransportOverHTTP3(http3Client); } case FCGI -> new HttpClientTransportOverFCGI(1, ""); diff --git a/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-client-transports/src/test/java/org/eclipse/jetty/ee10/test/client/transport/AsyncIOServletTest.java b/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-client-transports/src/test/java/org/eclipse/jetty/ee10/test/client/transport/AsyncIOServletTest.java index db0f3ebef975..aabe1d99071e 100644 --- a/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-client-transports/src/test/java/org/eclipse/jetty/ee10/test/client/transport/AsyncIOServletTest.java +++ b/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-client-transports/src/test/java/org/eclipse/jetty/ee10/test/client/transport/AsyncIOServletTest.java @@ -56,19 +56,13 @@ import org.eclipse.jetty.client.StringRequestContent; import org.eclipse.jetty.client.transport.internal.HttpConnectionOverHTTP; import org.eclipse.jetty.ee10.servlet.HttpOutput; -import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpHeaderValue; import org.eclipse.jetty.http.HttpMethod; import org.eclipse.jetty.http.HttpStatus; -import org.eclipse.jetty.http.MetaData; -import org.eclipse.jetty.http2.ErrorCode; import org.eclipse.jetty.http2.HTTP2Session; import org.eclipse.jetty.http2.api.Session; -import org.eclipse.jetty.http2.api.Stream; import org.eclipse.jetty.http2.client.transport.internal.HttpConnectionOverHTTP2; -import org.eclipse.jetty.http2.frames.HeadersFrame; -import org.eclipse.jetty.http2.frames.ResetFrame; import org.eclipse.jetty.io.Connection; import org.eclipse.jetty.io.EofException; import org.eclipse.jetty.logging.StacklessLogging; @@ -84,7 +78,6 @@ import org.junit.jupiter.api.Disabled; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; -import org.junit.jupiter.params.provider.ValueSource; import static org.awaitility.Awaitility.await; import static org.hamcrest.MatcherAssert.assertThat; @@ -1545,15 +1538,27 @@ public void onError(Throwable x) } @ParameterizedTest - @ValueSource(booleans = {true, false}) - public void testStartAsyncThenClientResetRemoteErrorNotification(boolean notify) throws Exception + @MethodSource("transportsWithStreams") + public void testStartAsyncThenClientResetRemoteErrorDoesNotify(Transport transport) throws Exception + { + testStartAsyncThenClientResetRemoteErrorNotification(transport, true); + } + + @ParameterizedTest + @MethodSource("transportsWithStreams") + public void testStartAsyncThenClientResetRemoteErrorDoesNotNotify(Transport transport) throws Exception + { + testStartAsyncThenClientResetRemoteErrorNotification(transport, false); + } + + private void testStartAsyncThenClientResetRemoteErrorNotification(Transport transport, boolean notify) throws Exception { httpConfig.setNotifyRemoteAsyncErrors(notify); AtomicReference errorAsyncEventRef = new AtomicReference<>(); AtomicReference responseRef = new AtomicReference<>(); CountDownLatch latch = new CountDownLatch(1); - start(Transport.H2C, new HttpServlet() + start(transport, new HttpServlet() { @Override protected void service(HttpServletRequest request, HttpServletResponse response) @@ -1588,18 +1593,13 @@ public void onStartAsync(AsyncEvent event) } }); - Session session = newHttp2ClientSession(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, null); - Stream stream = promise.get(5, TimeUnit.SECONDS); + long streamId = newRequestOnStream(transport); // Wait for the server to be in ASYNC_WAIT. assertTrue(latch.await(5, TimeUnit.SECONDS)); sleep(500); - stream.reset(new ResetFrame(stream.getId(), ErrorCode.CANCEL_STREAM_ERROR.code), Callback.NOOP); + resetStream(transport, streamId); if (notify) // Wait for the reset to be notified to the async context listener. From 09ea4e9f1c35250d48ca7b481b8fe1170a80b1df Mon Sep 17 00:00:00 2001 From: Ludovic Orban Date: Tue, 18 Jun 2024 14:16:01 +0200 Subject: [PATCH 11/22] add h3 test Signed-off-by: Ludovic Orban --- .../main/java/org/eclipse/jetty/ee9/nested/HttpOutput.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/HttpOutput.java b/jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/HttpOutput.java index c69bb3664108..b326486e504e 100644 --- a/jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/HttpOutput.java +++ b/jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/HttpOutput.java @@ -740,7 +740,9 @@ public void flush() throws IOException catch (Throwable t) { onWriteComplete(false, t); - throw t; + if (t instanceof IOException) + throw t; + throw new IOException(t); } } } From ecd9e98da3412193a3c81edcaef732010a782283 Mon Sep 17 00:00:00 2001 From: Ludovic Orban Date: Tue, 18 Jun 2024 14:17:18 +0200 Subject: [PATCH 12/22] fix checkstyle Signed-off-by: Ludovic Orban --- .../src/main/java/org/eclipse/jetty/http3/HTTP3Session.java | 2 +- .../main/java/org/eclipse/jetty/http3/parser/MessageParser.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/jetty-core/jetty-http3/jetty-http3-common/src/main/java/org/eclipse/jetty/http3/HTTP3Session.java b/jetty-core/jetty-http3/jetty-http3-common/src/main/java/org/eclipse/jetty/http3/HTTP3Session.java index 035d706b6332..ab6239811c5a 100644 --- a/jetty-core/jetty-http3/jetty-http3-common/src/main/java/org/eclipse/jetty/http3/HTTP3Session.java +++ b/jetty-core/jetty-http3/jetty-http3-common/src/main/java/org/eclipse/jetty/http3/HTTP3Session.java @@ -439,7 +439,7 @@ public void onData(long streamId, DataFrame frame) if (stream != null) stream.onData(frame); else - onSessionFailure(HTTP3ErrorCode.FRAME_UNEXPECTED_ERROR.code(), false,"invalid_frame_sequence", new IllegalStateException("invalid frame sequence")); + onSessionFailure(HTTP3ErrorCode.FRAME_UNEXPECTED_ERROR.code(), false, "invalid_frame_sequence", new IllegalStateException("invalid frame sequence")); } @Override diff --git a/jetty-core/jetty-http3/jetty-http3-common/src/main/java/org/eclipse/jetty/http3/parser/MessageParser.java b/jetty-core/jetty-http3/jetty-http3-common/src/main/java/org/eclipse/jetty/http3/parser/MessageParser.java index 3d144696b919..15b2a1c18b87 100644 --- a/jetty-core/jetty-http3/jetty-http3-common/src/main/java/org/eclipse/jetty/http3/parser/MessageParser.java +++ b/jetty-core/jetty-http3/jetty-http3-common/src/main/java/org/eclipse/jetty/http3/parser/MessageParser.java @@ -147,7 +147,7 @@ public Result parse(ByteBuffer buffer) // SPEC: control frames on a message stream are invalid. if (LOG.isDebugEnabled()) LOG.debug("invalid control frame type {} on message stream", Long.toHexString(frameType)); - sessionFailure(buffer, false, HTTP3ErrorCode.FRAME_UNEXPECTED_ERROR.code(),"invalid_frame_type", new IOException("invalid control frame in message stream")); + sessionFailure(buffer, false, HTTP3ErrorCode.FRAME_UNEXPECTED_ERROR.code(), "invalid_frame_type", new IOException("invalid control frame in message stream")); return Result.NO_FRAME; } From bf492f4f6b3ffcc348d3ee02761f895e705bbf25 Mon Sep 17 00:00:00 2001 From: Ludovic Orban Date: Tue, 18 Jun 2024 14:59:04 +0200 Subject: [PATCH 13/22] add ee9 tests Signed-off-by: Ludovic Orban --- .../jetty/quic/common/QuicStreamEndPoint.java | 3 +- .../jetty-ee9-test-client-transports/pom.xml | 2 +- .../test/client/transport/AbstractTest.java | 103 +++++++++++++++++- .../client/transport/AsyncIOServletTest.java | 34 +++--- 4 files changed, 118 insertions(+), 24 deletions(-) diff --git a/jetty-core/jetty-quic/jetty-quic-common/src/main/java/org/eclipse/jetty/quic/common/QuicStreamEndPoint.java b/jetty-core/jetty-quic/jetty-quic-common/src/main/java/org/eclipse/jetty/quic/common/QuicStreamEndPoint.java index ba35b851f2d9..465a2af0c393 100644 --- a/jetty-core/jetty-quic/jetty-quic-common/src/main/java/org/eclipse/jetty/quic/common/QuicStreamEndPoint.java +++ b/jetty-core/jetty-quic/jetty-quic-common/src/main/java/org/eclipse/jetty/quic/common/QuicStreamEndPoint.java @@ -42,6 +42,7 @@ public class QuicStreamEndPoint extends AbstractEndPoint { private static final Logger LOG = LoggerFactory.getLogger(QuicStreamEndPoint.class); private static final ByteBuffer LAST_FLAG = ByteBuffer.allocate(0); + private static final ByteBuffer EMPTY_WRITABLE_BUFFER = ByteBuffer.allocate(0); private final QuicSession session; private final long streamId; @@ -273,7 +274,7 @@ public boolean onReadable() boolean reset = false; try { - streamEndPoint.fill(BufferUtil.EMPTY_BUFFER); + streamEndPoint.fill(EMPTY_WRITABLE_BUFFER); } catch (IOException x) { diff --git a/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-client-transports/pom.xml b/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-client-transports/pom.xml index e98c0af0c962..4a28e6e5dfd9 100644 --- a/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-client-transports/pom.xml +++ b/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-client-transports/pom.xml @@ -112,7 +112,7 @@ @{argLine} ${jetty.surefire.argLine} - --enable-native-access org.eclipse.jetty.quic.quiche.foreign + --enable-native-access=ALL-UNNAMED diff --git a/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-client-transports/src/test/java/org/eclipse/jetty/ee9/test/client/transport/AbstractTest.java b/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-client-transports/src/test/java/org/eclipse/jetty/ee9/test/client/transport/AbstractTest.java index e7e3b81d429e..d3799a0a84de 100644 --- a/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-client-transports/src/test/java/org/eclipse/jetty/ee9/test/client/transport/AbstractTest.java +++ b/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-client-transports/src/test/java/org/eclipse/jetty/ee9/test/client/transport/AbstractTest.java @@ -25,8 +25,11 @@ import java.security.cert.CertificateException; import java.util.Collection; import java.util.EnumSet; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Objects; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; import jakarta.servlet.http.HttpServlet; @@ -43,13 +46,17 @@ import org.eclipse.jetty.http.HttpScheme; import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.http.MetaData; +import org.eclipse.jetty.http2.ErrorCode; import org.eclipse.jetty.http2.HTTP2Cipher; -import org.eclipse.jetty.http2.api.Session; import org.eclipse.jetty.http2.client.HTTP2Client; import org.eclipse.jetty.http2.client.transport.HttpClientTransportOverHTTP2; +import org.eclipse.jetty.http2.frames.HeadersFrame; +import org.eclipse.jetty.http2.frames.ResetFrame; import org.eclipse.jetty.http2.server.AbstractHTTP2ServerConnectionFactory; import org.eclipse.jetty.http2.server.HTTP2CServerConnectionFactory; import org.eclipse.jetty.http2.server.HTTP2ServerConnectionFactory; +import org.eclipse.jetty.http3.HTTP3ErrorCode; +import org.eclipse.jetty.http3.api.Stream; import org.eclipse.jetty.http3.client.HTTP3Client; import org.eclipse.jetty.http3.client.transport.HttpClientTransportOverHTTP3; import org.eclipse.jetty.http3.server.AbstractHTTP3ServerConnectionFactory; @@ -70,6 +77,7 @@ import org.eclipse.jetty.server.SslConnectionFactory; import org.eclipse.jetty.toolchain.test.jupiter.WorkDir; import org.eclipse.jetty.toolchain.test.jupiter.WorkDirExtension; +import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.FuturePromise; import org.eclipse.jetty.util.SocketAddressResolver; import org.eclipse.jetty.util.component.LifeCycle; @@ -92,7 +100,11 @@ public class AbstractTest protected AbstractConnector connector; protected ServletContextHandler servletContextHandler; protected HttpClient client; - protected HTTP2Client http2Client; + + private HTTP2Client http2Client; + private final Map h2Streams = new HashMap<>(); + private HTTP3Client http3Client; + private final Map h3Streams = new HashMap<>(); public static Collection transports() { @@ -116,6 +128,14 @@ public static Collection transportsWithPushSupport() return transports; } + public static Collection transportsWithStreams() + { + EnumSet transports = EnumSet.of(Transport.H2C, Transport.H3); + if ("ci".equals(System.getProperty("env"))) + transports.remove(Transport.H3); + return transports; + } + @BeforeEach public void prepare() { @@ -125,6 +145,8 @@ public void prepare() @AfterEach public void dispose() { + h2Streams.clear(); + h3Streams.clear(); LifeCycle.stop(client); LifeCycle.stop(server); } @@ -206,16 +228,87 @@ protected void startClient(Transport transport) throws Exception client.start(); } - protected Session newHttp2ClientSession(Session.Listener listener) throws Exception + protected long newRequestOnStream(Transport transport) throws Exception + { + switch (transport) + { + case H2C, H2 -> + { + return sendHeadersWithNewH2Stream(); + } + case H3 -> + { + return sendHeadersWithNewH3Stream(); + } + default -> throw new IllegalArgumentException("Transport does not support streams: " + transport); + } + } + + private int sendHeadersWithNewH2Stream() throws Exception + { + org.eclipse.jetty.http2.api.Session session = newHttp2ClientSession(new org.eclipse.jetty.http2.api.Session.Listener() {}); + MetaData.Request metaData = newRequest("GET", HttpFields.EMPTY); + HeadersFrame frame = new HeadersFrame(metaData, null, false); + FuturePromise promise = new FuturePromise<>(); + session.newStream(frame, promise, null); + org.eclipse.jetty.http2.api.Stream stream = promise.get(5, TimeUnit.SECONDS); + int streamId = stream.getId(); + h2Streams.put(streamId, stream); + return streamId; + } + + private long sendHeadersWithNewH3Stream() throws Exception + { + org.eclipse.jetty.http3.api.Session.Client session = newHttp3ClientSession(new org.eclipse.jetty.http3.api.Session.Client.Listener() {}); + MetaData.Request metaData = newRequest("GET", HttpFields.EMPTY); + CompletableFuture cf = session.newRequest(new org.eclipse.jetty.http3.frames.HeadersFrame(metaData, false), null); + org.eclipse.jetty.http3.api.Stream stream = cf.get(5, TimeUnit.SECONDS); + long streamId = stream.getId(); + h3Streams.put(streamId, stream); + return streamId; + } + + protected void resetStream(Transport transport, long streamId) + { + switch (transport) + { + case H2C, H2 -> resetH2Stream((int)streamId); + case H3 -> resetH3Stream(streamId); + default -> throw new IllegalArgumentException("Transport does not support streams: " + transport); + } + } + + private void resetH2Stream(int streamId) + { + org.eclipse.jetty.http2.api.Stream stream = h2Streams.get(streamId); + stream.reset(new ResetFrame(streamId, ErrorCode.CANCEL_STREAM_ERROR.code), Callback.NOOP); + } + + private void resetH3Stream(long streamId) + { + org.eclipse.jetty.http3.api.Stream stream = h3Streams.get(streamId); + stream.reset(HTTP3ErrorCode.REQUEST_CANCELLED_ERROR.code(), new Exception(getClass().getSimpleName() + " reset")); + } + + private org.eclipse.jetty.http2.api.Session newHttp2ClientSession(org.eclipse.jetty.http2.api.Session.Listener listener) throws Exception { String host = "localhost"; int port = ((NetworkConnector)connector).getLocalPort(); InetSocketAddress address = new InetSocketAddress(host, port); - FuturePromise promise = new FuturePromise<>(); + FuturePromise promise = new FuturePromise<>(); http2Client.connect(address, listener, promise); return promise.get(5, TimeUnit.SECONDS); } + private org.eclipse.jetty.http3.api.Session.Client newHttp3ClientSession(org.eclipse.jetty.http3.api.Session.Client.Listener listener) throws Exception + { + String host = "localhost"; + int port = ((NetworkConnector)connector).getLocalPort(); + InetSocketAddress address = new InetSocketAddress(host, port); + CompletableFuture cf = http3Client.connect(address, listener); + return cf.get(5, TimeUnit.SECONDS); + } + protected MetaData.Request newRequest(String method, HttpFields fields) { return newRequest(method, "/", fields); @@ -311,7 +404,7 @@ protected HttpClientTransport newHttpClientTransport(Transport transport) throws SslContextFactory.Client sslContextFactory = newSslContextFactoryClient(); clientConnector.setSslContextFactory(sslContextFactory); Path clientPemDirectory = Files.createDirectories(pemDir.resolve("client")); - HTTP3Client http3Client = new HTTP3Client(new ClientQuicConfiguration(sslContextFactory, clientPemDirectory)); + http3Client = new HTTP3Client(new ClientQuicConfiguration(sslContextFactory, clientPemDirectory)); yield new HttpClientTransportOverHTTP3(http3Client); } case FCGI -> new HttpClientTransportOverFCGI(1, ""); diff --git a/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-client-transports/src/test/java/org/eclipse/jetty/ee9/test/client/transport/AsyncIOServletTest.java b/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-client-transports/src/test/java/org/eclipse/jetty/ee9/test/client/transport/AsyncIOServletTest.java index 11204d699bbc..e4a674b6b846 100644 --- a/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-client-transports/src/test/java/org/eclipse/jetty/ee9/test/client/transport/AsyncIOServletTest.java +++ b/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-client-transports/src/test/java/org/eclipse/jetty/ee9/test/client/transport/AsyncIOServletTest.java @@ -58,19 +58,13 @@ import org.eclipse.jetty.ee9.nested.ContextHandler; import org.eclipse.jetty.ee9.nested.HttpInput; import org.eclipse.jetty.ee9.nested.HttpOutput; -import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpHeaderValue; import org.eclipse.jetty.http.HttpMethod; import org.eclipse.jetty.http.HttpStatus; -import org.eclipse.jetty.http.MetaData; -import org.eclipse.jetty.http2.ErrorCode; import org.eclipse.jetty.http2.HTTP2Session; import org.eclipse.jetty.http2.api.Session; -import org.eclipse.jetty.http2.api.Stream; import org.eclipse.jetty.http2.client.transport.internal.HttpConnectionOverHTTP2; -import org.eclipse.jetty.http2.frames.HeadersFrame; -import org.eclipse.jetty.http2.frames.ResetFrame; import org.eclipse.jetty.io.Connection; import org.eclipse.jetty.io.EofException; import org.eclipse.jetty.logging.StacklessLogging; @@ -84,7 +78,6 @@ import org.junit.jupiter.api.Disabled; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; -import org.junit.jupiter.params.provider.ValueSource; import static java.nio.ByteBuffer.wrap; import static org.awaitility.Awaitility.await; @@ -1872,15 +1865,27 @@ public void onError(Throwable x) } @ParameterizedTest - @ValueSource(booleans = {true, false}) - public void testStartAsyncThenClientResetRemoteErrorNotification(boolean notify) throws Exception + @MethodSource("transportsWithStreams") + public void testStartAsyncThenClientResetRemoteErrorDoesNotify(Transport transport) throws Exception + { + testStartAsyncThenClientResetRemoteErrorNotification(transport, true); + } + + @ParameterizedTest + @MethodSource("transportsWithStreams") + public void testStartAsyncThenClientResetRemoteErrorDoesNotNotify(Transport transport) throws Exception + { + testStartAsyncThenClientResetRemoteErrorNotification(transport, false); + } + + private void testStartAsyncThenClientResetRemoteErrorNotification(Transport transport, boolean notify) throws Exception { httpConfig.setNotifyRemoteAsyncErrors(notify); AtomicReference errorAsyncEventRef = new AtomicReference<>(); AtomicReference responseRef = new AtomicReference<>(); CountDownLatch latch = new CountDownLatch(1); - start(Transport.H2C, new HttpServlet() + start(transport, new HttpServlet() { @Override protected void service(HttpServletRequest request, HttpServletResponse response) @@ -1915,18 +1920,13 @@ public void onStartAsync(AsyncEvent event) } }); - Session session = newHttp2ClientSession(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, null); - Stream stream = promise.get(5, TimeUnit.SECONDS); + long streamId = newRequestOnStream(transport); // Wait for the server to be in ASYNC_WAIT. assertTrue(latch.await(5, TimeUnit.SECONDS)); sleep(500); - stream.reset(new ResetFrame(stream.getId(), ErrorCode.CANCEL_STREAM_ERROR.code), Callback.NOOP); + resetStream(transport, streamId); if (notify) // Wait for the reset to be notified to the async context listener. From 1de023fc59c6b983cd6aeef580f513c305490a61 Mon Sep 17 00:00:00 2001 From: Ludovic Orban Date: Tue, 18 Jun 2024 15:03:18 +0200 Subject: [PATCH 14/22] remove unneeded code Signed-off-by: Ludovic Orban --- .../org/eclipse/jetty/http3/HTTP3StreamConnection.java | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/jetty-core/jetty-http3/jetty-http3-common/src/main/java/org/eclipse/jetty/http3/HTTP3StreamConnection.java b/jetty-core/jetty-http3/jetty-http3-common/src/main/java/org/eclipse/jetty/http3/HTTP3StreamConnection.java index 699e5eaf1c63..bbec04eea7ea 100644 --- a/jetty-core/jetty-http3/jetty-http3-common/src/main/java/org/eclipse/jetty/http3/HTTP3StreamConnection.java +++ b/jetty-core/jetty-http3/jetty-http3-common/src/main/java/org/eclipse/jetty/http3/HTTP3StreamConnection.java @@ -13,7 +13,6 @@ package org.eclipse.jetty.http3; -import java.io.EOFException; import java.io.IOException; import java.io.UncheckedIOException; import java.nio.ByteBuffer; @@ -151,15 +150,10 @@ private void processDataFrames(boolean setFillInterest) long error = HTTP3ErrorCode.REQUEST_CANCELLED_ERROR.code(); getEndPoint().close(error, x); // Notify the application that a failure happened. - parser.getListener().onStreamFailure(getEndPoint().getStreamId(), isReset(x), error, x); + parser.getListener().onStreamFailure(getEndPoint().getStreamId(), false, error, x); } } - private static boolean isReset(Throwable x) - { - return x.getCause() instanceof EOFException; - } - private void processNonDataFrames() { try @@ -244,7 +238,7 @@ private void processNonDataFrames() long error = HTTP3ErrorCode.REQUEST_CANCELLED_ERROR.code(); getEndPoint().close(error, x); // Notify the application that a failure happened. - parser.getListener().onStreamFailure(getEndPoint().getStreamId(), isReset(x), error, x); + parser.getListener().onStreamFailure(getEndPoint().getStreamId(), false, error, x); } } From efc71700e0f2e3f51d1c2625a1e24aa7d92cc01f Mon Sep 17 00:00:00 2001 From: Ludovic Orban Date: Tue, 18 Jun 2024 15:07:08 +0200 Subject: [PATCH 15/22] fix compilation Signed-off-by: Ludovic Orban --- .../java/org/eclipse/jetty/http3/tests/ClientServerTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jetty-core/jetty-http3/jetty-http3-tests/src/test/java/org/eclipse/jetty/http3/tests/ClientServerTest.java b/jetty-core/jetty-http3/jetty-http3-tests/src/test/java/org/eclipse/jetty/http3/tests/ClientServerTest.java index 7e0b11832a20..74edd5a5efa2 100644 --- a/jetty-core/jetty-http3/jetty-http3-tests/src/test/java/org/eclipse/jetty/http3/tests/ClientServerTest.java +++ b/jetty-core/jetty-http3/jetty-http3-tests/src/test/java/org/eclipse/jetty/http3/tests/ClientServerTest.java @@ -492,7 +492,7 @@ public void onSettings(Session session, SettingsFrame frame) clientSession.newRequest(new HeadersFrame(newRequest("/large"), true), new Stream.Client.Listener() { @Override - public void onFailure(Stream.Client stream, long error, Throwable failure) + public void onFailure(Stream.Client stream, boolean remote, long error, Throwable failure) { streamFailureLatch.countDown(); } From 9229515f0c5804ab9a4b32dcf32a7ac37fa44e10 Mon Sep 17 00:00:00 2001 From: Ludovic Orban Date: Tue, 18 Jun 2024 15:39:57 +0200 Subject: [PATCH 16/22] fix doc Signed-off-by: Ludovic Orban --- .../jetty/docs/programming/client/http3/HTTP3ClientDocs.java | 4 ++-- .../jetty/docs/programming/client/http3/HTTP3ClientDocs.java | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) 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 a18a2b14903c..dcc9ca374544 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 @@ -257,9 +257,9 @@ public void reset() throws Exception CompletableFuture streamCF = session.newRequest(headersFrame, new Stream.Client.Listener() { @Override - public void onFailure(Stream.Client stream, long error, Throwable failure) + public void onFailure(Stream.Client stream, boolean remote, long error, Throwable failure) { - // The server reset this stream. + // The server reset this stream when remote is true. } }); Stream stream = streamCF.get(); diff --git a/documentation/jetty/modules/code/examples/src/main/java/org/eclipse/jetty/docs/programming/client/http3/HTTP3ClientDocs.java b/documentation/jetty/modules/code/examples/src/main/java/org/eclipse/jetty/docs/programming/client/http3/HTTP3ClientDocs.java index a18a2b14903c..dcc9ca374544 100644 --- a/documentation/jetty/modules/code/examples/src/main/java/org/eclipse/jetty/docs/programming/client/http3/HTTP3ClientDocs.java +++ b/documentation/jetty/modules/code/examples/src/main/java/org/eclipse/jetty/docs/programming/client/http3/HTTP3ClientDocs.java @@ -257,9 +257,9 @@ public void reset() throws Exception CompletableFuture streamCF = session.newRequest(headersFrame, new Stream.Client.Listener() { @Override - public void onFailure(Stream.Client stream, long error, Throwable failure) + public void onFailure(Stream.Client stream, boolean remote, long error, Throwable failure) { - // The server reset this stream. + // The server reset this stream when remote is true. } }); Stream stream = streamCF.get(); From 2a63ee4c9f0b056e538233978b87bceef10dc3e8 Mon Sep 17 00:00:00 2001 From: Ludovic Orban Date: Tue, 18 Jun 2024 17:09:57 +0200 Subject: [PATCH 17/22] rewire h3 Signed-off-by: Ludovic Orban --- .../client/http3/HTTP3ClientDocs.java | 4 ++-- .../client/http3/HTTP3ClientDocs.java | 4 ++-- .../server/internal/HttpStreamOverHTTP2.java | 1 + .../internal/HttpReceiverOverHTTP3.java | 2 +- .../internal/SessionClientListener.java | 4 ++-- .../jetty/http3/client/ClientHTTP3Session.java | 6 +++--- .../jetty/http3/client/HTTP3StreamClient.java | 4 ++-- .../org/eclipse/jetty/http3/HTTP3Session.java | 18 +++++++++--------- .../org/eclipse/jetty/http3/HTTP3Stream.java | 8 ++++---- .../jetty/http3/HTTP3StreamConnection.java | 4 ++-- .../http3/InstructionStreamConnection.java | 12 ++++++------ .../org/eclipse/jetty/http3/api/Session.java | 2 +- .../org/eclipse/jetty/http3/api/Stream.java | 6 ++---- .../eclipse/jetty/http3/parser/BodyParser.java | 14 +++++++------- .../jetty/http3/parser/ControlParser.java | 8 ++++---- .../jetty/http3/parser/HeadersBodyParser.java | 6 +++--- .../jetty/http3/parser/MessageParser.java | 8 ++++---- .../jetty/http3/parser/ParserListener.java | 12 ++++++------ .../jetty/http3/parser/SettingsBodyParser.java | 8 ++++---- .../server/HTTP3ServerConnectionFactory.java | 8 ++++---- .../server/internal/HTTP3StreamServer.java | 4 ++-- .../server/internal/HttpStreamOverHTTP3.java | 5 ++++- .../server/internal/ServerHTTP3Session.java | 6 +++--- .../internal/ServerHTTP3StreamConnection.java | 4 ++-- .../jetty/http3/tests/ClientServerTest.java | 2 +- .../eclipse/jetty/http3/tests/GoAwayTest.java | 8 ++++---- .../http3/tests/StreamIdleTimeoutTest.java | 4 ++-- .../jetty/http3/tests/UnexpectedFrameTest.java | 2 +- .../quic/client/ClientProtocolSession.java | 2 +- .../jetty/quic/common/ProtocolSession.java | 2 +- .../jetty/quic/common/QuicConnection.java | 2 +- .../eclipse/jetty/quic/common/QuicSession.java | 4 ++-- .../jetty/quic/common/QuicStreamEndPoint.java | 11 +++++------ .../quic/server/ServerProtocolSession.java | 2 +- 34 files changed, 99 insertions(+), 98 deletions(-) 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 dcc9ca374544..a18a2b14903c 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 @@ -257,9 +257,9 @@ public void reset() throws Exception CompletableFuture streamCF = session.newRequest(headersFrame, new Stream.Client.Listener() { @Override - public void onFailure(Stream.Client stream, boolean remote, long error, Throwable failure) + public void onFailure(Stream.Client stream, long error, Throwable failure) { - // The server reset this stream when remote is true. + // The server reset this stream. } }); Stream stream = streamCF.get(); diff --git a/documentation/jetty/modules/code/examples/src/main/java/org/eclipse/jetty/docs/programming/client/http3/HTTP3ClientDocs.java b/documentation/jetty/modules/code/examples/src/main/java/org/eclipse/jetty/docs/programming/client/http3/HTTP3ClientDocs.java index dcc9ca374544..a18a2b14903c 100644 --- a/documentation/jetty/modules/code/examples/src/main/java/org/eclipse/jetty/docs/programming/client/http3/HTTP3ClientDocs.java +++ b/documentation/jetty/modules/code/examples/src/main/java/org/eclipse/jetty/docs/programming/client/http3/HTTP3ClientDocs.java @@ -257,9 +257,9 @@ public void reset() throws Exception CompletableFuture streamCF = session.newRequest(headersFrame, new Stream.Client.Listener() { @Override - public void onFailure(Stream.Client stream, boolean remote, long error, Throwable failure) + public void onFailure(Stream.Client stream, long error, Throwable failure) { - // The server reset this stream when remote is true. + // The server reset this stream. } }); Stream stream = streamCF.get(); diff --git a/jetty-core/jetty-http2/jetty-http2-server/src/main/java/org/eclipse/jetty/http2/server/internal/HttpStreamOverHTTP2.java b/jetty-core/jetty-http2/jetty-http2-server/src/main/java/org/eclipse/jetty/http2/server/internal/HttpStreamOverHTTP2.java index 820b46d01ffc..89b98f9776a4 100644 --- a/jetty-core/jetty-http2/jetty-http2-server/src/main/java/org/eclipse/jetty/http2/server/internal/HttpStreamOverHTTP2.java +++ b/jetty-core/jetty-http2/jetty-http2-server/src/main/java/org/eclipse/jetty/http2/server/internal/HttpStreamOverHTTP2.java @@ -587,6 +587,7 @@ public void onTimeout(TimeoutException timeout, BiConsumer co @Override public Runnable onFailure(Throwable failure, boolean remote, Callback callback) { + // TODO failure.getCause() instanceof EOFException Runnable runnable = remote ? _httpChannel.onRemoteFailure(failure) : _httpChannel.onFailure(failure); return () -> { diff --git a/jetty-core/jetty-http3/jetty-http3-client-transport/src/main/java/org/eclipse/jetty/http3/client/transport/internal/HttpReceiverOverHTTP3.java b/jetty-core/jetty-http3/jetty-http3-client-transport/src/main/java/org/eclipse/jetty/http3/client/transport/internal/HttpReceiverOverHTTP3.java index bf91417bb900..594de886793c 100644 --- a/jetty-core/jetty-http3/jetty-http3-client-transport/src/main/java/org/eclipse/jetty/http3/client/transport/internal/HttpReceiverOverHTTP3.java +++ b/jetty-core/jetty-http3/jetty-http3-client-transport/src/main/java/org/eclipse/jetty/http3/client/transport/internal/HttpReceiverOverHTTP3.java @@ -154,7 +154,7 @@ public void onIdleTimeout(Stream.Client stream, Throwable failure, Promise prom } @Override - protected void notifyFailure(boolean remote, long error, Throwable failure) + protected void notifyFailure(long error, Throwable failure) { Listener listener = getListener(); try { if (listener != null) - listener.onFailure(this, remote, error, failure); + listener.onFailure(this, error, failure); } catch (Throwable x) { diff --git a/jetty-core/jetty-http3/jetty-http3-common/src/main/java/org/eclipse/jetty/http3/HTTP3Session.java b/jetty-core/jetty-http3/jetty-http3-common/src/main/java/org/eclipse/jetty/http3/HTTP3Session.java index ab6239811c5a..c6b1b9ce506a 100644 --- a/jetty-core/jetty-http3/jetty-http3-common/src/main/java/org/eclipse/jetty/http3/HTTP3Session.java +++ b/jetty-core/jetty-http3/jetty-http3-common/src/main/java/org/eclipse/jetty/http3/HTTP3Session.java @@ -439,7 +439,7 @@ public void onData(long streamId, DataFrame frame) if (stream != null) stream.onData(frame); else - onSessionFailure(HTTP3ErrorCode.FRAME_UNEXPECTED_ERROR.code(), false, "invalid_frame_sequence", new IllegalStateException("invalid frame sequence")); + onSessionFailure(HTTP3ErrorCode.FRAME_UNEXPECTED_ERROR.code(), "invalid_frame_sequence", new IllegalStateException("invalid frame sequence")); } @Override @@ -682,7 +682,7 @@ private void failStreams(Predicate predicate, long error, String re stream.reset(error, failure); // Since the stream failure was generated // by a GOAWAY, notify the application. - stream.onFailure(false, error, failure); + stream.onFailure(error, failure); }); } @@ -796,7 +796,7 @@ public void onClose(long error, String reason) failStreams(stream -> true, error, reason, false, failure); if (notifyFailure) - onSessionFailure(error, false, reason, failure); + onSessionFailure(error, reason, failure); notifyDisconnect(error, reason); } @@ -814,27 +814,27 @@ private void notifyDisconnect(long error, String reason) } @Override - public void onStreamFailure(long streamId, boolean remote, long error, Throwable failure) + public void onStreamFailure(long streamId, long error, Throwable failure) { if (LOG.isDebugEnabled()) LOG.debug("stream failure 0x{}/{} for stream #{} on {}", Long.toHexString(error), failure.getMessage(), streamId, this); HTTP3Stream stream = getStream(streamId); if (stream != null) - stream.onFailure(remote, error, failure); + stream.onFailure(error, failure); } @Override - public void onSessionFailure(long error, boolean remote, String reason, Throwable failure) + public void onSessionFailure(long error, String reason, Throwable failure) { - notifyFailure(error, remote, reason, failure); + notifyFailure(error, reason, failure); inwardClose(error, reason); } - private void notifyFailure(long error, boolean remote, String reason, Throwable failure) + private void notifyFailure(long error, String reason, Throwable failure) { try { - listener.onFailure(this, remote, error, reason, failure); + listener.onFailure(this, error, reason, failure); } catch (Throwable x) { diff --git a/jetty-core/jetty-http3/jetty-http3-common/src/main/java/org/eclipse/jetty/http3/HTTP3Stream.java b/jetty-core/jetty-http3/jetty-http3-common/src/main/java/org/eclipse/jetty/http3/HTTP3Stream.java index d2275927788b..2eadbe900122 100644 --- a/jetty-core/jetty-http3/jetty-http3-common/src/main/java/org/eclipse/jetty/http3/HTTP3Stream.java +++ b/jetty-core/jetty-http3/jetty-http3-common/src/main/java/org/eclipse/jetty/http3/HTTP3Stream.java @@ -370,13 +370,13 @@ public void onTrailer(HeadersFrame frame) protected abstract void notifyIdleTimeout(TimeoutException timeout, Promise promise); - public void onFailure(boolean remote, long error, Throwable failure) + public void onFailure(long error, Throwable failure) { - notifyFailure(remote, error, failure); + notifyFailure(error, failure); session.removeStream(this, failure); } - protected abstract void notifyFailure(boolean remote, long error, Throwable failure); + protected abstract void notifyFailure(long error, Throwable failure); protected boolean validateAndUpdate(EnumSet allowed, FrameState target) { @@ -392,7 +392,7 @@ protected boolean validateAndUpdate(EnumSet allowed, FrameState targ if (frameState == FrameState.FAILED) return false; frameState = FrameState.FAILED; - session.onSessionFailure(HTTP3ErrorCode.FRAME_UNEXPECTED_ERROR.code(), false, "invalid_frame_sequence", new IllegalStateException("invalid frame sequence")); + session.onSessionFailure(HTTP3ErrorCode.FRAME_UNEXPECTED_ERROR.code(), "invalid_frame_sequence", new IllegalStateException("invalid frame sequence")); return false; } } diff --git a/jetty-core/jetty-http3/jetty-http3-common/src/main/java/org/eclipse/jetty/http3/HTTP3StreamConnection.java b/jetty-core/jetty-http3/jetty-http3-common/src/main/java/org/eclipse/jetty/http3/HTTP3StreamConnection.java index bbec04eea7ea..29542da3d1eb 100644 --- a/jetty-core/jetty-http3/jetty-http3-common/src/main/java/org/eclipse/jetty/http3/HTTP3StreamConnection.java +++ b/jetty-core/jetty-http3/jetty-http3-common/src/main/java/org/eclipse/jetty/http3/HTTP3StreamConnection.java @@ -150,7 +150,7 @@ private void processDataFrames(boolean setFillInterest) long error = HTTP3ErrorCode.REQUEST_CANCELLED_ERROR.code(); getEndPoint().close(error, x); // Notify the application that a failure happened. - parser.getListener().onStreamFailure(getEndPoint().getStreamId(), false, error, x); + parser.getListener().onStreamFailure(getEndPoint().getStreamId(), error, x); } } @@ -238,7 +238,7 @@ private void processNonDataFrames() long error = HTTP3ErrorCode.REQUEST_CANCELLED_ERROR.code(); getEndPoint().close(error, x); // Notify the application that a failure happened. - parser.getListener().onStreamFailure(getEndPoint().getStreamId(), false, error, x); + parser.getListener().onStreamFailure(getEndPoint().getStreamId(), error, x); } } diff --git a/jetty-core/jetty-http3/jetty-http3-common/src/main/java/org/eclipse/jetty/http3/InstructionStreamConnection.java b/jetty-core/jetty-http3/jetty-http3-common/src/main/java/org/eclipse/jetty/http3/InstructionStreamConnection.java index 5f0a74ba1776..22f1a4e2d20d 100644 --- a/jetty-core/jetty-http3/jetty-http3-common/src/main/java/org/eclipse/jetty/http3/InstructionStreamConnection.java +++ b/jetty-core/jetty-http3/jetty-http3-common/src/main/java/org/eclipse/jetty/http3/InstructionStreamConnection.java @@ -109,28 +109,28 @@ else if (filled < 0) } catch (QpackException.SessionException x) { - fail(x.getErrorCode(), false, x.getMessage(), x); + fail(x.getErrorCode(), x.getMessage(), x); } catch (Throwable x) { - fail(HTTP3ErrorCode.INTERNAL_ERROR.code(), false, "internal_error", x); + fail(HTTP3ErrorCode.INTERNAL_ERROR.code(), "internal_error", x); } } - private void fail(long errorCode, boolean remote, String message, Throwable failure) + private void fail(long errorCode, String message, Throwable failure) { buffer.release(); buffer = null; if (LOG.isDebugEnabled()) LOG.debug("could not process instruction stream {}", getEndPoint(), failure); - notifySessionFailure(errorCode, remote, message, failure); + notifySessionFailure(errorCode, message, failure); } - protected void notifySessionFailure(long error, boolean remote, String reason, Throwable failure) + protected void notifySessionFailure(long error, String reason, Throwable failure) { try { - listener.onSessionFailure(error, remote, reason, failure); + listener.onSessionFailure(error, reason, failure); } catch (Throwable x) { diff --git a/jetty-core/jetty-http3/jetty-http3-common/src/main/java/org/eclipse/jetty/http3/api/Session.java b/jetty-core/jetty-http3/jetty-http3-common/src/main/java/org/eclipse/jetty/http3/api/Session.java index 015a22457c47..1dccc54ba10f 100644 --- a/jetty-core/jetty-http3/jetty-http3-common/src/main/java/org/eclipse/jetty/http3/api/Session.java +++ b/jetty-core/jetty-http3/jetty-http3-common/src/main/java/org/eclipse/jetty/http3/api/Session.java @@ -243,7 +243,7 @@ public default void onDisconnect(Session session, long error, String reason) * @param reason the failure reason * @param failure the failure */ - public default void onFailure(Session session, boolean remote, long error, String reason, Throwable failure) + public default void onFailure(Session session, long error, String reason, Throwable failure) { } } diff --git a/jetty-core/jetty-http3/jetty-http3-common/src/main/java/org/eclipse/jetty/http3/api/Stream.java b/jetty-core/jetty-http3/jetty-http3-common/src/main/java/org/eclipse/jetty/http3/api/Stream.java index 3129c905fd56..d2e83791404a 100644 --- a/jetty-core/jetty-http3/jetty-http3-common/src/main/java/org/eclipse/jetty/http3/api/Stream.java +++ b/jetty-core/jetty-http3/jetty-http3-common/src/main/java/org/eclipse/jetty/http3/api/Stream.java @@ -253,11 +253,10 @@ public default void onIdleTimeout(Client stream, Throwable failure, Promise * * @param stream the stream - * @param remote true if the error comes from a remote notification * @param error the failure error * @param failure the cause of the failure */ - public default void onFailure(Stream.Client stream, boolean remote, long error, Throwable failure) + public default void onFailure(Stream.Client stream, long error, Throwable failure) { } } @@ -370,11 +369,10 @@ public default void onIdleTimeout(Server stream, TimeoutException failure, Promi * the stream has been reset.

* * @param stream the stream - * @param remote true if the error comes from a remote notification * @param error the failure error * @param failure the cause of the failure */ - public default void onFailure(Server stream, boolean remote, long error, Throwable failure) + public default void onFailure(Stream.Server stream, long error, Throwable failure) { } } diff --git a/jetty-core/jetty-http3/jetty-http3-common/src/main/java/org/eclipse/jetty/http3/parser/BodyParser.java b/jetty-core/jetty-http3/jetty-http3-common/src/main/java/org/eclipse/jetty/http3/parser/BodyParser.java index f8941b2c39cd..9aa050d57936 100644 --- a/jetty-core/jetty-http3/jetty-http3-common/src/main/java/org/eclipse/jetty/http3/parser/BodyParser.java +++ b/jetty-core/jetty-http3/jetty-http3-common/src/main/java/org/eclipse/jetty/http3/parser/BodyParser.java @@ -66,20 +66,20 @@ protected long getBodyLength() protected void emptyBody(ByteBuffer buffer) { - sessionFailure(buffer, HTTP3ErrorCode.PROTOCOL_ERROR.code(), false, "invalid_frame", new IOException("invalid empty body frame")); + sessionFailure(buffer, HTTP3ErrorCode.PROTOCOL_ERROR.code(), "invalid_frame", new IOException("invalid empty body frame")); } - protected void sessionFailure(ByteBuffer buffer, long error, boolean remote, String reason, Throwable failure) + protected void sessionFailure(ByteBuffer buffer, long error, String reason, Throwable failure) { BufferUtil.clear(buffer); - notifySessionFailure(error, remote, reason, failure); + notifySessionFailure(error, reason, failure); } - protected void notifySessionFailure(long error, boolean remote, String reason, Throwable failure) + protected void notifySessionFailure(long error, String reason, Throwable failure) { try { - listener.onSessionFailure(error, remote, reason, failure); + listener.onSessionFailure(error, reason, failure); } catch (Throwable x) { @@ -87,11 +87,11 @@ protected void notifySessionFailure(long error, boolean remote, String reason, T } } - protected void notifyStreamFailure(long streamId, boolean remote, long error, Throwable failure) + protected void notifyStreamFailure(long streamId, long error, Throwable failure) { try { - listener.onStreamFailure(streamId, remote, error, failure); + listener.onStreamFailure(streamId, error, failure); } catch (Throwable x) { diff --git a/jetty-core/jetty-http3/jetty-http3-common/src/main/java/org/eclipse/jetty/http3/parser/ControlParser.java b/jetty-core/jetty-http3/jetty-http3-common/src/main/java/org/eclipse/jetty/http3/parser/ControlParser.java index 96f3d2091d09..9ad1873fc29a 100644 --- a/jetty-core/jetty-http3/jetty-http3-common/src/main/java/org/eclipse/jetty/http3/parser/ControlParser.java +++ b/jetty-core/jetty-http3/jetty-http3-common/src/main/java/org/eclipse/jetty/http3/parser/ControlParser.java @@ -88,7 +88,7 @@ public void parse(ByteBuffer buffer) // SPEC: message frames on the control stream are invalid. if (LOG.isDebugEnabled()) LOG.debug("invalid message frame type {} on control stream", Long.toHexString(frameType)); - sessionFailure(buffer, false, HTTP3ErrorCode.FRAME_UNEXPECTED_ERROR.code(), "invalid_frame_type", new IOException("invalid message frame on control stream")); + sessionFailure(buffer, HTTP3ErrorCode.FRAME_UNEXPECTED_ERROR.code(), "invalid_frame_type", new IOException("invalid message frame on control stream")); return; } @@ -135,13 +135,13 @@ public void parse(ByteBuffer buffer) { if (LOG.isDebugEnabled()) LOG.debug("parse failed", x); - sessionFailure(buffer, false, HTTP3ErrorCode.INTERNAL_ERROR.code(), "parser_error", x); + sessionFailure(buffer, HTTP3ErrorCode.INTERNAL_ERROR.code(), "parser_error", x); } } - private void sessionFailure(ByteBuffer buffer, boolean remote, long error, String reason, Throwable failure) + private void sessionFailure(ByteBuffer buffer, long error, String reason, Throwable failure) { - unknownBodyParser.sessionFailure(buffer, error, remote, reason, failure); + unknownBodyParser.sessionFailure(buffer, error, reason, failure); } private enum State diff --git a/jetty-core/jetty-http3/jetty-http3-common/src/main/java/org/eclipse/jetty/http3/parser/HeadersBodyParser.java b/jetty-core/jetty-http3/jetty-http3-common/src/main/java/org/eclipse/jetty/http3/parser/HeadersBodyParser.java index 7d73a934ccab..686c20712ec2 100644 --- a/jetty-core/jetty-http3/jetty-http3-common/src/main/java/org/eclipse/jetty/http3/parser/HeadersBodyParser.java +++ b/jetty-core/jetty-http3/jetty-http3-common/src/main/java/org/eclipse/jetty/http3/parser/HeadersBodyParser.java @@ -127,19 +127,19 @@ private boolean decode(ByteBuffer encoded, boolean last) { if (LOG.isDebugEnabled()) LOG.debug("decode failure", x); - notifyStreamFailure(streamId, false, x.getErrorCode(), x); + notifyStreamFailure(streamId, x.getErrorCode(), x); } catch (QpackException.SessionException x) { if (LOG.isDebugEnabled()) LOG.debug("decode failure", x); - notifySessionFailure(x.getErrorCode(), false, x.getMessage(), x); + notifySessionFailure(x.getErrorCode(), x.getMessage(), x); } catch (Throwable x) { if (LOG.isDebugEnabled()) LOG.debug("decode failure", x); - notifySessionFailure(HTTP3ErrorCode.INTERNAL_ERROR.code(), false, "internal_error", x); + notifySessionFailure(HTTP3ErrorCode.INTERNAL_ERROR.code(), "internal_error", x); } return false; } diff --git a/jetty-core/jetty-http3/jetty-http3-common/src/main/java/org/eclipse/jetty/http3/parser/MessageParser.java b/jetty-core/jetty-http3/jetty-http3-common/src/main/java/org/eclipse/jetty/http3/parser/MessageParser.java index 15b2a1c18b87..2c364a187e25 100644 --- a/jetty-core/jetty-http3/jetty-http3-common/src/main/java/org/eclipse/jetty/http3/parser/MessageParser.java +++ b/jetty-core/jetty-http3/jetty-http3-common/src/main/java/org/eclipse/jetty/http3/parser/MessageParser.java @@ -147,7 +147,7 @@ public Result parse(ByteBuffer buffer) // SPEC: control frames on a message stream are invalid. if (LOG.isDebugEnabled()) LOG.debug("invalid control frame type {} on message stream", Long.toHexString(frameType)); - sessionFailure(buffer, false, HTTP3ErrorCode.FRAME_UNEXPECTED_ERROR.code(), "invalid_frame_type", new IOException("invalid control frame in message stream")); + sessionFailure(buffer, HTTP3ErrorCode.FRAME_UNEXPECTED_ERROR.code(), "invalid_frame_type", new IOException("invalid control frame in message stream")); return Result.NO_FRAME; } @@ -207,14 +207,14 @@ public Result parse(ByteBuffer buffer) { if (LOG.isDebugEnabled()) LOG.debug("parse failed", x); - sessionFailure(buffer, false, HTTP3ErrorCode.INTERNAL_ERROR.code(), "parser_error", x); + sessionFailure(buffer, HTTP3ErrorCode.INTERNAL_ERROR.code(), "parser_error", x); return Result.NO_FRAME; } } - private void sessionFailure(ByteBuffer buffer, boolean remote, long error, String reason, Throwable failure) + private void sessionFailure(ByteBuffer buffer, long error, String reason, Throwable failure) { - unknownBodyParser.sessionFailure(buffer, error, remote, reason, failure); + unknownBodyParser.sessionFailure(buffer, error, reason, failure); } public enum Result diff --git a/jetty-core/jetty-http3/jetty-http3-common/src/main/java/org/eclipse/jetty/http3/parser/ParserListener.java b/jetty-core/jetty-http3/jetty-http3-common/src/main/java/org/eclipse/jetty/http3/parser/ParserListener.java index d15ac9204d83..2db2e77a1357 100644 --- a/jetty-core/jetty-http3/jetty-http3-common/src/main/java/org/eclipse/jetty/http3/parser/ParserListener.java +++ b/jetty-core/jetty-http3/jetty-http3-common/src/main/java/org/eclipse/jetty/http3/parser/ParserListener.java @@ -36,11 +36,11 @@ public default void onGoAway(GoAwayFrame frame) { } - public default void onStreamFailure(long streamId, boolean remote, long error, Throwable failure) + public default void onStreamFailure(long streamId, long error, Throwable failure) { } - public default void onSessionFailure(long error, boolean remote, String reason, Throwable failure) + public default void onSessionFailure(long error, String reason, Throwable failure) { } @@ -72,15 +72,15 @@ public void onSettings(SettingsFrame frame) } @Override - public void onStreamFailure(long streamId, boolean remote, long error, Throwable failure) + public void onStreamFailure(long streamId, long error, Throwable failure) { - listener.onStreamFailure(streamId, remote, error, failure); + listener.onStreamFailure(streamId, error, failure); } @Override - public void onSessionFailure(long error, boolean remote, String reason, Throwable failure) + public void onSessionFailure(long error, String reason, Throwable failure) { - listener.onSessionFailure(error, remote, reason, failure); + listener.onSessionFailure(error, reason, failure); } } } diff --git a/jetty-core/jetty-http3/jetty-http3-common/src/main/java/org/eclipse/jetty/http3/parser/SettingsBodyParser.java b/jetty-core/jetty-http3/jetty-http3-common/src/main/java/org/eclipse/jetty/http3/parser/SettingsBodyParser.java index fb467bb6d24a..8ca5569c8b11 100644 --- a/jetty-core/jetty-http3/jetty-http3-common/src/main/java/org/eclipse/jetty/http3/parser/SettingsBodyParser.java +++ b/jetty-core/jetty-http3/jetty-http3-common/src/main/java/org/eclipse/jetty/http3/parser/SettingsBodyParser.java @@ -73,12 +73,12 @@ public Result parse(ByteBuffer buffer) { if (settings.containsKey(key)) { - sessionFailure(buffer, HTTP3ErrorCode.SETTINGS_ERROR.code(), false, "settings_duplicate", new IllegalArgumentException("invalid duplicate setting")); + sessionFailure(buffer, HTTP3ErrorCode.SETTINGS_ERROR.code(), "settings_duplicate", new IllegalArgumentException("invalid duplicate setting")); return Result.NO_FRAME; } if (SettingsFrame.isReserved(key)) { - sessionFailure(buffer, HTTP3ErrorCode.SETTINGS_ERROR.code(), false, "settings_reserved", new IllegalArgumentException("invalid reserved setting")); + sessionFailure(buffer, HTTP3ErrorCode.SETTINGS_ERROR.code(), "settings_reserved", new IllegalArgumentException("invalid reserved setting")); return Result.NO_FRAME; } if (length > 0) @@ -87,7 +87,7 @@ public Result parse(ByteBuffer buffer) } else { - sessionFailure(buffer, HTTP3ErrorCode.FRAME_ERROR.code(), false, "settings_invalid_format", new IllegalArgumentException("invalid setting")); + sessionFailure(buffer, HTTP3ErrorCode.FRAME_ERROR.code(), "settings_invalid_format", new IllegalArgumentException("invalid setting")); return Result.NO_FRAME; } break; @@ -116,7 +116,7 @@ else if (length == 0) } else { - sessionFailure(buffer, HTTP3ErrorCode.FRAME_ERROR.code(), false, "settings_invalid_format", new IllegalArgumentException("invalid setting")); + sessionFailure(buffer, HTTP3ErrorCode.FRAME_ERROR.code(), "settings_invalid_format", new IllegalArgumentException("invalid setting")); return Result.NO_FRAME; } break; diff --git a/jetty-core/jetty-http3/jetty-http3-server/src/main/java/org/eclipse/jetty/http3/server/HTTP3ServerConnectionFactory.java b/jetty-core/jetty-http3/jetty-http3-server/src/main/java/org/eclipse/jetty/http3/server/HTTP3ServerConnectionFactory.java index 8c624b173b29..fde8bb5dbfbe 100644 --- a/jetty-core/jetty-http3/jetty-http3-server/src/main/java/org/eclipse/jetty/http3/server/HTTP3ServerConnectionFactory.java +++ b/jetty-core/jetty-http3/jetty-http3-server/src/main/java/org/eclipse/jetty/http3/server/HTTP3ServerConnectionFactory.java @@ -92,11 +92,11 @@ public boolean onIdleTimeout(Session session) } @Override - public void onFailure(Session session, boolean remote, long error, String reason, Throwable failure) + public void onFailure(Session session, long error, String reason, Throwable failure) { session.getStreams().stream() .map(stream -> (HTTP3Stream)stream) - .forEach(stream -> stream.onFailure(remote, error, failure)); + .forEach(stream -> stream.onFailure(error, failure)); } } @@ -165,10 +165,10 @@ public void onIdleTimeout(Stream.Server stream, TimeoutException timeout, Promis } @Override - public void onFailure(Stream.Server stream, boolean remote, long error, Throwable failure) + public void onFailure(Stream.Server stream, long error, Throwable failure) { HTTP3Stream http3Stream = (HTTP3Stream)stream; - Runnable task = getConnection().onFailure((HTTP3Stream)stream, remote, failure); + Runnable task = getConnection().onFailure((HTTP3Stream)stream, failure); if (task != null) { ServerHTTP3Session protocolSession = (ServerHTTP3Session)http3Stream.getSession().getProtocolSession(); diff --git a/jetty-core/jetty-http3/jetty-http3-server/src/main/java/org/eclipse/jetty/http3/server/internal/HTTP3StreamServer.java b/jetty-core/jetty-http3/jetty-http3-server/src/main/java/org/eclipse/jetty/http3/server/internal/HTTP3StreamServer.java index 2f3a9702638a..9b994a93fce8 100644 --- a/jetty-core/jetty-http3/jetty-http3-server/src/main/java/org/eclipse/jetty/http3/server/internal/HTTP3StreamServer.java +++ b/jetty-core/jetty-http3/jetty-http3-server/src/main/java/org/eclipse/jetty/http3/server/internal/HTTP3StreamServer.java @@ -120,13 +120,13 @@ protected void notifyIdleTimeout(TimeoutException timeout, Promise prom } @Override - protected void notifyFailure(boolean remote, long error, Throwable failure) + protected void notifyFailure(long error, Throwable failure) { Listener listener = this.listener; try { if (listener != null) - listener.onFailure(this, remote, error, failure); + listener.onFailure(this, error, failure); } catch (Throwable x) { diff --git a/jetty-core/jetty-http3/jetty-http3-server/src/main/java/org/eclipse/jetty/http3/server/internal/HttpStreamOverHTTP3.java b/jetty-core/jetty-http3/jetty-http3-server/src/main/java/org/eclipse/jetty/http3/server/internal/HttpStreamOverHTTP3.java index d32cda87da5d..5299ac9a9230 100644 --- a/jetty-core/jetty-http3/jetty-http3-server/src/main/java/org/eclipse/jetty/http3/server/internal/HttpStreamOverHTTP3.java +++ b/jetty-core/jetty-http3/jetty-http3-server/src/main/java/org/eclipse/jetty/http3/server/internal/HttpStreamOverHTTP3.java @@ -13,6 +13,7 @@ package org.eclipse.jetty.http3.server.internal; +import java.io.EOFException; import java.nio.ByteBuffer; import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeoutException; @@ -527,7 +528,7 @@ public void onIdleTimeout(TimeoutException failure, BiConsumer Date: Tue, 18 Jun 2024 17:17:55 +0200 Subject: [PATCH 18/22] rewire h2 Signed-off-by: Ludovic Orban --- .../java/org/eclipse/jetty/http2/HTTP2Channel.java | 2 +- .../http2/server/HTTP2ServerConnectionFactory.java | 11 +++++++++-- .../http2/server/internal/HTTP2ServerConnection.java | 6 +++--- .../http2/server/internal/HttpStreamOverHTTP2.java | 5 +++-- .../server/internal/ServerHTTP2StreamEndPoint.java | 4 ++-- 5 files changed, 18 insertions(+), 10 deletions(-) diff --git a/jetty-core/jetty-http2/jetty-http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Channel.java b/jetty-core/jetty-http2/jetty-http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Channel.java index ef1ab7ea848f..ff4ef40c12e2 100644 --- a/jetty-core/jetty-http2/jetty-http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Channel.java +++ b/jetty-core/jetty-http2/jetty-http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Channel.java @@ -63,7 +63,7 @@ public interface Server // TODO: can it be simplified? The callback seems to only be succeeded, which // means it can be converted into a Runnable which may just be the return type // so we can get rid of the Callback parameter. - public Runnable onFailure(Throwable failure, boolean remote, Callback callback); + public Runnable onFailure(Throwable failure, Callback callback); // TODO: is this needed? public boolean isIdle(); diff --git a/jetty-core/jetty-http2/jetty-http2-server/src/main/java/org/eclipse/jetty/http2/server/HTTP2ServerConnectionFactory.java b/jetty-core/jetty-http2/jetty-http2-server/src/main/java/org/eclipse/jetty/http2/server/HTTP2ServerConnectionFactory.java index fab196ee7e3a..a254f4970e93 100644 --- a/jetty-core/jetty-http2/jetty-http2-server/src/main/java/org/eclipse/jetty/http2/server/HTTP2ServerConnectionFactory.java +++ b/jetty-core/jetty-http2/jetty-http2-server/src/main/java/org/eclipse/jetty/http2/server/HTTP2ServerConnectionFactory.java @@ -13,6 +13,7 @@ package org.eclipse.jetty.http2.server; +import java.io.EOFException; import java.util.Map; import java.util.concurrent.TimeoutException; @@ -156,7 +157,8 @@ public void onDataAvailable(Stream stream) public void onReset(Stream stream, ResetFrame frame, Callback callback) { EofException failure = new EofException("Reset " + ErrorCode.toString(frame.getError(), null)); - getConnection().onStreamFailure(stream, failure, true, callback); + failure.initCause(new EOFException()); // reset marker + onFailure(stream, failure, callback); } @Override @@ -164,7 +166,12 @@ public void onFailure(Stream stream, int error, String reason, Throwable failure { if (!(failure instanceof QuietException)) failure = new EofException(failure); - getConnection().onStreamFailure(stream, failure, false, callback); + onFailure(stream, failure, callback); + } + + private void onFailure(Stream stream, Throwable failure, Callback callback) + { + getConnection().onStreamFailure(stream, failure, callback); } @Override diff --git a/jetty-core/jetty-http2/jetty-http2-server/src/main/java/org/eclipse/jetty/http2/server/internal/HTTP2ServerConnection.java b/jetty-core/jetty-http2/jetty-http2-server/src/main/java/org/eclipse/jetty/http2/server/internal/HTTP2ServerConnection.java index ca0a237fd138..7a2a9299636b 100644 --- a/jetty-core/jetty-http2/jetty-http2-server/src/main/java/org/eclipse/jetty/http2/server/internal/HTTP2ServerConnection.java +++ b/jetty-core/jetty-http2/jetty-http2-server/src/main/java/org/eclipse/jetty/http2/server/internal/HTTP2ServerConnection.java @@ -194,14 +194,14 @@ public void onStreamTimeout(Stream stream, TimeoutException timeout, Promise co } @Override - public Runnable onFailure(Throwable failure, boolean remote, Callback callback) + public Runnable onFailure(Throwable failure, Callback callback) { - // TODO failure.getCause() instanceof EOFException + boolean remote = failure != null && failure.getCause() instanceof EOFException; Runnable runnable = remote ? _httpChannel.onRemoteFailure(failure) : _httpChannel.onFailure(failure); return () -> { diff --git a/jetty-core/jetty-http2/jetty-http2-server/src/main/java/org/eclipse/jetty/http2/server/internal/ServerHTTP2StreamEndPoint.java b/jetty-core/jetty-http2/jetty-http2-server/src/main/java/org/eclipse/jetty/http2/server/internal/ServerHTTP2StreamEndPoint.java index 30f34ef43dc5..8f285557f288 100644 --- a/jetty-core/jetty-http2/jetty-http2-server/src/main/java/org/eclipse/jetty/http2/server/internal/ServerHTTP2StreamEndPoint.java +++ b/jetty-core/jetty-http2/jetty-http2-server/src/main/java/org/eclipse/jetty/http2/server/internal/ServerHTTP2StreamEndPoint.java @@ -67,10 +67,10 @@ public void onTimeout(TimeoutException timeout, BiConsumer co } @Override - public Runnable onFailure(Throwable failure, boolean remote, Callback callback) + public Runnable onFailure(Throwable failure, Callback callback) { if (LOG.isDebugEnabled()) - LOG.debug("{}failure on {}", remote ? "remote " : "", this, failure); + LOG.debug("failure on {}", this, failure); processFailure(failure); close(failure); return callback::succeeded; From 3d2e2fdd3faf5e804469ddac28df603effae32cc Mon Sep 17 00:00:00 2001 From: Ludovic Orban Date: Tue, 18 Jun 2024 17:41:15 +0200 Subject: [PATCH 19/22] change wrapping in h2 Signed-off-by: Ludovic Orban --- .../jetty/http2/server/HTTP2ServerConnectionFactory.java | 3 +-- .../jetty/http2/server/internal/HttpStreamOverHTTP2.java | 5 +++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/jetty-core/jetty-http2/jetty-http2-server/src/main/java/org/eclipse/jetty/http2/server/HTTP2ServerConnectionFactory.java b/jetty-core/jetty-http2/jetty-http2-server/src/main/java/org/eclipse/jetty/http2/server/HTTP2ServerConnectionFactory.java index a254f4970e93..498b54fd9dbb 100644 --- a/jetty-core/jetty-http2/jetty-http2-server/src/main/java/org/eclipse/jetty/http2/server/HTTP2ServerConnectionFactory.java +++ b/jetty-core/jetty-http2/jetty-http2-server/src/main/java/org/eclipse/jetty/http2/server/HTTP2ServerConnectionFactory.java @@ -156,8 +156,7 @@ public void onDataAvailable(Stream stream) @Override public void onReset(Stream stream, ResetFrame frame, Callback callback) { - EofException failure = new EofException("Reset " + ErrorCode.toString(frame.getError(), null)); - failure.initCause(new EOFException()); // reset marker + EOFException failure = new EOFException("Reset " + ErrorCode.toString(frame.getError(), null)); onFailure(stream, failure, callback); } diff --git a/jetty-core/jetty-http2/jetty-http2-server/src/main/java/org/eclipse/jetty/http2/server/internal/HttpStreamOverHTTP2.java b/jetty-core/jetty-http2/jetty-http2-server/src/main/java/org/eclipse/jetty/http2/server/internal/HttpStreamOverHTTP2.java index 0d9be074822a..f03e9479843f 100644 --- a/jetty-core/jetty-http2/jetty-http2-server/src/main/java/org/eclipse/jetty/http2/server/internal/HttpStreamOverHTTP2.java +++ b/jetty-core/jetty-http2/jetty-http2-server/src/main/java/org/eclipse/jetty/http2/server/internal/HttpStreamOverHTTP2.java @@ -39,6 +39,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.server.HttpChannel; import org.eclipse.jetty.server.HttpStream; import org.eclipse.jetty.server.Request; @@ -588,8 +589,8 @@ public void onTimeout(TimeoutException timeout, BiConsumer co @Override public Runnable onFailure(Throwable failure, Callback callback) { - boolean remote = failure != null && failure.getCause() instanceof EOFException; - Runnable runnable = remote ? _httpChannel.onRemoteFailure(failure) : _httpChannel.onFailure(failure); + boolean remote = failure instanceof EOFException; + Runnable runnable = remote ? _httpChannel.onRemoteFailure(new EofException(failure)) : _httpChannel.onFailure(failure); return () -> { if (runnable != null) From c0458a7b3eb3074a3027b828934da89b0292b357 Mon Sep 17 00:00:00 2001 From: Ludovic Orban Date: Tue, 18 Jun 2024 17:57:48 +0200 Subject: [PATCH 20/22] change wrapping in h3 Signed-off-by: Ludovic Orban --- .../jetty/http3/HTTP3StreamConnection.java | 14 +++-------- .../server/internal/HttpStreamOverHTTP3.java | 5 ++-- .../jetty/quic/common/QuicStreamEndPoint.java | 25 +++++++++++-------- 3 files changed, 20 insertions(+), 24 deletions(-) diff --git a/jetty-core/jetty-http3/jetty-http3-common/src/main/java/org/eclipse/jetty/http3/HTTP3StreamConnection.java b/jetty-core/jetty-http3/jetty-http3-common/src/main/java/org/eclipse/jetty/http3/HTTP3StreamConnection.java index 29542da3d1eb..52b0bd715e9d 100644 --- a/jetty-core/jetty-http3/jetty-http3-common/src/main/java/org/eclipse/jetty/http3/HTTP3StreamConnection.java +++ b/jetty-core/jetty-http3/jetty-http3-common/src/main/java/org/eclipse/jetty/http3/HTTP3StreamConnection.java @@ -14,7 +14,6 @@ package org.eclipse.jetty.http3; import java.io.IOException; -import java.io.UncheckedIOException; import java.nio.ByteBuffer; import java.util.concurrent.Executor; import java.util.concurrent.TimeoutException; @@ -275,7 +274,7 @@ private void tryReleaseInputBuffer(boolean force) } } - private MessageParser.Result parseAndFill(boolean setFillInterest) + private MessageParser.Result parseAndFill(boolean setFillInterest) throws IOException { try { @@ -336,16 +335,9 @@ private MessageParser.Result parseAndFill(boolean setFillInterest) } } - private int fill(ByteBuffer byteBuffer) + private int fill(ByteBuffer byteBuffer) throws IOException { - try - { - return getEndPoint().fill(byteBuffer); - } - catch (IOException x) - { - throw new UncheckedIOException(x.getMessage(), x); - } + return getEndPoint().fill(byteBuffer); } private void processHeaders(HeadersFrame frame, boolean wasBlocked, Runnable delegate) diff --git a/jetty-core/jetty-http3/jetty-http3-server/src/main/java/org/eclipse/jetty/http3/server/internal/HttpStreamOverHTTP3.java b/jetty-core/jetty-http3/jetty-http3-server/src/main/java/org/eclipse/jetty/http3/server/internal/HttpStreamOverHTTP3.java index 5299ac9a9230..33e9828772d8 100644 --- a/jetty-core/jetty-http3/jetty-http3-server/src/main/java/org/eclipse/jetty/http3/server/internal/HttpStreamOverHTTP3.java +++ b/jetty-core/jetty-http3/jetty-http3-server/src/main/java/org/eclipse/jetty/http3/server/internal/HttpStreamOverHTTP3.java @@ -34,6 +34,7 @@ import org.eclipse.jetty.http3.frames.DataFrame; import org.eclipse.jetty.http3.frames.HeadersFrame; import org.eclipse.jetty.io.Content; +import org.eclipse.jetty.io.EofException; import org.eclipse.jetty.server.HttpChannel; import org.eclipse.jetty.server.HttpStream; import org.eclipse.jetty.server.Request; @@ -538,7 +539,7 @@ public Runnable onFailure(Throwable failure) } connection.onFailure(failure); - boolean remote = failure != null && failure.getCause() instanceof EOFException; - return remote ? httpChannel.onRemoteFailure(failure) : httpChannel.onFailure(failure); + boolean remote = failure instanceof EOFException; + return remote ? httpChannel.onRemoteFailure(new EofException(failure)) : httpChannel.onFailure(failure); } } diff --git a/jetty-core/jetty-quic/jetty-quic-common/src/main/java/org/eclipse/jetty/quic/common/QuicStreamEndPoint.java b/jetty-core/jetty-quic/jetty-quic-common/src/main/java/org/eclipse/jetty/quic/common/QuicStreamEndPoint.java index e40b1b9a72f2..227af953034c 100644 --- a/jetty-core/jetty-quic/jetty-quic-common/src/main/java/org/eclipse/jetty/quic/common/QuicStreamEndPoint.java +++ b/jetty-core/jetty-quic/jetty-quic-common/src/main/java/org/eclipse/jetty/quic/common/QuicStreamEndPoint.java @@ -13,6 +13,7 @@ package org.eclipse.jetty.quic.common; +import java.io.EOFException; import java.io.IOException; import java.net.SocketAddress; import java.nio.ByteBuffer; @@ -266,23 +267,25 @@ public boolean onReadable() } else { - QuicStreamEndPoint streamEndPoint = getQuicSession().getStreamEndPoint(streamId); - if (streamEndPoint.isStreamFinished()) + if (isStreamFinished()) { - // Check if the stream was finished because of a reset frame. - Throwable x = null; + // Check if the stream was finished normally. try { - streamEndPoint.fill(EMPTY_WRITABLE_BUFFER); + fill(EMPTY_WRITABLE_BUFFER); } - catch (IOException ioe) + catch (EOFException x) { - x = ioe; + // Got reset. + getFillInterest().onFail(x); + getQuicSession().onFailure(x); + } + catch (Throwable x) + { + EofException e = new EofException(x); + getFillInterest().onFail(e); + getQuicSession().onFailure(e); } - - EofException e = new EofException(x); - streamEndPoint.getFillInterest().onFail(e); - streamEndPoint.getQuicSession().onFailure(e); } } return interested; From a428f3ce64c46e20823ea30abfb85b34054c0070 Mon Sep 17 00:00:00 2001 From: Ludovic Orban Date: Tue, 18 Jun 2024 19:21:23 +0200 Subject: [PATCH 21/22] refactor ee* tests Signed-off-by: Ludovic Orban --- .../test/client/transport/AbstractTest.java | 111 +----------- .../client/transport/AsyncIOServletTest.java | 89 ---------- .../transport/Http2AsyncIOServletTest.java | 151 ++++++++++++++++ .../transport/Http3AsyncIOServletTest.java | 161 ++++++++++++++++++ .../test/client/transport/AbstractTest.java | 130 +------------- .../client/transport/AsyncIOServletTest.java | 92 ---------- .../transport/Http2AsyncIOServletTest.java | 150 ++++++++++++++++ .../transport/Http3AsyncIOServletTest.java | 161 ++++++++++++++++++ 8 files changed, 627 insertions(+), 418 deletions(-) create mode 100644 jetty-ee10/jetty-ee10-tests/jetty-ee10-test-client-transports/src/test/java/org/eclipse/jetty/ee10/test/client/transport/Http2AsyncIOServletTest.java create mode 100644 jetty-ee10/jetty-ee10-tests/jetty-ee10-test-client-transports/src/test/java/org/eclipse/jetty/ee10/test/client/transport/Http3AsyncIOServletTest.java create mode 100644 jetty-ee9/jetty-ee9-tests/jetty-ee9-test-client-transports/src/test/java/org/eclipse/jetty/ee9/test/client/transport/Http2AsyncIOServletTest.java create mode 100644 jetty-ee9/jetty-ee9-tests/jetty-ee9-test-client-transports/src/test/java/org/eclipse/jetty/ee9/test/client/transport/Http3AsyncIOServletTest.java diff --git a/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-client-transports/src/test/java/org/eclipse/jetty/ee10/test/client/transport/AbstractTest.java b/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-client-transports/src/test/java/org/eclipse/jetty/ee10/test/client/transport/AbstractTest.java index 104005b44619..85ab339dc865 100644 --- a/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-client-transports/src/test/java/org/eclipse/jetty/ee10/test/client/transport/AbstractTest.java +++ b/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-client-transports/src/test/java/org/eclipse/jetty/ee10/test/client/transport/AbstractTest.java @@ -14,18 +14,13 @@ package org.eclipse.jetty.ee10.test.client.transport; import java.io.InputStream; -import java.net.InetSocketAddress; import java.net.URI; import java.nio.file.Files; import java.nio.file.Path; import java.security.KeyStore; import java.util.Collection; import java.util.EnumSet; -import java.util.HashMap; import java.util.List; -import java.util.Map; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.TimeUnit; import java.util.function.Consumer; import jakarta.servlet.http.HttpServlet; @@ -42,16 +37,12 @@ import org.eclipse.jetty.http.HttpScheme; import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.http.MetaData; -import org.eclipse.jetty.http2.ErrorCode; import org.eclipse.jetty.http2.HTTP2Cipher; import org.eclipse.jetty.http2.client.HTTP2Client; import org.eclipse.jetty.http2.client.transport.HttpClientTransportOverHTTP2; -import org.eclipse.jetty.http2.frames.HeadersFrame; -import org.eclipse.jetty.http2.frames.ResetFrame; import org.eclipse.jetty.http2.server.AbstractHTTP2ServerConnectionFactory; import org.eclipse.jetty.http2.server.HTTP2CServerConnectionFactory; import org.eclipse.jetty.http2.server.HTTP2ServerConnectionFactory; -import org.eclipse.jetty.http3.HTTP3ErrorCode; import org.eclipse.jetty.http3.client.HTTP3Client; import org.eclipse.jetty.http3.client.transport.HttpClientTransportOverHTTP3; import org.eclipse.jetty.http3.server.AbstractHTTP3ServerConnectionFactory; @@ -72,8 +63,6 @@ import org.eclipse.jetty.server.SslConnectionFactory; import org.eclipse.jetty.toolchain.test.jupiter.WorkDir; import org.eclipse.jetty.toolchain.test.jupiter.WorkDirExtension; -import org.eclipse.jetty.util.Callback; -import org.eclipse.jetty.util.FuturePromise; import org.eclipse.jetty.util.SocketAddressResolver; import org.eclipse.jetty.util.component.LifeCycle; import org.eclipse.jetty.util.ssl.SslContextFactory; @@ -96,11 +85,6 @@ public class AbstractTest protected ServletContextHandler servletContextHandler; protected HttpClient client; - private HTTP2Client http2Client; - private final Map h2Streams = new HashMap<>(); - private HTTP3Client http3Client; - private final Map h3Streams = new HashMap<>(); - public static Collection transports() { EnumSet transports = EnumSet.allOf(Transport.class); @@ -131,14 +115,6 @@ public static Collection transportsSecure() return transports; } - public static Collection transportsWithStreams() - { - EnumSet transports = EnumSet.of(Transport.H2C, Transport.H3); - if ("ci".equals(System.getProperty("env"))) - transports.remove(Transport.H3); - return transports; - } - @BeforeEach public void prepare() { @@ -148,8 +124,6 @@ public void prepare() @AfterEach public void dispose() { - h2Streams.clear(); - h3Streams.clear(); LifeCycle.stop(client); LifeCycle.stop(server); } @@ -228,87 +202,6 @@ protected void startClient(Transport transport, Consumer consumer) t client.start(); } - protected long newRequestOnStream(Transport transport) throws Exception - { - switch (transport) - { - case H2C, H2 -> - { - return sendHeadersWithNewH2Stream(); - } - case H3 -> - { - return sendHeadersWithNewH3Stream(); - } - default -> throw new IllegalArgumentException("Transport does not support streams: " + transport); - } - } - - private int sendHeadersWithNewH2Stream() throws Exception - { - org.eclipse.jetty.http2.api.Session session = newHttp2ClientSession(new org.eclipse.jetty.http2.api.Session.Listener() {}); - MetaData.Request metaData = newRequest("GET", HttpFields.EMPTY); - HeadersFrame frame = new HeadersFrame(metaData, null, false); - FuturePromise promise = new FuturePromise<>(); - session.newStream(frame, promise, null); - org.eclipse.jetty.http2.api.Stream stream = promise.get(5, TimeUnit.SECONDS); - int streamId = stream.getId(); - h2Streams.put(streamId, stream); - return streamId; - } - - private long sendHeadersWithNewH3Stream() throws Exception - { - org.eclipse.jetty.http3.api.Session.Client session = newHttp3ClientSession(new org.eclipse.jetty.http3.api.Session.Client.Listener() {}); - MetaData.Request metaData = newRequest("GET", HttpFields.EMPTY); - CompletableFuture cf = session.newRequest(new org.eclipse.jetty.http3.frames.HeadersFrame(metaData, false), null); - org.eclipse.jetty.http3.api.Stream stream = cf.get(5, TimeUnit.SECONDS); - long streamId = stream.getId(); - h3Streams.put(streamId, stream); - return streamId; - } - - protected void resetStream(Transport transport, long streamId) - { - switch (transport) - { - case H2C, H2 -> resetH2Stream((int)streamId); - case H3 -> resetH3Stream(streamId); - default -> throw new IllegalArgumentException("Transport does not support streams: " + transport); - } - } - - private void resetH2Stream(int streamId) - { - org.eclipse.jetty.http2.api.Stream stream = h2Streams.get(streamId); - stream.reset(new ResetFrame(streamId, ErrorCode.CANCEL_STREAM_ERROR.code), Callback.NOOP); - } - - private void resetH3Stream(long streamId) - { - org.eclipse.jetty.http3.api.Stream stream = h3Streams.get(streamId); - stream.reset(HTTP3ErrorCode.REQUEST_CANCELLED_ERROR.code(), new Exception(getClass().getSimpleName() + " reset")); - } - - private org.eclipse.jetty.http2.api.Session newHttp2ClientSession(org.eclipse.jetty.http2.api.Session.Listener listener) throws Exception - { - String host = "localhost"; - int port = ((NetworkConnector)connector).getLocalPort(); - InetSocketAddress address = new InetSocketAddress(host, port); - FuturePromise promise = new FuturePromise<>(); - http2Client.connect(address, listener, promise); - return promise.get(5, TimeUnit.SECONDS); - } - - private org.eclipse.jetty.http3.api.Session.Client newHttp3ClientSession(org.eclipse.jetty.http3.api.Session.Client.Listener listener) throws Exception - { - String host = "localhost"; - int port = ((NetworkConnector)connector).getLocalPort(); - InetSocketAddress address = new InetSocketAddress(host, port); - CompletableFuture cf = http3Client.connect(address, listener); - return cf.get(5, TimeUnit.SECONDS); - } - protected MetaData.Request newRequest(String method, HttpFields fields) { return newRequest(method, "/", fields); @@ -395,7 +288,7 @@ protected HttpClientTransport newHttpClientTransport(Transport transport) throws ClientConnector clientConnector = new ClientConnector(); clientConnector.setSelectors(1); clientConnector.setSslContextFactory(newSslContextFactoryClient()); - http2Client = new HTTP2Client(clientConnector); + HTTP2Client http2Client = new HTTP2Client(clientConnector); yield new HttpClientTransportOverHTTP2(http2Client); } case H3 -> @@ -405,7 +298,7 @@ protected HttpClientTransport newHttpClientTransport(Transport transport) throws SslContextFactory.Client sslContextFactory = newSslContextFactoryClient(); clientConnector.setSslContextFactory(sslContextFactory); Path clientPemDirectory = Files.createDirectories(pemDir.resolve("client")); - http3Client = new HTTP3Client(new ClientQuicConfiguration(sslContextFactory, clientPemDirectory)); + HTTP3Client http3Client = new HTTP3Client(new ClientQuicConfiguration(sslContextFactory, clientPemDirectory)); yield new HttpClientTransportOverHTTP3(http3Client); } case FCGI -> new HttpClientTransportOverFCGI(1, ""); diff --git a/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-client-transports/src/test/java/org/eclipse/jetty/ee10/test/client/transport/AsyncIOServletTest.java b/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-client-transports/src/test/java/org/eclipse/jetty/ee10/test/client/transport/AsyncIOServletTest.java index aabe1d99071e..41472d1fd7c0 100644 --- a/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-client-transports/src/test/java/org/eclipse/jetty/ee10/test/client/transport/AsyncIOServletTest.java +++ b/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-client-transports/src/test/java/org/eclipse/jetty/ee10/test/client/transport/AsyncIOServletTest.java @@ -34,8 +34,6 @@ import java.util.concurrent.atomic.AtomicReference; import jakarta.servlet.AsyncContext; -import jakarta.servlet.AsyncEvent; -import jakarta.servlet.AsyncListener; import jakarta.servlet.DispatcherType; import jakarta.servlet.ReadListener; import jakarta.servlet.ServletInputStream; @@ -85,7 +83,6 @@ import static org.hamcrest.Matchers.empty; import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.nullValue; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -1537,92 +1534,6 @@ public void onError(Throwable x) assertEquals(HttpStatus.NO_CONTENT_204, response.getStatus()); } - @ParameterizedTest - @MethodSource("transportsWithStreams") - public void testStartAsyncThenClientResetRemoteErrorDoesNotify(Transport transport) throws Exception - { - testStartAsyncThenClientResetRemoteErrorNotification(transport, true); - } - - @ParameterizedTest - @MethodSource("transportsWithStreams") - public void testStartAsyncThenClientResetRemoteErrorDoesNotNotify(Transport transport) throws Exception - { - testStartAsyncThenClientResetRemoteErrorNotification(transport, false); - } - - private void testStartAsyncThenClientResetRemoteErrorNotification(Transport transport, boolean notify) throws Exception - { - httpConfig.setNotifyRemoteAsyncErrors(notify); - - AtomicReference errorAsyncEventRef = new AtomicReference<>(); - AtomicReference responseRef = new AtomicReference<>(); - CountDownLatch latch = new CountDownLatch(1); - start(transport, new HttpServlet() - { - @Override - protected void service(HttpServletRequest request, HttpServletResponse response) - { - AsyncContext asyncContext = request.startAsync(); - asyncContext.addListener(new AsyncListener() - { - @Override - public void onComplete(AsyncEvent event) - { - } - - @Override - public void onTimeout(AsyncEvent event) - { - } - - @Override - public void onError(AsyncEvent event) - { - errorAsyncEventRef.set(event); - } - - @Override - public void onStartAsync(AsyncEvent event) - { - } - }); - asyncContext.setTimeout(0); - responseRef.set(response); - latch.countDown(); - } - }); - - long streamId = newRequestOnStream(transport); - - // Wait for the server to be in ASYNC_WAIT. - assertTrue(latch.await(5, TimeUnit.SECONDS)); - sleep(500); - - resetStream(transport, streamId); - - if (notify) - // Wait for the reset to be notified to the async context listener. - await().atMost(5, TimeUnit.SECONDS).until(() -> - { - AsyncEvent asyncEvent = errorAsyncEventRef.get(); - return asyncEvent == null ? null : asyncEvent.getThrowable(); - }, instanceOf(EofException.class)); - else - // Wait for the reset to NOT be notified to the failure listener. - await().atMost(5, TimeUnit.SECONDS).during(1, TimeUnit.SECONDS).until(errorAsyncEventRef::get, nullValue()); - - ServletOutputStream output = responseRef.get().getOutputStream(); - - assertThrows(IOException.class, - () -> - { - // Large writes or explicit flush() must - // fail because the stream has been reset. - output.flush(); - }); - } - private static class Listener implements ReadListener, WriteListener { private final Executor executor = Executors.newFixedThreadPool(32); diff --git a/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-client-transports/src/test/java/org/eclipse/jetty/ee10/test/client/transport/Http2AsyncIOServletTest.java b/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-client-transports/src/test/java/org/eclipse/jetty/ee10/test/client/transport/Http2AsyncIOServletTest.java new file mode 100644 index 000000000000..c55aadae64c9 --- /dev/null +++ b/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-client-transports/src/test/java/org/eclipse/jetty/ee10/test/client/transport/Http2AsyncIOServletTest.java @@ -0,0 +1,151 @@ +// +// ======================================================================== +// Copyright (c) 1995 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.ee10.test.client.transport; + +import java.net.InetSocketAddress; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; + +import jakarta.servlet.AsyncContext; +import jakarta.servlet.AsyncEvent; +import jakarta.servlet.AsyncListener; +import jakarta.servlet.http.HttpServlet; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.eclipse.jetty.ee10.servlet.ServletContextHandler; +import org.eclipse.jetty.ee10.servlet.ServletHolder; +import org.eclipse.jetty.http.HttpFields; +import org.eclipse.jetty.http.HttpURI; +import org.eclipse.jetty.http.HttpVersion; +import org.eclipse.jetty.http.MetaData; +import org.eclipse.jetty.http2.ErrorCode; +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.HeadersFrame; +import org.eclipse.jetty.http2.frames.ResetFrame; +import org.eclipse.jetty.http2.server.HTTP2CServerConnectionFactory; +import org.eclipse.jetty.io.EofException; +import org.eclipse.jetty.server.HttpConfiguration; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.util.FuturePromise; +import org.eclipse.jetty.util.component.LifeCycle; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import static org.awaitility.Awaitility.await; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.nullValue; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class Http2AsyncIOServletTest +{ + private Server server; + private ServerConnector connector; + private HTTP2Client client; + + private void start(HttpConfiguration httpConfig, HttpServlet httpServlet) throws Exception + { + server = new Server(); + connector = new ServerConnector(server, 1, 1, new HTTP2CServerConnectionFactory(httpConfig)); + server.addConnector(connector); + ServletContextHandler servletContextHandler = new ServletContextHandler("/"); + servletContextHandler.addServlet(new ServletHolder(httpServlet), "/*"); + server.setHandler(servletContextHandler); + server.start(); + + client = new HTTP2Client(); + client.start(); + } + + @AfterEach + public void tearDown() + { + LifeCycle.stop(client); + LifeCycle.stop(server); + } + + @ParameterizedTest + @ValueSource(booleans = {true, false}) + public void testStartAsyncThenClientResetRemoteErrorNotification(boolean notify) throws Exception + { + HttpConfiguration httpConfig = new HttpConfiguration(); + httpConfig.setNotifyRemoteAsyncErrors(notify); + + AtomicReference errorAsyncEventRef = new AtomicReference<>(); + CountDownLatch latch = new CountDownLatch(1); + start(httpConfig, new HttpServlet() + { + @Override + protected void service(HttpServletRequest request, HttpServletResponse response) + { + AsyncContext asyncContext = request.startAsync(); + asyncContext.addListener(new AsyncListener() + { + @Override + public void onComplete(AsyncEvent event) + { + } + + @Override + public void onTimeout(AsyncEvent event) + { + } + + @Override + public void onError(AsyncEvent event) + { + errorAsyncEventRef.set(event); + asyncContext.complete(); + } + + @Override + public void onStartAsync(AsyncEvent event) + { + } + }); + asyncContext.setTimeout(0); + latch.countDown(); + } + }); + + InetSocketAddress address = new InetSocketAddress("localhost", connector.getLocalPort()); + FuturePromise sessionPromise = new FuturePromise<>(); + client.connect(address, new Session.Listener() {}, sessionPromise); + Session session = sessionPromise.get(5, TimeUnit.SECONDS); + MetaData.Request metaData = new MetaData.Request("GET", HttpURI.from("/"), HttpVersion.HTTP_2, HttpFields.EMPTY); + HeadersFrame frame = new HeadersFrame(metaData, null, false); + Stream stream = session.newStream(frame, null).get(5, TimeUnit.SECONDS); + + // Wait for the server to be in ASYNC_WAIT. + assertTrue(latch.await(5, TimeUnit.SECONDS)); + Thread.sleep(500); + + stream.reset(new ResetFrame(stream.getId(), ErrorCode.CANCEL_STREAM_ERROR.code)); + + if (notify) + // Wait for the reset to be notified to the async context listener. + await().atMost(5, TimeUnit.SECONDS).until(() -> + { + AsyncEvent asyncEvent = errorAsyncEventRef.get(); + return asyncEvent == null ? null : asyncEvent.getThrowable(); + }, instanceOf(EofException.class)); + else + // Wait for the reset to NOT be notified to the failure listener. + await().atMost(5, TimeUnit.SECONDS).during(1, TimeUnit.SECONDS).until(errorAsyncEventRef::get, nullValue()); + } +} diff --git a/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-client-transports/src/test/java/org/eclipse/jetty/ee10/test/client/transport/Http3AsyncIOServletTest.java b/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-client-transports/src/test/java/org/eclipse/jetty/ee10/test/client/transport/Http3AsyncIOServletTest.java new file mode 100644 index 000000000000..64d5eaf00bbc --- /dev/null +++ b/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-client-transports/src/test/java/org/eclipse/jetty/ee10/test/client/transport/Http3AsyncIOServletTest.java @@ -0,0 +1,161 @@ +// +// ======================================================================== +// Copyright (c) 1995 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.ee10.test.client.transport; + +import java.net.InetSocketAddress; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; + +import jakarta.servlet.AsyncContext; +import jakarta.servlet.AsyncEvent; +import jakarta.servlet.AsyncListener; +import jakarta.servlet.http.HttpServlet; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.eclipse.jetty.ee10.servlet.ServletContextHandler; +import org.eclipse.jetty.ee10.servlet.ServletHolder; +import org.eclipse.jetty.http.HttpFields; +import org.eclipse.jetty.http.HttpURI; +import org.eclipse.jetty.http.HttpVersion; +import org.eclipse.jetty.http.MetaData; +import org.eclipse.jetty.http3.HTTP3ErrorCode; +import org.eclipse.jetty.http3.api.Stream; +import org.eclipse.jetty.http3.client.HTTP3Client; +import org.eclipse.jetty.http3.frames.HeadersFrame; +import org.eclipse.jetty.http3.server.HTTP3ServerConnectionFactory; +import org.eclipse.jetty.io.EofException; +import org.eclipse.jetty.quic.client.ClientQuicConfiguration; +import org.eclipse.jetty.quic.server.QuicServerConnector; +import org.eclipse.jetty.quic.server.ServerQuicConfiguration; +import org.eclipse.jetty.server.HttpConfiguration; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.toolchain.test.MavenPaths; +import org.eclipse.jetty.toolchain.test.jupiter.WorkDir; +import org.eclipse.jetty.toolchain.test.jupiter.WorkDirExtension; +import org.eclipse.jetty.util.component.LifeCycle; +import org.eclipse.jetty.util.ssl.SslContextFactory; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import static org.awaitility.Awaitility.await; +import static org.eclipse.jetty.http3.api.Session.Client; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.nullValue; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@ExtendWith(WorkDirExtension.class) +public class Http3AsyncIOServletTest +{ + public WorkDir workDir; + + private Server server; + private QuicServerConnector connector; + private HTTP3Client client; + + private void start(HttpConfiguration httpConfig, HttpServlet httpServlet) throws Exception + { + server = new Server(); + SslContextFactory.Server serverSslContextFactory = new SslContextFactory.Server(); + serverSslContextFactory.setKeyStorePath(MavenPaths.findTestResourceFile("keystore.p12").toString()); + serverSslContextFactory.setKeyStorePassword("storepwd"); + ServerQuicConfiguration serverQuicConfiguration = new ServerQuicConfiguration(serverSslContextFactory, workDir.getEmptyPathDir()); + connector = new QuicServerConnector(server, serverQuicConfiguration, new HTTP3ServerConnectionFactory(serverQuicConfiguration, httpConfig)); + server.addConnector(connector); + ServletContextHandler servletContextHandler = new ServletContextHandler("/"); + servletContextHandler.addServlet(new ServletHolder(httpServlet), "/*"); + server.setHandler(servletContextHandler); + server.start(); + + client = new HTTP3Client(new ClientQuicConfiguration(new SslContextFactory.Client(true), null)); + client.start(); + } + + @AfterEach + public void tearDown() + { + LifeCycle.stop(client); + LifeCycle.stop(server); + } + + @ParameterizedTest + @ValueSource(booleans = {true, false}) + public void testStartAsyncThenClientResetRemoteErrorNotification(boolean notify) throws Exception + { + HttpConfiguration httpConfig = new HttpConfiguration(); + httpConfig.setNotifyRemoteAsyncErrors(notify); + + AtomicReference errorAsyncEventRef = new AtomicReference<>(); + CountDownLatch latch = new CountDownLatch(1); + start(httpConfig, new HttpServlet() + { + @Override + protected void service(HttpServletRequest request, HttpServletResponse response) + { + AsyncContext asyncContext = request.startAsync(); + asyncContext.addListener(new AsyncListener() + { + @Override + public void onComplete(AsyncEvent event) + { + } + + @Override + public void onTimeout(AsyncEvent event) + { + } + + @Override + public void onError(AsyncEvent event) + { + errorAsyncEventRef.set(event); + asyncContext.complete(); + } + + @Override + public void onStartAsync(AsyncEvent event) + { + } + }); + asyncContext.setTimeout(0); + latch.countDown(); + } + }); + + InetSocketAddress address = new InetSocketAddress("localhost", connector.getLocalPort()); + Client session = client.connect(address, new Client.Listener() {}).get(5, TimeUnit.SECONDS); + MetaData.Request metaData = new MetaData.Request("GET", HttpURI.from("/"), HttpVersion.HTTP_3, HttpFields.EMPTY); + HeadersFrame frame = new HeadersFrame(metaData, false); + Stream stream = session.newRequest(frame, null).get(5, TimeUnit.SECONDS); + + // Wait for the server to be in ASYNC_WAIT. + assertTrue(latch.await(5, TimeUnit.SECONDS)); + Thread.sleep(500); + + stream.reset(HTTP3ErrorCode.REQUEST_CANCELLED_ERROR.code(), new Exception()); + + if (notify) + // Wait for the reset to be notified to the async context listener. + await().atMost(5, TimeUnit.SECONDS).until(() -> + { + AsyncEvent asyncEvent = errorAsyncEventRef.get(); + return asyncEvent == null ? null : asyncEvent.getThrowable(); + }, instanceOf(EofException.class)); + else + // Wait for the reset to NOT be notified to the failure listener. + await().atMost(5, TimeUnit.SECONDS).during(1, TimeUnit.SECONDS).until(errorAsyncEventRef::get, nullValue()); + } +} diff --git a/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-client-transports/src/test/java/org/eclipse/jetty/ee9/test/client/transport/AbstractTest.java b/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-client-transports/src/test/java/org/eclipse/jetty/ee9/test/client/transport/AbstractTest.java index d3799a0a84de..63efe5877d06 100644 --- a/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-client-transports/src/test/java/org/eclipse/jetty/ee9/test/client/transport/AbstractTest.java +++ b/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-client-transports/src/test/java/org/eclipse/jetty/ee9/test/client/transport/AbstractTest.java @@ -15,7 +15,6 @@ import java.io.IOException; import java.io.InputStream; -import java.net.InetSocketAddress; import java.net.URI; import java.nio.file.Files; import java.nio.file.Path; @@ -25,12 +24,8 @@ import java.security.cert.CertificateException; import java.util.Collection; import java.util.EnumSet; -import java.util.HashMap; import java.util.List; -import java.util.Map; import java.util.Objects; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.TimeUnit; import jakarta.servlet.http.HttpServlet; import org.eclipse.jetty.alpn.server.ALPNServerConnectionFactory; @@ -41,22 +36,12 @@ import org.eclipse.jetty.ee9.servlet.ServletHolder; import org.eclipse.jetty.fcgi.client.transport.HttpClientTransportOverFCGI; import org.eclipse.jetty.fcgi.server.ServerFCGIConnectionFactory; -import org.eclipse.jetty.http.HostPortHttpField; -import org.eclipse.jetty.http.HttpFields; -import org.eclipse.jetty.http.HttpScheme; -import org.eclipse.jetty.http.HttpVersion; -import org.eclipse.jetty.http.MetaData; -import org.eclipse.jetty.http2.ErrorCode; import org.eclipse.jetty.http2.HTTP2Cipher; import org.eclipse.jetty.http2.client.HTTP2Client; import org.eclipse.jetty.http2.client.transport.HttpClientTransportOverHTTP2; -import org.eclipse.jetty.http2.frames.HeadersFrame; -import org.eclipse.jetty.http2.frames.ResetFrame; import org.eclipse.jetty.http2.server.AbstractHTTP2ServerConnectionFactory; import org.eclipse.jetty.http2.server.HTTP2CServerConnectionFactory; import org.eclipse.jetty.http2.server.HTTP2ServerConnectionFactory; -import org.eclipse.jetty.http3.HTTP3ErrorCode; -import org.eclipse.jetty.http3.api.Stream; import org.eclipse.jetty.http3.client.HTTP3Client; import org.eclipse.jetty.http3.client.transport.HttpClientTransportOverHTTP3; import org.eclipse.jetty.http3.server.AbstractHTTP3ServerConnectionFactory; @@ -77,8 +62,6 @@ import org.eclipse.jetty.server.SslConnectionFactory; import org.eclipse.jetty.toolchain.test.jupiter.WorkDir; import org.eclipse.jetty.toolchain.test.jupiter.WorkDirExtension; -import org.eclipse.jetty.util.Callback; -import org.eclipse.jetty.util.FuturePromise; import org.eclipse.jetty.util.SocketAddressResolver; import org.eclipse.jetty.util.component.LifeCycle; import org.eclipse.jetty.util.ssl.SslContextFactory; @@ -101,11 +84,6 @@ public class AbstractTest protected ServletContextHandler servletContextHandler; protected HttpClient client; - private HTTP2Client http2Client; - private final Map h2Streams = new HashMap<>(); - private HTTP3Client http3Client; - private final Map h3Streams = new HashMap<>(); - public static Collection transports() { EnumSet transports = EnumSet.allOf(Transport.class); @@ -128,14 +106,6 @@ public static Collection transportsWithPushSupport() return transports; } - public static Collection transportsWithStreams() - { - EnumSet transports = EnumSet.of(Transport.H2C, Transport.H3); - if ("ci".equals(System.getProperty("env"))) - transports.remove(Transport.H3); - return transports; - } - @BeforeEach public void prepare() { @@ -145,8 +115,6 @@ public void prepare() @AfterEach public void dispose() { - h2Streams.clear(); - h3Streams.clear(); LifeCycle.stop(client); LifeCycle.stop(server); } @@ -228,100 +196,6 @@ protected void startClient(Transport transport) throws Exception client.start(); } - protected long newRequestOnStream(Transport transport) throws Exception - { - switch (transport) - { - case H2C, H2 -> - { - return sendHeadersWithNewH2Stream(); - } - case H3 -> - { - return sendHeadersWithNewH3Stream(); - } - default -> throw new IllegalArgumentException("Transport does not support streams: " + transport); - } - } - - private int sendHeadersWithNewH2Stream() throws Exception - { - org.eclipse.jetty.http2.api.Session session = newHttp2ClientSession(new org.eclipse.jetty.http2.api.Session.Listener() {}); - MetaData.Request metaData = newRequest("GET", HttpFields.EMPTY); - HeadersFrame frame = new HeadersFrame(metaData, null, false); - FuturePromise promise = new FuturePromise<>(); - session.newStream(frame, promise, null); - org.eclipse.jetty.http2.api.Stream stream = promise.get(5, TimeUnit.SECONDS); - int streamId = stream.getId(); - h2Streams.put(streamId, stream); - return streamId; - } - - private long sendHeadersWithNewH3Stream() throws Exception - { - org.eclipse.jetty.http3.api.Session.Client session = newHttp3ClientSession(new org.eclipse.jetty.http3.api.Session.Client.Listener() {}); - MetaData.Request metaData = newRequest("GET", HttpFields.EMPTY); - CompletableFuture cf = session.newRequest(new org.eclipse.jetty.http3.frames.HeadersFrame(metaData, false), null); - org.eclipse.jetty.http3.api.Stream stream = cf.get(5, TimeUnit.SECONDS); - long streamId = stream.getId(); - h3Streams.put(streamId, stream); - return streamId; - } - - protected void resetStream(Transport transport, long streamId) - { - switch (transport) - { - case H2C, H2 -> resetH2Stream((int)streamId); - case H3 -> resetH3Stream(streamId); - default -> throw new IllegalArgumentException("Transport does not support streams: " + transport); - } - } - - private void resetH2Stream(int streamId) - { - org.eclipse.jetty.http2.api.Stream stream = h2Streams.get(streamId); - stream.reset(new ResetFrame(streamId, ErrorCode.CANCEL_STREAM_ERROR.code), Callback.NOOP); - } - - private void resetH3Stream(long streamId) - { - org.eclipse.jetty.http3.api.Stream stream = h3Streams.get(streamId); - stream.reset(HTTP3ErrorCode.REQUEST_CANCELLED_ERROR.code(), new Exception(getClass().getSimpleName() + " reset")); - } - - private org.eclipse.jetty.http2.api.Session newHttp2ClientSession(org.eclipse.jetty.http2.api.Session.Listener listener) throws Exception - { - String host = "localhost"; - int port = ((NetworkConnector)connector).getLocalPort(); - InetSocketAddress address = new InetSocketAddress(host, port); - FuturePromise promise = new FuturePromise<>(); - http2Client.connect(address, listener, promise); - return promise.get(5, TimeUnit.SECONDS); - } - - private org.eclipse.jetty.http3.api.Session.Client newHttp3ClientSession(org.eclipse.jetty.http3.api.Session.Client.Listener listener) throws Exception - { - String host = "localhost"; - int port = ((NetworkConnector)connector).getLocalPort(); - InetSocketAddress address = new InetSocketAddress(host, port); - CompletableFuture cf = http3Client.connect(address, listener); - return cf.get(5, TimeUnit.SECONDS); - } - - protected MetaData.Request newRequest(String method, HttpFields fields) - { - return newRequest(method, "/", fields); - } - - protected MetaData.Request newRequest(String method, String path, HttpFields fields) - { - String host = "localhost"; - int port = ((NetworkConnector)connector).getLocalPort(); - String authority = host + ":" + port; - return new MetaData.Request(method, HttpScheme.HTTP.asString(), new HostPortHttpField(authority), path, HttpVersion.HTTP_2, fields, -1); - } - public AbstractConnector newConnector(Transport transport, Server server) { return switch (transport) @@ -394,7 +268,7 @@ protected HttpClientTransport newHttpClientTransport(Transport transport) throws ClientConnector clientConnector = new ClientConnector(); clientConnector.setSelectors(1); clientConnector.setSslContextFactory(newSslContextFactoryClient()); - http2Client = new HTTP2Client(clientConnector); + HTTP2Client http2Client = new HTTP2Client(clientConnector); yield new HttpClientTransportOverHTTP2(http2Client); } case H3 -> @@ -404,7 +278,7 @@ protected HttpClientTransport newHttpClientTransport(Transport transport) throws SslContextFactory.Client sslContextFactory = newSslContextFactoryClient(); clientConnector.setSslContextFactory(sslContextFactory); Path clientPemDirectory = Files.createDirectories(pemDir.resolve("client")); - http3Client = new HTTP3Client(new ClientQuicConfiguration(sslContextFactory, clientPemDirectory)); + HTTP3Client http3Client = new HTTP3Client(new ClientQuicConfiguration(sslContextFactory, clientPemDirectory)); yield new HttpClientTransportOverHTTP3(http3Client); } case FCGI -> new HttpClientTransportOverFCGI(1, ""); diff --git a/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-client-transports/src/test/java/org/eclipse/jetty/ee9/test/client/transport/AsyncIOServletTest.java b/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-client-transports/src/test/java/org/eclipse/jetty/ee9/test/client/transport/AsyncIOServletTest.java index e4a674b6b846..4a10ca2b01fe 100644 --- a/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-client-transports/src/test/java/org/eclipse/jetty/ee9/test/client/transport/AsyncIOServletTest.java +++ b/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-client-transports/src/test/java/org/eclipse/jetty/ee9/test/client/transport/AsyncIOServletTest.java @@ -35,8 +35,6 @@ import java.util.concurrent.atomic.AtomicReference; import jakarta.servlet.AsyncContext; -import jakarta.servlet.AsyncEvent; -import jakarta.servlet.AsyncListener; import jakarta.servlet.DispatcherType; import jakarta.servlet.ReadListener; import jakarta.servlet.ServletInputStream; @@ -87,7 +85,6 @@ import static org.hamcrest.Matchers.empty; import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.nullValue; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -1864,95 +1861,6 @@ public void onError(Throwable x) assertEquals(1, allDataReadCount.get()); } - @ParameterizedTest - @MethodSource("transportsWithStreams") - public void testStartAsyncThenClientResetRemoteErrorDoesNotify(Transport transport) throws Exception - { - testStartAsyncThenClientResetRemoteErrorNotification(transport, true); - } - - @ParameterizedTest - @MethodSource("transportsWithStreams") - public void testStartAsyncThenClientResetRemoteErrorDoesNotNotify(Transport transport) throws Exception - { - testStartAsyncThenClientResetRemoteErrorNotification(transport, false); - } - - private void testStartAsyncThenClientResetRemoteErrorNotification(Transport transport, boolean notify) throws Exception - { - httpConfig.setNotifyRemoteAsyncErrors(notify); - - AtomicReference errorAsyncEventRef = new AtomicReference<>(); - AtomicReference responseRef = new AtomicReference<>(); - CountDownLatch latch = new CountDownLatch(1); - start(transport, new HttpServlet() - { - @Override - protected void service(HttpServletRequest request, HttpServletResponse response) - { - AsyncContext asyncContext = request.startAsync(); - asyncContext.addListener(new AsyncListener() - { - @Override - public void onComplete(AsyncEvent event) - { - } - - @Override - public void onTimeout(AsyncEvent event) - { - } - - @Override - public void onError(AsyncEvent event) - { - errorAsyncEventRef.set(event); - } - - @Override - public void onStartAsync(AsyncEvent event) - { - } - }); - asyncContext.setTimeout(0); - responseRef.set(response); - latch.countDown(); - } - }); - - long streamId = newRequestOnStream(transport); - - // Wait for the server to be in ASYNC_WAIT. - assertTrue(latch.await(5, TimeUnit.SECONDS)); - sleep(500); - - resetStream(transport, streamId); - - if (notify) - // Wait for the reset to be notified to the async context listener. - await().atMost(5, TimeUnit.SECONDS).until(() -> - { - AsyncEvent asyncEvent = errorAsyncEventRef.get(); - return asyncEvent == null ? null : asyncEvent.getThrowable(); - }, instanceOf(EofException.class)); - else - // Wait for the reset to NOT be notified to the failure listener. - await().atMost(5, TimeUnit.SECONDS).during(1, TimeUnit.SECONDS).until(errorAsyncEventRef::get, nullValue()); - - ServletOutputStream output = responseRef.get().getOutputStream(); - - if (notify) - output.flush(); - else - assertThrows(IOException.class, - () -> - { - // Large writes or explicit flush() must - // fail because the stream has been reset. - output.flush(); - }); - } - private static class Listener implements ReadListener, WriteListener { private final Executor executor = Executors.newFixedThreadPool(32); diff --git a/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-client-transports/src/test/java/org/eclipse/jetty/ee9/test/client/transport/Http2AsyncIOServletTest.java b/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-client-transports/src/test/java/org/eclipse/jetty/ee9/test/client/transport/Http2AsyncIOServletTest.java new file mode 100644 index 000000000000..12e6a7cfef6c --- /dev/null +++ b/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-client-transports/src/test/java/org/eclipse/jetty/ee9/test/client/transport/Http2AsyncIOServletTest.java @@ -0,0 +1,150 @@ +// +// ======================================================================== +// Copyright (c) 1995 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.ee9.test.client.transport; + +import java.net.InetSocketAddress; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; + +import jakarta.servlet.AsyncContext; +import jakarta.servlet.AsyncEvent; +import jakarta.servlet.AsyncListener; +import jakarta.servlet.http.HttpServlet; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.eclipse.jetty.ee9.servlet.ServletContextHandler; +import org.eclipse.jetty.ee9.servlet.ServletHolder; +import org.eclipse.jetty.http.HttpFields; +import org.eclipse.jetty.http.HttpURI; +import org.eclipse.jetty.http.HttpVersion; +import org.eclipse.jetty.http.MetaData; +import org.eclipse.jetty.http2.ErrorCode; +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.HeadersFrame; +import org.eclipse.jetty.http2.frames.ResetFrame; +import org.eclipse.jetty.http2.server.HTTP2CServerConnectionFactory; +import org.eclipse.jetty.io.EofException; +import org.eclipse.jetty.server.HttpConfiguration; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.util.FuturePromise; +import org.eclipse.jetty.util.component.LifeCycle; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import static org.awaitility.Awaitility.await; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.nullValue; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class Http2AsyncIOServletTest +{ + private Server server; + private ServerConnector connector; + private HTTP2Client client; + + private void start(HttpConfiguration httpConfig, HttpServlet httpServlet) throws Exception + { + server = new Server(); + connector = new ServerConnector(server, 1, 1, new HTTP2CServerConnectionFactory(httpConfig)); + server.addConnector(connector); + ServletContextHandler servletContextHandler = new ServletContextHandler(server, "/"); + servletContextHandler.addServlet(new ServletHolder(httpServlet), "/*"); + server.start(); + + client = new HTTP2Client(); + client.start(); + } + + @AfterEach + public void tearDown() + { + LifeCycle.stop(client); + LifeCycle.stop(server); + } + + @ParameterizedTest + @ValueSource(booleans = {true, false}) + public void testStartAsyncThenClientResetRemoteErrorNotification(boolean notify) throws Exception + { + HttpConfiguration httpConfig = new HttpConfiguration(); + httpConfig.setNotifyRemoteAsyncErrors(notify); + + AtomicReference errorAsyncEventRef = new AtomicReference<>(); + CountDownLatch latch = new CountDownLatch(1); + start(httpConfig, new HttpServlet() + { + @Override + protected void service(HttpServletRequest request, HttpServletResponse response) + { + AsyncContext asyncContext = request.startAsync(); + asyncContext.addListener(new AsyncListener() + { + @Override + public void onComplete(AsyncEvent event) + { + } + + @Override + public void onTimeout(AsyncEvent event) + { + } + + @Override + public void onError(AsyncEvent event) + { + errorAsyncEventRef.set(event); + asyncContext.complete(); + } + + @Override + public void onStartAsync(AsyncEvent event) + { + } + }); + asyncContext.setTimeout(0); + latch.countDown(); + } + }); + + InetSocketAddress address = new InetSocketAddress("localhost", connector.getLocalPort()); + FuturePromise sessionPromise = new FuturePromise<>(); + client.connect(address, new Session.Listener() {}, sessionPromise); + Session session = sessionPromise.get(5, TimeUnit.SECONDS); + MetaData.Request metaData = new MetaData.Request("GET", HttpURI.from("/"), HttpVersion.HTTP_2, HttpFields.EMPTY); + HeadersFrame frame = new HeadersFrame(metaData, null, false); + Stream stream = session.newStream(frame, null).get(5, TimeUnit.SECONDS); + + // Wait for the server to be in ASYNC_WAIT. + assertTrue(latch.await(5, TimeUnit.SECONDS)); + Thread.sleep(500); + + stream.reset(new ResetFrame(stream.getId(), ErrorCode.CANCEL_STREAM_ERROR.code)); + + if (notify) + // Wait for the reset to be notified to the async context listener. + await().atMost(5, TimeUnit.SECONDS).until(() -> + { + AsyncEvent asyncEvent = errorAsyncEventRef.get(); + return asyncEvent == null ? null : asyncEvent.getThrowable(); + }, instanceOf(EofException.class)); + else + // Wait for the reset to NOT be notified to the failure listener. + await().atMost(5, TimeUnit.SECONDS).during(1, TimeUnit.SECONDS).until(errorAsyncEventRef::get, nullValue()); + } +} diff --git a/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-client-transports/src/test/java/org/eclipse/jetty/ee9/test/client/transport/Http3AsyncIOServletTest.java b/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-client-transports/src/test/java/org/eclipse/jetty/ee9/test/client/transport/Http3AsyncIOServletTest.java new file mode 100644 index 000000000000..737e29e4dd03 --- /dev/null +++ b/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-client-transports/src/test/java/org/eclipse/jetty/ee9/test/client/transport/Http3AsyncIOServletTest.java @@ -0,0 +1,161 @@ +// +// ======================================================================== +// Copyright (c) 1995 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.ee9.test.client.transport; + +import java.net.InetSocketAddress; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; + +import jakarta.servlet.AsyncContext; +import jakarta.servlet.AsyncEvent; +import jakarta.servlet.AsyncListener; +import jakarta.servlet.http.HttpServlet; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.eclipse.jetty.ee9.servlet.ServletContextHandler; +import org.eclipse.jetty.ee9.servlet.ServletHolder; +import org.eclipse.jetty.http.HttpFields; +import org.eclipse.jetty.http.HttpURI; +import org.eclipse.jetty.http.HttpVersion; +import org.eclipse.jetty.http.MetaData; +import org.eclipse.jetty.http3.HTTP3ErrorCode; +import org.eclipse.jetty.http3.api.Session; +import org.eclipse.jetty.http3.api.Stream; +import org.eclipse.jetty.http3.client.HTTP3Client; +import org.eclipse.jetty.http3.frames.HeadersFrame; +import org.eclipse.jetty.http3.server.HTTP3ServerConnectionFactory; +import org.eclipse.jetty.io.EofException; +import org.eclipse.jetty.quic.client.ClientQuicConfiguration; +import org.eclipse.jetty.quic.server.QuicServerConnector; +import org.eclipse.jetty.quic.server.ServerQuicConfiguration; +import org.eclipse.jetty.server.HttpConfiguration; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.toolchain.test.MavenPaths; +import org.eclipse.jetty.toolchain.test.jupiter.WorkDir; +import org.eclipse.jetty.toolchain.test.jupiter.WorkDirExtension; +import org.eclipse.jetty.util.component.LifeCycle; +import org.eclipse.jetty.util.ssl.SslContextFactory; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import static org.awaitility.Awaitility.await; +import static org.eclipse.jetty.http3.api.Session.Client; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.nullValue; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@ExtendWith(WorkDirExtension.class) +public class Http3AsyncIOServletTest +{ + public WorkDir workDir; + + private Server server; + private QuicServerConnector connector; + private HTTP3Client client; + + private void start(HttpConfiguration httpConfig, HttpServlet httpServlet) throws Exception + { + server = new Server(); + SslContextFactory.Server serverSslContextFactory = new SslContextFactory.Server(); + serverSslContextFactory.setKeyStorePath(MavenPaths.findTestResourceFile("keystore.p12").toString()); + serverSslContextFactory.setKeyStorePassword("storepwd"); + ServerQuicConfiguration serverQuicConfiguration = new ServerQuicConfiguration(serverSslContextFactory, workDir.getEmptyPathDir()); + connector = new QuicServerConnector(server, serverQuicConfiguration, new HTTP3ServerConnectionFactory(serverQuicConfiguration, httpConfig)); + server.addConnector(connector); + ServletContextHandler servletContextHandler = new ServletContextHandler(server, "/"); + servletContextHandler.addServlet(new ServletHolder(httpServlet), "/*"); + server.start(); + + client = new HTTP3Client(new ClientQuicConfiguration(new SslContextFactory.Client(true), null)); + client.start(); + } + + @AfterEach + public void tearDown() + { + LifeCycle.stop(client); + LifeCycle.stop(server); + } + + @ParameterizedTest + @ValueSource(booleans = {true, false}) + public void testStartAsyncThenClientResetRemoteErrorNotification(boolean notify) throws Exception + { + HttpConfiguration httpConfig = new HttpConfiguration(); + httpConfig.setNotifyRemoteAsyncErrors(notify); + + AtomicReference errorAsyncEventRef = new AtomicReference<>(); + CountDownLatch latch = new CountDownLatch(1); + start(httpConfig, new HttpServlet() + { + @Override + protected void service(HttpServletRequest request, HttpServletResponse response) + { + AsyncContext asyncContext = request.startAsync(); + asyncContext.addListener(new AsyncListener() + { + @Override + public void onComplete(AsyncEvent event) + { + } + + @Override + public void onTimeout(AsyncEvent event) + { + } + + @Override + public void onError(AsyncEvent event) + { + errorAsyncEventRef.set(event); + asyncContext.complete(); + } + + @Override + public void onStartAsync(AsyncEvent event) + { + } + }); + asyncContext.setTimeout(0); + latch.countDown(); + } + }); + + InetSocketAddress address = new InetSocketAddress("localhost", connector.getLocalPort()); + Session.Client session = client.connect(address, new Client.Listener() {}).get(5, TimeUnit.SECONDS); + MetaData.Request metaData = new MetaData.Request("GET", HttpURI.from("/"), HttpVersion.HTTP_3, HttpFields.EMPTY); + HeadersFrame frame = new HeadersFrame(metaData, false); + Stream stream = session.newRequest(frame, null).get(5, TimeUnit.SECONDS); + + // Wait for the server to be in ASYNC_WAIT. + assertTrue(latch.await(5, TimeUnit.SECONDS)); + Thread.sleep(500); + + stream.reset(HTTP3ErrorCode.REQUEST_CANCELLED_ERROR.code(), new Exception()); + + if (notify) + // Wait for the reset to be notified to the async context listener. + await().atMost(5, TimeUnit.SECONDS).until(() -> + { + AsyncEvent asyncEvent = errorAsyncEventRef.get(); + return asyncEvent == null ? null : asyncEvent.getThrowable(); + }, instanceOf(EofException.class)); + else + // Wait for the reset to NOT be notified to the failure listener. + await().atMost(5, TimeUnit.SECONDS).during(1, TimeUnit.SECONDS).until(errorAsyncEventRef::get, nullValue()); + } +} From bacbabeb06fd02ef49c6ef39be1656a902bd4e20 Mon Sep 17 00:00:00 2001 From: Ludovic Orban Date: Tue, 18 Jun 2024 19:22:12 +0200 Subject: [PATCH 22/22] refactor ee* tests Signed-off-by: Ludovic Orban --- .../test/client/transport/AbstractTest.java | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-client-transports/src/test/java/org/eclipse/jetty/ee10/test/client/transport/AbstractTest.java b/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-client-transports/src/test/java/org/eclipse/jetty/ee10/test/client/transport/AbstractTest.java index 85ab339dc865..a04f32398b8e 100644 --- a/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-client-transports/src/test/java/org/eclipse/jetty/ee10/test/client/transport/AbstractTest.java +++ b/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-client-transports/src/test/java/org/eclipse/jetty/ee10/test/client/transport/AbstractTest.java @@ -32,11 +32,6 @@ import org.eclipse.jetty.ee10.servlet.ServletHolder; import org.eclipse.jetty.fcgi.client.transport.HttpClientTransportOverFCGI; import org.eclipse.jetty.fcgi.server.ServerFCGIConnectionFactory; -import org.eclipse.jetty.http.HostPortHttpField; -import org.eclipse.jetty.http.HttpFields; -import org.eclipse.jetty.http.HttpScheme; -import org.eclipse.jetty.http.HttpVersion; -import org.eclipse.jetty.http.MetaData; import org.eclipse.jetty.http2.HTTP2Cipher; import org.eclipse.jetty.http2.client.HTTP2Client; import org.eclipse.jetty.http2.client.transport.HttpClientTransportOverHTTP2; @@ -202,19 +197,6 @@ protected void startClient(Transport transport, Consumer consumer) t client.start(); } - protected MetaData.Request newRequest(String method, HttpFields fields) - { - return newRequest(method, "/", fields); - } - - protected MetaData.Request newRequest(String method, String path, HttpFields fields) - { - String host = "localhost"; - int port = ((NetworkConnector)connector).getLocalPort(); - String authority = host + ":" + port; - return new MetaData.Request(method, HttpScheme.HTTP.asString(), new HostPortHttpField(authority), path, HttpVersion.HTTP_2, fields, -1); - } - public AbstractConnector newConnector(Transport transport, Server server) { return switch (transport)