Python and Apache

As part of the improvements we made to the third edition of Apache, the Definitive Guide, we covered the interface between Apache, the major scripting languages, and a database manager.

For some reason, we left out Python--a perfectly good language, with some useful features of its own--so here is what we should have said in the book.

Python is freeware and you can download it from www.python.org. I downloaded version 2.2.2, which seemed to be the latest stable release. There is an executable for Windows and source code for Unices, in the usual way. I took the Unix version and compiled it under FreeBSD.

Once I had downloaded, compiled, and installed Python I discovered I was a complete novice in the language. But if you check out the Beginners Guide on python.org, you will find several adequate tutorials. I learned enough to carry out this exercise in an hour or so.

(You can also download the manuals. In the world of freeware, where no one is paid to do the tiresome work of presenting user-friendly material, you get over 700 files without, as far as I could see, an index. But, hey!)

Next, I needed an interface between Python and MySQL, which I downloaded from zope.org. The only installation problem was the simple matter of telling it not to use the thread-safe library because my version of FreeBSD is flaky on threads.

As usual, the User and Group that Apache will run as are set to be unimportant and powerless. This is so hackers who penetrate Apache's defenses will find themselves unable to do anything interesting on the server.

The next three lines in the code above are obvious enough, and the last invokes our Python script, script.py, when we browse to the URL www.butterthlies.com.

The script assumes we have a database of people with Christian names (SQL column name xname) and surnames (SQL column name sname). The idea is to put up an HTML form where you specify a Christian name. The script then looks it up and prints the full names of everyone that fits.

Line 1 is the "shebang" line that makes the shell load Python and run the rest of the script. Lines 2 through 4 import Python modules, including the new _mysql, which we'll need later.

Line 5 defines one of the two functions. An oddity of Python is that it does not use the squiggly braces that C or Perl employ to mark out code blocks. Python does it by indentation, so you will notice that lines 6 through 12 have moved right one tab. This may have seemed a cute idea when Python was first conceived, but I imagine it might get a bit tiresome. For example:

If you change the ordering of the code you have to change the indentation on every line affected, rather than just moving the end braces.

If a line runs over (and this depends on your editor) you have to end it with a "\" to make the next line read on. You, reading this as HTML, may get the effect twice because your screen may make the lines fold yet again.

If the indentation is out of whack by just one space, the code may fail in some mysterious way.

This function simply prints the HTML for a little form to stdout. Apache sends it to the client, where you see:

Which Christian name would you like to search for?
<search box>
<Go button>
done it all

Line 6 sends the essential HTML header. Without that, you would be looking at a blank screen.

Line 7 sets up the form with the action /cgi-bin/script.py; that is, the server is to execute this same script again.

done it all is printed by line 38 after execution has returned from the function.

Lines 13 through 29 form the second function, and we will come back to them.

Execution starts at lines 31 and 32, which open the database.

Line 33 reads from stdin and puts the result in the variable indata. If we are on the first pass through the script, stdin will be empty, since the client hasn't yet sent us a Christian name. The if at line 34 will fail, and we will get to the function ask_for_name() at line 37.

If we are on the second pass, the if will succeed because indata contains some data, and we will go to the function get_name(), with indata as the argument. Line 14 again prints the HTML content-type line to stdout.

Line 15 prints indata so that we can see that eveything is working properly. If the client wants to find every "John," for example, you should see:

Got: xname=John

It is sensible to give the HTML input field the same name as the corresponding column in the SQL table. We now have to split indata into name and value. If we had a more complicated form, we would first have to split it into name=value pairs.

Line 16 invokes Python's regex module and searches indata to find the character =. (I would have preferred to use the split() function, but it didn't work, and I didn't have the time to find out why. See page 331 of Apache: The Definitive Guide, 3rd Edition for Perl's Regex split(), which handles the same problem.)

search() returns a number b; the position of the = sign (in this case, b=5). The desired Christian name then starts at position b+1, and that is what c is set to in line 17. We print it at line 18 to make sure we have it correctly.

Line 19 sets up the SQL search query. The + signs are Python's concatenation commands, so we end up with a set to:

select xname,sname from people where xname='john'

We print it at line 20 and query the database with it at 21. Line 22 stores the result in the variable r.

We start a perpetual while() loop at line 23, and extract the database returns record by record into a.

If we have a database return to print, a will be full and non-zero in length. Line 25 tests for this and if we have a record, line 26 prints it. a[0][0] is the first field, the Christian name, and a[0][1] the second, the surname. (In Perl and PHP, the returns from a database query are nicely indexed by their field names. It is possible that this interface offers such a feature if you dig hard enough.) If a is empty, we break out of the loop at line 29 and return from the function. Line 38 then shows that everything has finished properly.

Once you have this skeleton working, it should be easy enough to elaborate it to do a useful e-commerce job.

Is Python the language for a first-timer? I'm not sure. It is a solid piece of work, properly organized and developed, and is, happily, without some of the annoying quirks of Perl. On the other hand, its odd indentation syntax is unlike any other language, so you will eventually have to unlearn it. I get the impression it is not much used for CGI scripts, so although it will probably work (as it does above), it may lack some of the bells and whistles other languages offer.

However, as we recommend in the book, if you don't know a language and do know someone who can guide you through the brambles, and their language is Python, then yours might well be too.

Peter Laurie
is the coauthor of Apache: The Definitive Guide, 3nd Edition