My Real URL Shortening Service

For those of you who didn't get the joke last night, the whole thing was a joke.  While it does do exactly as advertised, it deliberately creates exceptionally long URLs.  In order for the app to actually return a shorter URL than you submit, the original URL must be at least 189 characters long.  I assumed the lunacy of the behaviour and the actual code to implement it would give it away.  Looks like I win the deadpaning contest again.

Here's a real implementation: http://u.barneyb.com/.  Note that the generated URLs aren't actually that short because I'm using a long domain name (13 characters), but if you just run the code on a shorter domain name that problem will go away.  I didn't figure it was worth it to go get a new domain name just for this.

As with the fake app, the full code is below, still organized into the same five files plus an Apache vhost.  It has exactly the same functionality, except that it's now backed with a database for resilience across server restarts.  This makes it actually production-viable for the most part.  Here's the vhost:

<VirtualHost *:80>
    ServerName      u.barneyb.com
    DocumentRoot    /home/www/u
    RewriteEngine   On
    RewriteRule     ^/([a-z0-9]+)$   /forward.cfm?key=$1 [PT]
</VirtualHost>

And here is the CFML code:

Application.cfm:
----------------
<cfapplication name="u" sessionmanagement="false" />
<cfset urlRoot = "http://u.barneyb.com" />
<cfset my.dsn = "urlshrink" />

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

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

geturl.cfm:
-----------
<cfquery datasource="#variables.my.dsn#" name="get">
        select id
        from lookup
        where longUrl = <cfqueryparam cfsqltype="cf_sql_varchar" value="#form.url#" />
</cfquery>
<cfif get.recordCount>
        <cfset key = get.id />
        <cfset newUrl = urlRoot & "/" & key />
<cfelse>
        <cftransaction>
        <cfquery datasource="#variables.my.dsn#" name="">
                insert into lookup
                        (longUrl
                        )
                values
                        (<cfqueryparam cfsqltype="cf_sql_varchar" value="#form.url#" />
                        )
        </cfquery>
    <cfquery datasource="#variables.my.dsn#" name="get">
                select last_insert_id() as id
        </cfquery>
        </cftransaction>
        <cfset key = lCase(formatBaseN(get.id, 36)) />
        <cfset newUrl = urlRoot & "/" & 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 #numberFormat(percentSavings, ".0")#%!
</p>
<h2>Shrink Another?</h2>
<cfinclude template="frm_shrink.cfm" />
</cfoutput>

forward.cfm:
------------
<cfparam name="url.key" default="" />
<cfquery datasource="#variables.my.dsn#" name="get">
        select longUrl
        from lookup
        where id = <cfqueryparam cfsqltype="cf_sql_integer" value="#inputBaseN(url.key, 36)#" />
</cfquery>
<cfif get.recordCount GT 0>
        <cflocation url="#get.longUrl#" 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>

Finally, because it is now database backed, you need a CF DSN named "urlshrink" pointed to a database with this table.  The CREATE TABLE is from MySQL, but the in-code SQL should be cross platform (though I haven't tested it):

CREATE TABLE lookup (
  id int(10) unsigned NOT NULL auto_increment,
  longUrl text,
  PRIMARY KEY  (`id`),
  KEY `k_longUrl` (`longUrl`(255)) -- to speed stable url queries
)

Total code size has grown to 1,993 bytes (92 more than the joke code), almost entirely due to CFQUERY and CFQUERYPARAM.  There are also a lot more lines of code because of the way I write my SQL, which also contributes to increased bytes from leading tab characters.

3 responses to “My Real URL Shortening Service”

  1. Lisa Walker

    I have everything working to create the values and insert them in a database. When I click on the shortened urlshrink url, I get a 404 file not found.
    what am I doing incorrectly on this for the setup.
    I have IIS 6 and have a separate url setup (not yet available to public). I have the index.cfm and index.htm as the default documents.
    I also added the forward.cfm as a default document.

  2. Lisa Walker

    Took the later suggestion and I can make it work that way with some alterations in the code.
    Thanks a bunch.