如何在Cloudflare Worker中实现与Node.js同款的流式multipart/form-data中转上传功能
如何在Cloudflare Worker中实现与Node.js同款的流式multipart/form-data中转上传功能
我之前正好碰到过这个问题!Cloudflare Worker 环境里的标准 FormData 实现和 Node.js(尤其是基于 undici 的新版 FormData)不一样,它不支持直接把 ReadableStream 作为表单字段值传入,这就是你看到类型错误的原因。不过别担心,我们可以手动构建流式的 multipart/form-data 请求体,完全实现和 Node.js 一样的流式中转效果,而且全程不会把文件内容加载到内存里,完美适配 Worker 的内存限制。
核心思路
绕过 Worker 自带的 FormData,自己构造符合 multipart/form-data 格式的数据流:
- 生成随机的分隔边界(boundary)字符串,用来区分表单里的不同字段
- 构造表单字段的头部信息(包含字段名、文件名、Content-Type 等)
- 把「头部流」→「下载的文件流」→「分隔边界流」拼接成一个完整的请求体流
- 最后带着正确的 Content-Type 头(包含边界)发起 POST 请求
完整实现代码(TypeScript)
async function streamTransfer( downloadUrl: string, uploadUrl: string, fieldName: string = 'file', fileName: string = 'transfered-file' ) { // 1. 发起下载请求,获取响应流 const downloadRes = await fetch(downloadUrl); if (!downloadRes.ok || !downloadRes.body) { throw new Error(`下载失败: ${downloadRes.status} ${downloadRes.statusText}`); } // 2. 生成唯一的 multipart 分隔边界 const boundary = `----CloudflareWorkerBoundary${crypto.randomUUID()}`; // 3. 构造表单字段的头部内容(遵循HTTP multipart规范) const header = `--${boundary}\r\nContent-Disposition: form-data; name="${fieldName}"; filename="${fileName}"\r\nContent-Type: ${downloadRes.headers.get('Content-Type') || 'application/octet-stream'}\r\n\r\n`; // 构造请求体末尾的结束边界 const footer = `\r\n--${boundary}--\r\n`; // 4. 拼接头部、文件流、尾部为一个完整的可读流 const combinedStream = new ReadableStream({ async start(controller) { // 先写入头部 controller.enqueue(new TextEncoder().encode(header)); // 逐段读取下载流并转发到控制器 const reader = downloadRes.body.getReader(); try { while (true) { const { done, value } = await reader.read(); if (done) break; controller.enqueue(value); } // 写入尾部边界并关闭流 controller.enqueue(new TextEncoder().encode(footer)); controller.close(); } catch (err) { controller.error(err); reader.cancel(err); } } }); // 5. 发起上传请求,使用拼接后的流作为请求体 const uploadRes = await fetch(uploadUrl, { method: 'POST', headers: { 'Content-Type': `multipart/form-data; boundary=${boundary}` }, body: combinedStream }); if (!uploadRes.ok) { throw new Error(`上传失败: ${uploadRes.status} ${uploadRes.statusText}`); } return uploadRes; } // Worker 请求入口示例 addEventListener('fetch', (event) => { event.respondWith(handleRequest(event.request)); }); async function handleRequest(request: Request) { try { await streamTransfer( 'http://someurltoDownload', 'http://someurltoUpload', 'file', 'my-file.jpg' ); return new Response('中转上传成功', { status: 200 }); } catch (err) { return new Response(`错误: ${(err as Error).message}`, { status: 500 }); } }
关键细节说明
- 零内存占用:整个过程只是逐段转发下载流的内容到上传流,不会把文件全部加载到内存,完全符合 Cloudflare Worker 的内存限制。
- 规范兼容:手动构造的 multipart 格式严格遵循 RFC 7578 标准,服务器端可以正常解析。
- 类型与错误处理:添加了下载、流转发、上传各环节的错误捕获,同时保留了原文件的 Content-Type 信息。
- 边界唯一性:用
crypto.randomUUID()生成唯一边界,避免和文件内容中的字符串冲突。
额外注意事项
- 如果需要添加多个表单字段,只需在拼接流时依次加入每个字段的头部、值(或流)、分隔边界即可。
- 若要处理中文文件名,可将文件名用
encodeURIComponent编码后放入头部,或遵循 RFC 5987 标准进行编码。 - 若下载的响应有
Content-Length头,也可以把它加入到上传请求的头中(部分服务器会依赖这个字段)。




