Upload directly to S3 with SWFUpload

I was working on an application earlier today that required allowing a user to upload a large file (several hundred MB) which would eventually be stored on Amazon S3. After reviewing the requirements, I realized it made sense to just upload the file directly to S3 instead of having to first stage the file on a server and then use PHP to push the file to S3.

Amazon has a nice walk through of using a plain HTML form to upload a file directly to S3 here.

I had all ready been using SWFUpload to upload files to the server so I decided to look into using it to uploading directly to S3. After some head banging, I finally got it to work - here’s the quick n dirty.

  1. Download SWFUpload 2.5
  2. Get SWFUpload ready to use in your project. Copy the SWF file somewhere accessible and include their swfupload.js Javascript file. More info here
  3. Setup an S3 bucket. You’ll need to set the policy to allow uploads from your own user (its the default).
  4. Place a crossdomain.xml file in the root of your S3 bucket. This file “authorizes” flash player to upload files into this host. The content of the file is below.
  5. Initialize the SWFUpload object (example below).
  6. Before beginning the upload, you need to set the appropriate postParams in the SWFUpload object. This is really the “magic” of this process. Example is below.
  7. Start the upload with startUpload()

Thats it! It’s pretty straight forward once you have things going. As an FYI, you can put SWFUpload into “debug” mode by adding debug: true as a property to the initialization object. You can also debug the responses from Amazon by using a packet sniffer like Wireshark.

crossdomain.xml

You probably want to make this file a little less permissive. More details here. Also note, there are differences in the implementation of the file between various versions of Flash player.

<?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>

Initialize SWFUpload

        var swfu = new SWFUpload(
                       {  flash_url: "/assets/swfupload.swf", 
                          flash9_url: "/assets/swfupload_fp9.swf", 
                          file_size_limit: "1000 MB",
                          file_types: "*.*",
                          debug: false,
                          upload_url: "http://your-bucket.s3.amazonaws.com",
                          button_placeholder_id : "SWFUploadButton",
                          button_image_url : "/assets/select_filesbtn.png",
                          button_width: '112',
                          button_height: '33',
                          button_cursor : SWFUpload.CURSOR.HAND,
                          http_success : [201, 303, 200], /* Amazon returns a 303 on success */
                          file_post_name: "file", /* Amazon expects the file data to be in a input named "file"
                          
                          file_queued_handler: function(f){
                            
                            // track the filenames so you can upload them later                          
                            cachedUploadFiles[ f.index ] = f.name;
                          },
                          upload_complete_handler: function(e){ uploadSWFFile( ); },
                          upload_start_handler: function(e){
                                  // reset the progress bar
                        	  $("#progressBar").progressbar( 'value', 0 );
                          },
                          upload_error_handler: function(e){

                          },
                          upload_progress_handler: function(f, c, t){
                             // update the progress bar as the process continues
                             $("#progressBar").progressbar( 'value', Math.ceil( ( c/t ) * 100 ) );
                          }
        });  

Set SWFUpload postParams

The HMAC signature MUST be calculated on the server because it uses your S3 secret. You MUST keep that value secret in order to maintain the security of your S3 buckets. I’m using Don Schonknecht’s S3 PHP library to calculate the HMAC signatures but you could just as easily do it in straight PHP.

<?php
/* In PHP */
$encodedPolicy = json_encode( array(
              "expiration" => "2011-4-22T13:54:23.000Z",
              "conditions" => array(
                  0 => array( "acl" => "public-read" ),
                  1 => array( "bucket" => "your-bucket" ),
                  2 => array( "x-amz-meta-sig" => 'some meta signature to ensure authentic requests'),
                  3 => array( "redirect" => $'URL to redirect a success request (its doesnt matter)' ),
                  4 => array( "key" => "the S3 key for the file (the S3 filename)" ),
                  5 => array( "Filename" => "The original filename of the file. THIS IS IMPORTANT." )
              ),
            )
);
          
$encodedPolicy = base64_encode( $encodedPolicy );
$s3 = new S3( sfConfig::get("app_amazon_s3_id"), sfConfig::get("app_amazon_s3_secret") );
list($dist, $hmacSignature) = explode(":", $s3->__getSignature( $encodedPolicy ));

/* END PHP */
?>
<script>
            var swfConfig = { 
                    'AWSAccessKeyId': 'your amazon ID',
                    'acl': 'public-read',
                    'key': 'the S3 key for the file (the S3 filename)',
                    'policy': '<?php echo $encodedPolicy?>',
                    'signature': '<?php echo $signature'?>,
                    'redirect': 'URL to redirect a success request (its doesnt matter)',
                    'x-amz-meta-sig': 'some meta signature to ensure authentic requests',
            };

            // this line sets the post params so that SWFUpload will send the additional fields when it uploads the file.
            $.swfu.setPostParams( swfConfig );
</script>

Bostonbuilt.org - The built in Boston initiative

Earlier today we launched Boston Built in collaboration with our friends at Bocoup, UpStatement, and SignedOn

Basically, Boston built allows you to “represent” by either adding a graphic logo or a 1x1 tracking pixel to your site via Javascript. Then, the bostonbuilt.org site will pick you up and list your favicon along side the other sites that are including the tracking code.

BostInnovation has a nice write up and a poll at The ‘Built in Boston’ initiative.

Happy Friday!

TrackYourImpact.com Launched!

Recently we’ve launched a new site for a client called Purpose Beverages: http://trackyourimpact.com . We’ve received great feedback from users so far. The site uses a wide range of technologies. It is built on http://symfony-project.org">symfony and uses the http://www.apostrophenow.com/">Apostrophe CMS to manage the main parts of the site. It integrates with a SMS provider to allow you text into it to find out more about your purchase.

Tēvolution is a new brand of tea on the market that does good with each purchase. Every time someone buys it they done a specific amount(for example 25 cents) to a specific charity. In order to find out how large of a donation and what charity your bottle goes to you can actually text the code found on the bottle to the website, or you can login in on your phone browser or regular browser and enter the code. You will find out how much and to whom you just donated money to!

Right now Tēvolution is just coming to the market so keep your eyes peeled for it! It’s a great product that does good!

jQuery UI confirm

I was looking around earlier for a jQuery plugin to allow me to use jQuery UI’s dialog() widget to popup a confirm dialog. Didn’t have any luck finding one so I whipped something up. It’s not pretty but it works. You could even go as far as to overload window.confirm() but thats probably a bad idea.

jQuery.confirm = function(options){
	
	var opts = jQuery.extend( { message: "", ok: function(){}, cancel: function(){ } }, options );
	
	jQuery("<div class='span-10'><div class='ui-confirm-message'>" 
				+ opts.message + "<img class='loader' style='padding-left: 10px' src='/images/loader.gif' />" 
				+ "</div></div>").dialog({
					autoOpen: true,
					modal: true,
					autoOpen: false,
					resizable: false,
					draggable: false,
					title: "",
					width: "400px",
					buttons: {
					    "Cancel": function(){ 
							opts.cancel.call( this ); 
						},
						"Ok": function(){ 
					    	opts.ok.call( this );
					    }
					}
	}).dialog("open");
	
	
};

// use it 

		jQuery.confirm( {
			"message": "Are you sure?",
			"ok": function( ){
				
				$(this).find(".loader:first").show();
				
				// do stuff
                               $(this).dialog("close");
			},
			"cancel": function( ){ 
				$(this).dialog("close");
			}
		});