(yet another?) attempt at restful method dispatching, and questions

114 views
Skip to first unread message

polaar

unread,
Oct 17, 2005, 7:26:13 PM10/17/05
to cherrypy-users
Hello,

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

Dave Warnock

unread,
Oct 18, 2005, 10:30:47 AM10/18/05
to cherryp...@googlegroups.com
Steven,

As a non expert for who REST semantics are important this
(@restful.expose) sounds like a good idea.

Hopefully one of the experts can respond to your problem with the bodies
of POST and PUT requests when application/x-www-form-urlencoded is used.

Dave
Reply all
Reply to author
Forward
0 new messages