From 1fc786b404031e2cf784d8d188c5d26191632ab0 Mon Sep 17 00:00:00 2001 From: JingkaiTang Date: Mon, 2 Nov 2026 17:05:43 +0800 Subject: [PATCH 1/2] transport: support Jetty 12 with Spring Boot BOM precedence Fixes #152 --- .github/workflows/ci.yml | 4 + .../trpc-dependencies-bom/pom.xml | 84 ++-------------- .../http/server/DefaultHttpExecutorTest.java | 10 +- .../trpc-spring-cloud-gateway/pom.xml | 6 +- trpc-transport/trpc-transport-http/pom.xml | 11 +-- .../http/support/jetty/JettyHttpServer.java | 11 ++- .../src/test/it/boot-bom-first/README.md | 52 ++++++++++ .../src/test/it/boot-bom-first/pom.xml | 97 +++++++++++++++++++ .../src/test/it/boot-bom-first/run.py | 87 +++++++++++++++++ .../transport/http/JettyHttp2ServerTest.java | 2 +- .../transport/http/JettyHttp2cServerTest.java | 2 +- .../transport/http/JettyHttpsServerTest.java | 2 +- .../trpc/transport/http/JettyServerTest.java | 59 ++++++++++- 13 files changed, 325 insertions(+), 102 deletions(-) create mode 100644 trpc-transport/trpc-transport-http/src/test/it/boot-bom-first/README.md create mode 100644 trpc-transport/trpc-transport-http/src/test/it/boot-bom-first/pom.xml create mode 100644 trpc-transport/trpc-transport-http/src/test/it/boot-bom-first/run.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 82be6786b..445a41707 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 BOM first + run: python3 trpc-transport/trpc-transport-http/src/test/it/boot-bom-first/run.py 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 BOM first + run: python3 trpc-transport/trpc-transport-http/src/test/it/boot-bom-first/run.py 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..f36efab38 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.1.14 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/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..05379070f --- /dev/null +++ b/trpc-transport/trpc-transport-http/src/test/it/boot-bom-first/README.md @@ -0,0 +1,52 @@ +# 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'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 CI jobs run the compatibility matrix after +installing the reactor artifacts: + +```sh +python3 trpc-transport/trpc-transport-http/src/test/it/boot-bom-first/run.py +``` + +The runner reads the current `springboot.version` from tRPC's dependency BOM and +Maven Central's release metadata. It selects every published stable patch of +that baseline's minor line and all later stable versions, plus the latest patch +of the two preceding minor lines within the same major. For the 3.5.0 baseline, +this covers every 3.5.x patch and +later stable releases, with 3.4.x and 3.3.x as backward compatibility samples. +Milestones, release candidates and snapshots are excluded. Boot 3.1 and Jetty 11 +are outside this support range; their incompatibility is not a release blocker. +New stable versions are picked up on subsequent CI runs, rather than assumed +compatible in advance. + +Jetty core and EE10 BOMs default to the 12.1 line. Boot 3.5's BOM manages both +core and EE10 artifacts, selecting its aligned 12.0.x versions. Boot 4 manages +EE11 instead of EE10, so tRPC must provide a 12.1-compatible EE10 default to +avoid mixing EE10 12.0 with a 12.1 core. The Servlet-based tRPC executor contract +is unchanged. These tests cover the HTTP transport under each BOM, not the full +Spring Boot starter, application auto-configuration or gateway application. + +Each version runs in a separate Maven invocation. Results and dependency trees +are saved under the repository's `target/jetty-boot-compatibility/` directory. +The normal reactor `test` command does not invoke this fixture. Additional +arguments to `run.py` are passed to Maven, so isolated settings and repository +options can be reused. For a single version, use the standalone POM command +above with `-Dspring-boot.version=...`. 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..e25a5e836 --- /dev/null +++ b/trpc-transport/trpc-transport-http/src/test/it/boot-bom-first/pom.xml @@ -0,0 +1,97 @@ + + + 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 + + + + diff --git a/trpc-transport/trpc-transport-http/src/test/it/boot-bom-first/run.py b/trpc-transport/trpc-transport-http/src/test/it/boot-bom-first/run.py new file mode 100644 index 000000000..b1247e06f --- /dev/null +++ b/trpc-transport/trpc-transport-http/src/test/it/boot-bom-first/run.py @@ -0,0 +1,87 @@ +#!/usr/bin/env python3 +"""Run the HTTP transport contract against the supported Boot BOM range.""" + +import json +from pathlib import Path +import re +import subprocess +import sys +import time +import urllib.request +import xml.etree.ElementTree as ET + + +METADATA = ( + "https://repo.maven.apache.org/maven2/org/springframework/boot/" + "spring-boot-dependencies/maven-metadata.xml" +) + + +def select_versions(metadata, baseline): + versions = sorted({ + tuple(map(int, item.text.split("."))) + for item in metadata.findall("versioning/versions/version") + if item.text and re.fullmatch(r"\d+\.\d+\.\d+", item.text) + }) + if baseline not in versions: + raise ValueError("The framework's Boot baseline is absent from Maven Central") + previous_lines = sorted({ + version[:2] for version in versions + if version[0] == baseline[0] and version[:2] < baseline[:2] + })[-2:] + previous = [max(v for v in versions if v[:2] == line) for line in previous_lines] + supported = previous + [v for v in versions if v[:2] >= baseline[:2]] + return [".".join(map(str, v)) for v in supported] + + +def main(): + fixture = Path(__file__).resolve().parent + repository = fixture.parents[5] + ns = {"m": "http://maven.apache.org/POM/4.0.0"} + bom = ET.parse(repository / "trpc-dependencies/trpc-dependencies-bom/pom.xml") + baseline_text = bom.findtext("m:properties/m:springboot.version", namespaces=ns) + baseline = tuple(map(int, baseline_text.split("."))) + with urllib.request.urlopen(METADATA, timeout=30) as response: + metadata = ET.fromstring(response.read()) + versions = select_versions(metadata, baseline) + output = repository / "target/jetty-boot-compatibility" + output.mkdir(parents=True, exist_ok=True) + (output / "summary.json").write_text("[]\n") + print("HTTP transport Boot BOM versions: " + ", ".join(versions), flush=True) + results = [] + for version in versions: + log = output / (version + ".log") + command = [ + "mvn", "-B", "-ntp", "-f", str(fixture / "pom.xml"), *sys.argv[1:], + "-Dspring-boot.version=" + version, + "org.apache.maven.plugins:maven-dependency-plugin:3.8.1:tree", + "-Dincludes=org.eclipse.jetty*:*,jakarta.servlet:*", "clean", "test", + ] + started = time.time() + with log.open("w") as stream: + process = subprocess.run(command, stdout=stream, stderr=subprocess.STDOUT) + counts = dict.fromkeys(("tests", "failures", "errors", "skipped"), 0) + for report in (fixture / "target/surefire-reports").glob("TEST-*.xml"): + if report.stat().st_mtime < started: + continue + suite = ET.parse(report).getroot() + for key in counts: + counts[key] += int(suite.get(key, "0")) + passed = process.returncode == 0 and counts["tests"] > 0 and not any( + counts[key] for key in ("failures", "errors", "skipped") + ) + text = log.read_text() + jetty = re.search(r"org.eclipse.jetty:jetty-server:jar:([^:]+):", text) + result = {"boot": version, "passed": passed, "exit_code": process.returncode, + "jetty": jetty.group(1) if jetty else None, **counts, "log": str(log)} + results.append(result) + (output / "summary.json").write_text(json.dumps(results, indent=2) + "\n") + print(json.dumps(result), flush=True) + if not passed: + errors = [line for line in text.splitlines() if "[ERROR]" in line or "Caused by:" in line] + print("\n".join(errors[:15]), flush=True) + return 0 if all(result["passed"] for result in results) else 1 + + +if __name__ == "__main__": + sys.exit(main()) 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..42305a713 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 @@ -62,7 +62,7 @@ public class JettyHttp2ServerTest { 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"); 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(); + } + } + } From 3fcf82370b154e6080e6490131980cc1337fe219 Mon Sep 17 00:00:00 2001 From: JingkaiTang Date: Tue, 10 Nov 2026 17:46:24 +0800 Subject: [PATCH 2/2] transport: align Jetty with Spring Boot 3.5.16 --- .github/workflows/ci.yml | 8 +- .../trpc-dependencies-bom/pom.xml | 2 +- .../http/support/jetty/JettyHttp2Server.java | 2 +- .../src/test/it/boot-bom-first/README.md | 35 +------- .../src/test/it/boot-bom-first/pom.xml | 3 + .../src/test/it/boot-bom-first/run.py | 87 ------------------- .../transport/http/JettyHttp2ServerTest.java | 42 +++++++-- 7 files changed, 47 insertions(+), 132 deletions(-) delete mode 100644 trpc-transport/trpc-transport-http/src/test/it/boot-bom-first/run.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 445a41707..1371ca9aa 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -32,8 +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 BOM first - run: python3 trpc-transport/trpc-transport-http/src/test/it/boot-bom-first/run.py + - 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: @@ -50,8 +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 BOM first - run: python3 trpc-transport/trpc-transport-http/src/test/it/boot-bom-first/run.py + - 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 f36efab38..2b26edfc6 100644 --- a/trpc-dependencies/trpc-dependencies-bom/pom.xml +++ b/trpc-dependencies/trpc-dependencies-bom/pom.xml @@ -96,7 +96,7 @@ 4.0.2 2.1.1 2.0.1.Final - 12.1.14 + 12.0.36 2.7 2.4.14 5.12.2 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/test/it/boot-bom-first/README.md b/trpc-transport/trpc-transport-http/src/test/it/boot-bom-first/README.md index 05379070f..38000552c 100644 --- 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 @@ -1,7 +1,7 @@ # 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's BOM precedes +[#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`. @@ -19,34 +19,5 @@ 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 CI jobs run the compatibility matrix after -installing the reactor artifacts: - -```sh -python3 trpc-transport/trpc-transport-http/src/test/it/boot-bom-first/run.py -``` - -The runner reads the current `springboot.version` from tRPC's dependency BOM and -Maven Central's release metadata. It selects every published stable patch of -that baseline's minor line and all later stable versions, plus the latest patch -of the two preceding minor lines within the same major. For the 3.5.0 baseline, -this covers every 3.5.x patch and -later stable releases, with 3.4.x and 3.3.x as backward compatibility samples. -Milestones, release candidates and snapshots are excluded. Boot 3.1 and Jetty 11 -are outside this support range; their incompatibility is not a release blocker. -New stable versions are picked up on subsequent CI runs, rather than assumed -compatible in advance. - -Jetty core and EE10 BOMs default to the 12.1 line. Boot 3.5's BOM manages both -core and EE10 artifacts, selecting its aligned 12.0.x versions. Boot 4 manages -EE11 instead of EE10, so tRPC must provide a 12.1-compatible EE10 default to -avoid mixing EE10 12.0 with a 12.1 core. The Servlet-based tRPC executor contract -is unchanged. These tests cover the HTTP transport under each BOM, not the full -Spring Boot starter, application auto-configuration or gateway application. - -Each version runs in a separate Maven invocation. Results and dependency trees -are saved under the repository's `target/jetty-boot-compatibility/` directory. -The normal reactor `test` command does not invoke this fixture. Additional -arguments to `run.py` are passed to Maven, so isolated settings and repository -options can be reused. For a single version, use the standalone POM command -above with `-Dspring-boot.version=...`. +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 index e25a5e836..36b509497 100644 --- 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 @@ -91,6 +91,9 @@ org.apache.maven.plugins maven-surefire-plugin 3.0.0-M9 + + true + diff --git a/trpc-transport/trpc-transport-http/src/test/it/boot-bom-first/run.py b/trpc-transport/trpc-transport-http/src/test/it/boot-bom-first/run.py deleted file mode 100644 index b1247e06f..000000000 --- a/trpc-transport/trpc-transport-http/src/test/it/boot-bom-first/run.py +++ /dev/null @@ -1,87 +0,0 @@ -#!/usr/bin/env python3 -"""Run the HTTP transport contract against the supported Boot BOM range.""" - -import json -from pathlib import Path -import re -import subprocess -import sys -import time -import urllib.request -import xml.etree.ElementTree as ET - - -METADATA = ( - "https://repo.maven.apache.org/maven2/org/springframework/boot/" - "spring-boot-dependencies/maven-metadata.xml" -) - - -def select_versions(metadata, baseline): - versions = sorted({ - tuple(map(int, item.text.split("."))) - for item in metadata.findall("versioning/versions/version") - if item.text and re.fullmatch(r"\d+\.\d+\.\d+", item.text) - }) - if baseline not in versions: - raise ValueError("The framework's Boot baseline is absent from Maven Central") - previous_lines = sorted({ - version[:2] for version in versions - if version[0] == baseline[0] and version[:2] < baseline[:2] - })[-2:] - previous = [max(v for v in versions if v[:2] == line) for line in previous_lines] - supported = previous + [v for v in versions if v[:2] >= baseline[:2]] - return [".".join(map(str, v)) for v in supported] - - -def main(): - fixture = Path(__file__).resolve().parent - repository = fixture.parents[5] - ns = {"m": "http://maven.apache.org/POM/4.0.0"} - bom = ET.parse(repository / "trpc-dependencies/trpc-dependencies-bom/pom.xml") - baseline_text = bom.findtext("m:properties/m:springboot.version", namespaces=ns) - baseline = tuple(map(int, baseline_text.split("."))) - with urllib.request.urlopen(METADATA, timeout=30) as response: - metadata = ET.fromstring(response.read()) - versions = select_versions(metadata, baseline) - output = repository / "target/jetty-boot-compatibility" - output.mkdir(parents=True, exist_ok=True) - (output / "summary.json").write_text("[]\n") - print("HTTP transport Boot BOM versions: " + ", ".join(versions), flush=True) - results = [] - for version in versions: - log = output / (version + ".log") - command = [ - "mvn", "-B", "-ntp", "-f", str(fixture / "pom.xml"), *sys.argv[1:], - "-Dspring-boot.version=" + version, - "org.apache.maven.plugins:maven-dependency-plugin:3.8.1:tree", - "-Dincludes=org.eclipse.jetty*:*,jakarta.servlet:*", "clean", "test", - ] - started = time.time() - with log.open("w") as stream: - process = subprocess.run(command, stdout=stream, stderr=subprocess.STDOUT) - counts = dict.fromkeys(("tests", "failures", "errors", "skipped"), 0) - for report in (fixture / "target/surefire-reports").glob("TEST-*.xml"): - if report.stat().st_mtime < started: - continue - suite = ET.parse(report).getroot() - for key in counts: - counts[key] += int(suite.get(key, "0")) - passed = process.returncode == 0 and counts["tests"] > 0 and not any( - counts[key] for key in ("failures", "errors", "skipped") - ) - text = log.read_text() - jetty = re.search(r"org.eclipse.jetty:jetty-server:jar:([^:]+):", text) - result = {"boot": version, "passed": passed, "exit_code": process.returncode, - "jetty": jetty.group(1) if jetty else None, **counts, "log": str(log)} - results.append(result) - (output / "summary.json").write_text(json.dumps(results, indent=2) + "\n") - print(json.dumps(result), flush=True) - if not passed: - errors = [line for line in text.splitlines() if "[ERROR]" in line or "Caused by:" in line] - print("\n".join(errors[:15]), flush=True) - return 0 if all(result["passed"] for result in results) else 1 - - -if __name__ == "__main__": - sys.exit(main()) 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 42305a713..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,6 +58,8 @@ public class JettyHttp2ServerTest { private static HttpServer httpServer; + private static SSLContext sslContext; + private static CloseableHttpAsyncClient httpAsyncClient; @BeforeAll @@ -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/");