The magic lies in the page headers that get sent as the first pieces of content to the client browser. Page headers, while invisible to the end user, contain a ton of useful information and processing suggestions that change the way the browser will act. In this case, the page header we need to look at is the "refresh" command. The refresh header tells the client to refresh to a new location (URL) after a given delay (in seconds).
To demonstrate this, let's take a look at the pages involved. First, we need the page that provides a link to the file download:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html> <head> <title>ColdFusion Download Demo</title> </head> <body> <h1> Download Pictures </h1> <p> <a href="./download.cfm">Click Here To Download</a> </p> </body> </html>
This file does nothing else except take the user to the following page where the delayed download takes place. In our example, the file being downloaded is hard coded into the logic, but this is something could easily be passed in the URL as a file ID or some encrypted value:
<!--- Kill extra output. ---> <cfsilent> <!--- Param the URL variable that will flag whether or not we are actually performing the download or whether we are showing the donwload landing page. Since we are requiring this URL value to be of a certain type, we need to wrap it in CFTry / CFCatch tags when CFParaming it (as a bad param will throw a data conversion exception). ---> <cftry> <cfparam name="URL.download" type="boolean" default="false" /> <!--- Catch any data conversion issues (if the URL value is not a boolean, then an exception will be thrown. ---> <cfcatch> <!--- Set to default false. ---> <cfset URL.download = false /> </cfcatch> </cftry> <!--- Check to see if we are downloading the target file. If we are not, then we are going to shortly refresh. ---> <cfif URL.download> <!--- The file has been requested so now we have to present it. You can do this through either a CFLocation if the file is publically available or through CFContet if you need to stream it from a non-web-accessible file. ---> <cflocation url="./red_hot.jpg" addtoken="false" /> <!--- ... OR ... ---> <!--- The following will NOT be executed because of the above CFLocation; it is here to demonstrate an alternate way of getting at the file. CFHeader / CFContent gives you more control of how the browser will handle the target file, but it is not as server-friendly as the CFLocation tag (uses more processing power since ColdFusion is handling the file stream. ---> <cfheader name="content-disposition" value="attachment; filename=red_hot.jpg" /> <!--- Stream the file to the client. ---> <cfcontent type="image/jpeg" file="#ExpandPath( './red_hot.jpg' )#" /> <cfelse> <!--- We have not started the download just yet. We are going to be displaying the landing page and then momentarily take the user to the download action. In order to do so, we are going to send back a refresh command in the page's headers. 2: The number of seconds we are going to delay. url: The url are the page is going to refresh to. ---> <cfheader name="refresh" value="2; url=./download.cfm?download=1" /> </cfif> </cfsilent> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html> <head> <title>ColdFusion Download Demo</title> </head> <body> <h1> Download Pictures </h1> <p> Thank you for your interest in downloading this file. Your download should begin shortly. </p> <!--- In case something is wrong with the header information, provide the user with a manual link to accomplish exactly the same thing that the refresh was going to be doing. ---> <p> If the download does not being in a few seconds, click <a href="./download.cfm?download=1">here</a> </p> </body> </html>
This page will be displayed for 2 seconds before the browser gets taken to the target file (red_hot.jpg). The trick here is the two modes the page is running in. Notice that we are defining the URL value, download. If this is false (which it is by default) then we display the intermediary download page. If this value if true then we either take the user to the target file or prompt them for download.
If the download value is false, meaning we are going to display the intermediary page, we set the refresh command using the ColdFusion CFHeader tag. This will set a header value to be sent to the client browser, in this case a 2 second pause followed by a forwarding to the url, ./download.cfm?download=1. This will take use back to the same page but with a different URL.download value. All header values must be set before any content is flushed to the client (as headers are and can only be the first pieces of data sent to the client).
In this demo, I am using CFLocation to forward the user to the target file. This is the easiest solution and puts much less strain on the ColdFusion server since the actual file transfer is handled by IIS. It does, however, require that the target files be web-accessible since CFLocation can only go to public URLs. CFHeader / CFContent, on the other hand, give you much more control over how the file is handled (inline vs. attachment, suggested file name) and where the files can be located (in a private folder), but they require that ColdFusion handle the file transfer, which can be a drain since it ties of processing power.
Want to use code from this post? Check out the license.