Commits
Tarun Sukhani authored 495b1af7a56
refactor: HTTP/3 implicitly activates with HTTPS — drop play.http3.enabled / play.http3.port flags (release 1.12.20)
Mirrors the play.http2.enabled removal: PF-57 added play.http3.enabled
as an opt-in safety net while we built confidence in the QUIC path. With
HTTPS configured, advertising h3 via Alt-Svc is purely additive — clients
that can't use QUIC continue with whatever TCP-side protocol they
already negotiated. There's no scenario where "h3 off" is meaningfully
better than "h3 on with platform-aware degradation."
play.http3.port is similarly theatre: TCP and UDP have separate port
spaces, so TCP:9443 and UDP:9443 coexist on the same port number — the
standard h3 deployment shape per RFC 9114. The flag's only purpose was
to allow operators to put h3 on a different UDP port than HTTPS, which
no real deployment does.
Server.java changes:
- Drop play.http3.enabled read; activate h3 whenever https.port is set.
- Drop play.http3.port read; bind UDP on httpsPort directly.
- On platform-native unavailability (linux-riscv64, JNI link failure):
log WARN + skip h3 init instead of fatal-erroring. HTTPS+h2+h1.1
keep working normally on TCP.
- Publish public static volatile boolean Server.http3BoundOnHttpsPort
set after a successful UDP bind. PlayHandler reads this when deciding
whether to emit Alt-Svc — so platforms without QUIC don't lure clients
into UDP timeouts via a phantom advertisement.
PlayHandler.java changes:
- Drop the static cached altSvcHeaderValue field and the
computeAltSvcHeader static-init read. Compute Alt-Svc per-response
via currentAltSvcHeaderValue() reading Server.http3BoundOnHttpsPort
+ Server.httpsPort. Per-response cost is one boolean check + one
string concat — negligible.
- Per-response computation also handles the class-load timing: PlayHandler
may load before Server.start() finishes its UDP bind, so a static-init
read would always see the default (false).
Other:
- framework/test-src/integration/testapp/conf/application.conf: drop
%test.play.http3.enabled=true and %test.play.http3.port=19443 — h3
now activates implicitly from %test.https.port=19443.
- Http3SslContextFactory.java: error message no longer references the
removed flag.
- Http2FunctionalTest.java: tlsResponsesAdvertiseH3ViaAltSvc comment +
assertion message updated to reflect implicit activation.
- dependencies.yml: netty-codec-http3 comment updated.
- cmd-enable-https.txt: lists all three protocols (h1.1, h2, h3) the
framework activates from a single https.port.
End-to-end verified on a fresh app:
$ play new h3test && cd h3test && play enable-https && play start
Listening for HTTP on port 9000 (...)
Listening for HTTPS on port 9443 (...)
Listening for HTTP/3 on UDP port 9443 ...
$ lsof -i:9443
java ... TCP *:9443 (LISTEN)
java ... UDP *:9443
$ curl -kvs https://localhost:9443/ | grep alt-svc
< alt-svc: h3=":9443"; ma=86400
$ curl -ks --http2 https://localhost:9443/ → status=200 protocol=2
application.conf after enable-https: ONLY has http.port, https.port,
and keystore.* lines. No h3 flags whatsoever.
Integration tests pass:
- Http3FunctionalTest (1/1): QUIC client fetches JSON over h3
- Http2FunctionalTest (3/3): including tlsResponsesAdvertiseH3ViaAltSvc
asserting the cascade still works with implicit activation
User-facing model is now: https.port presence is the single signal.
HTTPS + h2 + h3 all activate; framework gracefully degrades h3 on
unsupported platforms.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>