You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于.NET与Next.js从Cloud Run下载大文件遇响应超限问题排查

解决Cloud Run部署后大文件下载触发响应大小限制的问题

核心问题分析

Cloud Run对非流式响应有32MiB的大小限制,你的场景中视频平均50MiB,触发限制的核心原因是:

  • API端先把整个视频加载到内存(byte[]/MemoryStream)再返回,属于非流式响应
  • 即便前端用了流式处理,API端的内存加载行为已经让Cloud Run判定整个响应为非流式,触发限制

第一步:修复API端的流式传输

必须让API直接返回文件流,避免把整个视频加载到内存:

1. 修改服务层,返回Stream而非byte[]/MemoryStream

假设视频存储在Google Cloud Storage,修改服务方法直接获取对象流:

public async Task<Stream> GetVideoStreamAsync(string id)
{
    var storage = StorageClient.Create();
    var bucketName = "your-bucket-name";
    var objectName = $"videos/{id}.mp4";
    
    // 直接打开GCS对象的可读流,无需加载到内存
    return await storage.OpenReadAsync(bucketName, objectName);
}

如果视频存在本地磁盘,用File.OpenRead打开流即可。

2. 修改DownloadVideo端点,返回FileStreamResult

[HttpGet("{id}")]
public async Task<IActionResult> DownloadVideo([FromRoute, Required] string id)
{
    var videoStream = await _service.GetVideoStreamAsync(id);
    if (videoStream is null)
    {
        return NotFound();
    }
    
    // 使用FileStreamResult确保流式传输,ASP.NET Core会自动设置分块传输头
    return new FileStreamResult(videoStream, "video/mp4")
    {
        FileDownloadName = $"{id}.mp4",
        EnableRangeProcessing = true
    };
}

修改后API会以分块方式返回响应,Cloud Run会识别为流式响应,不再受32MiB限制。


第二步:修复前端下载方式

方式1:Next.js无服务器函数转发

确保转发时保持流式传输,禁用响应缓存:

import stream from 'stream';
import { promisify } from 'util';
import fetch from 'node-fetch';

const pipeline = promisify(stream.pipeline);

const handler = async (req, res) => {
    const { id } = req.query;
    const apiUrl = `https://your-cloud-run-api.com/DownloadVideo/${id}`;
    
    const response = await fetch(apiUrl);
    if (!response.ok) {
        res.status(response.status).send(response.statusText);
        return;
    }

    // 透传API的响应头,确保流式和范围请求支持
    res.setHeader('Content-Type', response.headers.get('Content-Type'));
    res.setHeader('Content-Disposition', response.headers.get('Content-Disposition'));
    res.setHeader('Accept-Ranges', 'bytes');
    res.setHeader('Transfer-Encoding', 'chunked');

    await pipeline(response.body, res);
};

// 禁用Next.js的响应大小限制和自动缓存
export const config = {
    api: {
        responseLimit: false,
        externalResolver: true,
    },
};

export default handler;

方式2:直接用react-use-downloader

API修复后,直接请求API端点即可,组件会自动处理流式下载:

const { download } = useDownloader();
const apiUrl = `https://your-cloud-run-api.com/DownloadVideo/${videoId}`;

<button onClick={() => download(apiUrl, `${videoId}.mp4`)}>下载视频</button>

最优方案:直接下载GCS签名URL

如果视频存储在GCS,推荐API生成临时签名URL返回给前端,前端直接从GCS下载,完全绕过Cloud Run的响应限制:

API端生成签名URL

public async Task<string> GetVideoSignedUrlAsync(string id)
{
    var storage = StorageClient.Create();
    var bucketName = "your-bucket-name";
    var objectName = $"videos/{id}.mp4";
    
    // 生成1小时有效期的签名URL
    var url = storage.GetSignedUrl(new GetSignedUrlOptions
    {
        Bucket = bucketName,
        Object = objectName,
        Expiration = TimeSpan.FromHours(1),
        HttpMethod = HttpMethod.Get,
        ContentType = "video/mp4"
    });
    
    return url;
}

前端直接下载

const { download } = useDownloader();
const signedUrl = await fetch(`/api/get-signed-url?id=${videoId}`).then(res => res.text());

<button onClick={() => download(signedUrl, `${videoId}.mp4`)}>下载视频</button>

内容的提问来源于stack exchange,提问作者Bukk94

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.30 19:17:12