Ошибка ENOENT после того, как помощник официанта получил файл

#nestjs #busboy #enoent

#nestjs #помощник официанта #enoent

Вопрос:

Я работаю с устаревшим проектом, в котором файлы MP4 загружаются один за другим из приложения React с axios в NestJS API с busboy на экземпляре EC2. Затем файл загружается в корзину S3.

Когда библиотека AWS S3 пытается загрузить файл, иногда возникает очевидная «случайная» ошибка:

  warn
 module: videoservice
 method: saveTostream
 message: Error on Upload Success callback
 { [Error: ENOENT: no such file or directory, open 'file.mp4'] errno: -2, code: 'ENOENT', syscall: 'open', path: 'file.mp4' }

 (node:26886) UnhandledPromiseRejectionWarning: Error: ENOENT: no such file or directory, open 'file.mp4'
 (node:26886) UnhandledPromiseRejectionwarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catc h(). (rejection id: 20)
  

Вот фрагменты:

Конечная точка загрузки API

   @Post('upload/:id')
  @ApiOperation({ ... })
  @ApiResponse({ status: 201, type: Video })
  @ApiConsumes('multipart/form-data')
  @ApiImplicitFile({ name: 'file', required: true })
  @ApiCreatedResponse({ type: UploadResponseDto })
  async upload(
    @Decoded() decoded: any,
    @Param('id') videoId: number,
    @Query('fileSize') fileSize :number,
    @Req() req: Request,
    @Res() res: Response,
    @Body() uploadDto: UploadDto,
  ): Promise<any> {
    try {
      const busboy = new Busboy({
        headers: req.headers,
      });

      const data = new Map();
      const writtenFiles = [];
      let upload;

      busboy.on('field', (fieldname, val) => {
        data.set(fieldname, val);
      });

      busboy.on(
        'file',
        async (fieldname, file, filename, encoding, mimetype) => {
          try {
            data.set('filename', filename);
            data.set('mimetype', mimetype);
            data.set('encoding', encoding);

            const filepath = path.join(fieldname   path.extname(filename));
            upload = filepath;

            const writeStream = fs.createWriteStream(filepath);
            file.pipe(writeStream);

            const promise = new Promise((resolve, reject) => {
              file.on('end', () => {
                writeStream.end();
              });
              writeStream.on('finish', resolve);
              writeStream.on('error', reject);
            });
    
            writtenFiles.push(promise);
          } catch (err) {
            this.logger.error(log('busboy on file', err.message));
            return res.status(HttpStatus.INTERNAL_SERVER_ERROR).send({
              statusCode: HttpStatus.INTERNAL_SERVER_ERROR,
              message: err.message,
            });
          }
        },
      );

      busboy.on('error', err => {
        this.logger.warn(log('busboy on error', err.message));
      });

      busboy.on('finish', async () => {
        await Promise.all(writtenFiles);
        res.status(HttpStatus.CREATED).send('OK');

        await this.bucketService.saveStream( // Next snippet
          fs.createReadStream(upload),
          data.get('filename'),
          data.get('mimetype'),
          data.get('fileSize'),
           data.get('programId'),
           data.get('videoId')
        );
        return;
        fs.unlinkSync(upload);
      });

      req.pipe(busboy);
    } catch (err) {
      this.logger.error(log('catch', err.message));
      return res.status(HttpStatus.INTERNAL_SERVER_ERROR).send({
        statusCode: HttpStatus.INTERNAL_SERVER_ERROR,
        message: err.message,
      });
    }
  }
  

метод BucketService saveStream

 public async saveStream(
    body: any
    filename: string,
    mimeType: string,
    fileSize: number,
    programId: number,
    videoId: number
  ): Promise<any> {
    try {
      const callback = async (err: any, data: any) => {
        if (err) {
          this.logger.warn(log('Error on Upload Success callback', err));
          throw err; // Here is where the error is raised
        }
      };

      return this.s3
        .upload(
          this.setParams(body, filename, mimeType, programId, videoId),
          (err, data) => callback(err, data),
        )
    } catch (err) {
      this.logger.error(log('Error on S3 Upload', err));
      throw err;
    }
  }

  private setParams(
    file: any,
    filename: string,
    mimeType: string,
    programId: number,
    videoId: number
  ): any {
    return {
      ...awsBucketConfig,
      Key: `${AWS_UPLOAD_DIR}${programId}/${videoId}/${Date.now()}${path.extname(
        filename,
      )}`,
      Body: file,
      ContentType: mimeType,
    };
  }
  

В какой-то момент я подумал, что, возможно, это происходит потому, что имя временного файла в EC2 всегда одно и то же: file.mp4 , и когда два файла загружаются одновременно, первый по завершении удаляет файл ( fs.unlinkSync(upload); на конечной точке), оставляя другой текущий процесс без него, поэтому при попытке загрузить его этот процесс его не найдет. Но это неверно, потому что я выполнил тесты, в которых я гарантировал, что файлы были загружены один за другим. Однако я также убедился, что имя всегда было другим, изменив его на контроллере:

const filepath = path.join(fieldname path.extname(filename));

Автор:

const filepath = path.join(Math.floor(Math.random() * 10000) path.extname(filename));

но ошибка все еще происходит. Еще одна странная вещь, которая происходит, заключается в том, что на моем компьютере я могу видеть ( ls ) файл, пока он загружен, но в EC2 нет.

Факты:

  • EC2: t2.xl большого размера (около 4 ГБ свободного места)

  • ОС: Ubuntu 18

  • Версия узла: 10.21.0

  • Средний размер файла: 2 ГБ

  • Зависимости

      "archiver": "^3.1.1",
     "async-busboy": "^0.7.0",
     "aws-sdk": "^2.553.0",
     "axios": "^0.19.0",
     "body-parser": "^1.19.0",
     "busboy": "^0.3.1",