Creating an url shortener using Python

On twitter, everybody is using an url shortener. Though, there can be several problems using an external service, as for example the fact that there’s no garantee that your shortened urls will always be available. So, what about creating our own URL shortner, using the Python language?

Acknowledgements

There’s many tools to help you creating an url shortener, for example, ur1 using PHP and urly using Python. In this tutorial, you’ll learn how to create your own url shortener from scratch.

This tutorial uses the Python programming language. Python’s basic concepts aren’t explained here, but this tutorial can be easily followed by anyone having at least some experience in another language as such as PHP or Ruby.

Goals

Let see what we need to achieve:

  • Being able to easily redirect to blog posts
  • Being able to choose a pertinent shortened url name
  • A minimalistic administration interface

1. Redirections

Let’s start by redirections. For now, we just have to add the following URLs :

urls = (
    "/p/(\d+)",         "RedirectToPost",
    "/t/(\d+)",         "RedirectToThought",
)

Then, it’s time to create the redirection classes :

class RedirectToPost:
    def GET(self, post_id):
        return web.redirect(POST_REDIRECT_URL % post_id)

class RedirectToThought:
    def GET(self, thought_id):
        return web.redirect(THOUGHT_REDIRECT_URL % thought_id)

As you can see, many variables are defined on the beginning of the file, to allow you to easily adapt the code to your own needs.

Now, let’s work on redirection to other urls by adding an entry in the urls :

urls = (
    "/(.*)",            "RedirectToOthers",
)

Now we have to take the URLs which have been created via the admin interface (We’ll see that point later) and are stocked in a shelve object, which allow a quick key/value correspondance.

class RedirectToOthers:
    def GET(self, short_name):
        storage = shelve.open(SHELVE_FILENAME)
        # shelve does not allow unicode keys
        short_name = str(short_name)
        if storage.has_key(short_name):
            response = web.redirect(storage[short_name])
        else:
            response = FAIL_MESSAGE
        storage.close()
        return response

Definitely nothing hard here. The code redirects to the corresponding url, or display and error if non is found.

2. Administration

If you always have a shell connected to your server, you can fill your shelve file using Python shell. But creating an admin interface is simple and useful, so let’s do it.

To "protect" the admin interface URL, we’ll just make it harder to find. Just use what you think is the more pertinent, in a variable :

urls = (
    ADMIN,              "Admin",
    ADMIN+"/done/(.*)", "AdminDone",
)

The Admin class allow you to display the form and to submit a new shortcut/url correspondence:

class Admin:
    def GET(self):
        admin_form = web.form.Form(
            web.form.Textbox("url",     description="Long URL"),
            web.form.Textbox("shortcut",description="Shortcut"),
        )
        admin_template = web.template.Template("""$def with(form)
        <!DOCTYPE HTML>
        <html lang="en">
          <head>
            <meta charset=utf-8>

            <title>URL shortener administration</title>
          </head>
          <body onload="document.getElementById(’url’).focus()">
            <header><h1>Admin</h1></header>

            <form method="POST" action="/admin">
              $:form.render()
              <input type="submit" value="Shorten this long URL">
            </form>
          </body>
        </html>
        """)
        return admin_template(admin_form())

    def POST(self):
        data = web.input()
        shortcut = str(data.shortcut) or random_shortcut()
        storage = shelve.open(SHELVE_FILENAME)
        if storage.has_key(shortcut) or not data.url:
            response = web.badrequest()
        else:
            storage[shortcut] = data.url
            response = web.seeother(ADMIN+’/done/’+shortcut)
        storage.close()
        return response

Webpy forms and templates are directly used in the code because they’re super concise. If the request is GET we create the form and send it to the template, if the request is a POST, we add the url to the base and redirect user to the confirm page.
The code itself don’t verify a lot of things: It just makes sure that it will not erase an existing url and that a url has been submitted.

Then we just have to display the confirm page with our newly created link and a shortcut to directly tweet the link :

class AdminDone:
    def GET(self, short_name):
        admin_done_template = web.template.Template("""$def with(new_url)
        <!DOCTYPE HTML>
        <html lang="en">
          <head>

            <meta charset=utf-8>
            <title>URL shortener administration</title>
          </head>
          <body>
            <header><h1>Done!</h1></header>

            <p>You created: $new_url</p>
            <p><a href="http://twitter.com/home?status=$new_url"
              title="Tweet it!">Tweet it?</a></p>
          </body>
        </html>

        """)
        return admin_done_template(SERVICE_URL+short_name)

That’s all. This code can easily be enhanced, but right now it does what we needed: It create short urls.

3. Going live

I’m using lighty, which have to be adapted according to your server configuration.

Don’t forget to make code.py executable and modify ADMIN!:

$HTTP["host"] =~ "bgk.me" {
        server.document-root = "/path/"

        fastcgi.server = (
            "/code.py" => (
                "main" => (
                    "socket" => "/path/bgkme.socket",
                    "bin-path" => "/path/code.py",
                    "max-procs" => 1,
                    "bin-environment" => (
                        "REAL_SCRIPT_NAME" => ""
                    ),
                    "check-local" => "disable"
                )
            )
        )

        url.rewrite-once = (
            "^(/.*)$" => "/code.py$1",
        )
}

Get the full code