fastify / fastify-multipart

Multipart support for Fastify
MIT License
485 stars 103 forks source link

`for await req.parts()` hangs if `file.toBuffer()` hasn't been called on each file #495

Closed MrFoxPro closed 10 months ago

MrFoxPro commented 11 months ago

Prerequisites

Fastify version

4.24.3

Plugin version

8.0.0

Node.js version

21.2.0

Operating system

Linux

Operating system version (i.e. 20.04, 11.3, 10)

6.6.2-arch1-1

Description

Unable to use parts if files haven't been read

Steps to Reproduce

const parts = {}
for await (const part of req.parts()) {
  parts[part.fieldname] = part
  // if(part.type === "file") part.toBuffer()
}
// this will never happen if toBuffer wasn't called

Expected Behavior

No response

mcollina commented 10 months ago

Thanks for reporting!

Can you provide steps to reproduce? We often need a reproducible example, e.g. some code that allows someone else to recreate your problem by just copying and pasting it. If it involves more than a couple of different file, create a new repository on GitHub and add a link to that.

AndriyAntonenko commented 10 months ago

Hello everyone. Had the same issue when I was trying to read all fields in the request body(I am using Nest.js library with fastify). Here is my code example:

import { Readable } from 'node:stream';
import { CallHandler, ExecutionContext, NestInterceptor, BadRequestException } from '@nestjs/common';
import { Observable } from 'rxjs';
import { FastifyRequest } from 'fastify';

export class MultipartInterceptor implements NestInterceptor {
  async intercept(context: ExecutionContext, next: CallHandler<any>): Promise<Observable<unknown>> {
    const request = context.switchToHttp().getRequest<FastifyRequest>();

    if (!request.isMultipart()) {
      throw new BadRequestException('Request is not multipart');
    }

    const body: Record<string, Readable | unknown> = {};

    for await (const part of request.parts()) {
      if (part.type === 'field') {
        body[part.fieldname as string] = part.value;
      }

      if (part.type === 'file') {
        body[part.fieldname as string] = part.file;
      }
    }

    request.body = body;

    return next.handle();
  }
}

It stuck when trying after setting any file to the body property

AndriyAntonenko commented 10 months ago

It seems that I have found possible solution. I've added attachFieldsToBody property as true:

await app.register(fastifyMultipart, { limits: { fileSize: 2e9 }, attachFieldsToBody: true });

And I've just mapped body values:

import { Readable } from 'node:stream';
import { Multipart } from '@fastify/multipart';
import { CallHandler, ExecutionContext, NestInterceptor, BadRequestException } from '@nestjs/common';
import { Observable } from 'rxjs';
import { FastifyRequest } from 'fastify';

export class MultipartInterceptor implements NestInterceptor {
  async intercept(context: ExecutionContext, next: CallHandler<any>): Promise<Observable<unknown>> {
    const request = context.switchToHttp().getRequest<FastifyRequest>();

    if (!request.isMultipart()) {
      throw new BadRequestException('Request is not multipart');
    }

    request.body = Object.entries(request.body as Record<string, Multipart>).reduce(
      (acc, [key, multipart]) => {
        if (multipart.type === 'file') acc[key] = multipart.file;
        if (multipart.type === 'field') acc[key] = multipart.value;
        return acc;
      },
      {} as Record<string, Readable | unknown>,
    );
    return next.handle();
  }
}
climba03003 commented 10 months ago

It is how stream works, you need to consume it until it reach next block.