Skip to content

Commit 45de7cb

Browse files
authored
Migrate integration test server from Jetty to Netty (#2491)
Also refactor routes and endpoints to simplify server bring-up.
1 parent 1df14ed commit 45de7cb

44 files changed

Lines changed: 2553 additions & 1139 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/dependabot.yml

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,6 @@ updates:
88
cooldown:
99
default-days: 12
1010
ignore:
11-
# Jetty 9.x needed for JDK8 compatibility; it still receives security updates. Only used in tests.
12-
- dependency-name: "org.eclipse.jetty:jetty-server"
13-
update-types: ["version-update:semver-major"]
14-
- dependency-name: "org.eclipse.jetty:jetty-servlet"
15-
update-types: ["version-update:semver-major"]
1611
# Et tu, junit? Keep us on 5, as 6 has min JDK17 - https://docs.junit.org/6.0.0-RC3/release-notes/#release-notes-6.0.0-M1
1712
- dependency-name: "org.junit.jupiter:junit-jupiter"
1813
update-types: ["version-update:semver-major"]

CHANGES.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@
1616
* `Connection.Response.streamParser()` and `DataUtil.streamParser(Path, ...)` could fail on small inputs without a declared charset, if the initial 5 KB charset sniff fully consumed the input and closed it before the stream parse began. [#2483](https://github.com/jhy/jsoup/issues/2483)
1717
* In XML mode, doctypes with an internal subset (the `[...]` section inside `<!DOCTYPE ...>` used for entity declarations) now round-trip correctly. The subset is preserved as raw text only; entities are not expanded and external DTDs are not loaded. [#2486](https://github.com/jhy/jsoup/issues/2486)
1818

19+
### Build Changes
20+
* Migrated the integration test server from Jetty to Netty, which actively maintains support for our minimum JDK target (8). [#2491](https://github.com/jhy/jsoup/pull/2491)
21+
1922
## 1.22.1 (2026-Jan-01)
2023

2124
### Improvements

pom.xml

Lines changed: 13 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@
3333

3434
<properties>
3535
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
36-
<jetty.version>9.4.58.v20250814</jetty.version>
3736
</properties>
3837

3938
<build>
@@ -529,26 +528,16 @@
529528
</dependency>
530529

531530
<dependency>
532-
<!-- jetty for webserver integration tests. 9.x is last with Java7 support -->
533-
<groupId>org.eclipse.jetty</groupId>
534-
<artifactId>jetty-server</artifactId>
535-
<version>${jetty.version}</version>
531+
<!-- netty for webserver integration tests -->
532+
<groupId>io.netty</groupId>
533+
<artifactId>netty-codec-http</artifactId>
536534
<scope>test</scope>
537535
</dependency>
538536

539537
<dependency>
540-
<!-- jetty for webserver integration tests -->
541-
<groupId>org.eclipse.jetty</groupId>
542-
<artifactId>jetty-servlet</artifactId>
543-
<version>${jetty.version}</version>
544-
<scope>test</scope>
545-
</dependency>
546-
547-
<dependency>
548-
<!-- jetty proxy, for integration tests -->
549-
<groupId>org.eclipse.jetty</groupId>
550-
<artifactId>jetty-proxy</artifactId>
551-
<version>${jetty.version}</version>
538+
<!-- netty SSL and channel handlers for webserver integration tests -->
539+
<groupId>io.netty</groupId>
540+
<artifactId>netty-handler</artifactId>
552541
<scope>test</scope>
553542
</dependency>
554543

@@ -572,6 +561,13 @@
572561

573562
<dependencyManagement>
574563
<dependencies>
564+
<dependency>
565+
<groupId>io.netty</groupId>
566+
<artifactId>netty-bom</artifactId>
567+
<version>4.2.12.Final</version>
568+
<type>pom</type>
569+
<scope>import</scope>
570+
</dependency>
575571
</dependencies>
576572
</dependencyManagement>
577573

src/test/java/org/jsoup/integration/ConnectIT.java

Lines changed: 44 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,7 @@
33
import org.jsoup.Connection;
44
import org.jsoup.Jsoup;
55
import org.jsoup.helper.DataUtil;
6-
import org.jsoup.integration.servlets.EchoServlet;
7-
import org.jsoup.integration.servlets.FileServlet;
8-
import org.jsoup.integration.servlets.SlowRider;
6+
import org.jsoup.integration.routes.SlowRider;
97
import org.jsoup.internal.SharedConstants;
108
import org.jsoup.nodes.Document;
119
import org.jsoup.nodes.Element;
@@ -21,16 +19,19 @@
2119
import java.nio.charset.StandardCharsets;
2220
import java.util.concurrent.atomic.AtomicBoolean;
2321

22+
import static org.jsoup.integration.TestServer.origin;
23+
import static org.jsoup.integration.TestServer.start;
2424
import static org.junit.jupiter.api.Assertions.*;
2525

2626
/**
27-
* Failsafe integration tests for Connect methods. These take a bit longer to run, so included as Integ, not Unit, tests.
27+
Failsafe integration tests for Connect methods. These take a bit longer to run, so included as Integ, not Unit, tests.
2828
*/
2929
public class ConnectIT {
3030
@BeforeAll
3131
public static void setUp() {
32-
TestServer.start();
33-
System.setProperty(SharedConstants.UseHttpClient, "false"); // use the default UrlConnection. See HttpClientConnectIT for other version
32+
start();
33+
System.setProperty(SharedConstants.UseHttpClient,
34+
"false"); // use the default UrlConnection. See HttpClientConnectIT for other version
3435
}
3536

3637
// Slow Rider tests.
@@ -39,7 +40,7 @@ public void canInterruptBodyStringRead() throws InterruptedException {
3940
final String[] body = new String[1];
4041
Thread runner = new Thread(() -> {
4142
try {
42-
Connection.Response res = Jsoup.connect(SlowRider.Url)
43+
Connection.Response res = Jsoup.connect(origin().slowRider.url())
4344
.timeout(15 * 1000)
4445
.execute();
4546
body[0] = res.body();
@@ -65,7 +66,7 @@ public void canInterruptDocumentRead() throws InterruptedException {
6566
final String[] body = new String[1];
6667
Thread runner = new Thread(() -> {
6768
try {
68-
Connection.Response res = Jsoup.connect(SlowRider.Url)
69+
Connection.Response res = Jsoup.connect(origin().slowRider.url())
6970
.timeout(15 * 1000)
7071
.execute();
7172
body[0] = res.parse().text();
@@ -87,18 +88,20 @@ public void canInterruptDocumentRead() throws InterruptedException {
8788
assertTrue(end - start < 10 * 1000);
8889
}
8990

90-
@Test public void canInterruptThenJoinASpawnedThread() throws InterruptedException {
91+
@Test
92+
public void canInterruptThenJoinASpawnedThread() throws InterruptedException {
9193
// https://github.com/jhy/jsoup/issues/1991
9294
AtomicBoolean ioException = new AtomicBoolean();
9395
Thread runner = new Thread(() -> {
9496
try {
9597
while (!Thread.currentThread().isInterrupted()) {
96-
Document doc = Jsoup.connect(SlowRider.Url)
98+
Document doc = Jsoup.connect(origin().slowRider.url())
9799
.timeout(30000)
98100
.get();
99101
}
100102
} catch (IOException e) {
101-
ioException.set(true); // don't expect to catch, because the outer sleep will complete before this timeout
103+
ioException.set(
104+
true); // don't expect to catch, because the outer sleep will complete before this timeout
102105
}
103106
});
104107

@@ -115,7 +118,7 @@ public void totalTimeout() throws IOException {
115118
long start = System.currentTimeMillis();
116119
boolean threw = false;
117120
try {
118-
Jsoup.connect(SlowRider.Url).timeout(timeout).get();
121+
Jsoup.connect(origin().slowRider.url()).timeout(timeout).get();
119122
} catch (SocketTimeoutException e) {
120123
long end = System.currentTimeMillis();
121124
long took = end - start;
@@ -130,18 +133,19 @@ public void totalTimeout() throws IOException {
130133
@Test
131134
public void slowReadOk() throws IOException {
132135
// make sure that a slow read that is under the request timeout is still OK
133-
Document doc = Jsoup.connect(SlowRider.Url)
136+
Document doc = Jsoup.connect(origin().slowRider.url())
134137
.data(SlowRider.MaxTimeParam, "2000") // the request completes in 2 seconds
135138
.get();
136139

137140
Element h1 = doc.selectFirst("h1");
138141
assertEquals("outatime", h1.text());
139142
}
140143

141-
@Test void readFullyThrowsOnTimeout() throws IOException {
144+
@Test
145+
void readFullyThrowsOnTimeout() throws IOException {
142146
// tests that response.readFully excepts on timeout
143147
boolean caught = false;
144-
Connection.Response res = Jsoup.connect(SlowRider.Url).timeout(3000).execute();
148+
Connection.Response res = Jsoup.connect(origin().slowRider.url()).timeout(3000).execute();
145149
try {
146150
res.readFully();
147151
} catch (IOException e) {
@@ -150,10 +154,11 @@ public void slowReadOk() throws IOException {
150154
assertTrue(caught);
151155
}
152156

153-
@Test void readBodyThrowsOnTimeout() throws IOException {
157+
@Test
158+
void readBodyThrowsOnTimeout() throws IOException {
154159
// tests that response.readBody excepts on timeout
155160
boolean caught = false;
156-
Connection.Response res = Jsoup.connect(SlowRider.Url).timeout(3000).execute();
161+
Connection.Response res = Jsoup.connect(origin().slowRider.url()).timeout(3000).execute();
157162
try {
158163
res.readBody();
159164
} catch (IOException e) {
@@ -162,10 +167,11 @@ public void slowReadOk() throws IOException {
162167
assertTrue(caught);
163168
}
164169

165-
@Test void bodyThrowsUncheckedOnTimeout() throws IOException {
170+
@Test
171+
void bodyThrowsUncheckedOnTimeout() throws IOException {
166172
// tests that response.body unchecked excepts on timeout
167173
boolean caught = false;
168-
Connection.Response res = Jsoup.connect(SlowRider.Url).timeout(3000).execute();
174+
Connection.Response res = Jsoup.connect(origin().slowRider.url()).timeout(3000).execute();
169175
try {
170176
res.body();
171177
} catch (UncheckedIOException e) {
@@ -176,7 +182,7 @@ public void slowReadOk() throws IOException {
176182

177183
@Test
178184
public void infiniteReadSupported() throws IOException {
179-
Document doc = Jsoup.connect(SlowRider.Url)
185+
Document doc = Jsoup.connect(origin().slowRider.url())
180186
.timeout(0)
181187
.data(SlowRider.MaxTimeParam, "2000")
182188
.get();
@@ -185,11 +191,13 @@ public void infiniteReadSupported() throws IOException {
185191
assertEquals("outatime", h1.text());
186192
}
187193

188-
@Test void streamParserUncheckedExceptionOnTimeoutInStream() throws IOException {
194+
@Test
195+
void streamParserUncheckedExceptionOnTimeoutInStream() throws IOException {
189196
boolean caught = false;
190-
try (StreamParser streamParser = Jsoup.connect(SlowRider.Url)
197+
try (StreamParser streamParser = Jsoup.connect(origin().slowRider.url())
191198
.data(SlowRider.MaxTimeParam, "10000")
192-
.data(SlowRider.IntroSizeParam, "8000") // 8K to pass first buffer, or the timeout would occur in execute or streamparser()
199+
.data(SlowRider.IntroSizeParam,
200+
"8000") // 8K to pass first buffer, or the timeout would occur in execute or streamparser()
193201
.timeout(4000) // has a 1000 sleep at the start
194202
.execute()
195203
.streamParser()) {
@@ -209,11 +217,13 @@ public void infiniteReadSupported() throws IOException {
209217
assertTrue(caught);
210218
}
211219

212-
@Test void streamParserCheckedExceptionOnTimeoutInSelect() throws IOException {
220+
@Test
221+
void streamParserCheckedExceptionOnTimeoutInSelect() throws IOException {
213222
boolean caught = false;
214-
try (StreamParser streamParser = Jsoup.connect(SlowRider.Url)
223+
try (StreamParser streamParser = Jsoup.connect(origin().slowRider.url())
215224
.data(SlowRider.MaxTimeParam, "10000")
216-
.data(SlowRider.IntroSizeParam, "8000") // 8K to pass first buffer, or the timeout would occur in execute or streamparser()
225+
.data(SlowRider.IntroSizeParam,
226+
"8000") // 8K to pass first buffer, or the timeout would occur in execute or streamparser()
217227
.timeout(4000) // has a 1000 sleep at the start
218228
.execute()
219229
.streamParser()) {
@@ -238,7 +248,7 @@ public void remainingAfterFirstRead() throws IOException {
238248
int bufferSize = 5 * 1024;
239249
int capSize = 100 * 1024;
240250

241-
String url = FileServlet.urlTo("/htmltests/large.html"); // 280 K
251+
String url = origin().file.url("/htmltests/large.html"); // 280 K
242252

243253
try (BufferedInputStream stream = Jsoup.connect(url).maxBodySize(capSize)
244254
.execute().bodyStream()) {
@@ -274,7 +284,7 @@ public void remainingAfterFirstRead() throws IOException {
274284
public void noLimitAfterFirstRead() throws IOException {
275285
int firstMaxRead = 5 * 1024;
276286

277-
String url = FileServlet.urlTo("/htmltests/large.html"); // 280 K
287+
String url = origin().file.url("/htmltests/large.html"); // 280 K
278288
try (BufferedInputStream stream = Jsoup.connect(url).execute().bodyStream()) {
279289
// simulates parse which does a limited read first
280290
stream.mark(firstMaxRead);
@@ -294,9 +304,10 @@ public void noLimitAfterFirstRead() throws IOException {
294304
}
295305
}
296306

297-
@Test public void bodyStreamConstrainedViaReadFully() throws IOException {
307+
@Test
308+
public void bodyStreamConstrainedViaReadFully() throws IOException {
298309
int cap = 5 * 1024;
299-
String url = FileServlet.urlTo("/htmltests/large.html"); // 280 K
310+
String url = origin().file.url("/htmltests/large.html"); // 280 K
300311
try (BufferedInputStream stream = Jsoup
301312
.connect(url)
302313
.maxBodySize(cap)
@@ -309,9 +320,10 @@ public void noLimitAfterFirstRead() throws IOException {
309320
}
310321
}
311322

312-
@Test public void bodyStreamConstrainedViaBufferUp() throws IOException {
323+
@Test
324+
public void bodyStreamConstrainedViaBufferUp() throws IOException {
313325
int cap = 5 * 1024;
314-
String url = FileServlet.urlTo("/htmltests/large.html"); // 280 K
326+
String url = origin().file.url("/htmltests/large.html"); // 280 K
315327
try (BufferedInputStream stream = Jsoup
316328
.connect(url)
317329
.maxBodySize(cap)

0 commit comments

Comments
 (0)