Множественная загрузка файлов на S3 с помощью Node.js и Busboy

Я пытаюсь реализовать конечную точку API, которая позволяет загружать несколько файлов.

Я не хочу записывать какие-либо файлы на диск, а хочу их буферизовать и передавать на S3.

Вот мой код для загрузки одного файла. Как только я попытаюсь опубликовать несколько файлов в конечной точке в route.js, это не сработает.

route.js - Я буду держать это как можно более независимым от фреймворка

import Busboy from 'busboy'
// or const Busboy = require('busboy')

const parseForm = async req => {
  return new Promise((resolve, reject) => {
    const form = new Busboy({ headers: req.headers })
    let chunks = []
    form.on('file', (field, file, filename, enc, mime) => {
      file.on('data', data => {
        chunks.push(data)
      })
    })
    form.on('error', err => {
      reject(err)
    })
    form.on('finish', () => {
      const buf = Buffer.concat(chunks)
      resolve({ 
        fileBuffer: buf,
        fileType: mime,
        fileName: filename,
        fileEnc: enc,
      })
    })
    req.pipe(form)
  })
}

export default async (req, res) => {
// or module.exports = async (req, res) => {
  try {
    const { fileBuffer, ...fileParams } = await parseForm(req)
    const result = uploadFile(fileBuffer, fileParams)
    res.status(200).json({ success: true, fileUrl: result.Location })
  } catch (err) {
    console.error(err)
    res.status(500).json({ success: false, error: err.message })
  }
}

upload.js

import S3 from 'aws-sdk/clients/s3'
// or const S3 = require('aws-sdk/clients/s3')

export default (buffer, fileParams) => {
// or module.exports = (buffer, fileParams) => {
  const params = {
    Bucket: 'my-s3-bucket',
    Key: fileParams.fileName,
    Body: buffer,
    ContentType: fileParams.fileType,
    ContentEncoding: fileParams.fileEnc,
  }
  return s3.upload(params).promise()
}

person josiahwiebe    schedule 11.12.2019    source источник


Ответы (1)


Я не смог найти много документации для этого, но я думаю, что нашел решение.

Большинство реализаций записывают файл на диск перед его загрузкой на S3, но я хотел иметь возможность буферизовать файлы и загружать их на S3 без записи на диск.

Я создал эту реализацию, которая могла обрабатывать загрузку одного файла, но когда я пытался предоставить несколько файлов, буферы объединялись в один файл.

Единственное ограничение, которое я не могу преодолеть, — это имя поля. Например, вы можете настроить FormData() следующим образом:

const formData = new FormData()
fileData.append('file[]', form.firstFile[0])
fileData.append('file[]', form.secondFile[0])
fileData.append('file[]', form.thirdFile[0])

await fetch('/api/upload', {
  method: 'POST',
  body: formData,
}

Эта структура показана в примере MDN FormData.append(). . Однако я не уверен, как это обработать. В конце концов, я настроил свой FormData() следующим образом:

Данные формы

const formData = new FormData()
fileData.append('file1', form.firstFile[0])
fileData.append('file2', form.secondFile[0])
fileData.append('file3', form.thirdFile[0])

await fetch('/api/upload', {
  method: 'POST',
  body: formData,
}

Насколько я могу судить, это не является явно неправильным, но это не предпочтительный метод.

Вот мой обновленный код

route.js

import Busboy from 'busboy'
// or const Busboy = require('busboy')

const parseForm = async req => {
  return new Promise((resolve, reject) => {
    const form = new Busboy({ headers: req.headers })
    const files = [] // create an empty array to hold the processed files
    const buffers = {} // create an empty object to contain the buffers
    form.on('file', (field, file, filename, enc, mime) => {
      buffers[field] = [] // add a new key to the buffers object
      file.on('data', data => {
        buffers[field].push(data)
      })
      file.on('end', () => {
        files.push({
          fileBuffer: Buffer.concat(buffers[field]),
          fileType: mime,
          fileName: filename,
          fileEnc: enc,
        })
      })
    })
    form.on('error', err => {
      reject(err)
    })
    form.on('finish', () => {
      resolve(files)
    })
    req.pipe(form) // pipe the request to the form handler
  })
}

export default async (req, res) => {
// or module.exports = async (req, res) => {
  try {
    const files = await parseForm(req)
    const fileUrls = []
    for (const file of files) {
      const { fileBuffer, ...fileParams } = file
      const result = uploadFile(fileBuffer, fileParams)
      urls.push({ filename: result.key, url: result.Location })
    }
    res.status(200).json({ success: true, fileUrls: urls })
  } catch (err) {
    console.error(err)
    res.status(500).json({ success: false, error: err.message })
  }
}

upload.js

import S3 from 'aws-sdk/clients/s3'
// or const S3 = require('aws-sdk/clients/s3')

export default (buffer, fileParams) => {
// or module.exports = (buffer, fileParams) => {
  const params = {
    Bucket: 'my-s3-bucket',
    Key: fileParams.fileName,
    Body: buffer,
    ContentType: fileParams.fileType,
    ContentEncoding: fileParams.fileEnc,
  }
  return s3.upload(params).promise()
}
person josiahwiebe    schedule 11.12.2019
comment
Если вы буферизуете весь файл в памяти, вы могли бы также использовать хранилище памяти Multer, нет? - person ericsicons; 30.06.2021