My New URL Shortening Service

Joshua, Koen and I were discussing URL shortening services (tinyurl, bit.ly, is.gd, etc.) over lunch this week, and so I decided to write my own (UrlShrink) available here: http://www.barneyb.com/applications/urlshrink/app/

It's ludicriously simple.  I opted for memory storage (think Mailinator) instead of a database for performance reasons.  With the load I anticipate, I think it'll be a deciding factor.  I've also included stable URL generation (which I don't know that any other service does), so if you try to shorten the same URL repeatedly, you'll get the same result every time.  As well as being more user friendly, this further increases performance because it prevents artificial bloat to the keyspace, thereby making searches marginally faster.

The app is all CFML, aside from an Alias and couple RewriteRules to make the URLs pretty.  Here are the rules:

Alias   /applications/urlshrink/app /home/www/urlshrink
RewriteRule ^(/applications/urlshrink/app)/geturl   $1/geturl.cfm   [PT]
RewriteRule ^(/applications/urlshrink/app)/shorturl/([^/]*) $1/forward.cfm?key=$2   [PT]

And here's the CFML (in five files) in case anyone else is interested in running their own service:

Application.cfm:
----------------
<cfapplication name="urlshrink" sessionmanagement="false" />
<cfset urlRoot = "http://www.barneyb.com/applications/urlshrink/app" />
<cfif NOT structKeyExists(application, "forwardLookup")>
        <cfset application.forwardLookup = {} />
        <cfset application.reverseLookup = {} />
</cfif>

index.cfm:
----------
<cfoutput>
<h1>UrlShrink</h1>
</cfoutput>
<cfinclude template="frm_shrink.cfm" />

frm_shrink.cfm:
---------------
<cfoutput>
<p>To make a shorter URL, just paste your long URL here and hit "Go!".
</p>
<form method="post" action="#urlRoot#/geturl">
URL:
<input name="url" size="50" />
<input type="submit" value="Go!" />
</form>
</cfoutput>

geturl.cfm:
-----------
<cfif structKeyExists(application.reverseLookup, form.url)>
        <cfset key = application.reverseLookup[form.url] />
        <cfset newUrl = urlRoot & "/shorturl/" & key />
<cfelse>
        <cfset key = hash(form.url, "SHA-256") />
        <cfset newUrl = urlRoot & "/shorturl/" & key />
        <cfif len(form.url) GT len(newUrl)>
                <cfset key = hash(form.url, "SHA-512") />
                <cfset newUrl = urlRoot & "/shorturl/" & key />
        </cfif>
        <cfset application.forwardLookup[key] = form.url />
        <cfset application.reverseLookup[form.url] = key />
</cfif>
<cfset percentSavings = -1 * (len(newUrl) - len(form.url)) / len(form.url) * 100 />
<cfoutput>
<h1>UrlShrink</h1>
<p>Your shortened URL is:
</p>
<p><a href="#newUrl#" style="white-space:nowrap;">#newUrl#</a>
</p>
<p>You've reduced the length of your URL by an exceptional #numberFormat(percentSavings, ".0")#%!
</p>
<h2>Shrink Another?</h2>
<cfinclude template="frm_shrink.cfm" />
</cfoutput>

forward.cfm:
------------
<cfparam name="url.key" default="" />
<cfif structKeyExists(url, "key") AND structKeyExists(application.forwardLookup, url.key)>
        <cflocation url="#application.forwardLookup[url.key]#" addtoken="false" />
<cfelse>
        <cfoutput>
        <h1>UrlShrink</h1>
        <p>The URL you're visiting isn't recognized.  Perhaps you'd like to shrink your own?
        </p>
        <cfinclude template="frm_shrink.cfm" />
        </cfoutput>
</cfif>

Total size is 1,901 bytes; that's pretty respectable for such a full-featured app.

5 responses to “My New URL Shortening Service”

  1. Daver

    Nice – you saved me some time. I had thought about making one, but hadnt gotten beyond the fact that I wanted my own. :)

    I just listed you on my TinyURL Alternatives list.

  2. Gary Fenton

    Am I doing this wrong? The following long URL of 101 chars was "shortened" to 123 chars.

    Original: http://www.dailymail.co.uk/news/article-1127093/Brown-pips-European-rivals-phone-President-Obama.html

    Barney's: http://www.barneyb.com/applications/urlshrink/app/shorturl/3BBDE5E5539B479F341D60ED74F5E9B655BE06798E689AD87392EE910A65992F

    SnipURL: http://snipurl.com/an586

    Metamark: http://xrl.us/bednsq

    I appreciate Barney's domain and app path adds a lot to the url's length, but I'm not sure it uses a full 64 chars for the identifier? This is a lot of padding. Instead of using 16 base consider using 36 base so your encoded urls become a lot shorter. If it logs the full urls in memory or even a db (so the short url identifier points to a record or struct) then start counting from 100 rather than a trillion trillion (or whatever a 64 char hex value is). That way the short urls will always be considerably shorter, or just a handful of chars long, even if there's a billion of them.

    The advantage of your code is that each URL is almost "private" making it nearly impossible to guess working short urls by randomly typing them in. But private information on the web should be protected by a proper login process rather than security by obscurity which leads me back to the thought that a short url should be short.

  3. Rasheed

    I had the same results. To test the service I tried to shorten http://www.facebook.com and the end result was: http://www.barneyb.com/applications/urlshrink/app/shorturl/3E723B591BDB95CE8F5C9B7032DC572CA97351D0DA5EFC73459C1FBAF438E43B

    *Slightly* longer than the original URL. I'm guessing it has to do with using the key from the SHA-256/SHA-512 hash.

  4. andy matthews

    Yay! I shortened http://www.cnn.com by -583%!!!

    :)

  5. Sami Hoda

    You guys need to see Andy's code: http://shrinkurl.riaforge.org/