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 CFUNITED 2010 (Landsdown, VA) with: Sandeep Paliwal and Kiran Sakhare and Rupesh Kumar

Snurl.com (SnipUrl) API ColdFusion Component Wrapper

By Ben Nadel on
Tags: ColdFusion

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.



Reader Comments

@Andy,

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

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.

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

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.

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

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.

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