For more than 10 years, web programmers have used the Common Gateway Interface (CGI) to connect applications to web servers and the web browsers on the other side. CGI has much to recommend it: It can be used with any programming language, and it's supported nearly universally across web servers and hosting services. Unfortunately, CGI also has serious drawbacks. The interface between the web server and a CGI script is somewhat convoluted. In addition, web servers spawn a separate process for every CGI request, which means poor performance and no persistence across requests.
Over the years, dissatisfied hackers have created alternate ways to bridge this gap between web servers and application code. In recent years, some popular ways of doing this have included Java™ servlets, the Ruby on Rails framework, and the Apache modules mod_perl and mod_python.
These bridges are so numerous that picking one can be difficult, and the problem of overabundance is especially acute in the Python world. Some of the server application bridges are full-fledged application frameworks, with their own templating systems, authentication services, object-relational mappers, and other such features. With so many choices offering so many features to learn, it's not surprising that programmers with no free time stick to what they already know.
This article introduces CherryPy, a simple but very usable web framework for Python. All it does is connect the web server to your Python code with as little fuss as possible. It doesn't make decisions about what other tools to use, so you're free to pick a templating system, database mapper, or other tool on its own terms. I'll show you how to write applications that use CherryPy. The article assumes you have some knowledge of Python and of how HTTP requests and responses work.
Instead of relying on Apache or another web server, CherryPy runs its own small Python-based web server. A traditional web server creates a web space out of a tree of directories disk, but the CherryPy server creates its web space out of a tree of Python objects.
Consider a request for the URL http://localhost:8080/hello/. In a traditional web server, this URL corresponds to the hello/ directory beneath the root of the web space. When you access it with a web browser, the web server reads the hello/ directory's index.html file or invokes that directory's index.cgi script as a CGI and sends you the output.
Instead of serving a web space rooted in a directory on disk, the CherryPy
web server serves a web space rooted at a particular Python object,
cpg.root. Each method and member of that object
is accessible by tacking its name onto the root URL. Therefore, the hello/
URL corresponds in CherryPy to the hello member
of cpg.root.
Serve a request by defining a method
If cpg.root.hello is a method, CherryPy calls
that method, and the output of the method is sent to the web browser. The
following code defines an object that exposes a
hello method:
Listing 1. Defining an object that exposes a
hello method
#!/usr/bin/env python
from cherrypy import cpg
class Application:
@cpg.expose
def hello(self):
return "Hello, world!"
cpg.root = Application()
cpg.server.start()
|
Run this script through Python, and the CherryPy web server will start up.
As long as you keep the script running, you can access
http://localhost:8080/hello/ and be served the string
Hello, world!. (Of course, this assumes you
don't already have a server running on port 8080.) If you visit any other
URL, including the web server root, you'll get a CherryPy error; /hello/
is the only URL this application knows how to serve.
Serve a request by defining an object tree
If cpg.root.hello is an object instead of a
method, when a user accesses /hello/, CherryPy calls the
hello object's
index() method. This code serves the /hello/
URL the same way as the previous example:
Listing 2. CherryPy calls
hello object's index() method
#!/usr/bin/env python
from cherrypy import cpg
class HelloWorld:
@cpg.expose
def index(self):
return "Hello, world!"
class Application:
hello = HelloWorld()
cpg.root = Application()
cpg.server.start()
|
When you visit the /hello/ URL, your request is mapped to the object
cpg.root.hello, and its default method
(index()) is called to handle the request.
What's the point of the @cpg.expose decorator on
the hello and index
methods? It tells CherryPy that it's OK to call that method in response to
a web request.
In a website that serves static files, it's assumed that every file and directory in the web space is intended for public consumption. There are exceptions, though. For instance, Apache won't serve hidden files, such as .htaccess, on UNIX® systems.
When you expose an object tree as a CherryPy URL space, the assumption goes
the other way: Unless you explicitly label a method as exposed, it isn't
served to outside users. Think of the distinction between public and
private class members in many programming languages (and enforced
unofficially by the _method() convention in
Python). A well-designed CherryPy class will probably have only a few
public methods exposed to web clients, but it may have many internal
methods a client shouldn't be allowed to access directly.
I think the decorator is the best way to expose a method to CherryPy, but
you can also expose a method by setting its
exposed member to
True:
Listing 3. Expose a method by setting its
exposed member to True
class HelloWorld:
def index(self):
return "Hello, world!"
index.exposed = True
|
Decorators don't exist in versions of Python prior to version 2.4, so the
exposed trick may be the only way for you to
expose methods to CherryPy.
When a user submits a form or otherwise provides information to a CGI script, the web browser gathers that information and passes it to the script through environment variables. It's the script's responsibility to parse and make sense of this information, even though a set of conventions governs the proper format and use of this information. CherryPy uses these informal conventions to eliminate several steps of this process, providing user input as the arguments to the Python method it decides to call.
Turn a query string into keyword arguments
Consider a URL that has query arguments, like
http://localhost:8080/hello/?what=hello&who=world. The user may have
clicked this URL or submitted it as the result of filling out an HTML
form. A traditional CGI-based web server would pass
what=hello&who=world to the CGI script in
the QUERY_STRING environment variable. The CGI
script would be responsible for fetching that variable and parsing the
string. Python's cgi module does the parsing
for you. But with CherryPy, you don't have to do anything. The CherryPy
web server automatically transforms a URL's query string into a set of
keyword arguments.
Listing 4. CherryPy web server automatically transforms URL query string into set of keyword arguments
class Application:
@cpg.expose
def hello(self, what='Hello', who='world'):
return '%s, %s!' % (what, who)
|
When a user hits the /hello/ URL, CherryPy turns any
what and the who in
the query string into arguments to the hello()
method. The transition from URL to Python method call is totally
transparent to you. It doesn't even matter whether the original HTTP
request came in through the GET or the
POST method.
Turn extra path portions into positional arguments
The other source of user input for CGI scripts is the
PATH_INFO environment variable. It's commonly
used to make a web application's URLs look more like real web pages. For
instance, consider the URL http://localhost:8080/hello/world/. If /hello/
designates a CGI script, then visiting /hello/world/ invokes that script
with the PATH_INFO environment variable set to
/world/.
In a CGI environment, it's the CGI script's responsibility to parse the
extra path information. But as with query string arguments, CherryPy takes
care of the parsing for you based on typical usage conventions. Whereas
query string key-value pairs become keyword arguments in a CherryPy
application, extra path info arguments become positional arguments to an
object's default() method:
Listing 5. Extra path info arguments become positional arguments to an object's
default() method
class Hello:
@cpg.expose
def default(self, who):
return 'Hello, %s!' % who
class Application:
hello = Hello()
cpg.root = Application()
cpg.server.start()
|
The hello/ portion of the /hello/world/ URL gets mapped to
cpg.root.hello, an instance of
Hello. The Hello
object has no method or member object called
world, so the world portion of the URL
is passed in as a positional argument to that object's
default() method.
Read headers from the request object
CherryPy takes care of parsing the URL the user requested and dispatching to a Python method with the appropriate arguments, but an HTTP request is more than just a URL. What about the incoming HTTP headers?
Your CherryPy methods have access to an object called
cgp.request, which contains a lot of
information about the user's HTTP request. The most interesting member of
this object is requestMap, which contains all
the incoming HTTP headers associated with a web request:
Listing 6.
requestMap
contains incoming HTTP headers associated with web request
class Application:
@cpg.expose
def index(self):
items = [x + ': ' + y for x,y in cpg.request.headerMap.items()]
return "<br />".join(items)
|
Run this application and visit http://localhost:8080/, and you'll see a listing of all the HTTP headers your browser sent along with its request.
As with the HTTP request, so with the response. A CherryPy application
method typically returns the body of the response as a string, but
sometimes you need to set additional HTTP headers, do a redirect, or
change the HTTP response code. You can do all of these things with the
cpg.response object made available to each
method.
cpg.response.headerMap is a map of outgoing HTTP
headers, just as cpg.request.requestMap is a
map of incoming headers:
Listing 7.
cpg.response.headerMap is a map of outgoing HTTP headers
#!/usr/bin/env python
from cherrypy import cpg
class Application:
@cpg.expose
def setHeader(self, header, value):
"""Hit the '/setHeader?header=Value&foo=bar' URL to get a
response in which the HTTP header "foo" has a value of
"bar"."""
cpg.response.headerMap[header] = value
return 'Set HTTP response header "%s" to "%s"' % (header, value)
|
The HTTP status code is just an HTTP header called Status, so you can set it to 404, 503, or whatever other status you need:
Listing 8. Set HTTP header status
@cpg.expose
def forbidden(self):
"Hit the '/forbidden' URL to be denied access."
cpg.response.headerMap['Status'] = '503 Forbidden'
return "You don't have permission to access this resource."
|
To do an HTTP redirect, you can manually set the Status and Location
headers, or you can use the redirect method of
CherryPy's httputils helper library, which does
the same thing:
Listing 9. Use
redirect method of CherryPy's httputils helper library
@cpg.expose
def redirect(self):
"Hit the '/redirect' URL to be redirected."
from cherrypy.lib import httptools
httptools.redirect('./destination')
@cpg.expose
def destination(self):
"This is where you end up if you hit the '/redirect' URL."
from cherrypy.lib import httptools
cpg.response.headerMap['Content-Type'] = 'text/plain'
return 'Here is some plain text.'
cpg.root = Application()
cpg.server.start()
|
Keep persistent information in a session
Consider an application in which I can hit the URL /name/set?name=leonardr
to set a bit of data. I can then hit the URL /name/show and be told,
Your previously set name is leonardr. Because
the second request used information from the first, both requests must
have formed part of a single session. The string
leonardr I sent on my first request was stored
on the server somewhere. When I made my second request, I was somehow
identified as the same person who made the first request, and the
information I sent along with the first request was retrieved.
CherryPy hides most of this complexity and makes it easy to set up and use
cookie-based sessions -- an important feature, but not one supported out
of the box by CGI. You can store any Python object in the
cpg.request.sessionMap map, and it will be
there the next time the same user hits one of your pages. The following
example works just like the application just described:
Listing 10. Store any Python object in the
cpg.request.sessionMap map
#!/usr/bin/env python
from cherrypy import cpg
class Application:
@cpg.expose
def set(self, name):
cpg.request.sessionMap['name'] = name
return 'Set name to %s' % name
@cpg.expose
def show(self):
return 'Your previously set name is %s.' % \
cpg.request.sessionMap.get('name', '[none]')
|
Pretty simple so far. However, for this code to work, the CherryPy server needs to be set up to associate a session cookie with each HTTP response. Otherwise, CherryPy will never be able to associate two requests with the same session. You can do this configuration in a CherryPy server configuration file (see Resources), but it's easier to demonstrate it as part of the Python code that starts the CherryPy web server:
Listing 11. Part of Python code that starts CherryPy web server
cpg.root = Application()
cpg.server.start(configMap={'sessionStorageType' : 'ram',
'sessionCookieName' : 'CherryPySessionCookie',
'sessionTimeout' : 60}) #Session expires in an hour
|
CherryPy uses the same concepts as CGI to bind a web server to a web application, but it improves performance and gains persistence across requests by handling all its requests within a single process. Because it binds only to Python code, it doesn't need the often obscure information-passing techniques of CGI. This leads to fewer lines of more understandable code. CherryPy makes an excellent replacement for CGI and a good base on which to build Python web applications.
| Description | Name | Size | Download method |
|---|---|---|---|
| Sample applications | os-CherryPy-sample-code.zip | 3 KB | HTTP |
Information about download methods
Learn
- The format of the CherryPy web server config file and the options you
can put into it are described on this Wiki.
- See "Charming Python: Review of Python IDEs" to learn more about
Python tools.
- "Discover Python, Part 1: Python's built-in numerical types"
is the first in a series written for Java developers.
- Follow developerWorks on
Twitter.
- Watch developerWorks demos ranging
from product installation and setup demos for beginners, to advanced
functionality for experienced developers.
- In the developerWorks Open source
zone, find extensive how-to information, tools, and project
updates to help you develop with open source technologies and use them
with IBM's products.
- Stay current with developerWorks technical events
and webcasts focused on a variety of IBM products and IT industry
topics.
Get products and technologies
- Visit the CherryPy website for downloads and
documentation.
- Visit the Python website for downloads and documentation.



