2015-01-19 15 views
37

Ho riscontrato un problema durante il tentativo di caricare un file nel mio bucket S3. Tutto funziona tranne che i miei file paramter non sembrano appropriati. Sto usando sdk Amazon S3 per caricare da nodejs a s3.Carica un file su Amazon S3 con NodeJS

Queste sono le mie impostazioni di rotte:

var multiparty = require('connect-multiparty'), 
    multipartyMiddleware = multiparty(); 
app.route('/api/items/upload').post(multipartyMiddleware, items.upload); 

Questo è items.upload function():

exports.upload = function(req, res) { 
    var file = req.files.file; 
    var s3bucket = new AWS.S3({params: {Bucket: 'mybucketname'}}); 
    s3bucket.createBucket(function() { 
     var params = { 
      Key: file.name, 
      Body: file 
     }; 
     s3bucket.upload(params, function(err, data) { 
      console.log("PRINT FILE:", file); 
      if (err) { 
       console.log('ERROR MSG: ', err); 
      } else { 
       console.log('Successfully uploaded data'); 
      } 
     }); 
    }); 
}; 

Impostazione Body param ad una stringa come "hello" funziona bene. In base a doc, il parametro Body deve utilizzare (Buffer, Array tipizzato, Blob, String, ReadableStream) Dati oggetto. Tuttavia, il caricamento di un oggetto file non riesce con il seguente messaggio di errore:

[Error: Unsupported body payload object] 

questo è l'oggetto del file:

{ fieldName: 'file', 
    originalFilename: 'second_fnp.png', 
    path: '/var/folders/ps/l8lvygws0w93trqz7yj1t5sr0000gn/T/26374-7ttwvc.png', 
    headers: 
    { 'content-disposition': 'form-data; name="file"; filename="second_fnp.png"', 
    'content-type': 'image/png' }, 
    ws: 
    { _writableState: 
     { highWaterMark: 16384, 
     objectMode: false, 
     needDrain: true, 
     ending: true, 
     ended: true, 
     finished: true, 
     decodeStrings: true, 
     defaultEncoding: 'utf8', 
     length: 0, 
     writing: false, 
     sync: false, 
     bufferProcessing: false, 
     onwrite: [Function], 
     writecb: null, 
     writelen: 0, 
     buffer: [], 
     errorEmitted: false }, 
    writable: true, 
    domain: null, 
    _events: { error: [Object], close: [Object] }, 
    _maxListeners: 10, 
    path: '/var/folders/ps/l8lvygws0w93trqz7yj1t5sr0000gn/T/26374-7ttwvc.png', 
    fd: null, 
    flags: 'w', 
    mode: 438, 
    start: undefined, 
    pos: undefined, 
    bytesWritten: 261937, 
    closed: true }, 
    size: 261937, 
    name: 'second_fnp.png', 
    type: 'image/png' } 

Ogni aiuto sarà molto apprezzato!

+0

Il server solo bisogno di memorizzare i segreti AWS per creare S3-firmato-URL (usando aws-sdk). Il client recupera l'url firmato, qualsiasi upload poi avviene sul lato client usando quell'URL. Leggi questa guida: https://devcenter.heroku.com/articles/s3-upload-node – rocketspacer

risposta

63

Quindi sembra che ci siano alcune cose che vanno storte qui. In base al tuo post sembra che tu stia tentando di supportare i caricamenti di file utilizzando il middleware connect-multiparty. Ciò che questo middleware fa è prendere il file caricato, scriverlo sul filesystem locale e quindi impostare req.files sui file caricati.

La configurazione del percorso sembra corretta, il problema sembra essere con la tua funzione items.upload(). In particolare, con questa parte:

var params = { 
    Key: file.name, 
    Body: file 
}; 

Come ho detto all'inizio della mia risposta connect-multiparty scrive il file al file system locale, quindi avrai bisogno di aprire il file e leggerlo, poi caricarlo, e poi cancellarlo sul filesystem locale.

Detto questo si potrebbe aggiornare il metodo a qualcosa di simile al seguente:

var fs = require('fs'); 
exports.upload = function (req, res) { 
    var file = req.files.file; 
    fs.readFile(file.path, function (err, data) { 
     if (err) throw err; // Something went wrong! 
     var s3bucket = new AWS.S3({params: {Bucket: 'mybucketname'}}); 
     s3bucket.createBucket(function() { 
      var params = { 
       Key: file.originalFilename, //file.name doesn't exist as a property 
       Body: data 
      }; 
      s3bucket.upload(params, function (err, data) { 
       // Whether there is an error or not, delete the temp file 
       fs.unlink(file.path, function (err) { 
        if (err) { 
         console.error(err); 
        } 
        console.log('Temp File Delete'); 
       }); 

       console.log("PRINT FILE:", file); 
       if (err) { 
        console.log('ERROR MSG: ', err); 
        res.status(500).send(err); 
       } else { 
        console.log('Successfully uploaded data'); 
        res.status(200).end(); 
       } 
      }); 
     }); 
    }); 
}; 

Quello che fa è leggere il file caricato dal filesystem locale, poi carica a S3, quindi elimina il file temporaneo e invia una risposta.

Ci sono alcuni problemi con questo approccio. Prima di tutto, non è efficiente come potrebbe essere, in quanto per i file di grandi dimensioni verrà caricato l'intero file prima di scriverlo. In secondo luogo, questo processo non supporta il caricamento di più parti per file di grandi dimensioni (penso che il cut-off sia 5 Mb prima di dover eseguire un caricamento multiparte).

Quello che vorrei suggerire invece è che si utilizza un modulo che ho lavorato sulla chiamata S3FS che fornisce un'interfaccia simile a quella nativa FS in Node.JS ma astrae alcuni dettagli, come l'upload più parti e l'API S3 (oltre ad aggiungere alcune funzionalità aggiuntive come i metodi ricorsivi).

Se si dovesse tirare nella biblioteca S3FS il codice sarebbe simile a questa:

var fs = require('fs'), 
    S3FS = require('s3fs'), 
    s3fsImpl = new S3FS('mybucketname', { 
     accessKeyId: XXXXXXXXXXX, 
     secretAccessKey: XXXXXXXXXXXXXXXXX 
    }); 

// Create our bucket if it doesn't exist 
s3fsImpl.create(); 

exports.upload = function (req, res) { 
    var file = req.files.file; 
    var stream = fs.createReadStream(file.path); 
    return s3fsImpl.writeFile(file.originalFilename, stream).then(function() { 
     fs.unlink(file.path, function (err) { 
      if (err) { 
       console.error(err); 
      } 
     }); 
     res.status(200).end(); 
    }); 
}; 

Che cosa questo farà è istanziare il modulo per il secchio fornito e le credenziali AWS e quindi creare il secchio se non esiste Quindi, quando arriva una richiesta per caricare un file, apriremo un flusso sul file e lo useremo per scrivere il file su S3 nel percorso specificato. Questo gestirà il pezzo di upload multiparte dietro le quinte (se necessario) e ha il vantaggio di essere eseguito attraverso un flusso, quindi non devi aspettare di leggere l'intero file prima di iniziare a caricarlo.

Se si preferisce, è possibile modificare il codice in callback da Promises. Oppure utilizzare il metodo pipe() con il listener di eventi per determinare la fine/gli errori.

Se stai cercando alcuni metodi aggiuntivi, consulta la documentazione di s3fs e sentiti libero di aprire un problema se stai cercando altri metodi o problemi.

+11

Mi spiace sentirti dire che sono maleducato. Purtroppo, non ero io a rifiutare la modifica era la comunità. Quindi ho apportato la modifica da sola in questo modo in realtà riflette il codice corretto. Ciao uomo! – David

+2

Ho un secchio e una cartella al suo interno. Come posso specificare l'ACL per un oggetto che viene caricato in questa cartella. Ho modificato le autorizzazioni della cartella in "Crea pubblico" ma ogni volta che viene caricato un nuovo oggetto non è pubblico. Ho provato acl: "public-read" all'interno del costruttore S3FS ma non funziona. – Dan

+0

Se si utilizza S3FS quando si va a scrivere un oggetto, è possibile specificare l'ACL. Controlla questo link: http://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html#putObject-property Altrimenti dovrai specificare la politica quando carichi il file al di fuori di S3FS. – David

5

ho trovato quanto segue per essere una soluzione di lavoro ::

npm install aws-sdk 


Una volta installato l'AWS-sdk, utilizzare il seguente codice sostituendo i valori con il proprio dove necessario.

var AWS = require('aws-sdk'); 
var fs = require('fs'); 

var s3 = new AWS.S3(); 

// Bucket names must be unique across all S3 users 

var myBucket = 'njera'; 

var myKey = 'jpeg'; 
//for text file 
//fs.readFile('demo.txt', function (err, data) { 
//for Video file 
//fs.readFile('demo.avi', function (err, data) { 
//for image file     
fs.readFile('demo.jpg', function (err, data) { 
    if (err) { throw err; } 



    params = {Bucket: myBucket, Key: myKey, Body: data }; 

    s3.putObject(params, function(err, data) { 

     if (err) { 

      console.log(err) 

     } else { 

      console.log("Successfully uploaded data to myBucket/myKey"); 

     } 

     }); 

}); 

ho trovato il tutorial completo su questo argomento qui nel caso in cui siete alla ricerca di riferimenti ::


How to upload files (text/image/video) in amazon s3 using node.js