diff --git a/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/HttpClient.java b/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/HttpClient.java index d19ff33dd65..bc5877a3f36 100644 --- a/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/HttpClient.java +++ b/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/HttpClient.java @@ -180,13 +180,21 @@ public class HttpClient extends ContainerLifeCycle } /** - * @return the {@link SslContextFactory} that manages TLS encryption + * @return the {@link SslContextFactory.Client} that manages TLS encryption */ public SslContextFactory.Client getSslContextFactory() { return connector.getSslContextFactory(); } + /** + * @param sslContextFactory the {@link SslContextFactory.Client} that manages TLS encryption + */ + public void setSslContextFactory(SslContextFactory.Client sslContextFactory) + { + connector.setSslContextFactory(sslContextFactory); + } + @Override protected void doStart() throws Exception { diff --git a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/AbstractConnector.java b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/AbstractConnector.java index 7d1be1a9159..1fb5f5fb410 100644 --- a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/AbstractConnector.java +++ b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/AbstractConnector.java @@ -185,22 +185,14 @@ public abstract class AbstractConnector extends ContainerLifeCycle implements Co _scheduler = scheduler != null ? scheduler : new ScheduledExecutorScheduler(String.format("Connector-Scheduler-%x", hashCode()), false); addBean(_scheduler); - synchronized (server) + if (pool == null) { + // Look for (and cache) a common pool on the server + pool = server.getBean(ByteBufferPool.class); if (pool == null) { - // Look for (and cache) a common pool on the server - pool = server.getBean(ByteBufferPool.class); - if (pool == null) - { - pool = new LogarithmicArrayByteBufferPool(); - server.addBean(pool, true); - } - addBean(pool, false); - } - else - { - addBean(pool, true); + pool = new LogarithmicArrayByteBufferPool(); + server.addBean(pool, true); } } _byteBufferPool = pool; diff --git a/jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/AbstractTest.java b/jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/AbstractTest.java index fc03e2b0757..b468618ef4c 100644 --- a/jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/AbstractTest.java +++ b/jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/AbstractTest.java @@ -27,15 +27,18 @@ import org.eclipse.jetty.fcgi.server.ServerFCGIConnectionFactory; 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.server.AbstractHTTP2ServerConnectionFactory; import org.eclipse.jetty.http2.server.HTTP2CServerConnectionFactory; import org.eclipse.jetty.http2.server.HTTP2ServerConnectionFactory; import org.eclipse.jetty.http3.client.HTTP3Client; import org.eclipse.jetty.http3.client.transport.HttpClientTransportOverHTTP3; +import org.eclipse.jetty.http3.server.AbstractHTTP3ServerConnectionFactory; import org.eclipse.jetty.http3.server.HTTP3ServerConnectionFactory; import org.eclipse.jetty.http3.server.HTTP3ServerConnector; import org.eclipse.jetty.io.ClientConnector; +import org.eclipse.jetty.quic.server.QuicServerConnector; +import org.eclipse.jetty.server.AbstractConnector; import org.eclipse.jetty.server.ConnectionFactory; -import org.eclipse.jetty.server.Connector; import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.HostHeaderCustomizer; import org.eclipse.jetty.server.HttpConfiguration; @@ -59,7 +62,7 @@ public class AbstractTest protected final HttpConfiguration httpConfig = new HttpConfiguration(); protected SslContextFactory.Server sslContextFactoryServer; protected Server server; - protected Connector connector; + protected AbstractConnector connector; protected HttpClient client; protected Path unixDomainPath; @@ -97,14 +100,20 @@ public class AbstractTest Files.delete(unixDomainPath); } sslContextFactoryServer = newSslContextFactoryServer(); - QueuedThreadPool serverThreads = new QueuedThreadPool(); - serverThreads.setName("server"); - server = new Server(serverThreads); + if (server == null) + server = newServer(); connector = newConnector(transport, server); server.addConnector(connector); server.setHandler(handler); } + protected Server newServer() + { + QueuedThreadPool serverThreads = new QueuedThreadPool(); + serverThreads.setName("server"); + return new Server(serverThreads); + } + protected SslContextFactory.Server newSslContextFactoryServer() { SslContextFactory.Server ssl = new SslContextFactory.Server(); @@ -125,7 +134,7 @@ public class AbstractTest client.start(); } - public Connector newConnector(Transport transport, Server server) + public AbstractConnector newConnector(Transport transport, Server server) { return switch (transport) { @@ -238,6 +247,37 @@ public class AbstractTest return URI.create(uri); } + protected void setStreamIdleTimeout(long idleTimeout) + { + AbstractHTTP2ServerConnectionFactory h2 = connector.getConnectionFactory(AbstractHTTP2ServerConnectionFactory.class); + if (h2 != null) + { + h2.setStreamIdleTimeout(idleTimeout); + } + else + { + AbstractHTTP3ServerConnectionFactory h3 = connector.getConnectionFactory(AbstractHTTP3ServerConnectionFactory.class); + if (h3 != null) + h3.getHTTP3Configuration().setStreamIdleTimeout(idleTimeout); + else + connector.setIdleTimeout(idleTimeout); + } + } + + protected void setMaxRequestsPerConnection(int maxRequestsPerConnection) + { + AbstractHTTP2ServerConnectionFactory h2 = connector.getConnectionFactory(AbstractHTTP2ServerConnectionFactory.class); + if (h2 != null) + { + h2.setMaxConcurrentStreams(maxRequestsPerConnection); + } + else + { + if (connector instanceof QuicServerConnector) + ((QuicServerConnector)connector).getQuicConfiguration().setMaxBidirectionalRemoteStreams(maxRequestsPerConnection); + } + } + public enum Transport { HTTP, HTTPS, H2C, H2, H3, FCGI, UNIX_DOMAIN; @@ -250,5 +290,14 @@ public class AbstractTest case HTTPS, H2, H3 -> true; }; } + + public boolean isMultiplexed() + { + return switch (this) + { + case HTTP, HTTPS, FCGI, UNIX_DOMAIN -> false; + case H2C, H2, H3 -> true; + }; + } } } diff --git a/jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/Continue100Test.java b/jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/Continue100Test.java new file mode 100644 index 00000000000..cf79e9bd709 --- /dev/null +++ b/jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/Continue100Test.java @@ -0,0 +1,19 @@ +// +// ======================================================================== +// Copyright (c) 1995-2022 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License v. 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +package org.eclipse.jetty.test.client.transport; + +// TODO: write 100 Continue tests for Handler (not Servlet) functionality. +public class Continue100Test +{ +} diff --git a/jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/EmptyServerHandler.java b/jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/EmptyServerHandler.java new file mode 100644 index 00000000000..abc0bd321d6 --- /dev/null +++ b/jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/EmptyServerHandler.java @@ -0,0 +1,28 @@ +// +// ======================================================================== +// Copyright (c) 1995-2022 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License v. 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +package org.eclipse.jetty.test.client.transport; + +import org.eclipse.jetty.server.Handler; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Response; +import org.eclipse.jetty.util.Callback; + +public class EmptyServerHandler extends Handler.Processor +{ + @Override + public void process(Request request, Response response, Callback callback) + { + callback.succeeded(); + } +} diff --git a/jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/HttpChannelAssociationTest.java b/jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/HttpChannelAssociationTest.java index 31939fdb720..ed7d5ab2f52 100644 --- a/jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/HttpChannelAssociationTest.java +++ b/jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/HttpChannelAssociationTest.java @@ -43,9 +43,6 @@ import org.eclipse.jetty.http3.client.transport.internal.HttpChannelOverHTTP3; import org.eclipse.jetty.http3.client.transport.internal.HttpConnectionOverHTTP3; import org.eclipse.jetty.io.ClientConnector; import org.eclipse.jetty.io.EndPoint; -import org.eclipse.jetty.server.Handler; -import org.eclipse.jetty.server.Response; -import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.Promise; import org.eclipse.jetty.util.thread.QueuedThreadPool; import org.junit.jupiter.params.ParameterizedTest; @@ -59,14 +56,7 @@ public class HttpChannelAssociationTest extends AbstractTest @MethodSource("transports") public void testAssociationFailedAbortsRequest(Transport transport) throws Exception { - startServer(transport, new Handler.Processor() - { - @Override - public void process(org.eclipse.jetty.server.Request request, Response response, Callback callback) - { - callback.succeeded(); - } - }); + startServer(transport, new EmptyServerHandler()); client = new HttpClient(newHttpClientTransport(transport, exchange -> false)); QueuedThreadPool clientThreads = new QueuedThreadPool(); @@ -89,14 +79,7 @@ public class HttpChannelAssociationTest extends AbstractTest @MethodSource("transports") public void testIdleTimeoutJustBeforeAssociation(Transport transport) throws Exception { - startServer(transport, new Handler.Processor() - { - @Override - public void process(org.eclipse.jetty.server.Request request, Response response, Callback callback) - { - callback.succeeded(); - } - }); + startServer(transport, new EmptyServerHandler()); long idleTimeout = 1000; client = new HttpClient(newHttpClientTransport(transport, exchange -> diff --git a/jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/HttpClientConnectTimeoutTest.java b/jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/HttpClientConnectTimeoutTest.java index 8b4eeb0b16c..e18ff9a2019 100644 --- a/jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/HttpClientConnectTimeoutTest.java +++ b/jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/HttpClientConnectTimeoutTest.java @@ -22,9 +22,6 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import org.eclipse.jetty.client.api.Request; -import org.eclipse.jetty.server.Handler; -import org.eclipse.jetty.server.Response; -import org.eclipse.jetty.util.Callback; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; @@ -45,17 +42,8 @@ public class HttpClientConnectTimeoutTest extends AbstractTest int connectTimeout = 1000; assumeTrue(connectTimeout(host, port, connectTimeout)); - start(transport, new Handler.Processor() - { - @Override - public void process(org.eclipse.jetty.server.Request request, Response response, Callback callback) throws Exception - { - callback.succeeded(); - } - }); - client.stop(); + start(transport, new EmptyServerHandler()); client.setConnectTimeout(connectTimeout); - client.start(); CountDownLatch latch = new CountDownLatch(1); Request request = client.newRequest(host, port); @@ -78,17 +66,8 @@ public class HttpClientConnectTimeoutTest extends AbstractTest int connectTimeout = 2000; assumeTrue(connectTimeout(host, port, connectTimeout)); - start(transport, new Handler.Processor() - { - @Override - public void process(org.eclipse.jetty.server.Request request, Response response, Callback callback) throws Exception - { - callback.succeeded(); - } - }); - client.stop(); + start(transport, new EmptyServerHandler()); client.setConnectTimeout(connectTimeout); - client.start(); AtomicInteger completes = new AtomicInteger(); CountDownLatch latch = new CountDownLatch(2); @@ -114,17 +93,8 @@ public class HttpClientConnectTimeoutTest extends AbstractTest int connectTimeout = 1000; assumeTrue(connectTimeout(host, port, connectTimeout)); - start(transport, new Handler.Processor() - { - @Override - public void process(org.eclipse.jetty.server.Request request, Response response, Callback callback) throws Exception - { - callback.succeeded(); - } - }); - client.stop(); + start(transport, new EmptyServerHandler()); client.setConnectTimeout(connectTimeout); - client.start(); CountDownLatch latch = new CountDownLatch(1); Request request = client.newRequest(host, port); diff --git a/jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/HttpClientDemandTest.java b/jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/HttpClientDemandTest.java index 41f1f22856a..2e83161209f 100644 --- a/jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/HttpClientDemandTest.java +++ b/jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/HttpClientDemandTest.java @@ -454,14 +454,7 @@ public class HttpClientDemandTest extends AbstractTest @MethodSource("transports") public void testDelayedBeforeContentDemandWithNoResponseContent(Transport transport) throws Exception { - start(transport, new Handler.Processor() - { - @Override - public void process(Request request, org.eclipse.jetty.server.Response response, Callback callback) - { - callback.succeeded(); - } - }); + start(transport, new EmptyServerHandler()); AtomicReference beforeContentDemandRef = new AtomicReference<>(); CountDownLatch beforeContentLatch = new CountDownLatch(1); diff --git a/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-http-client-transport/src/test/java/org/eclipse/jetty/ee9/http/client/HttpClientIdleTimeoutTest.java b/jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/HttpClientIdleTimeoutTest.java similarity index 52% rename from jetty-ee9/jetty-ee9-tests/jetty-ee9-test-http-client-transport/src/test/java/org/eclipse/jetty/ee9/http/client/HttpClientIdleTimeoutTest.java rename to jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/HttpClientIdleTimeoutTest.java index 2728ecce919..5513eddb338 100644 --- a/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-http-client-transport/src/test/java/org/eclipse/jetty/ee9/http/client/HttpClientIdleTimeoutTest.java +++ b/jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/HttpClientIdleTimeoutTest.java @@ -11,57 +11,45 @@ // ======================================================================== // -package org.eclipse.jetty.ee9.http.client; +package org.eclipse.jetty.test.client.transport; -import java.io.IOException; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; -import jakarta.servlet.AsyncContext; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; import org.eclipse.jetty.client.api.ContentResponse; import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.Request; -import org.eclipse.jetty.server.handler.AbstractHandler; +import org.eclipse.jetty.server.Response; +import org.eclipse.jetty.util.Callback; import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.ArgumentsSource; +import org.junit.jupiter.params.provider.MethodSource; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; -public class HttpClientIdleTimeoutTest extends AbstractTest +public class HttpClientIdleTimeoutTest extends AbstractTest { private final long idleTimeout = 1000; - @Override - public void init(Transport transport) throws IOException - { - setScenario(new TransportScenario(transport)); - } - @ParameterizedTest - @ArgumentsSource(TransportProvider.class) + @MethodSource("transports") public void testClientIdleTimeout(Transport transport) throws Exception { - init(transport); - scenario.startServer(new AbstractHandler() + start(transport, new Handler.Processor() { @Override - public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) + public void process(Request request, Response response, Callback callback) throws Exception { - baseRequest.setHandled(true); - if (target.equals("/timeout")) - { - AsyncContext asyncContext = request.startAsync(); - asyncContext.setTimeout(0); - } + // Do not succeed the callback if it's a timeout request. + if (!request.getPathInContext().equals("/timeout")) + callback.succeeded(); } }); - scenario.startClient(httpClient -> httpClient.setIdleTimeout(idleTimeout)); + client.setIdleTimeout(idleTimeout); - final CountDownLatch latch = new CountDownLatch(1); - scenario.client.newRequest(scenario.newURI()) + CountDownLatch latch = new CountDownLatch(1); + client.newRequest(newURI(transport)) .path("/timeout") .send(result -> { @@ -72,31 +60,29 @@ public class HttpClientIdleTimeoutTest extends AbstractTest assertTrue(latch.await(2 * idleTimeout, TimeUnit.MILLISECONDS)); // Verify that after the timeout we can make another request. - ContentResponse response = scenario.client.newRequest(scenario.newURI()).send(); + ContentResponse response = client.newRequest(newURI(transport)) + .timeout(5, TimeUnit.SECONDS) + .send(); assertEquals(HttpStatus.OK_200, response.getStatus()); } @ParameterizedTest - @ArgumentsSource(TransportProvider.class) + @MethodSource("transports") public void testRequestIdleTimeout(Transport transport) throws Exception { - init(transport); - scenario.start(new AbstractHandler() + start(transport, new Handler.Processor() { @Override - public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) + public void process(Request request, Response response, Callback callback) throws Exception { - baseRequest.setHandled(true); - if (target.equals("/timeout")) - { - AsyncContext asyncContext = request.startAsync(); - asyncContext.setTimeout(0); - } + // Do not succeed the callback if it's a timeout request. + if (!request.getPathInContext().equals("/timeout")) + callback.succeeded(); } }); - final CountDownLatch latch = new CountDownLatch(1); - scenario.client.newRequest(scenario.newURI()) + CountDownLatch latch = new CountDownLatch(1); + client.newRequest(newURI(transport)) .path("/timeout") .idleTimeout(idleTimeout, TimeUnit.MILLISECONDS) .send(result -> @@ -108,46 +94,46 @@ public class HttpClientIdleTimeoutTest extends AbstractTest assertTrue(latch.await(2 * idleTimeout, TimeUnit.MILLISECONDS)); // Verify that after the timeout we can make another request. - ContentResponse response = scenario.client.newRequest(scenario.newURI()).send(); + ContentResponse response = client.newRequest(newURI(transport)) + .timeout(5, TimeUnit.SECONDS) + .send(); assertEquals(HttpStatus.OK_200, response.getStatus()); } @ParameterizedTest - @ArgumentsSource(TransportProvider.class) + @MethodSource("transports") public void testIdleClientIdleTimeout(Transport transport) throws Exception { - init(transport); - scenario.startServer(new EmptyServerHandler()); - scenario.startClient(httpClient -> httpClient.setIdleTimeout(idleTimeout)); + start(transport, new EmptyServerHandler()); + client.setIdleTimeout(idleTimeout); // Make a first request to open a connection. - ContentResponse response = scenario.client.newRequest(scenario.newURI()).send(); + ContentResponse response = client.newRequest(newURI(transport)).send(); assertEquals(HttpStatus.OK_200, response.getStatus()); // Let the connection idle timeout. Thread.sleep(2 * idleTimeout); // Verify that after the timeout we can make another request. - response = scenario.client.newRequest(scenario.newURI()).send(); + response = client.newRequest(newURI(transport)).send(); assertEquals(HttpStatus.OK_200, response.getStatus()); } @ParameterizedTest - @ArgumentsSource(TransportProvider.class) + @MethodSource("transports") public void testIdleServerIdleTimeout(Transport transport) throws Exception { - init(transport); - scenario.start(new EmptyServerHandler()); - scenario.setConnectionIdleTimeout(idleTimeout); + start(transport, new EmptyServerHandler()); + connector.setIdleTimeout(idleTimeout); - ContentResponse response1 = scenario.client.newRequest(scenario.newURI()).send(); + ContentResponse response1 = client.newRequest(newURI(transport)).send(); assertEquals(HttpStatus.OK_200, response1.getStatus()); // Let the server idle timeout. Thread.sleep(2 * idleTimeout); // Make sure we can make another request successfully. - ContentResponse response2 = scenario.client.newRequest(scenario.newURI()).send(); + ContentResponse response2 = client.newRequest(newURI(transport)).send(); assertEquals(HttpStatus.OK_200, response2.getStatus()); } } diff --git a/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-http-client-transport/src/test/java/org/eclipse/jetty/ee9/http/client/HttpClientLoadTest.java b/jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/HttpClientLoadTest.java similarity index 55% rename from jetty-ee9/jetty-ee9-tests/jetty-ee9-test-http-client-transport/src/test/java/org/eclipse/jetty/ee9/http/client/HttpClientLoadTest.java rename to jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/HttpClientLoadTest.java index 8a6c0d129a2..6dc832b9592 100644 --- a/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-http-client-transport/src/test/java/org/eclipse/jetty/ee9/http/client/HttpClientLoadTest.java +++ b/jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/HttpClientLoadTest.java @@ -11,10 +11,8 @@ // ======================================================================== // -package org.eclipse.jetty.ee9.http.client; +package org.eclipse.jetty.test.client.transport; -import java.io.IOException; -import java.io.InterruptedIOException; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.List; @@ -27,9 +25,6 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; import java.util.stream.IntStream; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import org.eclipse.jetty.client.HttpClientTransport; import org.eclipse.jetty.client.LeakTrackingConnectionPool; import org.eclipse.jetty.client.api.Connection; import org.eclipse.jetty.client.api.Request; @@ -39,62 +34,68 @@ import org.eclipse.jetty.client.util.BytesRequestContent; import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpHeaderValue; import org.eclipse.jetty.http.HttpMethod; -import org.eclipse.jetty.http3.server.HTTP3ServerConnector; -import org.eclipse.jetty.io.ArrayByteBufferPool; import org.eclipse.jetty.io.ByteBufferPool; +import org.eclipse.jetty.io.Content; import org.eclipse.jetty.io.LeakTrackingByteBufferPool; +import org.eclipse.jetty.io.LogarithmicArrayByteBufferPool; import org.eclipse.jetty.io.MappedByteBufferPool; -import org.eclipse.jetty.server.Connector; -import org.eclipse.jetty.server.Server; -import org.eclipse.jetty.server.ServerConnector; -import org.eclipse.jetty.server.handler.AbstractHandler; -import org.eclipse.jetty.unixdomain.server.UnixDomainServerConnector; -import org.eclipse.jetty.util.IO; +import org.eclipse.jetty.server.Handler; +import org.eclipse.jetty.server.NetworkConnector; +import org.eclipse.jetty.util.BufferUtil; +import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.LeakDetector; import org.eclipse.jetty.util.NanoTime; -import org.eclipse.jetty.util.ProcessorUtils; -import org.eclipse.jetty.util.ssl.SslContextFactory; import org.eclipse.jetty.util.thread.Scheduler; import org.hamcrest.Matchers; import org.junit.jupiter.api.Assumptions; import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.ArgumentsSource; +import org.junit.jupiter.params.provider.MethodSource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.jupiter.api.Assertions.assertTrue; -public class HttpClientLoadTest extends AbstractTest +public class HttpClientLoadTest extends AbstractTest { private final Logger logger = LoggerFactory.getLogger(HttpClientLoadTest.class); private final AtomicLong requestCount = new AtomicLong(); private final AtomicLong connectionLeaks = new AtomicLong(); - @Override - public void init(Transport transport) throws IOException - { - setScenario(new LoadTransportScenario(transport, connectionLeaks)); - } - @ParameterizedTest - @ArgumentsSource(TransportProvider.class) + @MethodSource("transports") public void testIterative(Transport transport) throws Exception { // TODO: cannot run HTTP/3 (or UDP) in Jenkins. if ("ci".equals(System.getProperty("env"))) Assumptions.assumeTrue(transport != Transport.H3); - init(transport); - scenario.start(new LoadHandler(), client -> + server = newServer(); + server.addBean(new LeakTrackingByteBufferPool(new LogarithmicArrayByteBufferPool())); + start(transport, new LoadHandler()); + setStreamIdleTimeout(120000); + client.stop(); + client.setByteBufferPool(new LeakTrackingByteBufferPool(new MappedByteBufferPool.Tagged())); + client.setMaxConnectionsPerDestination(32768); + client.setMaxRequestsQueuedPerDestination(1024 * 1024); + client.setIdleTimeout(120000); + switch (transport) { - client.setByteBufferPool(new LeakTrackingByteBufferPool(new MappedByteBufferPool.Tagged())); - client.setMaxConnectionsPerDestination(32768); - client.setMaxRequestsQueuedPerDestination(1024 * 1024); - }); - scenario.setConnectionIdleTimeout(120000); - scenario.setRequestIdleTimeout(120000); - scenario.client.setIdleTimeout(120000); + case HTTP, HTTPS, FCGI, UNIX_DOMAIN -> + { + // Track connection leaking only for non-multiplexed transports. + client.getTransport().setConnectionPoolFactory(destination -> new LeakTrackingConnectionPool(destination, client.getMaxConnectionsPerDestination(), destination) + { + @Override + protected void leaked(LeakDetector.LeakInfo leakInfo) + { + super.leaked(leakInfo); + connectionLeaks.incrementAndGet(); + } + }); + } + } + client.start(); // At least 25k requests to warmup properly (use -XX:+PrintCompilation to verify JIT activity) int runs = 1; @@ -115,19 +116,18 @@ public class HttpClientLoadTest extends AbstractTest - { - client.setByteBufferPool(new LeakTrackingByteBufferPool(new MappedByteBufferPool.Tagged())); - client.setMaxConnectionsPerDestination(32768); - client.setMaxRequestsQueuedPerDestination(1024 * 1024); - }); + start(transport, new LoadHandler()); + client.stop(); + client.setByteBufferPool(new LeakTrackingByteBufferPool(new MappedByteBufferPool.Tagged())); + client.setMaxConnectionsPerDestination(32768); + client.setMaxRequestsQueuedPerDestination(1024 * 1024); + client.start(); int runs = 1; int iterations = 128; @@ -142,20 +142,18 @@ public class HttpClientLoadTest extends AbstractTest + Scheduler.Task task = client.getScheduler().schedule(() -> { logger.warn("Interrupting test, it is taking too long (maxTime={} ms){}{}{}{}", maxTime, - System.lineSeparator(), scenario.server.dump(), - System.lineSeparator(), scenario.client.dump()); + System.lineSeparator(), server.dump(), + System.lineSeparator(), client.dump()); testThread.interrupt(); }, maxTime, TimeUnit.MILLISECONDS); long begin = NanoTime.now(); for (int i = 0; i < iterations; ++i) { - test(latch, failures); + test(transport, latch, failures); // test("http", "localhost", "GET", false, false, 64 * 1024, false, latch, failures); } long end = NanoTime.now(); @@ -202,7 +200,7 @@ public class HttpClientLoadTest extends AbstractTest failures) + private void test(Transport transport, CountDownLatch latch, List failures) { ThreadLocalRandom random = ThreadLocalRandom.current(); // Choose a random destination @@ -210,7 +208,7 @@ public class HttpClientLoadTest extends AbstractTest failures) + private void test(String uri, String method, boolean clientClose, boolean serverClose, long clientTimeout, int contentLength, boolean checkContentLength, CountDownLatch latch, List failures) { long requestId = requestCount.incrementAndGet(); - Request request = scenario.client.newRequest(host, scenario.getServerPort().orElse(0)) - .scheme(scheme) + Request request = client.newRequest(uri) .path("/" + requestId) .method(method); @@ -247,13 +247,12 @@ public class HttpClientLoadTest extends AbstractTest headers.put("X-Download", String.valueOf(contentLength))); - break; - case "POST": + case "GET" -> request.headers(headers -> headers.put("X-Download", String.valueOf(contentLength))); + case "POST" -> + { request.headers(headers -> headers.put("X-Upload", String.valueOf(contentLength))); request.body(new BytesRequestContent(new byte[contentLength])); - break; + } } CountDownLatch requestLatch = new CountDownLatch(1); @@ -302,19 +301,19 @@ public class HttpClientLoadTest extends AbstractTest { - int contentLength = request.getIntHeader("X-Download"); - if (contentLength > 0) + ByteBuffer content = BufferUtil.EMPTY_BUFFER; + int contentLength = (int)request.getHeaders().getLongField("X-Download"); + if (contentLength >= 0) { - response.setHeader("X-Content", String.valueOf(contentLength)); - response.getOutputStream().write(new byte[contentLength]); + response.getHeaders().putLongField("X-Content", contentLength); + content = ByteBuffer.allocate(contentLength); } - break; + response.write(true, content, callback); } - case "POST": + case "POST" -> { - response.setHeader("X-Content", request.getHeader("X-Upload")); - IO.copy(request.getInputStream(), response.getOutputStream()); - break; + response.getHeaders().putLongField("X-Content", request.getHeaders().getLongField("X-Upload")); + Content.copy(request, response, callback); } } - if (Boolean.parseBoolean(request.getHeader("X-Close"))) - response.setHeader("Connection", "close"); - } - - private void sleep(long time) throws InterruptedIOException - { - try - { - Thread.sleep(time); - } - catch (InterruptedException x) - { - throw new InterruptedIOException(); - } - } - } - - public static class LoadTransportScenario extends TransportScenario - { - private final AtomicLong connectionLeaks; - - public LoadTransportScenario(Transport transport, AtomicLong connectionLeaks) throws IOException - { - super(transport); - this.connectionLeaks = connectionLeaks; - } - - @Override - public Connector newServerConnector(Server server) - { - int selectors = Math.min(1, ProcessorUtils.availableProcessors() / 2); - ByteBufferPool byteBufferPool = new ArrayByteBufferPool(); - byteBufferPool = new LeakTrackingByteBufferPool(byteBufferPool); - switch (transport) - { - case HTTP: - case HTTPS: - case H2C: - case H2: - case FCGI: - return new ServerConnector(server, null, null, byteBufferPool, 1, selectors, provideServerConnectionFactory(transport)); - case H3: - return new HTTP3ServerConnector(server, null, null, byteBufferPool, sslContextFactory, provideServerConnectionFactory(transport)); - case UNIX_DOMAIN: - UnixDomainServerConnector unixSocketConnector = new UnixDomainServerConnector(server, null, null, byteBufferPool, 1, selectors, provideServerConnectionFactory(transport)); - unixSocketConnector.setUnixDomainPath(unixDomainPath); - return unixSocketConnector; - default: - throw new IllegalStateException(); - } - } - - @Override - public HttpClientTransport provideClientTransport(Transport transport, SslContextFactory.Client sslContextFactory) - { - HttpClientTransport clientTransport = super.provideClientTransport(transport, sslContextFactory); - switch (transport) - { - case HTTP: - case HTTPS: - case FCGI: - case UNIX_DOMAIN: - { - // Track connection leaking only for non-multiplexed transports. - clientTransport.setConnectionPoolFactory(destination -> new LeakTrackingConnectionPool(destination, client.getMaxConnectionsPerDestination(), destination) - { - @Override - protected void leaked(LeakDetector.LeakInfo leakInfo) - { - super.leaked(leakInfo); - connectionLeaks.incrementAndGet(); - } - }); - break; - } - default: - { - break; - } - } - return clientTransport; + if (Boolean.parseBoolean(request.getHeaders().get("X-Close"))) + response.getHeaders().put(HttpHeader.CONNECTION, HttpHeaderValue.CLOSE); } } } diff --git a/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-http-client-transport/src/test/java/org/eclipse/jetty/ee9/http/client/HttpClientStreamTest.java b/jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/HttpClientStreamTest.java similarity index 64% rename from jetty-ee9/jetty-ee9-tests/jetty-ee9-test-http-client-transport/src/test/java/org/eclipse/jetty/ee9/http/client/HttpClientStreamTest.java rename to jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/HttpClientStreamTest.java index 275da51040e..fa2658ea211 100644 --- a/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-http-client-transport/src/test/java/org/eclipse/jetty/ee9/http/client/HttpClientStreamTest.java +++ b/jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/HttpClientStreamTest.java @@ -11,14 +11,14 @@ // ======================================================================== // -package org.eclipse.jetty.ee9.http.client; +package org.eclipse.jetty.test.client.transport; import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InterruptedIOException; import java.io.OutputStream; +import java.net.URI; import java.nio.ByteBuffer; import java.nio.channels.AsynchronousCloseException; import java.nio.charset.StandardCharsets; @@ -36,12 +36,6 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; -import jakarta.servlet.AsyncContext; -import jakarta.servlet.ReadListener; -import jakarta.servlet.ServletOutputStream; -import jakarta.servlet.http.HttpServlet; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; import org.eclipse.jetty.client.api.ContentResponse; import org.eclipse.jetty.client.api.Response; import org.eclipse.jetty.client.api.Result; @@ -51,17 +45,19 @@ import org.eclipse.jetty.client.util.BytesRequestContent; import org.eclipse.jetty.client.util.InputStreamRequestContent; import org.eclipse.jetty.client.util.InputStreamResponseListener; import org.eclipse.jetty.client.util.OutputStreamRequestContent; -import org.eclipse.jetty.http.HttpMethod; +import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.io.Content; +import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.Request; -import org.eclipse.jetty.server.handler.AbstractHandler; import org.eclipse.jetty.toolchain.test.MavenTestingUtils; +import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.IO; import org.eclipse.jetty.util.NanoTime; import org.junit.jupiter.api.Assumptions; import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.ArgumentsSource; +import org.junit.jupiter.params.provider.MethodSource; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.instanceOf; @@ -74,19 +70,12 @@ import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; -public class HttpClientStreamTest extends AbstractTest +public class HttpClientStreamTest extends AbstractTest { - @Override - public void init(Transport transport) throws IOException - { - setScenario(new TransportScenario(transport)); - } - @ParameterizedTest - @ArgumentsSource(TransportProvider.class) + @MethodSource("transports") public void testFileUpload(Transport transport) throws Exception { - init(transport); // Prepare a big file to upload Path targetTestsDir = MavenTestingUtils.getTargetTestingDir().toPath(); Files.createDirectories(targetTestsDir); @@ -100,30 +89,23 @@ public class HttpClientStreamTest extends AbstractTest } } - scenario.start(new AbstractHandler() + start(transport, new Handler.Processor() { @Override - public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + public void process(Request request, org.eclipse.jetty.server.Response response, Callback callback) throws Exception { - baseRequest.setHandled(true); response.setStatus(200); - response.setContentLength(0); - response.flushBuffer(); + response.getHeaders().putLongField(HttpHeader.CONTENT_LENGTH, 0); + response.write(false, BufferUtil.EMPTY_BUFFER, Callback.NOOP); - InputStream in = request.getInputStream(); - byte[] buffer = new byte[1024]; - while (true) - { - int read = in.read(buffer); - if (read < 0) - break; - } + Content.Source.consumeAll(request); + + callback.succeeded(); } }); AtomicLong requestTime = new AtomicLong(); - ContentResponse response = scenario.client.newRequest(scenario.newURI()) - .scheme(scenario.getScheme()) + ContentResponse response = client.newRequest(newURI(transport)) .file(upload) .onRequestSuccess(request -> requestTime.set(NanoTime.now())) .timeout(2, TimeUnit.MINUTES) @@ -139,26 +121,23 @@ public class HttpClientStreamTest extends AbstractTest } @ParameterizedTest - @ArgumentsSource(TransportProvider.class) + @MethodSource("transports") public void testDownload(Transport transport) throws Exception { - init(transport); byte[] data = new byte[128 * 1024]; byte value = 1; Arrays.fill(data, value); - scenario.start(new AbstractHandler() + start(transport, new Handler.Processor() { @Override - public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + public void process(Request request, org.eclipse.jetty.server.Response response, Callback callback) { - baseRequest.setHandled(true); - response.getOutputStream().write(data); + response.write(true, ByteBuffer.wrap(data), callback); } }); InputStreamResponseListener listener = new InputStreamResponseListener(); - scenario.client.newRequest(scenario.newURI()) - .scheme(scenario.getScheme()) + client.newRequest(newURI(transport)) .send(listener); Response response = listener.get(5, TimeUnit.SECONDS); assertNotNull(response); @@ -184,24 +163,21 @@ public class HttpClientStreamTest extends AbstractTest } @ParameterizedTest - @ArgumentsSource(TransportProvider.class) + @MethodSource("transports") public void testDownloadOfUTF8Content(Transport transport) throws Exception { - init(transport); byte[] data = new byte[]{(byte)0xC3, (byte)0xA8}; // UTF-8 representation of è - scenario.start(new AbstractHandler() + start(transport, new Handler.Processor() { @Override - public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + public void process(Request request, org.eclipse.jetty.server.Response response, Callback callback) { - baseRequest.setHandled(true); - response.getOutputStream().write(data); + response.write(true, ByteBuffer.wrap(data), callback); } }); InputStreamResponseListener listener = new InputStreamResponseListener(); - scenario.client.newRequest(scenario.newURI()) - .scheme(scenario.getScheme()) + client.newRequest(newURI(transport)) .send(listener); Response response = listener.get(5, TimeUnit.SECONDS); assertNotNull(response); @@ -226,31 +202,29 @@ public class HttpClientStreamTest extends AbstractTest } @ParameterizedTest - @ArgumentsSource(TransportProvider.class) + @MethodSource("transports") public void testDownloadWithFailure(Transport transport) throws Exception { - init(transport); byte[] data = new byte[64 * 1024]; byte value = 1; Arrays.fill(data, value); - scenario.start(new AbstractHandler() + start(transport, new Handler.Processor() { @Override - public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + public void process(Request request, org.eclipse.jetty.server.Response response, Callback callback) { - baseRequest.setHandled(true); // Say we want to send this much... - response.setContentLength(2 * data.length); + response.getHeaders().putLongField(HttpHeader.CONTENT_LENGTH, 2 * data.length); // ...but write only half... - response.getOutputStream().write(data); + response.write(false, ByteBuffer.wrap(data), Callback.NOOP); // ...then shutdown output - baseRequest.getHttpChannel().getEndPoint().shutdownOutput(); + request.getConnectionMetaData().getConnection().getEndPoint().shutdownOutput(); + callback.succeeded(); } }); InputStreamResponseListener listener = new InputStreamResponseListener(); - scenario.client.newRequest(scenario.newURI()) - .scheme(scenario.getScheme()) + client.newRequest(newURI(transport)) .send(listener); Response response = listener.get(5, TimeUnit.SECONDS); assertNotNull(response); @@ -278,17 +252,15 @@ public class HttpClientStreamTest extends AbstractTest } @ParameterizedTest - @ArgumentsSource(TransportProvider.class) + @MethodSource("transports") public void testInputStreamResponseListenerClosedBeforeReading(Transport transport) throws Exception { - init(transport); - scenario.start(new AbstractHandler() + start(transport, new Handler.Processor() { @Override - public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + public void process(Request request, org.eclipse.jetty.server.Response response, Callback callback) { - baseRequest.setHandled(true); - IO.copy(request.getInputStream(), response.getOutputStream()); + Content.copy(request, response, callback); } }); @@ -297,8 +269,7 @@ public class HttpClientStreamTest extends AbstractTest // Close the stream immediately. stream.close(); - scenario.client.newRequest(scenario.newURI()) - .scheme(scenario.getScheme()) + client.newRequest(newURI(transport)) .body(new BytesRequestContent(new byte[]{0, 1, 2, 3})) .send(listener); Response response = listener.get(5, TimeUnit.SECONDS); @@ -308,19 +279,17 @@ public class HttpClientStreamTest extends AbstractTest } @ParameterizedTest - @ArgumentsSource(TransportProvider.class) + @MethodSource("transports") public void testInputStreamResponseListenerClosedBeforeContent(Transport transport) throws Exception { - init(transport); - AtomicReference contextRef = new AtomicReference<>(); - scenario.start(new AbstractHandler() + AtomicReference contextRef = new AtomicReference<>(); + start(transport, new Handler.Processor() { @Override - public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + public void process(Request request, org.eclipse.jetty.server.Response response, Callback callback) { - baseRequest.setHandled(true); - contextRef.set(request.startAsync()); - response.flushBuffer(); + contextRef.set(new HandlerContext(request, response, callback)); + response.write(false, BufferUtil.EMPTY_BUFFER, Callback.NOOP); } }); @@ -341,8 +310,7 @@ public class HttpClientStreamTest extends AbstractTest }); } }; - scenario.client.newRequest(scenario.newURI()) - .scheme(scenario.getScheme()) + client.newRequest(newURI(transport)) .send(listener); Response response = listener.get(5, TimeUnit.SECONDS); @@ -351,9 +319,8 @@ public class HttpClientStreamTest extends AbstractTest InputStream input = listener.getInputStream(); input.close(); - AsyncContext asyncContext = contextRef.get(); - asyncContext.getResponse().getOutputStream().write(new byte[1024]); - asyncContext.complete(); + HandlerContext handlerContext = contextRef.get(); + handlerContext.response().write(true, ByteBuffer.allocate(1024), handlerContext.callback()); assertTrue(latch.await(5, TimeUnit.SECONDS)); @@ -361,23 +328,19 @@ public class HttpClientStreamTest extends AbstractTest } @ParameterizedTest - @ArgumentsSource(TransportProvider.class) + @MethodSource("transports") public void testInputStreamResponseListenerClosedWhileWaiting(Transport transport) throws Exception { - init(transport); byte[] chunk1 = new byte[]{0, 1}; byte[] chunk2 = new byte[]{2, 3}; - scenario.start(new AbstractHandler() + start(transport, new Handler.Processor() { @Override - public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + public void process(Request request, org.eclipse.jetty.server.Response response, Callback callback) { - baseRequest.setHandled(true); - response.setContentLength(chunk1.length + chunk2.length); - ServletOutputStream output = response.getOutputStream(); - output.write(chunk1); - output.flush(); - output.write(chunk2); + response.getHeaders().putLongField(HttpHeader.CONTENT_LENGTH, chunk1.length + chunk2.length); + response.write(false, ByteBuffer.wrap(chunk1), Callback.NOOP); + response.write(true, ByteBuffer.wrap(chunk2), callback); } }); @@ -400,8 +363,7 @@ public class HttpClientStreamTest extends AbstractTest contentLatch.countDown(); } }; - scenario.client.newRequest(scenario.newURI()) - .scheme(scenario.getScheme()) + client.newRequest(newURI(transport)) .send(listener); Response response = listener.get(5, TimeUnit.SECONDS); assertEquals(HttpStatus.OK_200, response.getStatus()); @@ -418,20 +380,17 @@ public class HttpClientStreamTest extends AbstractTest } @ParameterizedTest - @ArgumentsSource(TransportProvider.class) + @MethodSource("transports") public void testInputStreamResponseListenerFailedWhileWaiting(Transport transport) throws Exception { - init(transport); - scenario.start(new AbstractHandler() + start(transport, new Handler.Processor() { @Override - public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + public void process(Request request, org.eclipse.jetty.server.Response response, Callback callback) { - baseRequest.setHandled(true); byte[] data = new byte[1024]; - response.setContentLength(data.length); - ServletOutputStream output = response.getOutputStream(); - output.write(data); + response.getHeaders().putLongField(HttpHeader.CONTENT_LENGTH, data.length); + response.write(true, ByteBuffer.wrap(data), callback); } }); @@ -454,8 +413,7 @@ public class HttpClientStreamTest extends AbstractTest contentLatch.countDown(); } }; - scenario.client.newRequest(scenario.newURI()) - .scheme(scenario.getScheme()) + client.newRequest(newURI(transport)) .send(listener); Response response = listener.get(5, TimeUnit.SECONDS); assertEquals(HttpStatus.OK_200, response.getStatus()); @@ -471,46 +429,41 @@ public class HttpClientStreamTest extends AbstractTest } @ParameterizedTest - @ArgumentsSource(TransportProvider.class) + @MethodSource("transports") public void testInputStreamResponseListenerFailedBeforeResponse(Transport transport) throws Exception { // Failure to connect is based on TCP connection refused // (as the server is stopped), which does not work for UDP. Assumptions.assumeTrue(transport != Transport.H3); - init(transport); - scenario.start(new EmptyServerHandler()); - String uri = scenario.newURI(); - scenario.server.stop(); + start(transport, new EmptyServerHandler()); + URI uri = newURI(transport); + server.stop(); InputStreamResponseListener listener = new InputStreamResponseListener(); // Connect to the wrong port - scenario.client.newRequest(uri) - .scheme(scenario.getScheme()) + client.newRequest(uri) .send(listener); Result result = listener.await(5, TimeUnit.SECONDS); assertNotNull(result); } @ParameterizedTest - @ArgumentsSource(TransportProvider.class) + @MethodSource("transports") public void testInputStreamContentProviderThrowingWhileReading(Transport transport) throws Exception { - init(transport); - scenario.start(new AbstractHandler() + start(transport, new Handler.Processor() { @Override - public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + public void process(Request request, org.eclipse.jetty.server.Response response, Callback callback) { - baseRequest.setHandled(true); - IO.copy(request.getInputStream(), response.getOutputStream()); + Content.copy(request, response, callback); } }); byte[] data = new byte[]{0, 1, 2, 3}; ExecutionException e = assertThrows(ExecutionException.class, () -> - scenario.client.newRequest(scenario.newURI()) - .scheme(scenario.getScheme()) + client.newRequest(newURI(transport)) .body(new InputStreamRequestContent(new InputStream() { private int index = 0; @@ -528,21 +481,19 @@ public class HttpClientStreamTest extends AbstractTest } @ParameterizedTest - @ArgumentsSource(TransportProvider.class) + @MethodSource("transports") public void testDownloadWithCloseBeforeContent(Transport transport) throws Exception { - init(transport); byte[] data = new byte[128 * 1024]; byte value = 3; Arrays.fill(data, value); CountDownLatch latch = new CountDownLatch(1); - scenario.start(new AbstractHandler() + start(transport, new Handler.Processor() { @Override - public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + public void process(Request request, org.eclipse.jetty.server.Response response, Callback callback) throws Exception { - baseRequest.setHandled(true); - response.flushBuffer(); + response.write(false, BufferUtil.EMPTY_BUFFER, Callback.NOOP); try { @@ -553,13 +504,12 @@ public class HttpClientStreamTest extends AbstractTest throw new InterruptedIOException(); } - response.getOutputStream().write(data); + response.write(true, ByteBuffer.wrap(data), callback); } }); InputStreamResponseListener listener = new InputStreamResponseListener(); - scenario.client.newRequest(scenario.newURI()) - .scheme(scenario.getScheme()) + client.newRequest(newURI(transport)) .send(listener); Response response = listener.get(5, TimeUnit.SECONDS); assertNotNull(response); @@ -575,21 +525,18 @@ public class HttpClientStreamTest extends AbstractTest } @ParameterizedTest - @ArgumentsSource(TransportProvider.class) + @MethodSource("transports") public void testDownloadWithCloseMiddleOfContent(Transport transport) throws Exception { - init(transport); byte[] data1 = new byte[1024]; byte[] data2 = new byte[1024]; CountDownLatch latch = new CountDownLatch(1); - scenario.start(new AbstractHandler() + start(transport, new Handler.Processor() { @Override - public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + public void process(Request request, org.eclipse.jetty.server.Response response, Callback callback) throws Exception { - baseRequest.setHandled(true); - response.getOutputStream().write(data1); - response.flushBuffer(); + response.write(false, ByteBuffer.wrap(data1), Callback.NOOP); try { @@ -600,13 +547,12 @@ public class HttpClientStreamTest extends AbstractTest throw new InterruptedIOException(); } - response.getOutputStream().write(data2); + response.write(true, ByteBuffer.wrap(data2), callback); } }); InputStreamResponseListener listener = new InputStreamResponseListener(); - scenario.client.newRequest(scenario.newURI()) - .scheme(scenario.getScheme()) + client.newRequest(newURI(transport)) .send(listener); Response response = listener.get(5, TimeUnit.SECONDS); assertNotNull(response); @@ -628,25 +574,21 @@ public class HttpClientStreamTest extends AbstractTest } @ParameterizedTest - @ArgumentsSource(TransportProvider.class) + @MethodSource("transports") public void testDownloadWithCloseEndOfContent(Transport transport) throws Exception { - init(transport); byte[] data = new byte[1024]; - scenario.start(new AbstractHandler() + start(transport, new Handler.Processor() { @Override - public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + public void process(Request request, org.eclipse.jetty.server.Response response, Callback callback) { - baseRequest.setHandled(true); - response.getOutputStream().write(data); - response.flushBuffer(); + response.write(true, ByteBuffer.wrap(data), callback); } }); InputStreamResponseListener listener = new InputStreamResponseListener(); - scenario.client.newRequest(scenario.newURI()) - .scheme(scenario.getScheme()) + client.newRequest(newURI(transport)) .send(listener); Response response = listener.get(5, TimeUnit.SECONDS); assertNotNull(response); @@ -670,17 +612,15 @@ public class HttpClientStreamTest extends AbstractTest } @ParameterizedTest - @ArgumentsSource(TransportProvider.class) + @MethodSource("transports") public void testUploadWithDeferredContentProviderFromInputStream(Transport transport) throws Exception { - init(transport); - scenario.start(new AbstractHandler() + start(transport, new Handler.Processor() { @Override - public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + public void process(Request request, org.eclipse.jetty.server.Response response, Callback callback) { - baseRequest.setHandled(true); - IO.copy(request.getInputStream(), new ByteArrayOutputStream()); + Content.copy(request, response, callback); } }); @@ -688,8 +628,7 @@ public class HttpClientStreamTest extends AbstractTest CountDownLatch responseLatch = new CountDownLatch(1); try (AsyncRequestContent content = new AsyncRequestContent()) { - scenario.client.newRequest(scenario.newURI()) - .scheme(scenario.getScheme()) + client.newRequest(newURI(transport)) .body(content) .onRequestCommit((request) -> requestSentLatch.countDown()) .send(result -> @@ -707,7 +646,7 @@ public class HttpClientStreamTest extends AbstractTest int read; while ((read = input.read(buffer)) >= 0) { - content.offer(ByteBuffer.wrap(buffer, 0, read)); + content.write(ByteBuffer.wrap(buffer, 0, read), Callback.NOOP); } } } @@ -715,17 +654,15 @@ public class HttpClientStreamTest extends AbstractTest } @ParameterizedTest - @ArgumentsSource(TransportProvider.class) + @MethodSource("transports") public void testUploadWithDeferredContentAvailableCallbacksNotifiedOnce(Transport transport) throws Exception { - init(transport); - scenario.start(new AbstractHandler() + start(transport, new Handler.Processor() { @Override - public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + public void process(Request request, org.eclipse.jetty.server.Response response, Callback callback) { - baseRequest.setHandled(true); - IO.copy(request.getInputStream(), new ByteArrayOutputStream()); + Content.copy(request, response, callback); } }); @@ -734,7 +671,7 @@ public class HttpClientStreamTest extends AbstractTest try (AsyncRequestContent content = new AsyncRequestContent()) { // Make the content immediately available. - content.offer(ByteBuffer.allocate(1024), new Callback() + content.write(ByteBuffer.allocate(1024), new Callback() { @Override public void succeeded() @@ -743,8 +680,7 @@ public class HttpClientStreamTest extends AbstractTest } }); - scenario.client.newRequest(scenario.newURI()) - .scheme(scenario.getScheme()) + client.newRequest(newURI(transport)) .body(content) .send(result -> { @@ -757,17 +693,15 @@ public class HttpClientStreamTest extends AbstractTest } @ParameterizedTest - @ArgumentsSource(TransportProvider.class) + @MethodSource("transports") public void testUploadWithDeferredContentProviderRacingWithSend(Transport transport) throws Exception { - init(transport); - scenario.start(new AbstractHandler() + start(transport, new Handler.Processor() { @Override - public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + public void process(Request request, org.eclipse.jetty.server.Response response, Callback callback) { - baseRequest.setHandled(true); - IO.copy(request.getInputStream(), response.getOutputStream()); + Content.copy(request, response, callback); } }); @@ -776,17 +710,16 @@ public class HttpClientStreamTest extends AbstractTest AsyncRequestContent content = new AsyncRequestContent() { @Override - public void demand() + public void demand(Runnable demandCallback) { - super.demand(); + super.demand(demandCallback); // Simulate a concurrent call - offer(ByteBuffer.wrap(data)); + write(ByteBuffer.wrap(data), Callback.NOOP); close(); } }; - scenario.client.newRequest(scenario.newURI()) - .scheme(scenario.getScheme()) + client.newRequest(newURI(transport)) .body(content) .send(new BufferingResponseListener() { @@ -804,25 +737,22 @@ public class HttpClientStreamTest extends AbstractTest } @ParameterizedTest - @ArgumentsSource(TransportProvider.class) + @MethodSource("transports") public void testUploadWithOutputStream(Transport transport) throws Exception { - init(transport); - scenario.start(new AbstractHandler() + start(transport, new Handler.Processor() { @Override - public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + public void process(Request request, org.eclipse.jetty.server.Response response, Callback callback) { - baseRequest.setHandled(true); - IO.copy(request.getInputStream(), response.getOutputStream()); + Content.copy(request, response, callback); } }); byte[] data = new byte[512]; CountDownLatch latch = new CountDownLatch(1); OutputStreamRequestContent content = new OutputStreamRequestContent(); - scenario.client.newRequest(scenario.newURI()) - .scheme(scenario.getScheme()) + client.newRequest(newURI(transport)) .body(content) .send(new BufferingResponseListener() { @@ -848,17 +778,15 @@ public class HttpClientStreamTest extends AbstractTest } @ParameterizedTest - @ArgumentsSource(TransportProvider.class) + @MethodSource("transports") public void testBigUploadWithOutputStreamFromInputStream(Transport transport) throws Exception { - init(transport); - scenario.start(new AbstractHandler() + start(transport, new Handler.Processor() { @Override - public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + public void process(Request request, org.eclipse.jetty.server.Response response, Callback callback) { - baseRequest.setHandled(true); - IO.copy(request.getInputStream(), response.getOutputStream()); + Content.copy(request, response, callback); } }); @@ -866,8 +794,7 @@ public class HttpClientStreamTest extends AbstractTest new Random().nextBytes(data); CountDownLatch latch = new CountDownLatch(1); OutputStreamRequestContent content = new OutputStreamRequestContent(); - scenario.client.newRequest(scenario.newURI()) - .scheme(scenario.getScheme()) + client.newRequest(newURI(transport)) .body(content) .send(new BufferingResponseListener(data.length) { @@ -893,23 +820,21 @@ public class HttpClientStreamTest extends AbstractTest } @ParameterizedTest - @ArgumentsSource(TransportProvider.class) + @MethodSource("transports") public void testUploadWithOutputStreamFailureToConnect(Transport transport) throws Exception { - // Failure to connect is based on InetSocket address failure, which Unix-Domain does not use. + // Failure to connect is based on InetSocketAddress failure, which Unix-Domain does not use. Assumptions.assumeTrue(transport != Transport.UNIX_DOMAIN); - init(transport); - long connectTimeout = 1000; - scenario.start(new EmptyServerHandler(), httpClient -> httpClient.setConnectTimeout(connectTimeout)); + start(transport, new EmptyServerHandler()); + client.setConnectTimeout(connectTimeout); byte[] data = new byte[512]; CountDownLatch latch = new CountDownLatch(1); OutputStreamRequestContent content = new OutputStreamRequestContent(); String uri = "http://0.0.0.1"; - scenario.client.newRequest(uri) - .scheme(scenario.getScheme()) + client.newRequest(uri) .body(content) .send(result -> { @@ -929,11 +854,10 @@ public class HttpClientStreamTest extends AbstractTest } @ParameterizedTest - @ArgumentsSource(TransportProvider.class) + @MethodSource("transports") public void testUploadWithDeferredContentProviderFailsMultipleOffers(Transport transport) throws Exception { - init(transport); - scenario.start(new EmptyServerHandler()); + start(transport, new EmptyServerHandler()); CountDownLatch failLatch = new CountDownLatch(2); Callback callback = new Callback() @@ -947,13 +871,12 @@ public class HttpClientStreamTest extends AbstractTest CountDownLatch completeLatch = new CountDownLatch(1); AsyncRequestContent content = new AsyncRequestContent(); - scenario.client.newRequest(scenario.newURI()) - .scheme(scenario.getScheme()) + client.newRequest(newURI(transport)) .body(content) .onRequestBegin(request -> { - content.offer(ByteBuffer.wrap(new byte[256]), callback); - content.offer(ByteBuffer.wrap(new byte[256]), callback); + content.write(ByteBuffer.wrap(new byte[256]), callback); + content.write(ByteBuffer.wrap(new byte[256]), callback); request.abort(new Exception("explicitly_thrown_by_test")); }) .send(result -> @@ -966,7 +889,7 @@ public class HttpClientStreamTest extends AbstractTest // Make sure that adding more content results in the callback to be failed. CountDownLatch latch = new CountDownLatch(1); - content.offer(ByteBuffer.wrap(new byte[128]), new Callback() + content.write(ByteBuffer.wrap(new byte[128]), new Callback() { @Override public void failed(Throwable x) @@ -978,16 +901,15 @@ public class HttpClientStreamTest extends AbstractTest } @ParameterizedTest - @ArgumentsSource(TransportProvider.class) + @MethodSource("transports") public void testUploadWithConnectFailureClosesStream(Transport transport) throws Exception { // Failure to connect is based on InetSocket address failure, which Unix-Domain does not use. Assumptions.assumeTrue(transport != Transport.UNIX_DOMAIN); - init(transport); - long connectTimeout = 1000; - scenario.start(new EmptyServerHandler(), httpClient -> httpClient.setConnectTimeout(connectTimeout)); + start(transport, new EmptyServerHandler()); + client.setConnectTimeout(connectTimeout); CountDownLatch closeLatch = new CountDownLatch(1); InputStream stream = new ByteArrayInputStream("test".getBytes(StandardCharsets.UTF_8)) @@ -1002,13 +924,13 @@ public class HttpClientStreamTest extends AbstractTest InputStreamRequestContent content = new InputStreamRequestContent(stream); CountDownLatch completeLatch = new CountDownLatch(1); + // TODO: fix scheme String uri = "http://0.0.0.1"; - scenario.client.newRequest(uri) - .scheme(scenario.getScheme()) + client.newRequest(uri) .body(content) .send(result -> { - assertTrue(result.isFailed()); + Assertions.assertTrue(result.isFailed()); completeLatch.countDown(); }); @@ -1017,19 +939,15 @@ public class HttpClientStreamTest extends AbstractTest } @ParameterizedTest - @ArgumentsSource(TransportProvider.class) + @MethodSource("transports") public void testUploadWithConcurrentServerCloseClosesStream(Transport transport) throws Exception { - init(transport); CountDownLatch serverLatch = new CountDownLatch(1); - scenario.start(new AbstractHandler() + start(transport, new Handler.Processor() { @Override - public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) + public void process(Request request, org.eclipse.jetty.server.Response response, Callback callback) { - baseRequest.setHandled(true); - AsyncContext asyncContext = request.startAsync(); - asyncContext.setTimeout(0); serverLatch.countDown(); } }); @@ -1053,7 +971,7 @@ public class HttpClientStreamTest extends AbstractTest try { assertTrue(serverLatch.await(5, TimeUnit.SECONDS)); - scenario.connector.stop(); + connector.stop(); return 0; } catch (Throwable x) @@ -1063,7 +981,7 @@ public class HttpClientStreamTest extends AbstractTest } else { - return scenario.connector.isStopped() ? -1 : 0; + return connector.isStopped() ? -1 : 0; } } @@ -1077,13 +995,12 @@ public class HttpClientStreamTest extends AbstractTest InputStreamRequestContent content = new InputStreamRequestContent(stream, 1); CountDownLatch completeLatch = new CountDownLatch(1); - scenario.client.newRequest(scenario.newURI()) - .scheme(scenario.getScheme()) + client.newRequest(newURI(transport)) .body(content) .onRequestCommit(request -> commit.set(true)) .send(result -> { - assertTrue(result.isFailed()); + Assertions.assertTrue(result.isFailed()); completeLatch.countDown(); }); @@ -1092,41 +1009,36 @@ public class HttpClientStreamTest extends AbstractTest } @ParameterizedTest - @ArgumentsSource(TransportProvider.class) + @MethodSource("transports") public void testInputStreamResponseListenerBufferedRead(Transport transport) throws Exception { - init(transport); - AtomicReference asyncContextRef = new AtomicReference<>(); + AtomicReference contextRef = new AtomicReference<>(); CountDownLatch latch = new CountDownLatch(1); - scenario.start(new AbstractHandler() + start(transport, new Handler.Processor() { @Override - public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) + public void process(Request request, org.eclipse.jetty.server.Response response, Callback callback) { - baseRequest.setHandled(true); - asyncContextRef.set(request.startAsync()); + contextRef.set(new HandlerContext(request, response, callback)); latch.countDown(); } }); InputStreamResponseListener listener = new InputStreamResponseListener(); - scenario.client.newRequest(scenario.newURI()) - .scheme(scenario.getScheme()) + client.newRequest(newURI(transport)) .timeout(5, TimeUnit.SECONDS) .send(listener); assertTrue(latch.await(5, TimeUnit.SECONDS)); - AsyncContext asyncContext = asyncContextRef.get(); - assertNotNull(asyncContext); + HandlerContext context = contextRef.get(); + assertNotNull(context); Random random = new Random(); byte[] chunk = new byte[64]; random.nextBytes(chunk); - ServletOutputStream output = asyncContext.getResponse().getOutputStream(); - output.write(chunk); - output.flush(); + context.response().write(false, ByteBuffer.wrap(chunk), Callback.NOOP); // Use a buffer larger than the data // written to test that the read returns. @@ -1140,31 +1052,28 @@ public class HttpClientStreamTest extends AbstractTest totalRead += read; } - asyncContext.complete(); + context.response().write(true, BufferUtil.EMPTY_BUFFER, context.callback()); Response response = listener.get(5, TimeUnit.SECONDS); assertEquals(200, response.getStatus()); } @ParameterizedTest - @ArgumentsSource(TransportProvider.class) + @MethodSource("transports") public void testInputStreamResponseListenerWithRedirect(Transport transport) throws Exception { - init(transport); - scenario.start(new AbstractHandler() + start(transport, new Handler.Processor() { @Override - public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + public void process(Request request, org.eclipse.jetty.server.Response response, Callback callback) { - baseRequest.setHandled(true); - if (target.startsWith("/303")) - response.sendRedirect("/200"); + if (request.getPathInContext().startsWith("/303")) + org.eclipse.jetty.server.Response.sendRedirect(request, response, callback, "/200"); } }); InputStreamResponseListener listener = new InputStreamResponseListener(); - scenario.client.newRequest(scenario.newURI()) - .scheme(scenario.getScheme()) + client.newRequest(newURI(transport)) .path("/303") .followRedirects(true) .send(listener); @@ -1176,76 +1085,7 @@ public class HttpClientStreamTest extends AbstractTest assertTrue(result.isSucceeded()); } - @ParameterizedTest - @ArgumentsSource(TransportProvider.class) - public void testClientDefersContentServerIdleTimeout(Transport transport) throws Exception + private record HandlerContext(Request request, org.eclipse.jetty.server.Response response, Callback callback) { - init(transport); - CountDownLatch dataLatch = new CountDownLatch(1); - CountDownLatch errorLatch = new CountDownLatch(1); - scenario.start(new HttpServlet() - { - @Override - protected void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException - { - AsyncContext asyncContext = request.startAsync(); - asyncContext.setTimeout(0); - request.getInputStream().setReadListener(new ReadListener() - { - @Override - public void onDataAvailable() - { - dataLatch.countDown(); - } - - @Override - public void onAllDataRead() - { - dataLatch.countDown(); - } - - @Override - public void onError(Throwable t) - { - errorLatch.countDown(); - response.setStatus(HttpStatus.REQUEST_TIMEOUT_408); - asyncContext.complete(); - } - }); - } - }); - long idleTimeout = 1000; - scenario.setRequestIdleTimeout(idleTimeout); - - CountDownLatch latch = new CountDownLatch(1); - byte[] bytes = "[{\"key\":\"value\"}]".getBytes(StandardCharsets.UTF_8); - OutputStreamRequestContent content = new OutputStreamRequestContent("application/json;charset=UTF-8") - { - @Override - public long getLength() - { - return bytes.length; - } - }; - scenario.client.newRequest(scenario.newURI()) - .method(HttpMethod.POST) - .path(scenario.servletPath) - .body(content) - .onResponseSuccess(response -> - { - assertEquals(HttpStatus.REQUEST_TIMEOUT_408, response.getStatus()); - latch.countDown(); - }) - .send(null); - - // Wait for the server to idle timeout. - Thread.sleep(2 * idleTimeout); - - assertTrue(errorLatch.await(5, TimeUnit.SECONDS)); - - // Do not send the content to the server. - - assertFalse(dataLatch.await(1, TimeUnit.SECONDS)); - assertTrue(latch.await(5, TimeUnit.SECONDS)); } } diff --git a/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-http-client-transport/src/test/java/org/eclipse/jetty/ee9/http/client/HttpClientTest.java b/jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/HttpClientTest.java similarity index 57% rename from jetty-ee9/jetty-ee9-tests/jetty-ee9-test-http-client-transport/src/test/java/org/eclipse/jetty/ee9/http/client/HttpClientTest.java rename to jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/HttpClientTest.java index 37ebf8e742a..54ade75fe51 100644 --- a/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-http-client-transport/src/test/java/org/eclipse/jetty/ee9/http/client/HttpClientTest.java +++ b/jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/HttpClientTest.java @@ -11,11 +11,13 @@ // ======================================================================== // -package org.eclipse.jetty.ee9.http.client; +package org.eclipse.jetty.test.client.transport; import java.io.IOException; import java.io.InputStream; import java.io.InterruptedIOException; +import java.net.URI; +import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.util.List; import java.util.Random; @@ -27,12 +29,6 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import java.util.stream.IntStream; -import jakarta.servlet.ServletException; -import jakarta.servlet.ServletInputStream; -import jakarta.servlet.ServletOutputStream; -import jakarta.servlet.http.HttpServlet; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; import org.eclipse.jetty.client.HttpDestination; import org.eclipse.jetty.client.Origin; import org.eclipse.jetty.client.api.ContentResponse; @@ -43,23 +39,23 @@ import org.eclipse.jetty.client.util.FutureResponseListener; import org.eclipse.jetty.client.util.InputStreamResponseListener; import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpMethod; -import org.eclipse.jetty.http.HttpScheme; import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.http.HttpURI; import org.eclipse.jetty.http2.FlowControlStrategy; import org.eclipse.jetty.http3.client.transport.HttpClientTransportOverHTTP3; +import org.eclipse.jetty.io.Content; +import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.SecureRequestCustomizer; -import org.eclipse.jetty.server.handler.AbstractHandler; import org.eclipse.jetty.toolchain.test.Net; +import org.eclipse.jetty.util.Blocker; import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.IO; import org.eclipse.jetty.util.ssl.SslContextFactory; -import org.eclipse.jetty.util.thread.QueuedThreadPool; import org.hamcrest.Matchers; -import org.junit.jupiter.api.Assumptions; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.ArgumentsSource; +import org.junit.jupiter.params.provider.MethodSource; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.containsString; @@ -70,32 +66,26 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.jupiter.api.Assumptions.assumeTrue; -public class HttpClientTest extends AbstractTest +public class HttpClientTest extends AbstractTest { - @Override - public void init(Transport transport) throws IOException - { - setScenario(new TransportScenario(transport)); - } - @ParameterizedTest - @ArgumentsSource(TransportProvider.class) + @MethodSource("transports") public void testRequestWithoutResponseContent(Transport transport) throws Exception { - init(transport); final int status = HttpStatus.NO_CONTENT_204; - scenario.start(new AbstractHandler() + start(transport, new Handler.Processor() { @Override - public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) + public void process(Request request, org.eclipse.jetty.server.Response response, Callback callback) { - baseRequest.setHandled(true); response.setStatus(status); + callback.succeeded(); } }); - ContentResponse response = scenario.client.newRequest(scenario.newURI()) + ContentResponse response = client.newRequest(newURI(transport)) .timeout(5, TimeUnit.SECONDS) .send(); @@ -104,37 +94,34 @@ public class HttpClientTest extends AbstractTest } @ParameterizedTest - @ArgumentsSource(TransportProvider.class) + @MethodSource("transports") public void testRequestWithSmallResponseContent(Transport transport) throws Exception { - init(transport); - testRequestWithResponseContent(1024); + testRequestWithResponseContent(transport, 1024); } @ParameterizedTest - @ArgumentsSource(TransportProvider.class) + @MethodSource("transports") public void testRequestWithLargeResponseContent(Transport transport) throws Exception { - init(transport); - testRequestWithResponseContent(1024 * 1024); + testRequestWithResponseContent(transport, 1024 * 1024); } - private void testRequestWithResponseContent(int length) throws Exception + private void testRequestWithResponseContent(Transport transport, int length) throws Exception { final byte[] bytes = new byte[length]; new Random().nextBytes(bytes); - scenario.start(new AbstractHandler() + start(transport, new Handler.Processor() { @Override - public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + public void process(Request request, org.eclipse.jetty.server.Response response, Callback callback) { - baseRequest.setHandled(true); - response.setContentLength(length); - response.getOutputStream().write(bytes); + response.getHeaders().putLongField(HttpHeader.CONTENT_LENGTH, length); + response.write(true, ByteBuffer.wrap(bytes), callback); } }); - org.eclipse.jetty.client.api.Request request = scenario.client.newRequest(scenario.newURI()); + org.eclipse.jetty.client.api.Request request = client.newRequest(newURI(transport)); FutureResponseListener listener = new FutureResponseListener(request, length); request.timeout(10, TimeUnit.SECONDS).send(listener); ContentResponse response = listener.get(); @@ -144,22 +131,20 @@ public class HttpClientTest extends AbstractTest } @ParameterizedTest - @ArgumentsSource(TransportProvider.class) + @MethodSource("transports") public void testRequestWithSmallResponseContentChunked(Transport transport) throws Exception { - init(transport); - testRequestWithResponseContentChunked(512); + testRequestWithResponseContentChunked(transport, 512); } @ParameterizedTest - @ArgumentsSource(TransportProvider.class) + @MethodSource("transports") public void testRequestWithLargeResponseContentChunked(Transport transport) throws Exception { - init(transport); - testRequestWithResponseContentChunked(512 * 512); + testRequestWithResponseContentChunked(transport, 512 * 512); } - private void testRequestWithResponseContentChunked(int length) throws Exception + private void testRequestWithResponseContentChunked(Transport transport, int length) throws Exception { final byte[] chunk1 = new byte[length]; final byte[] chunk2 = new byte[length]; @@ -169,20 +154,17 @@ public class HttpClientTest extends AbstractTest byte[] bytes = new byte[chunk1.length + chunk2.length]; System.arraycopy(chunk1, 0, bytes, 0, chunk1.length); System.arraycopy(chunk2, 0, bytes, chunk1.length, chunk2.length); - scenario.start(new AbstractHandler() + start(transport, new Handler.Processor() { @Override - public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + public void process(Request request, org.eclipse.jetty.server.Response response, Callback callback) { - baseRequest.setHandled(true); - ServletOutputStream output = response.getOutputStream(); - output.write(chunk1); - output.flush(); - output.write(chunk2); + response.write(false, ByteBuffer.wrap(chunk1), Callback.NOOP); + response.write(true, ByteBuffer.wrap(chunk2), callback); } }); - org.eclipse.jetty.client.api.Request request = scenario.client.newRequest(scenario.newURI()); + org.eclipse.jetty.client.api.Request request = client.newRequest(newURI(transport)); FutureResponseListener listener = new FutureResponseListener(request, 2 * length); request.timeout(10, TimeUnit.SECONDS).send(listener); ContentResponse response = listener.get(); @@ -192,49 +174,45 @@ public class HttpClientTest extends AbstractTest } @ParameterizedTest - @ArgumentsSource(TransportProvider.class) + @MethodSource("transports") public void testUploadZeroLengthWithoutResponseContent(Transport transport) throws Exception { - init(transport); - testUploadWithoutResponseContent(0); + testUploadWithoutResponseContent(transport, 0); } @ParameterizedTest - @ArgumentsSource(TransportProvider.class) + @MethodSource("transports") public void testUploadSmallWithoutResponseContent(Transport transport) throws Exception { - init(transport); - testUploadWithoutResponseContent(1024); + testUploadWithoutResponseContent(transport, 1024); } @ParameterizedTest - @ArgumentsSource(TransportProvider.class) + @MethodSource("transports") public void testUploadLargeWithoutResponseContent(Transport transport) throws Exception { - init(transport); - testUploadWithoutResponseContent(1024 * 1024); + testUploadWithoutResponseContent(transport, 1024 * 1024); } - private void testUploadWithoutResponseContent(int length) throws Exception + private void testUploadWithoutResponseContent(Transport transport, int length) throws Exception { final byte[] bytes = new byte[length]; new Random().nextBytes(bytes); - scenario.start(new AbstractHandler() + start(transport, new Handler.Processor() { @Override - public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + public void process(Request request, org.eclipse.jetty.server.Response response, Callback callback) throws Exception { - baseRequest.setHandled(true); - ServletInputStream input = request.getInputStream(); + InputStream input = Request.asInputStream(request); for (byte b : bytes) { - assertEquals(b & 0xFF, input.read()); + Assertions.assertEquals(b & 0xFF, input.read()); } - assertEquals(-1, input.read()); + Assertions.assertEquals(-1, input.read()); } }); - ContentResponse response = scenario.client.newRequest(scenario.newURI()) + ContentResponse response = client.newRequest(newURI(transport)) .method(HttpMethod.POST) .body(new BytesRequestContent(bytes)) .timeout(15, TimeUnit.SECONDS) @@ -245,35 +223,43 @@ public class HttpClientTest extends AbstractTest } @ParameterizedTest - @ArgumentsSource(TransportProvider.class) + @MethodSource("transports") public void testClientManyWritesSlowServer(Transport transport) throws Exception { - init(transport); - scenario.start(new AbstractHandler() + start(transport, new Handler.Processor() { @Override - public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + public void process(Request request, org.eclipse.jetty.server.Response response, Callback callback) throws Exception { - baseRequest.setHandled(true); - long sleep = 1024; long total = 0; - ServletInputStream input = request.getInputStream(); - byte[] buffer = new byte[1024]; while (true) { - int read = input.read(buffer); - if (read < 0) - break; - total += read; + Content.Chunk chunk = request.read(); + if (chunk == null) + { + try (Blocker.Runnable blocker = Blocker.runnable()) + { + request.demand(blocker); + blocker.block(); + continue; + } + } + if (chunk instanceof Content.Chunk.Error error) + throw IO.rethrow(error.getCause()); + + total += chunk.remaining(); if (total >= sleep) { sleep(250); sleep += 256; } + chunk.release(); + if (chunk.isLast()) + break; } - response.getOutputStream().print(total); + Content.Sink.write(response, true, String.valueOf(total), callback); } }); @@ -281,7 +267,7 @@ public class HttpClientTest extends AbstractTest int chunkSize = 16; byte[][] bytes = IntStream.range(0, chunks).mapToObj(x -> new byte[chunkSize]).toArray(byte[][]::new); BytesRequestContent content = new BytesRequestContent("application/octet-stream", bytes); - ContentResponse response = scenario.client.newRequest(scenario.newURI()) + ContentResponse response = client.newRequest(newURI(transport)) .method(HttpMethod.POST) .body(content) .timeout(15, TimeUnit.SECONDS) @@ -292,29 +278,22 @@ public class HttpClientTest extends AbstractTest } @ParameterizedTest - @ArgumentsSource(TransportProvider.class) + @MethodSource("transports") public void testRequestAfterFailedRequest(Transport transport) throws Exception { - init(transport); int length = FlowControlStrategy.DEFAULT_WINDOW_SIZE; - scenario.start(new AbstractHandler() + start(transport, new Handler.Processor() { @Override - public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) + public void process(Request request, org.eclipse.jetty.server.Response response, Callback callback) { - try - { - baseRequest.setHandled(true); - response.getOutputStream().write(new byte[length]); - } - catch (IOException ignored) - { - } + response.write(true, ByteBuffer.allocate(length), Callback.NOOP); + callback.succeeded(); } }); // Make a request with a large enough response buffer. - org.eclipse.jetty.client.api.Request request = scenario.client.newRequest(scenario.newURI()); + org.eclipse.jetty.client.api.Request request = client.newRequest(newURI(transport)); FutureResponseListener listener = new FutureResponseListener(request, length); request.send(listener); ContentResponse response = listener.get(15, TimeUnit.SECONDS); @@ -323,7 +302,7 @@ public class HttpClientTest extends AbstractTest // Make a request with a small response buffer, should fail. try { - request = scenario.client.newRequest(scenario.newURI()); + request = client.newRequest(newURI(transport)); listener = new FutureResponseListener(request, length / 10); request.send(listener); listener.get(15, TimeUnit.SECONDS); @@ -335,7 +314,7 @@ public class HttpClientTest extends AbstractTest } // Verify that we can make another request. - request = scenario.client.newRequest(scenario.newURI()); + request = client.newRequest(newURI(transport)); listener = new FutureResponseListener(request, length); request.send(listener); response = listener.get(15, TimeUnit.SECONDS); @@ -343,64 +322,58 @@ public class HttpClientTest extends AbstractTest } @ParameterizedTest - @ArgumentsSource(TransportProvider.class) + @MethodSource("transports") public void testClientCannotValidateServerCertificate(Transport transport) throws Exception { - init(transport); // Only run this test for transports over TLS. - Assumptions.assumeTrue(scenario.transport.isTlsBased()); + assumeTrue(transport.isSecure()); - scenario.startServer(new EmptyServerHandler()); + start(transport, new EmptyServerHandler()); // Disable validations on the server to be sure // that the test failure happens during the // validation of the certificate on the client. - scenario.httpConfig.getCustomizer(SecureRequestCustomizer.class).setSniHostCheck(false); + httpConfig.getCustomizer(SecureRequestCustomizer.class).setSniHostCheck(false); - // Use a default SslContextFactory, requests should fail because the server certificate is unknown. - SslContextFactory.Client clientTLS = scenario.newClientSslContextFactory(); + // Use a SslContextFactory.Client that verifies server certificates, + // requests should fail because the server certificate is unknown. + SslContextFactory.Client clientTLS = newSslContextFactoryClient(); clientTLS.setEndpointIdentificationAlgorithm("HTTPS"); - scenario.client = scenario.newHttpClient(scenario.provideClientTransport(transport, clientTLS)); - QueuedThreadPool clientThreads = new QueuedThreadPool(); - clientThreads.setName("client"); - scenario.client.setExecutor(clientThreads); - scenario.client.start(); + client.stop(); + client.setSslContextFactory(clientTLS); + client.start(); if (transport == Transport.H3) { - Assumptions.assumeTrue(false, "certificate verification not yet supported in quic"); + assumeTrue(false, "certificate verification not yet supported in quic"); // TODO: the lines below should be enough, but they don't work. To be investigated. - HttpClientTransportOverHTTP3 http3Transport = (HttpClientTransportOverHTTP3)scenario.client.getTransport(); + HttpClientTransportOverHTTP3 http3Transport = (HttpClientTransportOverHTTP3)client.getTransport(); http3Transport.getHTTP3Client().getQuicConfiguration().setVerifyPeerCertificates(true); } assertThrows(ExecutionException.class, () -> { // Use an IP address not present in the certificate. - int serverPort = scenario.getServerPort().orElse(0); - scenario.client.newRequest("https://127.0.0.2:" + serverPort) + int serverPort = newURI(transport).getPort(); + client.newRequest("https://127.0.0.2:" + serverPort) .timeout(5, TimeUnit.SECONDS) .send(); }); } @ParameterizedTest - @ArgumentsSource(TransportProvider.class) + @MethodSource("transports") public void testOPTIONS(Transport transport) throws Exception { - init(transport); - scenario.start(new AbstractHandler() + start(transport, new Handler.Processor() { @Override - public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) + public void process(Request request, org.eclipse.jetty.server.Response response, Callback callback) { - baseRequest.setHandled(true); assertTrue(HttpMethod.OPTIONS.is(request.getMethod())); - assertEquals("*", target); - assertEquals("*", request.getPathInfo()); + assertEquals("*", request.getPathInContext()); } }); - ContentResponse response = scenario.client.newRequest(scenario.newURI()) - .scheme(scenario.getScheme()) + ContentResponse response = client.newRequest(newURI(transport)) .method(HttpMethod.OPTIONS) .path("*") .timeout(5, TimeUnit.SECONDS) @@ -410,28 +383,25 @@ public class HttpClientTest extends AbstractTest } @ParameterizedTest - @ArgumentsSource(TransportProvider.class) + @MethodSource("transports") public void testOPTIONSWithRelativeRedirect(Transport transport) throws Exception { - init(transport); - scenario.start(new AbstractHandler() + start(transport, new Handler.Processor() { @Override - public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) + public void process(Request request, org.eclipse.jetty.server.Response response, Callback callback) { - baseRequest.setHandled(true); - if ("*".equals(target)) + if ("*".equals(request.getPathInContext())) { // Be nasty and send a relative redirect. // Code 303 will change the method to GET. response.setStatus(HttpStatus.SEE_OTHER_303); - response.setHeader("Location", "/"); + response.getHeaders().put(HttpHeader.LOCATION, "/"); } } }); - ContentResponse response = scenario.client.newRequest(scenario.newURI()) - .scheme(scenario.getScheme()) + ContentResponse response = client.newRequest(newURI(transport)) .method(HttpMethod.OPTIONS) .path("*") .timeout(5, TimeUnit.SECONDS) @@ -441,25 +411,22 @@ public class HttpClientTest extends AbstractTest } @ParameterizedTest - @ArgumentsSource(TransportProvider.class) + @MethodSource("transports") public void testDownloadWithInputStreamResponseListener(Transport transport) throws Exception { - init(transport); String content = "hello world"; - scenario.start(new AbstractHandler() + start(transport, new Handler.Processor() { @Override - public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + public void process(Request request, org.eclipse.jetty.server.Response response, Callback callback) { - baseRequest.setHandled(true); - response.getOutputStream().print(content); + Content.Sink.write(response, true, content, callback); } }); CountDownLatch latch = new CountDownLatch(1); InputStreamResponseListener listener = new InputStreamResponseListener(); - scenario.client.newRequest(scenario.newURI()) - .scheme(scenario.getScheme()) + client.newRequest(newURI(transport)) .onResponseSuccess(response -> latch.countDown()) .send(listener); Response response = listener.get(5, TimeUnit.SECONDS); @@ -475,17 +442,16 @@ public class HttpClientTest extends AbstractTest } @ParameterizedTest - @ArgumentsSource(TransportProvider.class) + @MethodSource("transports") public void testConnectionListener(Transport transport) throws Exception { - init(transport); - scenario.startServer(new EmptyServerHandler()); + start(transport, new EmptyServerHandler()); long idleTimeout = 1000; - scenario.startClient(httpClient -> httpClient.setIdleTimeout(idleTimeout)); + client.setIdleTimeout(idleTimeout); CountDownLatch openLatch = new CountDownLatch(1); CountDownLatch closeLatch = new CountDownLatch(1); - scenario.client.addBean(new org.eclipse.jetty.io.Connection.Listener() + client.addBean(new org.eclipse.jetty.io.Connection.Listener() { @Override public void onOpened(org.eclipse.jetty.io.Connection connection) @@ -500,8 +466,7 @@ public class HttpClientTest extends AbstractTest } }); - ContentResponse response = scenario.client.newRequest(scenario.newURI()) - .scheme(scenario.getScheme()) + ContentResponse response = client.newRequest(newURI(transport)) .timeout(5, TimeUnit.SECONDS) .send(); @@ -513,18 +478,16 @@ public class HttpClientTest extends AbstractTest } @ParameterizedTest - @ArgumentsSource(TransportProvider.class) + @MethodSource("transports") public void testAsyncResponseContentBackPressure(Transport transport) throws Exception { - init(transport); - scenario.start(new AbstractHandler() + start(transport, new Handler.Processor() { @Override - public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + public void process(Request request, org.eclipse.jetty.server.Response response, Callback callback) { - baseRequest.setHandled(true); // Large write to generate multiple DATA frames. - response.getOutputStream().write(new byte[256 * 1024]); + response.write(true, ByteBuffer.allocate(256 * 1024), callback); } }); @@ -532,8 +495,7 @@ public class HttpClientTest extends AbstractTest AtomicInteger counter = new AtomicInteger(); AtomicReference callbackRef = new AtomicReference<>(); AtomicReference latchRef = new AtomicReference<>(new CountDownLatch(1)); - scenario.client.newRequest(scenario.newURI()) - .scheme(scenario.getScheme()) + client.newRequest(newURI(transport)) .onResponseContentAsync((response, content, callback) -> { if (counter.incrementAndGet() == 1) @@ -558,21 +520,20 @@ public class HttpClientTest extends AbstractTest } @ParameterizedTest - @ArgumentsSource(TransportProvider.class) + @MethodSource("transports") public void testResponseWithContentCompleteListenerInvokedOnce(Transport transport) throws Exception { - init(transport); - scenario.start(new EmptyServerHandler() + start(transport, new Handler.Processor() { @Override - protected void service(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + public void process(Request request, org.eclipse.jetty.server.Response response, Callback callback) { - response.getWriter().write("Jetty"); + Content.Sink.write(response, true, "Jetty", callback); } }); AtomicInteger completes = new AtomicInteger(); - scenario.client.newRequest(scenario.newURI()) + client.newRequest(newURI(transport)) .send(result -> completes.incrementAndGet()); sleep(1000); @@ -581,35 +542,37 @@ public class HttpClientTest extends AbstractTest } @ParameterizedTest - @ArgumentsSource(TransportProvider.class) + @MethodSource("transports") public void testHEADResponds200(Transport transport) throws Exception { - init(transport); - testHEAD(scenario.servletPath, HttpStatus.OK_200); + testHEAD(transport, "/", HttpStatus.OK_200); } @ParameterizedTest - @ArgumentsSource(TransportProvider.class) + @MethodSource("transports") public void testHEADResponds404(Transport transport) throws Exception { - init(transport); - testHEAD("/notMapped", HttpStatus.NOT_FOUND_404); + testHEAD(transport, "/notMapped", HttpStatus.NOT_FOUND_404); } - private void testHEAD(String path, int status) throws Exception + private void testHEAD(Transport transport, String path, int status) throws Exception { byte[] data = new byte[1024]; new Random().nextBytes(data); - scenario.start(new HttpServlet() + start(transport, new Handler.Processor() { @Override - protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException + public void process(Request request, org.eclipse.jetty.server.Response response, Callback callback) { - response.getOutputStream().write(data); + String target = request.getPathInContext(); + if ("/notMapped".equals(target)) + org.eclipse.jetty.server.Response.writeError(request, response, callback, HttpStatus.NOT_FOUND_404); + else + response.write(true, ByteBuffer.wrap(data), callback); } }); - ContentResponse response = scenario.client.newRequest(scenario.newURI()) + ContentResponse response = client.newRequest(newURI(transport)) .method(HttpMethod.HEAD) .path(path) .send(); @@ -619,23 +582,21 @@ public class HttpClientTest extends AbstractTest } @ParameterizedTest - @ArgumentsSource(TransportProvider.class) + @MethodSource("transports") public void testHEADWithAcceptHeaderAndSendError(Transport transport) throws Exception { - init(transport); int status = HttpStatus.BAD_REQUEST_400; - scenario.start(new HttpServlet() + start(transport, new Handler.Processor() { @Override - protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException + public void process(Request request, org.eclipse.jetty.server.Response response, Callback callback) { - resp.sendError(status); + org.eclipse.jetty.server.Response.writeError(request, response, callback, status); } }); - ContentResponse response = scenario.client.newRequest(scenario.newURI()) + ContentResponse response = client.newRequest(newURI(transport)) .method(HttpMethod.HEAD) - .path(scenario.servletPath) .headers(headers -> headers.put(HttpHeader.ACCEPT, "*/*")) .send(); @@ -644,25 +605,22 @@ public class HttpClientTest extends AbstractTest } @ParameterizedTest - @ArgumentsSource(TransportProvider.class) + @MethodSource("transports") public void testHEADWithContentLengthGreaterThanMaxBufferingCapacity(Transport transport) throws Exception { int length = 1024; - init(transport); - scenario.start(new HttpServlet() + start(transport, new Handler.Processor() { @Override - protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException + public void process(Request request, org.eclipse.jetty.server.Response response, Callback callback) { - response.setContentLength(length); - response.getOutputStream().write(new byte[length]); + response.getHeaders().putLongField(HttpHeader.CONTENT_LENGTH, length); + response.write(true, ByteBuffer.allocate(length), callback); } }); - org.eclipse.jetty.client.api.Request request = scenario.client - .newRequest(scenario.newURI()) - .method(HttpMethod.HEAD) - .path(scenario.servletPath); + org.eclipse.jetty.client.api.Request request = client.newRequest(newURI(transport)) + .method(HttpMethod.HEAD); FutureResponseListener listener = new FutureResponseListener(request, length / 2); request.send(listener); ContentResponse response = listener.get(5, TimeUnit.SECONDS); @@ -672,52 +630,50 @@ public class HttpClientTest extends AbstractTest } @ParameterizedTest - @ArgumentsSource(TransportProvider.class) + @MethodSource("transports") public void testOneDestinationPerUser(Transport transport) throws Exception { - init(transport); - scenario.start(new EmptyServerHandler()); - + start(transport, new EmptyServerHandler()); int runs = 4; int users = 16; for (int i = 0; i < runs; ++i) { for (int j = 0; j < users; ++j) { - ContentResponse response = scenario.client.newRequest(scenario.newURI()) + ContentResponse response = client.newRequest(newURI(transport)) .tag(j) .send(); assertEquals(HttpStatus.OK_200, response.getStatus()); } } - List destinations = scenario.client.getDestinations(); + List destinations = client.getDestinations(); assertEquals(users, destinations.size()); } @ParameterizedTest - @ArgumentsSource(TransportProvider.class) + @MethodSource("transports") public void testIPv6Host(Transport transport) throws Exception { - Assumptions.assumeTrue(Net.isIpv6InterfaceAvailable()); - Assumptions.assumeTrue(transport != Transport.UNIX_DOMAIN); + assumeTrue(Net.isIpv6InterfaceAvailable()); + assumeTrue(transport != Transport.UNIX_DOMAIN); - init(transport); - scenario.start(new EmptyServerHandler() + start(transport, new Handler.Processor() { @Override - protected void service(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + public void process(Request request, org.eclipse.jetty.server.Response response, Callback callback) { - response.setContentType("text/plain"); - response.getOutputStream().print(request.getHeader("Host")); + response.getHeaders().put(HttpHeader.CONTENT_TYPE, "text/plain"); + Content.Sink.write(response, true, request.getHeaders().get("Host"), callback); } }); // Test with a full URI. String hostAddress = "::1"; String host = "[" + hostAddress + "]"; - String uri = scenario.newURI().replace("localhost", host) + "/path"; - ContentResponse response = scenario.client.newRequest(uri) + URI serverURI = newURI(transport); + String uri = serverURI.toString().replace("localhost", host) + "/path"; + ContentResponse response = client.newRequest(uri) .method(HttpMethod.PUT) .timeout(5, TimeUnit.SECONDS) .send(); @@ -726,9 +682,9 @@ public class HttpClientTest extends AbstractTest assertThat(new String(response.getContent(), StandardCharsets.ISO_8859_1), Matchers.startsWith("[::1]:")); // Test with host address. - int port = scenario.getServerPort().orElse(0); - response = scenario.client.newRequest(hostAddress, port) - .scheme(scenario.getScheme()) + int port = serverURI.getPort(); + response = client.newRequest(hostAddress, port) + .scheme(serverURI.getScheme()) .method(HttpMethod.PUT) .timeout(5, TimeUnit.SECONDS) .send(); @@ -736,9 +692,9 @@ public class HttpClientTest extends AbstractTest assertEquals(200, response.getStatus()); assertThat(new String(response.getContent(), StandardCharsets.ISO_8859_1), Matchers.startsWith("[::1]:")); - // Test with host. - response = scenario.client.newRequest(host, port) - .scheme(scenario.getScheme()) + // Test with host name. + response = client.newRequest(host, port) + .scheme(serverURI.getScheme()) .method(HttpMethod.PUT) .timeout(5, TimeUnit.SECONDS) .send(); @@ -746,37 +702,35 @@ public class HttpClientTest extends AbstractTest assertEquals(200, response.getStatus()); assertThat(new String(response.getContent(), StandardCharsets.ISO_8859_1), Matchers.startsWith("[::1]:")); - assertEquals(1, scenario.client.getDestinations().size()); + Assertions.assertEquals(1, client.getDestinations().size()); } @ParameterizedTest - @ArgumentsSource(TransportProvider.class) + @MethodSource("transports") public void testRequestWithDifferentDestination(Transport transport) throws Exception { - init(transport); - - String requestScheme = HttpScheme.HTTPS.is(scenario.getScheme()) ? "http" : "https"; + String requestScheme = newURI(transport).getScheme(); String requestHost = "otherHost.com"; int requestPort = 8888; - scenario.start(new EmptyServerHandler() + start(transport, new Handler.Processor() { @Override - protected void service(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) + public void process(Request request, org.eclipse.jetty.server.Response response, Callback callback) { - HttpURI uri = jettyRequest.getHttpURI(); + HttpURI uri = request.getHttpURI(); assertEquals(requestHost, uri.getHost()); assertEquals(requestPort, uri.getPort()); - if (scenario.transport == Transport.H2C || scenario.transport == Transport.H2) - assertEquals(requestScheme, jettyRequest.getMetaData().getURI().getScheme()); + if (transport == Transport.H2C || transport == Transport.H2) + assertEquals(requestScheme, uri.getScheme()); } }); - if (transport.isTlsBased()) - scenario.httpConfig.getCustomizer(SecureRequestCustomizer.class).setSniHostCheck(false); + if (transport.isSecure()) + httpConfig.getCustomizer(SecureRequestCustomizer.class).setSniHostCheck(false); - Origin origin = new Origin(scenario.getScheme(), "localhost", scenario.getServerPort().orElse(0)); - HttpDestination destination = scenario.client.resolveDestination(origin); + Origin origin = new Origin(requestScheme, "localhost", requestPort); + HttpDestination destination = client.resolveDestination(origin); - org.eclipse.jetty.client.api.Request request = scenario.client.newRequest(requestHost, requestPort) + org.eclipse.jetty.client.api.Request request = client.newRequest(requestHost, requestPort) .scheme(requestScheme) .path("/path"); @@ -792,38 +746,29 @@ public class HttpClientTest extends AbstractTest } @ParameterizedTest - @ArgumentsSource(TransportProvider.class) + @MethodSource("transports") public void testRequestIdleTimeout(Transport transport) throws Exception { - init(transport); CountDownLatch latch = new CountDownLatch(1); long idleTimeout = 500; - scenario.start(new AbstractHandler() + start(transport, new Handler.Processor() { @Override - public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws ServletException + public void process(Request request, org.eclipse.jetty.server.Response response, Callback callback) throws Exception { - try - { - baseRequest.setHandled(true); - if (target.equals("/1")) - assertTrue(latch.await(5, TimeUnit.SECONDS)); - else if (target.equals("/2")) - Thread.sleep(2 * idleTimeout); - else - fail("Unknown path: " + target); - } - catch (InterruptedException x) - { - throw new ServletException(x); - } + String target = request.getPathInContext(); + if (target.equals("/1")) + assertTrue(latch.await(5, TimeUnit.SECONDS)); + else if (target.equals("/2")) + Thread.sleep(2 * idleTimeout); + else + fail("Unknown path: " + target); } }); assertThrows(TimeoutException.class, () -> - scenario.client.newRequest(scenario.newURI()) - .scheme(scenario.getScheme()) + client.newRequest(newURI(transport)) .path("/1") .idleTimeout(idleTimeout, TimeUnit.MILLISECONDS) .timeout(2 * idleTimeout, TimeUnit.MILLISECONDS) @@ -831,8 +776,7 @@ public class HttpClientTest extends AbstractTest latch.countDown(); // Make another request without specifying the idle timeout, should not fail - ContentResponse response = scenario.client.newRequest(scenario.newURI()) - .scheme(scenario.getScheme()) + ContentResponse response = client.newRequest(newURI(transport)) .path("/2") .timeout(3 * idleTimeout, TimeUnit.MILLISECONDS) .send(); diff --git a/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-http-client-transport/src/test/java/org/eclipse/jetty/ee9/http/client/HttpClientTimeoutTest.java b/jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/HttpClientTimeoutTest.java similarity index 70% rename from jetty-ee9/jetty-ee9-tests/jetty-ee9-test-http-client-transport/src/test/java/org/eclipse/jetty/ee9/http/client/HttpClientTimeoutTest.java rename to jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/HttpClientTimeoutTest.java index 53e33a66cc4..1d13947d044 100644 --- a/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-http-client-transport/src/test/java/org/eclipse/jetty/ee9/http/client/HttpClientTimeoutTest.java +++ b/jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/HttpClientTimeoutTest.java @@ -11,11 +11,9 @@ // ======================================================================== // -package org.eclipse.jetty.ee9.http.client; +package org.eclipse.jetty.test.client.transport; import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InterruptedIOException; import java.net.InetSocketAddress; import java.net.Socket; import java.net.SocketTimeoutException; @@ -27,9 +25,6 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import javax.net.ssl.SSLEngine; -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; import org.eclipse.jetty.client.HttpClient; import org.eclipse.jetty.client.api.Connection; import org.eclipse.jetty.client.api.ContentResponse; @@ -42,20 +37,23 @@ import org.eclipse.jetty.client.util.InputStreamRequestContent; import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.io.ClientConnectionFactory; +import org.eclipse.jetty.io.Content; import org.eclipse.jetty.io.EndPoint; import org.eclipse.jetty.io.ssl.SslClientConnectionFactory; import org.eclipse.jetty.io.ssl.SslConnection; -import org.eclipse.jetty.server.handler.AbstractHandler; +import org.eclipse.jetty.server.Handler; +import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.FuturePromise; -import org.eclipse.jetty.util.IO; import org.eclipse.jetty.util.ssl.SslContextFactory; +import org.eclipse.jetty.util.thread.QueuedThreadPool; +import org.hamcrest.MatcherAssert; import org.hamcrest.Matchers; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Assumptions; import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.ArgumentsSource; +import org.junit.jupiter.params.provider.MethodSource; import org.opentest4j.TestAbortedException; -import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; @@ -65,40 +63,32 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; -public class HttpClientTimeoutTest extends AbstractTest +public class HttpClientTimeoutTest extends AbstractTest { - @Override - public void init(Transport transport) throws IOException - { - setScenario(new TransportScenario(transport)); - } - @ParameterizedTest - @ArgumentsSource(TransportProvider.class) + @MethodSource("transports") public void testTimeoutOnFuture(Transport transport) throws Exception { - init(transport); long timeout = 1000; - scenario.start(new TimeoutHandler(2 * timeout)); + start(transport, new TimeoutHandler(2 * timeout)); assertThrows(TimeoutException.class, () -> { - scenario.client.newRequest(scenario.newURI()) + client.newRequest(newURI(transport)) .timeout(timeout, TimeUnit.MILLISECONDS) .send(); }); } @ParameterizedTest - @ArgumentsSource(TransportProvider.class) + @MethodSource("transports") public void testTimeoutOnListener(Transport transport) throws Exception { - init(transport); long timeout = 1000; - scenario.start(new TimeoutHandler(2 * timeout)); + start(transport, new TimeoutHandler(2 * timeout)); final CountDownLatch latch = new CountDownLatch(1); - Request request = scenario.client.newRequest(scenario.newURI()) + Request request = client.newRequest(newURI(transport)) .timeout(timeout, TimeUnit.MILLISECONDS); request.send(result -> { @@ -109,19 +99,18 @@ public class HttpClientTimeoutTest extends AbstractTest } @ParameterizedTest - @ArgumentsSource(TransportProvider.class) + @MethodSource("transports") public void testTimeoutOnQueuedRequest(Transport transport) throws Exception { - init(transport); long timeout = 1000; - scenario.start(new TimeoutHandler(3 * timeout)); + start(transport, new TimeoutHandler(3 * timeout)); // Only one connection so requests get queued - scenario.client.setMaxConnectionsPerDestination(1); + client.setMaxConnectionsPerDestination(1); // The first request has a long timeout final CountDownLatch firstLatch = new CountDownLatch(1); - Request request = scenario.client.newRequest(scenario.newURI()) + Request request = client.newRequest(newURI(transport)) .timeout(4 * timeout, TimeUnit.MILLISECONDS); request.send(result -> { @@ -131,7 +120,7 @@ public class HttpClientTimeoutTest extends AbstractTest // Second request has a short timeout and should fail in the queue final CountDownLatch secondLatch = new CountDownLatch(1); - request = scenario.client.newRequest(scenario.newURI()) + request = client.newRequest(newURI(transport)) .timeout(timeout, TimeUnit.MILLISECONDS); request.send(result -> { @@ -146,16 +135,15 @@ public class HttpClientTimeoutTest extends AbstractTest } @ParameterizedTest - @ArgumentsSource(TransportProvider.class) + @MethodSource("transports") public void testTimeoutIsCancelledOnSuccess(Transport transport) throws Exception { - init(transport); long timeout = 1000; - scenario.start(new TimeoutHandler(timeout)); + start(transport, new TimeoutHandler(timeout)); final CountDownLatch latch = new CountDownLatch(1); final byte[] content = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; - Request request = scenario.client.newRequest(scenario.newURI()) + Request request = client.newRequest(newURI(transport)) .body(new InputStreamRequestContent(new ByteArrayInputStream(content))) .timeout(2 * timeout, TimeUnit.MILLISECONDS); request.send(new BufferingResponseListener() @@ -177,17 +165,16 @@ public class HttpClientTimeoutTest extends AbstractTest } @ParameterizedTest - @ArgumentsSource(TransportProvider.class) + @MethodSource("transports") public void testTimeoutOnListenerWithExplicitConnection(Transport transport) throws Exception { - init(transport); long timeout = 1000; - scenario.start(new TimeoutHandler(2 * timeout)); + start(transport, new TimeoutHandler(2 * timeout)); - Request request = scenario.client.newRequest(scenario.newURI()).timeout(timeout, TimeUnit.MILLISECONDS); + Request request = client.newRequest(newURI(transport)).timeout(timeout, TimeUnit.MILLISECONDS); CountDownLatch latch = new CountDownLatch(1); - Destination destination = scenario.client.resolveDestination(request); + Destination destination = client.resolveDestination(request); FuturePromise futureConnection = new FuturePromise<>(); destination.newConnection(futureConnection); try (Connection connection = futureConnection.get(5, TimeUnit.SECONDS)) @@ -203,17 +190,16 @@ public class HttpClientTimeoutTest extends AbstractTest } @ParameterizedTest - @ArgumentsSource(TransportProvider.class) + @MethodSource("transports") public void testTimeoutIsCancelledOnSuccessWithExplicitConnection(Transport transport) throws Exception { - init(transport); long timeout = 1000; - scenario.start(new TimeoutHandler(timeout)); + start(transport, new TimeoutHandler(timeout)); - Request request = scenario.client.newRequest(scenario.newURI()).timeout(2 * timeout, TimeUnit.MILLISECONDS); + Request request = client.newRequest(newURI(transport)).timeout(2 * timeout, TimeUnit.MILLISECONDS); CountDownLatch latch = new CountDownLatch(1); - Destination destination = scenario.client.resolveDestination(request); + Destination destination = client.resolveDestination(request); FuturePromise futureConnection = new FuturePromise<>(); destination.newConnection(futureConnection); try (Connection connection = futureConnection.get(5, TimeUnit.SECONDS)) @@ -235,16 +221,16 @@ public class HttpClientTimeoutTest extends AbstractTest } @ParameterizedTest - @ArgumentsSource(TransportProvider.class) + @MethodSource("transports") public void testIdleTimeout(Transport transport) throws Exception { - init(transport); long timeout = 1000; - scenario.startServer(new TimeoutHandler(2 * timeout)); + startServer(transport, new TimeoutHandler(2 * timeout)); AtomicBoolean sslIdle = new AtomicBoolean(); - SslContextFactory.Client sslContextFactory = scenario.newClientSslContextFactory(); - scenario.client = new HttpClient(scenario.provideClientTransport(transport, sslContextFactory)) + QueuedThreadPool clientThreads = new QueuedThreadPool(); + clientThreads.setName("client"); + client = new HttpClient(newHttpClientTransport(transport)) { @Override public ClientConnectionFactory newSslClientConnectionFactory(SslContextFactory.Client sslContextFactory, ClientConnectionFactory connectionFactory) @@ -269,54 +255,50 @@ public class HttpClientTimeoutTest extends AbstractTest }; } }; - scenario.client.setIdleTimeout(timeout); - scenario.client.start(); + client.setExecutor(clientThreads); + client.setIdleTimeout(timeout); + client.start(); assertThrows(TimeoutException.class, () -> { - scenario.client.newRequest(scenario.newURI()) + client.newRequest(newURI(transport)) .send(); }); assertFalse(sslIdle.get()); } @ParameterizedTest - @ArgumentsSource(TransportProvider.class) + @MethodSource("transports") public void testBlockingConnectTimeoutFailsRequest(Transport transport) throws Exception { // Failure to connect is based on InetSocket address failure, which Unix-Domain does not use. Assumptions.assumeTrue(transport != Transport.UNIX_DOMAIN); - init(transport); - testConnectTimeoutFailsRequest(true); + testConnectTimeoutFailsRequest(transport, true); } @ParameterizedTest - @ArgumentsSource(TransportProvider.class) + @MethodSource("transports") public void testNonBlockingConnectTimeoutFailsRequest(Transport transport) throws Exception { // Failure to connect is based on InetSocket address failure, which Unix-Domain does not use. Assumptions.assumeTrue(transport != Transport.UNIX_DOMAIN); - init(transport); - testConnectTimeoutFailsRequest(false); + testConnectTimeoutFailsRequest(transport, false); } - private void testConnectTimeoutFailsRequest(boolean blocking) throws Exception + private void testConnectTimeoutFailsRequest(Transport transport, boolean blocking) throws Exception { String host = "10.255.255.1"; int port = 80; int connectTimeout = 1000; assumeConnectTimeout(host, port, connectTimeout); - scenario.start(new EmptyServerHandler()); - HttpClient client = scenario.client; - client.stop(); + start(transport, new EmptyServerHandler()); client.setConnectTimeout(connectTimeout); client.setConnectBlocking(blocking); - client.start(); final CountDownLatch latch = new CountDownLatch(1); Request request = client.newRequest(host, port); - request.scheme(scenario.getScheme()) + request.scheme(newURI(transport).getScheme()) .send(result -> { if (result.isFailed()) @@ -328,29 +310,24 @@ public class HttpClientTimeoutTest extends AbstractTest } @ParameterizedTest - @ArgumentsSource(TransportProvider.class) + @MethodSource("transports") public void testConnectTimeoutIsCancelledByShorterRequestTimeout(Transport transport) throws Exception { // Failure to connect is based on InetSocket address failure, which Unix-Domain does not use. Assumptions.assumeTrue(transport != Transport.UNIX_DOMAIN); - init(transport); - String host = "10.255.255.1"; int port = 80; int connectTimeout = 2000; assumeConnectTimeout(host, port, connectTimeout); - scenario.start(new EmptyServerHandler()); - HttpClient client = scenario.client; - client.stop(); + start(transport, new EmptyServerHandler()); client.setConnectTimeout(connectTimeout); - client.start(); final AtomicInteger completes = new AtomicInteger(); final CountDownLatch latch = new CountDownLatch(2); Request request = client.newRequest(host, port); - request.scheme(scenario.getScheme()) + request.scheme(newURI(transport).getScheme()) .timeout(connectTimeout / 2, TimeUnit.MILLISECONDS) .send(result -> { @@ -364,35 +341,31 @@ public class HttpClientTimeoutTest extends AbstractTest } @ParameterizedTest - @ArgumentsSource(TransportProvider.class) + @MethodSource("transports") public void testRetryAfterConnectTimeout(Transport transport) throws Exception { // Failure to connect is based on InetSocket address failure, which Unix-Domain does not use. Assumptions.assumeTrue(transport != Transport.UNIX_DOMAIN); - init(transport); - final String host = "10.255.255.1"; final int port = 80; int connectTimeout = 1000; assumeConnectTimeout(host, port, connectTimeout); - scenario.start(new EmptyServerHandler()); - HttpClient client = scenario.client; - client.stop(); + start(transport, new EmptyServerHandler()); client.setConnectTimeout(connectTimeout); - client.start(); final CountDownLatch latch = new CountDownLatch(1); Request request = client.newRequest(host, port); - request.scheme(scenario.getScheme()) + String scheme = newURI(transport).getScheme(); + request.scheme(scheme) .send(result -> { if (result.isFailed()) { // Retry client.newRequest(host, port) - .scheme(scenario.getScheme()) + .scheme(scheme) .send(retryResult -> { if (retryResult.isFailed()) @@ -406,14 +379,13 @@ public class HttpClientTimeoutTest extends AbstractTest } @ParameterizedTest - @ArgumentsSource(TransportProvider.class) + @MethodSource("transports") public void testVeryShortTimeout(Transport transport) throws Exception { - init(transport); - scenario.start(new EmptyServerHandler()); + start(transport, new EmptyServerHandler()); final CountDownLatch latch = new CountDownLatch(1); - scenario.client.newRequest(scenario.newURI()) + client.newRequest(newURI(transport)) .timeout(1, TimeUnit.MILLISECONDS) // Very short timeout .send(result -> latch.countDown()); @@ -421,18 +393,13 @@ public class HttpClientTimeoutTest extends AbstractTest } @ParameterizedTest - @ArgumentsSource(TransportProvider.class) + @MethodSource("transports") public void testTimeoutCancelledWhenSendingThrowsException(Transport transport) throws Exception { - init(transport); - - scenario.start(new EmptyServerHandler()); + start(transport, new EmptyServerHandler()); long timeout = 1000; - String uri = "badscheme://0.0.0.1"; - if (scenario.getServerPort().isPresent()) - uri += ":" + scenario.getServerPort().getAsInt(); - Request request = scenario.client.newRequest(uri); + Request request = client.newRequest("badscheme://0.0.0.1/"); // TODO: assert a more specific Throwable assertThrows(Exception.class, () -> @@ -450,32 +417,23 @@ public class HttpClientTimeoutTest extends AbstractTest } @ParameterizedTest - @ArgumentsSource(TransportProvider.class) + @MethodSource("transports") public void testFirstRequestTimeoutAfterSecondRequestCompletes(Transport transport) throws Exception { - init(transport); long timeout = 2000; - scenario.start(new EmptyServerHandler() + start(transport, new Handler.Processor() { @Override - protected void service(String target, org.eclipse.jetty.server.Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + public void process(org.eclipse.jetty.server.Request request, org.eclipse.jetty.server.Response response, Callback callback) throws Exception { - if (request.getRequestURI().startsWith("/one")) - { - try - { - Thread.sleep(3 * timeout); - } - catch (InterruptedException x) - { - throw new InterruptedIOException(); - } - } + if (request.getPathInContext().startsWith("/one")) + Thread.sleep(3 * timeout); + callback.succeeded(); } }); CountDownLatch latch = new CountDownLatch(1); - scenario.client.newRequest(scenario.newURI()) + client.newRequest(newURI(transport)) .path("/one") .timeout(2 * timeout, TimeUnit.MILLISECONDS) .send(result -> @@ -484,7 +442,7 @@ public class HttpClientTimeoutTest extends AbstractTest latch.countDown(); }); - ContentResponse response = scenario.client.newRequest(scenario.newURI()) + ContentResponse response = client.newRequest(newURI(transport)) .path("/two") .timeout(timeout, TimeUnit.MILLISECONDS) .send(); @@ -494,55 +452,45 @@ public class HttpClientTimeoutTest extends AbstractTest } @ParameterizedTest - @ArgumentsSource(TransportProvider.class) + @MethodSource("transports") public void testRequestQueuedDoesNotCancelTimeoutOfQueuedRequests(Transport transport) throws Exception { - init(transport); CountDownLatch serverLatch = new CountDownLatch(1); - scenario.start(new EmptyServerHandler() + start(transport, new Handler.Processor() { @Override - protected void service(String target, org.eclipse.jetty.server.Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + public void process(org.eclipse.jetty.server.Request request, org.eclipse.jetty.server.Response response, Callback callback) throws Exception { - if (request.getRequestURI().startsWith("/one")) - { - try - { - serverLatch.await(); - } - catch (InterruptedException x) - { - throw new InterruptedIOException(); - } - } + if (request.getPathInContext().startsWith("/one")) + serverLatch.await(); + callback.succeeded(); } }); - - scenario.client.setMaxConnectionsPerDestination(1); - scenario.setMaxRequestsPerConnection(1); + setMaxRequestsPerConnection(1); + client.setMaxConnectionsPerDestination(1); // Send the first request so that the others get queued. CountDownLatch latch1 = new CountDownLatch(1); - scenario.client.newRequest(scenario.newURI()) + client.newRequest(newURI(transport)) .path("/one") .send(result -> { - assertTrue(result.isSucceeded()); - assertEquals(HttpStatus.OK_200, result.getResponse().getStatus()); + Assertions.assertTrue(result.isSucceeded()); + Assertions.assertEquals(HttpStatus.OK_200, result.getResponse().getStatus()); latch1.countDown(); }); // Queue a second request, it should expire in the queue. long timeout = 1000; CountDownLatch latch2 = new CountDownLatch(1); - scenario.client.newRequest(scenario.newURI()) + client.newRequest(newURI(transport)) .path("/two") .timeout(2 * timeout, TimeUnit.MILLISECONDS) .send(result -> { - assertTrue(result.isFailed()); - assertThat(result.getFailure(), Matchers.instanceOf(TimeoutException.class)); + Assertions.assertTrue(result.isFailed()); + MatcherAssert.assertThat(result.getFailure(), Matchers.instanceOf(TimeoutException.class)); latch2.countDown(); }); @@ -550,13 +498,13 @@ public class HttpClientTimeoutTest extends AbstractTest // Queue a third request, it should not reset the timeout of the second request. CountDownLatch latch3 = new CountDownLatch(1); - scenario.client.newRequest(scenario.newURI()) + client.newRequest(newURI(transport)) .path("/three") .timeout(2 * timeout, TimeUnit.MILLISECONDS) .send(result -> { - assertTrue(result.isSucceeded()); - assertEquals(HttpStatus.OK_200, result.getResponse().getStatus()); + Assertions.assertTrue(result.isSucceeded()); + Assertions.assertEquals(HttpStatus.OK_200, result.getResponse().getStatus()); latch3.countDown(); }); @@ -593,7 +541,7 @@ public class HttpClientTimeoutTest extends AbstractTest } } - private static class TimeoutHandler extends AbstractHandler + private static class TimeoutHandler extends Handler.Processor { private final long timeout; @@ -603,18 +551,10 @@ public class HttpClientTimeoutTest extends AbstractTest } @Override - public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + public void process(org.eclipse.jetty.server.Request request, org.eclipse.jetty.server.Response response, Callback callback) throws Exception { - baseRequest.setHandled(true); - try - { - TimeUnit.MILLISECONDS.sleep(timeout); - IO.copy(request.getInputStream(), response.getOutputStream()); - } - catch (InterruptedException x) - { - throw new ServletException(x); - } + TimeUnit.MILLISECONDS.sleep(timeout); + Content.copy(request, response, callback); } } } diff --git a/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-http-client-transport/src/test/java/org/eclipse/jetty/ee9/http/client/HttpClientTransportDynamicTest.java b/jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/HttpClientTransportDynamicTest.java similarity index 93% rename from jetty-ee9/jetty-ee9-tests/jetty-ee9-test-http-client-transport/src/test/java/org/eclipse/jetty/ee9/http/client/HttpClientTransportDynamicTest.java rename to jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/HttpClientTransportDynamicTest.java index 8d52c765143..606af39f51d 100644 --- a/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-http-client-transport/src/test/java/org/eclipse/jetty/ee9/http/client/HttpClientTransportDynamicTest.java +++ b/jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/HttpClientTransportDynamicTest.java @@ -11,9 +11,8 @@ // ======================================================================== // -package org.eclipse.jetty.ee9.http.client; +package org.eclipse.jetty.test.client.transport; -import java.io.IOException; import java.util.List; import java.util.Random; import java.util.concurrent.CountDownLatch; @@ -22,8 +21,6 @@ import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.TimeUnit; import java.util.function.Function; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; import org.eclipse.jetty.alpn.server.ALPNServerConnectionFactory; import org.eclipse.jetty.client.AbstractConnectionPool; import org.eclipse.jetty.client.HttpClient; @@ -50,15 +47,17 @@ import org.eclipse.jetty.http2.server.HTTP2CServerConnectionFactory; import org.eclipse.jetty.http2.server.HTTP2ServerConnectionFactory; import org.eclipse.jetty.io.ClientConnectionFactory; import org.eclipse.jetty.io.ClientConnector; +import org.eclipse.jetty.io.Content; import org.eclipse.jetty.server.AbstractConnectionFactory; import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.HttpConfiguration; import org.eclipse.jetty.server.HttpConnectionFactory; import org.eclipse.jetty.server.ProxyConnectionFactory; import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Response; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; -import org.eclipse.jetty.util.IO; +import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.ssl.SslContextFactory; import org.eclipse.jetty.util.thread.QueuedThreadPool; import org.junit.jupiter.api.AfterEach; @@ -397,13 +396,13 @@ public class HttpClientTransportDynamicTest // client :1234 <-> :8888 proxy :5678 <-> server :8080 // client :2345 <-> :8888 proxy :6789 <-> server :8080 - startServer(this::proxyH1H2C, new EmptyServerHandler() + startServer(this::proxyH1H2C, new Handler.Processor() { @Override - protected void service(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + public void process(Request request, Response response, Callback callback) { - response.setContentType(MimeTypes.Type.TEXT_PLAIN.asString()); - response.getOutputStream().print(request.getRemotePort()); + response.getHeaders().put(HttpHeader.CONTENT_TYPE, MimeTypes.Type.TEXT_PLAIN.asString()); + Content.Sink.write(response, true, String.valueOf(Request.getRemotePort(request)), callback); } }); startClient(HttpClientConnectionFactory.HTTP11); @@ -494,13 +493,13 @@ public class HttpClientTransportDynamicTest public void testHTTP11UpgradeToH2C() throws Exception { String content = "upgrade"; - startServer(this::h1H2C, new EmptyServerHandler() + startServer(this::h1H2C, new Handler.Processor() { @Override - protected void service(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + public void process(Request request, Response response, Callback callback) { - response.setContentType("text/plain; charset=UTF-8"); - response.getOutputStream().print(content); + response.getHeaders().put(HttpHeader.CONTENT_TYPE, MimeTypes.Type.TEXT_PLAIN_UTF_8.asString()); + Content.Sink.write(response, true, content, callback); } }); ClientConnector clientConnector = new ClientConnector(); @@ -569,13 +568,13 @@ public class HttpClientTransportDynamicTest public void testHTTP11UpgradeToH2CWithForwardProxy() throws Exception { String content = "upgrade"; - startServer(this::h1H2C, new EmptyServerHandler() + startServer(this::h1H2C, new Handler.Processor() { @Override - protected void service(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + public void process(Request request, Response response, Callback callback) { - response.setContentType("text/plain; charset=UTF-8"); - response.getOutputStream().print(content); + response.getHeaders().put(HttpHeader.CONTENT_TYPE, MimeTypes.Type.TEXT_PLAIN_UTF_8.asString()); + Content.Sink.write(response, true, content, callback); } }); ClientConnector clientConnector = new ClientConnector(); @@ -608,13 +607,13 @@ public class HttpClientTransportDynamicTest public void testHTTP11UpgradeToH2COverTLS() throws Exception { String content = "upgrade"; - startServer(this::sslH1H2C, new EmptyServerHandler() + startServer(this::h1H2C, new Handler.Processor() { @Override - protected void service(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + public void process(Request request, Response response, Callback callback) { - response.setContentType("text/plain; charset=UTF-8"); - response.getOutputStream().print(content); + response.getHeaders().put(HttpHeader.CONTENT_TYPE, MimeTypes.Type.TEXT_PLAIN_UTF_8.asString()); + Content.Sink.write(response, true, content, callback); } }); ClientConnector clientConnector = new ClientConnector(); @@ -641,12 +640,12 @@ public class HttpClientTransportDynamicTest @Test public void testHTTP11UpgradeToH2CWithRequestContentDoesNotUpgrade() throws Exception { - startServer(this::h1H2C, new EmptyServerHandler() + startServer(this::h1H2C, new Handler.Processor() { @Override - protected void service(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + public void process(Request request, Response response, Callback callback) { - IO.copy(request.getInputStream(), response.getOutputStream()); + Content.copy(request, response, callback); } }); ClientConnector clientConnector = new ClientConnector(); @@ -708,12 +707,13 @@ public class HttpClientTransportDynamicTest @Test public void testHTTP11UpgradeToH2CFailedServerClose() throws Exception { - startServer(this::h1H2C, new EmptyServerHandler() + startServer(this::h1H2C, new Handler.Processor() { @Override - protected void service(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) + public void process(Request request, Response response, Callback callback) { - jettyRequest.getHttpChannel().getEndPoint().close(); + request.getConnectionMetaData().getConnection().getEndPoint().close(); + callback.succeeded(); } }); ClientConnector clientConnector = new ClientConnector(); diff --git a/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-http-client-transport/src/test/java/org/eclipse/jetty/ee9/http/client/ProxyWithDynamicTransportTest.java b/jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/ProxyWithDynamicTransportTest.java similarity index 98% rename from jetty-ee9/jetty-ee9-tests/jetty-ee9-test-http-client-transport/src/test/java/org/eclipse/jetty/ee9/http/client/ProxyWithDynamicTransportTest.java rename to jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/ProxyWithDynamicTransportTest.java index f0e63a74101..3b898eba349 100644 --- a/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-http-client-transport/src/test/java/org/eclipse/jetty/ee9/http/client/ProxyWithDynamicTransportTest.java +++ b/jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/ProxyWithDynamicTransportTest.java @@ -11,7 +11,7 @@ // ======================================================================== // -package org.eclipse.jetty.ee9.http.client; +package org.eclipse.jetty.test.client.transport; import java.net.ConnectException; import java.net.InetSocketAddress; @@ -24,8 +24,6 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; import org.eclipse.jetty.alpn.server.ALPNServerConnectionFactory; import org.eclipse.jetty.client.AbstractConnectionPool; import org.eclipse.jetty.client.HttpClient; @@ -36,10 +34,6 @@ import org.eclipse.jetty.client.api.ContentResponse; import org.eclipse.jetty.client.api.Destination; import org.eclipse.jetty.client.dynamic.HttpClientTransportDynamic; import org.eclipse.jetty.client.http.HttpClientConnectionFactory; -import org.eclipse.jetty.ee9.proxy.AsyncProxyServlet; -import org.eclipse.jetty.ee9.proxy.ConnectHandler; -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.HttpScheme; import org.eclipse.jetty.http.HttpStatus; @@ -61,6 +55,7 @@ import org.eclipse.jetty.http2.server.HTTP2ServerConnectionFactory; import org.eclipse.jetty.io.ClientConnectionFactory; import org.eclipse.jetty.io.ClientConnector; import org.eclipse.jetty.io.EndPoint; +import org.eclipse.jetty.server.ConnectHandler; import org.eclipse.jetty.server.ConnectionFactory; import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.HttpConfiguration; diff --git a/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-http-client-transport/src/test/java/org/eclipse/jetty/ee9/http/client/RoundRobinConnectionPoolTest.java b/jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/RoundRobinConnectionPoolTest.java similarity index 77% rename from jetty-ee9/jetty-ee9-tests/jetty-ee9-test-http-client-transport/src/test/java/org/eclipse/jetty/ee9/http/client/RoundRobinConnectionPoolTest.java rename to jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/RoundRobinConnectionPoolTest.java index c97845c567f..87923e676cc 100644 --- a/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-http-client-transport/src/test/java/org/eclipse/jetty/ee9/http/client/RoundRobinConnectionPoolTest.java +++ b/jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/RoundRobinConnectionPoolTest.java @@ -11,9 +11,8 @@ // ======================================================================== // -package org.eclipse.jetty.ee9.http.client; +package org.eclipse.jetty.test.client.transport; -import java.io.IOException; import java.util.List; import java.util.Map; import java.util.concurrent.CompletableFuture; @@ -27,50 +26,45 @@ import java.util.concurrent.atomic.AtomicReference; import java.util.function.Function; import java.util.stream.Collectors; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; import org.eclipse.jetty.client.RoundRobinConnectionPool; import org.eclipse.jetty.client.api.ContentResponse; import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.quic.server.QuicServerConnector; +import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Response; +import org.eclipse.jetty.util.Callback; import org.hamcrest.Matchers; import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.ArgumentsSource; +import org.junit.jupiter.params.provider.MethodSource; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.lessThanOrEqualTo; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; -public class RoundRobinConnectionPoolTest extends AbstractTest +public class RoundRobinConnectionPoolTest extends AbstractTest { - @Override - public void init(Transport transport) throws IOException - { - setScenario(new TransportScenario(transport)); - } - @ParameterizedTest - @ArgumentsSource(TransportProvider.class) + @MethodSource("transports") public void testRoundRobin(Transport transport) throws Exception { - init(transport); AtomicBoolean record = new AtomicBoolean(); List remotePorts = new CopyOnWriteArrayList<>(); - scenario.start(new EmptyServerHandler() + start(transport, new Handler.Processor() { @Override - protected void service(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) + public void process(Request request, Response response, Callback callback) { if (record.get()) - remotePorts.add(request.getRemotePort()); + remotePorts.add(Request.getRemotePort(request)); + callback.succeeded(); } }); int maxConnections = 3; CompletableFuture setup = new CompletableFuture<>(); - scenario.client.getTransport().setConnectionPoolFactory(destination -> + client.getTransport().setConnectionPoolFactory(destination -> { RoundRobinConnectionPool pool = new RoundRobinConnectionPool(destination, maxConnections, destination); pool.preCreateConnections(maxConnections).handle((r, x) -> x != null ? setup.completeExceptionally(x) : setup.complete(null)); @@ -80,7 +74,7 @@ public class RoundRobinConnectionPoolTest extends AbstractTest 0) assertThat(remotePorts.get(i - 1), Matchers.not(Matchers.equalTo(candidate))); } } @ParameterizedTest - @ArgumentsSource(TransportProvider.class) + @MethodSource("transports") public void testMultiplex(Transport transport) throws Exception { - init(transport); int multiplex = 1; - if (scenario.transport.isMultiplexed()) + if (transport.isMultiplexed()) multiplex = 4; int maxMultiplex = multiplex; @@ -125,30 +118,31 @@ public class RoundRobinConnectionPoolTest extends AbstractTest requestLatch = new AtomicReference<>(); CountDownLatch serverLatch = new CountDownLatch(count); CyclicBarrier barrier = new CyclicBarrier(count + 1); - scenario.start(new EmptyServerHandler() + start(transport, new Handler.Processor() { @Override - protected void service(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) + public void process(Request request, Response response, Callback callback) { try { if (record.get()) { - remotePorts.add(request.getRemotePort()); + remotePorts.add(Request.getRemotePort(request)); requestLatch.get().countDown(); serverLatch.countDown(); barrier.await(); } + callback.succeeded(); } catch (Exception x) { - throw new RuntimeException(x); + callback.failed(x); } } }); CompletableFuture setup = new CompletableFuture<>(); - scenario.client.getTransport().setConnectionPoolFactory(destination -> + client.getTransport().setConnectionPoolFactory(destination -> { RoundRobinConnectionPool pool = new RoundRobinConnectionPool(destination, maxConnections, destination); pool.preCreateConnections(maxConnections).handle((r, x) -> x != null ? setup.completeExceptionally(x) : setup.complete(null)); @@ -158,7 +152,7 @@ public class RoundRobinConnectionPoolTest extends AbstractTest requests.incrementAndGet()) .onRequestBegin(request -> requests.decrementAndGet()) @@ -195,20 +189,18 @@ public class RoundRobinConnectionPoolTest extends AbstractTest 0) assertThat(remotePorts.get(i - 1), Matchers.not(Matchers.equalTo(candidate))); } } @ParameterizedTest - @ArgumentsSource(TransportProvider.class) + @MethodSource("transports") public void testMultiplexWithMaxUsage(Transport transport) throws Exception { - init(transport); - int multiplex = 1; - if (scenario.transport.isMultiplexed()) + if (transport.isMultiplexed()) multiplex = 2; int maxMultiplex = multiplex; @@ -217,27 +209,28 @@ public class RoundRobinConnectionPoolTest extends AbstractTest remotePorts = new CopyOnWriteArrayList<>(); - scenario.start(new EmptyServerHandler() + start(transport, new Handler.Processor() { @Override - protected void service(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) + public void process(Request request, Response response, Callback callback) { - remotePorts.add(request.getRemotePort()); + remotePorts.add(Request.getRemotePort(request)); + callback.succeeded(); } }); if (transport == Transport.H3) - ((QuicServerConnector)scenario.connector).getQuicConfiguration().setMaxBidirectionalRemoteStreams(maxUsage); - scenario.client.getTransport().setConnectionPoolFactory(destination -> + ((QuicServerConnector)connector).getQuicConfiguration().setMaxBidirectionalRemoteStreams(maxUsage); + client.getTransport().setConnectionPoolFactory(destination -> { RoundRobinConnectionPool pool = new RoundRobinConnectionPool(destination, maxConnections, destination, maxMultiplex); - pool.setMaxUsageCount(maxUsage); + pool.setMaxUsage(maxUsage); return pool; }); CountDownLatch clientLatch = new CountDownLatch(count); for (int i = 0; i < count; ++i) { - scenario.client.newRequest(scenario.newURI()) + client.newRequest(newURI(transport)) .path("/" + i) .timeout(5, TimeUnit.SECONDS) .send(result -> diff --git a/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-http-client-transport/pom.xml b/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-http-client-transport/pom.xml index 11443be5120..0c266d51316 100644 --- a/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-http-client-transport/pom.xml +++ b/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-http-client-transport/pom.xml @@ -56,7 +56,7 @@ - enable-foreign-and-virtual-threads-preview + enable-preview [19,) diff --git a/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-http-client-transport/src/test/java/org/eclipse/jetty/ee9/http/client/AsyncIOServletTest.java b/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-http-client-transport/src/test/java/org/eclipse/jetty/ee9/http/client/AsyncIOServletTest.java index 265a28cb9ea..d895bf8bb9b 100644 --- a/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-http-client-transport/src/test/java/org/eclipse/jetty/ee9/http/client/AsyncIOServletTest.java +++ b/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-http-client-transport/src/test/java/org/eclipse/jetty/ee9/http/client/AsyncIOServletTest.java @@ -32,7 +32,6 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; -import java.util.zip.GZIPOutputStream; import jakarta.servlet.AsyncContext; import jakarta.servlet.DispatcherType; @@ -51,9 +50,11 @@ import org.eclipse.jetty.client.http.HttpConnectionOverHTTP; import org.eclipse.jetty.client.util.AsyncRequestContent; import org.eclipse.jetty.client.util.BufferingResponseListener; import org.eclipse.jetty.client.util.InputStreamRequestContent; +import org.eclipse.jetty.client.util.OutputStreamRequestContent; import org.eclipse.jetty.client.util.StringRequestContent; import org.eclipse.jetty.ee9.nested.HttpInput; import org.eclipse.jetty.ee9.nested.HttpOutput; +import org.eclipse.jetty.ee9.servlet.ServletContextHandler; import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpHeaderValue; import org.eclipse.jetty.http.HttpMethod; @@ -65,7 +66,6 @@ import org.eclipse.jetty.io.Connection; import org.eclipse.jetty.io.Content; import org.eclipse.jetty.io.EofException; import org.eclipse.jetty.logging.StacklessLogging; -import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.handler.ContextHandler; import org.eclipse.jetty.server.internal.HttpChannelState; @@ -73,6 +73,7 @@ import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.FuturePromise; import org.hamcrest.Matchers; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Assumptions; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ArgumentsSource; @@ -1106,8 +1107,7 @@ public class AsyncIOServletTest extends AbstractTest + { + Assertions.assertEquals(HttpStatus.REQUEST_TIMEOUT_408, response.getStatus()); + latch.countDown(); + }) + .send(null); + + // Wait for the server to idle timeout. + Thread.sleep(2 * idleTimeout); + + assertTrue(errorLatch.await(5, TimeUnit.SECONDS)); + + // Do not send the content to the server. + + assertFalse(dataLatch.await(1, TimeUnit.SECONDS)); + assertTrue(latch.await(5, TimeUnit.SECONDS)); + } + private static class Listener implements ReadListener, WriteListener { private final Executor executor = Executors.newFixedThreadPool(32); @@ -1833,29 +1897,26 @@ public class AsyncIOServletTest extends AbstractTest private void testExpect100ContinueRespond100Continue(Transport transport, byte[]... contents) throws Exception { init(transport); - scenario.start(new AbstractHandler() + scenario.start(new HttpServlet() { @Override - public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + protected void service(HttpServletRequest request, HttpServletResponse response) throws IOException { - baseRequest.setHandled(true); // Send 100-Continue and copy the content back IO.copy(request.getInputStream(), response.getOutputStream()); } @@ -124,12 +124,11 @@ public class HttpClientContinueTest extends AbstractTest public void testExpect100ContinueWithChunkedContentRespond100Continue(Transport transport) throws Exception { init(transport); - scenario.start(new AbstractHandler() + scenario.start(new HttpServlet() { @Override - public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + protected void service(HttpServletRequest request, HttpServletResponse response) throws IOException { - baseRequest.setHandled(true); // Send 100-Continue and copy the content back ServletInputStream input = request.getInputStream(); // Make sure we chunk the response too @@ -185,12 +184,11 @@ public class HttpClientContinueTest extends AbstractTest private void testExpect100ContinueWithContentRespondError(Transport transport, int error) throws Exception { init(transport); - scenario.start(new AbstractHandler() + scenario.start(new HttpServlet() { @Override - public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + protected void service(HttpServletRequest request, HttpServletResponse response) throws IOException { - baseRequest.setHandled(true); response.sendError(error); } }); @@ -226,12 +224,11 @@ public class HttpClientContinueTest extends AbstractTest { init(transport); String data = "success"; - scenario.start(new AbstractHandler() + scenario.start(new HttpServlet() { @Override - public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + protected void service(HttpServletRequest request, HttpServletResponse response) throws IOException { - baseRequest.setHandled(true); if (request.getRequestURI().endsWith("/done")) { response.getOutputStream().print(data); @@ -276,12 +273,11 @@ public class HttpClientContinueTest extends AbstractTest // A request with Expect: 100-Continue cannot receive non-final responses like 3xx String data = "success"; - scenario.start(new AbstractHandler() + scenario.start(new HttpServlet() { @Override - public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + protected void service(HttpServletRequest request, HttpServletResponse response) throws IOException { - baseRequest.setHandled(true); if (request.getRequestURI().endsWith("/done")) { // Send 100-Continue and consume the content @@ -328,12 +324,11 @@ public class HttpClientContinueTest extends AbstractTest CountDownLatch clientLatch = new CountDownLatch(1); CountDownLatch serverLatch = new CountDownLatch(1); - scenario.startServer(new AbstractHandler() + scenario.start(new HttpServlet() { @Override - public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws ServletException + protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException { - baseRequest.setHandled(true); clientRequestRef.get().abort(new Exception("abort!")); try { @@ -379,12 +374,11 @@ public class HttpClientContinueTest extends AbstractTest AtomicReference clientRequestRef = new AtomicReference<>(); CountDownLatch clientLatch = new CountDownLatch(1); CountDownLatch serverLatch = new CountDownLatch(1); - scenario.startServer(new AbstractHandler() + scenario.start(new HttpServlet() { @Override - public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - baseRequest.setHandled(true); // Send 100-Continue and consume the content IO.copy(request.getInputStream(), new ByteArrayOutputStream()); clientRequestRef.get().abort(new Exception("abort!")); @@ -429,12 +423,11 @@ public class HttpClientContinueTest extends AbstractTest public void testExpect100ContinueWithContentWithResponseFailureDuring100Continue(Transport transport) throws Exception { init(transport); - scenario.start(new AbstractHandler() + scenario.start(new HttpServlet() { @Override - public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + protected void service(HttpServletRequest request, HttpServletResponse response) throws IOException { - baseRequest.setHandled(true); // Send 100-Continue and consume the content IO.copy(request.getInputStream(), new ByteArrayOutputStream()); } @@ -497,12 +490,11 @@ public class HttpClientContinueTest extends AbstractTest CountDownLatch serverLatch = new CountDownLatch(1); AtomicReference handlerThread = new AtomicReference<>(); init(transport); - scenario.start(new AbstractHandler() + scenario.start(new HttpServlet() { @Override - public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + protected void service(HttpServletRequest request, HttpServletResponse response) throws IOException { - baseRequest.setHandled(true); handlerThread.set(Thread.currentThread()); // Send 100-Continue and echo the content @@ -540,7 +532,7 @@ public class HttpClientContinueTest extends AbstractTest return thread != null && thread.getState() == Thread.State.WAITING; }); - content.offer(ByteBuffer.wrap(chunk1)); + content.write(ByteBuffer.wrap(chunk1), Callback.NOOP); // Wait for the handler thread to be blocked in the 2nd IO. assertTrue(serverLatch.await(5, TimeUnit.SECONDS)); @@ -550,7 +542,7 @@ public class HttpClientContinueTest extends AbstractTest return thread != null && thread.getState() == Thread.State.WAITING; }); - content.offer(ByteBuffer.wrap(chunk2)); + content.write(ByteBuffer.wrap(chunk2), Callback.NOOP); content.close(); assertTrue(requestLatch.await(5, TimeUnit.SECONDS)); @@ -562,12 +554,11 @@ public class HttpClientContinueTest extends AbstractTest { AtomicReference handlerThread = new AtomicReference<>(); init(transport); - scenario.start(new AbstractHandler() + scenario.start(new HttpServlet() { @Override - public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + protected void service(HttpServletRequest request, HttpServletResponse response) throws IOException { - baseRequest.setHandled(true); handlerThread.set(Thread.currentThread()); // Send 100-Continue and echo the content IO.copy(request.getInputStream(), response.getOutputStream()); @@ -602,7 +593,7 @@ public class HttpClientContinueTest extends AbstractTest return thread != null && thread.getState() == Thread.State.WAITING; }); - content.offer(ByteBuffer.wrap(chunk2)); + content.write(ByteBuffer.wrap(chunk2), Callback.NOOP); content.close(); assertTrue(latch.await(5, TimeUnit.SECONDS)); @@ -613,12 +604,11 @@ public class HttpClientContinueTest extends AbstractTest public void testExpect100ContinueWithConcurrentDeferredContentRespond100Continue(Transport transport) throws Exception { init(transport); - scenario.start(new AbstractHandler() + scenario.start(new HttpServlet() { @Override - public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + protected void service(HttpServletRequest request, HttpServletResponse response) throws IOException { - baseRequest.setHandled(true); // Send 100-Continue and echo the content IO.copy(request.getInputStream(), response.getOutputStream()); } @@ -632,7 +622,7 @@ public class HttpClientContinueTest extends AbstractTest .headers(headers -> headers.put(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE)) .onRequestHeaders(request -> { - content.offer(ByteBuffer.wrap(data)); + content.write(ByteBuffer.wrap(data), Callback.NOOP); content.close(); }) .body(content) @@ -654,12 +644,11 @@ public class HttpClientContinueTest extends AbstractTest public void testExpect100ContinueWithInitialAndConcurrentDeferredContentRespond100Continue(Transport transport) throws Exception { init(transport); - scenario.start(new AbstractHandler() + scenario.start(new HttpServlet() { @Override - public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + protected void service(HttpServletRequest request, HttpServletResponse response) throws IOException { - baseRequest.setHandled(true); // Send 100-Continue and echo the content IO.copy(request.getInputStream(), response.getOutputStream()); } @@ -684,7 +673,7 @@ public class HttpClientContinueTest extends AbstractTest public void onHeaders(Response response) { super.onHeaders(response); - content.offer(ByteBuffer.wrap(chunk2)); + content.write(ByteBuffer.wrap(chunk2), Callback.NOOP); content.close(); } }; @@ -742,24 +731,26 @@ public class HttpClientContinueTest extends AbstractTest readRequestHeaders(socket.getInputStream()); OutputStream output = socket.getOutputStream(); - String responses = - "HTTP/1.1 100 Continue\r\n" + - "\r\n" + - "HTTP/1.1 200 OK\r\n" + - "Transfer-Encoding: chunked\r\n" + - "\r\n" + - "10\r\n" + - "0123456789ABCDEF\r\n"; + String responses = """ + HTTP/1.1 100 Continue\r + \r + HTTP/1.1 200 OK\r + Transfer-Encoding: chunked\r + \r + 10\r + 0123456789ABCDEF\r + """; output.write(responses.getBytes(StandardCharsets.UTF_8)); output.flush(); Thread.sleep(1000); - String content = - "10\r\n" + - "0123456789ABCDEF\r\n" + - "0\r\n" + - "\r\n"; + String content = """ + 10\r + 0123456789ABCDEF\r + 0\r + \r + """; output.write(content.getBytes(StandardCharsets.UTF_8)); output.flush(); @@ -773,12 +764,12 @@ public class HttpClientContinueTest extends AbstractTest public void testNoExpectRespond100Continue(Transport transport) throws Exception { init(transport); - scenario.start(new AbstractHandler() + scenario.start(new HttpServlet() { @Override - public void handle(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + protected void service(HttpServletRequest request, HttpServletResponse response) throws IOException { - jettyRequest.setHandled(true); + Request jettyRequest = (Request)request; // Force a 100 Continue response. jettyRequest.getHttpChannel().sendResponse(HttpGenerator.CONTINUE_100_INFO, null, false); // Echo the content. @@ -826,24 +817,26 @@ public class HttpClientContinueTest extends AbstractTest OutputStream output = socket.getOutputStream(); readRequestHeaders(input); - String response1 = - "HTTP/1.1 100 Continue\r\n" + - "\r\n" + - "HTTP/1.1 303 See Other\r\n" + - "Location: /redirect\r\n" + - "Content-Length: 0\r\n" + - "\r\n"; + String response1 = """ + HTTP/1.1 100 Continue\r + \r + HTTP/1.1 303 See Other\r + Location: /redirect\r + Content-Length: 0\r + \r + """; output.write(response1.getBytes(StandardCharsets.UTF_8)); output.flush(); readRequestHeaders(input); - String response2 = - "HTTP/1.1 100 Continue\r\n" + - "\r\n" + - "HTTP/1.1 200 OK\r\n" + - "Content-Length: 0\r\n" + - "Connection: close\r\n" + - "\r\n"; + String response2 = """ + HTTP/1.1 100 Continue\r + \r + HTTP/1.1 200 OK\r + Content-Length: 0\r + Connection: close\r + \r + """; output.write(response2.getBytes(StandardCharsets.UTF_8)); output.flush(); } diff --git a/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-http-client-transport/src/test/java/org/eclipse/jetty/ee9/http/client/HttpTrailersTest.java b/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-http-client-transport/src/test/java/org/eclipse/jetty/ee9/http/client/HttpTrailersTest.java index 2ba08f7718c..eb4618100a5 100644 --- a/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-http-client-transport/src/test/java/org/eclipse/jetty/ee9/http/client/HttpTrailersTest.java +++ b/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-http-client-transport/src/test/java/org/eclipse/jetty/ee9/http/client/HttpTrailersTest.java @@ -17,14 +17,16 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.nio.charset.StandardCharsets; +import java.util.Map; import java.util.Random; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; -import java.util.stream.Collectors; +import jakarta.servlet.ServletException; import jakarta.servlet.ServletInputStream; +import jakarta.servlet.http.HttpServlet; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.eclipse.jetty.client.HttpRequest; @@ -32,12 +34,9 @@ import org.eclipse.jetty.client.HttpResponse; import org.eclipse.jetty.client.api.ContentResponse; import org.eclipse.jetty.client.util.BytesRequestContent; import org.eclipse.jetty.client.util.InputStreamResponseListener; -import org.eclipse.jetty.http.HttpField; import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.HttpMethod; import org.eclipse.jetty.http.HttpStatus; -import org.eclipse.jetty.server.Request; -import org.eclipse.jetty.server.Response; import org.junit.jupiter.api.Assumptions; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ArgumentsSource; @@ -78,13 +77,13 @@ public class HttpTrailersTest extends AbstractTest { String trailerName = "Trailer"; String trailerValue = "value"; - scenario.start(new EmptyServerHandler() + scenario.start(new HttpServlet() { @Override - protected void service(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // Read the content first. - ServletInputStream input = jettyRequest.getInputStream(); + ServletInputStream input = request.getInputStream(); while (true) { int read = input.read(); @@ -92,8 +91,10 @@ public class HttpTrailersTest extends AbstractTest break; } + assertTrue(request.isTrailerFieldsReady()); + // Now the trailers can be accessed. - HttpFields trailers = jettyRequest.getTrailerHttpFields(); + Map trailers = request.getTrailerFields(); assertNotNull(trailers); assertEquals(trailerValue, trailers.get(trailerName)); } @@ -114,13 +115,13 @@ public class HttpTrailersTest extends AbstractTest public void testEmptyRequestTrailers(Transport transport) throws Exception { init(transport); - scenario.start(new EmptyServerHandler() + scenario.start(new HttpServlet() { @Override - protected void service(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // Read the content first. - ServletInputStream input = jettyRequest.getInputStream(); + ServletInputStream input = request.getInputStream(); while (true) { int read = input.read(); @@ -128,9 +129,12 @@ public class HttpTrailersTest extends AbstractTest break; } + assertTrue(request.isTrailerFieldsReady()); + // Now the trailers can be accessed. - HttpFields trailers = jettyRequest.getTrailerHttpFields(); - assertNull(trailers); + Map trailers = request.getTrailerFields(); + assertNotNull(trailers); + assertTrue(trailers.isEmpty()); } }); @@ -162,19 +166,16 @@ public class HttpTrailersTest extends AbstractTest AtomicBoolean once = new AtomicBoolean(); String trailerName = "Trailer"; String trailerValue = "value"; - scenario.start(new EmptyServerHandler() + scenario.start(new HttpServlet() { @Override - protected void service(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - Response jettyResponse = jettyRequest.getResponse(); - if (once.compareAndSet(false, true)) { - HttpFields trailers = HttpFields.build().put(trailerName, trailerValue); - jettyResponse.setTrailers(() -> trailers); + Map trailers = Map.of(trailerName, trailerValue); + response.setTrailerFields(() -> trailers); } - if (content != null) response.getOutputStream().write(content); } @@ -228,14 +229,12 @@ public class HttpTrailersTest extends AbstractTest public void testEmptyResponseTrailers(Transport transport) throws Exception { init(transport); - scenario.start(new EmptyServerHandler() + scenario.start(new HttpServlet() { @Override - protected void service(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) + protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - HttpFields trailers = HttpFields.build(); - response.setTrailerFields(() -> - trailers.stream().collect(Collectors.toMap(HttpField::getName, HttpField::getValue))); + response.setTrailerFields(Map::of); } }); @@ -270,15 +269,12 @@ public class HttpTrailersTest extends AbstractTest String trailerName = "Trailer"; String trailerValue = "value"; init(transport); - scenario.start(new EmptyServerHandler() + scenario.start(new HttpServlet() { @Override - protected void service(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - HttpFields trailers = HttpFields.build().put(trailerName, trailerValue); - response.setTrailerFields(() -> - trailers.stream().collect(Collectors.toMap(HttpField::getName, HttpField::getValue))); - + response.setTrailerFields(() -> Map.of(trailerName, trailerValue)); // Write a large content response.getOutputStream().write(content); } @@ -318,14 +314,12 @@ public class HttpTrailersTest extends AbstractTest public void testResponseResetAlsoResetsTrailers(Transport transport) throws Exception { init(transport); - scenario.start(new EmptyServerHandler() + scenario.start(new HttpServlet() { @Override - protected void service(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - Response jettyResponse = jettyRequest.getResponse(); - HttpFields trailers = HttpFields.build().put("name", "value"); - jettyResponse.setTrailers(() -> trailers); + response.setTrailerFields(() -> Map.of("name", "value")); // Fill some other response field. response.setHeader("name", "value"); response.setStatus(HttpServletResponse.SC_NOT_ACCEPTABLE); diff --git a/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-http-client-transport/src/test/java/org/eclipse/jetty/ee9/http/client/InformationalResponseTest.java b/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-http-client-transport/src/test/java/org/eclipse/jetty/ee9/http/client/InformationalResponseTest.java index 827c5571d8c..ef4d8358530 100644 --- a/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-http-client-transport/src/test/java/org/eclipse/jetty/ee9/http/client/InformationalResponseTest.java +++ b/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-http-client-transport/src/test/java/org/eclipse/jetty/ee9/http/client/InformationalResponseTest.java @@ -11,7 +11,7 @@ // ======================================================================== // -package org.eclipse.jetty.http.client; +package org.eclipse.jetty.ee9.http.client; import java.io.IOException; import java.util.List; @@ -20,7 +20,7 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; -import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServlet; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.eclipse.jetty.client.HttpConversation; @@ -33,13 +33,10 @@ import org.eclipse.jetty.client.util.BufferingResponseListener; import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpHeaderValue; import org.eclipse.jetty.http.HttpStatus; -import org.eclipse.jetty.server.Request; -import org.eclipse.jetty.server.handler.AbstractHandler; import org.junit.jupiter.api.Assumptions; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ArgumentsSource; -import static org.eclipse.jetty.http.client.Transport.FCGI; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.is; @@ -51,7 +48,7 @@ public class InformationalResponseTest extends AbstractTest public void init(Transport transport) throws IOException { // Skip FCGI for now, not much interested in its server-side behavior. - Assumptions.assumeTrue(transport != FCGI); + Assumptions.assumeTrue(transport != Transport.FCGI); setScenario(new TransportScenario(transport)); } @@ -60,12 +57,11 @@ public class InformationalResponseTest extends AbstractTest public void test102Processing(Transport transport) throws Exception { init(transport); - scenario.start(new AbstractHandler() + scenario.start(new HttpServlet() { @Override - public void handle(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + protected void service(HttpServletRequest request, HttpServletResponse response) throws IOException { - jettyRequest.setHandled(true); response.sendError(HttpStatus.PROCESSING_102); response.sendError(HttpStatus.PROCESSING_102); response.setStatus(200); @@ -144,12 +140,11 @@ public class InformationalResponseTest extends AbstractTest public void test103EarlyHint(Transport transport) throws Exception { init(transport); - scenario.start(new AbstractHandler() + scenario.start(new HttpServlet() { @Override - public void handle(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + protected void service(HttpServletRequest request, HttpServletResponse response) throws IOException { - jettyRequest.setHandled(true); response.setHeader("Hint", "one"); response.sendError(HttpStatus.EARLY_HINT_103); response.setHeader("Hint", "two"); diff --git a/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-http-client-transport/src/test/java/org/eclipse/jetty/ee9/http/client/RequestReaderTest.java b/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-http-client-transport/src/test/java/org/eclipse/jetty/ee9/http/client/RequestReaderTest.java index 370f2094cd0..0c2acb5babf 100644 --- a/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-http-client-transport/src/test/java/org/eclipse/jetty/ee9/http/client/RequestReaderTest.java +++ b/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-http-client-transport/src/test/java/org/eclipse/jetty/ee9/http/client/RequestReaderTest.java @@ -17,13 +17,13 @@ import java.io.BufferedReader; import java.io.IOException; import java.util.concurrent.TimeUnit; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServlet; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.eclipse.jetty.client.api.ContentResponse; import org.eclipse.jetty.client.util.BytesRequestContent; import org.eclipse.jetty.http.HttpStatus; -import org.eclipse.jetty.server.Request; -import org.eclipse.jetty.server.handler.AbstractHandler; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ArgumentsSource; @@ -43,10 +43,10 @@ public class RequestReaderTest extends AbstractTest public void testRecyclingWhenUsingReader(Transport transport) throws Exception { init(transport); - scenario.start(new AbstractHandler() + scenario.start(new HttpServlet() { @Override - public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // Must be a Reader and not an InputStream. BufferedReader br = request.getReader(); @@ -58,9 +58,8 @@ public class RequestReaderTest extends AbstractTest } // Paranoid check. assertThat(br.read(), is(-1)); - baseRequest.setHandled(true); } - }, client -> {}); + }); ContentResponse response1 = scenario.client.newRequest(scenario.newURI()) .method("POST") diff --git a/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-http-client-transport/src/test/java/org/eclipse/jetty/ee9/http/client/ServerTimeoutsTest.java b/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-http-client-transport/src/test/java/org/eclipse/jetty/ee9/http/client/ServerTimeoutsTest.java index 01aaf5a8d47..d9bec29f7f4 100644 --- a/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-http-client-transport/src/test/java/org/eclipse/jetty/ee9/http/client/ServerTimeoutsTest.java +++ b/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-http-client-transport/src/test/java/org/eclipse/jetty/ee9/http/client/ServerTimeoutsTest.java @@ -42,9 +42,6 @@ import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.http2.FlowControlStrategy; import org.eclipse.jetty.http2.client.transport.HttpClientTransportOverHTTP2; import org.eclipse.jetty.logging.StacklessLogging; -import org.eclipse.jetty.server.Handler; -import org.eclipse.jetty.server.Request; -import org.eclipse.jetty.server.handler.AbstractHandler; import org.eclipse.jetty.server.internal.HttpChannelState; import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.IO; @@ -106,10 +103,10 @@ public class ServerTimeoutsTest extends AbstractTest private void testBlockingReadWithDelayedFirstContentIdleTimeoutFires(TransportScenario scenario, boolean delayDispatch) throws Exception { - testReadWithDelayedFirstContentIdleTimeoutFires(scenario, new EmptyServerHandler() + testReadWithDelayedFirstContentIdleTimeoutFires(scenario, new HttpServlet() { @Override - protected void service(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + protected void service(HttpServletRequest request, HttpServletResponse response) throws IOException { // The client did not send the content, // idle timeout should result in IOException. @@ -120,10 +117,10 @@ public class ServerTimeoutsTest extends AbstractTest private void testAsyncReadWithDelayedFirstContentIdleTimeoutFires(TransportScenario scenario, boolean delayDispatch) throws Exception { - testReadWithDelayedFirstContentIdleTimeoutFires(scenario, new EmptyServerHandler() + testReadWithDelayedFirstContentIdleTimeoutFires(scenario, new HttpServlet() { @Override - protected void service(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + protected void service(HttpServletRequest request, HttpServletResponse response) throws IOException { AsyncContext asyncContext = request.startAsync(); asyncContext.setTimeout(0); @@ -152,18 +149,18 @@ public class ServerTimeoutsTest extends AbstractTest }, delayDispatch); } - private void testReadWithDelayedFirstContentIdleTimeoutFires(TransportScenario scenario, Handler handler, boolean delayDispatch) throws Exception + private void testReadWithDelayedFirstContentIdleTimeoutFires(TransportScenario scenario, HttpServlet servlet, boolean delayDispatch) throws Exception { scenario.httpConfig.setDelayDispatchUntilContent(delayDispatch); CountDownLatch handlerLatch = new CountDownLatch(1); - scenario.start(new AbstractHandler() + scenario.start(new HttpServlet() { @Override - public void handle(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { try { - handler.handle(target, jettyRequest, request, response); + servlet.service(request, response); } finally { @@ -198,12 +195,11 @@ public class ServerTimeoutsTest extends AbstractTest { init(transport); CountDownLatch handlerLatch = new CountDownLatch(1); - scenario.start(new AbstractHandler() + scenario.start(new HttpServlet() { @Override - public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + protected void service(HttpServletRequest request, HttpServletResponse response) throws IOException { - baseRequest.setHandled(true); AsyncContext asyncContext = request.startAsync(); asyncContext.setTimeout(0); ServletInputStream input = request.getInputStream(); @@ -262,12 +258,11 @@ public class ServerTimeoutsTest extends AbstractTest init(transport); CountDownLatch handlerLatch = new CountDownLatch(1); - scenario.start(new AbstractHandler() + scenario.start(new HttpServlet() { @Override - public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + protected void service(HttpServletRequest request, HttpServletResponse response) throws IOException { - baseRequest.setHandled(true); AsyncContext asyncContext = request.startAsync(); asyncContext.setTimeout(0); ServletOutputStream output = response.getOutputStream(); @@ -330,14 +325,13 @@ public class ServerTimeoutsTest extends AbstractTest scenario.requestLog.clear(); scenario.httpConfig.setMinRequestDataRate(bytesPerSecond); CountDownLatch handlerLatch = new CountDownLatch(1); - scenario.start(new AbstractHandler() + scenario.start(new HttpServlet() { @Override - public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + protected void service(HttpServletRequest request, HttpServletResponse response) throws IOException { try { - baseRequest.setHandled(true); ServletInputStream input = request.getInputStream(); while (true) { @@ -375,7 +369,7 @@ public class ServerTimeoutsTest extends AbstractTest for (int i = 0; i < 3; ++i) { - content.offer(ByteBuffer.allocate(bytesPerSecond / 2)); + content.write(ByteBuffer.allocate(bytesPerSecond / 2), Callback.NOOP); Thread.sleep(2500); } content.close(); @@ -398,12 +392,11 @@ public class ServerTimeoutsTest extends AbstractTest int bytesPerSecond = 20; scenario.httpConfig.setMinRequestDataRate(bytesPerSecond); CountDownLatch handlerLatch = new CountDownLatch(1); - scenario.start(new AbstractHandler() + scenario.start(new HttpServlet() { @Override - public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + protected void service(HttpServletRequest request, HttpServletResponse response) throws IOException { - baseRequest.setHandled(true); ServletInputStream input = request.getInputStream(); while (true) { @@ -427,7 +420,7 @@ public class ServerTimeoutsTest extends AbstractTest for (int i = 0; i < 3; ++i) { - content.offer(ByteBuffer.allocate(bytesPerSecond * 2)); + content.write(ByteBuffer.allocate(bytesPerSecond * 2), Callback.NOOP); Thread.sleep(2500); } content.close(); @@ -445,7 +438,7 @@ public class ServerTimeoutsTest extends AbstractTest long idleTimeout = 3 * httpIdleTimeout; scenario.httpConfig.setIdleTimeout(httpIdleTimeout); CountDownLatch handlerLatch = new CountDownLatch(1); - scenario.start(new BlockingReadHandler(handlerLatch)); + scenario.start(new BlockingReadServlet(handlerLatch)); scenario.setRequestIdleTimeout(idleTimeout); try (StacklessLogging ignore = new StacklessLogging(HttpChannelState.class)) @@ -477,12 +470,11 @@ public class ServerTimeoutsTest extends AbstractTest long idleTimeout = 3 * httpIdleTimeout; scenario.httpConfig.setIdleTimeout(httpIdleTimeout); CountDownLatch handlerLatch = new CountDownLatch(1); - scenario.start(new AbstractHandler() + scenario.start(new HttpServlet() { @Override - public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + protected void service(HttpServletRequest request, HttpServletResponse response) throws IOException { - baseRequest.setHandled(true); AsyncContext asyncContext = request.startAsync(); asyncContext.setTimeout(0); ServletInputStream input = request.getInputStream(); @@ -582,7 +574,7 @@ public class ServerTimeoutsTest extends AbstractTest // Wait for the server application to block reading. Thread.sleep(2 * idleTimeout); - content.offer(ByteBuffer.wrap(data2)); + content.write(ByteBuffer.wrap(data2), Callback.NOOP); content.close(); assertTrue(latch.await(5, TimeUnit.SECONDS)); @@ -610,10 +602,10 @@ public class ServerTimeoutsTest extends AbstractTest int bytesPerSecond = 16 * 1024; scenario.httpConfig.setMinResponseDataRate(bytesPerSecond); CountDownLatch serverLatch = new CountDownLatch(1); - scenario.start(new EmptyServerHandler() + scenario.start(new HttpServlet() { @Override - protected void service(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) + protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { try { @@ -661,19 +653,18 @@ public class ServerTimeoutsTest extends AbstractTest assertTrue(clientLatch.await(15, TimeUnit.SECONDS)); } - private static class BlockingReadHandler extends AbstractHandler + private static class BlockingReadServlet extends HttpServlet { private final CountDownLatch handlerLatch; - public BlockingReadHandler(CountDownLatch handlerLatch) + public BlockingReadServlet(CountDownLatch handlerLatch) { this.handlerLatch = handlerLatch; } @Override - public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - baseRequest.setHandled(true); ServletInputStream input = request.getInputStream(); assertEquals(0, input.read()); try diff --git a/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-http-client-transport/src/test/java/org/eclipse/jetty/ee9/http/client/TransportScenario.java b/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-http-client-transport/src/test/java/org/eclipse/jetty/ee9/http/client/TransportScenario.java index 21ccc433d74..577d0c8a922 100644 --- a/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-http-client-transport/src/test/java/org/eclipse/jetty/ee9/http/client/TransportScenario.java +++ b/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-http-client-transport/src/test/java/org/eclipse/jetty/ee9/http/client/TransportScenario.java @@ -45,11 +45,9 @@ import org.eclipse.jetty.http3.server.HTTP3ServerConnectionFactory; import org.eclipse.jetty.http3.server.HTTP3ServerConnector; import org.eclipse.jetty.io.ClientConnector; import org.eclipse.jetty.jmx.MBeanContainer; -import org.eclipse.jetty.quic.server.QuicServerConnector; import org.eclipse.jetty.server.AbstractConnector; import org.eclipse.jetty.server.ConnectionFactory; import org.eclipse.jetty.server.Connector; -import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.HostHeaderCustomizer; import org.eclipse.jetty.server.HttpConfiguration; import org.eclipse.jetty.server.HttpConnectionFactory; @@ -80,7 +78,6 @@ public class TransportScenario protected SslContextFactory.Server sslContextFactory; protected Server server; protected Connector connector; - protected ServletContextHandler context; protected String servletPath = "/servlet"; protected HttpClient client; protected Path unixDomainPath; @@ -145,6 +142,7 @@ public class TransportScenario ret.append(getScheme()); ret.append("://localhost"); getServerPort().ifPresent(s -> ret.append(':').append(s)); + ret.append(servletPath); return ret.toString(); } @@ -278,31 +276,6 @@ public class TransportScenario } } - public void setMaxRequestsPerConnection(int maxRequestsPerConnection) - { - AbstractHTTP2ServerConnectionFactory h2 = connector.getConnectionFactory(AbstractHTTP2ServerConnectionFactory.class); - if (h2 != null) - { - h2.setMaxConcurrentStreams(maxRequestsPerConnection); - } - else - { - if (connector instanceof QuicServerConnector) - ((QuicServerConnector)connector).getQuicConfiguration().setMaxBidirectionalRemoteStreams(maxRequestsPerConnection); - } - } - - public void start(Handler handler) throws Exception - { - start(handler, null); - } - - public void start(Handler handler, Consumer config) throws Exception - { - startServer(handler); - startClient(config); - } - public void start(HttpServlet servlet) throws Exception { startServer(servlet); @@ -334,21 +307,21 @@ public class TransportScenario public void startServer(HttpServlet servlet) throws Exception { - context = new ServletContextHandler(); + prepareServer(servlet); + server.start(); + } + + protected void prepareServer(HttpServlet servlet) + { + ServletContextHandler context = new ServletContextHandler(); context.setContextPath("/"); ServletHolder holder = new ServletHolder(servlet); holder.setAsyncSupported(true); context.addServlet(holder, servletPath); - startServer(context); + prepareServer(context); } - public void startServer(Handler handler) throws Exception - { - prepareServer(handler); - server.start(); - } - - protected void prepareServer(Handler handler) + protected void prepareServer(ServletContextHandler handler) { sslContextFactory = newServerSslContextFactory(); QueuedThreadPool serverThreads = new QueuedThreadPool(); @@ -360,10 +333,13 @@ public class TransportScenario connector = newServerConnector(server); server.addConnector(connector); server.setRequestLog((request, response) -> - { - int status = response.getCommittedMetaData().getStatus(); - requestLog.offer(String.format("%s %s %s %03d", request.getMethod(), request.getRequestURI(), request.getProtocol(), status)); - }); + requestLog.offer(String.format("%s %s %s %03d", + request.getMethod(), + request.getHttpURI(), + request.getConnectionMetaData().getProtocol(), + response.getStatus()) + ) + ); server.setHandler(handler); } diff --git a/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-http-client-transport/src/test/java/org/eclipse/jetty/ee9/http/client/VirtualThreadsTest.java b/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-http-client-transport/src/test/java/org/eclipse/jetty/ee9/http/client/VirtualThreadsTest.java index f9f1cb619d5..515cc7862a7 100644 --- a/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-http-client-transport/src/test/java/org/eclipse/jetty/ee9/http/client/VirtualThreadsTest.java +++ b/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-http-client-transport/src/test/java/org/eclipse/jetty/ee9/http/client/VirtualThreadsTest.java @@ -20,17 +20,16 @@ import java.util.concurrent.atomic.AtomicInteger; import jakarta.servlet.AsyncContext; import jakarta.servlet.ReadListener; -import jakarta.servlet.ServletException; import jakarta.servlet.ServletInputStream; import jakarta.servlet.ServletOutputStream; import jakarta.servlet.WriteListener; +import jakarta.servlet.http.HttpServlet; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.eclipse.jetty.client.api.ContentResponse; import org.eclipse.jetty.client.util.StringRequestContent; import org.eclipse.jetty.http.HttpMethod; import org.eclipse.jetty.http.HttpStatus; -import org.eclipse.jetty.server.Request; import org.eclipse.jetty.util.VirtualThreads; import org.eclipse.jetty.util.thread.ThreadPool; import org.junit.jupiter.api.Assumptions; @@ -59,10 +58,10 @@ public class VirtualThreadsTest extends AbstractTest Assumptions.assumeTrue(transport != Transport.FCGI); init(transport); - scenario.prepareServer(new EmptyServerHandler() + scenario.prepareServer(new HttpServlet() { @Override - protected void service(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + protected void service(HttpServletRequest request, HttpServletResponse response) { if (!VirtualThreads.isVirtualThread()) response.setStatus(HttpStatus.NOT_IMPLEMENTED_501); @@ -90,10 +89,10 @@ public class VirtualThreadsTest extends AbstractTest init(transport); byte[] data = new byte[128 * 1024 * 1024]; - scenario.prepareServer(new EmptyServerHandler() + scenario.prepareServer(new HttpServlet() { @Override - protected void service(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + protected void service(HttpServletRequest request, HttpServletResponse response) throws IOException { if (!VirtualThreads.isVirtualThread()) response.setStatus(HttpStatus.NOT_IMPLEMENTED_501);