Amazon’s CloudFront is great to reduce load on local servers if you are serving a lot of static files or files that can have a long cache timer set.
Sometimes you need to remove files from the CloudFront caches and this is where the Invalidation feature comes in.
An invalidation batch-request invalidates one or more files (paths) from all CloudFront edge location.
Currently it will take about 10-15 minutes for an avarage invalidation batch to run thru. But hey, better than nothing.
A while ago i integrated the SQS and S3 parts i found floating around into one single CFC. Today i added a part for CloudFront invalidation. So here it is:
<!---
Parts of this are based on Tim Dawe's
http://amazonsig.riaforge.org
and
Joe Danziger's Amazon S3 REST Wrapper
http://amazons3.riaforge.org/
Written by Patrick Liess
Twitter: @smrchy
http://www.tcs.de
--->
<cfcomponent>
<cffunction name="init" output="false" hint="Returns an instance of the CFC initialized.">
<cfargument name="awsAccessKeyId" type="string" required="true" />
<cfargument name="secretAccessKey" type="string" required="true" />
<cfscript>
This.awsAccessKeyId = Arguments.awsAccessKeyId;
This.secretAccessKey = Arguments.secretAccessKey;
This.SQSVersion = "2009-02-01";
This.SQSserviceUrl = "http://queue.amazonaws.com/";
This.CFVersion = "2010-11-01";
return This;
</cfscript>
</cffunction>
<!--- CloudFront --->
<cffunction name="CFInvalidation" output="false" returntype="any">
<cfargument name="distributionid" type="string" required="yes">
<cfargument name="patharray" type="array" required="yes">
<cfset var i = 0>
<cfset var dateTimeString = GetHTTPTimeString(Now())>
<cfset var cs = "#dateTimeString#">
<cfset var signature = createSignature(cs)>
<cfset var thexml = "<InvalidationBatch>">
<cfloop index="i" array="#Arguments.patharray#">
<cfset thexml = thexml & "<Path>#i#</Path>">
</cfloop>
<cfset thexml = thexml & "<CallerReference>#CreateUUID()#</CallerReference></InvalidationBatch>">
<cfhttp method="POST" url="https://cloudfront.amazonaws.com/#This.CFVersion#/distribution/#Arguments.distributionid#/invalidation">
<cfhttpparam type="header" name="Date" value="#dateTimeString#">
<cfhttpparam type="header" name="Content-Type" value="text/xml">
<cfhttpparam type="header" name="Authorization" value="AWS #This.awsAccessKeyId#:#signature#">
<cfhttpparam type="body" value="#thexml#">
</cfhttp>
<cfreturn cfhttp>
</cffunction>
<!--- S3 --->
<cffunction name="S3createSignedURL" output="false" returntype="string">
<cfargument name="bucketName" type="string" required="yes">
<cfargument name="fileKey" type="string" required="yes">
<cfargument name="minutesValid" type="string" required="false" default="60">
<cfargument name="secure" type="boolean" required="false" default="false">
<cfscript>
var epochTime = DateDiff("s", DateConvert("utc2Local", "January 1 1970 00:00"), now()) + (arguments.minutesValid * 60);
// Create a canonical string to send
var cs = "GET\n\n\n#epochTime#\n/#arguments.bucketName#/#arguments.fileKey#";
// Create a proper signature
var signature = createSignature(cs);
// Create the timed link for the image
var protocol = 'http://';
if (arguments.secure) {
protocol = 'https://';
}
var timedAmazonLink = protocol & arguments.bucketName&'.s3.amazonaws.com/' & arguments.fileKey & '?AWSAccessKeyId=' & URLEncodedFormat(This.awsAccessKeyId) & '&Expires=' & epochTime & '&Signature=' & URLEncodedFormat(signature);
return timedAmazonLink;
</cfscript>
</cffunction>
<cffunction name="S3deleteObject" output="false" returntype="struct">
<cfargument name="bucketName" type="string" required="yes">
<cfargument name="fileKey" type="string" required="yes">
<cfset var dateTimeString = GetHTTPTimeString(Now())>
<cfset var cs = "DELETE\n\n\n#dateTimeString#\n/#arguments.bucketName#/#arguments.fileKey#">
<cfset var signature = createSignature(cs)>
<cfhttp method="DELETE" url="http://s3.amazonaws.com/#arguments.bucketName#/#arguments.fileKey#">
<cfhttpparam type="header" name="Date" value="#dateTimeString#">
<cfhttpparam type="header" name="Authorization" value="AWS #This.awsAccessKeyId#:#signature#">
</cfhttp>
<!--- We return the full struct so we can check the cfhttp.statuscode for errors --->
<cfreturn cfhttp>
</cffunction>
<cffunction name="S3putObject" access="public" output="false" returntype="string">
<cfargument name="bucketName" type="string" required="yes">
<cfargument name="filekey" type="string" required="yes">
<cfargument name="filebinary" type="binary" required="yes">
<cfargument name="contentType" type="string" required="yes">
<cfargument name="ACL" type="string" required="no" default="public-read">
<cfargument name="HTTPtimeout" type="numeric" required="no" default="300">
<cfargument name="cacheControl" type="boolean" required="no" default="true">
<cfargument name="cacheDays" type="numeric" required="no" default="300">
<cfset var dateTimeString = GetHTTPTimeString(Now())>
<cfset var cacheSeconds = Arguments.cacheDays * 86400>
<cfset var cs = "PUT\n\n#arguments.contentType#\n#dateTimeString#\nx-amz-acl:#arguments.ACL#\n/#arguments.bucketName#/#arguments.filekey#">
<!--- Never allow cacheControl for secure images --->
<cfif arguments.ACL NEQ "public-read">
<cfset arguments.cacheControl = false>
</cfif>
<cfset var signature = createSignature(cs)>
<!--- Send the file to amazon. The "x-amz-acl" controls the access properties of the file --->
<cfhttp method="PUT" url="http://s3.amazonaws.com/#arguments.bucketName#/#arguments.fileKey#" timeout="#arguments.HTTPtimeout#">
<cfhttpparam type="header" name="Authorization" value="AWS #This.awsAccessKeyId#:#signature#">
<cfhttpparam type="header" name="Content-Type" value="#arguments.contentType#">
<cfhttpparam type="header" name="Date" value="#dateTimeString#">
<cfhttpparam type="header" name="x-amz-acl" value="#arguments.ACL#">
<cfhttpparam type="body" value="#Arguments.filebinary#">
<cfif arguments.cacheControl>
<cfhttpparam type="header" name="Cache-Control" value="max-age=#cacheSeconds#">
</cfif>
</cfhttp>
<cfreturn cfhttp.FileContent>
</cffunction>
<!--- SQS --->
<cffunction name="SQSgetQueueAttributes" output="false" returntype="string">
<cfargument name="queue" type="string" required="true"/>
<cfargument name="name" type="string" required="true"/>
<cfset var requrl= This.SQSserviceUrl & Arguments.queue &
"?Action=GetQueueAttributes" &
"&AttributeName=" & Arguments.name &
"&Version=" & This.SQSVersion>
<cfset requrl = signRequest(requrl,"GET")>
<cfhttp method="GET" url="#requrl#" charset="UTF-8"></cfhttp>
<cfreturn cfhttp.FileContent>
</cffunction>
<cffunction name="SQSdeleteMessage" output="false" returntype="struct">
<cfargument name="queue" type="string" required="true"/>
<cfargument name="receipthandle" type="string" required="true"/>
<cfset var requrl= This.SQSserviceUrl & Arguments.queue &
"?Action=DeleteMessage" &
"&ReceiptHandle=" & URLEncodedFormat(Arguments.receipthandle,"utf-8") &
"&Version=" & This.SQSVersion>
<cfset requrl = signRequest(requrl,"GET")>
<cfhttp method="GET" url="#requrl#" charset="UTF-8"></cfhttp>
<!--- We return the full struct so we can check the cfhttp.statuscode for errors --->
<cfreturn cfhttp>
</cffunction>
<cffunction name="SQSreceiveMessage" output="false" returntype="string">
<cfargument name="queue" type="string" required="true"/>
<cfargument name="amount" type="numeric" required="true"/>
<cfset var requrl= This.SQSserviceUrl & Arguments.queue &
"?Action=ReceiveMessage" &
"&AttributeName=None" &
"&MaxNumberOfMessages=" & Arguments.amount &
"&Version=" & This.SQSVersion>
<cfset requrl = signRequest(requrl,"GET")>
<cfhttp method="GET" url="#requrl#" charset="UTF-8"></cfhttp>
<cfreturn cfhttp.FileContent>
</cffunction>
<cffunction name="SQSsendMessage" output="false" returntype="string">
<cfargument name="queue" type="string" required="true"/>
<cfargument name="msg" type="string" required="true"/>
<cfset var requrl= This.SQSserviceUrl & Arguments.queue &
"?Action=SendMessage" &
"&MessageBody=" & URLEncodedFormat(Arguments.msg,"utf-8") &
"&Version=" & This.SQSVersion>
<cfset requrl = signRequest(requrl,"GET")>
<cfhttp method="GET" url="#requrl#" charset="UTF-8"></cfhttp>
<cfreturn cfhttp.FileContent>
</cffunction>
<cffunction name="SQSsetQueueAttributes" output="false" returntype="string">
<cfargument name="queue" type="string" required="true"/>
<cfargument name="name" type="string" required="true"/>
<cfargument name="value" type="string" required="true"/>
<cfset var requrl= This.SQSserviceUrl & Arguments.queue &
"?Action=SetQueueAttributes" &
"&Attribute.Name=" & Arguments.name &
"&Attribute.Value=" & Arguments.value &
"&Version=" & This.SQSVersion>
<cfset requrl = signRequest(requrl,"GET")>
<cfhttp method="GET" url="#requrl#" charset="UTF-8"></cfhttp>
<cfreturn cfhttp.FileContent>
</cffunction>
<!---- Util functions --->
<cffunction name="signRequest" returntype="string" output="false">
<cfargument name="request" required="yes" type="string">
<cfargument name="method" required="no" default="GET" type="string">
<cfscript>
var lc = structnew();
// Extract the URL part of the request and strip the protocol
lc.requesturl = listfirst(arguments.request, "?");
lc.requesturl = replacenocase(lc.requesturl, "http://", "");
// Split into host and path
lc.host = listfirst(lc.requesturl, "/");
lc.path = right(lc.requesturl, len(lc.requesturl) - len(lc.host));
// Process the query string parameters into a structure
lc.querystring = listlast(arguments.request, "?");
lc.strParams = structnew();
</cfscript>
<cfloop list="#lc.querystring#" index="i" delimiters="&">
<cfset lc.strParams[listfirst(i, "=")] = urldecode(listlast(i, "="))>
</cfloop>
<cfscript>
// Add the timestamp
if (not StructKeyExists(lc.strParams, "Timestamp")) {
lc.utcdate = dateconvert("local2Utc", now());
lc.strParams["Timestamp"] = dateformat(lc.utcdate, 'yyyy-mm-dd') & "T" & timeformat(lc.utcdate, 'HH:mm:ss') & "Z";
}
// Add the standard parameters
lc.strParams["AWSAccessKeyId"] = This.awsAccessKeyId;
lc.strParams["SignatureVersion"] = 2;
lc.strParams["SignatureMethod"] = "HmacSHA1";
// Sort the parameters
lc.keys = listsort(structkeylist(lc.strParams), "text");
// Generate a new query string including timestamp, with parameters in the correct order, encoding as we go
lc.qs = "";
</cfscript>
<cfloop list="#lc.keys#" index="i">
<cfset lc.qs = lc.qs & rfc3986EncodedFormat(i) & "=" & rfc3986EncodedFormat(lc.strParams[i]) & "&">
</cfloop>
<cfscript>
// Strip off the last &
lc.qs = left(lc.qs, len(lc.qs)-1);
// Build the string to sign
lc.stringToSign = arguments.method & chr(10);
lc.stringToSign = lc.stringToSign & lc.host & chr(10);
lc.stringToSign = lc.stringToSign & lc.path & chr(10);
lc.stringToSign = lc.stringToSign & lc.qs;
lc.signature = HMAC_SHA1(lc.stringToSign);
// Return the new request URL
return "http://" & lc.host & lc.path & "?" & lc.qs & "&Signature=" & urlencodedformat(tobase64(lc.signature));
</cfscript>
</cffunction>
<cffunction name="HMAC_SHA1" returntype="binary" access="private" output="false">
<cfargument name="signMessage" type="string" required="true" />
<cfscript>
var jMsg = JavaCast("string",arguments.signMessage).getBytes("iso-8859-1");
var jKey = JavaCast("string",This.secretAccessKey).getBytes("iso-8859-1");
var key = createObject("java","javax.crypto.spec.SecretKeySpec");
var mac = createObject("java","javax.crypto.Mac");
key = key.init(jKey,"HmacSHA1");
mac = mac.getInstance(key.getAlgorithm());
mac.init(key);
mac.update(jMsg);
return mac.doFinal();
</cfscript>
</cffunction>
<cffunction name="createSignature" returntype="string" access="public" output="false">
<cfargument name="stringIn" type="string" required="true" />
<cfset var fixedData = replace(arguments.stringIn,"\n","#chr(10)#","all")>
<cfset var digest = HMAC_SHA1(fixedData)>
<cfset var signature = ToBase64("#digest#")>
<cfreturn signature>
</cffunction>
<cffunction name="rfc3986EncodedFormat" returntype="string" output="false">
<cfargument name="text" required="yes" type="string">
<cfset var lc = structnew()>
<cfset lc.objNet = createObject("java","java.net.URLEncoder")>
<cfset lc.encodedText = lc.objNet.encode(arguments.text, 'utf-8').replace("+", "%20").replace("*", "%2A").replace("%7E", "~")>
<cfreturn lc.encodedText>
</cffunction>
</cfcomponent>