Add http3 module: HTTP/3 in the Java HTTP Client (JEP 517), opt-in, discovery modes, fallback timings, proxy downgrade and a packet-loss comparison

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01KqJyCidz3ZgRyHABv2GVJh
This commit is contained in:
2026-09-30 20:27:46 +00:00
co-authored by Claude Sonnet 5.5
parent c8ff3acf09
commit fadfd05670
25 changed files with 617 additions and 0 deletions
+11
View File
@@ -0,0 +1,11 @@
$ java src/Basics.java https://localhost:4433/hello (OpenJDK Runtime Environment Temurin-25.0.4.1+1 (build 25.0.4.1+1-LTS))
request 1: HTTP_2 200 alt-svc=h3=":4433"; ma=3600
request 2: HTTP_2 200 alt-svc=h3=":4433"; ma=3600
request 3: HTTP_2 200 alt-svc=h3=":4433"; ma=3600
$ java src/Basics.java https://localhost:4433/hello (OpenJDK Runtime Environment Temurin-26.0.2.1+1 (build 26.0.2.1+1))
request 1: HTTP_2 200 alt-svc=h3=":4433"; ma=3600
request 2: HTTP_2 200 alt-svc=h3=":4433"; ma=3600
request 3: HTTP_2 200 alt-svc=h3=":4433"; ma=3600
# the alt-svc value above is the server advertising h3 on UDP 4433; nobody asked the client to use it.
+29
View File
@@ -0,0 +1,29 @@
$ java src/OptIn.java https://localhost:4433/hello client (OpenJDK Runtime Environment Temurin-26.0.2.1+1 (build 26.0.2.1+1))
client-level HTTP_3, request 1: HTTP_2
client-level HTTP_3, request 2: HTTP_3
client-level HTTP_3, request 3: HTTP_3
client-level HTTP_3, request 4: HTTP_3
$ java src/OptIn.java https://localhost:4433/hello request (OpenJDK Runtime Environment Temurin-26.0.2.1+1 (build 26.0.2.1+1))
request-level HTTP_3, request 1: HTTP_3
request-level HTTP_3, request 2: HTTP_3
request-level HTTP_3, request 3: HTTP_3
request-level HTTP_3, request 4: HTTP_3
$ java src/OptIn.java https://localhost:4433/hello client (OpenJDK Runtime Environment Temurin-25.0.4.1+1 (build 25.0.4.1+1-LTS))
src/OptIn.java:12: error: cannot find symbol
if (where.equals("client")) builder.version(HttpClient.Version.HTTP_3);
^
symbol: variable HTTP_3
location: class Version
src/OptIn.java:15: error: cannot find symbol
$ first request of a fresh client-level HTTP_3 client, 8 fresh JVMs (OpenJDK Runtime Environment Temurin-26.0.2.1+1 (build 26.0.2.1+1))
client-level HTTP_3, request 1: HTTP_2
client-level HTTP_3, request 1: HTTP_2
client-level HTTP_3, request 1: HTTP_2
client-level HTTP_3, request 1: HTTP_2
client-level HTTP_3, request 1: HTTP_2
client-level HTTP_3, request 1: HTTP_2
client-level HTTP_3, request 1: HTTP_2
client-level HTTP_3, request 1: HTTP_2
+4
View File
@@ -0,0 +1,4 @@
$ java src/Discovery.java https://localhost:4433/hello (server: h1.1 + h2 on TCP, h3 on UDP, Alt-Svc advertised)
ANY -> HTTP_2 HTTP_3 HTTP_3 HTTP_3
ALT_SVC -> HTTP_2 HTTP_3 HTTP_3 HTTP_3
HTTP_3_URI_ONLY -> HTTP_3 HTTP_3 HTTP_3 HTTP_3
+19
View File
@@ -0,0 +1,19 @@
$ java src/Fallback.java https://localhost:4433/hello 120 (server: h2 + h1.1 on TCP only; nothing listens on UDP 4433)
request HTTP_3 (ANY) -> HTTP_2 after 3340 ms
client HTTP_3 (ANY) -> HTTP_2 after 31 ms
request HTTP_3_URI_ONLY -> java.net.ConnectException: No response from peer for 30 seconds after 60016 ms
$ java -Djdk.httpclient.http3.maxDirectConnectionTimeout=1 src/Fallback.java https://localhost:4433/hello 120 (same server)
request HTTP_3 (ANY) -> HTTP_2 after 501 ms
client HTTP_3 (ANY) -> HTTP_2 after 45 ms
request HTTP_3_URI_ONLY -> java.net.ConnectException: No response from peer for 30 seconds after 60016 ms
$ java -Djdk.httpclient.quic.maxInitialTimeout=3 src/Fallback.java https://localhost:4433/hello 120 (same server)
request HTTP_3 (ANY) -> HTTP_2 after 3338 ms
client HTTP_3 (ANY) -> HTTP_2 after 39 ms
request HTTP_3_URI_ONLY -> java.net.ConnectException: No response from peer for 3 seconds after 6011 ms
$ java src/Discovery.java https://localhost:4433/hello (server: HTTP/1.1 only, TCP)
ANY -> HTTP_1_1 HTTP_1_1 HTTP_1_1 HTTP_1_1
ALT_SVC -> HTTP_1_1 HTTP_1_1 HTTP_1_1 HTTP_1_1
HTTP_3_URI_ONLY -> ConnectException(No response from peer for 30 seconds) ConnectException(No response from peer for 30 seconds) ConnectException(No response from peer for 30 seconds) ConnectException(No response from peer for 30 seconds)
+5
View File
@@ -0,0 +1,5 @@
$ iptables: DROP 100% of UDP to/from port 4433 (TCP untouched); server is the h3 server
$ java src/Fallback.java https://localhost:4433/hello 120
request HTTP_3 (ANY) -> HTTP_2 after 3397 ms
client HTTP_3 (ANY) -> HTTP_2 after 46 ms
request HTTP_3_URI_ONLY -> java.net.ConnectException: No response from peer for 30 seconds after 60014 ms
+6
View File
@@ -0,0 +1,6 @@
$ java src/ViaProxy.java https://localhost:4433/hello 4480 (client.proxy(...) points at a CONNECT proxy on 4480)
ANY via proxy -> HTTP_2
HTTP_3_URI_ONLY via proxy -> java.net.http.UnsupportedProtocolVersionException: can't use HTTP/3 with proxied or unsecured connection
$ proxy log
proxy saw: CONNECT localhost:4433
+38
View File
@@ -0,0 +1,38 @@
# loss = per-packet drop probability, applied in EACH direction, TCP and UDP port 4433, by iptables -m statistic on lo (MTU 1500).
# no added latency (loopback RTT is microseconds). 20 trials per row after one discarded warm-up; OpenJDK Runtime Environment Temurin-26.0.2.1+1 (build 26.0.2.1+1)
# one = fresh client, 1 download of 1 MiB. many = fresh client, 10 parallel downloads of 100 KiB.
h1 one loss=0 ok=20 failed=0 median=59 p90=68 max=84 (ms)
h2 one loss=0 ok=20 failed=0 median=61 p90=90 max=97 (ms)
h3 one loss=0 ok=20 failed=0 median=129 p90=197 max=246 (ms)
h1 many loss=0 ok=20 failed=0 median=97 p90=114 max=143 (ms)
h2 many loss=0 ok=20 failed=0 median=118 p90=152 max=159 (ms)
h3 many loss=0 ok=20 failed=0 median=135 p90=167 max=214 (ms)
h1 one loss=1 ok=20 failed=0 median=61 p90=74 max=1278 (ms)
h2 one loss=1 ok=20 failed=0 median=57 p90=66 max=79 (ms)
h3 one loss=1 ok=20 failed=0 median=202 p90=273 max=1207 (ms)
h1 many loss=1 ok=20 failed=0 median=111 p90=1044 max=1054 (ms)
h2 many loss=1 ok=20 failed=0 median=160 p90=1039 max=1057 (ms)
h3 many loss=1 ok=20 failed=0 median=176 p90=232 max=454 (ms)
h1 one loss=2 ok=20 failed=0 median=87 p90=281 max=2082 (ms)
h2 one loss=2 ok=20 failed=0 median=57 p90=294 max=1082 (ms)
h3 one loss=2 ok=20 failed=0 median=216 p90=316 max=390 (ms)
h1 many loss=2 ok=20 failed=0 median=249 p90=1058 max=2818 (ms)
h2 many loss=2 ok=20 failed=0 median=282 p90=1056 max=1073 (ms)
h3 many loss=2 ok=20 failed=0 median=238 p90=296 max=305 (ms)
h1 one loss=5 ok=20 failed=0 median=100 p90=1070 max=1689 (ms)
h2 one loss=5 ok=20 failed=0 median=248 p90=1084 max=3057 (ms)
h3 one loss=5 ok=20 failed=0 median=302 p90=500 max=1508 (ms)
h1 many loss=5 ok=20 failed=0 median=1055 p90=2833 max=3496 (ms)
h2 many loss=5 ok=20 failed=0 median=1047 p90=1257 max=1305 (ms)
h3 many loss=5 ok=20 failed=0 median=322 p90=577 max=1330 (ms)
h1 one loss=10 ok=20 failed=0 median=322 p90=1578 max=3475 (ms)
h2 one loss=10 ok=20 failed=0 median=678 p90=2177 max=2742 (ms)
h3 one loss=10 ok=20 failed=0 median=968 p90=1169 max=1292 (ms)
h1 many loss=10 ok=20 failed=0 median=1509 p90=3370 max=6458 (ms)
h2 many loss=10 ok=20 failed=0 median=1478 p90=3370 max=13812 (ms)
h3 many loss=10 ok=20 failed=0 median=885 p90=1088 max=1250 (ms)
+1
View File
@@ -0,0 +1 @@
# what the Hypercorn/aioquic server wrote to stderr during 07 (the loss run), exception lines only, counted