CF9: onRequestEnd Vs. CFDocument

An odd thing happened to me on the way to the end of my request… I bumped into CFDocument, caught up on old times, but without realising I managed to end up back tracking a few steps, to a place I had already travelled through. Here is my story of a weird behaviour between CFDocument and onRequestEnd().


Our opening scene contains a simple Application.cfc file. It’s using onRequest to capture the output from the requested file and then onRequestEnd to embed it in a layout file. Everything goes to plan and requesting index.cfm throws it’s content into layout-html.cfm. The world’s a happy place and we all move on with our lifes… until CFDocument comes along. It’s decided that we need a PDF version of the page, so we simply switch to another layout file, pretty similar to the last apart from a CFDocument tag wrapping around the whole thing.

The Twist

Everything starts out normal, until you get to CFDocument being executed. That’s where the oddity begins by firing onRequestEnd a second time. Here’s a walk through of what happens:

  1. Request received.
  2. onRequest fired.

    1. index.cfm included
    2. Output captured
  3. onRequestEnd fired.

    1. Count execution
    2. Log to file
    3. Include layout.cfm file.
      1. CFDocument PDF.
      2. Rules of normality break down.
  4. onRequestEnd fired (again?).

    1. Count execution (now 2).
    2. Log to file.
    3. Include layout.cfm file.
      1. CFDocument PDF, doesn’t appear to do anything with this second invocation.
      2. Thankfully, CFDocument doesn’t flag onRequestEnd to execute again or we’d be stuck in a loop.
  5. Request ends.

Strangely enough, you won’t get any errors. The 2nd CFDocument doesn’t appear to do anything and thankfully won’t attempt to run onRequestEnd again, avoiding a loop.

The code

Our Application.cfc file, as mentioned above has been written to capture the output from onRequest and use onRequestEnd to apply it onto a layout file. Some logging code has added to onRequestEnd to highlight what’s going on.

<cfset = 'AFunnyThingHappenOnTheWay...' />

<cffunction name="onRequest" returntype="void">
  <cfargument name="thisPage" required="true" type="string" />
  <cfsavecontent variable="request.content">
    <cfinclude template="#arguments.thisPage#" />

<cffunction name="onRequestEnd" returntype="void">
  <cfset var msg = '' />
  <cfif Not StructKeyExists(request, 'onRequestEndCount')>
    <cfset msg = '<hr>' & Chr(10) />
    <cfset request.onRequestEndCount = 1 />
    <cfset request.onRequestEndCount++ />
  <cfset msg &= 'onRequestEnd() Fired: ' & Now() & '<br>' & Chr(10) />
  <cfset msg &= 'Count in single request = #request.onRequestEndCount#<br>' />
  <cffile action="append" file="#ExpandPath('./log.html')#" output="#msg#" />
  <cfcontent reset="true" /><cfinclude template="layout.cfm" />

An awesome index.cfm file…

<p>This content is super fantastico!</p>

And our layout file using CFDocument.

<cfset msg = 'layout.cfm executed: ' & Now() & '<br>' & Chr(10) />
<cffile action="append" file="#ExpandPath('./log.html')#" output="#msg#" />
<cfdocument format="pdf">
<!doctype html>

Put it all together and what do you get? onRequestEnd being fired twice. Here’s what you’ll probably see in log.html.

onRequestEnd() Fired: {ts '2013-05-22 12:37:09'}<br>
Count in single request = 1<br>
layout.cfm executed: {ts '2013-05-22 12:37:09'}<br>

onRequestEnd() Fired: {ts '2013-05-22 12:37:09'}<br>
Count in single request = 2<br>
layout.cfm executed: {ts '2013-05-22 12:37:09'}<br>

So having CFDocument execute within onRequestEnd will fire onRequestEnd a second time. This doesn’t happen in CF8, haven’t tested CF10 as I haven’t a version handy at the moment. I believe this is probably related to the change in onRequestEnd behaviour in CF9. Where it’ll fire even after a CFLocation. Haven’t stumbled across any bug entries for this so far, but will log one for it if I’m certain no one has beaten me to it.


David Boyer
David Boyer

Full-stack web developer from Cardiff, Wales. With a love for JavaScript, especially from within Node.js.