Skip to main content

Server sent events with HTML5 and ColdFusion

There are several ways to interact with the server apart from the traditional request\response and refresh all protocol. They are polling, long polling, Ajax and Websockets (pusherapp). Of all these Ajax and Websockets have been very popular. There is another way to interact with the server such that the server can send notifications to the client using Server Sent Events (SSE). SSE is a part of HTML5 spec: http://dev.w3.org/html5/eventsource/

What are Server Sent Events (SSE)
The specification says "API for opening an HTTP connection for receiving push notifications from a server in the form of DOM events. The API is designed such that it can be extended to work with other push notification schemes such as Push SMS."
Server Sent Events operate over HTTP and it provides an interface 'EventSource'. Web applications can subscribe to an EventSource and receive updates from the server side i.e. the server can push data to the clients as and when it wishes to do so.

Why not Websockets?
Websockets are very useful in designing various applications such as Chat applications, Games etc,. where in communication takes place over a bi-directional channel between the client and server. However in applications such as Stock ticker, Feed reader the server responds with a message to the client and client receives it and shows it to the user. In such applications the client doesn't post a message very often. In these scenarios SSEs are very useful. Another advantage of using SSEs over Websockets is that it operates over HTTP meaning it doesn't require a special protocol or any server side implementation.

An Example of SSEs with ColdFusion
As mentioned earlier the web application can subscribe to the server using the EventSource interface. So in my JavaScript I have this code:

var source = new EventSource('outputMessages.cfm');

It's very simple. We are subscribing to the server side code which is 'outputMessages.cfm'. Once the connection is successful, the client can then start receiving messages from the server. The EventSource object has the following event listeners: message, open and error.
source.addEventListener('message', function(e){     document.body.innerHTML += e.data + " "; }); source.addEventListener('open', function(e){     alert('open') }, false); source.addEventListener('error', function(e){     if (e.eventPhase == EventSource.CLOSED) {         alert('closed')     } }, false);

The EventSource object can then run these scripts in the background with out blocking any scripts. After establishing the connection with the server, the 'onopen' event will be fired and the client is ready to receive the messages from the server. If the server responds with the message then the 'onmessage' event will be fired and it's corresponding event handler will be executed. The 'onerror' event would be fired when the server has completed its execution and as it is observed in the above code the state of the EventSource object would be set to CLOSED. The EventSource object expects the server to send data with MIME type text/event-stream and the data will be sent in the following format:

data: Your message here \n\n

It expects the string 'data:' and then the message and then end of line character. The extra end of line character marks the end of message i.e. users can send multiple data lines in this way and then send the end of line character to mark the end of message. The below cfm file does just that:
<cfheader http:="http:" name="Content-Type" value="text/event -stream; charset=utf-8"> <cfheader> <cfsetting requesttimeout="60"> <cffunction name="sendData">     <cfoutput>data: #timeFormat(now(), "medium")# #Chr(10)#</cfoutput>     <cfoutput>data: End of this message #Chr(10)#</cfoutput>     <cfoutput>#Chr(10)#</cfoutput>     <cfflush /> </cffunction> <cfloop from="1" index="i" to="5">     <cfset senddata="senddata">     <cfthread action="sleep" duration="1000"> </cfthread> </cfloop>

The cfcontent tag sets the MIME type to text/event-stream and then the requestTimeout is set to 60 seconds. The function sendData is called 50 times (in cfloop) and will output the data to the client every second. The third cfoutput sends just the end of line character marking the end of message.
The loop would run for 50 seconds and once the served side program has completed its execution the state of the EventSource object would be set to CLOSED. The client then reestablishes the connection with server after 3 seconds and the same loop will be executed again.

SSEs can be of great help in reducing the bandwidth and in cases where the client wants to post some data then the XHR object comes in handy.

Comments

  1. Cool stuff Sagar! Do you know which broswers currently support this?

    ReplyDelete
  2. Thanks, this is my first hearing about SSE.

    Which HTML5 browser supports SSE?

    Thx

    ReplyDelete
  3. How many simultaneous connections can this handle? Each client connection consumes a thread, right? There's no way to have multiple listeners on a single thread and the client makes multiple connection requests too, right?

    This looks like it may be of use to send unique messages to individual logged in users.

    Which browsers/versions currently support SSE? (I recently wrote a CF app using websockets and had some issues with various browsers.)

    ReplyDelete
  4. @Todd, Henry

    Thanks, it works fine on Chrome. I guess it would work fine on Safari too, I haven't tried it though. On Opera it does establish the connection but the messages are not received. I'm not sure about this. I guess Opera has a different implementation for SSEs. No luck with Mozilla Firefox.

    @Anonymous:

    I have not load tested it and I cannot confirm the number of connections that it can handle. But I feel it shouldn't be a problem. The EventSource object works in the background and will not mess with rest of the script. It is very much possible to implement Twitter like application with SSEs.

    I think it will not take much time for all the browsers to be HTML5 compliant.

    ReplyDelete
  5. the cfm template has


    So I am assuming that the client would need to reconnect to the CFML server every minute

    ReplyDelete
  6. The EventSource object will take care of it. Once the server has completed its execution the client (EventSource object) would reconnect after 3 seconds.

    ReplyDelete
  7. actually, it looks like the client reconnects itself :-)

    ReplyDelete
  8. Yes. User need not write any extra logic to handle this.

    ReplyDelete
  9. This is some cool stuff. I hadn't heard of these before.

    ReplyDelete
  10. Thanks Ben.
    The EventSource object is the key here. With SSEs and Websockets the overhead on the server to handle various client requests is eliminated. The server sends the data to every connected client when appropriate, helping in reducing the load on the server.

    ReplyDelete
  11. Hey Sagar: it looks like Opera might use a different mime type for the response. Here is a helpful article - not sure if that is why your example won't work on Opera (and I don't have it installed here at work to test it myself).

    http://my.opera.com/WebApplications/blog/show.dml/438711

    ReplyDelete
  12. @Todd,

    The very first line said that Opera has added SSEs based on WHATWG specification (not W3C). Also, the content type should be set to application/x-dom-event-stream instead of text/event-stream. I just managed to sneak through it, felt that a few lines of code change will make my sample code to work on Opera.

    ReplyDelete
  13. Well done. I read about SSE but had not put much thought into how to execute.

    Looking forward to your next post!

    ReplyDelete
  14. Have you tried streaming messages in utf-8??

    I tried replace cfcontent with:

    <cfheader name="Content-Type" value="text/event-stream; charset=utf-8">

    Latest Chrome doesn't pick up the stream.

    ReplyDelete
  15. @Henry,

    It is accepting the header that you have mentioned. I'm on latest chrome - 10.9.648.134

    ReplyDelete
  16. Yes, it is accepting the header, but I couldn't stream anything in UTF-8 (e.g. in Chinese)

    ReplyDelete
  17. I haven't tried it. One way to work is to just to run the cfm file without any content-type. If that passes then, can you make sure the format is correct i.e. the chr(10) at the end of the line.

    ReplyDelete
  18. Thank you Sagar for this! So it's basically keeping the connection open until the .cfm page times out, and then it's sleeping for 3 seconds and opening the connection again. Is that it?

    ReplyDelete
    Replies
    1. @Phillip,
      You're right, the client tries to reconnect to the server after three seconds. The EventSource object does that on the users behalf. You as a developer need not write extra JavaScript code to do this.

      Delete

Post a Comment

Popular posts from this blog

File upload and Progress events with HTML5 XmlHttpRequest Level 2

The XmlHttpRequest Level 2 specification adds several enhancements to the XmlHttpRequest object. Last week I had blogged about cross-origin-requests and how it is different from Flash\Silverlight's approach .  With Level 2 specification one can upload the file to the server by passing the file object to the send method. In this post I'll try to explore uploading file using XmlHttpRequest 2 in conjunction with the progress events. I'll also provide a description on the new HTML5 tag -  progress which can be updated while the file is being uploaded to the server. And of course, some ColdFusion code that will show how the file is accepted and stored on the server directory.

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.