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(); + } + } + }