Next.js 13 App目录路由下能否流式传输POST文件至外部API?
问题
我尝试从浏览器上传图片,通过后端流式传输至Imgur API,但只有小图片(比如28KB)能正常上传,80KB的图片要花1分钟才崩溃。怀疑是内存问题,想知道能不能用流来解决。
现有代码
route.js(处理form-data POST请求)
import uploadImage from "@/utils/uploadImage"; import { NextResponse } from "next/server"; import path from 'path' export const POST = async (req) => { try { const formData = await req.formData(); const file = formData.get('image'); const allowedExtensions = ['.jpg', '.jpeg', '.png'] if (!file || !allowedExtensions.includes(path.extname(file.name))){ return NextResponse.json({ ok:false, msg: 'Invalid file' }) } let newFileURL = await uploadImage(file) if (!newFileURL){ return NextResponse.json({ ok:false, msg: 'Invalid file' }) } return NextResponse.json({ ok:true, url: newFileURL }) } catch (error) { console.log(error) return new Response(null, { status:500 }) } }
uploadImage函数
export default async function uploadImage(fileBlob){ const postData = new FormData() postData.set('image', fileBlob, fileBlob.name) let response = await fetch(process.env.IMGUR_UPLOAD_URL, { method:'POST', body:postData, headers:{ "Authorization": "Client-ID " + process.env.IMGUR_CLIENT_ID } }) response = await response.json() if (!response.status){ return null } return response.data.link }
解决方案:改用流式传输
当前代码会把整个文件解析成Blob存入内存,大文件会导致内存占用过高,引发超时或崩溃。改用流式处理可以避免一次性加载整个文件,直接把浏览器上传的流转发给Imgur。
简化版:直接转发请求流
不需要解析FormData,直接把原始请求流传给Imgur,同时保留必要的请求头:
import { NextResponse } from "next/server"; export const POST = async (req) => { try { const imgurResponse = await fetch(process.env.IMGUR_UPLOAD_URL, { method: 'POST', body: req.body, // 直接转发原始请求流 headers: { "Authorization": `Client-ID ${process.env.IMGUR_CLIENT_ID}`, // 保留浏览器传来的Content-Type(包含form-data边界) "Content-Type": req.headers.get('content-type') } }); if (!imgurResponse.ok) { return NextResponse.json({ ok: false, msg: '上传到Imgur失败' }, { status: imgurResponse.status }); } const data = await imgurResponse.json(); return NextResponse.json({ ok: true, url: data.data.link }); } catch (error) { console.log(error); return new Response(null, { status: 500 }); } };
带文件验证的优化版
如果需要验证文件类型,可通过解析multipart流的头部信息获取文件名,无需加载整个文件:
import { NextResponse } from "next/server"; import path from 'path'; // 简易multipart解析函数,仅提取文件信息和流 async function parseMultipartFile(req) { const contentType = req.headers.get('content-type'); if (!contentType?.startsWith('multipart/form-data')) { throw new Error('Invalid content type'); } const boundary = contentType.split('boundary=')[1]; const reader = req.body.getReader(); const decoder = new TextDecoder('utf-8'); let fileName = ''; let fileStream; while (true) { const { done, value } = await reader.read(); if (done) break; const chunkStr = decoder.decode(value, { stream: true }); if (!fileName) { // 从头部提取文件名 const fileNameMatch = chunkStr.match(/filename="([^"]+)"/); if (fileNameMatch) fileName = fileNameMatch[1]; } // 找到文件内容起始位置,截断后返回流 const contentStart = chunkStr.indexOf('\r\n\r\n'); if (contentStart !== -1) { const contentChunk = value.slice(contentStart + 4); fileStream = new ReadableStream({ async start(controller) { controller.enqueue(contentChunk); // 继续读取剩余流 while (true) { const { done, value } = await reader.read(); if (done) break; // 检查是否到达边界,截断尾部 const endIndex = value.indexOf(`--${boundary}`); if (endIndex !== -1) { controller.enqueue(value.slice(0, endIndex)); break; } controller.enqueue(value); } controller.close(); } }); break; } } return { fileName, fileStream }; } export const POST = async (req) => { try { const { fileName, fileStream } = await parseMultipartFile(req); const allowedExtensions = ['.jpg', '.jpeg', '.png']; if (!fileName || !allowedExtensions.includes(path.extname(fileName))) { return NextResponse.json({ ok: false, msg: 'Invalid file' }); } const formData = new FormData(); formData.set('image', fileStream, fileName); const imgurResponse = await fetch(process.env.IMGUR_UPLOAD_URL, { method: 'POST', body: formData, headers: { "Authorization": `Client-ID ${process.env.IMGUR_CLIENT_ID}` } }); if (!imgurResponse.ok) { return NextResponse.json({ ok: false, msg: '上传到Imgur失败' }, { status: imgurResponse.status }); } const data = await imgurResponse.json(); return NextResponse.json({ ok: true, url: data.data.link }); } catch (error) { console.log(error); return new Response(null, { status: 500 }); } };
流式处理的优势
- 内存占用低:文件分块传输,不会一次性加载整个文件到内存
- 传输效率高:避免中间内存拷贝,减少超时概率
内容的提问来源于stack exchange,提问作者Rafa0712
相关产品推荐
相关产品推荐

