Current Articles | RSS Feed
Most people who know about mod_python have run across it in situations when they needed to serve a Python-based application via Apache, like Mercurial or ViewVC. Actually, mod_python is more than just a CGI/WSGI alternative and is self described as "an Apache module that embeds a Python interpreter into the server." This means you can use mod_python not only to serve Python-based applications that run faster than traditional CGI, but you can actually use exposed Apache APIs to write full-blown Apache modules using the Python language. mod_python also includes a number of useful tools, like session management for example, that you get access to as well. That being said, let's learn more about mod_python by creating a simple application using most of the features that mod_python delivers.
mod_python is actually a suite of tools. Not only can it emulate a CGI environment and allow you to create Apache modules written in Python, but it also provides tooling for:
As you can see, mod_python provides quite the tool-set to accommodate a very wide range of needs. To showcase these features, we're going to write a very simple application using as many mod_python features as are available. We will then wrap up with using a mod_python authentication handler that uses your Twitter credentials to authenticate yourself for your application.
Apache handles requests in phases and mod_python provides you with handlers that allow you to write a Python function that will be used by Apache to handle a phase. So, if you wanted to have a Python-based authentication implementation for your SCM repository server in order to do fancy things like REST/SOAP/XML-RPC/etc to validate a user's credentials, you could use Python and its PythonAuthenHandler to implement such a thing. To see this in practice, let's get mod_python hooked up to Apache and using a very simple handler to give us the obligatory "Hello World!"
# Load the modulesLoadModule python_module libexec/apache2/mod_python.so<Location /mod_python_article> # Tell Apache that mod_python will handle this Location SetHandler mod_python # Tell mod_python which module to use for handling requests PythonHandler olex.publisher # Fix the Python path to be able to locate our application PythonPath "[r'/home/jwhitlock/tutorials/mod_python_article/src']+sys.path"</Location>
The above Apache configuration snippet creates a uri base (mod_python_article) and tells Apache that mod_python will use the PythonHandler handler, which is used to generate content and deliver it to the client. We also told mod_python which Python module (olex.publisher) would be responsible for handling the request and where to find it using the PythonPath mod_python directive. (Of course, you might need to update your path to be where you extract the sample code to.)
from mod_python import apachedef handler(req): """ This is the controller function that will take a request and writes out the content. """ publish(req, 'Hello from mod_python!', type='text/plain') return apache.OK# handlerdef publish(req, content, type='text/html'): """ Helper function that removes some boilerplate when writing content. """ req.content_type = '%s; charset=UTF8' % type req.write(content, 0)# publish
mod_python handler implementations will all accept a single argument, an Apache request object. If you were to restart/start Apache and visit http://hostname/mod_python_article, you'd see "Hello from mod_python!" on your screen. While this is a very simple example, you should have a good idea of how this will work. When we need to do something via mod_python, all we have to do is write a handler and hook it up in Apache by registering the handler. With the wiring taken care of, let's move on to the next feature: Session Management.
What good would any web-based application be without some session management? Well thankfully, mod_python provides you with not only generic session management system that will work out of the box but it also gives you the necessary APIs to write your own. (We will only be demonstrating the built-in session management at this time.) With session management, we can only prompt you for credentials when you've not already logged in and only show you information about your profile if you've logged in. Pretty standard stuff, but without an example it might not be useful. So, below is an example of session management in our application.
from mod_python import apache, Sessiondef handler(req): """ This is the controller function that will take a request and delegate the request to some Python function. """ # Create/Get the session session = Session.Session(req) if session.is_new(): visit_token = 'for the first time' else: visit_token = 'again' # Save the session session.save() publish(req, 'Hello, %s, from mod_python!' % visit_token, type='text/plain') return apache.OK# handlerdef publish(req, content, type='text/html'): """ Takes the content and writes it to the client. """ req.content_type = '%s; charset=UTF8' % type req.write(content, 0)# publish
With the code above, your first visit should produce "Hello, for the first time, from mod_python!" for the output, while on subsequent visits (like by refreshing the page for now) you should see "Hello, again, from mod_python." What we'll do after we've designed a login page, in the next section, is have people without a session get prompted for a name of their session so that they can then be redirected to their session information page. Now that we have session management scaffolding in place, let's wrap a UI around this and see the end result. To do this, we're going to use mod_python's Python Server Pages.
mod_python's Python Server Pages (PSP) are very similar to Java Server Pages in that you have a mix of business logic and presentation mixed together. Of course you can break this out into a template file for the presentation and a function that populates the template with tokens, but in the end the concept isn't new. For our example, we'll use PSPs to generate a simple login page that is displayed when you do not have a session, or you have a session and haven't named it yet. Here is an example of the PSP that is used for the login page:
<!-- Below is a PSP hack to avoid mod_python using 'text/plain' for the content type. --><% req.content_type = 'text/html' %><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml"><head> <title>Session Information - Login</title> <!-- The stylesheet for this page is omitted here but is available in the download --></head><body> <div id="box"> <h1 class="header">Session Information - Login</h1> <%=error%> <form id="login_form" name="login_form" method="POST" action="/mod_python_article"> <ol> <li> <label for="session_name">Session name:</label> <input id="session_name" name="session_name" type="text" value="<%=session_name%>"/> </li> <li> <button id="create" name="create">Create Session</button> </li> </ol> </form> </div></body></html>
There isn't much in this post other than the hack to fix a bug in mod_python, and you'll also see a few PSP tags where we'll put the content of an error into the page and the content of the session name into the form. (These are denoted by the <%=error%> and <%=session_name%> texts respectively.) A better example is in the PSP template used to display the session information (in the download). Of course, it might make sense to know how to tell mod_python how to find a template and how to populate it with variables, like error and session_name above. Here's an example of how to create a template from a file and feed data to it:
data = {'error': '', 'session_name': '',}template = psp.PSP(req, filename='create_session.tmpl')template.run(data)
At this point, you really have seen an example of all parts of mod_python. Here's a summary of the topics we've covered:
As promised, there is one more nifty piece of mod_python that we want to show you: how to use mod_python to write a custom authentication handler for Apache.
Have you ever wanted to have Apache authenticate you in a way that it didn't support? This is often the case in corporate worlds where people use directory systems like Active Directory and OpenLDAP for user/group/etc. management. Well, while Apache does have LDAP support available, what if you wanted to authenticate to a system that Apache was unaware of, like authenticating to a third-party application? Well, below is an example of how you can use Twitter to authenticate users of your Subversion repository, starting with the Apache configuration and ending with the mod_python handler:
<Location /svn/repos> # Subversion configuration ... # Authentication setup AuthType Basic AuthName "Subversion Repository" # Require a valid user Require valid-user # Make sure to use our authentication AuthBasicAuthoritative off # mod_python setup PythonAuthenHandler olex.twitter_authn PythonPath "sys.path+['/home/jwhitlock/tutorials/mod_python_article/src']"</Location>
from mod_python import apache# python-twitter is required for this example (http://code.google.com/p/python-twitter/)import sys, twitter, urllib2def authenhandler(req): """ Authenticates the user based on their Twitter credentials. """ # As documented in mod_python, before you can successfully call req.user you must call # req.get_basic_auth_pw(). # http://modpython.org/live/current/doc-html/pyapi-mprequest-mem.html#l2h-124 password = req.get_basic_auth_pw() username = req.user api = twitter.Api(username=username, password=password) response = apache.OK # Since there is no API to authenticate a user, other than writing one # let's just call the function that returns the least data try: api.GetDirectMessages() except urllib2.HTTPError, e: # There are many Twitter failure codes that mean more than authentication # failure but for brevity, a failure means failed authentication. response = apache.HTTP_UNAUTHORIZED req.log_error('[authn] Failure to authenticate: %s' % str(e)) except twitter.TwitterError, e: response = apache.HTTP_UNAUTHORIZED except: import traceback exception = sys.exc_info() traceLines = traceback.format_exception(exception[0], exception[1], exception[2]) req.log_error('[authn] Unexpected error authenticating to Twitter') for line in traceLines: req.log_error(' %s' % line) return apache.HTTP_INTERNAL_SERVER_ERROR return response
mod_python is an excellent way for people knowledgeable in the Python programming language to secure Apache-served applications/content and to even write your own web-based applications. Due to time constraints the above code samples are not 100% complete, so we've included a tar file with all of the source code above as well as all of the missing parts complete with documentation on how to run the examples.
Allowed tags: <a> link, <b> bold, <i> italics