21

I'm trying to find some example code that utilizes node.js, Express, and knox.

The docs for Knox only give clear examples of how to upload a file already stored in the file system. https://github.com/learnboost/knox#readme

Additionally, there a number of simple tutorials (even in Express itself) on how to upload files directly to express and save to the file system.

What I'm having trouble finding is an example that lets you upload a client upload to a node server and have the data streamed directly to S3 rather than storing in the local file system first.

Can someone point me to a gist or other example that contains this kind of information?

Geuis
  • 41,122
  • 56
  • 157
  • 219
  • 11
    Andrew Barber, how is it not clear what they are asking? Most of the people that answered the question seemed to have no trouble understanding it. – d512 May 30 '13 at 16:54
  • 8
    Agreed. Wtf. Why close a question that is 2 years old and has been updated with valuable information? Also, by closing this you ruin the seo value it has earned. – Geuis May 31 '13 at 02:13
  • For me the question is cristal clear, if you don't get the point of the topic, it probably only means you should not give an answer to it as you're not deep enough in that topic. – gwildu Jan 06 '17 at 06:21

6 Answers6

13

All of the previous answers involve having the upload pass through your node.js server which is inefficient and unnecessary. Your node server does not have to handle the bandwidth or processing of uploaded files whatsoever because Amazon S3 allows uploads direct from the browser.

Have a look at this blog post: http://blog.tcs.de/post-file-to-s3-using-node/

I have not tried the code listed there, but having looked over it, it appears solid and I will be attempting an implementation of it shortly ad will update this answer with my findings.

talentedmrjones
  • 7,511
  • 1
  • 26
  • 26
  • 1
    "All of the previous answers involve having the upload pass through your node.js server which is inefficient and unnecessary." but at the time of my answer (2011) your solution wasn't available. but i agree: now, it's the best way. – Philipp Kyeck Apr 06 '13 at 09:06
  • I'm using an s3 "compatible" service (vBlob from cloudfoundry) which doesn't accept method="post" but only method="put". Any ideas? – Michael Galaxy Aug 03 '13 at 23:50
  • 12
    isn't it unsafe having amazon codes in the client side? – coiso Nov 01 '13 at 10:09
  • 3
    your amazon/s3 codes are not on the client side, they are kept server side (where the node end point comes in), and are encoded and served to the client before the upload request to s3 is made – Alexander Zanfir May 07 '14 at 16:03
  • what if attacker use the amazon id in the client code and use up your storage? – OMGPOP Jul 28 '15 at 11:54
  • @OMGPOP The AWS access key (id) is essentially a "public" key. It is meant to be used in such cases. In order for someone to make use of the access key they would also need the secret key which is *never* sent out to the public. The form in this example would send the AWS access key and *signature* which is calculated from the secret key on the backend and delivered to the client – talentedmrjones Sep 08 '15 at 20:19
  • @talentedmrjones I create the s3Credentials on the server using secret key and passing the access key to the browser code. What if I am an adversary and I do the following: - I intercept your client to s3 request. - I change the s3 policy as it is available in plain text. - create a secret key and private key of my own. - create the s3Credentials again with the new data. - As the aws doesn't store those two ( awsSecretKey and AwsAccessKey) values it has to rely on whatever the client(now an adversary ) is sending. Now I can upload file of any size. Am I correct? – nsp Oct 28 '15 at 08:03
  • @nsp Nope. Any change to any parameter of the request will invalidate the signature. When all else fails, read the documentation http://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-authentication-HTTPPOST.html – talentedmrjones Nov 03 '15 at 14:47
4

Here is an example of streaming directly to s3 without ever touching your hard drive, using multiparty and knox:

var http = require('http')
  , util = require('util')
  , multiparty = require('multiparty')
  , knox = require('knox')
  , Batch = require('batch')
  , PORT = process.env.PORT || 27372

var s3Client = knox.createClient({
  secure: false,
  key: process.env.S3_KEY,
  secret: process.env.S3_SECRET,
  bucket: process.env.S3_BUCKET,
});

var Writable = require('readable-stream').Writable;
util.inherits(ByteCounter, Writable);
function ByteCounter(options) {
  Writable.call(this, options);
  this.bytes = 0;
}

ByteCounter.prototype._write = function(chunk, encoding, cb) {
  this.bytes += chunk.length;
  cb();
};

var server = http.createServer(function(req, res) {
  if (req.url === '/') {
    res.writeHead(200, {'content-type': 'text/html'});
    res.end(
      '<form action="/upload" enctype="multipart/form-data" method="post">'+
      '<input type="text" name="path"><br>'+
      '<input type="file" name="upload"><br>'+
      '<input type="submit" value="Upload">'+
      '</form>'
    );
  } else if (req.url === '/upload') {
    var headers = {
      'x-amz-acl': 'public-read',
    };
    var form = new multiparty.Form();
    var batch = new Batch();
    batch.push(function(cb) {
      form.on('field', function(name, value) {
        if (name === 'path') {
          var destPath = value;
          if (destPath[0] !== '/') destPath = '/' + destPath;
          cb(null, destPath);
        }
      });
    });
    batch.push(function(cb) {
      form.on('part', function(part) {
        if (! part.filename) return;
        cb(null, part);
      });
    });
    batch.end(function(err, results) {
      if (err) throw err;
      form.removeListener('close', onEnd);
      var destPath = results[0]
        , part = results[1];

      var counter = new ByteCounter();
      part.pipe(counter); // need this until knox upgrades to streams2
      headers['Content-Length'] = part.byteCount;
      s3Client.putStream(part, destPath, headers, function(err, s3Response) {
        if (err) throw err;
        res.statusCode = s3Response.statusCode;
        s3Response.pipe(res);
        console.log("https://s3.amazonaws.com/" + process.env.S3_BUCKET + destPath);
      });
      part.on('end', function() {
        console.log("part end");
        console.log("size", counter.bytes);
      });
    });
    form.on('close', onEnd);
    form.parse(req);

  } else {
    res.writeHead(404, {'content-type': 'text/plain'});
    res.end('404');
  }

  function onEnd() {
    throw new Error("no uploaded file");
  }
});
server.listen(PORT, function() {
  console.info('listening on http://0.0.0.0:'+PORT+'/');
});

example taken from https://github.com/superjoe30/node-multiparty/blob/master/examples/s3.js

andrewrk
  • 30,272
  • 27
  • 92
  • 113
  • Thanks for this, would love your help on my question here: http://stackoverflow.com/q/21873561/971592 – kentcdodds Feb 19 '14 at 07:16
  • @andrewrk In the above mentioned answer how should I stop the file upload before it completes. example : the file is currently uploading and I realise that the file is too big so I want to stop the upload by sending appropriatemessage. My node server hangs as I am not sending the correct response back when I want the upload to stop. http://stackoverflow.com/questions/33452952/why-does-this-request-to-node-server-hang – nsp Nov 02 '15 at 14:35
1

the node/express code doesn't work with nodejs v0.4.7

here is the updated code for nodejs v0.4.7

app.post('/upload', function (req, res) {
  // connect-form additions
  req.form.complete(function (err, fields, files) {
    // here lies your uploaded file:
    var path = files['upload-file']['path'];
    // do knox stuff here
  });
});
Donnie H
  • 1,602
  • 1
  • 14
  • 15
heisthedon
  • 3,657
  • 3
  • 21
  • 24
0

* update *

as of mid 2009 amazon supports CORS and the upload via your node.js server isn't needed anymore. you can directly upload the file to S3.


with the help of the "connect-form" module you could just upload the file to your server (through normal multipart FORM) and then handle the S3 stuff afterwards ...

<form action="/upload" method="POST" id="addContentForm" enctype="multipart/form-data">
  <p><label for="media">File<br/><input type="file" name="media" /></label></p>
  <p><button type="submit">upload</button></p>
</form>

node/express code:

app.post('/upload', function (req, res) {
  // connect-form additions
  req.form.complete(function (err, fields, files) {
    // here lies your uploaded file:
    var path = files['media']['path'];
    // do knox stuff here
  });
});

you have to add the following line to the app configuration:

app.configure(function(){
  // rest of the config stuff ...
  app.use(form({ keepExtensions: true }));
  // ...
});
Philipp Kyeck
  • 18,402
  • 15
  • 86
  • 123
  • Interesting! What are the security precautions we have to put into place when considering client side uploading directly to S3? I imagine we wouldn't want to give anyone and everyone that ability. Thanks :) – balupton Apr 28 '13 at 08:04
  • 1
    talentedmrjones has given a solution which is prone to man in the middle attack. There is one more solution which is given here https://www.terlici.com/2015/05/23/uploading-files-s3.html which is correct. But it has limitations like you cannot mention the file size etc. – nsp Oct 29 '15 at 11:53
-1

The connect-stream-s3 library can upload all of your forms files to S3 as part of middleware so you don't have to do any logic yourself. It needs express.bodyParser() for it to work at the moment, but I'm working on a version that will stream files direct to Amazon S3 prior to being written to disk:

Please let me know how you get on. Hopefully it's a lot less hassle than doing it yourself once you're in your page handler. :)

chilts
  • 451
  • 3
  • 12
-1

I made this to upload directly from the Jquery File Upload plugin to S3 with file being public - it should point you in the right direction.

https://gist.github.com/3995819

cyberwombat
  • 38,105
  • 35
  • 175
  • 251