July 22, 2007, at 09:35 AM

ApacheCherryProxy

The full title of this tutorial should be Deploying a CherryPy web application with Apache 1.3.37 using mod_proxy. I wondered if it should just be a tip but because it is really a complement to Chapter 10 of CherryPy Essentials I think it deserves to be a tutorial ;) based on Running CherryPy behind Apache using mod_proxy.

The CherryPy micro-app

Must give full credit to Sylvain Hellegouarch for that one, it is the demo app used in the book.

import cherrypy
def setup_app():
    class Root:
        @cherrypy.expose
        def index(self):
            return "Hello there %s from IP: %s" % (cherrypy.request.base, cherrypy.request.remote.ip)

    cherrypy.config.update({'server.socket_port': 8080,
                            'environment': 'production',
                            'log.screen': False,
                            'show_tracebacks': False,
                            'tools.proxy.on':True})
    cherrypy.tree.mount(Root())

if __name__ == '__main__':
    setup_app()
    cherrypy.server.quickstart()
    cherrypy.engine.start()

I won't go into too much detail on how it works but only focus on what it does.

Basically it displays the base of the request object i.e. the (scheme://host) portion of the requested URL and the ip of the client (remote is an http.Host(ip, port, hostname) object for the client socket).

Why? Because these are two essential piece of information for any web app :P (and it shows a critical aspect of reverse proxying).

Reverse proxy

A reverse proxy appears to the client just like an ordinary web server. No special configuration on the client is necessary. The client makes ordinary requests for content in the name-space of the reverse proxy. The reverse proxy then decides where to send those requests, and returns the content as if it was itself the origin.

What does this mean? On a server running apache (your server), you want to run a CP app amongst others managed directly by apache. What you do is tell apache that request to that CP app should be redirected to the CP server dealing with it. How?

Apache 1.3 config

First you need to have apache compiled with mod_proxy (if you are using OVH check the Tips.RecompilingApacheOVH). Then, where fit, usually in the VirtualHost section.

<VirtualHost *>
        ServerAdmin webmaster@domain.com
        DocumentRoot /home/username/www
        User username
        Group users
        ServerName www.domain.com
        ServerAlias domain.com
        CustomLog logs/domain-access_log combined
        ScriptAlias /cgi-bin/ /home/domain/cgi-bin/

        ProxyRequests Off
        # Alias /static/ /home/userdir/www/static/
        # does not work in 2.0 and later proxy can be ignored with
        # ProxyPass /static !
        # with 1.3.37 must do
        ProxyPass /static http://localhost/~userdir/static/
        ProxyPass / http://localhost:8080/
        ProxyPassReverse / http://localhost:8080/
</VirtualHost>

Note that Apache 2.0 is a major improvement on 1.3 but, if you are like me (not really keen to spend too much time on sys admin work and content yourself with what is pre-installed on your box yet patching it regularly) one can still do a lot with 1.3!

ProxyRequests Off is the default setup and only there to make sure because forward proxy can be tricky to secure.

The important part is the last 2 lines. ProxyPass / http://localhost:8080/ and ProxyPassReverse / http://localhost:8080/ make sure that every request to www.domain.com are redirected to the server listening locally on port 8080 (remember the 'server.socket_port': 8080 in cherrypy.config.update).

I have added a special trick that I am not sure is useful but ... ProxyPass /static http://localhost/~userdir/static/ makes apache deal directly with requests to content in the /static directory. I read that it could be a good idea to relieve the CP server from some of the load, specifically serving static content for which it is not as suited as apache (performance wise).

Graham Dumpleton suggested the Alias trick (Alias /static/ /home/userdir/www/static/) trick which doesn't seem to work despite being a smart and elegant way. It is more than likely related to the order in which modules are loaded ... too tricky to be addressed here and we have a working solution ;)

I am not entirely happy with my it because a request to apache creates another request to apache (instead of a request to CP). It would be better to be able to tell apache to deal with it in the first place which is probably why the ProxyPass ! syntax was introduced in 2.0.

What for?

Now you can restart apache for the configuration change to be taken into account, run the CherryPy micro-app and visit http://www.domain.com which should display ...

Hello there http://www.domain.com from IP: 84.98.2.199

... which is all you need to rock :P

Note that without the 'tools.proxy.on':True in cherrypy.config.update you would get the disappointing but expected Hello there http://localhost:8080 from 127.0.0.1. That's apache calling from the server ... utterly useless.