Skip to main content
Ben Nadel at NCDevCon 2016 (Raleigh, NC) with: Masha Edelen
Ben Nadel at NCDevCon 2016 (Raleigh, NC) with: Masha Edelen ( @mashaedelen )

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

By on
Tags:

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 and download photo to the given
	directory. Ping() will use the target file name.
--->
<cfset Ping(
	URL = "http://some-image-domain.com/kittens.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!

Want to use code from this post? Check out the license.

Reader Comments

6 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.

15,640 Comments

@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) :)

2 Comments

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.

15,640 Comments

@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.

I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel