Saltar al contenido

Transmita el archivo de carga a S3 en Node.js usando formidable y (knox o aws-sdk)

Solución:

Bueno, según el creador de Formidable, la transmisión directa a Amazon S3 es imposible:

La API de S3 requiere que proporciones el tamaño de los archivos nuevos al crearlos. Esta información no está disponible para archivos de datos de formularios o de varias partes hasta que se hayan recibido por completo. Esto significa que la transmisión es imposible.

En efecto, form.bytesExpected se refiere al tamaño de todo el formulario y no al tamaño del archivo individual.

Por lo tanto, los datos deben llegar primero a la memoria o al disco del servidor antes de cargarlos en S3.

Con el multipartUpload de AWS S3 (s3-upload-stream como módulo de trabajo) y el flujo legible de node-formidable, puede canalizar el flujo para cargar así:

var formidable = require('formidable');
var http = require('http');
var util = require('util');
var AWS      = require('aws-sdk');
var config = require('./config');
var s3 = new AWS.S3({
    accessKeyId: config.get('S3_ACCESS_KEY'),
    secretAccessKey: config.get('S3_SECRET_KEY'),
    apiVersion: '2006-03-01'
});
var s3Stream = require('s3-upload-stream')(s3);
var bucket="bucket-name";
var key = 'abcdefgh';


http.createServer(function(req, res) {

    if (req.url == '/upload' && req.method.toLowerCase() == 'post') {

        var form = new formidable.IncomingForm();
        form.on('progress', function(bytesReceived, bytesExpected) {
            //console.log('onprogress', parseInt( 100 * bytesReceived / bytesExpected ), '%');
        });

        form.on('error', function(err) {
            console.log('err',err);
        });

        // This 'end' is for the client to finish uploading
        // upload.on('uploaded') is when the uploading is
        // done on AWS S3
        form.on('end', function() {
            console.log('ended!!!!', arguments);
        });

        form.on('aborted', function() {
            console.log('aborted', arguments);
        });

        form.onPart = function(part) {
            console.log('part',part);
            // part looks like this
            //    {
            //        readable: true,
            //        headers:
            //        {
            //            'content-disposition': 'form-data; name="upload"; filename="00video38.mp4"',
            //            'content-type': 'video/mp4'
            //        },
            //        name: 'upload',
            //            filename: '00video38.mp4',
            //        mime: 'video/mp4',
            //        transferEncoding: 'binary',
            //        transferBuffer: ''
            //    }

            var start = new Date().getTime();
            var upload = s3Stream.upload({
                "Bucket": bucket,
                "Key": part.filename
            });

            // Optional configuration
            //upload.maxPartSize(20971520); // 20 MB
            upload.concurrentParts(5);

            // Handle errors.
            upload.on('error', function (error) {
                console.log('errr',error);
            });
            upload.on('part', function (details) {
                console.log('part',details);
            });
            upload.on('uploaded', function (details) {
                var end = new Date().getTime();
                console.log('it took',end-start);
                console.log('uploaded',details);
            });

            // Maybe you could add compress like
            // part.pipe(compress).pipe(upload)
            part.pipe(upload);
        };

        form.parse(req, function(err, fields, files) {
            res.writeHead(200, {'content-type': 'text/plain'});
            res.write('received upload:nn');
            res.end(util.inspect({fields: fields, files: files}));
        });
        return;
    }

    // show a file upload form
    res.writeHead(200, {'content-type': 'text/html'});
    res.end(
        '<form action="/upload" enctype="multipart/form-data" method="post">'+
        '<input type="text" name="title"><br>'+
        '<input type="file" name="upload" multiple="multiple"><br>'+
        '<input type="submit" value="Upload">'+
        '</form>'
    );
}).listen(8080);

Dado que esta publicación es tan antigua y creo que la transmisión directa ahora es compatible, pasé mucho tiempo leyendo respuestas desactualizadas sobre este tema …

Si ayuda a alguien, pude transmitir desde el cliente a s3 directamente sin la necesidad de instalar paquetes:

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

El servidor asume req es un objeto de flujo, en mi caso se usó un objeto File en xhr (enviar) que enviará datos binarios en los navegadores modernos.

const fileUploadStream = (req, res) => {
  //get "body" args from header
  const { id, fn } = JSON.parse(req.get('body'));
  const Key = id + "https://foroayuda.es/" + 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í, rompe las convenciones, pero si miras la esencia, es mucho más limpio que cualquier otra cosa que encontré confiando en otros paquetes.

+1 por pragmatismo y gracias a @SalehenRahman por su ayuda.

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *