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

如何在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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.16 08:16:02