Created
October 2, 2012 15:31
Revisions
-
weaver revised this gist
Oct 2, 2012 . 1 changed file with 1 addition and 2 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -77,7 +77,6 @@ var urlBase64 = (function() { return function urlBase64(buffer) { return buffer.toString('base64') .replace(/[\+\/]/g, function(token) { return alphabet[token]; }) .replace(/=+$/, ''); @@ -113,7 +112,7 @@ function makePolicy(expires, conditions) { obj = { expiration: when.toJSON(), conditions: conditions }, bytes = new Buffer(JSON.stringify(obj)); // console.log('policy bytes', bytes); return bytes.toString('base64'); } -
weaver revised this gist
Oct 2, 2012 . 6 changed files with 233 additions and 0 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,3 @@ node_modules .#* *~ This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -27,3 +27,4 @@ will serve uploaded files as `application/octet-stream`. Pass a The policy generated for the upload allows any content type, so the `Content-Type` could also be set client side. This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,186 @@ var Express = require('express'), Cons = require('consolidate'), Uuid = require('node-uuid'), Path = require('path'), Url = require('url'), Crypto = require('crypto'), app = Express(); // ## App Configuration ## // The canonical base url for this site is used to create success // redirect urls. app.set('Base Url', Url.parse('http://localhost:3000/')); // The name of the bucket is used to create bucket urls and upload // policies. app.set('S3 Bucket Name', process.env['S3_BUCKET']); // A folder in the bucket that files are uploaded into. This is // enforced by the policy. The entire upload destination is // {{S3 Folder}}/{{UUID}}/{{filename}} app.set('S3 Folder', 'example'); // The visibility of uploaded files. Choose `private` if downloads // must be authorized with a signature. The default is `public-read` // because files are uploaded over https and have a random name. app.set('S3 ACL', 'public-read'); // The AWS key id. Set this in the environment. app.set('AWS Key', process.env['AWS_KEY']); // The AWS key secret. Set this in the environment. app.set('AWS Secret', process.env['AWS_SECRET']); // When an upload policy is generated, it's only valid for a certain // period of time. Specify the period in milliseconds from the time the // policy is created. A fresh policy is created for each upload form // request. The default here is 1 minute. app.set('Upload Valid Millis', 1000 * 60 * 1); // Restrict the uploaded file to a maximum size. By default, 10MB. app.set('Max File Size Bytes', 1024 * 1024 * 1024 * 10); // ## Express Configuration ## app.engine('html', Cons.swig); app.set('view engine', 'html'); app.set('views', __dirname + '/views'); app.disable('view cache'); app.use(Express.logger('tiny')); app.use(app.router); app.use(function(err, req, res, next) { console.error(err.stack); res.send(500, 'server error'); }); process.nextTick(function() { var base = app.get('Base Url'); // Calculated settings app.set('S3 Bucket', 'https://s3.amazonaws.com/' + app.get('S3 Bucket Name') + '/'); app.set('S3 Bucket Url', Url.parse(app.get('S3 Bucket'))); app.listen(base.port || (base.protocol === 'https:' ? 443 : 80)); console.log('Listening:', Url.format(base)); }); // ## Helpers ## var urlBase64 = (function() { var alphabet = { '+': '-', '/': '_' }; return function urlBase64(buffer) { return buffer.toString('base64') .replace(/[\+\/]/g, function(token) { console.log('replace', token, alphabet, alphabet[token]); return alphabet[token]; }) .replace(/=+$/, ''); }; })(); function uuid() { var buffer = new Buffer(16); Uuid.v4(null, buffer); return urlBase64(buffer); } function siteUrl(dest, query) { var to = dest; if (query) { to = Url.parse(dest); to.query = query; } return Url.resolve(app.get('Base Url'), to); } // ## S3 ## // Generate a policy and signature for upload options. // See also: http://aws.amazon.com/articles/1434 function makePolicy(expires, conditions) { var when = new Date(Date.now() + expires), obj = { expiration: when.toJSON(), conditions: conditions }, bytes = new Buffer(JSON.stringify(obj)); console.log('policy bytes', bytes); return bytes.toString('base64'); } function sign(policy) { var secret = app.get('AWS Secret'); return Crypto.createHmac('sha1', secret) .update(policy) .digest('base64'); } function addPolicy(opt) { var expires = app.get('Upload Valid Millis'), bucket = app.get('S3 Bucket Name'), maxSize = app.get('Max File Size Bytes'); opt.policy = makePolicy(expires, [ {bucket: bucket}, ['starts-with', '$key', Path.dirname(opt.fileKey) + '/'], {acl: opt.acl}, {success_action_redirect: opt.success}, ['starts-with', '$Content-Type', ''], ['content-length-range', 0, maxSize] ]); opt.signature = sign(opt.policy); // console.log('policy', opt.policy); // console.log('signature', opt.signature); return opt; } function s3Url(key) { return Url.resolve(app.get('S3 Bucket Url'), key); } // ## Views ## // Create a random, unique name for each upload by generating a // uuid. Accept a `contentType` parameter to specify the content type of // the uploaded file. This could also be set on the client-side. // Once the file has been successfully uploaded, the browser will be // redirected to the success url `/uploaded`. S3 adds some query // parameters including the file's key and its etag. Additional // application parameters (like an upload session token) can be // encoded into the url before passing it to S3. app.get('/', function(req, res) { res.render('index', addPolicy({ action: app.get('S3 Bucket'), fileKey: Path.join(app.get('S3 Folder'), uuid(), '${filename}'), accessKey: app.get('AWS Key'), acl: app.get('S3 ACL'), success: siteUrl('/uploaded', { example: 'parameter' }), contentType: req.param('contentType') || 'application/octet-stream' })); }); app.get('/uploaded', function(req, res) { var info = req.query; console.log('uploaded', info); res.render('uploaded', { name: Path.basename(info.key), href: s3Url(info.key) }); }); This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,12 @@ { "name": "s3-upload", "description": "Example HTTP upload directly to S3 from browser", "version": "0.0.1", "private": true, "dependencies": { "express": "3.0.0rc4", "consolidate": "0.4.0", "swig": "0.12.0", "node-uuid": "1.3.3" } } This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,21 @@ <DOCTYPE html> <html> <head> <title>S3 Upload Example</title> </head> <body> <h1>Upload an SVG</h1> <form method="POST" action="{{ action }}" enctype="multipart/form-data"> <input type="hidden" name="key" value="{{ fileKey }}" /> <input type="hidden" name="AWSAccessKeyId" value="{{ accessKey }}" /> <input type="hidden" name="acl" value="{{ acl }}" /> <input type="hidden" name="success_action_redirect" value="{{ success }}" /> <input type="hidden" name="policy" value="{{ policy }}" /> <input type="hidden" name="signature" value="{{ signature }}" /> <input type="hidden" name="Content-Type" value="{{ contentType }}" /> <input name="file" type="file" /> <input type="submit" value="Upload!" /> </form> </body> </html> This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,10 @@ <DOCTYPE html> <html> <head> <title>Uploaded!</title> </head> <body> <h1>Upload Success</h1> <a href="{{ href }}">{{ name }}</a> </body> </html> -
weaver created this gist
Oct 2, 2012 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,29 @@ ## S3 Upload Example ## This example demonstrates how to upload a file directly to S3 from a browser. The Node server generates a policy and signature, authorizing the upload. Once a file has been successfully uploaded, S3 redirects the browser to a Node callback url. ## Get Started ## To get started: npm install export AWS_KEY='your-aws-key' export AWS_SECRET='your-key-secret' export S3_BUCKET='your-bucket-name' node app.js ## Uploaded Files ## Files are uploaded into example/{{uuid}}/filename.ext. By default, S3 will serve uploaded files as `application/octet-stream`. Pass a `contentType` parameter to choose a more specific type. For example: open http://localhost:3000/?contentType=text/html The policy generated for the upload allows any content type, so the `Content-Type` could also be set client side.