diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 82be6786b..1371ca9aa 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -32,6 +32,8 @@ jobs:
cache: maven
- name: Test with Maven
run: mvn clean install -Dmaven.test.skip=true -B -U --file pom.xml
+ - name: Test HTTP transport with Spring Boot 3.5.16 BOM first
+ run: mvn -B -ntp -f trpc-transport/trpc-transport-http/src/test/it/boot-bom-first/pom.xml test
JDK21:
runs-on: ubuntu-latest
steps:
@@ -48,6 +50,8 @@ jobs:
cache: maven
- name: Test with Maven
run: mvn clean install -Dmaven.test.skip=true -B -U --file pom.xml
+ - name: Test HTTP transport with Spring Boot 3.5.16 BOM first
+ run: mvn -B -ntp -f trpc-transport/trpc-transport-http/src/test/it/boot-bom-first/pom.xml test
JDK17-master:
runs-on: ubuntu-latest
steps:
diff --git a/trpc-dependencies/trpc-dependencies-bom/pom.xml b/trpc-dependencies/trpc-dependencies-bom/pom.xml
index 9dca91b50..2b26edfc6 100644
--- a/trpc-dependencies/trpc-dependencies-bom/pom.xml
+++ b/trpc-dependencies/trpc-dependencies-bom/pom.xml
@@ -96,8 +96,7 @@
4.0.2
2.1.1
2.0.1.Final
- 8.1.13.v20181017
- 11.0.25
+ 12.0.36
2.7
2.4.14
5.12.2
@@ -577,85 +576,18 @@
${javax.servlet-api.version}
- jetty-server
- org.eclipse.jetty
- ${jetty.version}
-
-
- jetty-servlet
- org.eclipse.jetty
- ${jetty.version}
-
-
- jetty-io
- org.eclipse.jetty
- ${jetty.version}
-
-
- jetty-util
- org.eclipse.jetty
- ${jetty.version}
-
-
- jetty-util-ajax
- org.eclipse.jetty
- ${jetty.version}
-
-
- jetty-security
- org.eclipse.jetty
- ${jetty.version}
-
-
- jetty-http
- org.eclipse.jetty
- ${jetty.version}
-
-
- http2-server
- org.eclipse.jetty.http2
- ${jetty.version}
-
-
- http2-common
- org.eclipse.jetty.http2
- ${jetty.version}
-
-
- http2-hpack
- org.eclipse.jetty.http2
- ${jetty.version}
-
-
- jetty-alpn-server
- org.eclipse.jetty
- ${jetty.version}
-
-
- jetty-alpn-conscrypt-server
- org.eclipse.jetty
- ${jetty.version}
-
-
- jetty-alpn-java-server
- org.eclipse.jetty
- ${jetty.version}
-
-
- jetty-alpn-openjdk8-server
org.eclipse.jetty
+ jetty-bom
${jetty.version}
+ pom
+ import
- org.eclipse.jetty
- jetty-webapp
+ org.eclipse.jetty.ee10
+ jetty-ee10-bom
${jetty.version}
-
-
- alpn-boot
- org.mortbay.jetty.alpn
- runtime
- ${jetty.alpn.boot.version}
+ pom
+ import
diff --git a/trpc-proto/trpc-proto-http/src/test/java/com/tencent/trpc/proto/http/server/DefaultHttpExecutorTest.java b/trpc-proto/trpc-proto-http/src/test/java/com/tencent/trpc/proto/http/server/DefaultHttpExecutorTest.java
index 6df61e884..5d0a7fe1d 100644
--- a/trpc-proto/trpc-proto-http/src/test/java/com/tencent/trpc/proto/http/server/DefaultHttpExecutorTest.java
+++ b/trpc-proto/trpc-proto-http/src/test/java/com/tencent/trpc/proto/http/server/DefaultHttpExecutorTest.java
@@ -21,10 +21,9 @@
import com.tencent.trpc.core.rpc.def.DefProviderInvoker;
import com.tencent.trpc.core.utils.NetUtils;
import com.tencent.trpc.proto.standard.common.TRPCProtocol;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
import javax.ws.rs.HttpMethod;
-import org.eclipse.jetty.server.HttpChannel;
-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.thread.QueuedThreadPool;
@@ -69,9 +68,8 @@ public void testRegist() {
connector.setAcceptQueueSize(60);
server.addConnector(connector);
- HttpChannel httpChannel = org.mockito.Mockito.mock(HttpChannel.class);
- Request request = org.mockito.Mockito.mock(Request.class);
- Response response = org.mockito.Mockito.mock(Response.class);
+ HttpServletRequest request = org.mockito.Mockito.mock(HttpServletRequest.class);
+ HttpServletResponse response = org.mockito.Mockito.mock(HttpServletResponse.class);
org.mockito.Mockito.when(request.getMethod()).thenReturn(HttpMethod.GET);
org.mockito.Mockito.when(request.getPathInfo()).thenReturn("/trpc.test.rpc.Hello/sayHello");
diff --git a/trpc-spring-support/trpc-spring-cloud-gateway/pom.xml b/trpc-spring-support/trpc-spring-cloud-gateway/pom.xml
index b47d8bbfc..33827dfc5 100644
--- a/trpc-spring-support/trpc-spring-cloud-gateway/pom.xml
+++ b/trpc-spring-support/trpc-spring-cloud-gateway/pom.xml
@@ -45,8 +45,8 @@
trpc-rpc-support
- org.eclipse.jetty
- jetty-webapp
+ org.eclipse.jetty.ee10
+ jetty-ee10-webapp
com.tencent.trpc
@@ -64,4 +64,4 @@
-
\ No newline at end of file
+
diff --git a/trpc-transport/trpc-transport-http/pom.xml b/trpc-transport/trpc-transport-http/pom.xml
index 72c0c34f1..f70fa1b0d 100644
--- a/trpc-transport/trpc-transport-http/pom.xml
+++ b/trpc-transport/trpc-transport-http/pom.xml
@@ -25,22 +25,17 @@
jetty-server
- org.eclipse.jetty
- jetty-servlet
+ org.eclipse.jetty.ee10
+ jetty-ee10-servlet
org.eclipse.jetty.http2
- http2-server
+ jetty-http2-server
org.eclipse.jetty
jetty-alpn-server
-
- org.mortbay.jetty.alpn
- alpn-boot
- runtime
-
org.eclipse.jetty
jetty-alpn-conscrypt-server
diff --git a/trpc-transport/trpc-transport-http/src/main/java/com/tencent/trpc/transport/http/support/jetty/JettyHttp2Server.java b/trpc-transport/trpc-transport-http/src/main/java/com/tencent/trpc/transport/http/support/jetty/JettyHttp2Server.java
index 3f7964fc6..9d0d02852 100644
--- a/trpc-transport/trpc-transport-http/src/main/java/com/tencent/trpc/transport/http/support/jetty/JettyHttp2Server.java
+++ b/trpc-transport/trpc-transport-http/src/main/java/com/tencent/trpc/transport/http/support/jetty/JettyHttp2Server.java
@@ -57,7 +57,7 @@ protected ServerConnector buildServerConnector(Server server, ProtocolConfig con
// 4. Config the connector of h2
ServerConnector http2Connector = new ServerConnector(server, ssl, alpn, h2,
- new HttpConnectionFactory(httpConfig));
+ new HttpConnectionFactory(httpsConfig));
http2Connector.setHost(config.getIp());
http2Connector.setPort(config.getPort());
http2Connector.setAcceptQueueSize(config.getMaxConns());
diff --git a/trpc-transport/trpc-transport-http/src/main/java/com/tencent/trpc/transport/http/support/jetty/JettyHttpServer.java b/trpc-transport/trpc-transport-http/src/main/java/com/tencent/trpc/transport/http/support/jetty/JettyHttpServer.java
index abbc6093b..a0cc63b88 100755
--- a/trpc-transport/trpc-transport-http/src/main/java/com/tencent/trpc/transport/http/support/jetty/JettyHttpServer.java
+++ b/trpc-transport/trpc-transport-http/src/main/java/com/tencent/trpc/transport/http/support/jetty/JettyHttpServer.java
@@ -20,11 +20,11 @@
import com.tencent.trpc.transport.http.ExecutorDispatcher;
import com.tencent.trpc.transport.http.HttpExecutor;
import com.tencent.trpc.transport.http.common.ServletManager;
+import org.eclipse.jetty.ee10.servlet.ServletContextHandler;
+import org.eclipse.jetty.ee10.servlet.ServletHandler;
+import org.eclipse.jetty.ee10.servlet.ServletHolder;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
-import org.eclipse.jetty.servlet.ServletContextHandler;
-import org.eclipse.jetty.servlet.ServletHandler;
-import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
/**
@@ -120,9 +120,10 @@ private ServletContextHandler getServletContextHandler(Server server) {
servletHandler.addServletWithMapping(ExecutorDispatcher.class, "/*");
servletHolder.setAsyncSupported(true);
servletHolder.setInitOrder(1);
- ServletContextHandler context =
- new ServletContextHandler(server, "/", ServletContextHandler.SESSIONS);
+ ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS);
+ context.setContextPath("/");
context.setServletHandler(servletHandler);
+ server.setHandler(context);
return context;
}
diff --git a/trpc-transport/trpc-transport-http/src/test/it/boot-bom-first/README.md b/trpc-transport/trpc-transport-http/src/test/it/boot-bom-first/README.md
new file mode 100644
index 000000000..38000552c
--- /dev/null
+++ b/trpc-transport/trpc-transport-http/src/test/it/boot-bom-first/README.md
@@ -0,0 +1,23 @@
+# Spring Boot BOM compatibility regression
+
+This consumer reproduces the dependency-management order from issue
+[#152](https://github.com/trpc-group/trpc-java/issues/152): Spring Boot 3.5.16's BOM precedes
+tRPC's BOM. It reuses the transport tests against the installed tRPC artifacts,
+covering HTTP/1.1, HTTPS, h2c, h2 with ALPN, request bodies and asynchronous Servlet
+responses. All server sockets bind explicitly to `127.0.0.1`.
+
+Run from the repository root with JDK 17 or 21:
+
+```sh
+mvn -B -ntp -pl trpc-dependencies/trpc-dependencies-bom,trpc-dependencies/trpc-bom,trpc-transport/trpc-transport-http -am -Dmaven.test.skip=true install
+mvn -B -ntp -f trpc-transport/trpc-transport-http/src/test/it/boot-bom-first/pom.xml test
+```
+
+To test the released implementation as a negative control, add
+`-Dtrpc.version=2.0.5` to the second command. With Spring Boot 3.5.16 this mixes
+Jetty 12 core artifacts with Jetty 11 Servlet/HTTP2 artifacts and fails during
+server initialization. The fixed snapshot uses Jetty 12 core and EE10 BOMs,
+EE10 Servlet coordinates, and the renamed HTTP/2 artifacts.
+
+The JDK 17 and JDK 21 Maven install jobs run this consumer regression with Spring Boot
+3.5.16 after installing the tRPC artifacts. Both BOMs manage Jetty 12.0.36.
diff --git a/trpc-transport/trpc-transport-http/src/test/it/boot-bom-first/pom.xml b/trpc-transport/trpc-transport-http/src/test/it/boot-bom-first/pom.xml
new file mode 100644
index 000000000..36b509497
--- /dev/null
+++ b/trpc-transport/trpc-transport-http/src/test/it/boot-bom-first/pom.xml
@@ -0,0 +1,100 @@
+
+
+ 4.0.0
+
+ com.tencent.trpc.tests
+ jetty-boot-bom-first
+ 1.0-SNAPSHOT
+
+
+ 2.0.5-SNAPSHOT
+ 3.5.16
+ 17
+ UTF-8
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-dependencies
+ ${spring-boot.version}
+ pom
+ import
+
+
+ com.tencent.trpc
+ trpc-dependencies-bom
+ ${trpc.version}
+ pom
+ import
+
+
+
+
+
+
+ com.tencent.trpc
+ trpc-transport-http
+ ${trpc.version}
+
+
+ org.apache.httpcomponents.core5
+ httpcore5-h2
+ test
+
+
+ org.apache.httpcomponents.client5
+ httpclient5
+ test
+
+
+ org.apache.httpcomponents
+ httpcore
+ test
+
+
+ org.apache.httpcomponents
+ httpclient
+ test
+
+
+ org.junit.jupiter
+ junit-jupiter
+ test
+
+
+ org.mockito
+ mockito-core
+ test
+
+
+
+
+
+ ${project.basedir}/../../java
+
+
+ ${project.basedir}/../../resources
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 3.11.0
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+ 3.0.0-M9
+
+ true
+
+
+
+
+
diff --git a/trpc-transport/trpc-transport-http/src/test/java/com/tencent/trpc/transport/http/JettyHttp2ServerTest.java b/trpc-transport/trpc-transport-http/src/test/java/com/tencent/trpc/transport/http/JettyHttp2ServerTest.java
index 0b2e39a2c..47cda19e9 100644
--- a/trpc-transport/trpc-transport-http/src/test/java/com/tencent/trpc/transport/http/JettyHttp2ServerTest.java
+++ b/trpc-transport/trpc-transport-http/src/test/java/com/tencent/trpc/transport/http/JettyHttp2ServerTest.java
@@ -34,14 +34,16 @@
import org.apache.hc.client5.http.async.methods.SimpleHttpRequest;
import org.apache.hc.client5.http.async.methods.SimpleHttpRequests;
import org.apache.hc.client5.http.async.methods.SimpleHttpResponse;
+import org.apache.hc.client5.http.config.TlsConfig;
import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient;
import org.apache.hc.client5.http.impl.async.HttpAsyncClients;
import org.apache.hc.client5.http.impl.nio.PoolingAsyncClientConnectionManager;
import org.apache.hc.client5.http.impl.nio.PoolingAsyncClientConnectionManagerBuilder;
-import org.apache.hc.core5.concurrent.FutureCallback;
-import org.apache.hc.core5.http2.HttpVersionPolicy;
import org.apache.hc.client5.http.ssl.ClientTlsStrategyBuilder;
import org.apache.hc.client5.http.ssl.NoopHostnameVerifier;
+import org.apache.hc.core5.concurrent.FutureCallback;
+import org.apache.hc.core5.http.HttpVersion;
+import org.apache.hc.core5.http2.HttpVersionPolicy;
import org.apache.hc.core5.ssl.SSLContexts;
import org.apache.hc.core5.ssl.TrustStrategy;
import org.eclipse.jetty.http.HttpStatus;
@@ -56,13 +58,15 @@ public class JettyHttp2ServerTest {
private static HttpServer httpServer;
+ private static SSLContext sslContext;
+
private static CloseableHttpAsyncClient httpAsyncClient;
@BeforeAll
public static void beforeClass() throws CertificateException, NoSuchAlgorithmException,
KeyStoreException, IOException, KeyManagementException, InterruptedException {
ProtocolConfig protocolConfig = ProtocolConfig.newInstance();
- protocolConfig.setIp("localhost");
+ protocolConfig.setIp("127.0.0.1");
protocolConfig.setPort(18082);
protocolConfig.setProtocol(HTTP2_SCHEME);
protocolConfig.setTransporter("jetty");
@@ -79,6 +83,8 @@ public static void beforeClass() throws CertificateException, NoSuchAlgorithmExc
String pathInfo = request.getPathInfo();
if (("/").equals(pathInfo)) {
response.setStatus(HttpStatus.OK_200);
+ response.setHeader("X-Request-Scheme", request.getScheme());
+ response.setHeader("X-Request-Secure", Boolean.toString(request.isSecure()));
response.flushBuffer();
} else {
response.setStatus(HttpStatus.NOT_FOUND_404);
@@ -98,23 +104,28 @@ public static void beforeClass() throws CertificateException, NoSuchAlgorithmExc
String keyStorePath = String.valueOf(path);
String keyStorePass = "init234";
- final SSLContext sslContext = SSLContexts.custom()
+ sslContext = SSLContexts.custom()
.loadTrustMaterial(new File(keyStorePath), keyStorePass.toCharArray(),
(TrustStrategy) (chain, authType) -> true)
.build();
+ httpAsyncClient = createClient(HttpVersionPolicy.FORCE_HTTP_2);
+ Thread.sleep(1000);
+ }
+
+ private static CloseableHttpAsyncClient createClient(HttpVersionPolicy versionPolicy) {
final PoolingAsyncClientConnectionManager cm = PoolingAsyncClientConnectionManagerBuilder
.create()
+ .setDefaultTlsConfig(TlsConfig.custom().setVersionPolicy(versionPolicy).build())
.setTlsStrategy(ClientTlsStrategyBuilder.create()
.setSslContext(sslContext)
.setHostnameVerifier(NoopHostnameVerifier.INSTANCE)
.build())
.build();
- httpAsyncClient = HttpAsyncClients.custom()
- .setVersionPolicy(HttpVersionPolicy.FORCE_HTTP_2)
+ CloseableHttpAsyncClient client = HttpAsyncClients.custom()
.setConnectionManager(cm)
.build();
- httpAsyncClient.start();
- Thread.sleep(1000);
+ client.start();
+ return client;
}
@AfterAll
@@ -132,6 +143,23 @@ public static void afterClass() {
}
}
+ @Test
+ public void testHttp1FallbackUsesHttpsConfiguration() throws Exception {
+ try (CloseableHttpAsyncClient client = createClient(HttpVersionPolicy.FORCE_HTTP_1)) {
+ SimpleHttpRequest request = SimpleHttpRequests.get("https://localhost:18082/");
+ SimpleHttpResponse response = client.execute(request, null).get(10, TimeUnit.SECONDS);
+ Assertions.assertEquals(HttpVersion.HTTP_1_1, response.getVersion());
+ Assertions.assertEquals(200, response.getCode());
+ Assertions.assertEquals("https", response.getHeader("X-Request-Scheme").getValue());
+ Assertions.assertEquals("true", response.getHeader("X-Request-Secure").getValue());
+
+ SimpleHttpRequest mismatchedHost = SimpleHttpRequests.get("https://localhost:18082/");
+ mismatchedHost.setHeader("Host", "127.0.0.2");
+ SimpleHttpResponse rejected = client.execute(mismatchedHost, null).get(10, TimeUnit.SECONDS);
+ Assertions.assertEquals(400, rejected.getCode());
+ }
+ }
+
@Test
public void testNormalRequestWithHttp2() throws Exception {
SimpleHttpRequest simpleHttpRequest = SimpleHttpRequests.get("https://localhost:18082/");
diff --git a/trpc-transport/trpc-transport-http/src/test/java/com/tencent/trpc/transport/http/JettyHttp2cServerTest.java b/trpc-transport/trpc-transport-http/src/test/java/com/tencent/trpc/transport/http/JettyHttp2cServerTest.java
index af653d798..edfc97b7c 100644
--- a/trpc-transport/trpc-transport-http/src/test/java/com/tencent/trpc/transport/http/JettyHttp2cServerTest.java
+++ b/trpc-transport/trpc-transport-http/src/test/java/com/tencent/trpc/transport/http/JettyHttp2cServerTest.java
@@ -52,7 +52,7 @@ public class JettyHttp2cServerTest {
@BeforeAll
public static void beforeClass() throws InterruptedException {
ProtocolConfig protocolConfig = ProtocolConfig.newInstance();
- protocolConfig.setIp("localhost");
+ protocolConfig.setIp("127.0.0.1");
protocolConfig.setPort(18081);
protocolConfig.setProtocol(HTTP2_SCHEME);
protocolConfig.setTransporter("jetty");
diff --git a/trpc-transport/trpc-transport-http/src/test/java/com/tencent/trpc/transport/http/JettyHttpsServerTest.java b/trpc-transport/trpc-transport-http/src/test/java/com/tencent/trpc/transport/http/JettyHttpsServerTest.java
index 597d40d00..8535b7491 100644
--- a/trpc-transport/trpc-transport-http/src/test/java/com/tencent/trpc/transport/http/JettyHttpsServerTest.java
+++ b/trpc-transport/trpc-transport-http/src/test/java/com/tencent/trpc/transport/http/JettyHttpsServerTest.java
@@ -62,7 +62,7 @@ public class JettyHttpsServerTest {
public static void beforeClass() throws CertificateException, NoSuchAlgorithmException,
KeyStoreException, IOException, KeyManagementException, InterruptedException {
ProtocolConfig protocolConfig = ProtocolConfig.newInstance();
- protocolConfig.setIp("localhost");
+ protocolConfig.setIp("127.0.0.1");
protocolConfig.setPort(18083);
protocolConfig.setProtocol(HTTP_SCHEME);
protocolConfig.setTransporter("jetty");
diff --git a/trpc-transport/trpc-transport-http/src/test/java/com/tencent/trpc/transport/http/JettyServerTest.java b/trpc-transport/trpc-transport-http/src/test/java/com/tencent/trpc/transport/http/JettyServerTest.java
index 3cd114064..c56b6ef86 100644
--- a/trpc-transport/trpc-transport-http/src/test/java/com/tencent/trpc/transport/http/JettyServerTest.java
+++ b/trpc-transport/trpc-transport-http/src/test/java/com/tencent/trpc/transport/http/JettyServerTest.java
@@ -19,8 +19,11 @@
import com.tencent.trpc.core.logger.LoggerFactory;
import com.tencent.trpc.transport.http.common.ServletManager;
import com.tencent.trpc.transport.http.spi.HttpServerFactory;
+import jakarta.servlet.AsyncContext;
+import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;
+import java.nio.charset.StandardCharsets;
import org.eclipse.jetty.http.HttpStatus;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Assertions;
@@ -36,7 +39,7 @@ public class JettyServerTest {
@BeforeAll
public static void beforeClass() {
ProtocolConfig protocolConfig = ProtocolConfig.newInstance();
- protocolConfig.setIp("localhost");
+ protocolConfig.setIp("127.0.0.1");
protocolConfig.setPort(18080);
protocolConfig.setProtocol("http");
protocolConfig.setTransporter("jetty");
@@ -49,6 +52,20 @@ public static void beforeClass() {
String pathInfo = request.getPathInfo();
if ("/".equals(pathInfo)) {
response.setStatus(HttpStatus.OK_200);
+ } else if ("/echo".equals(pathInfo)) {
+ response.getWriter().write(request.getMethod() + ":" + pathInfo + ":"
+ + request.getParameter("id") + ":" + request.getReader().readLine());
+ } else if ("/async".equals(pathInfo)) {
+ AsyncContext asyncContext = request.startAsync();
+ asyncContext.start(() -> {
+ try {
+ asyncContext.getResponse().getWriter().write("async response");
+ } catch (IOException e) {
+ throw new IllegalStateException(e);
+ } finally {
+ asyncContext.complete();
+ }
+ });
} else {
response.sendError(HttpStatus.NOT_FOUND_404);
}
@@ -122,4 +139,44 @@ public void testNotExistRequest() throws Exception {
}
}
+ @Test
+ public void testPostRequestBody() throws Exception {
+ HttpURLConnection connection = (HttpURLConnection)
+ new URL("http://127.0.0.1:18080/echo?id=42").openConnection();
+ try {
+ connection.setRequestMethod("POST");
+ connection.setRequestProperty("Content-Type", "text/plain; charset=UTF-8");
+ connection.setConnectTimeout(1000);
+ connection.setReadTimeout(5000);
+ connection.setDoOutput(true);
+ try (var output = connection.getOutputStream()) {
+ output.write("request body".getBytes(StandardCharsets.UTF_8));
+ }
+ Assertions.assertEquals(200, connection.getResponseCode());
+ try (var input = connection.getInputStream()) {
+ Assertions.assertEquals("POST:/echo:42:request body",
+ new String(input.readAllBytes(), StandardCharsets.UTF_8));
+ }
+ } finally {
+ connection.disconnect();
+ }
+ }
+
+ @Test
+ public void testAsyncRequest() throws Exception {
+ HttpURLConnection connection = (HttpURLConnection)
+ new URL("http://127.0.0.1:18080/async").openConnection();
+ try {
+ connection.setConnectTimeout(1000);
+ connection.setReadTimeout(5000);
+ Assertions.assertEquals(200, connection.getResponseCode());
+ try (var input = connection.getInputStream()) {
+ Assertions.assertEquals("async response",
+ new String(input.readAllBytes(), StandardCharsets.UTF_8));
+ }
+ } finally {
+ connection.disconnect();
+ }
+ }
+
}