getting what you PUT in python [08/30/2008 17:39:57]
The Problem
You want your python WSGI app to respond to HTTP PUT requests.
You soon discover this is a huge pain in the ass.
Everything you knew about programming back in the GET/POST days flies out the window and you feel like you're relearning everything from scratch.
The Solution
Here's how I got PUT working and lived to tell the tale:
Start with Paste
I'd prefer a solution that worked with plain old CGI+Apache, but the way Apache handles PUT is completely different from the way it handles GET and POST. The article makes it look easy, but I gave up in frustration, at least for now.
In any case, I want to make sure people can deploy my apps with nothing more than python and easy_install.
So... I started with Paste, a tool for gluing various WSGI components together. You can install it with:
% easy_install Paste PasteDeploy PasteScript
The Paste Config File
This file (you can call it anything you want) contains a handful of lines that tell Paste which components to use.
#!/bin/env paster [server:main] paste.server_factory = cherrypaste:server_factory host = myserver port = 8080 [app:main] paste.app_factory = myapp:app_factory myConfigVar = /path/to/something
The Server: CherryPyWSGIServer
My first preference was twisted, but the path to twisted WSGI is fraught with peril (or at least horrible documentation) and it doesn't yet work with paste.
I tried out several other WSGI servers before finding one that supported PUT. (The default http server that comes with Paste is supposedly method-neutral but it simply closed the connection whenever I tried to PUT anything.)
In any case, cherrypy.wsgiserver worked.
% easy_install cherrypy
The Server Factory: cherrypaste
Unfortunately, cherrypy isn't very paste-friendly (though they've at least made an effort).
Luckily, I don't care about cherrypy itself. I just wanted the wsgi server. Writing an adapter for paste was fairly easy:
# call this cherrypaste.py
from cherrypy.wsgiserver import CherryPyWSGIServer
import socket
def server_factory(global_conf, host, port):
# compensate for cherrypy's screwy handling of host names.
# (it insisted on mapping my server name to localhost)
addr = socket.gethostbyname(host)
port = int(port)
def serve(app):
cherry = CherryPyWSGIServer((addr, port), app, server_name=host)
cherry.start()
return serve
Yeah, I know... the one step that isn't in an egg is my own code. :(
You can also download the code from trac.
Note: Apparently there was already another CherryPaste on the paste site. It seems to deal with cherrypy the framework, though, not the wsgi server.
BTW, I just now found this in google, even though I looked through the paste site earlier. Paste has plenty of documentation, but it's not very well organized... kind of like my stuff.... :/
The App Factory
Paste also requires your app to have an entry point. Entry points are a feature provided by setuptools (eggs).
If you don't have an egg yet, you can just do what I did in the paste config above, (use myapp:app_factory).
This calls the function app_factory in myapp.py:
#in myapp.py
def app_factory(globalConfig, **localConfig):
"""
Do whatever you want with the config variables and
then return a WSGI-flavored callable.
"""
return MyWSGIHandler(localConfig['myConfigVar'])
The Actual Code
After all that, the actual implementation is plain old WSGI:
# still in myapp.py
class MyWSGIHandler(object):
def __init__(self, myConfigVar):
pass
def __call__(self, env, start):
meth = env['REQUEST_METHOD']
path = env['PATH_INFO']
# really, call something like:
# self.dispatch(meth, path)
# meanwhile, just print to the paste console...
print "%s %s" % (method, path)
# normal wsgi stuff:
start('200 OK', [('content-type','text/plain')])
yield ''
Conclusion
Getting PUT working is actually pretty easy - once you find the right parts. And (hopefully), this setup will work for any possible HTTP/DAV method - DELETE, MOVE, OPTIONS, etc.
There are probably other ways to get this working, but it was enough of a hassle that I figured I'd leave a trail for the next poor soul to venture down the path to madness that is serving HTTP PUT.
