Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
I am the chief technical officer at InVision App, Inc - a prototyping and collaboration platform for designers, built by designers. I also rock out in JavaScript and ColdFusion 24x7.
Meanwhile on Twitter
Loading latest tweet...
Ben Nadel at the jQuery Conference 2010 (Boston, MA) with: Seth Johnson

Pushing Base64-Encoded Images Over HTML5 WebSockets With Pusher And ColdFusion

By Ben Nadel on

Yesterday, I started playing around with Pusher, a realtime push notification web service that allows your server to "push" updates to the client using native HTML5 or Flash-based WebSockets. If you don't know what WebSockets are (neither did I), they provide a way for the client to open up a direct line of communication with the server. The server can then send information back to the client at any time without the client having to constantly check for it. In my first demo, all I did was push a short, user-entered text message to the client. In this demo, however, I wanted to further test the bandwidth supplied by Pusher's WebSockets by pushing sizable Base64-encoded images to client.

 
 
 
 
 
 
 
 
 
 

Pushing Base64-encoded images from the server to the client (via Pusher) is not really a great use case. Typically, you'd probably just push an image URL to client and have the client make a subsequent request for the full image binary. The only real reason you might want to push an image is if you needed to supply an image that was not web-accessible and you also didn't want to stream the image up using something like CFContent. That said, we're not here to debate use cases, we're here to push the limits of Pusher's HTML5 WebSockets.

In the following code, I am allowing the user to select a photograph. The selected photograph is then read into memory by ColdFusion and written back to the server as Base64 data (there's no way to currently encode Base64 images on the fly). This Base64 image data is then read back into memory and further serves as the text data being posted to the Pusher web service via ColdFusion's CFHTTP tag. Beyond the actual image encoding, this Pusher demo is almost exactly the same as my first one.

  • <!--- Param the url value. --->
  • <cfparam name="url.id" type="numeric" default="0" />
  •  
  •  
  • <!--- Check to see if the image was selected by the user. --->
  • <cfif url.id>
  •  
  •  
  • <!---
  • Pusher information used to tie this push notification
  • to a given application and to a given client (listening
  • for a given channel).
  •  
  • NOTE: Your Pusher account can have multiple applications
  • associated with it (each of which can have on-the-fly
  • channel creation).
  • --->
  • <cfset pusherAppID = "1346" />
  • <cfset pusherKey = "6a4bebacf22ae8081689" />
  • <cfset pusherSecret = "****************" />
  • <cfset pusherChannel = "imagePush" />
  •  
  • <!---
  • Event we are triggering (this is what the client will
  • "bind" to on the given channel).
  • --->
  • <cfset pusherEvent = "imageEvent" />
  •  
  • <!---
  • At this point, we need to assemble the data we are actually
  • going to Push using Pusher. In this case, we want to send
  • the Base64-encoded data of the image to Push. In ColdFusion,
  • there is no on-the-fly Base64 encoding. As such, we need
  • read the image in and then write it to a temp file.
  • --->
  •  
  • <!--- Read the image in. --->
  • <cfset image = imageNew( "./images/image#url.id#.jpg" ) />
  •  
  • <!---
  • Write it to disk as Base64 encoding. By adding the fourth,
  • option argument (inHTMLFormat), ColdFusion will automatically
  • prepend the base64 data with, "data:image/jpg;base64,", such
  • that it can easily be used as a Data Url for image source.
  • --->
  • <cfset imageWriteBase64(
  • image,
  • "./base64.txt",
  • "jpg",
  • true
  • ) />
  •  
  • <!--- Read in the Base64 data. --->
  • <cfset base64Data = fileRead( expandPath( "./base64.txt" ) ) />
  •  
  • <!---
  • Now that we have the image in Base64 data, we have to encode
  • that value in JSON to be passed to the Pusher app. All values
  • "pushed" must be in JSON format.
  • --->
  • <cfset pusherData = serializeJSON( base64Data ) />
  •  
  • <!--- Authentication information. --->
  • <cfset authVersion = "1.0" />
  • <cfset authMD5Body = lcase( hash( pusherData, "md5" ) ) />
  • <cfset authTimeStamp = fix( getTickCount() / 1000 ) />
  •  
  • <!--- Build the post resource (the RESTfule resource). --->
  • <cfset pusherResource = "/apps/#pusherAppID#/channels/#pusherChannel#/events" />
  •  
  •  
  • <!--- ------------------------------------------------- --->
  • <!--- ------------------------------------------------- --->
  •  
  •  
  • <!---
  • The following is the digital signing of the HTTP request.
  • Frankly, this stuff is pretty far above my understanding
  • of cryptology. I have adapted code from the PusherApp
  • ColdFusion component written by Bradley Lambert:
  • http://github.com/blambert/pusher-cfc
  • --->
  •  
  •  
  • <!---
  • Create the list of query string values (alpha-sorted with no
  • URL encoding).
  • --->
  • <cfsavecontent variable="queryStringData">
  • <cfoutput>
  • auth_key=#pusherKey#
  • auth_timestamp=#authTimeStamp#
  • auth_version=#authVersion#
  • body_md5=#authMD5Body#
  • name=#pusherEvent#
  • </cfoutput>
  • </cfsavecontent>
  •  
  • <!---
  • Create the raw signature data. This is the HTTP
  • method, the resource, and the alpha-ordered query
  • string (non-URL-encoded values).
  • --->
  • <cfset signatureData = (
  • ("POST" & chr( 10 )) &
  • (pusherResource & chr( 10 )) &
  • reReplace(
  • trim( queryStringData ),
  • "\s+",
  • "&",
  • "all"
  • )
  • ) />
  •  
  • <!---
  • Create our secret key generator. This can create a secret
  • key from a given byte array. Initialize it with the byte
  • array version of our PushApp secret key and the algorithm
  • we want to use to generate the secret key.
  • --->
  • <cfset secretKeySpec = createObject(
  • "java",
  • "javax.crypto.spec.SecretKeySpec"
  • ).init(
  • toBinary( toBase64( pusherSecret ) ),
  • "HmacSHA256"
  • )
  • />
  •  
  • <!---
  • Create our MAC (Message Authentication Code) generator
  • to encrypt the message data using the PusherApp shared
  • secret key.
  • --->
  • <cfset mac = createObject( "java", "javax.crypto.Mac" )
  • .getInstance( "HmacSHA256" )
  • />
  •  
  • <!--- Initialize the MAC instance using our secret key. --->
  • <cfset mac.init( secretKeySpec ) />
  •  
  • <!---
  • Complete the mac operation, encrypting the given secret
  • key (that we created above).
  • --->
  • <cfset encryptedBytes = mac.doFinal( signatureData.getBytes() ) />
  •  
  •  
  • <!---
  • Now that we have the encrypted data, we have to convert
  • that data to a HEX-encoded string. We will use the big
  • integer for this.
  • --->
  • <cfset bigInt = createObject( "java", "java.math.BigInteger" )
  • .init( 1, encryptedBytes )
  • />
  •  
  • <!--- Convert the encrypted bytes to the HEX string. --->
  • <cfset secureSignature = bigInt.toString(16) />
  •  
  • <!---
  • Apparently, we need to make sure the signature is at least
  • 32 characters long. As such, let's just left-pad with spaces
  • and then replace with zeroes.
  • --->
  • <cfset secureSignature = replace(
  • lJustify( secureSignature, 32 ),
  • " ",
  • "0",
  • "all"
  • ) />
  •  
  •  
  • <!--- ----------------------------------------------------- --->
  • <!--- ----------------------------------------------------- --->
  •  
  •  
  • <!---
  • Now that we have all the values we want to post, including
  • our encrypted signature, we can post to the PusherApp REST
  • web service using CFHTTP.
  • --->
  • <cfhttp
  • result="post"
  • method="post"
  • url="http://api.pusherapp.com#pusherResource#">
  •  
  • <!---
  • Alert the post resource that the value is coming through
  • as JSON data.
  • --->
  • <cfhttpparam
  • type="header"
  • name="content-type"
  • value="application/json"
  • />
  •  
  • <!--- Set the authorization parameters. --->
  •  
  • <cfhttpparam
  • type="url"
  • name="auth_version"
  • value="#authVersion#"
  • />
  •  
  • <cfhttpparam
  • type="url"
  • name="auth_key"
  • value="#pusherKey#"
  • />
  •  
  • <cfhttpparam
  • type="url"
  • name="auth_timestamp"
  • value="#authTimeStamp#"
  • />
  •  
  • <cfhttpparam
  • type="url"
  • name="body_md5"
  • value="#authMD5Body#"
  • />
  •  
  • <!--- Sent the name of the pusher event. --->
  • <cfhttpparam
  • type="url"
  • name="name"
  • value="#pusherEvent#"
  • />
  •  
  • <!--- Send the actual message data (JSON data). --->
  • <cfhttpparam
  • type="body"
  • value="#pusherData#"
  • />
  •  
  • <!--- Digitally sign the HTTP request. --->
  • <cfhttpparam
  • type="url"
  • name="auth_signature"
  • value="#secureSignature#"
  • />
  •  
  • </cfhttp>
  •  
  •  
  • </cfif>
  •  
  •  
  • <!--- ----------------------------------------------------- --->
  • <!--- ----------------------------------------------------- --->
  •  
  •  
  • <cfoutput>
  •  
  • <!DOCTYPE HTML>
  • <html>
  • <head>
  • <title>Pushing Base64 Image Data With ColdFusion</title>
  • <style type="text/css">
  •  
  • a {
  • margin-right: 10px ;
  • }
  •  
  • img {
  • border: 3px solid ##333333 ;
  • height: 100px ;
  • }
  •  
  • </style>
  • </head>
  • <body>
  •  
  • <h1>
  • Pushing Base64 Image Data With ColdFusion
  • </h1>
  •  
  • <p>
  • <!---
  • Loop over the images to provide pushing options
  • (images will be converted to Base64 for "pushing").
  • --->
  • <cfloop
  • index="imageIndex"
  • from="1"
  • to="4"
  • step="1">
  •  
  • <a href="#cgi.script_name#?id=#imageIndex#">
  • <img src="./images/image#imageIndex#.jpg" />
  • </a>
  •  
  • </cfloop>
  •  
  • </p>
  •  
  • <p>
  • Images Credit:<br />
  • http://www.pbase.com/gilp/stephanie_richard
  • </p>
  •  
  • </body>
  • </html>
  •  
  • </cfoutput>

On the other end of Pusher is our client. And just as before, the client opens up an HTML5 WebSocket connection to the Pusher server to listen for push events. In our case, the client is connecting to the "imagePush" channel of our application and is binding to "imageEvent"-type events. When one of these push events is detected, the client takes the Base64-encoded image data associated with the push event and uses it as the SRC attribute value of an IMG tag on the page.

NOTE: Not all browsers support Base64-encoded data URLs for images.

  • <!DOCTYPE HTML>
  • <html>
  • <head>
  • <title>Pushing Base64 Image Data With ColdFusion</title>
  • <script type="text/javascript" src="jquery-1.4.2.min.js"></script>
  • <script type="text/javascript" src="http://js.pusherapp.com/1.4/pusher.min.js"></script>
  • <script type="text/javascript">
  •  
  • // This is for compatability with browsers that don't yet
  • // support Web Sockets, but DO support Flash.
  • //
  • // NOTE: This SWF can be downloaded from the PusherApp
  • // website. It is a Flash proxy to the standard Web
  • // Sockets interface.
  • WebSocket.__swfLocation = "./WebSocketMain.swf";
  •  
  •  
  • // When the DOM is ready, init the scripts. Technically, we
  • // can bind the PusherApp before DOM ready, but we can't
  • // respond to it, so no need to bind early.
  • $(function(){
  •  
  • // Get a reference to the image placeholder.
  • var image = $( "#image" );
  •  
  • // Create a Pusher server object with your app's key and
  • // the channel you want to listen on. Channels are unique
  • // to your application.
  • var server = new Pusher(
  • "6a4bebacf22ae8081689",
  • "imagePush"
  • );
  •  
  • // Now that we have the pusher connection for a given
  • // channel, we want to listen for certain events to come
  • // over that channel.
  • server.bind(
  • "imageEvent",
  • function( data ) {
  •  
  • // The data that comes through is the evaluated
  • // JSON data that was pushed to the client. In
  • // our case, it's the Base64 encoded image data
  • // formatted to be used as a data URL.
  • image.attr( "src", data );
  • }
  • );
  •  
  • });
  •  
  • </script>
  • </head>
  • <body>
  •  
  • <h1>
  • Pushing Base64 Image Data With ColdFusion
  • </h1>
  •  
  • <p>
  • This page will listen for PusherApp via HTML5 Socket
  • connection (or, if not supported, a Flash SWF fallback).
  • The messages (image data) will be displayed below.
  • </p>
  •  
  • <!--- The placeholder image. --->
  • <p>
  • <img id="image" width="450" />
  • </p>
  •  
  • </body>
  • </html>

Again, pushing images over WebSockets is not the most choice use case. But, to my surprise and delight, this worked really well. Not only did the Pusher service successfully handle the 80K of Base64-encoded image data, it did so was somewhat shocking speed. The Pusher application seems very powerful; and, the more I play around with it, the more I am excited to start using it in my applications.




Reader Comments

In your video you say there is no way to base 64 encode image data without coldfusion.

Why not load the original images in canavas then its arbitrary to get the base64 data without a server request. you could send the direct base64 data from canvas directly to the pusher service.

just some thoughts!

Reply to this Comment

@Steve,

That's awesome! Great to hear it. The native support seems to be way faster than the Flash support.

@Josh,

I may have misspoke on the video. I did not mean to say there was no way to Base64 encode the image without ColdFusion. I mean to say there is no way to Base64 on the fly IN ColdFusion (hence the intermediary file write).

That said, you still need to post to the ColdFusion server for all of the other stuff, such as the cryptography libraries, not to mention that you probably want to keep your secret key hidden so that others cannot just look in your Javascript and find it.

Reply to this Comment

Ben,

I did try pusherapp to send messages and it works perfectly fine. A problem what I'm trying to solve is to get notified whenever I receive an e-mail. Say I receive an email in my gmail inbox and I want pusher to send me a notification for this. Are you aware of any gmail apis that I could use.

Reply to this Comment

@Sagar,

You could use IMAP or POP to monitor your GMail account and, if you get a message, it you can push an alert via Pusher, I suppose. I've used ColdFusion 8 with CFPOP and ColdFusion 9 with CFIMAP both to check GMail inboxes, so I know that's possible. I don't know what language you are using, but I would assume they have something similar.

Reply to this Comment

Ben,

Yes, I do use ColdFusion as my primary language for development. CFPOP and CFIMAP can be used to retrieve the message from my gmail inbox, however the client (browser) should be notified (using pusher) of the new message in inbox and then I could use my jQuery to invoke the cfc which in turn would make a CFIMAP request to gmail.

The alternative to this is to keep polling for new messages i.e. make CFIMAP request every 1 - 3 mins , but I would like to get a hook on the gmail inbox (using any of the available APIs) and ask pusher to send the notification to the client (browser).

Reply to this Comment

@Sagar,

Yeah, sounds like you get the right idea - use something like CFPOP to look for new messages on the server-side; then, if you see one (or more), you can POST a message to Pusher, which can then, in realtime, notify your client application.

Reply to this Comment

i am a student of engineering.
i am developing a web application for paint and want to save the client image using php into mysql database and want to show the same image to the other client to do a collaborative drawing is it possible through php and mysql and html5 websockets or ajax?

Reply to this Comment

@Nitesh,

That is a very cool idea. While I don't know all that much about the Canvas tag for drawing, the concept would probably be something like pushing canvas API commands from browser to browser as they are happening. So, for example, as you programmatically do moveTo() and lineTo() commands on one browser, you can simultaneously post those commands to the server, which pushes them to the other browser, which implements the same commands.

That would be a really fun little project to play with.

Reply to this Comment

@Ben,

Just wanted to let you know that pusher are soon to start limiting sizes on messages. This was the detail that came through in the Feb dispatch:

"However, we will soon be limiting the size of messages we will accept on our REST API. More information will be available soon, follow us on Twitter (@pusher) for the latest news."

I asked them about it, and they said they "may" offer larger message sizes for a fee, or you could send through the file in parts.

I have just got into Pusher (and push services) and I am REALLY enjoying it, it just feels so powerful. A reason why I am liking it so much, is that although I love node.js (from the small portions that I have seen), its alot faster to get applications up and running.

Reply to this Comment

Post A Comment

You — Get Out Of My Dreams, Get Into My Comments
Live in the Now
Oops!
Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.