I was playing around with using a decorator for dispatching based on
HTTP methods. The idea is that you would use an alternative decorator
(let's call it @restful.expose) instead of @cherrypy.expose.
What it does: instead of returning a response body from the exposed
method/function, you return a "resource". This is an object
implementing the desired HTTP methods.
It's just a little experiment really, the point being to see if I could
come up with something that
- stays true to HTTP/REST semantics
- stays true to cherrypy (except for returning the resource, everything
works as always)
- is easy to use
So I wanted to ask a few questions. First: does this approach sound
sensible/useful? Second: I have something that kind of works, but I've
run into a problem (There are probably other shortcomings as well, as
I'm not really an expert, don't know much about cherrypy internals, and
am not really sure I fully understand decorators ;-)).
I'll first show the code and then explain the problem:
# restful.py
import cherrypy
import cgi
METHODS = ['GET', 'HEAD', 'POST', 'PUT', 'DELETE']
def expose(func):
def newfunc(*args, **kwargs):
# call function with only querystring params
# as POST params don't identify the resource
newkwargs = cgi.parse_qs(cherrypy.request.queryString)
# the resource is the return value of the original exposed
callable
resource = func(*args, **newkwargs)
# determine allowed methods for the resource
allowed_methods = [m for m in METHODS if
callable(getattr(resource, m, None))]
# derive HEAD from GET if needed
# cp takes care of removing response body I think
if 'GET' in allowed_methods and 'HEAD' not in allowed_methods:
resource.HEAD = resource.GET
allowed_methods.append('HEAD')
# set Allow header containing the allowed methods
cherrypy.response.headerMap['Allow'] = ',
'.join(allowed_methods)
method = cherrypy.request.method
if method not in allowed_methods:
# return status '405 Method Not Allowed' if needed
raise cherrypy.HTTPStatusError(405)
elif method in ['POST', 'PUT']:
# call POST and PUT with request body as argument
return getattr(resource, method)(cherrypy.request.body)
else:
# GET, HEAD and DELETE take no arguments
return getattr(resource, method)()
# expose the new callable for cherrypy
newfunc.exposed = True
return newfunc
The problem is that it doesn't work if the POST (or PUT) body is
application/x-www-form-urlencoded. The body is not available because it
is processed into request.paramMap. (I am still uncertain about what
would be the best way of accessing the request body from the POST and
PUT methods, but that's another question.) There is an option
request.processRequestBody that seems to do what I'm after, but it
looks like that's only useable from a filter, which I don't think is
the right approach here.
I wouldn't really mind using the paramMap in case of urlencoded form
data, but then the problem is that it's already merged with querystring
params (those are not a problem, because request.queryString is still
available), and there seems to be no way to get to the real POST params
without using a filter.
Any suggestions for this? (all other remarks welcome too)
regards,
Steven