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());
}
});
}
};
}
};