Invia un flusso a s3.upload ()


95

Attualmente sto utilizzando un plug -in node.js chiamato s3-upload-stream per eseguire lo streaming di file molto grandi su Amazon S3. Utilizza l'API multipart e per la maggior parte funziona molto bene.

Tuttavia, questo modulo mostra la sua età e ho già dovuto modificarlo (anche l'autore lo ha deprecato). Oggi mi sono imbattuto in un altro problema con Amazon e vorrei davvero prendere il consiglio dell'autore e iniziare a utilizzare aws-sdk ufficiale per completare i miei caricamenti.

MA.

L'SDK ufficiale non sembra supportare il piping s3.upload(). La natura di s3.upload è che devi passare il flusso leggibile come argomento al costruttore S3.

Ho circa 120+ moduli di codice utente che eseguono varie elaborazioni di file e sono agnostici rispetto alla destinazione finale del loro output. Il motore trasmette loro un flusso di output scrivibile tramite pipe e loro si collegano ad esso. Non posso dare loro un AWS.S3oggetto e chiedere loro di chiamarlo upload()senza aggiungere codice a tutti i moduli. Il motivo per cui l'ho usato s3-upload-streamera perché supportava le tubazioni.

C'è un modo per rendere aws-sdk s3.upload()qualcosa a cui posso collegare lo stream?

Risposte:


137

Avvolgi la upload()funzione S3 con il stream.PassThrough()flusso node.js.

Ecco un esempio:

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
Fantastico, questo ha risolto il mio brutto trucco = -) Puoi spiegare cosa fa effettivamente stream.PassThrough ()?
mraxus

6
Il tuo flusso PassThrough si chiude quando esegui questa operazione? Mi sto divertendo moltissimo a proporre la chiusura in s3.upload per raggiungere il mio flusso PassThrough.
four43

7
la dimensione del file caricato è 0 byte. Se installo gli stessi dati dal flusso di origine al file system, tutto funziona bene. Qualche idea?
Radar155

3
Un flusso passthrough prenderà i byte scritti su di esso e li emetterà. Ciò ti consente di restituire un flusso scrivibile da cui aws-sdk leggerà mentre ci scrivi. Restituirei anche l'oggetto risposta da s3.upload () perché altrimenti non puoi assicurarti che il caricamento venga completato.
ricognizione

1
Non è lo stesso che passare il flusso leggibile a Body ma con più codice? L'SDK AWS chiamerà ancora read () sul flusso PassThrough, quindi non c'è un vero piping fino a S3. L'unica differenza è che c'è un flusso in più nel mezzo.
ShadowChaser

96

Risposta un po 'tardiva, potrebbe aiutare qualcun altro, si spera. Puoi restituire sia il flusso scrivibile che la promessa, in modo da ottenere i dati di risposta al termine del caricamento.

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

E puoi usare la funzione come segue:

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

const pipeline = readStream.pipe(writeStream);

Ora puoi controllare la promessa:

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

O come stream.pipe()restituisce stream.Writable, la destinazione (variabile writeStream sopra), consentendo una catena di pipe, possiamo anche usare i suoi eventi:

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

Sembra grande, ma da parte mia sto ottenendo questo errore stackoverflow.com/questions/62330721/...
Arco voltaico

ho appena risposto alla tua domanda. spero che sia d'aiuto.
Ahmet Cetin

49

Nella risposta accettata, la funzione termina prima che il caricamento sia completo e quindi non è corretto. Il codice seguente trasmette correttamente da un flusso leggibile.

Carica riferimento

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

Puoi anche fare un ulteriore passo avanti e generare informazioni sull'avanzamento utilizzando ManagedUploadcome tale:

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

Riferimento a ManagedUpload

Un elenco di eventi disponibili


1
aws-sdk ora offre promesse integrate nella 2.3.0+, quindi non devi più revocarle. s3.upload (params) .promise (). then (data => data) .catch (error => error);
DBrown

1
@DBrown Grazie per il puntatore! Ho aggiornato la risposta, di conseguenza.
tsuz

1
@tsuz, provando a implementare la tua soluzione mi dà un errore:, TypeError: dest.on is not a functionqualche idea perché?
FireBrand

Che cos'è dest.on? Puoi mostrare un esempio? @FireBrand
tsuz

9
Questo dice che la risposta accettata è incompleta ma non funziona con il piping a s3.upload come indicato nel post aggiornato di @ Womp. Sarebbe molto utile se questa risposta fosse aggiornata per prendere l'uscita convogliata di qualcos'altro!
MattW

6

Nessuna delle risposte ha funzionato per me perché volevo:

  • Pipe into s3.upload()
  • Convoglia il risultato di s3.upload()in un altro flusso

La risposta accettata non fa il secondo. Gli altri si affidano all'api della promessa, che è complicato da lavorare quando si lavora con i tubi di flusso.

Questa è la mia modifica della risposta accettata.

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


Sembra fantastico, ma da parte mia ricevo questo errore
stackoverflow.com/questions/62330721/…

5

Digitare Soluzione script:
questo esempio utilizza:

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

E funzione asincrona:

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

Chiama questo metodo da qualche parte come:

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

4

La cosa da notare qui nella risposta più accettata sopra è che: devi restituire il passaggio nella funzione se stai usando pipe come,

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

Altrimenti passerà silenziosamente al successivo senza generare un errore o genererà un errore a TypeError: dest.on is not a functionseconda di come hai scritto la funzione


3

Se aiuta qualcuno che sono riuscito a trasmettere in streaming dal client a s3 con successo:

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

Il codice lato server presume reqsia un oggetto flusso, nel mio caso è stato inviato dal client con le informazioni sul file impostate nelle intestazioni.

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

Sì, infrange le convenzioni ma se guardi il succo è molto più pulito di qualsiasi altra cosa che ho trovato usando multer, cameriere ecc ...

+1 per pragmatismo e grazie a @SalehenRahman per il suo aiuto.


multer, busboy gestisce caricamenti multipart / form-data. req come flusso funziona quando il client invia un buffer come corpo da XMLHttpRequest.
André Werlang

Per chiarire, il caricamento viene eseguito dal back-end, non è il client giusto?
numX

Sì, sta "trasmettendo" lo stream, sul backend, ma proviene da un frontend
mattdlockyer

3

Per coloro che si lamentano del fatto che quando usano la funzione di caricamento api s3 e un file a zero byte finisce su s3 (@ Radar155 e @gabo) - Ho avuto anche questo problema.

Crea un secondo flusso PassThrough e convoglia semplicemente tutti i dati dal primo al secondo e passa il riferimento a quel secondo a s3. Puoi farlo in un paio di modi diversi - forse un modo sporco è ascoltare l'evento "dati" sul primo flusso e poi scrivere gli stessi dati sul secondo flusso - allo stesso modo per l'evento "fine" - chiama semplicemente la funzione di fine sul secondo flusso. Non ho idea se si tratti di un bug nell'API di aws, nella versione di node o in qualche altro problema, ma per me ha risolto il problema.

Ecco come potrebbe apparire:

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

Questo in realtà ha funzionato anche per me. La funzione di caricamento S3 è semplicemente "morta" silenziosamente ogni volta che è stato utilizzato un caricamento in più parti, ma quando si utilizza la soluzione ha funzionato bene (!). Grazie! :)
jhdrn

Puoi fornire alcune informazioni sul motivo per cui è necessario il secondo flusso?
noob7

2

Seguendo le altre risposte e utilizzando l'ultimo SDK AWS per Node.js, c'è una soluzione molto più pulita e più semplice poiché la funzione upload () di s3 accetta un flusso, utilizzando la sintassi di await e la promessa di S3:

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

Funziona per il caso d'uso specifico di "leggere un file molto grande" menzionato dall'autore, ma le altre risposte sono ancora valide se stai usando flussi al di fuori del contesto di un file (per esempio provando a scrivere un flusso di cursore mongo su s3 dove è ancora necessario utilizzare un flusso PassThrough + pipe)
Ken Colton

0

Sto usando KnexJS e ho avuto un problema con la loro API di streaming. Alla fine l'ho risolto, spero che quanto segue possa aiutare qualcuno.

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

Se conosci la dimensione dello stream puoi usare minio-js per caricare lo stream in questo modo:

  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")
  })
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.