Sambungkan aliran ke s3.upload ()


95

Saat ini saya menggunakan plugin node.js yang disebut s3-upload-stream untuk mengalirkan file yang sangat besar ke Amazon S3. Ia menggunakan multipart API dan sebagian besar bekerja dengan sangat baik.

Namun, modul ini menunjukkan usianya dan saya sudah harus mengubahnya (penulis juga sudah menghentikannya). Hari ini saya mengalami masalah lain dengan Amazon, dan saya benar-benar ingin mengambil rekomendasi penulis dan mulai menggunakan aws-sdk resmi untuk menyelesaikan unggahan saya.

TAPI.

SDK resmi tampaknya tidak mendukung penyaluran ke s3.upload(). Sifat s3.upload adalah Anda harus meneruskan aliran yang dapat dibaca sebagai argumen ke konstruktor S3.

Saya memiliki sekitar 120+ modul kode pengguna yang melakukan berbagai pemrosesan file, dan mereka agnostik ke tujuan akhir keluarannya. Mesin memberi mereka aliran keluaran yang dapat ditulis dalam pipa, dan mereka menyalurkannya ke pipa. Saya tidak dapat memberikan AWS.S3objek kepada mereka dan meminta mereka untuk memanggilnya upload()tanpa menambahkan kode ke semua modul. Alasan saya menggunakan s3-upload-streamadalah karena mendukung pemipaan.

Apakah ada cara untuk membuat aws-sdk menjadi s3.upload()sesuatu yang dapat saya gunakan untuk menyalurkan streaming?

Jawaban:


137

Bungkus upload()fungsi S3 dengan stream.PassThrough()aliran node.js.

Berikut contohnya:

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
Hebat, ini memecahkan peretasan saya yang sangat jelek = -) Bisakah Anda menjelaskan apa yang sebenarnya dilakukan oleh stream.PassThrough ()?
mraxus

6
Apakah aliran PassThrough Anda tertutup saat Anda melakukan ini? Saya mengalami heck waktu mendorong penutupan di s3.upload untuk mencapai aliran PassThrough saya.
empat43

7
ukuran file yang diunggah adalah 0 byte. Jika saya menyalurkan data yang sama dari aliran sumber ke sistem file, semuanya berfungsi dengan baik. Ada ide?
Radar155

3
Aliran passthrough akan mengambil byte yang ditulis ke sana dan mengeluarkannya. Ini memungkinkan Anda mengembalikan aliran yang dapat ditulis yang akan dibaca aws-sdk saat Anda menulisnya. Saya juga akan mengembalikan objek respons dari s3.upload () karena jika tidak, Anda tidak dapat memastikan unggahan selesai.
Reconbot

1
Bukankah ini sama dengan meneruskan aliran yang dapat dibaca ke Body tetapi dengan lebih banyak kode? AWS SDK masih akan memanggil read () pada aliran PassThrough sehingga tidak ada pemipaan yang benar hingga S3. Satu-satunya perbedaan adalah ada aliran ekstra di tengah.
ShadowChaser

96

Jawaban yang agak terlambat, semoga bisa membantu orang lain. Anda bisa mengembalikan streaming yang bisa ditulisi dan promise, sehingga Anda bisa mendapatkan data respons saat upload selesai.

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(),
  };
}

Dan Anda dapat menggunakan fungsinya sebagai berikut:

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

const pipeline = readStream.pipe(writeStream);

Sekarang Anda dapat memeriksa janji:

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

Atau sebagai stream.pipe()return stream.Writable, tujuan (variabel writeStream di atas), memungkinkan untuk rangkaian pipa, kita juga dapat menggunakan kejadiannya:

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

Kelihatannya bagus, tetapi di sisi saya, saya mendapatkan kesalahan ini stackoverflow.com/questions/62330721/…
Arco Voltaico

baru saja membalas pertanyaan Anda. semoga membantu.
Ahmet Cetin

49

Dalam jawaban yang diterima, fungsi berakhir sebelum unggahan selesai, dan karenanya, itu salah. Kode di bawah disalurkan dengan benar dari aliran yang dapat dibaca.

Unggah referensi

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);
}

Anda juga dapat melangkah lebih jauh dan menampilkan info kemajuan menggunakan ManagedUploadseperti:

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

Referensi ManagedUpload

Daftar acara yang tersedia


1
aws-sdk sekarang menawarkan promise yang dibangun di 2.3.0+, jadi Anda tidak perlu mencabutnya lagi. s3.upload (params) .promise (). then (data => data) .catch (error => error);
DBrown

1
@DBrown Terima kasih atas penunjuknya! Saya telah memperbarui jawabannya.
tsuz

1
@ Tsuz, mencoba menerapkan solusi Anda memberi saya kesalahan:, TypeError: dest.on is not a functiontahu mengapa?
FireBrand

Apa dest.on? Bisakah Anda menunjukkan contoh? @FireBrand
tz

9
Ini mengatakan bahwa jawaban yang diterima tidak lengkap tetapi tidak berfungsi dengan perpipaan ke s3.upload seperti yang ditunjukkan dalam posting terbaru @ Womp. Akan sangat membantu jika jawaban ini diperbarui untuk mengambil keluaran yang disalurkan dari sesuatu yang lain!
MattW

6

Tidak ada jawaban yang berhasil untuk saya karena saya ingin:

  • Pipa ke s3.upload()
  • Masukkan hasil s3.upload()ke aliran lain

Jawaban yang diterima tidak sesuai dengan yang terakhir. Yang lain mengandalkan api promise, yang tidak praktis untuk digunakan saat bekerja dengan pipa aliran.

Ini adalah modifikasi saya atas jawaban yang diterima.

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
})


Kelihatannya bagus, tetapi di sisi saya, saya mendapatkan kesalahan ini stackoverflow.com/questions/62330721/…
Arco Voltaico

5

Solusi Type Script:
Contoh ini menggunakan:

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

Dan fungsi async:

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;
}

Panggil metode ini di suatu tempat seperti:

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

4

Hal yang perlu diperhatikan di sini dalam jawaban yang paling diterima di atas adalah bahwa: Anda perlu mengembalikan pass dalam fungsi jika Anda menggunakan pipa seperti,

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.
}

Jika tidak, itu akan diam-diam pindah ke berikutnya tanpa menimbulkan kesalahan atau akan melempar kesalahan TypeError: dest.on is not a functiontergantung pada bagaimana Anda menulis fungsi tersebut


3

Jika itu membantu siapa pun, saya berhasil melakukan streaming dari klien ke s3:

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

Kode di reqsisi server mengasumsikan sebagai objek aliran, dalam kasus saya itu dikirim dari klien dengan info file yang ditetapkan di header.

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);
    }
  });
};

Ya itu melanggar konvensi tetapi jika Anda melihat intinya itu jauh lebih bersih daripada apa pun yang saya temukan menggunakan multer, busboy dll ...

+1 untuk pragmatisme dan terima kasih kepada @SalehenRahman atas bantuannya.


multer, busboy menangani unggahan multibagian / formulir-data. req sebagai aliran bekerja ketika klien mengirimkan buffer sebagai badan dari XMLHttpRequest.
André Werlang

Untuk memperjelas, unggahan dilakukan dari ujung belakang bukan klien kan?
numX

Ya, itu "menyalurkan" alirannya, PADA backend, tetapi itu berasal dari frontend
mattdlockyer

3

Bagi mereka yang mengeluh bahwa ketika mereka menggunakan fungsi unggah s3 api dan file nol byte berakhir di s3 (@ Radar155 dan @gabo) - Saya juga punya masalah ini.

Buat aliran PassThrough kedua dan cukup pipa semua data dari yang pertama ke yang kedua dan teruskan referensi ke detik itu ke s3. Anda dapat melakukan ini dengan beberapa cara berbeda - mungkin cara yang kotor adalah dengan mendengarkan peristiwa "data" pada aliran pertama dan kemudian menulis data yang sama ke aliran kedua - cara yang sama untuk acara "akhir" - panggil saja fungsi akhir pada aliran kedua. Saya tidak tahu apakah ini adalah bug di aws api, versi node atau beberapa masalah lain - tetapi ini mengatasi masalah untuk saya.

Berikut tampilannya:

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();
});

Ini benar-benar berhasil untuk saya juga. Fungsi unggahan S3 hanya "mati" secara diam-diam setiap kali unggahan multi bagian digunakan, tetapi saat menggunakan solusi Anda, itu berfungsi dengan baik (!). Terima kasih! :)
jhdrn

Bisakah Anda memberikan beberapa info tentang mengapa streaming kedua diperlukan?
noob7

2

Mengikuti jawaban lain dan menggunakan AWS SDK terbaru untuk Node.js, ada solusi yang jauh lebih bersih dan sederhana karena fungsi upload s3 () menerima aliran, menggunakan sintaks menunggu dan janji S3:

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

Ini berfungsi untuk kasus penggunaan khusus "membaca file yang sangat besar" yang disebutkan penulis, tetapi jawaban lain masih valid jika Anda menggunakan aliran di luar konteks file (misalnya mencoba menulis aliran kursor mongo ke s3 di mana Anda masih perlu menggunakan aliran + pipa PassThrough)
Ken Colton

0

Saya menggunakan KnexJS dan mengalami masalah saat menggunakan API streaming mereka. Saya akhirnya memperbaikinya, semoga berikut ini akan membantu seseorang.

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

Jika Anda mengetahui ukuran aliran, Anda dapat menggunakan minio-js untuk mengunggah aliran seperti ini:

  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")
  })
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.