Using Plupload With Amazon S3 And Imgix In AngularJS
Yesterday, I took a look at using Imgix for "web scale" thumbnail generation in ColdFusion. That was fun to see in a one-off manor; but, today, I wanted to look at something a little more real-world oriented. In this exploration, I'm using Plupload to upload images directly from the client to Amazon S3. Then, I'm using Imgix to generate thumbnails using the pre-signed Amazon S3 URL as the "origin". And, for extra awesome funzies, I'm allowing the thumbnail style to be changed on-the-fly as well.
View this Project on my GitHub account.
The power of Imgix is a real one-two punch. Not only does it generate thumbnails very fast (much faster than what I typically see in ColdFusion, especially for large images); it also enables thumbnail styles, such as size, crop, and rotation, to be changed on the fly as well. Not only does this mean that thumbnail processing can be offloaded, it also means that the entire set of your application thumbnails can be regenerated with no effort on your part.
That last part - the near effortless regeneration of thumbnails - that's the part that really gets my heart racing. I deal with millions upon millions of thumbnails. And, the idea that I could, at any moment, start using different sized thumbnails - well, I can't even express how awesome that sounds.
There's too much code to review in this post, so I'll just look two parts - the original generate of the image URLs (S3 pre-signed URLs, and Imgix thumbnail URLs) and then the updating of the image URLs, on-the-fly.
When the user selects images to upload, we first save the "records" to the server. This request will save the "eventually valid" image URLs and return data that Plupload can subsequently use to upload the image binary directly from the client to Amazon S3:
<cfscript>
// Require the form fields.
param name="form.name" type="string";
// ------------------------------------------------------------------------------- //
// ------------------------------------------------------------------------------- //
// This is simply for internal testing so that I could see what would happen when
// our save-request would fail.
if ( reFind( "fail", form.name ) ) {
throw( type = "App.Forbidden" );
}
// ------------------------------------------------------------------------------- //
// ------------------------------------------------------------------------------- //
// Since we know the name of the file that is being uploaded to Amazon S3, we can
// create a pre-signed URL for the S3 object (that will be valid once the image is
// actually uploaded) and an upload policy that can be used to upload the object.
// In this case, we can use a completely unique URL since everything is in our
// control on the server.
// --
// NOTE: I am using getTickCount() to help ensure I won't overwrite files with the
// same name. I am just trying to make the demo a bit more interesting.
s3Directory = "pluploads/imgix/upload-#getTickCount()#/";
// Now that we have the target directory for the upload, we can define our full
// amazon S3 object key.
s3Key = ( s3Directory & form.name );
// Create a pre-signed Url for the S3 object. This is NOT used for the actual form
// post - this is used as the "origin" for the thumbnail that is going to be created
// through imgix. Imgix acts as proxy to our S3 object:
// --
// User --> Thumbnail --> Imgix CDN --> Imgix Processor --> S3 Pre-Signed Url --> S3
// --
imageUrl = application.s3.getPreSignedUrl(
s3Key,
dateConvert( "local2utc", dateAdd( "yyyy", 1, now() ) )
);
// Now that we have our Amazon S3 pre-signed URL, which will act as the origin object
// for Imgix, we can create the thumbnail URL that the user will use to access the
// on-demand thumbnail generation.
thumbnailUrl = application.imgix.getWebProxyUrl(
imageUrl,
{
w = 148,
fit = "crop"
}
);
// Create the policy for the upload. This policy is completely locked down to the
// current S3 object key. This means that it doesn't expose a security threat for
// our S3 bucket. Furthermore, since this policy is going to be used right away, we
// set it to expire very shortly (5 minute in this demo).
// ---
// NOTE: We are providing a success_action_status INSTEAD of a success_action_redirect
// since we don't want the browser to try and redirect once the image is uploaded.
settings = application.s3.getFormPostSettings(
dateConvert( "local2utc", dateAdd( "n", 5, now() ) ),
[
{
"acl" = "private"
},
{
"success_action_status" = 201
},
[ "starts-with", "$key", s3Key ],
[ "starts-with", "$Content-Type", "image/" ],
[ "content-length-range", 0, 10485760 ], // 10mb
// The following keys are ones that Plupload will inject into the form-post
// across the various environments. As such, we have to account for them in
// the policy conditions.
[ "starts-with", "$Filename", s3Key ],
[ "starts-with", "$name", "" ]
]
);
// Now that we have generated our pre-signed image URL, our Imgix thumbnail URL, and
// our Amazon S3 Form POST policy, we can actually add the image to our internal
// image collection. Of course, we have to accept that the image does NOT yet exist
// on Amazon S3.
imageID = application.images.addImage( form.name, imageUrl, thumbnailUrl );
// Get the full image record.
image = application.images.getImage( imageID );
// Prepare API response. This needs to contain information about the image record
// we just created, the location to which to post the form data, and all of the form
// data that we need to match our policy.
response.data = {
"image" = {
"id" = image.id,
"clientFile" = image.clientFile,
"imageUrl" = image.imageUrl,
"thumbnailUrl" = image.thumbnailUrl
},
"formUrl" = settings.url,
"formData" = {
"acl" = "private",
"success_action_status" = 201,
"key" = s3Key,
"Filename" = s3Key,
"Content-Type" = "image/#listLast( form.name, "." )#",
"AWSAccessKeyId" = application.aws.accessID,
"policy" = settings.policy,
"signature" = settings.signature
}
};
</cfscript>
In this code, the first URL I generate is the pre-signed Amazon S3 URL. Then, after that, I create an Imgix Web Proxy url that will use the pre-signed URL as the origin. That's all I have to do to create the thumbnail!
And, if I ever what to change the thumbnail style in my application, it's just a matter of updating the thumbnail URLs. In the demo, the user can apply different styles to the existing thumbnails by calling the following "update" page:
<cfscript>
// Require the form fields.
param name="form.style" type="string";
// Update each image with the new thumbnail url.
for ( image in application.images.getImages() ) {
switch ( form.style ) {
// Apply monochromatic thumbnail style.
case "mono":
newThumbnailUrl = application.imgix.getWebProxyUrl(
image.imageUrl,
{
w = 148,
fit = "crop",
mono = "FF33CC"
}
);
break;
// Apply pixelated thumbnail style.
case "pixelate":
newThumbnailUrl = application.imgix.getWebProxyUrl(
image.imageUrl,
{
w = 148,
fit = "crop",
px = 10
}
);
break;
// Apply sepia tone thumbnail style.
case "sepia":
newThumbnailUrl = application.imgix.getWebProxyUrl(
image.imageUrl,
{
w = 148,
fit = "crop",
sepia = 50
}
);
break;
// Revert back to normal thumbnail style.
default:
newThumbnailUrl = application.imgix.getWebProxyUrl(
image.imageUrl,
{
w = 148,
fit = "crop"
}
);
break;
} // END: Switch.
application.images.updateThumbnailUrl( image.id, newThumbnailUrl );
} // END: For.
// Prepare API response.
response.data = true;
</cfscript>
As you can see, there is no image processing happening here. All I'm doing is regenerating the Imgix Web Proxy URL and saving it to the repository.
When I actually see this on-the-fly thumbnail generation come to life, I am kind of blown away and my mind starts to race with fantasies of code restructuring. Now, I should point out that you don't have to use pre-signed S3 urls - I just happen to use those because I am used to them. Imgix also allows for more direct S3 integration (using stored IAM access credentials) and even public-folder integration. Definitely more to explore!
Want to use code from this post? Check out the license.
Reader Comments
Imgix is great but I'd like to throw out a shout to blitline.com , I create my own "imgix" like services with ColdFusion for in house customers that are not comfortable relying on someone else for image processing or if images need to be secure. I don't work for them, but they have been great and they have a really good free plan.
@Randall,
Seems like a interesting service as well. I like the fact that it can do screenshots as well - that's something that always seems useful and very hard to do on your own. Plus, JpegMini support is kind of player as well.