Some of the most powerful and versatile ColdFusion application event handlers are often the most ignored: the onRequest event handlers. Not sure why these so often seem to get ignored, but I can't name for you how many times, and how many apps, I've started working on that weren't using any of these methods in any capacity, and needed to.

Like the onApplication and onSession before it, the onRequest event handlers include methods that fire immediately before (onRequestStart) and after (onRequestEnd) the page request. Also like the scopes of those handlers, the onRequest handlers allow access to the REQUEST scope variables without having to lock access to those variables. Those are pretty standard, and not much to figure out. But the onRequest handlers also include two additional handlers: onRequest, and onCFCRequest. These two are a bit more confusing to understand "why?", so we'll go a little more in depth.

There are many different uses for the onRequest event handlers, but most don't understand (or use) these last two. onRequest has been aroundonRequest and onCFCRequest have one thing in common, the ultimately execute the request being made. If your application doesn't include them, that's OK, your request will still execute. But, if you do use them, you have to remember that ultimately that request won't fire as intended without you codeing that into the method. As an example, here's a base onRequest method, without any additional processing:

view plain print about
1/**
2 * FUNCTION onRequest
3 * This runs after the onRequestStart, and must include the template itself.
4 *     Output must be 'true' to display anything to display.
5 * @access public
6 * @returnType void
7 * @output true
8*/

9function onRequest(required string thePage) {
10    include ARGUMENTS.thePage; // When using this method, you must include the requested page
11}

Notice the include line? Without it you'd get a blank page on request. Now, on the surface this is really...boring. Then again, it can be really useful. Take this example:

view plain print about
1/**
2 * FUNCTION onRequest
3 * This runs after the onRequestStart, and must include the template itself.
4 *     Output must be 'true' to display anything to display.
5 * @access public
6 * @returnType void
7 * @output true
8 */

9function onRequest(required string thePage) {
10    var content = "";
11    savecontent variable = "content" {
12        include ARGUMENTS.thePage;
13    }
14    WriteOutput(Replace(content,"iPhone","Android","all"));
15}

See that? We processed the page content to a variable, then replaced every instance of 'iPhone' with 'Android' in the page content, prior to it's display. OK, now your imagination is starting to kick in.

The onRequest method was first introduced with Application.cfc, but didn't gain a ton of traction in applications. Many ColdFusion developers began embracing Web Services, Flash Remoting, and eventually Ajax, and the onRequest method would break requests being made direct to a CFC within it's heirarchy. The choice was to create and maintain a separate CFC in folders of remote access components, or just skip using the onRequest method all together. Luckily, Adobe introduced the onCFCRequest method, in ColdFusion 9, to address this issue.

onCFCRequest, like onRequest, must include the actual request being made. This can allow you to do things you might not typically do with your remote requests. As an example, I have a legacy application that has a lot of components, each with their own init methods. Those init methods take a single argument, the datasource to be used within the different methods. This is pretty standard, prior to ColdFusion 9, as it's bad practice to reference persistent variables directly within a CFC. You'd pass the reference into the instance, use it, and be done. This is something you can't typically do with a remoting request though, as you only have the opportunity to call a single method. Unless...

Yes, you can use the onCFCRequest method to 'init' your components on remote requests, if they all follow a consistent pattern. Consider the following:

view plain print about
1/**
2 * FUNCTION onCFCRequest
3 * New to CF9, this function runs only at the beginning of a direct CFC request (i.e.: not in normal page process)
4 * @access public
5 * @returnType any
6 * @output false
7 */

8function onCFCRequest(required string cfcname,required string method,required struct args) {
9    var tmpObj = "";
10    /*
11     *    Some of my components require the DSN to be passed into the init method. You can
12     *    set a global datasource in CF9, but this example accomodates a lot of legacy code.
13     *    You could use GetMetaData to test for the existance of the init() method, but
14     *    that would require you to first instantiate the object.
15     */

16    try {
17        tmpObj = CreateObject("component",ARGUMENTS.cfcname).init(DSN=THIS.dataSource);
18    } catch (any excpt) {
19        // if there isn't an init method then it will error, causing this to fire
20        tmpObj = CreateObject("component",ARGUMENTS.cfcname);
21    }
22    /*
23     *    Some methods require structs as arguments, for which we can only pass a JSON
24     *    string in Ajax requests. A quick loop of the arguments we can deserialize any
25     *    JSON strings, or otherwise ignore it.
26     */

27    for(var i in ARGUMENTS.args){
28        if(isJSON(ARGUMENTS.args[i])){
29            ARGUMENTS.args[i] = deserializeJSON(ARGUMENTS.args[i]);
30        }
31    }
32    /*
33     *    I've changed the function's basic return type, to allow us to return complex
34     *    objects (which you can't "output"). Since ColdFusion doesn't currently support
35     *    associative array notation for component method calls (i.e. comp[method](argumentCollection:args)),
36     *    then our only option is the dreaded evaluate() statement, which thankfully is much
37     *    improved over previous server versions.
38     */

39    return evaluate('tmpObj.'&ARGUMENTS.method&'(argumentCollection:ARGUMENTS.args)');
40}

OK, I made some adjustments to the returnType of the method, but I tried to explain the why's in the comments. As I mentioned in a previous post, many of my methods return a ColdFusion struct (success, message, and data). You can't 'output' a struct, like you can a simple object (string, boolean, number) so I changed it up for my usage. Also, most of my remote methods are Ajax calls, and the only way to pass complex objects in an Ajax request is as JSON. For this, I just loop over all of the arguments, test if it's JSON, and deserialize if necessary.

Remember that the process has to be generic enough to handle any of the remote requests that it receives, so you'll see options to cover different scenarios during process. The final piece is the return, where in script we have to use the evaluate method to 'create' our actual request:

Let's Do Real World Now

OK, so you've got some of the basics, but what about a real example? Well, in our last example we began some code to that we could use to track a user. Let's take that a little further:

view plain print about
1/**
2 * FUNCTION onRequestStart
3 * Runs at the beginning of a request, prior to the processing of
4 * the requested template.
5 * @access public
6 * @returnType boolean
7 * @output false
8 */

9function onRequestStart(required string thePage) {
10    REQUEST.message = "";
11    if(!SESSION.isLoggedIn and (structKeyExists(FORM,"action") and FORM.action eq "login")){
12        var login = SERVER.cfc.Security.login(FORM);
13        if(login.success){
14            lock scope="SESSION" type="exclusive" timeout="10" {
15                SESSION.isLoggedIn = true;
16                StructAppend(SESSION.user,{userID = login.data.userID, username = login.data.username},true);
17            }
18        } else {
19            REQUEST.message = login.message;
20        }
21    } else if (SESSION.isLoggedIn and (structKeyExists(FORM,"action") and FORM.action eq "logoff")){
22        lock name="appLock" type="exclusive" timeout="10" {
23            StructDelete(APPLICATION.users,SESSION.user.userID);
24        }
25        lock name="sessionLock" type="exclusive" timeout="10" {
26            onSessionEnd(SESSION,APPLICATION);
27            onSessionStart();
28        }
29    }
30    return true;
31}

You probably wouldn't process a login request in your onRequestStart, but as a quick example it allows us to setup this user struct. Something missing though. You probably want to track the page hit datetime. Then you could maybe do this:

view plain print about
1/**
2 * FUNCTION onRequest
3 * This runs after the onRequestStart, but still prior to the requested
4 * template itself.
5 * @access public
6 * @returnType void
7 * @output true
8 */

9function onRequest(required string thePage) {
10    if(SESSION.user.userId){
11        lock scope="SESSION" type="exclusive" timeout="10" {
12            SESSION.user.lastVisit = Now();
13        }
14    }
15    include ARGUMENTS.thePage; // When using this method, you must include the requested page
16}

The onCFCRequest can stand as is, but then there is the end of the request:

view plain print about
1/**
2 * FUNCTION onRequestEnd
3 * Runs after a requested template has completed it's process
4 * @access public
5 * @returnType void
6 * @output false
7 */

8function onRequestEnd(required string thePage) {
9    // Log the user and the page hit here, through some method
10    lock scope="SESSION" type="exclusive" timeout="10" {
11        APPLICATION.cfc.logger.logHit(ARGUMENTS.thePage,SESSION.user,REQUEST.somePageRequestDetails);
12    }
13}

There really are a lot of options here: changing session timeouts on bot requests, handling application reinit requests, redirecting users coming from mobile clients (or adding a link to the page to let them do so). And, in the end, any REQUEST level vars get cleaned up when they're done. Once you start applying it, there really are a lot of things you can do with the onRequest handlers.

In our next post, we'll start looking at the last application event handlers. How do you use your onRequest handlers? Let us know in the comments.