ColdFusion 10 - Native WebSocket Filtering And Channel Listeners Are Mutually Exclusive

Posted March 22, 2012 at 9:54 AM by Ben Nadel

Tags: ColdFusion

In the ColdFusion 10 documentation regarding WebSockets, both the publish and subscribe functions mention that special data parameters can be used to filter incoming and outgoing messages. In my previous blog post, I looked at using the WebSocket application pseudo-events as a means to execute filtering manually. Today, I want to look at using the native filtering of WebSocket messages through the use of selectors and custom header.

NOTE: At the time of this writing, ColdFusion 10 was in public beta.


 
 
 

 
  
 
 
 

This demo, while quite small (compared to my other demos) took me a long time to figure out. No matter what I did, I simply could not get the filtering to work. Finally, after much head-scratching and help from Sagar Ganatra, I finally had a breakthrough! I finally closed the understanding gap that was preventing my WebSocket filtering from working:

Native WebSocket filtering functionality (ie. the "selector" property) is mutually exclusive with ColdFusion Channel Listener components.

If you are going to use the "selector" property as a means to filter incoming or outgoing messages, you cannot define a Channel Listener in your WebSocket configuration (for the given channel). Conversely, if you do want to use a channel listener, you cannot use the "selector" property. Using the selector property with a channel listener won't raise an exception - it simply will not work.

With this understanding finally in place, I was able to put together a quick example of ColdFusion 10's native WebSocket filtering functionality. In the following application, I've gotten rid of all session management - all filtering relies on URL query string values.

First, let's take a look at the Application.cfc ColdFusion framework component. In the following code, take special notice that I have defined one WebSocket channel - but, I have not defined a Channel Listener for it.

Appication.cfc - Our ColdFusion Application Framework Component

  • <cfscript>
  • // NOTE: CFScript added for Gist color-coding only. Remove.
  •  
  • component
  • output="false"
  • hint="I define the application settings and event handlers."
  • {
  •  
  •  
  • // Define the application settings.
  • this.name = hash( getCurrentTemplatePath() );
  • this.applicationTimeout = createTimeSpan( 0, 0, 5, 0 );
  •  
  • // Turn off session management. In this case, we'll just rely
  • // completely on the values being passed through the WebSocket
  • // custom headers.
  • this.sessionManagement = false;
  •  
  • // Set up the WebSocket channels.
  • //
  • // NOTE: We are NOT defining a Channel Listener - using the
  • // native WebSocket filtering and selector functionality is
  • // mutually exclusive with the Channel Listener (ie. it's doing
  • // exactly what YOU would have had to do in your own Channel
  • // Lister component).
  • this.wsChannels = [
  • {
  • name: "demo"
  • }
  • ];
  •  
  •  
  • // I initialize the application.
  • function onApplicationStart(){
  •  
  • // Define some users with different IDs. For this demo, we're
  • // gonna look at Pushing messages to specific clients.
  • application.users = [
  • {
  • id: 1,
  • name: "Joanna"
  • },
  • {
  • id: 2,
  • name: "Sarah"
  • },
  • {
  • id: 3,
  • name: "Tricia"
  • }
  • ];
  •  
  • // Return true to the application can load.
  • return( true );
  •  
  • }
  •  
  •  
  • }
  •  
  • // NOTE: CFScript added for Gist color-coding only. Remove.
  • </cfscript>

Without a channel listener in place, I can take advantage of the native filtering functionality using the "selector" data property. Let's look a the server-side code that pushes messages to clients over the WebSocket connection. You'll see that I am using the "userID" value as the filtering key:

send.cfm - Our Server-Side WebSocket Push / Publish Code

  • <!--- Param our User ID variable. --->
  • <cfparam name="url.id" type="numeric" default="0" />
  •  
  • <!--- Check to see if a user ID has been selected. --->
  • <cfif url.id>
  •  
  • <!---
  • Loop over the application users to find one with the same ID
  • so we can send a message to that user.
  • --->
  • <cfloop
  • index="pushUser"
  • array="#application.users#">
  •  
  • <!---
  • Check to see if this user record is the one we're going
  • to be sending a message to.
  • --->
  • <cfif (pushUser.id eq url.id)>
  •  
  • <!---
  • Push a message to a SPECIFIC user (NOTE: This may
  • be multiple clients, depending on the user's browser
  • configuration - this will push a message to any
  • client that has subscribed with the given UserID).
  • --->
  • <cfset wsPublish(
  • "demo",
  • "Hello #pushUser.name#, I hope you are well.",
  • {
  • selector: "userID eq #pushUser.id#"
  • }
  • ) />
  •  
  • <!--- We found the user, no need to keep looping. --->
  • <cfbreak />
  •  
  • </cfif>
  •  
  • </cfloop>
  •  
  • </cfif>
  •  
  •  
  • <!--- ----------------------------------------------------- --->
  • <!--- ----------------------------------------------------- --->
  •  
  •  
  • <!--- Turn off debugging output. --->
  • <cfsetting showdebugoutput="false" />
  •  
  • <!--- Reset the output buffer. --->
  • <cfcontent type="text/html; charset=utf-8" />
  •  
  • <cfoutput>
  •  
  • <!doctype html>
  • <html>
  • <head>
  • <meta charset="utf-8">
  • <title>Using ColdFusion 10 WebSocket Native Filtering</title>
  • </head>
  • <body>
  •  
  • <h1>
  • Send A Message To A User
  • </h1>
  •  
  • <ul>
  •  
  • <!---
  • Output a link to send a static message to each of
  • the users.
  • --->
  • <cfloop
  • index="user"
  • array="#application.users#">
  •  
  • <li>
  • <a href="./send.cfm?id=#user.id#">
  • Send to #user.name#
  • </a>
  • </li>
  •  
  • </cfloop>
  •  
  • </ul>
  •  
  •  
  • <!--- Check to see if we have a user we pushed to. --->
  • <cfif !isNull( pushUser )>
  •  
  • <p>
  • Pushed to #pushUser.name#!
  • </p>
  •  
  • </cfif>
  •  
  • </body>
  • </html>
  •  
  • </cfoutput>

As you can see, when I use the wsPublish() function to publish a message on the WebSocket channel, I am including the key-value pair:

selector: "userID eq #pushUser.id#"

This tells ColdFusion's native WebSocket filtering to only push the given message down to users who have previously subscribed to the given channel [demo] with the subscription-key, userID, having the same value as "#pushUser.id#".

On the client-side of the code, the developer must then define a "userID" property when subscribing to the WebSocket channel. Here is the user interface (UI) of the client-side demo - notice that I am grabbing a userID value out of the URL collection and then using it to create a global JavaScript variable:

user.cfm - Our Client-Side User Interface (UI)

  • <!--- Param the user's selected persona. --->
  • <cfparam name="url.id" type="numeric" default="0" />
  • <cfparam name="url.name" type="string" default="" />
  •  
  • <!--- Make sure the user has selected a persona. --->
  • <cfif !url.id>
  •  
  • <!--- Redirect back to login. --->
  • <cflocation
  • url="./index.cfm"
  • addtoken="false"
  • />
  •  
  • </cfif>
  •  
  •  
  • <!--- ----------------------------------------------------- --->
  • <!--- ----------------------------------------------------- --->
  •  
  •  
  • <!--- Turn off debugging output. It can't help us in WebSockets. --->
  • <cfsetting showdebugoutput="false" />
  •  
  • <!--- Reset the output buffer. --->
  • <cfcontent type="text/html; charset=utf-8" />
  •  
  • <!doctype html>
  • <html>
  • <head>
  • <meta charset="utf-8">
  • <title>Using ColdFusion 10 WebSocket Native Filtering</title>
  •  
  • <script type="text/javascript">
  • <cfoutput>
  •  
  • // We need to pass the Application name through with the
  • // WebSocket connection so ColdFusion knows which memory
  • // space to access.
  • var coldfusionAppName = "#getApplicationMetaData().name#";
  •  
  • // Let's pass the user ID through with each WebSocket
  • // request. This way, we can use implicit WebSocket
  • // filtering on the server-side.
  • var coldfusionUserID = #url.id#;
  •  
  • </cfoutput>
  • </script>
  •  
  • <!--
  • Load the script loader and boot-strapping code. In this
  • demo, the "main" JavaScript file acts as a Controller for
  • the following Demo interface.
  • -->
  • <script
  • type="text/javascript"
  • src="./js/lib/require/require.js"
  • data-main="./js/main">
  • </script>
  • </head>
  • <body>
  • <cfoutput>
  •  
  • <h1>
  • Hello, I'm #url.name#
  • </h1>
  •  
  • <p>
  • Check out my <em>JavaScript console</em> - that's where
  • my messages show up.
  • </p>
  •  
  • <p>
  • <a href="./index.cfm">Choose a differen user</a>.
  • </p>
  •  
  • </cfoutput>
  • </body>
  • </html>

Once I have this global JavaScript variable in place, I can use it to subscribe to the ColdFusion WebSocket. In this case, my JavaScript controller will be using my ColdFusion WebSocket AMD module - ColdFusionWebSocket() - to perform the subscription and listening actions.

main.js - Our JavaScript Demo Controller

  • // Define the paths to be used in the script mappings. Also, define
  • // the named module for certain libraries that are AMD compliant.
  • require.config({
  • baseUrl: "js/",
  • paths: {
  • "domReady": "lib/require/domReady",
  • "jquery": "lib/jquery/jquery-1.7.1",
  • "order": "lib/require/order",
  • "text": "lib/require/text",
  • }
  • });
  •  
  •  
  • // Load the application. In order for the demo controller to
  • // run, we need to wait for jQuery and the CFWebSocket module to
  • // become available.
  • require(
  • [
  • "jquery",
  • "../../../cfwebsocket",
  • "domReady"
  • ],
  • function( $, ColdFusionWebSocket ){
  •  
  •  
  • // Create an instance of our ColdFusion WebSocket module
  • // and subscribe to the "Demo" channel. We are setting the
  • // userID as a custom header that will be passed-through with
  • // each socket request. This way, we can use the native
  • // WebSocket selector and filtering when we either subscribe
  • // to or publish from channels, respectively.
  • var socket = new ColdFusionWebSocket(
  • coldfusionAppName,
  • "demo",
  • {
  • userID: coldfusionUserID
  • }
  • );
  •  
  •  
  • // Listen for published messages on the "Demo" channel.
  • socket.on(
  • "message",
  • "demo",
  • function( event, data ){
  •  
  • console.log( "Published:", data );
  •  
  • }
  • );
  •  
  •  
  • // Listen for publish errors.
  • socket.on(
  • "error",
  • function( event, message ){
  •  
  • console.log( "Error:", message );
  •  
  • }
  • );
  •  
  •  
  • }
  • );

Notice that when we instantiate the ColdFusionWebSocket() module, we are passing along a custom header, userID, with the subscription. This is the property that will be evaluated by the server-side selector property (from above):

selector: "userID eq #pushUser.id#"

This example uses neither ColdFusion session manage nor any WebSocket channel listeners; and still, if you watch the video, you'll see that this filtering configuration pushes messages only to the appropriate client. Without a Channel Listener, you lose the ability to add fine-tuned security and message interception; but, if all you want to do is filter messages by user, the native ColdFusion WebSocket filtering makes it incredibly easy.

The ColdFusionWebSocket() AMD module and this demo is available on my GitHub account.


You Might Also Be Interested In:



Reader Comments

Mar 23, 2012 at 12:49 PM // reply »
11 Comments

@Ben, another great post and a great example of why I read your blog; you let us all learn together.

I still haven't had a break in projects to play with WS but rest assured I'll be back to try this code out!


Mar 23, 2012 at 1:26 PM // reply »
11,314 Comments

@Brian,

Thanks my man! Apparently, this behavior is actually documented, as Ray Camden pointed out after I posted this :) I swear, I read the docs over and over again and never saw it!!


Post A Comment

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.

Please review the following issues:

Author Name:


Author Email:

Author Website:

Comment:

Supported HTML tags for formatting: <strong>bold</strong>   <em>italic</em>   <code>code</code>







  • Help Wanted - Find Your Next ColdFusion Job
Ben Nadel's Company - Epicenter Consulting Recent Blog Comments
Jun 19, 2013 at 11:33 AM
Filter vs. ngHide With ngRepeat In AngularJS
In your assessment, is it correct to say that given a list of say 500 items its more performant to use the `ngHide` method over the `filter` method? ... read »
Jun 19, 2013 at 10:18 AM
ColdFusion Path Usage And Manipulation Overview
Anyone happen to know if the file created by getTempFile will be automatically removed at any point? Nothing mentioned in the docs, and restarting CF doesn't remove them, so it seems it needs manu ... read »
Jun 19, 2013 at 9:41 AM
Working With Inherited Collections In AngularJS
I actually just ran into this same situation with a demo I was putting together. Your implementation of multi-lvl $scope's > Mine :) ... read »
Jun 19, 2013 at 8:17 AM
My Experience With AngularJS - The Super-heroic JavaScript MVW Framework
@Prateek, to match a word or text you should use .toContain('word') that's a jasmine reference. website is : http://pivotal.github.io/jasmine/ ... read »
Jun 19, 2013 at 8:10 AM
My Experience With AngularJS - The Super-heroic JavaScript MVW Framework
Hi Guys, Actually i am doing e2e test of angular js of my project but i am not getting one thing that is how to press enter key through the test when my form is filled as i am not using a button but ... read »
Jun 18, 2013 at 9:20 PM
Mapping AngularJS Routes Onto URL Parameters And Client-Side Events
I couldn't find examples of passing multiple arguments using the when() routing statement so figured out through trial and error that you can pass multiple arguments using the following format: .whe ... read »
Jun 18, 2013 at 3:39 PM
Experimenting With The Amazon Simple Storage Service (S3) API Using ColdFusion
Hi Ben, THANKS! While not bleeding edge, it is new to me & I like learning new things every day! ... read »
Jun 18, 2013 at 12:30 PM
Disabling Auto-Correct And Auto-Capitalize Features On iPhone Inputs
Also spellcheck="false" should be mentioned as part of html5 specs ... read »
InVision App - Prototyping Made Beautiful With Prototyping Tools