Skip to main content
Ben Nadel at CFUNITED 2010 (Landsdown, VA) with: Sandeep Paliwal and Kiran Sakhare and Rupesh Kumar and Jayesh Viradiya
Ben Nadel at CFUNITED 2010 (Landsdown, VA) with: Sandeep Paliwal Kiran Sakhare Rupesh Kumar ( @rukumar ) Jayesh Viradiya

Snurl.com (SnipUrl) API ColdFusion Component Wrapper

By on
Tags:

I was thinking of integrating my Snurl.com account (one of the many url shortening services) into KinkyTwits, my ColdFusion and jQuery powered Twitter client. Before I could do that, though, I had to create a ColdFusion component that would wrap around the SnipURL API provided by Snurl.com. The API itself has Create, Update, and Read style methods, but I only included the Create and Read wrappers as I don't see a real need to update short URLs.

Although I did run into some oddities with the UrlDecode() method this morning, the SnipURL.cfc ColdFusion component is rather simple:

<cfcomponent
	output="false"
	hint="I provide SnipURL API functionality.">


	<cffunction
		name="Init"
		access="public"
		returntype="any"
		output="false"
		hint="I return an initialized component.">

		<!--- Define arguments. --->
		<cfargument
			name="Username"
			type="string"
			required="true"
			hint="I am the username for the SnipURL account."
			/>

		<cfargument
			name="APIKey"
			type="string"
			required="true"
			hint="I am the API key gotten from the SnipURL account settings."
			/>

		<!--- Set up instance variables and store arguments. --->
		<cfset VARIABLES.Instance = {
			Username = ARGUMENTS.Username,
			APIKey = ARGUMENTS.APIKey
			} />

		<!--- Return This reference. --->
		<cfreturn THIS />
	</cffunction>


	<cffunction
		name="CreateSnip"
		access="public"
		returntype="struct"
		output="false"
		hint="I create a snip with the given data.">

		<!--- Define arguments. --->
		<cfargument
			name="URL"
			type="string"
			required="true"
			hint="I am the URL that is being shortened."
			/>

		<cfargument
			name="NickName"
			type="string"
			required="false"
			default=""
			hint="I am the nickname that is associated with the short URL (will attempt to be used in the short URL)."
			/>

		<cfargument
			name="Title"
			type="string"
			required="false"
			default=""
			hint="I am the title of the snip used in the snip details."
			/>

		<cfargument
			name="PrivateKey"
			type="string"
			required="false"
			default=""
			hint="I am the private key."
			/>

		<cfargument
			name="Owner"
			type="string"
			required="false"
			default=""
			hint="I am the optional owner (if not by the given account)."
			/>

		<!--- Define the local scope. --->
		<cfset var LOCAL = {} />

		<!---
			Create the parameters collection and set required
			values for API call.
		--->
		<cfset LOCAL.Parameters = {
			SnipUser = VARIABLES.Instance.Username,
			SnipAPI = VARIABLES.Instance.APIKey,
			SnipLink = ARGUMENTS.URL
			} />

		<!--- Check for optional parametesr. --->
		<cfif Len( ARGUMENTS.NickName )>

			<cfset LOCAL.Parameters.SnipNick = ARGUMENTS.NickName />

		</cfif>

		<cfif Len( ARGUMENTS.Title )>

			<cfset LOCAL.Parameters.SnipTitle = ARGUMENTS.Title />

		</cfif>

		<cfif Len( ARGUMENTS.PrivateKey )>

			<cfset LOCAL.Parameters.SnipPK = ARGUMENTS.PrivateKey />

		</cfif>

		<cfif Len( ARGUMENTS.Owner )>

			<cfset LOCAL.Parameters.SnipOwner = ARGUMENTS.Owner />

		</cfif>


		<!--- Submit the raw API request. --->
		<cfset LOCAL.ResponseXML = THIS.SubmitRawSnipRequest(
			"http://snipr.com/site/getsnip",
			LOCAL.Parameters
			) />


		<!--- Create the response structure. --->
		<cfset LOCAL.Response = {
			ID = LOCAL.ResponseXML.XmlRoot.ID.XmlText,
			Title = LOCAL.ResponseXML.XmlRoot.Title.XmlText,
			URL = UrlDecode(
				LOCAL.ResponseXML.XmlRoot.URL.XmlText,
				"utf-8"
				),
			Created = ParseDateTime( LOCAL.ResponseXML.XmlRoot.Created.XmlText )
			} />

		<!--- Return API response. --->
		<cfreturn LOCAL.Response />
	</cffunction>


	<cffunction
		name="GetSnipDetails"
		access="public"
		returntype="struct"
		output="false"
		hint="I get details for the snip at the given ID.">

		<!--- Define arguments. --->
		<cfargument
			name="ID"
			type="string"
			required="true"
			hint="I am the ID of the snip (URL will be stripped automatically if need-be)."
			/>

		<!--- Define the local scope. --->
		<cfset var LOCAL = {} />

		<!--- Strip out any leading URL information to get the ID. --->
		<cfset ARGUMENTS.ID = REReplaceNoCase(
			ARGUMENTS.ID,
			"^http://[^/]+/",
			"",
			"one"
			) />

		<!---
			Create the parameters collection and set required
			values for API call.
		--->
		<cfset LOCAL.Parameters = {
			SnipUser = VARIABLES.Instance.Username,
			SnipAPI = VARIABLES.Instance.APIKey,
			SnipID = ARGUMENTS.ID
			} />

		<!--- Submit the raw API request. --->
		<cfset LOCAL.ResponseXML = THIS.SubmitRawSnipRequest(
			"http://snipr.com/site/getsnipdetails",
			LOCAL.Parameters
			) />


		<!--- Create the response structure. --->
		<cfset LOCAL.Response = {
			ID = LOCAL.ResponseXML.XmlRoot.ID.XmlText,
			Title = LOCAL.ResponseXML.XmlRoot.Title.XmlText,
			URL = UrlDecode( LOCAL.ResponseXML.XmlRoot.URL.XmlText ),
			Clicks = Val( LOCAL.ResponseXML.XmlRoot.Clicks.XmlText ),
			Unique = Val( LOCAL.ResponseXML.XmlRoot.Unique.XmlText ),
			Created = ParseDateTime( LOCAL.ResponseXML.XmlRoot.Created.XmlText ),
			Modified = ParseDateTime( LOCAL.ResponseXML.XmlRoot.Modified.XmlText )
			} />

		<!--- Return API response. --->
		<cfreturn LOCAL.Response />
	</cffunction>


	<cffunction
		name="SubmitRawSnipRequest"
		access="public"
		returntype="xml"
		output="false"
		hint="I submit the raw HTTP POST and return the XML document.">

		<!--- Define arguments. --->
		<cfargument
			name="URL"
			type="string"
			required="true"
			hint="I am the API action URL."
			/>

		<cfargument
			name="Parameters"
			type="struct"
			required="true"
			hint="I am the set of parameters to pass to API."
			/>

		<!--- Define the local scope. --->
		<cfset var LOCAL = {} />

		<!--- Make CFHTTP POST request. --->
		<cfhttp
			method="post"
			url="#ARGUMENTS.URL#"
			useragent="SnipURLAPIBot"
			result="LOCAL.APIResponse">

			<!--- Loop over paramters to post. --->
			<cfloop
				item="LOCAL.Parameter"
				collection="#ARGUMENTS.Parameters#">

				<!--- Send through as form field. --->
				<cfhttpparam
					type="formfield"
					name="#LCase( LOCAL.Parameter )#"
					value="#ARGUMENTS.Parameters[ LOCAL.Parameter ]#"
					encoded="false"
					/>

			</cfloop>

		</cfhttp>


		<!--- Check the response for a successful connection. --->
		<cfif NOT FindNoCase( "200", LOCAL.APIResponse.StatusCode )>

			<!--- Throw error. --->
			<cfthrow
				type="SnipURL.SubmitRawSnipRequest.HTTPFailure"
				message="The API request failed - #LOCAL.APIResponse.StatusCode#"
				detail="The API request failed: #LOCAL.APIResponse.FileContent#"
				/>

		</cfif>


		<!--- Parse the resposne into XML. --->
		<cfset LOCAL.ResponseXML = XmlParse(
			Trim( LOCAL.APIResponse.FileContent )
			) />

		<!--- Return XML document. --->
		<cfreturn LOCAL.ResponseXML />
	</cffunction>

</cfcomponent>

When you call the SnipURL.cfc methods, it submits the POST request and parses the XML response into easy-to-use ColdFusion structures. Here's a small demo that creates a shortened URL and then retrieves the URL details:

<!--- Create the SnipURL API component. --->
<cfset objAPI = CreateObject( "component", "SnipURL" ).Init(
	Username = "XXXXXX",
	APIKey = "XXXXXXXX"
	) />

<!--- Create a snip. --->
<cfset objResponse = objAPI.CreateSnip(
	URL = "http://flickr.com/photos/31062833@N07/3255353692/",
	NickName = "dangy"
	) />

<!--- Output the response. --->
<cfdump
	var="#objResponse#"
	label="CreateSnip() Response"
	/>


<!--- Get the snip details. --->
<cfset objResponse = objAPI.GetSnipDetails(
	ID = "dangy"
	) />

<!--- Output the response. --->
<cfdump
	var="#objResponse#"
	label="GetSnipDetails() Response"
	/>

When we run the above code, we get the following CFDump output:

Snurl.com (SnipURL) API ColdFusion Component Wrapper Response Structure.

Nothing too complicated here, but maybe this will be useful to someone.

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

Reader Comments

15,674 Comments

@Andy,

That is badass. I see it even covers Snurl! Perhaps I will just skip my integration and move to yours directly.

92 Comments

Snurl is the service that I personally use. It's quick, been around for a while, and offers built in tracking for URLs. Those are the three main requirements for me personally.

I hope you like it Ben...it was something I came up with so that I could integrate into the blog software that I'm writing. My original goal was to write a blog post then, when published, the software would create a short URL from a selected service, then submit a tweet about it using the Twitter API.

Adam Tuttle over at Fusiongrokker.com has submitted several changes and additions so it's been seen by more than just my eyes. Please let me know if you've got any suggestions. I'm hoping to let this get some good exposure and your software would definitely help it do that.

22 Comments

Indeed, it's pretty well tested at this point. And I've submitted some changes to make it compatible with CF 6, and 7, as well as Railo. :)

92 Comments

I'll be taking Adam's Railo compatibility changes, adding some additional services like URLZen and Zi.ma, and releasing shrinkURL 0.4 this weekend.

15,674 Comments

@Andy,

I haven't looked at the actual code yet - are you building all the services into one CFC? Or is there like an abstract CFC that the different services extends (like Snul.cfc extends ShrinkUrl.cfc)?

92 Comments

All services in one CFC.

Currently there's a controller method "shrink" which calls a single method for each service, each of which in turn reference a master "callAPI" method.

There's a structure in the CFC's variables scope containing meta data about each of the services. This metadata can be exposed by calling the listServices method.

I've got plans to remove the unique methods for each service, essentially building a method at runtime based on the metadata for that service.

22 Comments

Andy, I'm not sure if I'm misunderstanding your intentions, but wouldn't it be easier just to write a function that uses the configuration from the services structure to compose the API call as needed? You may need to add a configuration point or two (ReturnsXML = true/false, comes to mind), but it doesn't sound too difficult. :)

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