
Cloud Run 默认限制单次请求体不超过 32MB,且不支持 HTTP/1.1 中的分块传输(如 Range 请求)在代理链路中可靠透传,导致视频流服务返回 502 “upstream connect error”;本地正常而云端失败的根本原因在于网络协议栈与服务边界差异。
cloud run 默认限制单次请求体不超过 32mb,且不支持 http/1.1 中的分块传输(如 `range` 请求)在代理链路中可靠透传,导致视频流服务返回 502 “upstream connect error”;本地正常而云端失败的根本原因在于网络协议栈与服务边界差异。
Google Cloud Run 作为无服务器托管平台,其前端代理(Envoy)对 HTTP 协议有严格约束:默认仅支持 HTTP/1.1,且会拦截、重写或拒绝部分流式响应头(如 Content-Range、Accept-Ranges),尤其当后端异步读取 GCS 文件元数据(如 file.getMetadata())未 await 或返回 Promise 未正确处理时,极易触发 protocol error 类型的 502 错误——这正是你日志中 reset reason: protocol error 的根源。
更关键的是,你的代码存在两个高危隐患:
-
同步调用异步方法:
file.getMetadata().size是非法操作 ——getMetadata()返回 Promise,必须await,否则videoSize为undefined,后续计算(如end = Math.min(..., videoSize - 1))将抛出运行时错误或生成无效范围,被 Cloud Run 代理判定为协议异常; -
未设置响应状态码与流式管道:缺失
res.status(206)(Partial Content)及res.writeHead()显式写入头,也未通过file.createReadStream()管道传输字节流,而是试图手动构造响应体——这在 Cloud Run 的缓冲与超时机制下必然失败。
✅ 正确做法分两类场景:
✅ 场景一:视频上传(推荐使用 Signed URL)
绕过 Cloud Run 本身处理大文件,让客户端直传至 Cloud Storage:
// 后端生成预签名上传 URL(有效期建议 15–60 分钟)
const [signedUrl] = await bucket.file(videoName).getSignedUrl({
action: 'write',
expires: '15m',
contentType: 'video/mp4',
});
res.json({ uploadUrl: signedUrl });前端直接 PUT 视频二进制到该 URL,无需经过 Cloud Run 实例。
✅ 场景二:视频流式播放(需修复服务端逻辑)
若必须经 Cloud Run 中转(如鉴权、转码等),请严格遵循流式响应规范:
// ✅ 正确实现:await 元数据 + 显式状态码 + 流式管道
const [metadata] = await file.getMetadata(); // ⚠️ 必须 await
const videoSize = metadata.size;
const range = req.headers.range;
if (!range) {
res.status(200).header({
'Content-Length': videoSize,
'Content-Type': 'video/mp4',
'Accept-Ranges': 'bytes',
}).sendFile(file.name); // 或用 createReadStream 管道
return;
}
const parts = range.replace(/bytes=/, '').split('-');
const start = parseInt(parts[0], 10);
const end = parts[1] ? parseInt(parts[1], 10) : videoSize - 1;
const chunkSize = end - start + 1;
res.status(206).header({
'Content-Range': `bytes ${start}-${end}/${videoSize}`,
'Accept-Ranges': 'bytes',
'Content-Length': chunkSize,
'Content-Type': 'video/mp4',
});
// ✅ 关键:通过流式读取并管道传输,避免内存加载
const stream = file.createReadStream({ start, end });
stream.pipe(res);⚠️ 注意事项:
- Cloud Run 默认请求超时为 5 分钟,大视频流需在
gcloud run deploy时显式设置--timeout=3600(最高 1 小时); - 禁止在响应中使用
res.send()或res.end()手动写入二进制——必须依赖stream.pipe(res)或res.sendFile(); - 开发阶段可启用
--use-http2(需客户端也支持 HTTP/2),但生产环境强烈推荐 Signed URL 方案,既规避协议限制,又降低 Cold Start 压力与成本。
总结:本地能跑通是因为 Node.js 直连 GCS 无代理干扰;云端失败是 Cloud Run 架构使然。根本解法不是调优代码,而是重构数据路径——让大文件“绕开”无服务器实例,直传存储层;小文件流播则务必遵循 HTTP 流式规范,并始终 await 异步 I/O。

















