Monday, January 09, 2006

Creating your own mod_python request dispatcher

Creating your own mod_python request dispatcher that maps url's to python request handlers.

Conceptually I have something that looks like this:

+-------------+
| Clients |
| Web Browser |
+-------------+
| [http://somecooldomain.com/coorequest]
| ^
| | [Cool Response]
V |
+--------+ +------------+ +---------------+ +---------+
| Apache | --> | Mod_Python | --> | dispatcher.py | --> | cool.py | --+
| (A) | <-- | | <-- | (B) | | (D) | |
+--------+ +------------+ +---------------+ +---------+ |
| ^ |
| | |
| +------------------------+
V
+-------------+
| urlmap.conf |
| (C) |
+-------------+


A) Apache Configuration:
On my server I have mod_python set up to handle any request that comes in without a file extension. That way I can have those cool REST style URI's. I use the apache "FilesMatch" directive in my httpd.conf file to look for any request that does not contain a '.' before the '?' in the query string:


<FilesMatch "(^[^\.]*$|^[^\?]*[\?]+[^$]+$)">
SetHandler python-program
PythonHandler common.dispatch.dispatcher
PythonDebug On
</FilesMatch>


B) dispatcher.py
The source for dispatcher.py can be found here.

C) Example urlmap.conf


[somecooldomain.com]
/=cooldomain.handlers.home.handler
/signup=cooldomain.handlers.member.signup
/login=cooldomain.handlers.member.login
/logout=cooldomain.handlers.member.logout
/cool=cooldomain.handlers.cool.handler



D) cool.py
This is the handler that generates your content. In my content handlers I usually do things like query the database, process business logic, select a cheetah template and return the result as html.

In summary I use this approch for a couple of reasons, first it allows me to decouple my url's from my python code that way I don't have python code + html sitting around in the same directory. Secondly I get one piece of code that handles every request. (good for sessions and things like that)

Friday, January 06, 2006

urlencoder/decoder for python

EDIT (I so stand corrected.):

I could have just used:
urllib.quote
urllib.unquote

---------------------------------------------------

Every once and a while I run into the need for a urlencoder/decoder function that just accepts a string and returns a string.

The one that comes w/ urllib(2) takes a dictionary and returns a "name=val" formatted string.
My only problem is that it does 2 things at once:
1) url encodes.
2) constructs a query or post string.

So I wrote a quick utility to do just #1:


_keys = [
"$", "&", "+", ",", "/", ":", ";", "=", "?", "@", " ", '"',
"<", ">", "#", "%", "{", "}", "|", "\\", "^", "~", "[", "]", "`"]

_vals = [
'%24', '%26', '%2B', '%2C', '%2F', '%3A', '%3B', '%3D', '%3F',
'%40', '%20', '%22', '%3C', '%3E', '%23', '%25', '%7B', '%7D',
'%7C', '%5C', '%5E', '%7E', '%5B', '%5D', '%60']

def encode(str=""):
""" URL Encodes a string with out side effects
"""
return "".join([_swap(x) for x in str])

def decode(str=""):
""" Takes a URL encoded string and decodes it with out side effects
"""
if not str: return None
for v in _vals:
if v in str: str = str.replace(v, _keys[_vals.index(v)])
return str

def _swap(x):
""" Helper function for encode.
"""
if x in _keys: return _vals[_keys.index(x)]
return x

### units ###
if __name__ == "__main__":
assert("".join(_keys) == decode(encode("".join(_keys))))
assert("".join(_vals) == encode(decode("".join(_vals))))
print "passed all unit tests."