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 Scotch On The Rock (SOTR) 2010 (London) with:

Creating Remote ColdFusion Component (Web Service) Proxies In Node.js

By Ben Nadel on

The other day, I blogged about using the ColdFusion Socket Gateway to communicate between a Node.js (V8) and a ColdFusion (J2EE) application on the same server. I had wanted to use Sockets so that I could use "localhost" and a port number without having to worry about different domain names. But would that be so bad? What if, rather than using Sockets, I used HTTP requests to let Node.js communicate with ColdFusion as if it were a web service? Furthermore, since ColdFusion publishes a WSDL (Web Service Definition Language) file for its remote components, could I automatically create and assemble local proxy objects in Node.js that fully encapsulated the remote ColdFusion web service interaction? Heck yeah I can!

 
 
 
 
 
 
 
 
 
 

Before we can do anything in Node.js, we have to first set up our ColdFusion application. While our Node.js demo server will be running on "localhost", our ColdFusion application will be running on "cf8.bennadel.com". Behind the scenes, this ColdFusion application is also running on localhost; however, my Apache Virtual Host configuration requires an actual domain name.

The ColdFusion Side Of This Web Service

As with the socket-based experiment, this ColdFusion application will also revolve around the aggregation of girl names. However, since this approach is using a web service, we're going to turn of session management. That's not to say that web services can't use session management; but, it's just not the focus of this particular exploration.

Application.cfc

  • <cfcomponent
  • output="false"
  • hint="I define the application settings and event handlers.">
  •  
  • <!--- Define the application settings. --->
  • <cfset this.name = hash( getCurrentTemplatePath() ) />
  • <cfset this.applicationTimeout = createTimeSpan( 0, 0, 10, 0 ) />
  •  
  •  
  • <cffunction
  • name="onApplicationStart"
  • access="public"
  • returntype="boolean"
  • output="false"
  • hint="I initialize the application.">
  •  
  • <!---
  • Create a collection of girls. This will be a simple
  • array of strings.
  • --->
  • <cfset application.girls = [] />
  •  
  • <!--- Return true so the application can load. --->
  • <cfreturn true />
  • </cffunction>
  •  
  •  
  • <cffunction
  • name="onRequestStart"
  • access="public"
  • returntype="boolean"
  • output="false"
  • hint="I initialize the application.">
  •  
  • <!--- Check to see if we need to reset the application. --->
  • <cfif structKeyExists( url, "init" )>
  •  
  • <!--- Reset the application manually. --->
  • <cfset this.onApplicationStart() />
  •  
  • <!--- Redirect to page without init flag. --->
  • <cflocation
  • url="#cgi.script_name#"
  • addtoken="false"
  • />
  •  
  • </cfif>
  •  
  • <!--- Return true so the request can load. --->
  • <cfreturn true />
  • </cffunction>
  •  
  • </cfcomponent>

There's nothing special going on here. I show you the Application.cfc only to codify the fact that this application centers around a simple array of girls' names. The important part of the ColdFusion application is our remote ColdFusion component that offers web-based access to said collection (and to the mutation of it). This component (shown below) has four main methods:

  • get() - Gets the current list of girls.
  • add( name ) - Adds the given girl.
  • exists( name ) - Determines if the given girl exists in the list.
  • delete( name ) - Deletes all instances of the given girl

Let's take a look at the code. You'll see that these "remote" methods do nothing more than execute array-mutation functions on the cached collection.

GirlsAPI.cfc (Our ColdFusion Web Service)

  • <cfcomponent
  • output="false"
  • hint="I provide remote access to the girl collection.">
  •  
  • <!--- Default all the return formats to be JSON. --->
  • <cfparam
  • name="url.returnFormat"
  • type="string"
  • default="json"
  • />
  •  
  •  
  • <!--- ------------------------------------------------- --->
  • <!--- ------------------------------------------------- --->
  •  
  •  
  • <cffunction
  • name="add"
  • access="remote"
  • returntype="numeric"
  • output="false"
  • hint="I add the given girl to the collection ( and return the number of values in the resultant collection ).">
  •  
  • <!--- Define arguments. --->
  • <cfargument
  • name="name"
  • type="string"
  • required="true"
  • hint="I am the name of the girl being added."
  • />
  •  
  • <!--- Add to the collection. --->
  • <cfset arrayAppend( application.girls, arguments.name ) />
  •  
  • <!--- Return the current count of girls. --->
  • <cfreturn arrayLen( application.girls ) />
  • </cffunction>
  •  
  •  
  • <cffunction
  • name="delete"
  • access="remote"
  • returntype="numeric"
  • output="false"
  • hint="I delete all instances of the given name from the collection ( and return the number of values removed ).">
  •  
  • <!--- Define arguments. --->
  • <cfargument
  • name="name"
  • type="string"
  • required="true"
  • hint="I am the name of the girl being deleted."
  • />
  •  
  • <!--- Define the local scope. --->
  • <cfset var local = {} />
  •  
  • <!---
  • Create a counter to keep track of the number of records
  • that will be deleted.
  • --->
  • <cfset local.numberDeleted = 0 />
  •  
  • <!---
  • Loop backwards over the collection - this makes deleting
  • easier since we don't have to worry about undefined /
  • out-of-bounds indices.
  • --->
  • <cfloop
  • index="local.index"
  • from="#arrayLen( application.girls )#"
  • to="1"
  • step="-1">
  •  
  • <!--- Check to see if the current girl matches. --->
  • <cfif (application.girls[ local.index ] eq arguments.name)>
  •  
  • <!--- Delete the current record. --->
  • <cfset arrayDeleteAt( application.girls, local.index ) />
  •  
  • <!--- Increment the delete count. --->
  • <cfset local.numberDeleted++ />
  •  
  • </cfif>
  •  
  • </cfloop>
  •  
  • <!--- Return the number of record that were deleted. --->
  • <cfreturn local.numberDeleted />
  • </cffunction>
  •  
  •  
  • <cffunction
  • name="exists"
  • access="remote"
  • returntype="boolean"
  • output="false"
  • hint="I determine if the given girl already exists (at least once) in the current collection.">
  •  
  • <!--- Define arguments. --->
  • <cfargument
  • name="name"
  • type="string"
  • required="true"
  • hint="I am the name of the girl being tested."
  • />
  •  
  • <!--- Define the local scope. --->
  • <cfset var local = {} />
  •  
  • <!--- Loop over the array looking for the given girl. --->
  • <cfloop
  • index="local.girl"
  • array="#application.girls#">
  •  
  • <!--- Check to see if the given girl matches. --->
  • <cfif (local.girl eq arguments.name)>
  •  
  • <!---
  • We only need to find one instance - which we have
  • done. Return true for existence.
  • --->
  • <cfreturn true />
  •  
  • </cfif>
  •  
  • </cfloop>
  •  
  • <!---
  • If we made it this far then the girl could not be
  • located. Return false for a negative existence.
  • --->
  • <cfreturn false />
  • </cffunction>
  •  
  •  
  • <cffunction
  • name="get"
  • access="remote"
  • returntype="array"
  • output="false"
  • hint="I return the current collection of girls.">
  •  
  • <cfreturn duplicate( application.girls ) />
  • </cffunction>
  •  
  • </cfcomponent>

So far, nothing really exciting. There's also an index page for dumping out the current collection; but, I won't even bother showing that (you can see that in the video). Really, the cool stuff is how we are going to integrate this ColdFusion web service into our local Node.js application.

The Node.js Side Of This Web Service

A couple of years ago, I wrote about how poorly defined the CFAJAXProxy tag is in ColdFusion. In that post, I outlined some ways in which you could create browser-based proxies to the remote ColdFusion components. I wanted to see if I could take that same concept and apply it to a Node.js environment. I want to create Javascript wrappers for remote ColdFusion components; and, I want those wrappers to be automatically defined by the remote WSDL (Web Service Definition Language) file associated with the component.

To do this, I created a Node.js module named, "coldfusion-proxy.js". This module exports a single method, load(), which takes a remote ColdFusion component URL and a callback. This Node.js module then reads the remote WSDL file, caches the component structure locally, creates a local proxy to the remote component, and passes it off to the load() callback:

  • require( "./coldfusion-proxy" ).load(
  • remoteColdFusionComponentURL,
  • function( localCFCProxy ){
  •  
  • // Use localCFCProxy as if it were a local instance
  • // of the remote ColdFusion web service.
  •  
  • }
  • );

Once this local proxy is available, the Node.js environment can then use it as if it were a local version of the remote ColdFusion web service. All remote-access methods have automatically been defined on the local Javascript object. And, since all I/O is asynchronous in Node.js, each method of the local proxy expects the last argument to be callback.

NOTE: If no callback is provided, the proxy will use its own internal NOOP (no-operation) function as a place holder.

Ok, now that we see the concept, let's take a look at it in action. In the following Node.js server configuration, I am loading the remote ColdFusion web service proxy (for our GirlsAPI.cfc) and then initializing an HTTP server instance. When requests come into the Node.js application, the requested script-name then dictates how the local proxy will be used.

Server.js (Our Node.js HTTP Server)

  • // Include the necessary modules.
  • var sys = require( "sys" );
  • var http = require( "http" );
  •  
  • // Include the ColdFusion component proxy module. This will allow
  • // us to create local proxy objects for remote web services.
  • var cfcProxy = require( "./coldfusion-proxy" );
  •  
  •  
  • // ---------------------------------------------------------- //
  • // ---------------------------------------------------------- //
  •  
  •  
  • // Before we configure our server, we want to load our remote
  • // ColdFusion components proxies (so that they are immediately
  • // available on each request). The CFC proxy will allow us to invoke
  • // the remote methods as if they were local.
  • //
  • // Notice that we don't have to provide any method definitions -
  • // these will be gathered automatically by proxy loader (using the
  • // auto-generated WSDL file).
  • cfcProxy.load(
  • "http://cf8.bennadel.com/testing/nodejs/cf_proxy/GirlsAPI.cfc",
  • function( girls ){
  •  
  •  
  • // Now that our remote ColdFusion component proxies have
  • // finished loading, let's set up our HTTP server. Create an
  • // instance of our HTTP server.
  • var server = http.createServer(
  • function( request, response ){
  •  
  •  
  • // The script name of the request will server as our
  • // interface to the remote ColdFusion web service:
  • //
  • // /{method}/{firstArguemnt}
  • //
  • // NOTE: We are removing the first character as this
  • // is the leading slash and serves no purpose.
  • var scriptName = request.url.slice( 1 );
  •  
  • // Split the script name to find the invocation specs.
  • var invokeSpecs = scriptName.split( "/" );
  •  
  • // Pull the method name off the specs.
  • var methodName = invokeSpecs.shift();
  •  
  • // Define a method to handle the remote invocation
  • // responses (remember, all I/O is going to be
  • // asynchronous in a Node.js system).
  • var callback = function( apiResponse ){
  •  
  • // Write some debugging to the Node.js browser.
  • response.writeHead( 200 );
  • response.write( JSON.stringify( apiResponse ) );
  • response.end();
  •  
  • };
  •  
  •  
  • // Now, now that we have our infastructure set up,
  • // let's figure out how to route our incoming Node.js
  • // HTTP request.
  • if (methodName == "get"){
  •  
  • girls.get( callback );
  •  
  • } else if (methodName == "add"){
  •  
  • girls.add( invokeSpecs[ 0 ], callback );
  •  
  • } else if (methodName == "delete"){
  •  
  • girls.delete( invokeSpecs[ 0 ], callback );
  •  
  • } else if (methodName == "exists"){
  •  
  • girls.exists( invokeSpecs[ 0 ], callback );
  •  
  • } else {
  •  
  • // Command not recognized.
  • callback( "RESTful request not recognized." );
  •  
  • }
  •  
  •  
  • }
  • );
  •  
  • // Point the server to listen to the given port for incoming
  • // requests.
  • server.listen( 8080 );
  •  
  • // Output a success message.
  • console.log( "Server is now running on port 8080" );
  •  
  •  
  • }
  • );
  •  
  •  
  • // ---------------------------------------------------------- //
  • // ---------------------------------------------------------- //
  •  
  •  
  • // Output a success message.
  • console.log( "Server is being initialized on port 8080" );

As you can see, when the incoming requests gets routed, the local ColdFusion web service proxy, "girls," gets used as if it were a local copy of the web service. All the remote methods - get, add, exists, delete - have been defined on the local proxy and can be called as true object methods.

Now, let's take a look at the ColdFusion proxy module which allows for this local proxy object.

ColdFusion-Proxy.js

  • // Include the http library for making our web service requsts.
  • var http = require( "http" );
  •  
  •  
  • // ---------------------------------------------------------- //
  • // ---------------------------------------------------------- //
  •  
  •  
  • // I am a no-operation method that simplifies callback logic.
  • function noop(){
  • // Left intentionally blank.
  • };
  •  
  •  
  • // I simplify the GET request - I take care of the data chunking and
  • // only invoke the callback when the data has completely come back.
  • // Also, I allow the URL to be defined in its entirety, rather than
  • // as a separate domain and script.
  • function get( url, callback ){
  •  
  • // Parse the URL to get information needed to create the client
  • // and to GET the request.
  • var parsedUrl = require( "url" ).parse( url );
  •  
  • // Create a client to connect to.
  • var client = http.createClient(
  • (parsedUrl.port || 80),
  • parsedUrl.hostname
  • );
  •  
  • // Make the GET request.
  • var request = client.request(
  • "GET",
  • parsedUrl.pathname +
  • (
  • parsedUrl.query
  • ? ("?" + parsedUrl.query)
  • : ""
  • ),
  • {
  • "host": parsedUrl.hostname
  • }
  • );
  •  
  • // End the request (so everything gets flushed).
  • request.end();
  •  
  • // Bind to the response event of the outgoing request - this will
  • // only fire once when the response is first detected.
  • request.on(
  • "response",
  • function( response ){
  •  
  • // Create a buffer for our data response - we will use
  • // this to aggregate our individual data chunks.
  • var fullData = [ "" ];
  •  
  • // Bind to the data event so we can gather the response
  • // chunks that come back.
  • response.on(
  • "data",
  • function( chunk ){
  •  
  • // Add this to our chunk buffer.
  • fullData.push( chunk );
  •  
  • }
  • );
  •  
  • // When the response has finished coming back, we can
  • // flatten our data buffer and pass the response off
  • // to our callback.
  • response.on(
  • "end",
  • function(){
  •  
  • // Compile our data and add it to the response.
  • // I am using the fileContent property to
  • // mimic the way ColdFusion HTTP requests work.
  • response.fileContent = fullData.join( "" );
  •  
  • // Pass the response off to the callback.
  • callback( response );
  •  
  • }
  • );
  •  
  • }
  • );
  •  
  • }
  •  
  •  
  • // ---------------------------------------------------------- //
  • // ---------------------------------------------------------- //
  • // ---------------------------------------------------------- //
  • // ---------------------------------------------------------- //
  •  
  •  
  • // I am the cache of remote ColdFusion component webservice
  • // definitions (as defined by the WSDL file). This only needs to be
  • // loaded the first time. The WSDL will be cached by component URL.
  • var wsdlCache = {};
  •  
  •  
  • // I load and cache the Web Service Definition Language (WSDL)
  • // data for the given remote ColdFusion component. In essence, I am
  • // just caching the method names.
  • function loadProxy( componentUrl, callback ){
  •  
  • // Make a request to the remote ColdFusion component WSDL file
  • // (Web Service Definition Language) in order to gather the
  • // remote opreation that we need to proxy locally.
  • get(
  • (componentUrl + "?wsdl"),
  • function( response ){
  •  
  • // Create the method name cache for this component.
  • var methodNames = [];
  •  
  • // Find the operator definitions in the webservice
  • // definition markup. For each operation, we'll add a
  • // synthesized method to our proxy.
  • response.fileContent.replace(
  • new RegExp( "<" + "wsdl:operation name=\"([^\"]+)\">", "g" ),
  • function( $0, $1 ){
  •  
  • // Add the cached method name.
  • methodNames.push( $1 );
  •  
  • }
  • );
  •  
  • // Add the method names to the WSDL cache so that any
  • // subsequent requests for this CFC will be laoded
  • // instantly.
  • wsdlCache[ componentUrl ] = methodNames;
  •  
  • // Let the calling context know that WSDL has been cached
  • // locally (so that it can make use of it).
  • callback();
  •  
  • }
  • );
  • }
  •  
  •  
  • // I create a proxy using the cached information for the given
  • // remote ColdFusion component URL.
  • function createProxy( componentUrl ){
  •  
  • // Create a new proxy instance.
  • var proxy = {
  • componentUrl: componentUrl
  • };
  •  
  • // Get the method names out of the cache.
  • var methodNames = wsdlCache[ componentUrl ];
  •  
  • // Loop over the methods to create local copies of the remote
  • // methods (these will just be doing HTTP requests behind the
  • // scenes.
  • for (var i = 0 ; i < methodNames.length ; i++){
  •  
  • // Add this remote method proxy. Each instance will get its
  • // own copy of the remote methods.
  • (function( methodName ){
  •  
  • proxy [ methodName ] = function(){
  •  
  • // Invoke the remote method.
  • invokeRemoteMethod(
  • proxy,
  • methodName,
  • Array.prototype.slice.call( arguments )
  • );
  •  
  • // Return the proxy reference for possible method
  • // chaining (since we cannot return a value).
  • return( proxy );
  •  
  • };
  •  
  • })( methodNames[ i ] );
  •  
  • }
  •  
  • // Return the populated proxy instance.
  • return( proxy );
  •  
  • }
  •  
  •  
  • // I invoke the proxied-method on the remote ColdFusion component.
  • function invokeRemoteMethod( proxy, methodName, remoteArguments ){
  •  
  • // Check to see if the last argument of the remote arguments is
  • // our callback - if not, we'll use the noop instead. This way,
  • // our execution logic can be uniform.
  • if (
  • !remoteArguments.length ||
  • (typeof( remoteArguments[ remoteArguments.length - 1 ] ) != "function")
  • ){
  •  
  • // Use the NOOP function.
  • var callback = noop;
  •  
  • } else {
  •  
  • // The last argument is the callback - pop it off.
  • var callback = remoteArguments.pop();
  •  
  • }
  •  
  •  
  • // Create the query string need to invoke the remote method,
  • // starting with the name of the remote method.
  • var queryParts = [ ("method=" + methodName) ];
  •  
  • // Now, add an ordered argument for each remote argument.
  • // ColdFusion allows us to use positional arguments for web
  • // services, but CF is one-based. Therefore add one to each index
  • // of the argument position.
  • for (var i = 0 ; i < remoteArguments.length ; i++){
  •  
  • // Add the ordered argument.
  • queryParts.push(
  • ((i + 1) + "=" + encodeURIComponent( remoteArguments[ i ] ))
  • );
  •  
  • }
  •  
  • // Make the remote request to the full url. The response will be
  • // passed off to the callback.
  • get(
  • (proxy.componentUrl + "?" + queryParts.join( "&" )),
  • function( response ){
  • callback( JSON.parse( response.fileContent ) );
  • }
  • );
  •  
  • }
  •  
  •  
  • // ---------------------------------------------------------- //
  • // ---------------------------------------------------------- //
  • // ---------------------------------------------------------- //
  • // ---------------------------------------------------------- //
  •  
  •  
  • // I load the given remote ColdFusion component and then pass it
  • // off to the given callback when it has been initialized.
  • exports.load = function( componentUrl, callback ){
  •  
  • // Check to see if the given component has already been cached.
  • if (componentUrl in wsdlCache){
  •  
  • // We can populate this proxy immediately and pass it off
  • // to callback. Each load() request gets its own proxy
  • // instance so that the calling context can modifiy it if
  • // necessary (ex. cache requests).
  • (callback || noop)(
  • createProxy( componentUrl )
  • );
  •  
  • } else {
  •  
  • // The WSDL has not been loaded yet - we need to load it
  • // now. This will only need to be done once.
  • loadProxy(
  • componentUrl,
  • function(){
  •  
  • // Now that the remote component definition has been
  • // loaded, create an instance of it and pass it off
  • // to the calling context.
  • (callback || noop)(
  • createProxy( componentUrl )
  • );
  •  
  • }
  • );
  •  
  • }
  • };

There's a lot going on this file; but basically what's happening is that the Node.js module makes a request to the WSDL file of the given remote ColdFusion web service. Then, it parses that XML response in order to extract the names of the remote methods (operations) that are available. Once this list of methods is cached locally, the module then creates a Javascript object and populates it with proxy methods - one per remote operation.

The remote method invocation is greatly simplified by the fact that ColdFusion web services can use positional arguments, even for named parameters. This allows our Javascript proxy objects to present positional method signatures locally and still communicate with the remote ColdFusion server without having to fully parse the WSDL file.

The beauty of using a web service proxy is that the target ColdFusion web service can be located anywhere. In my case, both Node.js and ColdFusion happen to be running on the same server and the communication is relatively instantaneous. But, if the ColdFusion web service were remote, this would work in exactly the same way; and, since the I/O for the local proxy is already based on callbacks, any increase in latency is already accounted for.



Looking For A New Job?

100% of job board revenue is donated to Kiva. Loans that change livesFind out more »

Reader Comments

Ok, now what I want to see is 3 browsers:
1) NodeJS calls for getting, adding, etc
2/3) Clients connected w/ NowJS

:-) lol.

I think you could expand on this by adding the dispatchMessage call to easily update the connected clients.

Thoughts?

[and yes...I'm leaning on you to test as I hone my lazy genes] haha. :)

Reply to this Comment

Stumbled across your sockets gateway article and immediately thought, hey, why not use the proxy method…And then bam found you've been doing that too. Cool! thanks for posting.

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.