web-dev-qa-db-fra.com

Diriger un flux vers s3.upload ()

J'utilise actuellement un plug-in node.js appelé s3-upload-stream pour diffuser des fichiers très volumineux vers Amazon S3. Il utilise l'API multipart et cela fonctionne très bien dans la plupart des cas.

Cependant, ce module montre son âge et j'ai déjà dû le modifier (l'auteur l'a également déconseillé). Aujourd'hui, j'ai rencontré un autre problème avec Amazon et j'aimerais vraiment suivre la recommandation de l'auteur et commencer à utiliser le logiciel officiel aws-sdk pour effectuer mes téléchargements.

MAIS.

Le SDK officiel ne semble pas prendre en charge la tuyauterie à s3.upload(). La nature de s3.upload est que vous devez transmettre le flux lisible en tant qu’argument au constructeur S3.

J'ai environ 120 modules de code utilisateur qui traitent différents fichiers, et ils sont agnostiques vis-à-vis de la destination finale de leur sortie. Le moteur leur tend un flux de sortie inscriptible pouvant être enregistré. Je ne peux pas leur donner un objet AWS.S3 et leur demander d'appeler upload() sans ajouter de code à tous les modules. La raison pour laquelle j'ai utilisé s3-upload-stream était parce qu'elle prenait en charge la tuyauterie.

Existe-t-il un moyen de rendre aws-sdk s3.upload() quelque chose pour lequel je peux diriger le flux?

40
womp

Enveloppez la fonction S3 upload() avec le flux node.js stream.PassThrough().

Voici un exemple:

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;
}
74
Casey Benko

Dans la réponse acceptée, la fonction se termine avant la fin du téléchargement. Elle est donc incorrecte. Le code ci-dessous conduit correctement à partir d'un flux lisible.

Référence de téléchargement

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

Vous pouvez également aller plus loin et afficher les informations de progression en utilisant ManagedUpload en tant que tel:

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

Référence ManagedUpload

Une liste des événements disponibles

23
tsuz

Une réponse un peu tardive, cela pourrait aider quelqu'un d'autre, espérons-le. Vous pouvez renvoyer à la fois le flux en écriture et la promesse, de sorte que vous puissiez obtenir les données de réponse à la fin du téléchargement.

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

Et vous pouvez utiliser la fonction comme suit:

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

readStream.pipe(writeStream);
promise.then(console.log);
14
Ahmet Cetin

Solution de script de type: 
Cet exemple utilise: 

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

Et fonction asynchrone:

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

Appelez cette méthode quelque part comme:

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

Si cela aide quelqu'un que j'ai pu diffuser du s3 client avec succès vers s3:

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

Le code côté serveur suppose que req est un objet de flux, dans mon cas, il a été envoyé à partir du client avec les informations de fichier définies dans les en-têtes.

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

Oui, cela brise les conventions, mais si vous regardez le Gist, il est beaucoup plus propre que tout ce que j'ai trouvé avec multer, busboy, etc.

+1 pour le pragmatisme et merci à @SalehenRahman pour son aide.

1
mattdlockyer

Pour ceux qui se plaignent du fait qu’ils utilisent la fonction s3 api upload et un fichier de zéro octet se retrouvent sur s3 (@ Radar155 et @gabo), j’ai également eu ce problème.

Créez un deuxième flux PassThrough, dirigez simplement toutes les données du premier au second et transmettez la référence à cette seconde à s3. Vous pouvez le faire de deux manières différentes. Une manière fâcheuse est peut-être d'écouter l'événement "data" sur le premier flux, puis d'écrire ces mêmes données dans le second flux - de la même manière pour l'événement "end" - il suffit d'appeler la fonction de fin sur le deuxième flux. Je ne sais pas s'il s'agit d'un bogue de l'api aws, de la version de node ou d'un autre problème, mais cela a fonctionné pour moi.

Voici à quoi cela pourrait ressembler:

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