Requirements
 
Prerequisite knowledge
A basic understanding of ColdFusion, JavaScript and client-server architecture. Before reading this article, read Part 1 of the series by Awdhesh Kumar.
 
User level
Intermediate
 
 
Required products
Adobe ColdFusion Enterprise Edition (2016 release) (Download trial)
 
 
Sample files
HTML5 has gained a lot of traction, with a wide adoption across various industries. The HTML5 WebSocket protocol changes the equation of the traditional HTTP request response model completely by letting you to push data from the server to the client in real time. Yet, understanding the WebSocket protocol and the APIs associated enough to be able to build a full-fledged application remains a challenge.
 
The HTML5 WebSocket features within ColdFusion 10 completely abstracts the complexity involved in using the HTML5 WebSocket Protocol by helping you build applications easily, and which take advantage of the messaging infrastructure on top of the HTML5 WebSocket protocol. The ColdFusion team implemented the protocol within the ColdFusion 10 server and lets you to just focus on building your application.
 
WebSocket is a bi-directional, fully-duplex protocol over TCP. ColdFusion 10 empowers you to use WebSockets through its implementation of a messaging layer, making it highly scalable and making it easy to use. ColdFusion 10 provides client-side APIs to work with WebSockets and provides fallback mechanisms for the browsers, which do not support WebSocket natively.
 
Before proceeding, read HTML5 WebSockets and ColdFusion -- Part 1: An overview and first steps by Awdhesh Kumar. As a review from that article, to start with WebSockets in ColdFusion you must perform the following steps:
 
1) Specify channel(s) in Application.cfc : WebSocket works on a publish-subscribe model; to leverage that, you must define one or more channels.
 
2) Define a ChannelListener (optional) : In Application.cfc along with channel, you can define a channel-specific ChannelListener, which extends the base ChannelListener(CFIDE.websocket.ChannelListener) . Use this ChannelListener to control the flow of messages for a given channel.
 
3) Use the CFWEBSOCKET tag: This tag initializes a WebSocket connection on the client side that will receive, send, and manage the connection with the help of the JavaScript function that is a part of the ColdFusion WebSocket framework.
 
In this article we start by building a simple Hello World application and extend it step by step to a group chat application, visiting different features of the ColdFusion WebSocket framework along the way.
 

 
HelloWorld – Version 1 : Introducing the CFWEBSOCKET tag

To start with this sample application, we use the CFWEBSOCKET tag to develop an application. Version 1 introduces the CFWEBSOCKET tag, initializes the channel, auto-subscribe to a channel, and publish to a channel from the client side.
 
Step 1: Specifying a channel in the Application.cfc.
 
Application.cfc
 
component { this.name = “helloworld"; this.wschannels = [{name="world"}]; }
This code defines a channel named world in Application.cfc. A client can now subscribe and publish to this channel.
 
Step 2: As channel listener is optional and Application.cfc defines no channel listener, this example uses the base channel listener, CFIDE.websocket.ChannelListener .
 
Step 3: Create the WebSocket connection using the CFWEBSOCKET tag as follows:
 
HelloWorld.cfm
 
<cfwebsocket name="myworld" onMessage="msgHandler" subscribeto="world" onOpen="openHandler"/> <script> var msgHandler = function(message) { // Get data from the recieved message token var data = message.data; if(data) { // If data is present write it to the div var txt=document.getElementById("myDiv"); txt.innerHTML+= data + "<br>"; } } var sayHello = function() { // Client says Hello World myworld.publish("world","Hello World! WebSocket is here !!"); } var openHandler = function() { // do nothing } </script> <input id="hello" type="button" value="Say Hello!" onclick="sayHello();"> <div id="myDiv"></div>
The above HelloWorld.cfm contains the CFWEBSOCKET tag. This tag has an attribute name , which is a JavaScript reference to the WebSocket object that the channel will use to invoke various JavaScript functions. The above code uses the Websocket object myworld to publish a message from the client. Another attribute subscribeto auto-subscribes to the channel named world (defined in Application.cfc). Another attribute onMessage defines a handler to the JavaScript function, which receives all the messages from the server.
 
When a user lands on the HelloWorld.cfm page, the server creates a WebSocket on the client side and subscribes the user to the channel world . When user clicks the "Say Hello!" button, the client side sends a message through the publish method that the ColdFusion JavaScript API provides.
 

 
HelloWorld – Version 2: Using a channel listener

The above example explains how to use auto-subscribe, publishing a message, and receiving the responses from the server. Version 2 introduces the concept of channel listeners where you override the beforePublish() function to modify the message.
 
Now, if you wants to pre-process the message before publishing it, such as wanting to append a publish time to the original message, you can use channel listeners to do so. To use a channel listener, you must first define it in the Application.cfc. See the modified Application.cfc as follows:
 
component { this.name = "helloworld"; this.wschannels = [ {name="world", cfclistener="myChannelListener" }]; }
Here, myChannelListener is a CFC that extends the base channel listener, CFIDE.websocket.ChannelListener . The base channel listener has some predefined functions that you can override in the myChannelListener CFC. The following code shows the updated implementation in myChannelListener.cfc
 
component extends="CFIDE.websocket.ChannelListener" { public any function beforePublish(any message, Struct publisherInfo) { // Show server time also time = DateFormat(now(),"long"); message = time & ": <b>" & message & "</b>"; return message; } }
Here, the code overrides the beforePublish() function to process the message before returning it. Override this method to process the message for all clients. In this case, the code appends date/time to the message.
 

 
HelloWorld – Version 3: Channel listener and authentication

Version 3 highlights another predefined function in the base channel listener, beforeSendMessage() function and explains how it is different from the beforePublish() function explained in version 2. This example also shows you how to use authentication with WebSockets.
 
Version 3 upgrades the version 2 of the application to show the user name along with the hello world message. This require that you authenticate the user (the client) first and then store the username in a struct connectioninfo .
 
The connectioninfo struct stores all the information about an individual client. You can modify the connection info in Application.cfc and share it across all the channels for the given client.
 
To enable authentication in WebSocket, you must implement the onWSAuthenticate() function in the Application.cfc file. Add the onWSAuthenticate() function to the Application.cfc as follows:
 
function onWSAuthenticate(String username, String password, Struct connectionInfo) { // Put some authentication logic here (from LDAP or DB) // Adding the user name to the connection info connectionInfo.username=username; return true; }
The code above passes the connectionInfo struct to the function onWSAuthenticate() . You can use the connectionInfo object to store any client-specific data, which you can retrieve later (through an in-channel listener).
 
To call the defined onWSAuthenticate() method, the user must call the WebSocket JavaScript function authenticate() from the HelloWorld.cfm. Update this sayHello() function to authenticate, as follows:
 
var sayHello = function() { uname = document.getElementById("username").value; // Calling authenticate from client side. Calling this //function will invoke onWSAuthenticate from Application.cfc myworld.authenticate(uname,"password"); // Client says Hello World myworld.publish("world","Hello World! WebSocket is here !!"); }
Add the text field to take the user name, as follows:
 
<input id="username" name="username" type="text" value="admin" >
To make a message unique (by adding user name) for each client, override the beforeSendMessage() method in myChannelListener, as follows:
 
public any function beforeSendMessage(any message, Struct publisherInfo) { message = "<b>" & publisherInfo.connectionInfo.username & " :</b>" & " <i>" & message & "</i>"; return message; }
The difference between the beforePublish() and beforeSendMessage() functions is as follows: For any message to be sent, the code calls the former once and calls the latter for each and every client. So if there are 10 clients connected and subscribed to the channel 'world', the code calls the beforePublish() once and calls beforeSendMessage() 10 times.
 
Note: The connectionInfo object that you have updated in the onWSAuthenticate() function is a part of publisherInfo object passed to the beforeSendMessage() function.
 

 
HelloWorld – Version 4: Selective subscription, error handling, and the allowSubscribe() function

Version 4 explores selective subscription, error handling, and yet another pre-defined function allowSubscribe() in the base channel listener.
 
Selective subscription
 
Version 4 enhances the above application further to allow users to publish/subscribe only if they accept certain "terms and conditions." Since this is a selective subscription, the code does not subscribe users with the CFWEBSOCKET tag, rather a user subscribes explicitly along with information as to whether he/she accepted the proposed terms and conditions.
 
To enable this feature, you must modify HelloWorld.cfm slightly. The code adds a check box and button to accept terms and conditions. The CFWEBSOCKET tag does not subscribe to the channel by using the subscribeto attribute; there are two ways to subscribe to the channel—through using the CFWEBSOCKET tag, and the other by using the client-side API. This sample also defines an error handler to handle any error messages.
 
<cfwebsocket name="myworld" onMessage="msgHandler" onOpen="openHandler" onError="errorHandler"/>
Version 4 uses a new JavaScript function subscribe() to explicitly subscribe the user. While subscribing, the function passes some extra information about the user's agreement to the terms and conditions. To subscribe the user explicitly, use the WebSocket JavaScript function subscribe() with the iAgree() function. Here is the the iAgree() function:
 
var iAgree = function() { var chkbox= document.getElementById("check"); // Explicitly subscribe here with additional information myworld.subscribe("world",{agree:chkbox.checked}); }
In the above code snippet, the subscribe calls the first argument, which is the channel you wish to subscribe to. The second optional argument (a JavaScript object) contains the information about terms and conditions, which in this case has a value of "check." If you provide this second argument, the client send the information across the ColdFusion server along with the subscribe request. This extra information is available as subscriber information to some of the channel listener functions.
 
Error handling
 
When errors occur in WebSocket communication, ColdFusion calls the error handler callback that you have defined in the CFWEBSOCKET tag. the following defines the error handler implementation:
 
var errorHandler = function(err) { var txt=document.getElementById("myDiv"); txt.innerHTML= "<font size='5' color='red'><b>" + err.msg + "</b></font><br>" }
The allowSubscribe() function
 
On the channel listener side, this code overrides another function allowSubscribe() . The server uses the allowSubscribe() function to control client subscriptions. Here, if the user has agreed to terms and conditions, he/she can subscribe to the channel. If the user declined the terms and conditions, the channel declines his subscription request and displays a predefined error message from allowsubscribe().
 
The code below shows how to override the allowSubscribe() function in myChannelListener.cfc.
 
public boolean function allowSubscribe(Struct subscriberInfo) { // If user has agreed on terms and conditions, // return true if(subscriberInfo.agree) return true; return false; }
The function above returns true if the user has agreed to terms and conditions and allows the user to subscribe. This function also sends an acknowledgement of subscription success to the message handler msgHandler . If the allowSubscribe () function returns a value of false/true, the channel sends an error message to error handler defined in HelloWorld.cfm.
 

 
HelloWorld – Version 5: P2P communication and channel-specific message handlers

Version 5 highlights point-to-point (P2P) communication and channel-specific message handler. Version 5 enhances the existing application to show a user how long he/she has been logged in. This information is specific to each individual user. To achieve this, you can take advantage of using another WebSocket JavaScript function invoke() that ColdFusion exposes for your easy use. When you call the invoke() function, you initiate a P2P communication between client and server. The invoke() function calls a function defined in a CFC; this function will keep returning the time value—this is a problem because it will be difficult to handle both types of message from a single-message handler. The invoke() function returns the time value again and again to the message handler. To make things clear, you must define a different message handler for the channel world while subscribing.
 
In HelloWorld.cfm, the CFWEBSOCKET tag uses a default message handler that updates a div, as follows:
 
<cfwebsocket name="myworld" onMessage="defaultmsgHandler" onOpen="openHandler" onError="errorHandler"/>
Here is implementation of defaultmsgHandler :
 
var defaultmsgHandler = function(message) { // Get data from the recieved message token var data = message.data; if(data) { // If data is present write it to the div var txt=document.getElementById("timeCounter"); txt.innerHTML= "You have Logged in for <b>" + data + "</b> sec<br>"; } }
The code above displays the time since a user has logged in.
 
You can define a channel-specfic message handler at the time of subscribing to a channel itself. While subscribing, you use the iAgree() function, and within this function, you can pass a third argument that refers to the channel-specific message listener. In this case, we have passed the argument to msgHandler . Now any response for the channel world comes to msgHandler and not to defaultmsgHandler .
 
var iAgree = function() { var chkbox= document.getElementById("check"); // Explicitly subscribe here and defining custom message handler // This message handler will now receive messages for the channel world myworld.subscribe("world",{agree:chkbox.checked},"msgHandler"); myworld.invoke("invoke","getLoginTime"); }
Here is the implementation of channel-specific message handler:
 
var msgHandler = function(message) { // Get data from the recieved message token var data = message.data; if(data) { // If data is present write it to the div var txt=document.getElementById("myDiv"); txt.innerHTML+= data + "<br>"; } }
Also notice that within the iAgree() function, you call Websocket JavaScript function invoke() . This invoke() function invokes function getLoginTime(), defined in invoke.cfc. Here is the implementation for invoke.cfc.
 
component { public function getLoginTime() { uuid = CreateUUID(); // Unique name for each thread thread action="run" name=uuid { i=0; while(i <100){//running for 100 sec only sleep(1000); i = i +1; WsSendMessage(i); } } } }
The getLoginTime() function executes the ColdFusion server-side Websocket function WsSendMessage() . The WsSendMessage() function returns data to the client who has invoked this function. You call the WsSendMessage() function from inside a thread so that you can run it concurrently for different clients.
 

 
HelloWorld – Version 6: The invokeAndPublish() and WSGetSubscribers() functions

Version 6 highlights the client-side WebSocket JavaScript function invokeAndPublish() and the server-side ColdFusion function WSGetSubscribers() .
 
Version 6 adds functionality to the application to show how many users are online (subscribed) along with their names. To implement this feature, the application developer must have information about all subscribers. ColdFusion 10 provides a function called WSGetSubscribers(), which provides information about all the clients subscribed to a channel. This function returns an array of struct. This struct contains the subscriber's information, including channel name the subscriber subscribed to, and connection information.
 
Note: In earlier examples we have modified connection name to add username to it.
 
Below is the getallclients.cfc, which uses the WSGetSubscribers() function to fetch usernames subscribed to the channel world .
 
component{ public function getSubInfo() { info = ArrayNew(1); subInfo = WSGetSubscribers("world"); getMappings = function(obj){ ArrayAppend(info, obj["subscriberinfo"]["connectioninfo"]["USERNAME"]); }; // Using closure to get subscriber info ArrayEach(subInfo,getMappings); return info; } }
In the above code, the username is part of the connectioninfo struct, which in turn is a part of the sunscriberinfo struct. Whole functionality has been encapsulated in a CFC so that you can invoke it from the client side.
 
Modify HelloWorld.cfm to invoke function getSubInfo() from getallclints.cfc . The defaultmsgHandler JavaScript function calls invokeAndPublish to invoke getSubInfo() . Also, I moved invoke() from iAgree() to defaultmsgHandler() . The reason for this change is that all of these WebSocket JavaScript functions are asynchronous calls, and in iAgree() we call subscribe() , whose acknowledgment defaultmsgHandler() receives. Once the channel confirms subscription( in defaultmsgHandler() ) then only it calls invoke and invokeAndPublish.
 
Following is the implementation of defaultmsgHandler:
 
var defaultmsgHandler = function(message) { // Get data from the recieved message token var data = message.data; var reqType = message.reqType if(reqType) { if(reqType.indexOf("subscribe")!=-1) { // Now calling invoke and invokeAndPublish myworld.invoke("invoke","getLoginTime"); myworld.invokeAndPublish("world","getallclints", "getSubInfo"); } } if(data) { // If data is present write it to the div var txt=document.getElementById("timeCounter"); txt.innerHTML= "You have Logged in for <b>" + data + "</b> sec<br>"; } }
The code passes an acknowledgement of subscribe to defaultmsgHandler() that the request type is "subscribe." The response of invokeAndPublish comes to the message handler defined for the channel world, in other words, on msgHandler() . Here is updated version of msgHandler() that shows currently logged-in users.
 
var msgHandler = function(message) { // Get data from the recieved message token var msg = message.data; if(msg instanceof Array) { var usrtxt=document.getElementById("users"); usrtxt.innerHTML = "Following users are also logged in: "; for (a=0;a< msg.length;a++){ usrtxt.innerHTML +="<b>" + msg[a] + "</b> |"; } usrtxt.innerHTML += "<br>"; } else { // If data is present write it to the div var msgtxt=document.getElementById("myDiv"); msgtxt.innerHTML+= msg + "<br>"; } }

 
Where to go from here

This tutorial showed how you could create a basic group chat application. You can enhance this application further using other WebSocket features. This article covered basic building blocks to write an application using WebSockets. Refer to the ColdFusion 10 Help documentation for more information on WebSockets.
 
This quick start with WebSockets showed you how to write a basic hello world program and enhance it into a small group chat application. While enhancing the application, we visited different concepts of WebSockets like using a channel listener to control the flow of messages, P2P communication through the invoke() function, client-side broadcast using publish() and invokeAndPublish() functions, selective subscription, and authentication. For further reading please see ColdFusion team developer, Evelin Varghese's blog.
 

 
Attributions

I would like to acknowledge the help of the following people and resources that contributed to this article:
 
  • Awdhesh Kumar, Computer Scientist, ColdFusion product team
  • Evelin Varghese, QA, ColdFusion product team