从此

Jetty Web Server、HTTP/3

综合/最新

Java编程专题Nginx Jetty HTTP C/SJakarta EEAndroid应用开发JDK源码Gradle构建Spring
原则 - 首选内存用量小的Quarkus(GraalVM),次选嵌入式Jetty、Open Liberty InstantOn、Open Liberty非快速启动版。

通用

HTTP/3:
  注意 - 因 *.app 等域名属于 FORCE_HTTPS,若通过 http://... 跳转至 https:// 则报 ERR_SSL_PROTOCOL_ERROR。
  因为 HTTP/3 在 UDP 端口,浏览器需要个引导 - Alt-Svc 头、HTTPS DNS 记录 和 Chrome 参数:
    逗号分组/分号分项: Alt-Svc: h3-25=":443"; ma=3600, h2=":443"; ma=3600
    SvcParams 不分组/alpn 会择其一: alpn="h3,h2" ipv4hint=104.16.123.96,104.16.124.96 ipv6hint=2606:4700::6810:7b60,2606:4700::6810:7c60
      或另加一项/先看Priority优先数值,后看alpn协议新旧: alpn="h2" ipv4hint=2.2.2.2 ipv6hint=2606::7c60 port=8443
    Chrome 强制某域名走 HTTP/3: --origin-to-force-quic-on=example.com:443

Jetty

TCP + UDP 并存:
  var server = new Server();
  var tcpConn = new ServerConnector(server, scf, new ALPNServerConnectionFactory("h2"), new HTTP2ServerConnectionFactory(hc));
  server.addConnector(tcpConn);
  var h3 = new QuicheServerConnector(server, scf, h3config, http3);
  server.addConnector(h3); // 若想跟 tcpConn 用同一端口,必须监听 onOpen 回调,且晚于 tcpConn 添加。
  tcpConn.addEventListener(new NetworkConnector.Listener() {
    @Override public void onOpen(NetworkConnector nc) { h3.setPort(nc.getLocalPort()); }
  });
  server.start();

ServerConnector 入参 acceptors, selectors 默认值为内部决定数值的 -1,为了避免创建太多处理连接,故写死到1。

HTTP/3 必须依赖库:
    implementation(platform("org.eclipse.jetty:jetty-bom:12.1.9")) // 未包含 org.eclipse.jetty.ee11 等库。
    implementation("org.eclipse.jetty.http3:jetty-http3-server")
    implementation("org.eclipse.jetty.quic:jetty-quic-quiche-server")
    implementation("org.eclipse.jetty.quic:jetty-quic-quiche-jna") // 不加则报 no quiche binding implementation found
    implementation("org.slf4j:slf4j-simple:2.0.17") // 支持指定日志级别 -Dorg.slf4j.simpleLogger.defaultLogLevel=debug
    注意 - Windows 上 PemExporter 报 Unable to set Posix file permissions!暂只能跑在 Linux 上。

    最小响应/避免卡住超时:
        var sessionListener = new Session.Server.Listener() {
            @Override public Stream.Server.Listener onRequest(Session.Server ss, HeadersFrame hf) {
                return new Stream.Server.Listener() {
                    @Override public void onRequest(Stream.Server ss, HeadersFrame hf) {
                        var resp = new MetaData.Response(HttpStatus.NO_CONTENT_204, null, HttpVersion.HTTP_3, HttpFields.EMPTY);
                        ss.respond(new HeadersFrame(resp, true), Promise.Invocable.noop());
                    }
                };
            }
        };

其他