如何让graphql-ws基于HTTP/2使用WebSocket连接?
针对你描述的Traefik反向代理+仅HTTP/2后端的架构中,GraphQL订阅WebSocket无法正常工作的问题,以下是三个方向的具体解决方案:
方向1:配置GraphQL客户端强制复用HTTP/2连接
浏览器的WebSocket是否使用HTTP/2模式,依赖于是否复用已建立的HTTP/2连接。你可以通过调整graphql-ws客户端配置,确保订阅请求复用与普通GraphQL请求相同的HTTP/2连接:
- 对于Apollo Client,配置WebSocket链接时启用连接复用,避免新建独立连接:
import { createWebSocketLink } from '@apollo/client/link/ws'; import { createHttpLink, split } from '@apollo/client'; import { getMainDefinition } from '@apollo/client/utilities'; // 普通HTTP/2请求链接 const httpLink = createHttpLink({ uri: 'https://your-domain.com/graphql' }); // WebSocket链接,启用懒加载关闭+连接复用 const wsLink = createWebSocketLink({ uri: 'wss://your-domain.com/graphql', lazy: false, // 提前建立连接,复用HTTP/2会话 connectionParams: { // 可选:传递认证token等参数 }, }); // 根据操作类型自动选择传输方式 const splitLink = split( ({ query }) => { const definition = getMainDefinition(query); return ( definition.kind === 'OperationDefinition' && definition.operation === 'subscription' ); }, wsLink, httpLink );
- 验证:在浏览器Network面板中,查看WebSocket请求的
Connection列,确认其复用了与普通请求相同的HTTP/2连接ID。
方向2:调整Traefik与后端配置,触发浏览器自动使用HTTP/2 WebSocket
根据Chromium的规则,浏览器自动使用HTTP/2 WebSocket需要服务器支持CONNECT方法转发。你需要调整Traefik路由和后端服务的处理逻辑:
1. Traefik配置(支持CONNECT方法转发)
在Traefik动态配置中添加专门处理CONNECT请求的路由,确保转发到后端的h2c服务:
http: routers: graphql-ws-http2: rule: "Host(`your-domain.com`) && Method(`CONNECT`) && Path(`/graphql`)" service: graphql-backend tls: {} # 保留Traefik的TLS终止 services: graphql-backend: loadBalancer: servers: - url: "h2c://backend:4000" http2: enabled: true # 强制启用HTTP/2转发
2. 后端HTTP/2服务器配置(处理CONNECT请求)
确保后端HTTP/2服务器能够识别CONNECT请求并升级为WebSocket。以Node.js为例:
const http2 = require('http2'); const WebSocket = require('ws'); const server = http2.createServer(); const wss = new WebSocket.Server({ noServer: true }); // 处理HTTP/2的CONNECT请求,升级为WebSocket server.on('CONNECT', (req, socket, head) => { if (req.url === '/graphql') { wss.handleUpgrade(req, socket, head, (ws) => { wss.emit('connection', ws, req); // 后续处理GraphQL订阅逻辑 }); } else { socket.write('HTTP/1.1 404 Not Found\r\n\r\n'); socket.destroy(); } }); // 处理普通GraphQL HTTP/2请求 server.on('stream', (stream, headers) => { // 你的GraphQL请求处理逻辑 }); server.listen(4000);
3. 验证
在Chrome的chrome://net-internals/#http2页面查看连接详情,确认WebSocket连接使用的是HTTP/2协议的CONNECT方法。
方向3:后端实现多协议支持,无需管理TLS
如果不想修改客户端或Traefik的转发规则,可以让后端同时支持h2c(HTTP/2明文)和HTTP/1.1,TLS仍由Traefik管理:
1. 后端多协议服务器配置
创建支持HTTP/1.1和h2c的multi-use服务器,无需配置TLS证书。以Node.js为例:
const http2 = require('http2'); const WebSocket = require('ws'); // 创建同时支持HTTP/1.1和h2c的服务器 const server = http2.createServer({ allowHTTP1: true }); const wss = new WebSocket.Server({ noServer: true }); // 处理HTTP/1.1的WebSocket Upgrade请求 server.on('upgrade', (req, socket, head) => { if (req.url === '/graphql') { wss.handleUpgrade(req, socket, head, (ws) => { wss.emit('connection', ws, req); }); } else { socket.write('HTTP/1.1 404 Not Found\r\n\r\n'); socket.destroy(); } }); // 处理HTTP/2和HTTP/1.1的普通请求 server.on('stream', (stream, headers) => { // 你的GraphQL请求处理逻辑 }); server.listen(4000);
2. Traefik路由配置
配置两个路由,分别转发普通HTTP/2请求和WebSocket Upgrade请求:
http: routers: graphql-http2: rule: "Host(`your-domain.com`) && Path(`/graphql`) && !Header(`Upgrade`, `websocket`)" service: graphql-backend-h2c tls: {} graphql-ws: rule: "Host(`your-domain.com`) && Path(`/graphql`) && Header(`Upgrade`, `websocket`)" service: graphql-backend-http1 tls: {} services: graphql-backend-h2c: loadBalancer: servers: - url: "h2c://backend:4000" graphql-backend-http1: loadBalancer: servers: - url: "http://backend:4000"
这样Traefik会自动区分请求类型,将普通HTTP/2请求转发到h2c后端,WebSocket请求转发到HTTP/1.1后端,后端无需管理TLS证书。
内容的提问来源于stack exchange,提问作者floge07

