Alexey Romanov
Alexey Romanov

Reputation: 170805

How to POST formdata including a file field with http2 module in Node?

I create a FormData as follows (simplified):

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;
};

Then to send it over HTTP/2 (cleartext):

  // app is a 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();
    });
  });

Then in the Fastify server listening to requests I have

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));
  ...
});

which worked fine with HTTP/1.1 requests. And the log shows fileField is not received. How do I make the request correctly?

I found Opening an http/2 connection and POSTing form-data which says

In the Node HTTP2 docs it shows an example of how to do this.

but it seems to be only talking about separating const client = http2.connect(...) and client.request, not a form data example.

The minimum Node version I need to support is 20, and I'm happy to switch from form-data if needed; I just had different errors with built-in FormData but maybe I was doing something wrong there.

Upvotes: 2

Views: 52

Answers (1)

Alexey Romanov
Alexey Romanov

Reputation: 170805

The solution was to replace

  form.pipe(request);
  request.end();

with

  form.pipe(request);
  form.on('end', () => { request.end(); });

Upvotes: 0

Related Questions