Я создаю бэкэнд приложения. Клиенты отправляют файл как тело запроса на сервер, затем сервер загружает файл в AWS S3. Сервер использует NodeJS и веб-фреймворк KOA.
Если я использую raw-body
чтобы получить тело сообщения в буфер, когда файл большой, буфер большой и вызывает ошибку нехватки памяти.
Если я непосредственно передаю ctx.req (объект IncomingMessage) в S3.putObject, AWS SDK выдает ошибку " Cannot determine length of [object Object]
удается Cannot determine length of [object Object]
, похоже, что AWS SDK пытается получить длину потока, а затем начинает загрузку из нескольких частей.
AWS SDK версия 2.383.0 (в настоящее время последняя)
NodeJS 10.14.2
В настоящее время я написал функцию, которая читает из IncomingMessage как поток, ждет события данных для заполнения большого буфера (16 МБ), а затем выполняет загрузку нескольких частей на S3, это хорошо решает проблему, но я все еще ищу для лучшего решения.
После нескольких месяцев, я думаю, мое окончательное решение является стабильным и надежным.
Основной концепцией является прием из хранилища потока IncomingMessage в буфер, после того как буфер достигнет размера, затем поместит текущую часть в S3, затем продолжит чтение потока до конца.
const uploaderLogger = Log4js.getLogger('customUploader');
function customMultiPartUpload(s3, bucket, key, incomingMessage, partSizeInByte) {
return new Promise((resolve) => {
partSizeInByte = partSizeInByte || uploadBufferMB * 1024 * 1024;
uploaderLogger.debug('part size is ${partSizeInByte}');
let uploadId = null;
let partNumber = 0;
let parts = [];
let fileSize = 0;
let reserveBuffer = Buffer.alloc(0);
const sendBuffer = Buffer.alloc(partSizeInByte);
const md5Hash = Crypto.createHash('md5');
const doUpload = async (uploadBuffer) => {
if (!uploadId) {
uploaderLogger.debug('multipart upload not initialized');
const createData = await s3.createMultipartUpload({
Bucket: bucket,
Key: key
}).promise();
uploadId = createData.UploadId;
uploaderLogger.debug('uploadId ${uploadId}');
partNumber = 0;
}
fileSize += uploadBuffer.length;
uploaderLogger.debug('buffer length ${uploadBuffer.length}, total ${fileSize}');
partNumber += 1;
uploaderLogger.debug('part number ${partNumber}');
md5Hash.update(uploadBuffer);
const partData = await s3.uploadPart({
Bucket: bucket,
Key: key,
PartNumber: partNumber,
UploadId: uploadId,
Body: uploadBuffer
}).promise();
parts.push({
PartNumber: partNumber,
ETag: partData.ETag
});
uploaderLogger.debug('etag ${partData.ETag}');
};
incomingMessage.on('data', async (chunkBuffer) => {
incomingMessage.pause();
reserveBuffer = Buffer.concat([ reserveBuffer, chunkBuffer ]);
if (reserveBuffer.length > partSizeInByte) {
do {
reserveBuffer.copy(sendBuffer, 0, 0, partSizeInByte);
reserveBuffer = reserveBuffer.slice(partSizeInByte);
await doUpload(sendBuffer);
} while (reserveBuffer.length > partSizeInByte);
}
incomingMessage.resume();
});
incomingMessage.on('end', async () => {
uploaderLogger.debug('stream end');
if (reserveBuffer.length > 0) {
await doUpload(reserveBuffer);
}
if (uploadId) {
uploaderLogger.debug('uploadId not null');
await s3.completeMultipartUpload({
Bucket: bucket,
Key: key,
UploadId: uploadId,
MultipartUpload: {
Parts: parts
}
}).promise();
uploaderLogger.debug('multipart upload complete');
}
const hash = md5Hash.digest('hex');
resolve({
size: fileSize,
hash: hash
});
uploaderLogger.debug('return file size ${fileSize}, hash ${hash}');
});
});
}
Отрегулируйте partSizeInByte
в соответствии с использованием памяти вашего сервера. Слишком большой размер детали может привести к OOM, когда сервер обрабатывает много запросов, слишком малый размер детали может быть меньше ограничения части S3.