2013-06-26 17 views
11

Próbuję przesłać strumieniowo plik wysłany za pośrednictwem formularza bezpośrednio do zasobnika Amazon S3, używając aws-sdk lub knox. Obsługa formularzy odbywa się za pomocą formidable.Strumień przesyłania pliku do S3 na Node.js przy użyciu sformatowanego i (knox lub aws-sdk)

Moje pytanie brzmi: w jaki sposób prawidłowo używać potężnego przy pomocy aws-sdk (lub knox) przy użyciu każdej z najnowszych funkcji tej biblioteki do obsługi strumieni?

Zdaję sobie sprawę, że ten temat został już poproszony tutaj w różnych smakach, czyli:

Sądzę jednak, że odpowiedzi są nieco nieaktualne i/lub wyłączone z tematu (np. Wsparcie CORS, którego nie chcę na razie używać r różne powody) i/lub, co najważniejsze, nie odwołują się do najnowszych funkcji z aws-sdk (patrz: https://github.com/aws/aws-sdk-js/issues/13#issuecomment-16085442) lub doxx (w szczególności putStream() lub jego readableStream.pipe (req) variant, both explained in the doc).

Po kilku godzinach zmagań, doszedłem do wniosku, że potrzebuję pomocy (zrzeczenie się: Jestem dość początkującym ze strumieniami).

forma

HTML:

<form action="/uploadPicture" method="post" enctype="multipart/form-data"> 
    <input name="picture" type="file" accept="image/*"> 
    <input type="submit"> 
</form> 

ekspresowe bodyParser middleware jest skonfigurowany w ten sposób:

app.use(express.bodyParser({defer: true})) 

ofertę kupna Handler:

uploadPicture = (req, res, next) -> 
    form = new formidable.IncomingForm() 
    form.parse(req) 

    form.onPart = (part) -> 
    if not part.filename 
     # Let formidable handle all non-file parts (fields) 
     form.handlePart(part) 
    else 
     handlePart(part, form.bytesExpected) 

    handlePart = (part, fileSize) -> 
    # aws-sdk version 
    params = 
     Bucket: "mybucket" 
     Key: part.filename 
     ContentLength: fileSize 
     Body: part # passing stream object as body parameter 

    awsS3client.putObject(params, (err, data) -> 
     if err 
     console.log err 
     else 
     console.log data 
    ) 

Jednak ja otrzymuję następujący błąd:

{ [RequestTimeout: Your socket connection to the server was not read from or written to within the timeout period. Idle connections will be closed.]

message: 'Your socket connection to the server was not read from or written to within the timeout period. Idle connections will be closed.', code: 'RequestTimeout', name: 'RequestTimeout', statusCode: 400, retryable: false }

Wersja Knox z handlePart() funkcja dostosowane w ten sposób także nie zdało egzaminu:

handlePart = (part, fileSize) -> 
    headers = 
    "Content-Length": fileSize 
    "Content-Type": part.mime 
    knoxS3client.putStream(part, part.filename, headers, (err, res) -> 
    if err 
     console.log err 
    else 
     console.log res 
)  

ja również uzyskać duży obiekt o rozdzielczości 400 StatusCode gdzieś.

Region jest skonfigurowany do eu-west-1 w obu przypadkach.

Dodatkowe uwagi:

node 0.10.12

latest formidable from npm (1.0.14)

latest aws-sdk from npm (1.3.1)

latest knox from npm (0.8.3)

Odpowiedz

10

Well, according to the creator of Formidable, bezpośredni streaming Amazon S3 jest niemożliwe:

The S3 API requires you to provide the size of new files when creating them. This information is not available for multipart/form-data files until they have been fully received. This means streaming is impossible.

Rzeczywiście, form.bytesExpected odnosi się do rozmiaru całej postaci i nie rozmiar pojedynczego pliku.

Dane muszą najpierw trafić do pamięci lub dysku serwera, zanim zostaną przesłane do S3.

+3

To właśnie uratowało mnie dużo czasu. Dzięki. –

+6

W rzeczywistości tak nie jest. Możliwe jest potok/stream do s3! wystarczy znać rozmiar przesłanego pliku. Jeśli twój klient może to zapewnić, to możesz użyć rury do przesłania na s3 bez nieprzyjemnego zapisu na twardym dysku. Piszę serwer cli i pośredniczący, który zostanie przesłany do s3. Ponieważ kontroluję zarówno klienta, jak i serwer, mogę określić rozmiar pliku przed przesłaniem. Myślę, że mogą istnieć inne, skrajne przypadki, takie jak moje, których nie należy lekceważyć. Używam knox do streamowania do s3 za pomocą polecenia put. – CharlesTWall3

+0

@ CharlesTWall3 To jest bardzo ważny komentarz, nie myślałem o tym w tym czasie - myślałem o rozwiązaniu tylko po stronie serwera. Jeśli dostaniesz odpowiedź, jeśli chcesz, aby coś działało, z przyjemnością zagłosuję na Twoje rozwiązanie. Możesz także edytować tę odpowiedź. Dzięki! – jbmusso

2

Korzystanie AWS S3 multipartUpload (s3-upload-stream jako moduł roboczy) i węzła groźny na readable stream można rura strumień przesłać jak this:

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:\n\n'); 
      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); 
+0

Użyłem tego w przypadku wielościeżkowego – ifiok

2

Ponieważ ten post jest tak stary i wierzę strumieniowej bezpośrednio jest teraz obsługiwane, spędziłem dużo czasu na czytanie z datą odpowiedzi na ten temat ...

Jeśli to pomoże ktoś udało mi się strumień od klienta do S3 bezpośrednio bez konieczności instalowania pakietów:

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

Serwer zakłada req jest obiektem strumień, w moim przypadku to obiekt pliku użyto XHR (wysyłanie), który będzie wysyłał dane binarne w nowoczesnych przeglądarkach.

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

Tak łamie konwencję, ale jeśli spojrzeć na GIST jest znacznie czystsze niż cokolwiek innego uważam opierając się na innych pakietach.

+1 za pragmatyzm i podziękowania dla @SalehenRahmana za jego pomoc.

+0

W moim przypadku przesyłanie działało, ale plik obrazu nie mógł zostać poprawnie otwarty. – IroNEDR

+0

Czy sprawdziłeś swoje rozszerzenia? Prawidłowo otwarte na AWS lub z innego pobrania? Sprawdź również, czy otrzymujesz odpowiednią ilość bajtów. – mattdlockyer