Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Simple demo of client-side uploads to S3
CoffeeScript Ruby
branch: master

Add sample CORS configuration

As suggested in #1
latest commit dae28f4bbc
@dgoodlad authored
Failed to load latest commit information.
public
views
.gitignore Ignore stuff
Gemfile Re-add the client-side code, and a rake task for it
Gemfile.lock
LICENSE.txt
Procfile
README.md
Rakefile
app.rb
config.ru
dot-env.example
s3-upload.coffee

README.md

S3 Photo Upload Demo

This is a simple demonstration of client-side image uploads to Amazon S3. It simulates the scenario where you have an S3 bucket to which you want your users to be able to upload photos directly, with a reasonable user experience beyond what's used to be possible with a simple form POST and redirect.

Disclaimer I don't claim this is pretty code - it's just a prototype/demo of a few things that are now possible!

The AWS credentials used to upload to S3 are never exposed to the client-side, but the image data never has to be sent to the server. The client-side code makes a request to /sign which takes the requested username and filename, and returns a set of form fields, including a base64-encoded policy and a signature. These values are then used on the client-side when uploading the file.

Live Demo

A demo is, at the time of this writing, available at http://s3-photo-upload-demo.throwawayapp.com/

Server-Side

The server side is very straightforward (all in app.rb). /sign takes a username/filename combination (username is hardcoded to 'demo' atm) and returns some JSON:

{
  "url": "http://my-bucket-name.s3.amazonaws.com/",
  "fields": {
    "AWSAccessKeyId": "AKIAI6C4JGWHT2C5PGLQ",
    "key": "demo/oversize.jpg",
    "policy":"eyJleHBpcmF0aW9uIj[...]",
    "signature":"Q0qlCXilt4dc[...]",
    "Content-Type":"image/jpeg",
    "acl":"public-read"
  }
}

These values tell the client-side where to post to, and what multipart-encoded fields to include in order to make a successful request to S3. I've used the official AWS gem to generate these values for simplicity, but it's not a hard problem to generate the policy and signature on your own if you'd like. The policy looks like gibberish but is just base64-encoded json, so I recommend having a closer look at it to fully understand what's being generated.

Client-side

All of the client-side code is in s3-upload.coffee (which generates public/s3-upload.js). It's written to be very procedural, keeping the interesting bits together. Unless you're writing something trivial, I wouldn't use this code as-is in a real application: it's just a demonstration.

S3 CORS Configuration

This requires CORS to be configured on your S3 bucket. For the demo, I used the following, including an entry that allows requests from both my local dev environment and the demo app server:

<?xml version="1.0" encoding="UTF-8"?>
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
    <CORSRule>
        <AllowedOrigin>http://s3-photo-upload-demo.throwawayapp.com</AllowedOrigin>
        <AllowedOrigin>http://localhost:3000</AllowedOrigin>
        <AllowedMethod>POST</AllowedMethod>
        <MaxAgeSeconds>3000</MaxAgeSeconds>
        <AllowedHeader>*</AllowedHeader>
    </CORSRule>
</CORSConfiguration>

Hacking

The easiest way to get this running locally:

cp dot-env.example .env
vim .env
# Start the server
foreman start -p 3000

Now navigate to http://localhost:3000/ and you should be set to play.

If you're changing the coffeescript, make sure you run rake assets:compile afterwards to generate the javascript. This will become annoying if you're making serious changes, in which case I'd recommend setting up Guard or using the official coffeescript compiler's -w flag to continuously recompile it.

Something went wrong with that request. Please try again.