#!/usr/bin/env bash # HTTP/3 in the JDK HttpClient (JEP 517, final in JDK 26). Regenerates every file in output/. # JDK25=/path JDK26=/path [TRIALS=20] ./run.sh # Needs: python3 with `pip install "hypercorn[h3]"`, openssl, and (for 05/06) root with iptables + ip. # The loss tests change the loopback MTU and add iptables rules; both are undone on exit. set -uo pipefail unset JAVA_TOOL_OPTIONS HERE="$(cd "$(dirname "$0")" && pwd)"; cd "$HERE" JDK25="${JDK25:-/opt/jdks/jdk-25.0.4.1+1}"; JDK26="${JDK26:-/opt/jdks/jdk26}"; TRIALS="${TRIALS:-20}" OUT="$HERE/output"; mkdir -p "$OUT" URL=https://localhost:4433/hello [ -f certs/cert.pem ] || ./server/make-cert.sh SPID=""; PPID_="" start() { python3 server/serve.py "$1" 4433 >/dev/null 2>&1 & SPID=$!; sleep 2; } stop() { [ -n "$SPID" ] && kill -9 "$SPID" 2>/dev/null; wait "$SPID" 2>/dev/null; SPID=""; } cleanup() { stop; [ -n "$PPID_" ] && kill -9 "$PPID_" 2>/dev/null; scripts/loss.sh off 2>/dev/null; } trap cleanup EXIT v() { "$1/bin/java" -version 2>&1 | sed -n 2p; } J25="$JDK25/bin/java"; J26="$JDK26/bin/java" ONLY="${ONLY:-}" # ONLY=07 regenerates just the loss table if [ -z "$ONLY" ]; then # 01 - plain Java 11 code: same source, JDK 25 and 26, server offers HTTP/3. Nothing changes. start h3 { for j in "$JDK25" "$JDK26"; do echo "\$ java src/Basics.java $URL ($(v $j))"; "$j/bin/java" src/Basics.java $URL 2>&1; echo; done echo "# the alt-svc value above is the server advertising h3 on UDP 4433; nobody asked the client to use it." } > "$OUT/01-java11-code-unchanged.txt" # 02 - the opt-in, client level and request level; and the JDK 25 failure { echo "\$ java src/OptIn.java $URL client ($(v $JDK26))"; "$J26" src/OptIn.java $URL client 2>&1 echo; echo "\$ java src/OptIn.java $URL request ($(v $JDK26))"; "$J26" src/OptIn.java $URL request 2>&1 echo; echo "\$ java src/OptIn.java $URL client ($(v $JDK25))"; "$J25" src/OptIn.java $URL client 2>&1 | head -6 echo; echo "\$ first request of a fresh client-level HTTP_3 client, 8 fresh JVMs ($(v $JDK26))" for i in 1 2 3 4 5 6 7 8; do "$J26" src/OptIn.java $URL client 2>&1 | sed -n 1p; done } > "$OUT/02-opt-in.txt" # 03 - the three discovery modes against a server that speaks HTTP/3 (h3 server still running) { echo "\$ java src/Discovery.java $URL (server: h1.1 + h2 on TCP, h3 on UDP, Alt-Svc advertised)"; "$J26" src/Discovery.java $URL 2>&1; } > "$OUT/03-discovery-modes.txt" stop # 04 - fallback: servers that do not speak HTTP/3 at all { start h2 echo "\$ java src/Fallback.java $URL 120 (server: h2 + h1.1 on TCP only; nothing listens on UDP 4433)" "$J26" src/Fallback.java $URL 120 2>&1 echo; echo "\$ java -Djdk.httpclient.http3.maxDirectConnectionTimeout=1 src/Fallback.java $URL 120 (same server)" "$J26" -Djdk.httpclient.http3.maxDirectConnectionTimeout=1 src/Fallback.java $URL 120 2>&1 echo; echo "\$ java -Djdk.httpclient.quic.maxInitialTimeout=3 src/Fallback.java $URL 120 (same server)" "$J26" -Djdk.httpclient.quic.maxInitialTimeout=3 src/Fallback.java $URL 120 2>&1 stop; start h1 echo; echo "\$ java src/Discovery.java $URL (server: HTTP/1.1 only, TCP)" "$J26" src/Discovery.java $URL 2>&1 stop } > "$OUT/04-fallback.txt" # 05 - a firewall that drops all UDP to 4433 (common in offices): the server is h3-capable but unreachable over QUIC start h3; scripts/loss.sh 100 udp { echo "\$ iptables: DROP 100% of UDP to/from port 4433 (TCP untouched); server is the h3 server" echo "\$ java src/Fallback.java $URL 120" "$J26" src/Fallback.java $URL 120 2>&1 } > "$OUT/05-udp-blocked.txt" scripts/loss.sh off; stop # 06 - a proxy: HTTP/3 is not sent through it start h3; python3 server/connect-proxy.py 4480 > "$OUT/.proxy.log" 2>&1 & PPID_=$!; sleep 1 { echo "\$ java src/ViaProxy.java $URL 4480 (client.proxy(...) points at a CONNECT proxy on 4480)" "$J26" src/ViaProxy.java $URL 4480 2>&1 echo; echo "\$ proxy log"; cat "$OUT/.proxy.log" } > "$OUT/06-proxy.txt" kill $PPID_ 2>/dev/null; PPID_=""; rm -f "$OUT/.proxy.log"; stop fi # 07 - HTTP/1.1 vs HTTP/2 vs HTTP/3 under random packet loss (same h3-capable server process for all three) python3 server/serve.py h3 4433 2> "$OUT/.srv.err" >/dev/null & SPID=$!; sleep 2 { echo "# loss = per-packet drop probability, applied in EACH direction, TCP and UDP port 4433, by iptables -m statistic on lo (MTU 1500)." echo "# no added latency (loopback RTT is microseconds). $TRIALS trials per row after one discarded warm-up; $(v $JDK26)" echo "# one = fresh client, 1 download of 1 MiB. many = fresh client, 10 parallel downloads of 100 KiB." for L in 0 1 2 5 10; do scripts/loss.sh $L for w in one many; do for p in h1 h2 h3; do timeout 600 "$J26" src/Bench.java $p $w $TRIALS $L 2>&1 done; done echo done } > "$OUT/07-packet-loss.txt" scripts/loss.sh off; stop { echo "# what the Hypercorn/aioquic server wrote to stderr during 07 (the loss run), exception lines only, counted" grep -E "(Error|Exception)[:(]" "$OUT/.srv.err" | sed "s/^[ |+-]*//" | sort | uniq -c | sort -rn } > "$OUT/08-server-errors.txt"; rm -f "$OUT/.srv.err" echo "wrote $OUT"