Simple Web Services With Bottle

Recently I rediscovered Bottle, a Python-based micro framework for web applications. Bottle gives you full control over your URLs, has a simple built-in templating engine and a lot of other commonly used stuff. It takes you five minutes to get started and perhaps one hour with its great documentation to know everything there is to know.

I would still use Django in serious web applications for its richer ecosystem, but when it comes to quick prototyping, Bottle really excels. All you need is a Python interpreter; Bottle itself comes in a single file that you can put next to your own script without installation.

Let's have a look at a simple web service I used a few weeks ago: When updating your Ubuntu system, the update sometimes pauses to ask you something. Since I was too lazy to get up from the sofa to check if Ubuntu needed anything, I wrote a Bottle-based web service that makes the output of top(1) available via HTTP. Then I used my tablet to regularly check system load. If the load dropped to zero, then it was time to answer questions. This may be a weird, indirect indicator, but it worked perfectly.

With Bottle, it was dead simple to implement. Just copy the following code to a file and run it:

import subprocess
from bottle import route, response, run

@route("/")
def top():
    response.content_type = "text/plain"
    return subprocess.check_output(["top", "-bn", "1"])

run(host="localhost", port=8080)

Setting the content type via a global variable looks dangerous from a multi-threading point of view. But actually, Bottle gives you access to request and response via thread-local variables, so it's perfectly safe.

A second interesting thing is the somewhat unusual call to top(1). I'm calling it in its non-interactive batch mode (-b) and tell it to quit after one iteration (-n 1).

Let's add multiple URLs, redirects, query parameter processing, and templates:

import subprocess
from bottle import route, request, response, redirect, template, run

HTML = """\
<html>
<head>
  <meta http-equiv="refresh" content="{{ refresh }}">
</head>
<body>
  <pre>
    {{ data }}
  </pre>
</body>
"""

@route("/")
def index():
    redirect("/top/")

@route("/top/")
def top():
    refresh = request.query.refresh or "3"
    top = subprocess.check_output(["top", "-bn", "1"])
    return template(HTML, data=top, refresh=refresh)

@route("/w/")
def w():
    response.content_type = "text/plain"
    return subprocess.check_output(["w"])

run(host="localhost", port=8080)

We serve three different resources, with one redirecting to another. Our new top(1) now creates an auto-refreshing HTML document based on a template. For this, we read an optional refresh parameter from the URL (ie. "/top/?refresh=5") and render both top(1)'s output and the value of the query parameter in a template (Bottle quotes special characters for safety reasons). I embedded a template in the script, but Bottle can load them from files, too. We also serve a third resource "/w/" that doesn't do auto refresh and uses a different content type.

And that's it. Building web services has never been easier.

social