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().
Premise
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:
- Request received.
onRequest fired.
- index.cfm included
- Output captured
onRequestEnd fired.
- Count execution
- Log to file
- Include layout.cfm file.
- CFDocument PDF.
- Rules of normality break down.
onRequestEnd fired (again?).
- Count execution (now 2).
- Log to file.
- Include layout.cfm file.
- CFDocument PDF, doesn’t appear to do anything with this second invocation.
- Thankfully, CFDocument doesn’t flag onRequestEnd to execute again or we’d be stuck in a loop.
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 this.name = 'AFunnyThingHappenOnTheWay...' />
<cffunction name="onRequest" returntype="void">
<cfargument name="thisPage" required="true" type="string" />
<cfsavecontent variable="request.content">
<cfinclude template="#arguments.thisPage#" />
</cfsavecontent>
</cffunction>
<cffunction name="onRequestEnd" returntype="void">
<cfset var msg = '' />
<cfif Not StructKeyExists(request, 'onRequestEndCount')>
<cfset msg = '<hr>' & Chr(10) />
<cfset request.onRequestEndCount = 1 />
<cfelse>
<cfset request.onRequestEndCount++ />
</cfif>
<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" />
</cffunction>
An awesome index.cfm file…
<p>This content is super fantastico!</p>
<p><cfoutput>#Now()#</cfoutput></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>
<html>
<body>
<h1>Layout</h1>
<h2>Content</h2>
<cfoutput>#request.content#</cfoutput>
</body>
</html>
</cfdocument>
Put it all together and what do you get? onRequestEnd being fired twice. Here’s what you’ll probably see in log.html.
<hr>
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.