How many times have you tried to test a scheduled task that generates emails to your site's customers-- say, a nightly message to those customers whose subscriptions will expire soon-- and accidently sent the email off to customers? I've done it too many times to count, so I've had to develop a few practices to help me test my tasks without actually performing any actions I don't want to happen. At the same time, these practices have helped me learn how to protect my tasks from malicious users and log a task's results for later review.
Enabling a "debug mode" in your scheduled task
The first thing I do with a scheduled task is to create a variable to indicate whether we should run the script for real (e.g., send emails to users, write log files, alter/create database records, or perform any other permanent actions) or run it in what I call "debug mode", where no database records are altered and/or all emails are sent to me instead of to users. I set this mode by looking at who's calling the page based on user agent and IP address:
<cfset VARIABLES.debug = IIF(CGI.HTTP_USER_AGENT neq 'CFSCHEDULE' or CGI.REMOTE_ADDR neq '127.0.0.1', '1', '0')>
If the user agent isn't "CFSCHEDULE" or the IP isn't that of the CF server, I know that it's a human being calling the script and I initiate debug mode in the script.
Protecting your task from unauthorized users
If you don't want anyone besides yourself or the CF scheduler to run your tasks, you can password-protect your script with basic authentication (where you get a small, browser-generated popup with a username and password challenge). Just enter the username and password in the scheduled task form in the CF Administrator. But if you can't set up basic authentication, you can still protect your tasks from unauthorized use by requiring a special URL variable to be passed along in the request. It's unlikely that anyone else on the Net would be able to guess it:
<!--- If the correct token isn't passed in the query string --->
<cfif not IsDefined("URL.token") or URL.token neq '[somepass]'>
<!--- Abort the page with a message --->
You are not authorized to call this page.<cfabort>
</cfif>
Controlling whether customers receive emails
Once you have a debug mode in your task, controlling other actions becomes a snap. For instance, at the top of my tasks I define a debug email address, usually my own, to which emails will be sent when the task is in debug mode. If the task is running in normal mode, the email goes to the user instead:
<cfset VARIABLES.debugEmail = 'tom@mollerus.net'> <cfmail to="#IIF(VARIABLES.debug, 'VARIABLES.debugEmail', 'qUser.emailAddress')#" from="[from]" subject="[subject]">...</cfmail>
The same goes for database updates or other actions:
<cfif not VARIABLES.debug>
<!--- Perform your database updates/insertions, or any other permanent tasks --->
<cfelse>
<!--- Else we're in debug mode, so don't make any permanent changes --->
</cfif>
Logging your task
Since scheduled tasks usually run (or perhaps fail) without anyone watching the results in a browser, I find it very helpful to write out what happened to a file. I could use CF's logging functions, but I like to send the file I create to myself in an email each day, which would be a little difficult to do with a huge CF log file. Instead, I just append the results of all of my tasks to one log file, and the last task to run for the night reads the file, sends its contents to me, and deletes it from the hard drive, ready to be refreshed the next night. Note the use of a lock below to make sure that no two tasks are writing to the log file at the same time.
<cfset REQUEST.filepath = GetDirectoryFromPath(GetCurrentTemplatePath())>
<cfif not VARIABLES.debug>
<cflock type="exclusive" scope="server" timeout="30">
<CFFILE ACTION="append" FILE="#REQUEST.filepath#/task-log.txt" OUTPUT="=== #NumberFormat(ArrayLen(aSent))# Customer Notices sent ===
(on #DateFormat(tmpNow,"dddd, m/d/yyyy")# at #TimeFormat(tmpNow,"h:mm tt")#)
" ADDNEWLINE="Yes">
</cflock>
<!--- Run the following in the last task (or just when you want to reset the log file) --->
<cflock type="exclusive" scope="server" timeout="30">
<cffile action="read" file="#REQUEST.filepath#/task-log.txt" variable="VARIABLES.body">
<cfmail subject="Scheduled task summary for #DateFormat(tmpNow,"dddd, m/d/yyyy")#" to="[recipients]" from="[sender]">
#VARIABLES.body#
</cfmail>
<cffile action="delete" file="#REQUEST.filepath#/task-log.txt">
</cflock>
</cfif>

Post a comment