从此

Jetty Web Server、HTTP/3、QUIC

综合/最新

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/2 和 HTTP/3 增加了 Stream 概念,处于 Connection 之下,每个 SNI 对应一个活跃连接(连接后交换CID池来避免追踪),每个连接则会根据业务产生多个流。

  因为 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

层次结构:
  Server - Web Server 总控对象。
    Connector - ServerConnector 即按协议、端口进行服务的连接器。

  QuicheSession(ServerQuicheSession) - 即 Connection(单SNI)一对多 Stream 关联对象;主要成员 Map streams ...、QuicheConnection connection ...
    QuicheConnection - 即 HTTP/3 Connection。
    QuicheStream - 即 HTTP/3 Stream。

  ServerQuicheConnection - 存储所有 HTTP/3 连接和会话;ConcurrentMap sessions...;为了增删方便,故 extends QuicheConnection。
    ServerHTTP3Session - Jetty 用会话。
      StreamEndPoint - 即 HTTP/3 Stream;所属连接分为控制用的 ServerHTTP3StreamConnection 和数据通讯的 ControlStreamConnection。

从 Jetty Server 拿取连接器 - server.getBean(QuicheServerConnector.class);
从 Servlet 获取 Jetty Server - ServletContextHandler.getCurrentServletContextHandler().getServer();
从 Servlet 获取 Jetty Request - ServletContextRequest.getServletContextRequest(req).getRequest();
获取 ServerHTTP3StreamConnection - HttpChannel.from(jettyRequest).getConnectionMetaData().getConnection();
从连接获取 QuicheConnectionId - conn.getEndPoint().getStream().getSession().getId(); // chrome://net-export/ 日志 "connection_id":"d59ca572fb786a1d4a63d1e6708aa93846b1b087"
输出当前流(StreamEndPoint)- _SessionContainer.dump();

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.addEventListener(new LifeCycle.Listener() {
        @Override
        public void lifeCycleStarted(LifeCycle lifeCycle) {
            // 此时 Servlet 已全部准备好。
            IO.println("lifeCycleStarted - " + lifeCycle);
        }
    });
  server.start(); // 虽异步但守护(ShutdownMonitor)

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 http3 = new HTTP3ServerConnectionFactory(); // or 更底层但不支持 Servlet 的 new RawHTTP3ServerConnectionFactory(ssl);
        server.addConnector(new QuicheServerConnector(server, scf, serverQuicConfig, http3)); // 只有一个 h3 故无需协商; h3 可用时会向 h2Config 自动增加 AltSvcCustomizer 响应头。

    最小响应/避免卡住超时:
        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()); // 空响应则直接结束流。

                        // 响应内容需分两段:
                        var resp = new MetaData.Response(HttpStatus.OK_200, null, HttpVersion.HTTP_3, HttpFields.EMPTY);
                        ss.respond(new HeadersFrame(resp, false), new Promise.Invocable.NonBlocking<>() {
                            @Override
                            public void succeeded(Stream result) {
                                var buf = StandardCharsets.UTF_8.encode("[]"); // or ByteBuffer.allocate(0)
                                result.data(new DataFrame(buf, true), Promise.Invocable.noop());
                            }
                        });
                    }
                };
            }
        };

其他

Jetty 13+ 预计会支持 WebSocket over HTTP/3。