Overview of CherryPy - A Web Application Server (Part1)

Exclusive offer: get 50% off this eBook here
CherryPy Essentials: Rapid Python Web Application Development

CherryPy Essentials: Rapid Python Web Application Development — Save 50%

Design, develop, test, and deploy your Python web applications easily

$23.99    $12.00
by Sylvain Hellegouarch | September 2009 | Content Management

In this two-part article by Sylvain Hellegouarch, we will see how the project is designed and structured. We will first go through a basic CherryPy example. Then we will go through the CherryPy core, the publishing-object engine, and see how it wraps the HTTP protocol in an object-oriented library. Our next step will be to explore the concept of hooking into the core, the CherryPy library, and the tool mechanism. We will then review how CherryPy handles errors and exceptions and how you can benefit from it.

Vocabulary

In order to avoid misunderstandings, we need to define a few key words that will be used.

Keyword

Definition

Web server

A web server is the interface dealing with the HTTP protocol. Its goal is to transform incoming HTTP requests into entities that are then passed to the application server and also transform information from the application server back into HTTP responses.

Application

An application is a piece of software that takes a unit of information, applies business logic to it, and returns a processed unit of information.

Application server

An application server is the component hosting one or more applications.

Web application server

A web application server is simply the aggregation of a web server and an application server into a single component.

CherryPy is a web application server.

Basic Example

To illustrate the CherryPy library we will go through a very basic web application allowing a user to leave a note on the main page through an HTML form. The notes will be stacked and be rendered in a reverse order of their creation date. We will use a session object to store the name of the author of the note.

CherryPy Essentials: Rapid Python Web Application Development

Each note will have a URI attached to itself, of the form /note/id.

CherryPy Essentials: Rapid Python Web Application Development

Create a blank file named note.py and copy the following source code.

#!/usr/bin/python
# -*- coding: utf-8 -*
# Python standard library imports
import os.path
import time
###############################################################
#The unique module to be imported to use cherrypy
###############################################################
import cherrypy
# CherryPy needs an absolute path when dealing with static data
_curdir = os.path.join(os.getcwd(), os.path.dirname(__file__))
###############################################################
# We will keep our notes into a global list
# Please not that it is hazardous to use a simple list here
# since we will run the application in a multi-threaded environment
# which will not protect the access to this list
# In a more realistic application we would need either to use a
# thread safe object or to manually protect from concurrent access
# to this list
###############################################################
_notes = []
###############################################################
# A few HTML templates
###############################################################
_header = """
<html>
<head>
<title>Random notes</<title>
<link rel="stylesheet" type="text/css" href="/style.css"></link>
</head>
<body>
<div class="container">"""
_footer = """
</div>
</body>
</html>"""
_note_form = """
<div class="form">
<form method="post" action="post" class="form">
<input type="text" value="Your note here..." name="text"
size="60"></input>
<input type="submit" value="Add"></input>
</form>
</div>"""
_author_form = """
<div class="form">
<form method="post" action="set">
<input type="text" name="name"></input>
<input type="submit" value="Switch"></input>
</form>
</div>"""
_note_view = """
<br />
<div>
%s
<div class="info">%s - %s <a href="/note/%d">(%d)</a></div>
</div>"""
###############################################################
# Our only domain object (sometimes referred as to a Model)
###############################################################
class Note(object):
def __init__(self, author, note):
self.id = None
self.author = author
self.note = note
self.timestamp = time.gmtime(time.time())
def __str__(self):
return self.note
###############################################################
# The main entry point of the Note application
###############################################################
class NoteApp:
"""
The base application which will be hosted by CherryPy
"""
# Here we tell CherryPy we will enable the session
# from this level of the tree of published objects
# as well as its sub-levels
_cp_config = { 'tools.sessions.on': True }
def _render_note(self, note):
"""Helper to render a note into HTML"""
return _note_view % (note, note.author,
time.strftime("%a, %d %b %Y %H:%M:%S",
note.timestamp),
note.id, note.id)
@cherrypy.expose
def index(self):
# Retrieve the author stored in the current session
# None if not defined
author = cherrypy.session.get('author', None)
page = [_header]
if author:
page.append("""
<div><span>Hello %s, please leave us a note.
<a href="author">Switch identity</a>.</span></div>"""
%(author,))
page.append(_note_form)
else:
page.append("""<div><a href="author">Set your
identity</a></span></div>""")
notes = _notes[:]
notes.reverse()
for note in notes:
page.append(self._render_note(note))
page.append(_footer)
# Returns to the CherryPy server the page to render
return page
@cherrypy.expose
def note(self, id):
# Retrieve the note attached to the given id
try:
note = _notes[int(id)]
except:
# If the ID was not valid, let's tell the
# client we did not find it
raise cherrypy.NotFound
return [_header, self._render_note(note), _footer]
@cherrypy.expose
def post(self, text):
author = cherrypy.session.get('author', None)
# Here if the author was not in the session
# we redirect the client to the author form
if not author:
raise cherrypy.HTTPRedirect('/author')
note = Note(author, text)
_notes.append(note)
note.id = _notes.index(note)
raise cherrypy.HTTPRedirect('/')
class Author(object):
@cherrypy.expose
def index(self):
return [_header, _author_form, _footer]
@cherrypy.expose
def set(self, name):
cherrypy.session['author'] = name
return [_header, """
Hi %s. You can now leave <a href="/" title="Home">notes</a>.
""" % (name,), _footer]
if __name__ == '__main__':
# Define the global configuration settings of CherryPy
global_conf = {
'global': { 'engine.autoreload.on': False,
'server.socket_host': 'localhost',
'server.socket_port': 8080,
}}
application_conf = {
'/style.css': {
'tools.staticfile.on': True,
'tools.staticfile.filename': os.path.join(_curdir,
'style.css'),
}
}
# Update the global CherryPy configuration
cherrypy.config.update(global_conf)
# Create an instance of the application
note_app = NoteApp()
# attach an instance of the Author class to the main application
note_app.author = Author()
# mount the application on the '/' base path
cherrypy.tree.mount(note_app, '/', config = application_conf)
# Start the CherryPy HTTP server
cherrypy.server.quickstart()
# Start the CherryPy engine
cherrypy.engine.start()

Following is the CSS which should be saved in a file named style.css and stored in the same directory as note.py.

html, body {
background-color: #DEDEDE;
padding: 0px;
marging: 0px;
height: 100%;
}
.container {
border-color: #A1A1A1;
border-style: solid;
border-width: 1px;
background-color: #FFF;
margin: 10px 150px 10px 150px;
height: 100%;
}
a:link {
text-decoration: none;
color: #A1A1A1;
}
a:visited {
text-decoration: none;
color: #A1A1A1;
}
a:hover {
text-decoration: underline;
}
input {
border: 1px solid #A1A1A1;
}
.form {
margin: 5px 5px 5px 5px;
}
.info {
font-size: 70%;
color: #A1A1A1;
}

In the rest of this article we will refer to the application to explain CherryPy's design.

CherryPy Essentials: Rapid Python Web Application Development Design, develop, test, and deploy your Python web applications easily
Published: March 2007
eBook Price: $23.99
Book Price: $39.99
See more
Select your format and quantity:

Built-In HTTP Server

CherryPy comes with its own web (HTTP) server. The goal of this decision was to make CherryPy self-contained and allow users to run a CherryPy application within minutes of getting the library. As the name implies, the web server is the gateway to a CherryPy application through which all HTTP requests and responses have to go. It is therefore up to that layer to handle the low-level TCP sockets used to convey the information between the client and the server.

It is not compulsory to use the built-in server though and CherryPy is quite able to interface itself with other web servers if needed. Throughout this book, however, we will only use the default built-in web server.

To start the web server you have to make the following call:

cherrypy.server.quickstart()

Internal Engine

The CherryPy engine is the layer in charge of the following:

  • Creating and managing Request and Response objects
    • The Request is in charge of retrieving and calling the page handler matching the Request-URI.
    • The Response object constructs and validates the response before handing it back to the underlying server.
  • Controlling, managing, and monitoring the CherryPy process

To start the engine you must issue the following call:

cherrypy.engine.start()

Configuration

CherryPy comes with its own configuration system allowing you to parameterize the HTTP server as well as the behavior of the CherryPy engine when processing a Request-URI.

The settings can be stored either in a text file with syntax close to the INI format or in a pure Python dictionary. Choosing between the two is a matter of taste as both carry the same information.

CherryPy offers two entry points for passing configuration values—globally to the server instance through the cherrypy.config.update() method and per application via the cherrypy.tree.mount() method. In addition there is a third scope where configuration settings can be applied: per path.

To configure the CherryPy server instance itself you will need to use the global section of the settings.

In the note application we have defined the following settings:

global_conf = {
'global': {
'server.socket_host': 'localhost',
'server.socket_port': 8080,
},
}
application_conf = {
'/style.css': {
'tools.staticfile.on': True,
'tools.staticfile.filename': os.path.join(_curdir,
'style.css'),
}
}

This could be represented in a file like this:

[global]
server.socket_host="localhost"
server.socket_port=8080
[/style.css]
tools.staticfile.on=True
tools.staticfile.filename="/full/path/to.style.css

When using a file to store the settings you must use valid Python objects (string, integer, Boolean, etc.).

We define the host and the port on which the server will listen for incoming connections.

Then we indicate to the CherryPy engine that the /style.css file is to be handled by the staticfile tool and also indicate the absolute path of the physical file to be served. We will explain in detail what tools are in the following chapters but for now imagine them as a way to extend CherryPy's internal features and enhance its possibilities.

To notify CherryPy of our global settings we need to make the following call:

  • With a dictionary
    cherrypy.config.update(conf)
  • With a file
    cherrypy.config.update('/path/to/the/config/file')

We also have to pass the configuration values to the mounted applications as follows:

  • With a dictionary
    cherrypy.tree.mount(application_instance, script_name, config=conf)
  • With a file
    cherrypy.tree.mount(application_instance, script_name,
    config='/path/to/config/file')

Although in most cases choosing between a dictionary and a file will be a matter of taste, it may happen in some cases that one is better than the other. For instance, you may be required to pass complex data or objects to one key of the configuration, which cannot be achieved via a text file. On the other hand if the settings are to be amendable by the administrator of the application, using an INI file may facilitate that task.

Remember that if you configure parts of your application such as we do to serve the stylesheet in our Note application, you must make a call to cherrypy.tree.mount().

The last way of configuring your application is by using the _cp_config attribute on your page handler or as a class attribute of the class containing the page handlers, in which case the configuration will prevail for all page handlers.

In the following code sample, we indicate that all the page handlers of the Root class will use gzip compression except the hello page handler.

import cherrypy
class Root:
_cp_config = {'tools.gzip.on': True}
@cherrypy.expose
def index(self):
return "welcome"
@cherrypy.expose
def default(self, *args, **kwargs):
return "oops"
@cherrypy.expose
# this next line is useless because we have set the class
# attribute _cp_config but shows you how to configure a tool
# using its decorator. We will explain more in the next
# chapters.
@cherrypy.tools.gzip()
def echo(self, msg):
return msg
@cherrypy.expose
def hello(self):
return "there"
hello._cp_config = {'tools.gzip.on': False}
if __name__ == '__main__':
cherrypy.quickstart(Root(), '/')

The call to quickstart above is a shortcut for:

cherrypy.tree.mount(Root(), '/')
cherrypy.server.quickstart()
cherrypy.engine.start()

You can use this call anytime you only mount one single application on a CherryPy server.

The last important point is that configuration settings are independent of the prefix on which the application is mounted. Therefore in the above example even though the application could be mounted at /myapp instead of /, the settings would not be different. They would not include the prefix. Therefore consider the configuration settings to be relative to the application but independent of the prefix used to mount the application.

The prefix where the application is mounted is referred to the script_name.

Object Publisher Engine

HTTP servers such as Apache or lighttpd map Request-URIs to paths on the file system making them very efficient at handling websites mainly made of static content such as images.

CherryPy has chosen a completely different approach and uses its own internal lookup algorithm to retrieve the handler referred to by the Request-URI. The decision made with CherryPy 2.0 was that such a handler would be a Python-callable object attached to a tree of published objects. That is the reason why we speak of object publishing as the Request-URI maps to a Python object.

CherryPy defines two important concepts:

  • Published: A Python object is said to be published when it is attached to a tree of objects and the root of this tree is mounted on the CherryPy engine server via a call to cherrypy.tree.mount.

    For instance:

    root = Blog()
    root.admin = Admin()
    cherrypy.tree.mount(root, '/blog')

    In the above example the root object is said to be published. By extension the admin object, which is an attribute of a published object, is also published.

  • Exposed: A published object is said to be exposed when it has an attribute named exposed set to True. An exposed object must be Python callable.

    Being published is not sufficient for an object to be treated as being a potential handler for a URI by CherryPy. A published object must be exposed so that it becomes visible to the CherryPy engine. For instance:

    class Root:
    @cherrypy.expose
    def index(self):
    return self.dosome()
    def dosome(self):
    return "hello there"
    cherrypy.tree.mount(Root(), '/')

    In this example a request to /dosome would return a Not Found error because the method is not exposed even though it belongs to a published object. The reason is that the dosome callable object is not exposed to the internal engine as a potential match for a URI.

You can set the exposed attribute either manually or by using the expose decorator provided by CherryPy.

An exposed object is usually referred to as a page handler by the CherryPy community.

For example, in the Note application the published objects are note_app and author. The root of the tree is note_app and is mounted on the '/' prefix. Therefore CherryPy will use that tree of objects upon receiving a request for any path starting with '/'. Had we used a prefix such as /postit, the Note application would have only been served by CherryPy when getting a request starting with such a prefix.

It is therefore possible to mount several applications via distinct prefixes. CherryPy will call the correct one based on the Request-URI. (As we will explain later in the book, two applications mounted via cherrypy.tree.mount() are unaware of each other. CherryPy makes sure that they don't leak.)

The following table displays the relationship between a Request-URI and the page handler matching the path of the URI as found by CherryPy.

CherryPy Essentials: Rapid Python Web Application Development Design, develop, test, and deploy your Python web applications easily
Published: March 2007
eBook Price: $23.99
Book Price: $39.99
See more
Select your format and quantity:

Request-URI Path

Published Object

Page Handler

/

note_app

index

/author/

note_app.author

index

/author/set

note_app.author

set

/note/1

note_app

note

The index() and default() methods are special page handlers for CherryPy. The former one matches Request-URIs ending with a slash, similarly to the index.html file on the Apache server. The latter one is used by CherryPy when no explicit page handler is found for a Request-URI. Our Note application does not define one but the default page handler is often used to catch irregular URIs.

You can also notice that the /note/1 URI, in fact, matches note(id); this is because CherryPy supports positional parameters. The bottom line is that CherryPy will call the first page handler that has a signature matching the requested URI.

CherryPy treats /note/1 and /note?id=1 the same way as long as it finds a page handler with the following signature: note(id).

The following figure is a global overview of the process followed by an HTTP request when reaching the CherryPy server.

CherryPy Essentials: Rapid Python Web Application Development

>> Continue Reading: Overview of CherryPy - A Web Application Server (Part2)

If you have read this article you may be interested to view :

About the Author :


Sylvain Hellegouarch

Sylvain Hellegouarch is an IT Software Consultant dedicated to the development of free software projects such as CherryPy. Since 2004 he has been coordinating and administrating the community efforts around the project providing support for newcomers and seasoned developers, alike. In 2006 he developed 'bridge' and 'amplee', two Python-based projects centered on XML and the upcoming Atom Publishing Protocol respectively. He has also been deeply involved in The Viberavetions Project, a comprehensive grassroots solution for independent artists and musicians to better connect with consumers, as well as the nuXleus project, a platform designed for faster, more reliable inter and intra application and personal communication. Born in France, Sylvain graduated with a degree in Computer Science from South Brittany University, Vannes, France in 2002. Since then he has been working as an IT consultant for a variety of companies, both small and large. He currently resides in the United Kingdom.

Contact Sylvain Hellegouarch

Books From Packt

Drupal 6 JavaScript and jQuery: RAW
Drupal 6 JavaScript and jQuery: RAW

WordPress Theme Design
WordPress Theme Design

WordPress for Business Bloggers
WordPress for Business Bloggers

WordPress Complete
WordPress Complete

Building Powerful and Robust Websites with Drupal 6
Building Powerful and Robust Websites with Drupal 6

Building Websites with Joomla! 1.5
Building Websites with Joomla! 1.5

Drupal 6 Themes
Drupal 6 Themes

Professional Plone Development
Professional Plone Development

No votes yet

Post new comment

CAPTCHA
This question is for testing whether you are a human visitor and to prevent automated spam submissions.
Q
7
G
A
B
Z
Enter the code without spaces and pay attention to upper/lower case.
Code Download and Errata
Packt Anytime, Anywhere
Register Books
Print Upgrades
eBook Downloads
Video Support
Contact Us
Awards Voting Nominations Previous Winners
Judges Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software
Resources
Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software