如何在Node.js中用HTTP/2模块POST含文件字段的FormData?
问题:HTTP/2请求下Fastify无法接收FormData中的文件字段
现有代码在HTTP/1.1环境下能正常处理包含文件的FormData请求,但切换到HTTP/2后,Fastify服务端无法接收到fileField字段。相关代码如下:
客户端FormData创建代码
import FormData from 'form-data'; const createForm = async () => { const form = new FormData(); form.append('field1', 'stringValue1'); form.append('fileField', fs.createReadStream(__filename), { contentType: 'text/javascript', filename: 'file.js', }); return form; };
客户端HTTP/2请求发送代码
// app 为FastifyInstance await app.ready(); const { address, port } = app.server.address(); const client = http2.connect(`http://${address}:${port}`); const request = client.request({ ':method': 'POST', ':path': '/path', 'content-type': `multipart/form-data; boundary=${form.getBoundary()}`, }); request.setEncoding('utf8'); let status; request.on('response', (headers) => { status = headers[':status']; }); request.on('data', (data) => { ... }); form.pipe(request); request.end(); await new Promise((resolve) => { request.on('end', () => { client.close(); resolve(); }); });
Fastify服务端代码
import fastify from 'fastify'; import fastifyFormbody from '@fastify/formbody'; import fastifyMultipart from '@fastify/multipart'; const app = fastify({ http2: true, logger: logLevel === 'debug' }); const fieldSizeLimit = 1024 * 1024 * 10; void app.register(fastifyFormbody); void app.register(fastifyMultipart, { attachFieldsToBody: 'keyValues', limits: { fieldSize: fieldSizeLimit, fileSize: fieldSizeLimit, }, onFile: async (part) => { const destinationPath = path.join(bundlePath, 'uploads', part.filename); await saveMultipartFile(part, destinationPath); // eslint-disable-next-line no-param-reassign part.value = { filename: part.filename, savedFilePath: destinationPath, type: 'asset', }; }, }); app.post('/path', async (req, res) => { console.log(Object.entries(req.body)); ... });
要求支持最低Node.js版本20,可更换form-data库。
解决方案
1. 替换为Node.js原生FormData
Node.js 20+已内置FormData,无需额外安装第三方库,原生实现对HTTP/2的兼容性更好。修改FormData创建代码:
// 无需导入第三方form-data,直接用全局FormData const createForm = async () => { const form = new FormData(); form.append('field1', 'stringValue1'); // 用原生File包装可读流 const fileStream = fs.createReadStream(__filename); const file = new File([fileStream], 'file.js', { type: 'text/javascript' }); form.append('fileField', file); return form; };
2. 调整HTTP/2请求的流处理逻辑
第三方form-data的pipe方法在HTTP/2流上存在兼容性问题,改用原生formData.stream()获取可读流,并修正请求结束时机:
await app.ready(); const { address, port } = app.server.address(); const client = http2.connect(`http://${address}:${port}`); const form = await createForm(); const request = client.request({ ':method': 'POST', ':path': '/path', 'content-type': form.getHeaders()['content-type'], // 直接用原生FormData生成的头部,避免边界符不一致 }); // 不要设置utf8编码,multipart包含二进制内容,编码会导致解析错误 // request.setEncoding('utf8'); let status; request.on('response', (headers) => { status = headers[':status']; }); request.on('data', (data) => { ... }); // 获取FormData可读流并pipe到请求 const formStream = form.stream(); formStream.pipe(request); // 监听formStream的end事件再结束请求,确保数据全部发送 formStream.on('end', () => { request.end(); }); await new Promise((resolve) => { request.on('end', () => { client.close(); resolve(); }); });
3. 服务端添加排查日志
在fastify-multipart的onFile回调中添加日志,确认是否接收到文件部分:
onFile: async (part) => { console.log('Received file part:', part.filename); // 新增日志 const destinationPath = path.join(bundlePath, 'uploads', part.filename); await saveMultipartFile(part, destinationPath); part.value = { filename: part.filename, savedFilePath: destinationPath, type: 'asset', }; },
关键注意点
- 第三方
form-data库的边界符生成、流处理逻辑与HTTP/2流机制存在差异,原生FormData更适配Node.js HTTP/2客户端。 - HTTP/2请求中不能对响应流设置
utf8编码,否则会破坏二进制文件的解析。 - 直接使用
form.getHeaders()获取Content-Type头部,避免手动拼接边界符时出现不一致问题。
内容的提问来源于stack exchange,提问作者Alexey Romanov
相关产品推荐
相关产品推荐

