Learning ColdFusion 9: Application.cfc OnCFCRequest Event Handler For CFC Requests

Posted July 15, 2009 at 10:44 AM by Ben Nadel

Tags: ColdFusion

Earlier this week, Ray Camden blogged about ColdFusion 9's new onCFCRequest() event handler method in Application.cfc. If you look at the Application.cfc reference guide in the ColdFusion 9 documentation, you'll notice that it is not there at all (so far the CF9 docs have been extremely under whelming). As such, there are a lot of questions that I have about the use of the OnCFCRequest() event handler and very few answers. So, I figured it was time to take what Ray has shown us and start performing some experiments.

First thing's first, let's just get comfortable with the basic structure of the OnCFCRequest() event handler. As Ray mentioned, OnCFCRequest() takes three arguments:

  • Component: The name/path of the ColdFusion component requested by the user.
  • MethodName: The name of the method requested by the user for execution.
  • MethodArguments: The argument collection (struct) sent by the user in the request.

That's what it takes, but what does it return? From what I can gather, the ReturnType of the OnCFCRequest() method should be VOID just like its OnRequest() counter-part. Returning a value from this method does not seem to play any part in what actually gets returned in the page response. To return a value, you either have to output it within the method body, or stream it back via CFContent (covered later).

Here's what a simple Application.cfc component using OnCFCRequest() might look like:

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, 5, 0 ) />
  •  
  •  
  • <cffunction
  • name="onRequestStart"
  • access="public"
  • returntype="boolean"
  • output="false"
  • hint="I initialize the request for both CFM and CFC requests.">
  •  
  • <!--- Define arguments. --->
  • <cfargument
  • name="template"
  • type="string"
  • required="true"
  • hint="I am the template requested by the user."
  • />
  •  
  • <!--- Set page request settings. --->
  • <cfsetting showdebugoutput="false" />
  •  
  • <!--- Return true so page can run. --->
  • <cfreturn true />
  • </cffunction>
  •  
  •  
  • <cffunction
  • name="onRequest"
  • access="public"
  • returntype="void"
  • output="true"
  • hint="I process the user's CFM request..">
  •  
  • <!--- Define arguments. --->
  • <cfargument
  • name="template"
  • type="string"
  • required="true"
  • hint="I am the template requested by the user."
  • />
  •  
  • <!--- Include the requested page. --->
  • <cfinclude template="#arguments.template#" />
  •  
  • <!--- Return out. --->
  • <cfreturn />
  • </cffunction>
  •  
  •  
  • <cffunction
  • name="onCFCRequest"
  • access="public"
  • returntype="void"
  • output="true"
  • hint="I process the user's CFC request.">
  •  
  • <!--- Define arguments. --->
  • <cfargument
  • name="component"
  • type="string"
  • required="true"
  • hint="I am the component requested by the user."
  • />
  •  
  • <cfargument
  • name="methodName"
  • type="string"
  • required="true"
  • hint="I am the method requested by the user."
  • />
  •  
  • <cfargument
  • name="methodArguments"
  • type="struct"
  • required="true"
  • hint="I am the argument collection sent by the user."
  • />
  •  
  •  
  • <!--- RESPONSE OUTPUT -------------------------- --->
  • <!--- ------------------------------------------ --->
  •  
  • <!--- Output the query string. --->
  • <h3>
  • Query String:<br />
  • #cgi.query_string#
  • </h3>
  •  
  • <!--- Output the URL scope. --->
  • <cfdump
  • var="#url#"
  • label="URL Scope"
  • />
  •  
  • <br />
  •  
  • <!--- Output the onCFCRequest arguments. --->
  • <cfdump
  • var="#arguments#"
  • label="ARGUMENTS Scope"
  • />
  •  
  • <!--- ------------------------------------------ --->
  • <!--- END RESPONSE OUTPUT ---------------------- --->
  •  
  •  
  • <!--- Return out. --->
  • <cfreturn />
  • </cffunction>
  •  
  • </cfcomponent>

Notice that the OnRequest() and the OnCFCRequest() event handlers live side-by-side. We don't need to do anything special in order to get them to play nice. As Ray demonstrated in his blog post, calls made to remote CFCs will simply ignore OnRequest() and trigger OnCFCRequest(). For our initial demonstration purposes, my OnCFCRequest() outputs the query string, URL, and ARGUMENTS Scopes. This way we, we can see how ColdFusion pipes the request into the event handler method.

Now, let's set up a really simple ColdFusion component with a remote access method for testing:

Service.cfc

  • <cfcomponent
  • output="false"
  • hint="I am a service with some remote access.">
  •  
  •  
  • <cffunction
  • name="getGirl"
  • access="remote"
  • returntype="struct"
  • returnformat="json"
  • output="false"
  • hint="I return a girl object.">
  •  
  • <!--- Define arguments. --->
  • <cfargument
  • name="name"
  • type="string"
  • required="true"
  • hint="I am the name of the girl being returned."
  • />
  •  
  • <!--- Define the girl object. --->
  • <cfset local.girl = {
  • name = arguments.name
  • } />
  •  
  • <!--- Return girl object. --->
  • <cfreturn local.girl />
  • </cffunction>
  •  
  • </cfcomponent>

All this Service.cfc does is supply a remote method, GetGirl() which takes a girl's name, wraps it up in a struct, and returns it. If called remotely, I have defaulted the ReturnFormat to use JSON conversion.

With our simple Application.cfc / OnCFCRequest() event handler and Service.cfc remote object set up, let's make some page requests. First, I am going to call a simple REST-based request:

Service.cfc?method=getGirl&name=Tricia&returnformat=json

When we run this request, we get the following page output:

 
 
 
 
 
 
ColdFusion 9 OnCFCRequest() Event Handler. 
 
 
 

The first thing we can take away from this is that the call to the actual remote method never executed. The page request was piped into the OnCFCRequest() event handler; but, just as with the OnRequest() event handler, if we don't then explicitly call the requested component and method, it gets ignored. The next thing we can see is that ColdFusion appropriately separates the URL variable from the method arguments. Meaning, it doesn't send things like Method and ReturnFormat as part of the requested method's argument collection.

Seeing this, I wondered what would happen if we passed in "WSDL" with the query string:

Service.cfc?wsdl

Normally, this would present us with the Web Service Definition XML. But, when we run this, we get the following response:

 
 
 
 
 
 
ColdFusion 9 OnCFCRequest() Event Handler. 
 
 
 

It looks like WSDL requests are treated just like any other requests. This does not bode well for the use of OnCFCRequest() in conjunction with SOAP-based web service calls. My guess is that this kind of Application.cfc architecture is not going to play well with any kind of request that uses behind-the-scenes ColdFusion auto-wiring. Things like SOAP requests and AMF requests will probably fail (or be ignored - I am not sure how AMF makes requests).

That said, the rest of this exploration will stick to REST-based requests in which what you see is what you get in the response. Of course, up until now, we really haven't seen any response; or rather, we haven't seen the response expected by the intent of our request URL - all we've seen is the CFDump output we have in our OnCFCRequest() event handler. So, how do take our request, translate it to a CFC method execution, and then return that value?

I actually talked about this in depth when demonstrating how to handle errors in remote API calls in ColdFusion 8. The same principle applies: we need to make the CFC request, check the return format, perform any transformations manually, and then return it. To demonstrate this, I have beefed up the OnCFCRequest() event handler to follow this logic:

OnCFCRequest() Event Handler

  • <cffunction
  • name="onCFCRequest"
  • access="public"
  • returntype="void"
  • output="true"
  • hint="I process the user's CFC request.">
  •  
  • <!--- Define arguments. --->
  • <cfargument
  • name="component"
  • type="string"
  • required="true"
  • hint="I am the component requested by the user."
  • />
  •  
  • <cfargument
  • name="methodName"
  • type="string"
  • required="true"
  • hint="I am the method requested by the user."
  • />
  •  
  • <cfargument
  • name="methodArguments"
  • type="struct"
  • required="true"
  • hint="I am the argument collection sent by the user."
  • />
  •  
  • <!---
  • Create the target cfc instance on which we will be
  • performing the remote method call.
  • --->
  • <cfset local.cfc = createObject( "component", arguments.component ) />
  •  
  • <!---
  • Execute the remote method call and store the response
  • (note that if the response is void, it will destroy
  • the return variable).
  • --->
  • <cfinvoke
  • returnvariable="local.result"
  • component="#local.cfc#"
  • method="#arguments.methodName#"
  • argumentcollection="#arguments.methodArguments#"
  • />
  •  
  • <!---
  • Create a default response data variable and mime-type.
  • While all the values returned will be string, the
  • string might represent different data structures.
  • --->
  • <cfset local.responseData = "" />
  • <cfset local.responseMimeType = "text/plain" />
  •  
  • <!---
  • Check to see if the method call above resulted in any
  • return value. If it didn't, then we can just use the
  • default response value and mime type.
  • --->
  • <cfif structKeyExists( local, "result" )>
  •  
  • <!---
  • Check to see what kind of return format we need to
  • use in our transformation. Keep in mind that the
  • URL-based return format takes precedence. As such,
  • we're actually going to PARAM the URL-based format
  • with the default in the function. This will make
  • our logic much easier to follow.
  •  
  • NOTE: This expects the returnFormat to be defined
  • on your CFC - a "best practice" with remote
  • method definitions.
  • --->
  • <cfparam
  • name="url.returnFormat"
  • type="string"
  • default="#getMetaData( local.cfc[ arguments.methodName ] ).returnFormat#"
  • />
  •  
  • <!---
  • Now that we know the URL scope will have the
  • correct format, we can check that exclusively.
  • --->
  • <cfif (url.returnFormat eq "json")>
  •  
  • <!--- Convert the result to json. --->
  • <cfset local.responseData = serializeJSON( local.result ) />
  •  
  • <!--- Set the appropriate mime type. --->
  • <cfset local.responseMimeType = "text/x-json" />
  •  
  • <cfelseif (url.returnFormat eq "wddx")>
  •  
  • <!--- Convert the result to XML. --->
  • <cfwddx
  • action="cfml2wddx"
  • input="#local.result#"
  • output="local.responseData"
  • />
  •  
  • <!--- Set the appropriate mime type. --->
  • <cfset local.responseMimeType = "text/xml" />
  •  
  • <cfelse>
  •  
  • <!--- Convert the result to string. --->
  • <cfset local.responseData = local.result />
  •  
  • <!--- Set the appropriate mime type. --->
  • <cfset local.responseMimeType = "text/plain" />
  •  
  • </cfif>
  •  
  • </cfif>
  •  
  •  
  • <!---
  • Now that we have our response data and mime type
  • variables defined, we can stream the response back
  • to the client.
  • --->
  •  
  • <!--- Convert the response to binary. --->
  • <cfset local.binaryResponse = toBinary(
  • toBase64( local.responseData )
  • ) />
  •  
  • <!---
  • Set the content length (to help the client know how
  • much data is coming back).
  • --->
  • <cfheader
  • name="content-length"
  • value="#arrayLen( local.binaryResponse )#"
  • />
  •  
  • <!--- Stream the content. --->
  • <cfcontent
  • type="#local.responseMimeType#"
  • variable="#local.binaryResponse#"
  • />
  •  
  •  
  • <!--- Return out. --->
  • <cfreturn />
  • </cffunction>

As you can see, the OnCFCRequest() event handler takes the request, creates the CFC, calls the method, and then transforms and returns the result using CFContent. Now, when we call this URL:

Service.cfc?method=getGirl&name=Tricia&returnFormat=json

... we get this JSON response:

{"NAME":"Tricia"}

And, when we call this URL:

Service.cfc?method=getGirl&name=Tricia&returnFormat=wddx

... we get this WDDX response:

<wddxPacket version="1.0">
<header/>
<data>
<struct><var name="NAME"><string>Tricia</string></var></struct>
</data>
</wddxPacket>

That seems pretty straight forward, but I know what you're thinking - OK, so we've now re-created the inherent functionality of ColdFusion, but we've added a LOT of overhead. How can that be good?

Excellent question, and one that I certainly asked myself. With the overhead of the manually execution comes some advantages. Namely, better instantiation and caching. Let's take a look at that quickly.

API Component Instantiation And Caching

Because the CFC request is be routed through our OnCFCRequest() event handler, the target ColdFusion component doesn't get instantiated until we create it manually. And, because we are doing manual instantiation, we get two benefits: we can use an Init() method on our API objects to inject any required dependencies (without breaking encapsulation!!) and we can cache the CFC instance so we don't have to create it again.

In the following example, we will caching the target CFC in our APPLICATION. You'll notice that I'm calling Init() on the Service.cfc. I'v added it, but it doesn't do anything so I won't bother showing the code:

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, 2, 0 ) />
  •  
  •  
  • <cffunction
  • name="onApplicationStart"
  • access="public"
  • returntype="boolean"
  • output="false"
  • hint="I initialize the application.">
  •  
  • <!---
  • Create a cache for our API objects. Each object will
  • be cached at it's name/path such that each remote
  • method call does NOT have to mean a new ColdFusion
  • component instantiation.
  • --->
  • <cfset application.apiCache = {} />
  •  
  • <!--- Return true so page can run. --->
  • <cfreturn true />
  • </cffunction>
  •  
  •  
  • <cffunction
  • name="onRequestStart"
  • access="public"
  • returntype="boolean"
  • output="false"
  • hint="I initialize the request for both CFM and CFC requests.">
  •  
  • <!--- Define arguments. --->
  • <cfargument
  • name="template"
  • type="string"
  • required="true"
  • hint="I am the template requested by the user."
  • />
  •  
  • <!--- Set page request settings. --->
  • <cfsetting showdebugoutput="false" />
  •  
  • <!--- Check for manual application reset. --->
  • <cfif structKeyExists( url, "reset" )>
  • <cfset this.onApplicationStart() />
  • </cfif>
  •  
  • <!--- Return true so page can run. --->
  • <cfreturn true />
  • </cffunction>
  •  
  •  
  • <cffunction
  • name="onRequest"
  • access="public"
  • returntype="void"
  • output="true"
  • hint="I process the user's CFM request..">
  •  
  • <!--- Define arguments. --->
  • <cfargument
  • name="template"
  • type="string"
  • required="true"
  • hint="I am the template requested by the user."
  • />
  •  
  • <!--- Include the requested page. --->
  • <cfinclude template="#arguments.template#" />
  •  
  • <!--- Return out. --->
  • <cfreturn />
  • </cffunction>
  •  
  •  
  • <cffunction
  • name="onCFCRequest"
  • access="public"
  • returntype="void"
  • output="true"
  • hint="I process the user's CFC request.">
  •  
  • <!--- Define arguments. --->
  • <cfargument
  • name="component"
  • type="string"
  • required="true"
  • hint="I am the component requested by the user."
  • />
  •  
  • <cfargument
  • name="methodName"
  • type="string"
  • required="true"
  • hint="I am the method requested by the user."
  • />
  •  
  • <cfargument
  • name="methodArguments"
  • type="struct"
  • required="true"
  • hint="I am the argument collection sent by the user."
  • />
  •  
  • <!---
  • Check to see if the target CFC exists in our cache.
  • If it doesn't then, create it and cached it.
  • --->
  • <cfif !structKeyExists( application.apiCache, arguments.component )>
  •  
  • <!---
  • Create the CFC and cache it via its path in the
  • application cache. This way, it will exist for
  • the life of the application.
  • --->
  • <cfset application.apiCache[ arguments.component ] =
  • createObject(
  • "component",
  • arguments.component
  • ).init()
  • />
  •  
  • </cfif>
  •  
  •  
  • <!---
  • ASSERT: At this point, we know that the target
  • component has been created and cached in the
  • application.
  • --->
  •  
  •  
  • <!--- Get the target component out of the cache. --->
  • <cfset local.cfc = application.apiCache[ arguments.component ] />
  •  
  • <!---
  • Execute the remote method call and store the response
  • (note that if the response is void, it will destroy
  • the return variable).
  • --->
  • <cfinvoke
  • returnvariable="local.result"
  • component="#local.cfc#"
  • method="#arguments.methodName#"
  • argumentcollection="#arguments.methodArguments#"
  • />
  •  
  • <!---
  • Create a default response data variable and mime-type.
  • While all the values returned will be string, the
  • string might represent different data structures.
  • --->
  • <cfset local.responseData = "" />
  • <cfset local.responseMimeType = "text/plain" />
  •  
  • <!---
  • Check to see if the method call above resulted in any
  • return value. If it didn't, then we can just use the
  • default response value and mime type.
  • --->
  • <cfif structKeyExists( local, "result" )>
  •  
  • <!---
  • Check to see what kind of return format we need to
  • use in our transformation. Keep in mind that the
  • URL-based return format takes precedence. As such,
  • we're actually going to PARAM the URL-based format
  • with the default in the function. This will make
  • our logic much easier to follow.
  •  
  • NOTE: This expects the returnFormat to be defined
  • on your CFC - a "best practice" with remote
  • method definitions.
  • --->
  • <cfparam
  • name="url.returnFormat"
  • type="string"
  • default="#getMetaData( local.cfc[ arguments.methodName ] ).returnFormat#"
  • />
  •  
  • <!---
  • Now that we know the URL scope will have the
  • correct format, we can check that exclusively.
  • --->
  • <cfif (url.returnFormat eq "json")>
  •  
  • <!--- Convert the result to json. --->
  • <cfset local.responseData = serializeJSON( local.result ) />
  •  
  • <!--- Set the appropriate mime type. --->
  • <cfset local.responseMimeType = "text/x-json" />
  •  
  • <cfelseif (url.returnFormat eq "wddx")>
  •  
  • <!--- Convert the result to XML. --->
  • <cfwddx
  • action="cfml2wddx"
  • input="#local.result#"
  • output="local.responseData"
  • />
  •  
  • <!--- Set the appropriate mime type. --->
  • <cfset local.responseMimeType = "text/xml" />
  •  
  • <cfelse>
  •  
  • <!--- Convert the result to string. --->
  • <cfset local.responseData = local.result />
  •  
  • <!--- Set the appropriate mime type. --->
  • <cfset local.responseMimeType = "text/plain" />
  •  
  • </cfif>
  •  
  • </cfif>
  •  
  •  
  • <!---
  • Now that we have our response data and mime type
  • variables defined, we can stream the response back
  • to the client.
  • --->
  •  
  • <!--- Convert the response to binary. --->
  • <cfset local.binaryResponse = toBinary(
  • toBase64( local.responseData )
  • ) />
  •  
  • <!---
  • Set the content length (to help the client know how
  • much data is coming back).
  • --->
  • <cfheader
  • name="content-length"
  • value="#arrayLen( local.binaryResponse )#"
  • />
  •  
  • <!--- Stream the content. --->
  • <cfcontent
  • type="#local.responseMimeType#"
  • variable="#local.binaryResponse#"
  • />
  •  
  •  
  • <!--- Return out. --->
  • <cfreturn />
  • </cffunction>
  •  
  • </cfcomponent>

Notice that I've added an OnApplicationStart() event handler so that I can create our remote object cache:

application.apiCache

This struct will take component instances keyed using their path (as defined by the OnCFCRequest() method argument). Then, once inside the OnCFCRequest() event handler, I check to see if the API object already exists in the cache. If it does not, then I instantiate it and cache it. Once that is done, every subsequent remote CFC request to that object can simply pull the existing instance out of the cache.

OnMissingMethod() Functionality

When I first started playing around with this, I thought, Hey, because we are manually instantiating the target CFCs in our remote calls using OnCFCRequest(), we can now take full advantage of ColdFusion's OnMissingMethod() functionality introduced in ColdFusion 8. After all, because we are dealing with a true instance of the target component from within the ColdFusion application framework, requested a method that doesn't exist, should fire the OnMissingMethod() handler (if it exist).

So, I started to wire this all together. First, I went into my Service.cfc and adding an OnMissingMethod() handler that would simply return an array of the result of calling GetGirl() - I was planning to call "GetGirls" in the demo:

Service.cfc

  • <cfcomponent
  • output="false"
  • hint="I am a service with some remote access.">
  •  
  • <cffunction name="Init">
  • <cfreturn this />
  • </cffunction>
  •  
  •  
  • <cffunction
  • name="getGirl"
  • access="remote"
  • returntype="struct"
  • returnformat="json"
  • output="false"
  • hint="I return a girl object.">
  •  
  • <!--- Define arguments. --->
  • <cfargument
  • name="name"
  • type="string"
  • required="true"
  • hint="I am the name of the girl being returned."
  • />
  •  
  • <!--- Define the girl object. --->
  • <cfset local.girl = {
  • name = arguments.name
  • } />
  •  
  • <!--- Return girl object. --->
  • <cfreturn local.girl />
  • </cffunction>
  •  
  •  
  • <cffunction
  • name="onMissingMethod"
  • access="public"
  • returntype="any"
  • returnformat="json"
  • output="false"
  • hint="I handle missing method calls.">
  •  
  • <!--- Define arguments. --->
  • <cfargument
  • name="missingMethodName"
  • type="string"
  • required="true"
  • hint="I am the requested method."
  • />
  •  
  • <cfargument
  • name="missingMethodArguments"
  • type="struct"
  • required="true"
  • hint="I am the method arguments."
  • />
  •  
  • <!---
  • Create an array containing a girl with the given
  • name, which will get from getGirl().
  • --->
  • <cfset local.girls = [
  • this.getGirl(
  • arguments.missingMethodArguments.name
  • )
  • ] />
  •  
  • <!--- Return girls array. --->
  • <cfreturn local.girls />
  • </cffunction>
  •  
  • </cfcomponent>

Notice that it merely wraps the GetGirl() result in an implicit array [].

Then, I went into my OnCFCRequest() method and updated it to work with OnMissingMethod() events. Because we are getting the ReturnFormat from the target method, we had to add that to our URL.ReturnFormat parameterization logic:

OnCFCRequest() With OnMissingMethod Logic

  • <cffunction
  • name="onCFCRequest"
  • access="public"
  • returntype="void"
  • output="true"
  • hint="I process the user's CFC request.">
  •  
  • <!--- Define arguments. --->
  • <cfargument
  • name="component"
  • type="string"
  • required="true"
  • hint="I am the component requested by the user."
  • />
  •  
  • <cfargument
  • name="methodName"
  • type="string"
  • required="true"
  • hint="I am the method requested by the user."
  • />
  •  
  • <cfargument
  • name="methodArguments"
  • type="struct"
  • required="true"
  • hint="I am the argument collection sent by the user."
  • />
  •  
  • <!---
  • Check to see if the target CFC exists in our cache.
  • If it doesn't then, create it and cached it.
  • --->
  • <cfif !structKeyExists( application.apiCache, arguments.component )>
  •  
  • <!---
  • Create the CFC and cache it via its path in the
  • application cache. This way, it will exist for
  • the life of the application.
  • --->
  • <cfset application.apiCache[ arguments.component ] =
  • createObject(
  • "component",
  • arguments.component
  • ).init()
  • />
  •  
  • </cfif>
  •  
  •  
  • <!---
  • ASSERT: At this point, we know that the target
  • component has been created and cached in the
  • application.
  • --->
  •  
  •  
  • <!--- Get the target component out of the cache. --->
  • <cfset local.cfc = application.apiCache[ arguments.component ] />
  •  
  • <!---
  • Execute the remote method call and store the response
  • (note that if the response is void, it will destroy
  • the return variable).
  • --->
  • <cfinvoke
  • returnvariable="local.result"
  • component="#local.cfc#"
  • method="#arguments.methodName#"
  • argumentcollection="#arguments.methodArguments#"
  • />
  •  
  • <!---
  • Create a default response data variable and mime-type.
  • While all the values returned will be string, the
  • string might represent different data structures.
  • --->
  • <cfset local.responseData = "" />
  • <cfset local.responseMimeType = "text/plain" />
  •  
  • <!---
  • Check to see if the method call above resulted in any
  • return value. If it didn't, then we can just use the
  • default response value and mime type.
  • --->
  • <cfif structKeyExists( local, "result" )>
  •  
  • <!---
  • Check to see what kind of return format we need to
  • use in our transformation. Keep in mind that the
  • URL-based return format takes precedence. As such,
  • we're actually going to PARAM the URL-based format
  • with the default in the function. This will make
  • our logic much easier to follow.
  •  
  • Because we might be hitting onMissingMethod(), we
  • need to check to see if the target method that
  • exists. If not, then, we need to
  •  
  • NOTE: This expects the returnFormat to be defined
  • on your CFC - a "best practice" with remote
  • method definitions.
  • --->
  • <cfif structKeyExists( local.cfc, arguments.methodName )>
  •  
  • <!--- Get target method return format. --->
  • <cfparam
  • name="url.returnFormat"
  • type="string"
  • default="#getMetaData( local.cfc[ arguments.methodName ] ).returnFormat#"
  • />
  •  
  • <cfelseif structKeyExists( local.cfc, "onMissingMethod" )>
  •  
  • <!--- Get on missing method return format. --->
  • <cfparam
  • name="url.returnFormat"
  • type="string"
  • default="#getMetaData( local.cfc[ 'onMissingMethod' ] ).returnFormat#"
  • />
  •  
  • </cfif>
  •  
  • <!---
  • Now that we know the URL scope will have the
  • correct format, we can check that exclusively.
  • --->
  • <cfif (url.returnFormat eq "json")>
  •  
  • <!--- Convert the result to json. --->
  • <cfset local.responseData = serializeJSON( local.result ) />
  •  
  • <!--- Set the appropriate mime type. --->
  • <cfset local.responseMimeType = "text/x-json" />
  •  
  • <cfelseif (url.returnFormat eq "wddx")>
  •  
  • <!--- Convert the result to XML. --->
  • <cfwddx
  • action="cfml2wddx"
  • input="#local.result#"
  • output="local.responseData"
  • />
  •  
  • <!--- Set the appropriate mime type. --->
  • <cfset local.responseMimeType = "text/xml" />
  •  
  • <cfelse>
  •  
  • <!--- Convert the result to string. --->
  • <cfset local.responseData = local.result />
  •  
  • <!--- Set the appropriate mime type. --->
  • <cfset local.responseMimeType = "text/plain" />
  •  
  • </cfif>
  •  
  • </cfif>
  •  
  •  
  • <!---
  • Now that we have our response data and mime type
  • variables defined, we can stream the response back
  • to the client.
  • --->
  •  
  • <!--- Convert the response to binary. --->
  • <cfset local.binaryResponse = toBinary(
  • toBase64( local.responseData )
  • ) />
  •  
  • <!---
  • Set the content length (to help the client know how
  • much data is coming back).
  • --->
  • <cfheader
  • name="content-length"
  • value="#arrayLen( local.binaryResponse )#"
  • />
  •  
  • <!--- Stream the content. --->
  • <cfcontent
  • type="#local.responseMimeType#"
  • variable="#local.binaryResponse#"
  • />
  •  
  •  
  • <!--- Return out. --->
  • <cfreturn />
  • </cffunction>

Once I had this wired up, I got super excited and called the following URL:

Service.cfc?method=getGirls&name=Tricia

Because "getGirls" does not exist, the OnCFCRequest() event handler should have routed the request through the OnMissingMethod() method on the Service.cfc (implicitly) and returned and array. But, instead, what I got was this ColdFusion error:

The method getGirls was not found in component Service.cfc.

WHAT?!?!?! When I saw this, I almost blew my top (I had spend 15 minutes wiring this all up and debugging the parts). It doesn't work? Are you kidding me?

It appears as if ColdFusion is actually checking the target component meta data to see if the API request can be made before it even passes it off to the OnCFCRequest() event handler. This is clearly and blatantly a bug in the Application.cfc framework as it completely defeats the intent of have request-handlers in the Application.cfc! If the target file exists (Service.cfc), the surrounding data (ex. method name parameter) should be completely irrelevant - it should be up to the request handler to deal with that scenario.

Overall, I think the OnCFCRequest() event handler is interesting, but I don't know how useful it actually is. Yes, we can now cache our target API objects, but is the minor instantiation cost of a thin API layer worth the overhead of all the manual instantiation and caching? Maybe it is, maybe it isn't. If they can fix the serious OnMissingMethod() bug, then there might be some more use. For now, however, it seems that the best request-handling update is the fact that the OnRequest() event method will not be triggered for CFC-based requests.


You Might Also Be Interested In:



Reader Comments

Jul 15, 2009 at 11:00 AM // reply »
304 Comments

You posted a bug report for this, right?


Jul 15, 2009 at 11:02 AM // reply »
10,638 Comments

@Raymond,

Yeah, my first one :D


Jul 15, 2009 at 11:04 AM // reply »
304 Comments

It would also be nice if CF exposed the WSDL stuff more. When I hit foo.cfc?wsdl, CF automatically converts the result until WSDL. When a WS hit to foo.cfc, method=goo is run, CF automatically converts the response to WSDL.

So the question is - why don't we have access to this? Why can't we do returnFormat=WSDL? Or SerializeWSDL(cf data)?

If we had those, then we could use them in onCFCRequest.


Jul 15, 2009 at 11:09 AM // reply »
10,638 Comments

@Raymond,

Word up. Of course, by the time we start to have to think about WSDL... and then AMF... we probably will have bitten off more than we can chew. Already, as you can see, there's a lot of overhead just to replicate what inherently works with ColdFusion auto-wiring.

Of course, if you just solve the problem once, it's solved, so that's not a great argument :)


Jul 15, 2009 at 11:12 AM // reply »
304 Comments

Assuming, and I think it is fair to assume, that they fix the missing method thing, then this would be very useful just for Ajax itself.


Jul 15, 2009 at 11:13 AM // reply »
304 Comments

And by ajax itself, I also mean REST in general, ie, if it isn't useful for FlashRemoting, Web Services, it could still be darn useful.


Jul 15, 2009 at 11:18 AM // reply »
10,638 Comments

@Raymond,

True, and considering that RESTful stuff is the only thing I have experience with... life would be sweet :D


Jul 15, 2009 at 2:49 PM // reply »
30 Comments

I agree that this could be useful, but I'm not sure it is worth breaking web services and flex remoting. Has anyone tested this with flex remoting?

Also I have one request. In the future could you please post the bug number so we can easily vote on your issues? I found this bug, #78816, but sometimes it is difficult to find things in the bug tracker. Thanks!


Jul 15, 2009 at 2:51 PM // reply »
7 Comments

I agree Ben, this should be a bug, or at least for me its unexpected behavior. When they first announced this new feature of being able to remotely call methods directly on a CFC via a URL I tried it out and was disappointed onMissingMethod wasn't supported. Hopefully your insistence can push for this in the final release of CF9. ;)


Jul 15, 2009 at 2:54 PM // reply »
10,638 Comments

@Nathan,

Ahh, I didn't realize bugs got numbers... or there was any voting :) I'm new to that. Will do that in the future.

@Javier,

Word up. Finger crossed for final release.


Jul 15, 2009 at 3:10 PM // reply »
30 Comments

@Ben - You are finding some really important bugs and I want to make sure Adobe knows about them! I'm sure the CF team reads your blog, but just in case ;)


Jul 15, 2009 at 3:38 PM // reply »
10,638 Comments

@Nathan,

Rock on my friend - I'm all for getting them fixed.


Jul 29, 2009 at 5:57 AM // reply »
67 Comments

Hi Ben
I agree this is a bit cock-eyed.

One would also think that if CF is going to the effort of checking this sort of thing, it'd also be checking if the method being called is actually specified as remotely accessible in the first place. It *should* block remote calls to non-remote methods.

Using your approach to calling the CFC / method (which I think is the natural way of doing it), one can inadvertently open up all public methods to being remotely called, unless you yourself perform this check.

I don't think the component should be exposed to the onCfcRequest() handler as dotted-notation, it should be exposed as the URL that was requested in the first place, that way encouraging the internal call to it to be done as a remote call still. I think Adobe is second-guess here in an attempt at being helpful, and causing problems rather than solving them.

If a method is expecting to be called remotely, then that's how it should be called. For one thing, if the method was actually called remotely, you'd not need to horse around with your JSON/WDDX conversions, because the method would do it for you (via the returnFormat setting).

Either we're missing something, or Adobe have not thought this through.

--
Adam


Aug 5, 2009 at 10:15 AM // reply »
10,638 Comments

@Adam,

From the emails I have received, Adobe has fixed the method-existence bug for the final release (Adobe has actually fixed all the bugs I've filed so far - rock on Adobe!!). That will make this more useful.

Of course, remember, you only use the OnCFCRequest() method if you *want* to. If not, leave it out and all the CFCs will run as normal.


Nov 19, 2009 at 3:56 PM // reply »
5 Comments

Can something like onCFCRequest() be reproduced (faked) in CF8
I would like to intercept CFC calls from a single point
I want to interact with the objects in the MethodArguments so I can log/audit application activity in a very sprawling legacy application
This makes me really wish we were already using CF9 but I do not have that luxury at this time


Nov 19, 2009 at 3:58 PM // reply »
304 Comments

I believe you could for REST/HTTP CFC requests, but it will break for webservice/flash remoting.


Nov 19, 2009 at 6:22 PM // reply »
10,638 Comments

@Jeff,

Yeah, you can fake it in pre-CF9 applications. You have to write it manually a bit in the onRequestStart() method, pointing onRequest() to onCFCRequest(). If you want to see an example of this, download the code example in this project:

http://www.bennadel.com/blog/1730-Building-Single-Page-Applications-Using-jQuery-And-ColdFusion-With-Ben-Nadel-Video-Presentation-.htm

That's what I do there.


Apr 8, 2010 at 5:17 PM // reply »
13 Comments

I have been trying to get oncfcrequest working with Flex Remote Object.

I found that when you use an mx:Model your arguments get placed in an array.

arguments.args[1] will be what you pass on to the cfc and not arguments.args.

<mx:RemoteObject id="ro_cf" destination="ColdFusion" source="auth" showBusyCursor="true" >
<mx:method name="login" result="loginResult()" fault="Alert.show(event.fault.message)">
</mx:RemoteObject>

<mx:Model id="credsModel" >
<creds>
<USERNAME>{username.text}</USERNAME>
<PASSWORD>{password.text}</PASSWORD>
</creds>
</mx:Model>

Called with
ro_cf.login(credsModel) ;


Apr 9, 2010 at 8:58 AM // reply »
10,638 Comments

@Mike,

That's interesting. I know very little about FLEX and the way it communicates with CFCs. Is this the thing that needs an endpoint or something defined in the configuration? I have always been curious about this because I think FLEX communicates with CFCs in a different way than anything else.

That's odd that it puts the arguments in to an array and stuffs it into the first argument. It sounds like it's not getting wired properly.


Apr 9, 2010 at 10:16 AM // reply »
13 Comments

@Ben - the flex call defines a destination="ColdFusion" This gets resolved in the services-config file into a channel and then to a servlet mapping called flex2gateway. The mapping in the web.xml then goes to the messagebrokerservlet. So the messagebrokerservlet is the code that then makes the call into the cfc engine of CF.

It's at that point the data gets transferred into CF datatypes and we find ourselves in the application.cfc.

I know I used mx:model for large forms so it will be an issue for anyone using it to send in Flex forms etc.

I recommend using charlesproxy.com to debug any issue like this, it makes it pretty clear what is being passed and returned.


Apr 19, 2010 at 10:23 PM // reply »
10,638 Comments

@Mike,

The whole flex2gateway is a mystery to me. I think in the latter half of this year, after my presentations, I'm really going to put some effort into learning some Flex. I feel like it's the one really huge blind spot in my understanding of the ColdFusion application framework.



Post A Comment

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.

Please review the following issues:

Author Name:


Author Email:

Author Website:

Comment:

Supported HTML tags for formatting: <strong>bold</strong>   <em>italic</em>   <code>code</code>







  • Help Wanted - Find Your Next ColdFusion Job
InVision App - Prototyping Made Beautiful With Prototyping Tools Ben Nadel's Company - Epicenter Consulting Recent Blog Comments
Feb 3, 2012 at 10:49 PM
How I Got Node.js Running On A Linux Micro Instance Using Amazon EC2
Wow this was really helpful! Only thing I would add is you need to update your .bash_profile after you edit the secure_path. This is what I did: $ . ~/.bash_profile Otherwise, NPM won't be found. ... read »
Feb 3, 2012 at 10:14 PM
Pushing Base64-Encoded Images Over HTML5 WebSockets With Pusher And ColdFusion
@Ben, Just wanted to let you know that pusher are soon to start limiting sizes on messages. This was the detail that came through in the Feb dispatch: "However, we will soon be limiting the s ... read »
Feb 3, 2012 at 5:05 PM
Regular Expressions Make CSV Parsing In ColdFusion So Much Easier (And Faster)
I tried using your RegEx in my C# program, but it was matching an extra empty-string at the end and so I would end up with an extra field that doesn't exist, so I changed it to this: (^|,)("(?: ... read »
Feb 3, 2012 at 3:47 PM
ColdFusion Supports HTTP Verbs PUT And DELETE (As Well As GET And POST)
Josh Cyr posted this on Twitter just a little bit ago. Thought it was appropriate. http://stackoverflow.com/questions/1619152/how-to-create-rest-urls-without-verbs/1619677#1619677 ... read »
Feb 3, 2012 at 2:28 PM
Changing The Execution Context Of Your Self-Executing Function Blocks In JavaScript
@Michael, You definitely make a good point (and extra points for quoting movies - I love movies). When you use a return() statement to define the object's public API, it does provide a consistent a ... read »
Feb 3, 2012 at 2:04 PM
Changing The Execution Context Of Your Self-Executing Function Blocks In JavaScript
To quote Jurassic Park: "Just because you can doesn't mean you should". I completely, utterly disagree with the thought that this is more readable. Consider the current module pattern: if ... read »
Feb 3, 2012 at 1:10 PM
REST API Design Rulebook By Mark Masse
@Jordan, Yeah, WRML was created by Mark Masse (author of the book). I also found it to be a bit convoluted. I suppose it is intended to allow the Client to be able to programmaticaly respond to cha ... read »
Feb 3, 2012 at 1:08 PM
ColdFusion Supports HTTP Verbs PUT And DELETE (As Well As GET And POST)
@Jason, To be honest, I don't have good answers for that kinds of stuff. And, to the point, that is specifically why I *really* liked the REST API Design Rulebook by Mark Masse - he just cuts throu ... read »