Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
I am the chief technical officer at InVision App, Inc - a prototyping and collaboration platform for designers, built by designers. I also rock out in JavaScript and ColdFusion 24x7.
Meanwhile on Twitter
Loading latest tweet...
Ben Nadel at the NYC Tech Talk Meetup (Aug. 2010) with:

Learning ColdFusion 8: Ping - User Defined Function (Inspired By Ray Camden)

By Ben Nadel on
Tags: ColdFusion

In his comments posted to my "Set It And Forget It" CFThread tutorial, Ray Camden expressed interest in a ColdFusion user defined function that could be fired for Ping support in a similar, asynchronous manner). I am not one to take suggestions lightly, so I figured I would give this ColdFusion user defined function a shot. But more than just the fun of coding, this UDF gives us the opportunity to explore some new, very exciting features of ColdFusion 8:

  • Implicit struct creation (notice the LOCAL scope)
  • AttributeCollection tag attribute
  • CFThread

That being said, here is the ColdFusion user defined function, Ping():

  • <cffunction
  • name="Ping"
  • access="public"
  • returntype="void"
  • output="false"
  • hint="Pings the given URL in an asynchronous fashion. Will download the result if a file name is given.">
  •  
  • <!--- Define arguments. --->
  • <cfargument
  • name="URL"
  • type="string"
  • required="true"
  • hint="The URL to be called using CFHttp."
  • />
  •  
  • <cfargument
  • name="File"
  • type="string"
  • required="false"
  • default=""
  • hint="File to which the CFHttp binary result will be stored."
  • />
  •  
  •  
  • <!--- Define the local scope. --->
  • <cfset var LOCAL = {} />
  •  
  •  
  • <!---
  • Create the attributes collection that we are
  • going to use when firing the CFHttp tag.
  • --->
  • <cfset LOCAL.Attributes = {} />
  •  
  • <!--- Set the URL. --->
  • <cfset LOCAL.Attributes.URL = ARGUMENTS.URL />
  •  
  • <!---
  • Set the method. In this case, we are going to default
  • to HEAD since we don't care about the response body
  • unless we are goint to download the file.
  • --->
  • <cfset LOCAL.Attributes.Method = "HEAD" />
  •  
  • <!--- Set the user agent. --->
  • <cfset LOCAL.Attributes.UserAgent = "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.4) Gecko/20070515 Firefox/2.0.0.4" />
  •  
  •  
  • <!---
  • Check to see if a file name was given for
  • the binary download.
  • --->
  • <cfif ARGUMENTS.File.Length()>
  •  
  • <!---
  • Set the method to GET since we are going
  • to be downloading the file - we need to
  • have a response body.
  • --->
  • <cfset LOCAL.Attributes.Method = "GET" />
  •  
  • <!--- Flag for the binary get. --->
  • <cfset LOCAL.Attributes.GetAsBinary = "yes" />
  •  
  • <!--- Give the path for the file. --->
  • <cfset LOCAL.Attributes.Path = GetDirectoryFromPath(
  • ARGUMENTS.File
  • ) />
  •  
  • <!--- Give the file name to store. --->
  • <cfset LOCAL.Attributes.File = GetFileFromPath(
  • ARGUMENTS.File
  • ) />
  •  
  •  
  • <!---
  • There is a chance that only a directory was
  • supplied. If this is the case, try to grab the
  • file name from the URL as the target file.
  • --->
  • <cfif (
  • (NOT LOCAL.Attributes.File.Length()) AND
  • (GetFileFromPath( ARGUMENTS.URL ).Length())
  • ) >
  •  
  • <!--- Grab the file name from the URL. --->
  • <cfset LOCAL.Attributes.File = GetFileFromPath(
  • ARGUMENTS.URL
  • ) />
  •  
  • </cfif>
  •  
  • </cfif>
  •  
  •  
  •  
  • <!---
  • When using the CFHttp tag, we are going to need to
  • use a CFTry / CFCatch since we might be storing the
  • result to a file (plus to catch any other error that
  • might arise, like a timeout).
  • --->
  • <cftry>
  •  
  • <!---
  • When executing our CFHttp call, we are going to
  • wrap it up in a CFThread tag to allow us to set
  • it and forget it in an asynchronous manner.
  •  
  • When launching the CFThread tag, we need to pass
  • in any variables that are NOT stored in the
  • VARIABLES scope. In this case, we need to pass
  • in both the attribute collection structures as
  • well as the referer that we want to use in the
  • CFHttp call.
  • --->
  • <cfthread
  • action="run"
  • name="cfhttp_#CreateUUID()#"
  • attributes="#LOCAL.Attributes#"
  • referer="#ARGUMENTS.URL#">
  •  
  • <!---
  • Fire the CFHttp tag using our attributes
  • collection. Remember, when we use the
  • attributes collection, no other attributes
  • can be used.
  • --->
  • <cfhttp
  • attributecollection="#ATTRIBUTES.Attributes#">
  •  
  • <!---
  • Pass in the target as the referer. This
  • will just help make sure no security
  • issues block us.
  • --->
  • <cfhttpparam
  • type="CGI"
  • name="referer"
  • value="#ATTRIBUTES.Referer#"
  • />
  •  
  • </cfhttp>
  •  
  • </cfthread>
  •  
  • <cfcatch>
  • <!--- An error occurred. --->
  • </cfcatch>
  • </cftry>
  •  
  •  
  • <!--- Return out. --->
  • <cfreturn />
  • </cffunction>

This Ping method has two options: if you send in just a URL to ping, it will launch an asynchronous CFHttp request for that URL's header information. This will allow a more efficient ping that will execute the file, but not return a message body (remember, in this asynchronous scenario, we don't care about a return value). You can also send in a file path. In doing so, the Ping UDF will switch over from a HEAD method to a GET method in which the response content is stored as a binary object and saved to the given file (still in an asynchronous manner).

If the file path you supply does not have a file name, then the UDF will attempt to use the file name of the target URL. If neither is available, then I think CFHttp will fail, but since it's being executed in a different thread, we will not see this error; remember, CFThread-encapsulated code cannot output content to the parent page's content buffer in any direct way.

Here are the two different method of invocation:

  • <!---
  • Ping Flickr.com and download photo to the given
  • directory. Ping() will use the target file name.
  • --->
  • <cfset Ping(
  • URL = "http://farm2.static.flickr.com/1129/528629811_4d57a56ed5_b.jpg",
  • File = ExpandPath( "./data/" )
  • ) />
  •  
  •  
  • <!--- Ping URL, but to not save file. --->
  • <cfset Ping(
  • URL = "http://www.fullasagoog.com/googimporter.cfm?id=XXX"
  • ) />

One of the more subtle, tricky aspects of the UDF is giving the CFHttp tag access to the attribute collection we created for it. Since the CFHttp tag is located inside of our CFThread tag, it means that it only has access to the VARIABLES scope; however, since our attribute collection was created in the function's explicit LOCAL scope, CFHttp will not have default access to these values. In order to get that collection (and the referer) to the CFHttp tag, we have to pass these values in as attributes to the CFThread tag. Doing this will make those values available in the ATTRIBUTES scope of the CFThread body.

As a side note, notice how easy the AttributeCollection attribute makes it for us to create dynamic CFHttp tags. Before the introduction of the Coldfusion 8 AttributeCollection attribute, we would have had to have a CFIF / CFELSE statement for the downloading and non-downloading version of the CFHttp tag. Both of those CFHttp tags would have had 90% the same attributes and CFHttpParam tag - a very inefficient way of doing things. By using the AttributeCollection, we can dynamically include tag attributes without having to duplicate any effort.

ColdFusion 8 is going to be a beautiful thing!




Reader Comments

Looks to me to have some first person in there too.

I don't know about listening to you, but I enjoy reading your blog. One thing I have noticed is most programmers don't know how to write, other than code.

Reply to this Comment

@Gary,

Thanks for reading my stuff, I am glad you like it.... I am hoping that since you enjoy my writings than you were implying that I was one of the coders that can write well??? Of course, if you were not, no worries - I am a code-monkey at heart (but I did take both creative writing Fiction and Poetry) :)

Reply to this Comment

Ping uses ICMP to determine whether a "host" is reachable. Your UDF does nothing of the sort. You're convoluting cfhttp with ping. Not cool.

Reply to this Comment

@Daniel,

I am not performing the same kind of Ping action you would from the command line. By "ping" here, I am using the more generic sense of the word - to make contact. Think about it in terms of a conversation - sometimes you might here someone say "Ping me back in a few days once you've read over the documents"... they are not talking about ICMP nonsense - they just mean make contact. That is the way in which I am using it.

Reply to this Comment

Post A Comment

You — Get Out Of My Dreams, Get Into My Comments
Live in the Now
Oops!
Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.