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