Amazon S3 suport in Adobe AIR

I’m a big fan of Amazon.com’s S3 (simple storage service).  However the available “get started” code in Actionscript for use In Adobe Air applications has been a bit hard to come by.  I can remember when I first started working with S3 I was sorely disappointed that I could not use it from a Flex(2) application because of the way http headers were handled in Actionscript.  Luckily since then the Actionscript API has been evolved such that accessing S3 via Flex/Air can be done.

The most developed S3 (via Actionscript) API library I”ve found thus far is as3awss3lib.  The work contributed by the authors is generous indeed but there is some functionality that has not yet been added and it doesn’t appear that as3awss3lib has been updated in a while.  At least as of this writing.

I have an AIR project that I’m working on now that requires interaction with Amazon.com’s S3 service and as3awss3lib has been a good starting point.  One of the first things I needed though was the ability to set the Access Control List or ACL for my buckets and objects.

I found that modifying the AWSS3 class to enable ACL support wasn’t that difficult.  Since there will probably be others who will need this functionality and may be unsure how to implement it, i’ll post the code snippets I added below.  Hopefully this will save others some valuable time.

Note the code below may not be ‘the best’ way of doing things but it seemed pretty straight forward to me and it works.

First of course you’ll want to download the source and add it to your AIR project then…

Add some constants to the AWSS3 class:

  • public static const ACL_PRIVATE:String   = “private”;
  • public static const ACL_PUBLIC_READ:String  = “public-read”;
  • public static const ACL_PUBLIC_READ_WRITE:String  = “public-read-write”;
  • public static const ACL_AUTH_READ:String  = “authenticated-read”;

The methods to modify are below with my modifications:

/*********************************************************************/
private function getURLRequest(method:String, resource:String, contentType:String = null, hash:String = null, secure:Boolean = true,acl:String = null):URLRequest
{
     var protocol:String = (secure) ? "https" : "http";
     var req:URLRequest = new URLRequest(protocol + "://" + AMAZON_ENDPOINT + resource);
     req.cacheResponse = false;
     req.useCache = false;
     req.method = method;
     var dateString:String = getDateString(new Date());
     var dateHeader:URLRequestHeader = new URLRequestHeader("Date", dateString);
     var authHeader:URLRequestHeader = new URLRequestHeader("Authorization", getAuthenticationHeader(method, dateString, resource, contentType, hash,acl));
     req.requestHeaders.push(dateHeader);
     req.requestHeaders.push(authHeader);

if(acl != null)//SBNOTE:added by me
{
     var aclHeader:URLRequestHeader = new URLRequestHeader("x-amz-acl",acl);
req.requestHeaders.push(aclHeader);
}//end SBNOTE

return req;
}

/*********************************************************************/
//SBNOTE: take note of the added acl variable
private function getAuthenticationHeader(verb:String,
												 dateString:String,
												 resource:String,
												 contentType:String = null,
												 hash:String = null,acl:String = null):String
		{
			return ("AWS " + this.accessKey + ":" + getAuthenticationString(verb, dateString, resource, contentType, hash,acl));
		}

/*********************************************************************/
//SBNOTE: take note of the acl variable
private function getAuthenticationString(verb:String,
												 dateString:String,
												 resource:String,

												 contentType:String = null,
												 hash:String = null,acl:String = null):String
		{
			var toSign:String = verb + "\n";
			toSign += (hash != null) ? hash + "\n" : "\n";
			toSign += (contentType != null) ? contentType + "\n" : "\n";
			toSign += dateString + "\n";

			if(acl != null)
			{
				toSign += "x-amz-acl:" + acl + "\n";
			}

			toSign += resource;

			var toSignBytes:ByteArray = new ByteArray();
			toSignBytes.writeUTFBytes(toSign);
			var hmacBytes:ByteArray = hmac.compute(secretAccessKeyBytes,toSignBytes);
			return Base64.encodeByteArray(hmacBytes);
		}

/*********************************************************************/
		public function createNewBucket(bucketName:String,acl:String = null):void
		{
			var stream:URLStream = getURLStream();
			stream.addEventListener(HTTPStatusEvent.HTTP_STATUS,
				function(e:HTTPStatusEvent):void
				{
					var ae:AWSS3Event;
					if (e.status == 200)
					{
						ae = new AWSS3Event(AWSS3Event.BUCKET_CREATED);
						dispatchEvent(ae);
					}
					else if (e.status == 409)
					{
						ae = new AWSS3Event(AWSS3Event.ERROR);
						ae.data = "This bucket name is not unique. Bucket names must be unique across all of S3.";
						dispatchEvent(ae);
					}
				});
			//SBNOTE: modified by me
			var req:URLRequest = getURLRequest("PUT", "/" + escape(bucketName),null,null,false,acl);	

			stream.load(req);
		}

/*********************************************************************/
		public function saveObject(bucketName:String, objectName:String, contentType:String, objectFile:File,acl:String = null):void
		{
			objectFile.addEventListener(ProgressEvent.PROGRESS,
				function (e:ProgressEvent):void
				{
					dispatchEvent(e);
				});
			objectFile.addEventListener(Event.COMPLETE,
				function(e:Event):void
				{
					var ae:AWSS3Event = new AWSS3Event(AWSS3Event.OBJECT_SAVED);
					dispatchEvent(ae);
				});

			objectFile.addEventListener(IOErrorEvent.IO_ERROR, onError);
			var req:URLRequest = getURLRequest("PUT", "/" + escape(bucketName) + "/" + escape(objectName), contentType,null,false,acl);
			if (contentType != null) req.requestHeaders.push(new URLRequestHeader("Content-Type", contentType));
			objectFile.uploadUnencoded(req);
		}

I think at this point the other method modifications are self explanatory. The As3awss3lib uses PUT to add files to Amazon’s S3 service but it can also be done via POST using some libs that can be found here. Take note of the 403 error comments at the bottom of the page. I posted a possible work around.


About this entry