[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