Thursday 21 November 2013

<cflocation>

G'day:
This is not the article I started writing this morning.

Zac Spitzer pointed me to a post on CFAussie "cflocation fails in CF10 but fine in CF9" in which someone was wondering why their <cflocation> wasn't working. This is well-trod ground, but let's have a look at how <cflocation> works:

<!--- baseline.cfm --->
<cflocation url="target.cfm" addtoken="false">

<!--- target.cfm --->
Arrived at target @ <cfoutput>#timeFormat(now(), "HH:MM:SS.LLL")#</cfoutput>

When we browse to baseline.cfm, we end up on target.cfm. However let's look at what goes on in the browser. Here's the network traffic:


Note that two requests take place: the first to baseline.cfm responds with an HTTP status code of  302 Moved Temporarily, which the browser duly does, then requesting target.cfm. This is how <cflocation> works: it simply sets the HTTP status code to do a redirect. For this to work, ColdFusion mustn't've yet started to send the response back to the web server (and it down to the browser), because the first thing that gets sent is the HTTP headers, and once they've been transmitted, they cannot be changed.

Consider this example:

<!--- spooled.cfm --->
<cfoutput>#repeatstring("X", URL.size)#</cfoutput>
<cflocation url="target.cfm" addtoken="false">

My ColdFusion server is set to automatically spool content back to the web server once it's 1MB or more, so if I run this code with a size of 1048575 (one byte shy of 1MB), I get the same results as above: a redirect to target.cfm. If we look at the actual response, we see a bunch of headers, but no response body, because 300-series responses don't send a body, just the headers.

If however we set the size to be exactly 1MB, we get 1MB of Xs, and no redirect. Why? Because when CF reaches 1MB of content, it starts spooling it back to the web server, and it needs to send the response headers, so it just sends a "200 OK" as the HTTP status code. And once that's been sent, there's no changing it. So <cflocation>'s attempt to set the 302 status code cannot be fulfilled.

One can see the same thing happening if one has a <cfflush> in one's code. Flush starts sending the response back to the browser, the frist part of which are the headers, so - again - <cflocation> won't work.



Another thing that hadn't occurred to me before is that there's the same issue with exceptions. Consider this code:

<!--- exception.cfm --->
<cfthrow type="SomeException">

When browsing to this, the HTTP status code is "500 OK" (which... isn't right in my opinion: it should be "500 Internal Server Error". Railo gets it right, incidentally. As does OpenBD). But a 500-series status code is what we expect, anyhow.

But how about if CF has already started to spool the response back to the browser?

<!--- exceptionAfterSpooling.cfm --->
<cfoutput>#repeatstring("x", 1048576)#</cfoutput>
<cfthrow type="SomeException">

We still get the error message:

Error Occurred While Processing Request


The error occurred inC:/Apps/Adobe/ColdFusion/10/cfusion/wwwroot/scribble/shared/git/blogExamples/cfml/cflocation/exceptionAfterSpooling.cfm: line 3
1 : <!--- exceptionAfterSpooling.cfm --->
2 : <cfoutput>#repeatstring("x", 1048576)#</cfoutput>
3 : <cfthrow type="SomeException">

But if one looks at the HTTP status... it's 200 OK. This can't be helped, but it's something to remember.

And that's that. I've nothing further to bore you with on this topic. Time to start work.

--
Adam