PubNub.cfc - A ColdFusion Wrapper For The PubNub Realtime Messaging Platform
Last week, I started playing around with PubNub - a cloud-based, realtime messaging platform. In my first post, I explored the ability for client applications to send messages to each other (using JavaScript) without the aid of any server-side proxy. This bi-directional communication approach required the PubNub "publish key" to be displayed in public. In a more secure application, you'd want to keep your publish key on the server and have the clients publish through a secure, server-side endpoint. Of course, before I could explore that approach, I needed to create a ColdFusion component wrapper for the PubNub RESTful API.
Over the weekend, I wrote PubNub.cfc. Since the PubNub RESTful API is quite small in scope, the PubNub.cfc wrapper only consists of a few public methods:
init( ... ). The init() method has an optional "secret key" argument. This is used to sign the resource used in the Publish requests. This is only used in enterprise accounts and its use or misuse will make absolutely no difference in non-enterprise accounts (ie. it does nothing, it hurts nothing - it won't fail).
publish( channel, message ). This method posts a message to the given channel. The message is implicitly serialized as JSON before it is posted to the channel.
subscribe( channel [, timeToken] ). This method gets all the messages posted to the given channel since the given time token. If no time token is supplied, it seems that an empty array is always returned. However, in that response is a new time token that can be used on subsequent subscribe() requests.
subscribeAsync( channel, callback [, timeout] ). This method works like the subscribe() method; however, rather than returning the result, the subscribe() method is invoked repeatedly within an asynchronous CFThread body. All retrieved messages are passed off to the given callback argument. Only one channel can be subscribed at any one time. Subsequent calls to this method will automatically unsubscribe the previous channel.
unsubscribe(). This method simply kills the asynchronously subscribed CFThread.
history( channel, limit ). This gets the most recently published messages (as defined by the limit).
time( [returnAsBigInt] ). This returns a normalized time from the PubNub server (in milliseconds). The PubNub.cfc returns this value as a string due to the limitations of integer size in ColdFusion. It can optionally be returned as a Java BigInteger instance.
Ok, now that you see the API, let's take a look at the PubNub.cfc ColdFusion component wrapper in action. For this simple demo, I'm not going to show the asynchronous subscribe - perhaps another time (its a bit more involved and harder to demonstrate).
<!---
Create an instance of our PubNub component with DEMO credentials.
This is an "account" that all people can use (but there is no
privacy on it since everyone knows the keys).
--->
<cfset pubnub = createObject( "component", "com.PubNub" ).init(
publishKey = "demo",
subscribeKey = "demo"
) />
<!--- ----------------------------------------------------- --->
<!--- ----------------------------------------------------- --->
<!---
Get the current time token from PubNub. It uses a normalized,
centralized timeline and can give you a time token in the number
of milliseconds since the "zero" date. This will be used to for
the subscribe method call farther down.
--->
<cfset timeToken = pubnub.time() />
<!--- ----------------------------------------------------- --->
<!--- ----------------------------------------------------- --->
<!--- Create a message to publish. --->
<cfset message = {} />
<cfset message[ "uuid" ] = createUUID() />
<cfset message[ "message" ] = "This is a test message from ColdFusion (#timeFormat( now(), 'hh:mm:ss TT' )#)." />
<!--- Publish the message. --->
<cfset response = pubnub.publish(
channel = "coldfusion:hello_world",
message = message
) />
<h2>
Publish Response
</h2>
<cfdump
var="#response#"
label="Publish Response"
/>
<!--- ----------------------------------------------------- --->
<!--- ----------------------------------------------------- --->
<!---
Get the history for our channel. This will return the most
recent [limit] items in time-ascending order. That is, the
oldest of the group is first, the newest of the group is last
in the array.
--->
<cfset response = pubnub.history(
channel = "coldfusion:hello_world",
limit = 5
) />
<h2>
History Response
</h2>
<cfdump
var="#response#"
label="History Response"
/>
<!--- ----------------------------------------------------- --->
<!--- ----------------------------------------------------- --->
<!---
Subscribe the given channel using the retreived time token
from above. This will return all the messages posted to the
channel since the given time. This should include the broadcast
message from above.
--->
<cfset response = pubnub.subscribe(
channel = "coldfusion:hello_world",
timeToken = timeToken
) />
<h2>
Subscribe Response
</h2>
<cfdump
var="#response#"
label="Subscribe Response"
/>
Note that we are using the demo/demo credentials. This is the PubNub demo account which anyone can jump in and start using. This creates an extremely low barrier of entry to the API (not to mention cost-free); but, of course, it creates no privacy as everyone can access the channels on the demo account.
When we run the above code, we get the following page output:
As you can see, the message was published to the channel and then retrieved both as part of a history request and a subscribe request. Notice that the subscribe request also returns a timeToken. This is so that subsequent requests to the subscribe() API can get the latest messages.
PubNub.cfc ColdFusion Component on GitHub
The code for the PubNub.cfc ColdFusion component is currently on my GitHub account. I'm also working on some various demos, including one for asynchronous subscribe. The PubNub team has given me the go-ahead to push this code to the core PubNub repository; so, whenever I figure out how the heck to do that, I will.
Want to use code from this post? Check out the license.
Reader Comments
How would you say this compares to Pusher?
(www.bennadel.com/blog/1970-Pusher-cfc-ColdFusion-Component-For-Realtime-Notification-With-HTML5-WebSockets.htm)
I'm really liking the idea of sending push notifications to the browser.. I just need an excuse to build an app that would use these.
@Tim,
Once you have a CFC wrapper for the API, I think the differences are going to be minimal if you are going from the server to the client. From what I remember, the digital signing of the Pusher request was more complicated (using SHA-256 hashing).
However, Pusher allows you to create "Apps" in your dashboard with individual API keys; PubNub appears to only have one set of keys (using channels as the primary differentiators).
Also, PubNub allows client-to-client (with a public key), which I don't believe I've seen in any of the other Realtime options.
But really, my understanding of the Realtime SaaS platforms is more experimental than anything else. I've not done anything with any production level integration yet.
I am aware of ColdFusion's Event Gateways: They handle incoming realtime messages with a dedicated 'onIncomingMessage' event method.
The PubNub.cfc deals with incoming realtime messages like so:
- Method 'subscribeAsync': In an asynchronous CFThread body, all retrieved messages are passed off to the given callback function argument.
Questions:
- Is PubNub.cfc's 'subscribeAsync' approach comparable to a dedicated 'onIncomingMessage' event method ?
- To deal with several PubNub channels on the server-side is no esoteric use-case ! In order to tackle that use case with the PubNub.cfc: Is it appropriate to have one dedicated PubNub.cfc instance per channel ?
I am now trying to integrate the PubNub.cfc into the ColdBox Framework; the main problem I encounter is:
- An asynchronous subscription seems to last only for a few seconds; this is not what one calls sustainable ...
I have settings like so:
- Doing the subscription: pubnub.subscribeAsync(timeout = 6000000)
- ColdBox App settings:
<cfset this.sessionManagement = true>
<cfset this.sessionTimeout = createTimeSpan(0,0,30,0)>
<cfset this.applicationTimeout = createTimeSpan(0,0,30,0)>
I have no idea why the CFThread of the PubNub subscribeAsync method only serves me for a few seconds; since I am very much after a sustainable/usable PubNub.cfc, I am grateful for hints how to make it so !
I just wanted to add my newest insight: In order to control the (request) timeout of the endless cfloop used in 'subscribeAsync', I have to do the following:
- Add a cfsetting directive to the .cfm page invoking 'subscribeAsync' like so:
- <cfsetting requesttimeout="240">
...
... subscribeAsync(...) ...
This seems to work for me; in this particular case, the subscribeAsync thread would listen for 4 minutes.
Another story is: How to sustainably kill subscribeAsync threads ...