Many years ago, I took at look at the long-polling technique in ColdFusion. Long-polling creates a persistent HTTP connection that blocks-and-waits for data to be sent down over the network to the client. Eventually, this pattern became codified within the browser’s native functionality using EventSource
. I’ve never actually played with the EventSource
object; so, I thought it would be fun to put together a simple ColdFusion demo.
As a preface to this, I wanted to mention that I came across a number of articles that did not classify the EventSource
technique as a “long polling” technique. In those articles, they defined long polling as a blocking request that waited for a single message to arrive before terminating the connection and then reconnecting to the server.
That said, I don’t believe that long polling ever had to be that narrow in scope. I’ve always viewed long polling as a blanket term that covers any technique backed by a persistent uni-directional HTTP request. In fact, if you look at my “long polling” demo from 12-years ago, the serve-side ColdFusion code is almost identical to what I have in my EventSource
demo (if you ignore the modernized CFML syntax).
Suffice to say, I see EventSource
as a “long polling” technique that is much, much easier to implement now that the bulk of the logic is handled by native browser code.
To experiment with EventSource
and server-sent events, I’m going to maintain a “blocking queue” in my ColdFusion application scope. Then, I’ll create one ColdFusion page that pushes messages onto said queue (using traditional POST
-backs to the server); and, I’ll create another ColdFusion page that receives messages from that queue via EventSource
long polling.
My Application.cfc
does nothing but create an instance of LinkedBlockingQueue
to hold our messages. I decided to use this concurrent class instead of a native ColdFusion array so that the blocking aspect would be managed by lower-level Java code.
component
output = false
hint = "I define the application settings and event handlers."
{
// Define the application settings.
this.name = "ServerSentEvents";
this.applicationTimeout = createTimeSpan( 1, 0, 0, 0 );
this.sessionTimeout = false;
this.setClientCookies = false;
// ---
// LIFE-CYCLE EVENTS.
// ---
/**
* I run once to initialize the application state.
*/
public void function onApplicationStart() {
// The LinkedBlockingQueue is really just a fancy Array that has a .poll() method
// that will block the current request and wait for an item (for a given timeout)
// to become available. This will be handy in our EventSource target.
application.messageQueue = createObject( "java", "java.util.concurrent.LinkedBlockingQueue" )
.init()
;
}
}
With this messageQueue
in place, I created a very simple ColdFusion page that has an <input>
for a message and pushes that message onto the queue with each FORM
post:
<cfscript>
param name="form.message" type="string" default="";
// If a message is available, push it onto the queue for our EventSource / server-sent
// event stream.
if ( form.message.len() ) {
application.messageQueue.put({
id: createUniqueId(),
text: form.message.trim(),
createdBy: "Ben Nadel"
});
}
</cfscript>
<cfoutput>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="stylesheet" type="text/css" href="https://www.bennadel.com/blog/./styles.css"></link>
</head>
<body>
<h1>
Send a Message to the Queue
</h1>
<form method="post">
<input
type="text"
name="message"
placeholder="Message..."
size="50"
/>
<button type="submit">
Send to Queue
</button>
</form>
<script type="text/javascript">
// For some reason, the "autofocus" attribute wasn't working consistently
// inside the frameset.
document.querySelector( "input" ).focus();
</script>
</body>
</html>
</cfoutput>
Pushing messages onto the queue isn’t very interesting; but, things get a little more provocative when we start popping messages off of that queue and pushing them down over the network to the browser. When we create a server-sent event stream in ColdFusion, we have to adhere to a few implementation details:
The HTTP header,
Content-Type
, on the ColdFusion response has to betext/event-stream
.When pushing server-sent events, each line of the event data must be prefixed with,
data:
. And, each data line must end in a newline character.Individual messages must be delimited by two successive new-line characters.
We can optionally include a line prefixed with
event:
, for custom named events. By default, the event emitted in the client-side JavaScript ismessage
. However, if we include anevent:
line in our server-sent payload, the value we provide will then become the event-type emitted on theEventSource
instance.We can optionally include a line prefixed with
id:
, for a custom ID value.
By default, my CommandBox Lucee CFML server has a request timeout of 30-seconds. As such, I’m only going to poll the messageQueue
for about 20-seconds, after which the ColdFusion response will naturally terminate and the client-side EventSource
instance will attempt to reconnect to the ColdFusion API end-point.
In my code, I’m only blocking-and-polling the messageQueue
for 1-second at a time. There’s no technical reason that I can’t poll for a longer duration; other than the fact that I want to close-out the overall ColdFusion request in under 30-seconds. As such, by using a 1-second polling duration, it simply allows me to exercise more precise control over when I stop polling.
In the following server-sent event experiment, I’m providing the optional event:
and id:
lines as well as the required data:
line. In this case, my event will be called, cfmlMessage
.
<cfscript>
// Reset the output buffer and report the necessary content-type for EventSource.
content
type = "text/event-stream; charset=utf-8"
;
// By default, this Lucee CFML page a request timeout of 30-seconds. As such, let's
// stop polling the queue before the request times-out.
stopPollingAt = ( getTickCount() + ( 25 * 1000 ) );
newline = chr( 10 );
TimeUnit = createObject( "java", "java.util.concurrent.TimeUnit" );
while ( getTickCount() < stopPollingAt ) {
// The LinkedBlockingQueue will block-and-wait for a new message. In this case,
// I'm just blocking for up to 1-second since we're inside a while-loop that will
// quickly re-enter the polling.
message = application.messageQueue.poll( 1, TimeUnit.Seconds );
if ( isNull( message ) ) {
continue;
}
// By default, the EventSource uses "message" events. However, we're going to
// provide a custom name for our event, "cfmlMessage". This will be the type of
// event-listener that our client-side code will bind-to.
echo( "event: cfmlMessage" & newline )
echo( "id: #message.id#" & newline );
echo( "data: " & serializeJson( message ) & newline );
// Send an additional newline to denote the end-of-message.
echo( newline );
flush;
}
</cfscript>
The final piece of the puzzle is instantiating the EventSource
object on the client-side so that we can start long polling our server-sent events API end-point. In the following code, I’m taking the cfmlMessage
events sent by the ColdFusion server – and emitted by the EventSource
instance – and I’m merging them into the DOM (Document Object Model) by way of a cloned <template>
fragment.
Now, I should say that there is nothing inherent to the EventSource
object or to server-sent events that requires the use of JSON (JavaScript Object Notation). However, since I am encoding my message as JSON on the ColdFusion server, I then have to JSON.parse()
the payload in the JavaScript context in order to extract the message text.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="stylesheet" type="text/css" href="https://www.bennadel.com/blog/./styles.css"></link>
</head>
<body>
<h1>
Read a Message From the Queue
</h1>
<ul class="messages">
<!-- To be cloned and populated dynamically. -->
<template>
<li>
<strong><!-- Author. --></strong>:
<span><!-- Message. --></span>
</li>
</template>
</ul>
<script type="text/javascript">
var messagesNode = document.querySelector( ".messages" );
var messageTemplate = messagesNode.querySelector( "template" );
// Configure our event source stream to point to the ColdFusion API end-point.
var eventStream = new EventSource( "./messages.cfm" );
// NOTE: The default / unnamed event type is "message". This would be used if we
// didn't provide an explicit event type in the server-sent event in ColdFusion.
eventStream.addEventListener( "cfmlMessage", handleMessage );
eventStream.addEventListener( "error", handleError );
// --------------------------------------------------------------------------- //
// --------------------------------------------------------------------------- //
/**
* I handle a message event on the EventSource.
*/
function handleMessage( event ) {
console.group( "CFML Message Event" );
console.log( "Type:", event.type );
console.log( "Last Event Id:", event.lastEventId );
console.log( "Data:", event.data );
console.groupEnd();
// The payload does NOT HAVE TO BE JSON. We happened to encode the payload as
// JSON on the server; which is why we have to parse it from JSON here.
var payload = JSON.parse( event.data );
// Populate our cloned template with the message data.
var li = messageTemplate.content.cloneNode( true );
li.querySelector( "strong" ).textContent = payload.createdBy;
li.querySelector( "span" ).textContent = payload.text;
messagesNode.prepend( li );
}
/**
* I handle an error event on the EventSource. This is due to a network failure
* (such as when the ColdFusion end-point terminates the request). In such cases,
* the EventSource will automatically reconnect after a brief period.
*/
function handleError( event ) {
console.group( "Event Source Error" );
console.log( "Connecting:", ( event.eventPhase === EventSource.CONNECTING ) );
console.log( "Open:", ( event.eventPhase === EventSource.OPEN ) );
console.log( "Closed:", ( event.eventPhase === EventSource.CLOSED ) );
console.groupEnd();
}
</script>
</body>
</html>
If we now run the send.cfm
and the read.cfm
ColdFusion templates side-by-side (via a frameset), we can see that data pushed onto the messages queue in one frame is – more or less – made instantly available in the EventSource
event stream in the other frame:

And, if we look at the messages.cfm
HTTP response in Chrome’s network activity, we can see that a single, persistent HTTP request received all of the server-sent events that were emitting on the client-side:
While long polling is a very old technique, it’s nice to see how simple the browser technologies have made it. I don’t personally think it holds any significant benefit over using WebSockets, other than relative simplicity. But, I can definitely see this being useful in simple ColdFusion scenarios or prototypes where you want to demonstrate a value without having to have too much infrastructure behind it.
Want to use code from this post?
Check out the license.