without an e

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:

Serving HTTP Put from Python/WSGI

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.

Post a comment:
name: (shows up on site)
url: (shows up on site)
mail: (for michal only)
no html allowed yet. sorry: