Skip to content

Upload to Amazon S3

jayarjo edited this page Apr 28, 2013 · 21 revisions

Being able to upload files to Amazon S3, especially in HTML5, has been a goal for quite some time and while it was somehow possible in Flash and Silverlight, HTML5 was out of the game. Amazon S3 simply refused to add support for Access-Control-Allow-Origin - that single miraculous header that helps cross-domain AJAX requests to reach the server. Finally, after continuous lament from users, with bleeding shouts like: "Two and a half year later, still no cigar... AWS, Y U MAKIN ME CRY? :'(", Amazon made it happen.

So now it's possible.

Table of Contents

## Disclaimer
  • We assume that you already have active S3 account.
  • We describe the most generic scenario. Feel free to customize it to your needs.
  • This is a working draft. Suggestions are welcome.
## Preface

Do not expect it to just work. It is definitely achievable, but still requires some effort (not that much though). Each runtime has it's own specifics and requirements. Flash and Silverlight are similar in some sense and in general can share exactly the same configuration, both server- and client-side. But again - there's an option.

## Prepare server-side (S3)

First you need to create a bucket and make it accessible:

edit bucket permissions

Then we grant Upload/Delete permissions to group Everyone. And done.

Next step.

### ... for HTML5 runtime

In the same Permissions section there is an option to Add CORS Configuration (if you do not know what the CORS is, HTML5Rocks has an excellent write-up about it):

Add CORS Configuration

As always there are some options, but we will use the most generic configuration to make sure that S3 bucket is indeed compatible with our HTML5 upload:

<CORSConfiguration>
    <CORSRule>
        <AllowedOrigin>*</AllowedOrigin>
        <AllowedHeader>*</AllowedHeader>
        <AllowedMethod>GET</AllowedMethod>
        <AllowedMethod>POST</AllowedMethod>
        <MaxAgeSeconds>3000</MaxAgeSeconds>
    </CORSRule>
</CORSConfiguration>

What we say here is that we allow cross-domain access from any domain, using requests with any headers, via GET or POST. Preflight requests will be cached for 3000 secs.

### ... for Flash runtime

To support cross-origin requests, Flash requires crossdomain.xml policy file at the root of your bucket with such content for example:

<?xml version="1.0"?>
<!DOCTYPE cross-domain-policy SYSTEM
"http://www.macromedia.com/xml/dtds/cross-domain-policy.dtd">
<cross-domain-policy>
  <allow-access-from domain="*" secure="false" />
</cross-domain-policy>

Again, we simply allow all domains here. You might want to restrict this to specific ones only. Also notice secure="false" attribute - it will make your bucket accessible via HTTPS. If you want to allow only secured connections, then set it to true.

### ... for Silverlight runtime

2. Generating a policy and a signature

Uploading to S3 with multipart requires a policy and a signature. See this article for detailed information. These two will be passed to Plupload as multipart params (more on this later).

Heres a working policy generator written in Ruby. The code is intentionnaly simple and uses globals, please do not blindly copy-paste :

require 'base64'
require 'json'
require 'digest/sha1'

$ACL = 'public-read' # Change this according to your needs
$BUCKET = 'YOUR_BUCKET'
$AWS_SECRET = 'YOUR_SECRET'

def policy
  conditions = [
    ["starts-with", "$utf8", ""],
    # Change this path if you need, but adjust the javascript config
    ["starts-with", "$key", "uploads"],
    ["starts-with", "$filename", ""],
    { "bucket" => $BUCKET },
    { "acl" => $ACL }
  ]

  policy = {
    # Valid for 3 hours. Change according to your needs
    'expiration' => (Time.now.utc + 3600 * 3).iso8601,
    'conditions' => conditions
  }

  Base64.encode64(JSON.dump(policy)).gsub("\n","")
end

def signature
  Base64.encode64(
    OpenSSL::HMAC.digest(
      OpenSSL::Digest::Digest.new('sha1'),
      $AWS_SECRET, policy
    )
  ).gsub("\n","")
end

3. Configuring Plupload

{
  // General settings
  runtimes : 'flash,html5',

  // Flash settings
  flash_swf_url : '/plupload/src/moxie/bin/flash/Moxie.swf',

  // S3 specific settings
  url : "https://<%= $BUCKET %>.s3.amazonaws.com:443/",
  file_name_name: false, // Custom option to our fork to remove file_name_name
  multipart: true,
  multipart_params : {
    // Dummy filename to ensure the field is sent in HTML just like Flash
    // This allow to have a consistent AWS policy for both HTML and Flash
    filename: 'filename',
    utf8: true,

    AWSAccessKeyId: "YOUR_AWS_ACCESS_KEY_ID",
    acl: "public-read", // See http://docs.aws.amazon.com/AmazonS3/latest/dev/ACLOverview.html#CannedACL
    // See Generating a policy and a signature
    policy: "YOUR_POLICY",
    signature: "YOUR_SIGNATURE",

    // This is basically the resulting location of the file on S3.
    // See http://docs.aws.amazon.com/AmazonS3/latest/API/RESTObjectPOST.html#RESTObjectPOST-requests-form-fields
    //
    // WARNING : Change this to suite your needs.
    // You need to insert some sort of unique identifier to avoid
    // overriding files if they share the same filename.
    key: "uploads/${filename}",
  }
}

4. Putting it all together

I made a little Sinatra one-file-app that shows how to use all of this together.

Caveats

  • S3's success_action_redirect does not work in Chrome. I think the problem is that Moxie's XHR does not handle well redirect codes, but investigation is needed.
  • This demo requires a patched version of Plupload. Pull request #750 has to be merged for this to work with master.

Refs