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

Chrome扩展中如何通过XHR流式上传URL指向的文件?

高效流式衔接下载与上传(无内存占用)

这个问题的核心就是要实现无内存拷贝的流式传输——把下载到的文件数据直接“管道”到上传请求里,完全不用把整个文件塞进内存,自然也没有大小限制。我给你两种实用的实现方案,对应不同的服务器接收格式:

方案1:服务器支持直接接收原始文件流

如果你的服务器可以直接接收文件的原始二进制数据(不需要multipart/form-data格式),这个方案最简单,直接把下载的流作为上传请求的body:

async function streamUploadRawFile() {
  try {
    // 1. 发起流式下载请求
    const downloadResp = await fetch(ctx.srcUrl, {
      credentials: 'include' // 对应原代码中的xhr.withCredentials = true
    });

    if (!downloadResp.ok) {
      throw new Error(`下载失败:${downloadResp.status}`);
    }

    // 2. 直接把下载的流作为上传请求的body
    const uploadResp = await fetch('https://endpoint.com/upload', {
      method: 'POST',
      credentials: 'include',
      body: downloadResp.body, // 关键:传递ReadableStream,而非完整Blob
      headers: {
        // 复用原文件的Content-Type,让服务器识别文件类型
        'Content-Type': downloadResp.headers.get('Content-Type') || 'application/octet-stream'
      }
    });

    if (!uploadResp.ok) {
      throw new Error(`上传失败:${uploadResp.status}`);
    }

    console.log('流式上传成功!');
  } catch (err) {
    console.error('上传出错:', err);
  }
}

方案2:服务器要求multipart/form-data格式(和原代码兼容)

如果服务器必须用multipart/form-data(也就是原代码中FormData的格式),我们需要手动构造multipart的头部、尾部流,和文件流合并后发送——整个过程依然是流式的,不会把文件存进内存:

async function streamUploadFormData() {
  try {
    // 1. 发起流式下载请求
    const downloadResp = await fetch(ctx.srcUrl, {
      credentials: 'include'
    });

    if (!downloadResp.ok) {
      throw new Error(`下载失败:${downloadResp.status}`);
    }

    // 2. 准备multipart相关参数
    const boundary = `----WebKitFormBoundary${Math.random().toString(16).slice(2)}`;
    const fileName = new URL(ctx.srcUrl).pathname.split('/').pop() || 'unknown-file';
    const fileStream = downloadResp.body;

    // 3. 构造multipart的头部流(描述文件字段信息)
    const headerStream = new Blob([
      `--${boundary}\r\n`,
      `Content-Disposition: form-data; name="upload"; filename="${fileName}"\r\n`,
      `Content-Type: ${downloadResp.headers.get('Content-Type') || 'application/octet-stream'}\r\n\r\n`
    ]).stream();

    // 4. 构造multipart的尾部流(结束标记)
    const footerStream = new Blob([`\r\n--${boundary}--\r\n`]).stream();

    // 5. 合并头部、文件流、尾部为一个连续的流
    const combinedStream = new ReadableStream({
      async start(controller) {
        // 先发送头部
        const headerReader = headerStream.getReader();
        let result;
        while (!(result = await headerReader.read()).done) {
          controller.enqueue(result.value);
        }

        // 再发送文件流(核心:边下载边上传)
        const fileReader = fileStream.getReader();
        while (!(result = await fileReader.read()).done) {
          controller.enqueue(result.value);
        }

        // 最后发送尾部
        const footerReader = footerStream.getReader();
        while (!(result = await footerReader.read()).done) {
          controller.enqueue(result.value);
        }

        controller.close();
      }
    });

    // 6. 发起流式上传请求
    const uploadResp = await fetch('https://endpoint.com/upload', {
      method: 'POST',
      credentials: 'include',
      body: combinedStream,
      headers: {
        'Content-Type': `multipart/form-data; boundary=${boundary}`
      }
    });

    if (!uploadResp.ok) {
      throw new Error(`上传失败:${uploadResp.status}`);
    }

    console.log('流式上传成功!');
  } catch (err) {
    console.error('上传出错:', err);
  }
}

关键优势说明

  • 零内存占用:整个过程中,浏览器只会缓存一小段文件数据(几KB到几十KB),不会把整个大文件加载到内存里,完全规避了FileReader的大小限制。
  • 兼容性良好:Chrome 65+完全支持这些API,Chrome扩展的运行环境都是较新的Chrome版本,不用担心兼容问题。
  • 和原代码逻辑对齐:方案2完全匹配你原有的uploadFile函数的请求格式,不需要修改服务器端代码。

可选:用XHR替代Fetch

如果你更习惯用XHR,现代Chrome的XHR.send()也支持直接发送ReadableStream,把方案中的Fetch上传部分替换成下面的代码即可:

const xhr = new XMLHttpRequest();
xhr.open('POST', 'https://endpoint.com/upload');
xhr.withCredentials = true;
xhr.setRequestHeader('Content-Type', `multipart/form-data; boundary=${boundary}`); // 方案2需要这个,方案1替换成对应Content-Type
xhr.send(combinedStream); // 方案2用这个,方案1直接传downloadResp.body

// 可以添加监听事件
xhr.onload = () => {
  if (xhr.status >= 200 && xhr.status < 300) {
    console.log('上传成功');
  } else {
    console.error('上传失败');
  }
};

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.08 16:27:30