Проведіть потік до s3.upload ()


89

В даний час я використовую плагін node.js, який називається s3-upload-stream, щоб передавати дуже великі файли на Amazon S3. Він використовує багаточастинний API, і здебільшого він працює дуже добре.

Однак цей модуль показує свій вік, і мені вже довелося його модифікувати (автор його також не підтримував). Сьогодні я зіткнувся з черговою проблемою з Amazon, і я б дуже хотів скористатися рекомендацією автора і почати використовувати офіційний aws-sdk для здійснення своїх завантажень.

АЛЕ.

Офіційний SDK, схоже, не підтримує конвеєр s3.upload(). Природа s3.upload полягає в тому, що вам потрібно передати читабельний потік як аргумент конструктору S3.

У мене є приблизно 120+ модулів коду користувача, які виконують різну обробку файлів, і вони є агностичними для кінцевого пункту призначення своїх результатів. Двигун передає їм трубопровідний вихідний потік, і вони подають його до нього. Я не можу вручити їм AWS.S3об’єкт і попросити його викликати upload()його, не додаючи код до всіх модулів. Причиною, яку я використав, s3-upload-streamбуло те, що вона підтримувала трубопроводи.

Чи є спосіб зробити aws-sdk тим, до s3.upload()чого я можу направити потік?

Відповіді:


132

Оберніть функцію S3 потоком upload()node.js.stream.PassThrough()

Ось приклад:

inputStream
  .pipe(uploadFromStream(s3));

function uploadFromStream(s3) {
  var pass = new stream.PassThrough();

  var params = {Bucket: BUCKET, Key: KEY, Body: pass};
  s3.upload(params, function(err, data) {
    console.log(err, data);
  });

  return pass;
}

2
Чудово, це вирішило мій дуже потворний хак = -) Чи можете ви пояснити, що насправді робить stream.PassThrough ()?
mraxus

6
Чи закривається ваш потік PassThrough, коли ви це робите? Я відчуваю час, промотуючи закриття в s3.upload, щоб потрапити в мій потік PassThrough.
four43

7
розмір завантаженого файлу - 0 байт. Якщо я передаю однакові дані з вихідного потоку у файлову систему, все працює добре. Будь-яка ідея?
Радар155,

3
Прохідний потік прийме записані в нього байти і виведе їх. Це дозволяє повернути потік для запису, з якого aws-sdk буде читати, коли ви в нього пишете. Я також поверну об'єкт відповіді з s3.upload (), оскільки в іншому випадку ви не можете переконатися, що завантаження завершено.
переконструювати

1
звідки s3парам всередині труби і streamзвідки?
Блекджек

94

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

const AWS = require('aws-sdk');
const stream = require('stream');

const uploadStream = ({ Bucket, Key }) => {
  const s3 = new AWS.S3();
  const pass = new stream.PassThrough();
  return {
    writeStream: pass,
    promise: s3.upload({ Bucket, Key, Body: pass }).promise(),
  };
}

І ви можете використовувати функцію наступним чином:

const { writeStream, promise } = uploadStream({Bucket: 'yourbucket', Key: 'yourfile.mp4'});
const readStream = fs.createReadStream('/path/to/yourfile.mp4');

const pipeline = readStream.pipe(writeStream);

Тепер ви можете перевірити обіцянку:

promise.then(() => {
  console.log('upload completed successfully');
}).catch((err) => {
  console.log('upload failed.', err.message);
});

Або як stream.pipe()повертає потік. Writable, пункт призначення (змінна writeStream вище), що дозволяє створити ланцюжок каналів, ми також можемо використовувати його події:

 pipeline.on('close', () => {
   console.log('upload successful');
 });
 pipeline.on('error', (err) => {
   console.log('upload failed', err.message)
 });

Вона відмінно виглядає, але на моєму боці я отримую цю помилку stackoverflow.com/questions/62330721 / ...
Arco Voltaico

щойно відповів на ваше запитання. сподіваюся, це допоможе.
Ахмет Четін,

48

У прийнятій відповіді функція закінчується до завершення завантаження, а отже, вона є неправильною. Код нижче подає коректно з читабельного потоку.

Завантажити посилання

async function uploadReadableStream(stream) {
  const params = {Bucket: bucket, Key: key, Body: stream};
  return s3.upload(params).promise();
}

async function upload() {
  const readable = getSomeReadableStream();
  const results = await uploadReadableStream(readable);
  console.log('upload complete', results);
}

Ви також можете зробити крок далі і вивести інформацію про хід, використовуючи ManagedUploadяк таку:

const manager = s3.upload(params);
manager.on('httpUploadProgress', (progress) => {
  console.log('progress', progress) // { loaded: 4915, total: 192915, part: 1, key: 'foo.jpg' }
});

Посилання на ManagedUpload

Список доступних подій


1
aws-sdk тепер пропонує обіцянки, вбудовані в 2.3.0+, тому вам більше не доведеться їх виконувати. s3.upload (params) .promise (). then (data => data) .catch (error => error);
DBrown

1
@DBrown Дякую за вказівник! Відповідно я оновив відповідь.
цуз

1
@tsuz, намагаючись реалізувати своє рішення, мені видають помилку:, TypeError: dest.on is not a functionбудь-яка ідея чому?
FireBrand

Що це таке dest.on? Ви можете показати приклад? @FireBrand
tsuz

9
Це означає, що прийнята відповідь є неповною, але вона не працює з трубопроводом до s3.upload, як зазначено в оновленому дописі @ Womp. Було б дуже корисно, якби ця відповідь була оновлена, щоб взяти конвеєрне виведення чогось іншого!
MattW

6

Жодна з відповідей у ​​мене не спрацювала, бо я хотів:

  • Труба в s3.upload()
  • Результат направити s3.upload()в інший потік

Прийнята відповідь не робить останнього. Інші покладаються на обіцяний api, який громіздко працювати при роботі з потоковими трубами.

Це моя модифікація прийнятої відповіді.

const s3 = new S3();

function writeToS3({Key, Bucket}) {
  const Body = new stream.PassThrough();

  s3.upload({
    Body,
    Key,
    Bucket: process.env.adpBucket
  })
   .on('httpUploadProgress', progress => {
       console.log('progress', progress);
   })
   .send((err, data) => {
     if (err) {
       Body.destroy(err);
     } else {
       console.log(`File uploaded and available at ${data.Location}`);
       Body.destroy();
     }
  });

  return Body;
}

const pipeline = myReadableStream.pipe(writeToS3({Key, Bucket});

pipeline.on('close', () => {
  // upload finished, do something else
})
pipeline.on('error', () => {
  // upload wasn't successful. Handle it
})


Це виглядає чудово, але на моєму боці я отримую цю помилку
stackoverflow.com/questions/62330721/…

5

Рішення сценарію типу:
Цей приклад використовує:

import * as AWS from "aws-sdk";
import * as fsExtra from "fs-extra";
import * as zlib from "zlib";
import * as stream from "stream";

І функція асинхронізації:

public async saveFile(filePath: string, s3Bucket: AWS.S3, key: string, bucketName: string): Promise<boolean> { 

         const uploadStream = (S3: AWS.S3, Bucket: string, Key: string) => {
            const passT = new stream.PassThrough();
            return {
              writeStream: passT,
              promise: S3.upload({ Bucket, Key, Body: passT }).promise(),
            };
          };
        const { writeStream, promise } = uploadStream(s3Bucket, bucketName, key);
        fsExtra.createReadStream(filePath).pipe(writeStream);     //  NOTE: Addition You can compress to zip by  .pipe(zlib.createGzip()).pipe(writeStream)
        let output = true;
        await promise.catch((reason)=> { output = false; console.log(reason);});
        return output;
}

Викличте цей метод десь на зразок:

let result = await saveFileToS3(testFilePath, someS3Bucket, someKey, someBucketName);

4

Річ тут, на яку слід звернути увагу у найбільш прийнятій відповіді вище, полягає в наступному: Вам потрібно повернути прохід у функції, якщо ви використовуєте конвеєр типу

fs.createReadStream(<filePath>).pipe(anyUploadFunction())

function anyUploadFunction () { 
 let pass = new stream.PassThrough();
 return pass // <- Returning this pass is important for the stream to understand where it needs to write to.
}

В іншому випадку він буде мовчки переходити до наступного, не видаючи помилки, або видасть помилку TypeError: dest.on is not a functionзалежно від того, як ви написали функцію


3

Якщо це комусь допомагає, я зміг успішно передати з клієнта на s3:

https://gist.github.com/mattlockyer/532291b6194f6d9ca40cb82564db9d2a

Серверний код припускає, що reqце об’єкт потоку, у моєму випадку він був надісланий від клієнта з інформацією про файл, встановленою в заголовках.

const fileUploadStream = (req, res) => {
  //get "body" args from header
  const { id, fn } = JSON.parse(req.get('body'));
  const Key = id + '/' + fn; //upload to s3 folder "id" with filename === fn
  const params = {
    Key,
    Bucket: bucketName, //set somewhere
    Body: req, //req is a stream
  };
  s3.upload(params, (err, data) => {
    if (err) {
      res.send('Error Uploading Data: ' + JSON.stringify(err) + '\n' + JSON.stringify(err.stack));
    } else {
      res.send(Key);
    }
  });
};

Так, це порушує домовленості, але якщо поглянути на суть, це набагато чистіше, ніж будь-що інше, що я знайшов за допомогою мультера, автобуса тощо ...

+1 за прагматизм та подяку @SalehenRahman за допомогу.


multer, busboy обробляє завантаження даних із декількох частин / даних форми. req як потік працює, коли клієнт надсилає буфер як тіло з XMLHttpRequest.
Андре Верланг,

Для уточнення, завантаження виконується із задньої сторони, а не з клієнта, чи не так?
numX

Так, це "трубопровід" потоку, на серверній панелі, але він надійшов від інтерфейсу
mattdlockyer

3

Для тих, хто скаржиться, що коли вони використовують функцію завантаження api s3, а нульовий байт-файл потрапляє на s3 (@ Radar155 та @gabo) - у мене також була ця проблема.

Створіть другий потік PassThrough і просто переведіть усі дані від першого до другого і передайте посилання на цей другий s3. Ви можете зробити це декількома різними способами - можливо, брудним способом є прослуховування події "data" у першому потоці, а потім запис тих самих даних у другий потік - аналогічно для події "end" - просто зателефонуйте функція закінчення на другому потоці. Я не уявляю, чи це помилка в aws api, версія вузла чи якась інша проблема - але це обійшло цю проблему для мене.

Ось як це може виглядати:

var PassThroughStream = require('stream').PassThrough;
var srcStream = new PassThroughStream();

var rstream = fs.createReadStream('Learning/stocktest.json');
var sameStream = rstream.pipe(srcStream);
// interesting note: (srcStream == sameStream) at this point
var destStream = new PassThroughStream();
// call your s3.upload function here - passing in the destStream as the Body parameter
srcStream.on('data', function (chunk) {
    destStream.write(chunk);
});

srcStream.on('end', function () {
    dataStream.end();
});

Це насправді спрацювало і на мене. Функція завантаження S3 просто "тихо" вмирала, коли використовувалось багаточастинне завантаження, але при використанні вашого рішення вона працювала нормально (!). Дякую! :)
jhdrn

Чи можете ви дати інформацію про те, навіщо потрібен другий потік?
noob7

1

Слідом за іншими відповідями та використанням останнього AWS SDK для Node.js, є набагато більш чисте і просте рішення, оскільки функція s3 upload () приймає потік, використовуючи синтаксис await та обіцянку S3:

var model = await s3Client.upload({
    Bucket : bucket,
    Key : key,
    ContentType : yourContentType,
    Body : fs.createReadStream(path-to-file)
}).promise();

0

Я використовую KnexJS і виникла проблема з використанням їх потокового API. Я нарешті це виправив, сподіваюся, наступне допоможе комусь.

const knexStream = knex.select('*').from('my_table').stream();
const passThroughStream = new stream.PassThrough();

knexStream.on('data', (chunk) => passThroughStream.write(JSON.stringify(chunk) + '\n'));
knexStream.on('end', () => passThroughStream.end());

const uploadResult = await s3
  .upload({
    Bucket: 'my-bucket',
    Key: 'stream-test.txt',
    Body: passThroughStream
  })
  .promise();

-3

Якщо ви знаєте розмір потоку, ви можете використовувати minio-js для завантаження потоку таким чином:

  s3Client.putObject('my-bucketname', 'my-objectname.ogg', stream, size, 'audio/ogg', function(e) {
    if (e) {
      return console.log(e)
    }
    console.log("Successfully uploaded the stream")
  })
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.