web-dev-qa-db-fra.com

Fichier en streaming à partir de S3 avec Express, y compris des informations sur la longueur et le type de fichier

En utilisant le aws-sdk module et Express 4.13, il est possible de proxyer un fichier depuis S3 de plusieurs façons.

Cette version de rappel renverra le corps du fichier sous forme de tampon, ainsi que d'autres en-têtes pertinents tels que Content-Length:

function(req,res){

  var s3 = new AWS.S3();

  s3.getObject({Bucket: myBucket, Key: myFile},function(err,data){

    if (err) {
      return res.status(500).send("Error!");
    }

    // Headers
    res.set("Content-Length",data.ContentLength)
       .set("Content-Type",data.ContentType);

    res.send(data.Body); // data.Body is a buffer

  });

}

Le problème avec cette version est que vous devez récupérer l'intégralité du fichier avant de l'envoyer, ce qui n'est pas génial, surtout si c'est quelque chose de gros comme une vidéo.

Cette version diffusera directement le fichier:

function(req,res){

  var s3 = new AWS.S3();

  s3.getObject({Bucket: myBucket, Key: myFile})
    .createReadStream()
    .pipe(res);

}

Mais contrairement au premier, il ne fera rien pour les en-têtes, dont un navigateur pourrait avoir besoin pour gérer correctement le fichier.

Existe-t-il un moyen d'obtenir le meilleur des deux mondes, en passant par les en-têtes corrects de S3 mais en envoyant le fichier sous forme de flux? Cela pourrait être fait en faisant d'abord une demande HEAD à S3 pour obtenir les métadonnées, mais cela peut-il être fait avec un seul appel d'API?

26
NChase

Une approche consiste à écouter l'événement httpHeaders et à y créer un flux.

s3.getObject(params)
    .on('httpHeaders', function (statusCode, headers) {
        res.set('Content-Length', headers['content-length']);
        res.set('Content-Type', headers['content-type']);
        this.response.httpResponse.createUnbufferedStream()
            .pipe(res);
    })
    .send();
29
André Werlang

Pour mon projet, je fais simplement un headObject afin de récupérer uniquement les métadonnées de l'objet (c'est vraiment rapide et évitez de télécharger l'objet). Ensuite, j'ajoute dans la réponse tous les en-têtes dont j'ai besoin pour propager la tuyauterie:

    var s3 = new AWS.S3();

    var params = {
        Bucket: bucket,
        Key: key
    };
    s3.headObject(params, function (err, data) {
        if (err) {
            // an error occurred
            console.error(err);
            return next();
        }
        var stream = s3.getObject(params).createReadStream();

        // forward errors
        stream.on('error', function error(err) {
            //continue to the next middlewares
            return next();
        });

        //Add the content type to the response (it's not propagated from the S3 SDK)
        res.set('Content-Type', mime.lookup(key));
        res.set('Content-Length', data.ContentLength);
        res.set('Last-Modified', data.LastModified);
        res.set('ETag', data.ETag);

        stream.on('end', () => {
            console.log('Served by Amazon S3: ' + key);
        });
        //Pipe the s3 object to the response
        stream.pipe(res);
    });
15
Mathieu Seiler

En nous appuyant sur la réponse d'André Werlang, nous avons fait ce qui suit pour augmenter les objets AWS Request avec une méthode forwardToExpress:

const _ = require('lodash');
const AWS = require('aws-sdk');

AWS.Request.prototype.forwardToExpress = function forwardToExpress(res, next) {
    this
    .on('httpHeaders', function (code, headers) {
        if (code < 300) {
            res.set(_.pick(headers, 'content-type', 'content-length', 'last-modified'));
        }                            
    })
    .createReadStream()
    .on('error', next)
    .pipe(res);
};    

Ensuite, dans nos gestionnaires d'itinéraire, nous pouvons faire quelque chose comme ceci:

s3.getObject({Bucket: myBucket, Key: myFile}).forwardToExpress(res, next);
12
cdhowie