Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
I am the chief technical officer at InVision App, Inc - a prototyping and collaboration platform for designers, built by designers. I also rock out in JavaScript and ColdFusion 24x7.
Meanwhile on Twitter
Loading latest tweet...
Ben Nadel at RIA Unleashed (Nov. 2010) with:

Porting The ColdFusion Application.cfc Framework Component Over To Node.js

By Ben Nadel on

Node.js has been a really fun and challenging programming environment for me to try and wrap my head around. To keep the experiment going, I thought I would try to port some of the ColdFusion Application Framework concepts - Application.cfc - over to a Node.js application. One of the things that I love about the ColdFusion application framework is that it simultaneously gives you a lot of structure and an incredible amount of power and granular control over every request. Out of the box, Nodes.js give you total control; but, offers very little in the way of structure. I wanted to see if I could create an Application.js module to provide more structure without sacrificing any of the low-level control.

 
 
 
 
 
 
 
 
 
 

The foundational layer of the ColdFusion application framework is provided by the Application.cfc. This is a ColdFusion component that defines the application settings and event handlers for the incoming request. One of the most powerful features of the ColdFusion application framework is that the Application.cfc is re-instantiated on every single request; this allows the user to exert very granular control over all aspects of the Application, Session, and Request life cycles.

For this programming experiment, I haven't attempted to implement any of the ColdFusion framework's implicit invocation of files (since Node.js doesn't really work that way); but, I have tried to port over the basic settings and event handlers.

What I ended up creating was the Application.js - a Node.js module that exports a single function. This anonymous function acts partly like a constructor and partly like a factory. The actual instantiation of the Application.js is left to underlying framework; however, the Application.js runs like a constructor.

Let's take a look at the Application.js properties.

Application Settings

  • this.name - The unique name of the application. This property makes more sense in a ColdFusion setting since multiple applications run under the same ColdFusion instance. If, however, you change the name property in any request, a new application will be booted up.
  • this.applicationTimeout - This is the number of seconds for which an application can sit idle before it times out (and the onApplicationEnd() event handler is invoked). Every request to the application resets the idle timeout. Furthermore, this value can be changed on a per-request basis.
  • this.sessionManagement - This dictates whether or not session management will be used for the current request. If it is turned on, a session scope will be available. This can be changed on a per-request basis.
  • this.sessionTimeout - This is the number of seconds for which a session can sit idle before it times out (and the onSessionEnd() event handler is invoked). Every request to the application (for a specific user) resets the idle timeout. Furthermore, this value can be changed on a per-request basis.
  • this.setClientCookies - This determines whether or not the session cookies will be set automatically by the underlying framework. If you want to manage session cookies manually (ex. for the purposes of encrypting), you can set this to false. However, if you do that, the "sessionID" cookie will have to be manually created for each request (in the pseudo constructor of the Application.js).
  • this.requestTimeout - This is the number of seconds the current request can remain open until it is forced to close. If you exclude this (or set it to null), the request will have no timeout and must be closed manually.

The Application.js module also provides hooks into the events that take place during the application, session, and request life cycles. These are only invoked if they are present in the current Application.js instance. Due to the asynchronous nature of Node.js, all of the "start"-based hooks require the explicit invocation of a callback to indicate that they have completed processing.

Application Event Handlers

  • this.onApplicationStart( request, response, callback ) - This initializes the application. It is called only once per application lifecycle. During this event, the request has an "application" scope. If the callback is invoked with "false", neither the application nor the request will continue processing.
  • this.onSessionStart( request, response, callback ) - This initializes the session. It is called only once per session lifecycle. During this event, the request has a "session" scope.
  • this.onRequestStart( request, response, callback ) - This initializes the request. It is called at the beginning of every single request. At this point, both the application and session scopes are available in the request object. If the callback is invoked with "false", the request will stop processing.
  • this.onRequest( request, response ) - This processes the actual request, augmenting the response in any way that is appropriate. If the onRequest() event handler is provided, the response has to be closed manually (ie. response.end()).
  • this.onRequestEnd( request, response ) - This gets called every time a response is closed. It provides one last opportunity to write to the response; or, to perform some other task such as logging. Notice that no callback is provided - there are no asynchronous capabilities for this event handler.
  • this.onSessionEnd( applicationScope, sessionScope ) - This gets called implicitly when a session times out. Since it is, by nature, an asynchronous event, there is no request or response associated with it. The only objects the event handler is given access to are the application scope and the session scope.
  • this.onApplicationEnd( applicationScope ) - This gets called implicitly when the application times out. Since it is, by nature, an asynchronous event, there is request or response associated with it. The only object the event handler is given access to is the application scope that just timed out.
  • this.onError( error [, request, response ] ) - This gets called when any errors are raised. Due to the asynchronous callback nature of Node.js, it is not always possible to determine what request was processing when an exception was raised. As such, the request and response objects are not always present.

NOTE: The error handling was, by far, the area in which I applied the least amount of effort. I hope to be able to improve this situation at some point.

Now that you have a sense of what is available in the Application.js, let's take a look at an actual instance of the module. This version makes use of the application and session scopes and outputs the most simple HTML response.

Application.js

  • // Define the factory object for Application.js. Since each incoming
  • // request has to define a new instance of the Application.js, we
  • // have to get around the module-based caching. This gives every
  • // request the opportunity to re-define the settings (this is a good
  • // thing - and a very powerful thing).
  • module.exports = function( request, response ){
  •  
  •  
  • // Define the application settings.
  • this.name = "ColdFusion.js Test App";
  •  
  • // I am the amount of time (in seconds) the application can
  • // sit idle before it timesout. Once timing out, the
  • // onApplicationEnd() event handler will be implicitly invoked,
  • // and any subsequent request will boot up a new application.
  • this.applicationTimeout = 15;
  •  
  • // I determine whether or not session management is being used
  • // for this page request. This can be turned on or off for each
  • // request.
  • this.sessionManagement = true;
  •  
  • // I am the amount of time (in seconds) the session can sit idle
  • // before it timesout. Once timing out, the onSessionEnd() event
  • // handler is implicitly invoked, and any subsequent request will
  • // boot up a new session.
  • this.sessionTimeout = 5;
  •  
  • // I determine if the session cookies should be set
  • // automatically by the framework. If true, a sessionID cookie
  • // will be sent back to the browser. If false, the cookie needs
  • // to be manually set.
  • this.setClientCookies = true;
  •  
  • // I am the amount of time the request can run before it is
  • // forced to end (optional - exclude this property for an open-
  • // ended response time).
  • this.requestTimeout = 5;
  •  
  •  
  • // ------------------------------------------------------ //
  • // ------------------------------------------------------ //
  •  
  •  
  • // I initialize the application.
  • this.onApplicationStart = function( request, response, callback ){
  •  
  • // Log event handler.
  • console.log( ">> Event :: onApplicationStart()" );
  • console.log( ">>>> Application Name :: " + request.application.getName() );
  •  
  • // Set an application value.
  • request.application.set( "message", "ColdFusion FTW!!" );
  •  
  • // Return true so the rest of the application can load.
  • return( callback( true ) );
  •  
  • };
  •  
  •  
  • // I initialize the session.
  • this.onSessionStart = function( request, response, callback ){
  •  
  • // Log event handler.
  • console.log( ">> Event :: onSessionStart()" );
  • console.log( ">>>> Session ID :: " + request.session.getSessionID() );
  •  
  • // Store a session value.
  • request.session.set( "hitCount", 0 );
  •  
  • // Return out so the framework knows the event is over.
  • return( callback() );
  •  
  • };
  •  
  •  
  • // I initialize the request.
  • this.onRequestStart = function( request, response, callback ){
  •  
  • // Log event handler.
  • console.log( ">> Event :: onRequestStart()" );
  •  
  • // Increment the session-based hit-count. Notice that once a
  • // value is stored in the session cache, it can be referenced
  • // without a getter / setter.
  • request.session.hitCount++;
  •  
  • // Return true so the rest of the request can load.
  • return( callback( true ) );
  •  
  • };
  •  
  •  
  • // I process the request.
  • this.onRequest = function( request, response ){
  •  
  • // Log event handler.
  • console.log( ">> Event :: onRequest()" );
  •  
  • // Set the content type.
  • response.setHeader( "content-type", "text/html" );
  •  
  • // Write out some content.
  • response.write(
  • "<h1>ColdFusion.js On Node.js</h1>" +
  • "<p>Hello - this is page request " +
  • request.session.hitCount +
  • ".</p>"
  • );
  •  
  • // End the response.
  • response.end(
  • "<p>" +
  • request.application.message +
  • "</p>"
  • );
  •  
  • };
  •  
  •  
  • // I tear down the request.
  • this.onRequestEnd = function( request, response ){
  •  
  • // Log event handler.
  • console.log( ">> Event :: onRequestEnd()" );
  •  
  • };
  •  
  •  
  • // I tear down the session.
  • this.onSessionEnd = function( applicationScope, sessionScope ){
  •  
  • // Log event handler.
  • console.log( ">> Event :: onSessionEnd()" );
  • console.log( ">>>> Session ID :: " + sessionScope.getSessionID() );
  •  
  • };
  •  
  •  
  • // I tear down the application.
  • this.onApplicationEnd = function( applicationScope ){
  •  
  • // Log event handler.
  • console.log( ">> Event :: onApplicationEnd()" );
  • console.log( ">>>> Application Name :: " + applicationScope.getName() );
  •  
  • };
  •  
  •  
  • // I handle any global errors.
  • //
  • // NOTE: The request / response objects may NOT be present. If
  • // the error occurred during an asynchronous callback, this
  • // Application.js instance might not even be the one that started
  • // the action that ended up raising the exception.
  • this.onError = function( error, request, response ){
  •  
  • // Log event handler.
  • console.log( ">> Event :: onError()" );
  • console.log( ">>>> Error Message :: " + error.message );
  •  
  • // Check to see if there is a response associated with this
  • // error is available and is not committed.
  • if (
  • response &&
  • !response.isCommitted()
  • ){
  •  
  • // Output the error.
  • response.write( "<p>An error has occurred: " + error.message + ".</p>" );
  •  
  • }
  •  
  • };
  •  
  •  
  • };

As you can see, the Application.js module exports a single function. This function is invoked for every single request which allows the programmer to implement very exacting control over all aspects of the request. Both this constructor function and many of the event handlers are given a request and response object. A shared "cookies" scope is available in these objects at all times and can be used to both set cookies and to manually manage the session identifiers (not demonstrated above). The application scope and session scope are only placed into the request object as the request progresses (ie. session is not yet available in the onApplicationStart() event handler).

When I start this ColdFusion.js application and make a few page requests, here is the console output that I get:

ben-2:coldfusion.js ben$ node server.js
ColdFusion.js server listening on port 8080
>> Event :: onApplicationStart()
>>>> Application Name :: ColdFusion.js Test App
>> Event :: onSessionStart()
>>>> Session ID :: CFJS-1-1304345098097
>> Event :: onRequestStart()
>> Event :: onRequest()
>> Event :: onRequestEnd()
>> Event :: onRequestStart()
>> Event :: onRequest()
>> Event :: onRequestEnd()
>> Event :: onRequestStart()
>> Event :: onRequest()
>> Event :: onRequestEnd()
>> Event :: onSessionEnd()
>>>> Session ID :: CFJS-1-1304345098097
>> Event :: onSessionStart()
>>>> Session ID :: CFJS-2-1304345106659
>> Event :: onRequestStart()
>> Event :: onRequest()
>> Event :: onRequestEnd()
>> Event :: onRequestStart()
>> Event :: onRequest()
>> Event :: onRequestEnd()
>> Event :: onSessionEnd()
>>>> Session ID :: CFJS-2-1304345106659
>> Event :: onApplicationEnd()
>>>> Application Name :: ColdFusion.js Test App

You'll notice that the first line in the above output is a request to run the file, "server.js" in node. This file simply includes the ColdFusion.js module and starts the server:

Server.js

  • // Start the ColdFusion server.
  • require( "coldfusion" ).start();

Once the server is started you can see that every event is logged to the console. And, you can see that I let a session timeout between page requests, demonstrating how new sessions get spun up and then shut down.

The ColdFusion application framework is, without a doubt, amazing. I don't know, however, if something like this really belongs in Node.js. I have attempted to port it over to Node.js as a learning experiment. Doing something like this has really forced me to think about how responses are processed, when headers get flushed, how Event Emitters work, and how cookies are managed. All in all, it's been a really exciting little project to work on.

If you are interested in looking at the code, feel free to take a look at my GitHub account:

https://github.com/bennadel/ColdFusion.js

As I have been learning Node.js, I have also, sort of, been trying to learn about Git and GitHub as well. Going forward, I am going to try and put more code in repositories.




Reader Comments

That's some good stuff. I think we're going to be using Node.js pretty soon so I'll give this a look once we begin.

Thx dude!

Reply to this Comment

@Ben,

Site notification pop-up. When a new notification is saved, have it pop-up a message to that user [if they're on the site].

Also considering an instant messaging piece for simple one-on-one messages.

Reply to this Comment

@John,

Ha ha, whoa there :) I'm a play and tinker kind of guy - I'm not nearly that organized.

Reply to this Comment

We have a node.js webservice backend that does complex imagemagick processing in lieu of Coldfusion's native image processing. Coldfusion handles the upload and passes the image location to the node which fires a complex single command imagemagick script. (resize 3 times output individual files, render shadows and custom text, etc.) once the processing is finished it "pushes" a notice back to user the image processor is complete if user is still on the site somewhere. If user is no longer subscribed to the push notice, then a email notice is sent instead.

Amazing how much processing you can do with imagemagick in one command.

Reply to this Comment

Hello Ben !

I am very interested in Your Node.js/NowJS---ColdFusion marriage efforts, too !

Please allow me several questions:
- Will You make a comprehensive/consolidated Node.js/NowJS---ColdFusion marriage package available ?
- What about security (authentication/authorization) for the Node.js/NowJS part of the equation ? When it comes to security for ColdFusion, I would look into a framework like ColdBox, for instance ... Because I'm not aware of standalone security solutions ... But perhaps You have something in store for the whole Node.js/NowJS---ColdFusion couple ?

TIA for Your reply !

Cheers and Tschüss

Kai (Tischler) from Northrhine-Westfalia in Germany

Reply to this Comment

@Kai,

If I may, I think you have to reconsider the NowJS/NodeJS approach. CF wouldn't poll Node for data. Node would push CF data. That's the difference.

Now, you definitely could ping Node for data but you're in CF...why not use CF for general data. I really believe Node shines in the vein of push data. It would be a hard sell to get me to use Node for general data retrieval.

With that said, I sit back to see Ben's thoughts on security. :)

Reply to this Comment

@David,

Very cool stuff. How does the ColdFuison service talk to the Node.js service? Is it doing a CFHTTP request or a socket connection or a directory watch or something?

@Kai,

As a quick aside, I have a German friend and I used to sign my emails with "Choose" as I could never remember how to spell "Tschüss". It always confused her :)

As far as security, 1) I'm not a master security person and 2), I honestly am not sure what the best approach to security is with a push-model. At some point, the way Push works is because the client has to subscribe to the server. So, the question is, how can the server make sure that only the right client is connection to the right channel over the port (NOTE: I don't think "channel" is a technical term - I just see it get used a lot with WebSocket type stuff).

To be honest, I don't really know. It seems to me that a client could always try to subscribe to the server in a whole bunch of ways and eventually get information pushed to them that they should not have gotten.

And, since I don't understand how things like NowJS and PusherApp work at a technical level, I can't even be sure how what they are doing.

It really is a great question. I'll see if I can dig up some better answers and you have got me curious as well.

Reply to this Comment

@Ben,

I think any subscription would have to be done with a timestamp or some other way of encrypting the "key". This way I can't jump into Console in Chrome, call into Node w/ the tweaked properties [say ID 23 vs 22], and start hearing things for user id 22.

Let us know what you find Ben. This is definitely a great topic.

Reply to this Comment

it is doing a regular cfhttp request to a local port.

The pub/sub is handled through our frontend nginx module. Though eventually we may bring that to node.js.

Reply to this Comment

The node.js actually pushes to the pub channel of the image processing unique subscriber channel id to nginx.

We may eventually decide to setup a dedicated node.js pub/sub handler, but so far nginx has handled everything well. It is our "swiss army knife" frontend server that proxies to Node, load balances to multiple cf instances, handles pub/sub via a custom module, and serves static content.

The only thing problems we have is node failing once in a while. It might be a memory leak with (sys) and imagemagick but we have a linux service monitor that restarts node on fail.

Reply to this Comment

@David,

If your Node.js is failing once in a while, do you have to restart it manually? If so, I've heard of the "Forever" module; this is a module that will monitor a child process and restart it if it has failed. Apparently that's how Nodejitsu hosting platform monitors their hosted apps.

Reply to this Comment

@Ben,

we use monit to monitor specific port, response, and pid.

we have 4 node daemons (unix service) running and nginx config to do failover to "upstream proxy locations". Its kind of a round robin deal. If nodeA fails, then nginx routes requests to nodeB, etc. In the meantime monit is set for 60 sec checks on each nodeA, nodeB, nodeC, nodeD. If nodeA crashed and is unresponsive then monit restarts it.

This provides the image processing to run without downtime.

Nginx is very versatile proxy front end server, and easy to config. I can honestly say I wouldn't deploy any web application service without Nginx as the frontend, ever (at least on linux boxes :) )

Reply to this Comment

@Ben,

Btw I've used to follow you back in the hay day when your blog had a picture of your father? ancestor?. Then I got a role as a UX designer, now back into coding.

Reply to this Comment

@David,

Thanks for the link to the blog post - this sounds like really interesting stuff. I have heard of NGinx before, but I can't quite recall the kind of stuff that it does. Sounds like you're building some really high-availability stuff.

Thanks for coming back :) How was the UX stuff? I actually try to do a lot of UX-related stuff at work these days; it's been an interesting, sometimes fun, sometimes frustrating challenge. Did you like it?

Reply to this Comment

@Ben,

Spent lots of time doing prototyping and testing on different virtual machines (mobile, ie5-8,linux)

I realized sometimes you got to enforce requirements on your app/product and realize that ubiquity is hard to reach.

It was for a global financial institution. Have you tested on windows 98? Our goal was to allow people/students to be able to interact with the app through an educational portal who are using ancient systems.

Reply to this Comment

Post A Comment

You — Get Out Of My Dreams, Get Into My Comments
Live in the Now
Oops!
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.