Create your own tr.im

URL Shorteners can be really useful. Before twitter came along, the main reason for using a site like http://tinyurl.com was to be able to take a long url that wrapped in an email or newsgroup post and shorten it to allow for a better, cleaner view. Sometimes the wrapped URL broke, making it unclickable, too.

With twitter, short urls became even more important. Even those with one or two extra characters (bit.ly vs tr.im or tr.im vs tinyurl.com) lost out in the fray: 140 characters means 2 extra characters is about 1.5% of your message.

The problem is: tr.im went out of business, at least temporarily. With it, went all the shortened URLs. If your web site relied on those URLs, you were out o' luck. Turns out, though, a URL shortener is very, very easy to implement. This tutorial is based on http://wjmp.net/ where my sandbox URL shortener is.

First, the database. I built this in mysql, so change what you need if you aren't using mysql.


--
-- Table structure for table `hits`
--

CREATE TABLE IF NOT EXISTS `hits` (
  `id` bigint(20) NOT NULL auto_increment,
  `time_stamp` timestamp NOT NULL default CURRENT_TIMESTAMP,
  `url_id` bigint(20) NOT NULL,
  `ip` char(15) NOT NULL,
  PRIMARY KEY  (`id`),
  KEY `url_id` (`url_id`)
) ENGINE=MyISAM  DEFAULT CHARSET=latin1 AUTO_INCREMENT=353 ;

-- --------------------------------------------------------

--
-- Table structure for table `urls`
--

CREATE TABLE IF NOT EXISTS `urls` (
  `id` bigint(20) NOT NULL auto_increment,
  `url2go2` varchar(255) NOT NULL,
  `date_created` timestamp NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP,
  PRIMARY KEY  (`id`)
) ENGINE=MyISAM  DEFAULT CHARSET=latin1 AUTO_INCREMENT=95 ;

Okay, if that's set up, you need to put up a domain. The shorter the better, obviously, but any will work to test.

You'll need to configure apache, too (or whatever web server you do use). You can use mod_rewrite, which is what most people use, but in a pinch you can redirect 404 File Not Found errors to index.php and parse the redirected path. I'm going to give you mod_rewrite instructions.

Add this to your apache configuration. I think a .htaccess should work, too, but I haven't tested it.



RewriteEngine on
RewriteBase /
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule .* index.php [L] 

After you restart your server, you'll have 1) the database set up 2) the web server set up. Now, we just need to add the files. These are in PHP, but this is SO simple that it should be easily ported.

 
<?php
// file name: index.php
 
require '../config.inc.php';
$file = __FILE__;
 
$url = mysql_escape_string ( trim ( substr ( $_SERVER['REQUEST_URI'], 1 ) ) );
 
if ( empty ( $url ) ) // show them the insert form
{
	die( show_form() );
}
 
// this is a request to redirect
 
mysql_connect( DB_HOST, DB_USER , DB_PWD ) or logNdie ( mysql_error(), '1.' );
mysql_select_db( DB_DB ) or logNdie ( mysql_error(), '2.' );
 
// awesome trick to make generating the URL strings more awesome that it should be.
$id = base_convert ( $url, 36, 10 );
 
$q =<<<EOF
-- {$file}
select url2go2 from urls where id = '{$id}'
EOF;
 
$r 	= mysql_query ( $q ) or logNdie ( mysql_error(), '3.' );
$n  = mysql_num_rows ( $r );
 
if ( $n )
{
	$rs = mysql_fetch_array ( $r, MYSQL_ASSOC );
	log_hit ( $id );
	header ( "Location: {$rs['url2go2']}" );
} else {
	die( show_form() );
}
 
function log_hit ( $id )
{
	$q = "insert into hits set url_id = '" . mysql_escape_string ( $id ) . "', ip = '" . mysql_escape_string ( $_SERVER['REMOTE_ADDR'] ) . "' ";
	$r 	= mysql_query ( $q ) or logNdie ( mysql_error(), '3.' );
}
 
function show_form ()
{
 
?>
<html>
	<head>
		<title>Shrink Ray your URL</title>
<style>
 
			body	
			{
				text-align: center;
				margin: auto;
			}
 
			#srtit
			{
				font-size: 16px;
				font-weight: bold;
				margin-top: 15px; 
			}
</style>
 
		<script src="jquery.js"></script>
		<script>
		$(document).ready(function()
		{
			$('#srit').click(function ()
			{
				var u = escape($('#u').val());
				$('#shrink_ray').load('add.php?u='+u);
			});
		});
 
		</script>
	</head>
	<body>
<p id="srtit">SHRINK RAY!
<div id="shrink_ray">
<input type="text" size="36" id="u" />
<input type="button" value="shrink ray it!" id="srit">
		</div>
<p id="title">WJMP: each is the first letter on the num pad of your phone. Sorry about the '.net' not much I can do.
<p id="secure">Some of the other url-shorteners are tracking who is clicking them and selling the information to marketers!  The Shrink Ray at WJMP doesn't!
 
	</body>
	</html>
<?php
}
 
<?php
// file name: add.php
 
require '../config.inc.php';
 
mysql_connect( DB_HOST, DB_USER , DB_PWD ) or logNdie ( mysql_error(), '1.' );
mysql_select_db( DB_DB ) or logNdie ( mysql_error(), '2.' );
$u = mysql_escape_string ( trim ( strip_tags ( $_GET['u'] ) ) );
$file = __FILE__;
 
///////////////  Maybe this URL already exists?
$q =<<<EOF
-- {$file}
select id from urls where url2go2 = '{$u}'
EOF;
 
$r = mysql_query ( $q ) or logNdie ( mysql_error(), $q .'select url' );
$n = mysql_num_rows ( $r );
 
if ( $n )
{
	$id = mysql_result ( $r, 0, 'id' );
	die ( show_url ( $id ) );
}
///////////////
 
///////////////
$q =<<<EOF
-- {$file}
insert into urls set url2go2 = '{$u}'
EOF;
 
$r 	= mysql_query ( $q ) or logNdie ( mysql_error(), '3.' );
///////////////
 
if ( $r )
{
	$id = mysql_insert_id ();
	show_url ( $id );
} else {
	print 'ERROR URL NOT CREATED';
}
 
function show_url ( $id )
{
	$url = base_convert ( $id, 10, 36 );
	print <<<EOF
<input type="text" value="http://wjmp.net/$url" onFocus="this.select();"/> &#171; copy that  || <a href="/">another?</a>
EOF;
 
}
 

Copy & Paste both into the proper file name (index.php & add.php) and drop into your document root.

One more file, I store this one a level above docroot:

 
<?php
// file name: config.inc.php
 
define ( DB_HOST, 'localhost' );
define ( DB_USER, '**change**' );
define ( DB_PWD,  '**change**' );
define ( DB_DB,   '**change**' );
define ( LOG, 	  '../log.txt' ); // make writeable (chmod 666 log.txt)
 
function logNdie ( $log_msg, $display_msg='' )
{
	if ( empty ( $display_msg ) )
	{
		$display_msg = $log_msg;
	}
 
	error_log ( $log_msg );
	die ( display_msg ( $display_msg ) );
}
 
function write2log ( $msg )
{
	static $fp;
	if ( !$fp )
	{
		$fp = fopen ( LOG, 'a' );
	}
 
	fputs ( $fp, "$msg\n" );
}
 
function display_msg ( $msg )
{
	$msg = htmlentities ( strip_tags ( $msg ) );
	print <<<EOF
<div id="#msg">
	{$msg}
</div>
 
EOF;
 
}
 

You'll also need to download http://jquery.com/ and rename it to jquery.js in the root directory. Or, you can edit the functionality to avoid using jquery

For now, I'm storing "hits" but haven't written a stats tracking system, or a login system. But, if you have those needs, go for it. I also think implementing at least APC but maybe even memcached would be great; the most popular urls would be fetched from a memory cache making it that much faster.

You can create a short URL using a link or bookmark button this way (at least in Firefox on OpenSUSE):

  1. Go to Bookmarks, Organize Bookmarks, click on Bookmark Toolbar
  2. Right click, choose Add Bookmark
  3. Name it ("Shorten URL" or whatever you want)
  4. Location: javascript:location.href='http://yourdomain.com/add.php?u='+escape(location.href)

Enjoy! Now, you and your web site or company can rest easy knowing that you own your own URLs, long and short.