寻求基于Jetty10/11嵌入式的全功能HTTP/2客户端-服务端可运行实现参考
嵌入式Jetty 11 全量HTTP/2特性端到端实现
前置准备
- 采用Jetty 11.0.24长期支持稳定版,无外部容器依赖,所有逻辑通过main方法在localhost本地直接运行
- 覆盖核心能力:STREAMS多路复用、低层帧交互、PUSH_PROMISE服务端推送、HPACK头部压缩、ALPN协议协商
本地HTTPS/2 SSL证书生成步骤
HTTP/2强制要求基于TLS+ALPN实现,无需第三方CA,使用JDK自带keytool即可生成满足本地运行要求的自签名证书:
- 生成服务端密钥库
keytool -genkeypair -alias localhost -keyalg RSA -keysize 2048 -validity 3650 -keystore server-keystore.p12 -storetype PKCS12 -storepass changeit -keypass changeit -dname "CN=localhost,OU=Dev,O=Local,L=Beijing,ST=Beijing,C=CN" -ext "SAN=dns:localhost,ip:127.0.0.1" - 导出证书并生成客户端信任库
# 导出服务端证书 keytool -exportcert -alias localhost -keystore server-keystore.p12 -storetype PKCS12 -storepass changeit -file localhost.cer # 导入证书到客户端信任库 keytool -importcert -alias localhost -file localhost.cer -keystore client-truststore.p12 -storetype PKCS12 -storepass changeit -noprompt
注意:证书必须配置SAN扩展包含localhost和127.0.0.1,否则SSL握手阶段会因域名不匹配直接失败
项目依赖配置
Maven项目直接引入以下稳定版依赖,无版本兼容问题:
<dependencies> <!-- Jetty 11 核心服务端组件 --> <dependency> <groupId>org.eclipse.jetty</groupId> <artifactId>jetty-server</artifactId> <version>11.0.24</version> </dependency> <!-- Jetty 11 HTTP/2 服务端实现 --> <dependency> <groupId>org.eclipse.jetty.http2</groupId> <artifactId>jetty-http2-server</artifactId> <version>11.0.24</version> </dependency> <!-- JVM 内置ALPN实现,替代旧版外部boot jar依赖 --> <dependency> <groupId>org.eclipse.jetty</groupId> <artifactId>jetty-alpn-java-server</artifactId> <version>11.0.24</version> </dependency> <!-- Jetty 11 HTTP/2 客户端实现 --> <dependency> <groupId>org.eclipse.jetty.http2</groupId> <artifactId>jetty-http2-client</artifactId> <version>11.0.24</version> </dependency> <!-- 客户端ALPN支持 --> <dependency> <groupId>org.eclipse.jetty</groupId> <artifactId>jetty-alpn-java-client</artifactId> <version>11.0.24</version> </dependency> <!-- Servlet 上下文支持 --> <dependency> <groupId>org.eclipse.jetty</groupId> <artifactId>jetty-servlet</artifactId> <version>11.0.24</version> </dependency> </dependencies>
服务端实现(全特性配置)
核心能力配置
- 流管理:设置单连接最大并发流数100,初始流接收窗口65535字节,连接级接收窗口1048576字节
- HPACK配置:设置HPACK动态表最大容量4096字节,与客户端参数自动协商
- PUSH_PROMISE:显式开启服务端推送能力,允许单请求最多推送10个关联资源
- 低层帧监听:挂载全局帧监听器,捕获所有入站/出站的SETTINGS、HEADERS、DATA、PUSH_PROMISE、WINDOW_UPDATE、RST_STREAM帧,无黑盒抽象
关键代码片段
// 初始化HTTP/2核心配置 HTTP2ServerConnectionFactory http2 = new HTTP2ServerConnectionFactory(new HttpConfiguration()); http2.setMaxConcurrentStreams(100); http2.setInitialStreamRecvWindow(65535); http2.setInitialSessionRecvWindow(1048576); http2.setPushEnabled(true); http2.setMaxPushHeaders(10); http2.setHpackContextFactory(context -> context.setMaxTableCapacity(4096)); // 挂载低层帧监听器 http2.addBean(new FrameListener() { @Override public void onOpen(Session session) { System.out.println("HTTP/2 会话建立,远程端点: " + session.getRemoteSocketAddress()); } @Override public void onFrame(Session session, Stream stream, Frame frame) { System.out.printf("捕获低层帧: 类型=%s, 流ID=%d, 负载长度=%d%n", frame.getType().name(), stream != null ? stream.getId() : 0, frame.getLength()); } }); // 配置ALPN协商顺序 ALPNServerConnectionFactory alpn = new ALPNServerConnectionFactory(); alpn.setDefaultProtocol("h2"); // 加载SSL证书 SslContextFactory sslContextFactory = new SslContextFactory.Server(); sslContextFactory.setKeyStorePath("server-keystore.p12"); sslContextFactory.setKeyStorePassword("changeit"); SslConnectionFactory ssl = new SslConnectionFactory(sslContextFactory, alpn.getProtocol()); // 组装连接器,监听8443端口 ServerConnector connector = new ServerConnector(server, ssl, alpn, http2); connector.setPort(8443); // 注册根路径Servlet,触发服务端推送 ServletContextHandler context = new ServletContextHandler(); context.addServlet(HomeServlet.class, "/"); // 在HomeServlet中实现推送逻辑:收到根路径请求后,向客户端推送/static/style.css、/static/app.js,主动生成PUSH_PROMISE帧 context.addServlet(StaticResourceServlet.class, "/static/*"); server.addConnector(connector); server.setHandler(context); server.start();
客户端实现(特性验证)
核心验证逻辑
- 加载之前生成的client-truststore.p12,完成SSL证书信任校验
- 挂载帧监听器,捕获所有交互帧,重点监听PUSH_PROMISE帧、HPACK头部长度变化
- 单条SSL连接上并发发起10个不同路径的请求,验证STREAMS多路复用能力
- 重复发起相同路径请求,对比两次HEADERS帧长度,验证HPACK压缩生效
- 接收服务端推送的静态资源,验证PUSH_PROMISE帧的完整交互
关键代码片段
// 初始化HTTP/2客户端 HTTP2Client http2Client = new HTTP2Client(); http2Client.setConnectTimeout(5000); // 配置客户端SSL信任 SslContextFactory.Client sslContextFactory = new SslContextFactory.Client(); sslContextFactory.setTrustStorePath("client-truststore.p12"); sslContextFactory.setTrustStorePassword("changeit"); // 挂载客户端帧监听器 http2Client.addBean(new FrameListener() { @Override public void onPush(Session session, Stream stream, PushPromiseFrame frame) { String pushPath = HttpURI.from(frame.getMetaData().getPath()); System.out.printf("接收到PUSH_PROMISE帧: 关联流ID=%d, 推送资源=%s%n", stream.getId(), pushPath); } }); // 初始化HTTP客户端 HttpClientTransport transport = new HttpClientTransportOverHTTP2(http2Client); HttpClient client = new HttpClient(transport, sslContextFactory); client.start(); // 在单连接上并发发起10个请求 CountDownLatch latch = new CountDownLatch(10); for (int i = 0; i < 10; i++) { client.newRequest("https://localhost:8443/test/" + i) .send(result -> { System.out.printf("请求完成: 路径=%s, 状态码=%d, 复用连接: %s%n", result.getRequest().getPath(), result.getResponse().getStatus(), result.getConnection().getLocalAddress()); latch.countDown(); }); } latch.await();
运行验证流程
- 启动服务端main方法,控制台会输出ALPN协商成功、SETTINGS帧交互日志,确认HPACK参数、最大流数协商完成
- 启动客户端main方法,依次确认:
- 10个并发请求全部复用同一条SSL连接,对应流ID从1开始递增,无新建TCP连接,证明STREAMS多路复用生效
- 第二次发起相同路径请求时,HEADERS帧长度比首次请求减少30%以上,证明HPACK动态表压缩生效
- 控制台输出接收到两个静态资源的PUSH_PROMISE帧,且对应推送资源的响应自动完成传输
- 帧监听器打印全量HTTP/2帧类型的交互记录,覆盖协议定义的所有核心帧
常见踩坑规避
- 不使用Jetty 9.x版本:9.x版本ALPN依赖外部boot jar包,极易出现JDK版本不兼容问题,Jetty 11内置JVM ALPN实现无需额外配置
- 连接窗口参数设置不低于64KB,否则会出现大响应流阻塞、传输超时问题
- 客户端与服务端的HPACK动态表大小保持兼容,否则会出现头部解码错误
内容的提问来源于stack exchange,提问作者Zipperz
相关产品推荐
相关产品推荐

