Skip to main content

ColdFusion 10: Using filterCriteria in WebSockets for subscribing and publishing

Yesterday Ben Nadel asked me a question on Twitter, on using filterCriteria when publishing a message on a web socket channel from server side. The method ‘wspublish’ allows you to perform a server side push to a client who has subscribed to a channel. It takes three parameters – channelName, message and filterCriteria. I always assumed that the values present in subscriberInfo and publisherInfo can be compared in Channel Listener functions (canSendMessage, beforePublish etc) before the message can be received by the client. Although this technique is available, what I found after having a discussion with a fellow developer (Awdhesh), is that I can specify simple conditions when subscribing or publishing.

So what does it mean to subscribe to a channel with conditions? And more importantly why do I need it? Consider a scenario where you are subscribing to a particular company stock and would like to know its stock value in real time. The server on the other hand keeps updating the stock values for all the companies and you might end up receiving unnecessary data. Instead you can specify a filter criteria while subscribing to a channel and only the messages that satisfy the criteria will be delivered. Here’s how you do it:

socket.subscribe("myChannel", {name: 'Sagar', age: 25, selector: "stock eq 'ADBE'"}, myChannelHandler);

The second parameter is a JSON string containing some key-value pairs. One of the keys is the 'selector' where the filter criteria is specified. Here I’m declaring a condition ‘stock eq ADBE’ i.e. I’m interested in receiving stock quotes for ADOBE only.

The publisher can send a message on the channel by specifying the same as the third parameter:

<cfset wspublish("myChannel", 50, {stock=’ADBE’})>

The second parameter is the message (value of the stock) and the third parameter is a struct that specifies the key ‘stock’ with its value set to ‘ADBE’. When this message is published on the channel it will be received by the client since it matches the filter criteria specified by the client at the time of subscribing to the channel. If a message is published on the channel with the third parameter set to {stock=’MSFT’} then the same wouldn’t be sent to the client who has subscribe to ADBE stocks.

Similarly a filter criteria can be specified at the time of publishing as well. In the same context of stocks; say you want only those clients whose age is greater than 21 to receive the stock update. You can do that by specifying the filter criteria while publishing the message on a channel:

<cfset wspublish("myChannel", 50, {stock=’ADBE’, selector="age GT 21"}})>

Again, the filter criteria ‘age GT 21’ is specified as a value to the key ‘selector’. If you observe the subscribe method, it has ‘name’ and ‘age’ as keys specified as third parameter. Before publishing the message to a subscriber the value of the key ‘age’ is checked and if it satisfies the condition (age GT 21) then the message (stock value) will be published and received by the subscriber.

Demo:
I have created a sample demo that you can download it here. Open publisher.cfm, subscriber-ADBE.cfm, subscriber-MSFT.cfm on three browser instances. Click on the subscribe button to subscribe to the channels. In publisher.cfm select the stock from the drop down list and provide a value and click ‘Publish’. You’ll see that the subscribers (ADBE and MSFT) would receive values only for those stock quotes that they have subscribed to.

Comments

  1. Sagar, thanks for the write-up. I could have sworn that I did something along these lines; though, it's definitely possible that I was confusing when to "selector" and when not to. Let me go back and try to work something. In the meantime, I worked around the situation by using a persisted UserID, then return( true | false ) in the canSendMessage() event.

    ReplyDelete
  2. yes, the filter criteria is copied to subscriberInfo and publisherInfo which can then be used in listener functions. The canSendMessage function is called for each client and you can achieve the same. I think this is the right way to use filter criteria if you're performing a complex check before allowing the message to be sent to the client. If it is a simple check, then you can use this method.

    ReplyDelete
  3. yes, the filter criteria is copied to subscriberInfo and publisherInfo which can then be used in listener functions. The canSendMessage function is called for each client and you can achieve the same. I think this is the right way to use filter criteria if you're performing a complex check before allowing the message to be sent to the client. If it is a simple check, then you can use this method.

    ReplyDelete
  4. I'm gonna try this again this morning. Hopefully, I can get this to work. I'll let you know.

    ReplyDelete
  5. Sagar, I think there maybe something wrong with my version of the CF10 Beta - maybe it's an earlier version? I download your code and tried to run it. I had to remove the CFForm stuff (since the mapping was doing something strange). However, when I run the code, I didn't see anything happening (on publish). Take a look at this video: http://screencast.com/t/TAk2XRxQA8

    This is the same thing I was experiencing in my experiments. Maybe my CF10 is too early?

    ReplyDelete
  6. I think I uploaded the wrong version of demo files. Can you remove the filtercriteria i.e. the selector statement from publisher.cfm. That should work. 

    ReplyDelete
  7.  Ok, cool. That seems to have worked. Let me see if I can get an equivalent version running in my demo. Thanks!

    ReplyDelete
  8. OK! I'm finally making some progress. I've narrowed down my issue. If I use a *custom* channel listener CFC, I canNOT get the filtering to work; however, if I remove the listener so that the application uses the default ChannelListener.cfc, THEN the filter DOES work. Very strange! I'll see if I can figure out why my custom channel listener is overriding the native behavior. Is that even possible?

    ReplyDelete
  9. I did some more testing - I copied the core ChannelListener.cfc to my local application directory and tried using that. This breaks the filtering. It looks like the only way I can get the filtering to work is to NOT provide a local channel listener component: http://screencast.com/t/tZU7v9MeBa

    Maybe this is the intended behavior? Does the filtering only work if you don't provide your own event handles for canSendMessage(), etc?

    ReplyDelete
  10. In custom channel listener the filter criteria is copied to subscriberInfo and publisherInfo. You can use this perform some check in canSendMessage method and then return a boolean value. And yes, all communication will then go through this method event in a case where you have specified selector in filter criteria.

    If the method canSendMessage returns true always then all clients would receive the message published on the channel.

    ReplyDelete
  11.  I just confirmed, this also breaks your Demo as well. If I copy the ChannelListener.cfc (from the CFIDE folder) into your demo directory (and add the extends="CFIDE.websocket.ChannelListener") attribute, your demo filtering no longer works.

    ReplyDelete
  12. Yes that's the intended behavior because the channel listener function - canSendMessage would be invoked before sending the message to the subscriber. By default this method returns true and hence all the subscribers would get the message and yes it overrides the condition that you have specified in filter criteria (selector).

    ReplyDelete
  13. Sagar, thank you for all your help yesterday! I am super excited to finally understand how this feature works. I put this together for my own collection. http://www.bennadel.com/blog/2352-ColdFusion-10-Native-WebSocket-Filtering-And-Channel-Listeners-Are-Mutually-Exclusive.htm

    ReplyDelete
  14. Thanks for putting that up Ben. I'm glad that I was of help. BTW I would like to meet Joanna, Sarah and Tricia some day :)

    ReplyDelete
  15.  Ha ha, they are nice people :) After I posted this, Ray Camden pointed out that this feature (canSendMessage() vs. filtering) was clearly in the documentation. I guess I missed it! So sad, cause I kept reading and re-reading the docs trying to figure out why it wasn't working :D Oh well.

    ReplyDelete
  16. Sagar, Do you have any experience with front end controllers using websockets? I'm using ColdBox 3.5 on a new project and I cannot get wspublish() to work correctly. It looks like from the stack trace that the exception is being thrown at the onRequest method. The client has no issues in subscribing and it seems that the issue is isolated within the framework. Just thought I'd ask as I spin my wheels :)

    Thanks...Matt B

    ReplyDelete
  17. Matt, I've not tried CF WebSockets with any of the frameworks and hence I can't comment on that. Can you share the code and the stack trace, I'll try to look into it.

    ReplyDelete
  18.  Thank you Sagar, this may actually be tied to how Apache does rewriting of the URL to remove the "/index.cfm." I am tweaking the rule right now and will post back what I find.

    ReplyDelete
  19. btw, this is the bug: https://bugbase.adobe.com/index.cfm?event=bug&id=3650142

    ReplyDelete

Post a Comment

Popular posts from this blog

Custom validation messages for HTML5 Input elements using the constraint validation API

HTML5 has introduced several input types such as EMAIL, URL, RANGE, SEARCH, DATE, TIME, etc,. Most of the modern browsers have implemented them and are ready to be used in a HTML document. Another exciting feature introduced in HTML5 is the form validation. Instead of writing JavaScript to validate users input, browsers can now validate it and show an appropriate message if the validation fails. The validation message is shown in line with the field for which the validation has failed. The default error message is shown when the validation fails. In this post I'll explain how these error messages can be changed.

Adding beforeRender and afterRender functions to a Backbone View

I was working on a Backbone application that updated the DOM when a response was received from the server. In a Backbone View, the initialize method would perform some operations and then call the render method to update the view. This worked fine, however there was scenario where in I wanted to perform some tasks before and after rendering the view. This can be considered as firing an event before and after the function had completed its execution. I found a very simple way to do this with Underscore's wrap method.

A cheat sheet of keyboard shortcuts in ColdFusion Builder

In my last post I have explained about keyboard shortcuts in ColdFusion Builder 2.0. This blog post contains a list of all shortcut keys and I have listed these keyboard shortcuts based on the categories that it falls into. Well, this post was not planned since users can easily get to know the keyboard shortcuts by navigating to the preferences (ColdFusion -> Profiles -> Keys). However, I met Joshua at Scotch on the rocks in Edinburgh and he suggested that it would be nice to have the list of keyboard shortcuts handy. So this post is for those who would like to have the list with them and refer it whenever required.


Keyboard shortcuts for inserting text:

These are the keyboard shortcuts which are used to insert some text into the editor:

CommandKeyboard shortcuts on WindowsKeyboard shortcuts on MacInsert anchor tagCTRL + T, LCMD + T, LInsert bold tagCTRL + T, BCMD + T, BInsert br tagCTRL + T, RCMD + T, RInsert cfabortCTRL + T, ACMD + T, AInsert cfdumpCTRL + T, DCMD + T, DInsert cfs…