[Grok-dev] REST thinking
Martijn Faassen
faassen at startifact.com
Tue Aug 7 09:27:58 EDT 2007
Hi there,
I've been doing some more thinking on REST support for Grok. In the
interests of having some discussion, I'll sketch out the current state
of my thinking and experiments here. I intend to have REST integrated
into Grok within the next few weeks, and I'd appreciate feedback/help.
Why REST in Grok?
-----------------
Why does Grok need REST support? My personal motivation: I'm writing a
web service with Grok that primarily has a web interface. In general, I
think Grok (and Zope 3) is well suited towards writing REST-based web
services, but a bit more help is needed.
What is REST?
-------------
Very briefly, Grok exposes objects as resources that have their own
URLs. REST is a way to make those resources respond to only 5 methods:
GET, POST, PUT and DELETE (and a few stragglers like HEAD I'm going to
ignore). The idea is that each of these methods does something useful in
the context of the application. Typically that's CRUD (BREAD) like:
CRUD: Create Read Update Delete
BREAD: Browse Read Edit Add Delete
Browse: GET links to objects from container, or URL representing search
Read: GET representation of object
Edit: PUT representation of object
Add: POST representation of object to container or special factory URL
Delete: DELETE object
REST in Grok
------------
Basically we need to be able to respond directly to HTTP methods for a
resource. Following our XML-RPC and JSON support, we can do this using a
special REST view that looks like this (hypothetical code):
class MyREST(grok.REST):
grok.context(MyContainer)
def GET(self):
return "the information"
def POST(self):
self.context['new_object'] = create(request.bodyStream)
def PUT(self):
...
def DELETE(self):
del self.context.__parent__[self.context.__name__]
Again like with XML-RPC, each method is turned into its own view.
More high-level REST views
--------------------------
In the case of the container in particular, it would be nice if more
high-level REST views were available. In particular, POST needs a
factory that can turn a representation into a new object that is then
placed in the container. DELETE is as sketched out above. With POST a
more complicated choreography is often needed, where a suggestion to be
used for naming can be passed in a request header, and the response
contains the new location of the object that is just created.
HTTP response codes
-------------------
It would be nice if we had a good pattern for giving the right HTTP
response codes. I still need to think about it. Probably we can just
raise the appropriate exceptions, but in high-level views we may be able
to automate some of this.
Working together with browser views
-----------------------------------
The same application should be able to have a normal web-based UI as
well as a REST presentation. How to do this? Since Ruby on Rails
supports this, I've studied the way they distinguish between the two.
Initially, Rails' REST integration distinguished based on the 'Accept'
header in the request. If Accept prefers text/xml, it would go through
the REST code path, and if HTML is requested, it'd go through the normal
code path.
The drawback of this approach is that it is hard to debug using a
browser - a browser will always get the normal views. You'll have to use
a tool like Curl and give it command-line options.
So, while Rails still supports this, they have now also enabled another
way which is much easier to debug, based on extensions. It's also
addressable using an independent URL, which is a good feature. If a
resource is accessed with a particular extension, REST is enabled:
http://localhost/foo -> normal Grok handling, default view
http://localhost/foo.xml -> REST XML representation
Note that we're talking about URLs to *resources*, not URLs to
particular views, for REST. REST only talks to resources, not views, so
'foo' is an actual object stored in the database, not a view on an object.
We can change the traversal behavior to support this. An alternative is
to rely on skins:
http://localhost/foo -> normal grok handling
http://localhost/++skin++rest/foo -> REST XML representation
Thinking about this, this seems the better way. The URLs look uglier
(can be fixed with a rewrite rule though), but URL generation is going
to work correctly, and no traversal hacking is required. This gives me
an excuse to finally look into merging the skinning work. :)
WebDAV
------
Zope 3 supports DELETE and PUT for the purposes of webdav (and other
things). Integrating REST in Grok will probably break webdav. Is anyone
using webdav? If not, I propose we look into fixing webdav after we
actually complete the REST work and not worry too much about it now. If
you are using WebDAV with Grok I expect help from you in making it work. :)
Implementation notes
--------------------
I created a preliminary implementation. Grok already has a special
publication integrated that takes care of the removal of security
proxies. Besides this a new GrokRequest object is introduced that is a
special BrowserRequest. This request object is necessary to override the
traversal behavior to check for the REST views (GET, POST, etc). It's
important that this check takes place *before* a default view is looked
up - this happens in the request's traversal. If REST views are
unavailable, the normal views are looked up.
It may be that this design can change if we use a separate skin for REST
always. publications have a special 'canHandle' method that can sniff (I
think) the request to see whether the request is trying to access a REST
skin. This might allow for a more elegant design.
Feedback? Questions? Comments? As stated above, I'd like to make quick
progress on implementating this in the coming weeks.
Regards,
Martijn
More information about the Grok-dev
mailing list