Express.js+SPDY实现HTTP/2仅Chrome报net::ERR_HTTP2_PROTOCOL_ERROR问题
问题解答:Chrome下HTTP/2流式传输出现ERR_HTTP2_PROTOCOL_ERROR
1. 为何仅Chrome出现该错误?
Chrome对HTTP/2协议的合规性校验比Firefox、Edge更严格,主要触发场景包括:
- HTTP/2不支持
Connection: keep-alive这类HTTP/1.1专属头,Chrome会直接判定协议错误,而其他浏览器可能兼容忽略这类非合规头。 - 流式传输中
res.write的调用时机或数据格式违反HTTP/2流帧规则,比如流标记关闭后仍写入数据,或数据帧大小超出Chrome限制。 - SPDY库的HTTP/2实现存在兼容性问题,Chrome对HTTP/2最新规范支持更激进,而SPDY作为HTTP/2前身,部分细节不符合标准。
2. 该错误的修复方案是什么?
步骤1:移除HTTP/1.1专属响应头
HTTP/2不需要Connection: keep-alive,直接删除该头:
// 删除此行 // res.setHeader('Connection', 'keep-alive');
同时避免使用任何HTTP/1.1逐跳头(如Proxy-Connection、Keep-Alive)。
步骤2:修复流式传输的异步安全
确保res.write调用异步安全,避免流状态异常:
onResponse: async (chunk) => { const { dataString } = await extractStrategyData({ response: chunk, isDescription: requestPayload.description, }); // 用Promise包裹res.write,确保写入完成再处理下一个chunk await new Promise(resolve => res.write(dataString, resolve)); },
步骤3:调整SPDY配置或替换库
SPDY已多年未维护,建议优先替换为原生HTTP/2模块。若暂时无法替换,升级SPDY并精简配置:
const spdyOptions = { spdy: { protocols: ['h2', 'http/1.1'], // 移除过时的SPDY协议版本 plain: false, 'http2-settings': { 'MAX_CONCURRENT_STREAMS': 100 // 显式匹配Chrome默认并发流数 } } }
3. Express.js中处理高并发流式传输的更佳方案是什么?
方案1:直接Pipe上游流到响应流
避免手动处理chunk,利用Node.js流的原生能力,自动适配HTTP/2规则:
app.get('/stream', async (req, res) => { const response = await fetch(<API_URL>, httpOptions); if (!response.ok) { res.status(500).send('Streaming error'); return; } res.setHeader('Content-Type', 'text/event-stream'); res.setHeader('Cache-Control', 'no-cache'); // 直接将上游流pipe到Express响应流 response.body.pipeThrough(new TextDecoderStream()).pipe(res); });
方案2:使用SSE专用库
对于Server-Sent Events场景,用成熟库简化流管理:
express-sse:为Express设计的SSE库,自动兼容HTTP/1.1和HTTP/2。@microsoft/fetch-event-source:客户端、服务端双向兼容的流式传输库。
方案3:原生HTTP/2 + Express
用Node.js内置http2模块替代SPDY,避免第三方库兼容性问题(见问题4)。
4. Express.js实现HTTP/2有哪些SPDY的替代方案?
方案1:Node.js原生http2模块
最推荐的方案,Node.js v8.4.0起原生支持HTTP/2,兼容性最佳:
const http2 = require('http2'); const fs = require('fs'); const express = require('express'); const app = express(); // 创建HTTP/2安全服务器 const server = http2.createSecureServer({ key: fs.readFileSync('../localhost.key'), cert: fs.readFileSync('../localhost.crt'), maxHeaderSize: 64000 }, app); server.listen(443);
Express路由、中间件可直接复用,无需修改。
方案2:Fastify框架(替代Express)
Fastify对HTTP/2支持更完善,性能更高,原生适配流式传输:
const fastify = require('fastify')({ http2: true, https: { key: fs.readFileSync('../localhost.key'), cert: fs.readFileSync('../localhost.crt') } }); fastify.get('/stream', async (request, reply) => { reply.header('Content-Type', 'text/event-stream'); reply.header('Cache-Control', 'no-cache'); const response = await fetch(<API_URL>, httpOptions); response.body.pipeThrough(new TextDecoderStream()).pipe(reply.raw); }); fastify.listen({ port: 443 });
方案3:HTTP/2反向代理中间件
若需反向代理上游服务,可使用http2-proxy中间件配合Express实现HTTP/2代理。
内容的提问来源于stack exchange,提问作者Muhammad Ahmed Habib
相关产品推荐
相关产品推荐

