Bir akışı s3.upload () öğesine yönlendirin


95

Amazon S3'e çok büyük dosyaların akışını sağlamak için şu anda s3-upload-stream adlı bir node.js eklentisini kullanıyorum . Çok parçalı API kullanır ve çoğunlukla çok iyi çalışır.

Ancak, bu modül yaşını gösteriyor ve zaten üzerinde değişiklik yapmak zorunda kaldım (yazar da bunu reddetti). Bugün Amazon ile başka bir sorunla karşılaştım ve yazarın tavsiyesini almak ve yüklemelerimi gerçekleştirmek için resmi aws-sdk'yi kullanmaya başlamak istiyorum.

FAKAT.

Resmi SDK, borulamayı desteklemiyor gibi görünüyor s3.upload(). S3.upload'ın doğası, okunabilir akışı S3 yapıcısına argüman olarak iletmeniz gerektiğidir.

Çeşitli dosya işlemeyi gerçekleştiren yaklaşık 120'den fazla kullanıcı kodu modülüm var ve bunlar çıktılarının son hedefinden bağımsızdırlar. Motor onlara aktarılabilir bir yazılabilir çıktı akışı verir ve buna boru gönderir. Tüm modüllere kod eklemeden onlara bir AWS.S3nesne verip onu çağırmalarını upload()isteyemiyorum. Kullanmamın nedeni s3-upload-streamboruyu desteklemesiydi.

Aws-sdk yapmanın s3.upload()akışı yönlendirebileceğim bir şey var mı?

Yanıtlar:


137

S3 upload()işlevini node.js stream.PassThrough()akışıyla sarın .

İşte bir örnek:

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
Harika, bu benim çok çirkin hackimi çözdü = -) stream.PassThrough () işlevinin gerçekte ne yaptığını açıklayabilir misiniz?
mraxus

6
Bunu yaptığınızda PassThrough akışınız kapanıyor mu? PassThrough akışıma ulaşmak için s3.upload'daki kapanışı ilerletmek için çok fazla zaman geçiriyorum.
four43

7
yüklenen dosyanın boyutu 0 bayttır. Aynı verileri kaynak akışından dosya sistemine aktarırsam her şey iyi çalışıyor. Herhangi bir fikir?
Radar155

3
Bir geçiş akışı, kendisine yazılan baytları alır ve bunları çıkarır. Bu, siz yazarken aws-sdk'nin okuyacağı yazılabilir bir akış döndürmenizi sağlar. Ayrıca s3.upload () 'dan yanıt nesnesini döndürürdüm çünkü aksi takdirde yüklemenin tamamlandığından emin olamazsınız.
reconbot

1
Bu, okunabilir akışı Body'ye iletmekle aynı değil, daha fazla kodla mı? AWS SDK, PassThrough akışında read () 'yi çağırmaya devam edecek, bu nedenle S3'e kadar gerçek bir borulama yoktur. Tek fark ortada fazladan bir akış olmasıdır.
ShadowChaser

96

Biraz geç cevap, umarım başkasına yardımcı olabilir. Hem yazılabilir akışı hem de sözü döndürebilirsiniz, böylece yükleme bittiğinde yanıt verilerini alabilirsiniz.

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

Ve işlevi aşağıdaki gibi kullanabilirsiniz:

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

const pipeline = readStream.pipe(writeStream);

Şimdi sözünüzü kontrol edebilirsiniz:

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

Veya stream.pipe()dönüş akışı olarak.Writable, hedef (yukarıdaki writeStream değişkeni), bir boru zincirine izin verir, olaylarını da kullanabiliriz:

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

Harika görünüyor, ama benim tarafımda bu hatayı alıyorum stackoverflow.com/questions/62330721/…
Arco Voltaico

sadece sorunuzu yanıtladı. Umarım yardımcı olur.
Ahmet Çetin

49

Kabul edilen cevapta işlev, yükleme tamamlanmadan sona erer ve bu nedenle yanlıştır. Aşağıdaki kod, okunabilir bir akıştan doğru şekilde yönlendirir.

Referans yükle

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

Ayrıca bir adım daha ileri gidebilir ve aşağıdakileri kullanarak ilerleme bilgilerini alabilirsiniz ManagedUpload:

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

ManagedUpload referansı

Mevcut olayların listesi


1
aws-sdk artık 2.3.0+ sürümünde yerleşik vaatler sunuyor, böylece artık bunları kaldırmak zorunda değilsiniz. s3.upload (params) .promise (). then (data => data) .catch (error => error);
DBrown

1
@DBrown İşaretçi için teşekkürler! Cevabı buna göre güncelledim.
2017

1
@tsuz, çözümünüzü uygulamaya çalışırken bana bir hata verin:, TypeError: dest.on is not a functionherhangi bir fikriniz neden?
FireBrand

Nedir dest.on? Bir örnek gösterebilir misin? @FireBrand
tsuz

9
Bu, kabul edilen cevabın eksik olduğunu söylüyor, ancak @ Womp'un güncellenmiş gönderisinde belirtildiği gibi s3.upload'a borulamayla çalışmıyor. Bu cevabın başka bir şeyin borulu çıktısını alacak şekilde güncellenmesi çok faydalı olacaktır!
MattW

6

Cevaplardan hiçbiri benim için işe yaramadı çünkü şunu yapmak istedim:

  • Boru hattı s3.upload()
  • Sonucunu s3.upload()başka bir akıma aktarın

Kabul edilen cevap ikincisini yapmaz. Diğerleri, akış borularıyla çalışırken çalışması zahmetli olan sözde API'ye güveniyor.

Bu, kabul edilen cevaba ilişkin benim değiştirmem.

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


Harika görünüyor, ama benim tarafımda bu hatayı alıyorum stackoverflow.com/questions/62330721/…
Arco Voltaico

5

Type Script çözümü:
Bu örnek şunları kullanır:

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

Ve eşzamansız işlev:

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

Bu yöntemi şöyle bir yerde arayın:

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

4

Buradaki en çok kabul gören cevapta dikkat edilmesi gereken nokta şudur: Eğer boru gibi kullanıyorsanız, fonksiyonda geçişi döndürmeniz gerekir.

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

Aksi takdirde, bir hata atmadan sessizce bir sonrakine geçecek veya TypeError: dest.on is not a functionişlevi nasıl yazdığınıza bağlı olarak bir hata atacaktır.


3

İstemciden s3'e başarılı bir şekilde aktarabildiğim birine yardımcı oluyorsa:

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

Sunucu tarafı kodu reqbir akış nesnesi olduğunu varsayar , benim durumumda istemciden başlıklarda dosya bilgisi ayarlanmış olarak gönderilmiştir.

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

Evet geleneği bozuyor ama özüne bakarsanız, sap parçalayıcı, garson vb. Kullanarak bulduğum her şeyden çok daha temiz ...

Pragmatizm için +1 ve yardımı için @ SalehenRahman'a teşekkürler.


multer, busboy çok parçalı / form veri yüklemelerini yönetir. req as a stream, istemci XMLHttpRequest'ten gövde olarak bir arabellek gönderdiğinde çalışır.
André Werlang

Açıklığa kavuşturmak gerekirse, yükleme müşteriden değil arka uçtan yapılıyor değil mi?
numX

Evet, arka uçta akışı "
kanalize ediyor

3

S3 api yükleme işlevini kullandıklarında ve sıfır baytlık bir dosyanın s3'te (@ Radar155 ve @gabo) sona erdiğinden şikayet edenler için - Ben de bu sorunu yaşadım.

İkinci bir PassThrough akışı oluşturun ve tüm verileri birinciden ikinciye aktarın ve o saniyeye referansı s3'e iletin. Bunu birkaç farklı şekilde yapabilirsiniz - muhtemelen kirli bir yol, ilk akıştaki "veri" olayını dinlemek ve ardından aynı verileri ikinci akışa yazmaktır - benzer şekilde "son" olayı için de - sadece çağırın ikinci akıştaki bitiş işlevi. Bunun aws api'de, düğüm sürümünde veya başka bir sorunda bir hata olup olmadığı hakkında hiçbir fikrim yok - ama benim için sorunu çözdü.

İşte nasıl göründüğü:

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

Bu aslında benim için de çalıştı. S3 yükleme işlevi, çok parçalı bir yükleme kullanıldığında sessizce "ölüyordu", ancak çözümünüzü kullanırken iyi çalıştı (!). Teşekkürler! :)
jhdrn

İkinci yayına neden ihtiyaç duyulduğu konusunda biraz bilgi verebilir misiniz?
noob7

2

Diğer yanıtların ardından ve Node.js için en son AWS SDK'yı kullanarak, await sözdizimini ve S3'ün vaadini kullanarak s3 upload () işlevi bir akışı kabul ettiğinden çok daha temiz ve daha basit bir çözüm var:

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

Bu, yazarın bahsettiği "çok büyük bir dosyayı okumak" özel kullanım durumu için işe yarar, ancak diğer yanıtlar, bir dosyanın bağlamı dışındaki akışları kullanıyorsanız (örneğin, s3'e bir mongo imleç akışı yazmaya çalışırken) hala geçerlidir. Hala bir PassThrough akış + boru kullanmanız gereken yerlerde)
Ken Colton

0

KnexJS kullanıyorum ve akış API'larını kullanırken sorun yaşadım. Sonunda düzelttim, umarım aşağıdakiler birine yardımcı olur.

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

Akışın boyutunu biliyorsanız , akışı aşağıdaki gibi yüklemek için minio-j'leri kullanabilirsiniz :

  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")
  })
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.