[Checkins] SVN: grok/branches/faassen-rest/src/grok/ More work on better Method Not Allowed error reporting.

Martijn Faassen faassen at infrae.com
Thu Sep 20 19:50:46 EDT 2007


Log message for revision 79776:
  More work on better Method Not Allowed error reporting.
  

Changed:
  U   grok/branches/faassen-rest/src/grok/components.py
  U   grok/branches/faassen-rest/src/grok/ftests/rest/rest.py
  U   grok/branches/faassen-rest/src/grok/meta.py
  U   grok/branches/faassen-rest/src/grok/rest.py

-=-
Modified: grok/branches/faassen-rest/src/grok/components.py
===================================================================
--- grok/branches/faassen-rest/src/grok/components.py	2007-09-20 22:26:54 UTC (rev 79775)
+++ grok/branches/faassen-rest/src/grok/components.py	2007-09-20 23:50:45 UTC (rev 79776)
@@ -192,20 +192,22 @@
     pass
 
 
+class GrokMethodNotAllowed(MethodNotAllowed):
+    pass
+
 class REST(object):
     def GET(self):
-        raise MethodNotAllowed(self.context, self.request)
+        raise GrokMethodNotAllowed(self.context, self.request)
     
     def POST(self):
-        raise MethodNotAllowed(self.context, self.request)
-
+        raise GrokMethodNotAllowed(self.context, self.request)
+    
     def PUT(self):
-        raise MethodNotAllowed(self.context, self.request)
+        raise GrokMethodNotAllowed(self.context, self.request)
     
     def DELETE(self):
-        raise MethodNotAllowed(self.context, self.request)
+        raise GrokMethodNotAllowed(self.context, self.request)
 
-
 class JSON(BrowserPage):
 
     def __call__(self):

Modified: grok/branches/faassen-rest/src/grok/ftests/rest/rest.py
===================================================================
--- grok/branches/faassen-rest/src/grok/ftests/rest/rest.py	2007-09-20 22:26:54 UTC (rev 79775)
+++ grok/branches/faassen-rest/src/grok/ftests/rest/rest.py	2007-09-20 23:50:45 UTC (rev 79776)
@@ -52,7 +52,7 @@
   >>> response = http_call('POST', 'http://localhost/++rest++b/app')
   Traceback (most recent call last):
     ...
-  MethodNotAllowed: <grok.ftests.rest.rest.MyApp object at ...>,
+  GrokMethodNotAllowed: <grok.ftests.rest.rest.MyApp object at ...>,
   <zope.publisher.browser.BrowserRequest instance URL=http://localhost/++rest++b/app/@@POST>
 
 DELETE is also not defined, so we also expect a 405 error::
@@ -60,7 +60,7 @@
   >>> response = http_call('DELETE', 'http://localhost/++rest++b/app')
   Traceback (most recent call last):
     ...
-  MethodNotAllowed: <grok.ftests.rest.rest.MyApp object at ...>,
+  GrokMethodNotAllowed: <grok.ftests.rest.rest.MyApp object at ...>,
   <zope.publisher.http.HTTPRequest instance URL=http://localhost/++rest++b/app>
 
 Let's examine protocol c where no method is allowed::
@@ -68,38 +68,38 @@
   >>> response = http_call('GET', 'http://localhost/++rest++c/app')
   Traceback (most recent call last):
     ...
-  MethodNotAllowed: <grok.ftests.rest.rest.MyApp object at ...
+  GrokMethodNotAllowed: <grok.ftests.rest.rest.MyApp object at ...
   >>> response = http_call('POST', 'http://localhost/++rest++c/app')
   Traceback (most recent call last):
     ...
-  MethodNotAllowed: <grok.ftests.rest.rest.MyApp object at ...
+  GrokMethodNotAllowed: <grok.ftests.rest.rest.MyApp object at ...
   >>> response = http_call('PUT', 'http://localhost/++rest++c/app')
   Traceback (most recent call last):
     ...
-  MethodNotAllowed: <grok.ftests.rest.rest.MyApp object at ...
+  GrokMethodNotAllowed: <grok.ftests.rest.rest.MyApp object at ...
   >>> response = http_call('DELETE', 'http://localhost/++rest++c/app')
   Traceback (most recent call last):
     ...
-  MethodNotAllowed: <grok.ftests.rest.rest.MyApp object at ...
+  GrokMethodNotAllowed: <grok.ftests.rest.rest.MyApp object at ...
 
 Let's examine the default protocol d, where nothing should work as well::
 
   >>> response = http_call('GET', 'http://localhost/++rest++d/app')
   Traceback (most recent call last):
     ...
-  MethodNotAllowed: <grok.ftests.rest.rest.MyApp object at ...
+  GrokMethodNotAllowed: <grok.ftests.rest.rest.MyApp object at ...
   >>> response = http_call('POST', 'http://localhost/++rest++d/app')
   Traceback (most recent call last):
     ...
-  MethodNotAllowed: <grok.ftests.rest.rest.MyApp object at ...
+  GrokMethodNotAllowed: <grok.ftests.rest.rest.MyApp object at ...
   >>> response = http_call('PUT', 'http://localhost/++rest++d/app')
   Traceback (most recent call last):
     ...
-  MethodNotAllowed: <grok.ftests.rest.rest.MyApp object at ...
+  GrokMethodNotAllowed: <grok.ftests.rest.rest.MyApp object at ...
   >>> response = http_call('DELETE', 'http://localhost/++rest++d/app')
   Traceback (most recent call last):
     ...
-  MethodNotAllowed: <grok.ftests.rest.rest.MyApp object at ...
+  GrokMethodNotAllowed: <grok.ftests.rest.rest.MyApp object at ...
   
 We have added support for GET for the ``alpha`` subobject only, in
 the default rest layer::
@@ -113,18 +113,49 @@
   >>> response = http_call('POST', 'http://localhost/++rest++d/app/alpha')
   Traceback (most recent call last):
     ...
-  MethodNotAllowed: <grok.ftests.rest.rest.MyContent object at ...
+  GrokMethodNotAllowed: <grok.ftests.rest.rest.MyContent object at ...
 
+According to the HTTP spec, in case of a 405 Method Not Allowed error,
+the response MUST include an Allow header containing a list of valid
+methods for the requested resource::
+
+  >>> print http('POST /++rest++b/app HTTP/1.1')
+  HTTP/1. 405 Method Not Allowed
+  Allow: GET, PUT
+  Content-Length: 18
+  Content-Type: text/plain
+  <BLANKLINE>
+  Method Not Allowed
+
+  >>> print http('DELETE /++rest++b/app HTTP/1.1')
+  HTTP/1. 405 Method Not Allowed
+  Allow: GET, PUT
+  Content-Length: 18
+  <BLANKLINE>
+  Method Not Allowed
+  
+  >>> print http('POST /++rest++c/app HTTP/1.1')
+  HTTP/1. 405 Method Not Allowed
+  Allow: 
+  Content-Length: 18
+  Content-Type: text/plain
+  <BLANKLINE>
+  Method Not Allowed
+  
 Todo:
 
 * MethodNotAllowed error URLs for GET and POST have @@GET and @@POST
   attached. Not pretty.
 
+* Support for OPTIONS, HEAD, other methods?
+
+* Content-Type header is there for GET/POST, but not for PUT/DELETE...
+
+* MethodNotAllowed doesn't work correctly if the method is completely
+  unrecognized, such as FROG instead of POST. It falls back on the default
+  MethodNotAllowed then, instead of GrokMethodNotAllowed.
+  
 * Security tests.
-
-* According to HTTP spec: 405 Method Not Allowed:
-  The response MUST include an Allow header containing a list of valid
-  methods for the requested resource.
 """
 
 import grok

Modified: grok/branches/faassen-rest/src/grok/meta.py
===================================================================
--- grok/branches/faassen-rest/src/grok/meta.py	2007-09-20 22:26:54 UTC (rev 79775)
+++ grok/branches/faassen-rest/src/grok/meta.py	2007-09-20 23:50:45 UTC (rev 79776)
@@ -149,11 +149,18 @@
                                                default=grok.IRESTLayer)
 
         for method in methods:
+            # determine if the method is not allowed (it's the same
+            # as the method on the superclass)
+            is_not_allowed_method = (method.im_func is
+                                     getattr(grok.REST,
+                                             method.__name__).im_func)
+
             # Make sure that the class inherits RestPublisher, so that the
             # views have a location
             method_view = type(
                 factory.__name__, (factory, RestPublisher),
-                {'__call__': method}
+                {'__call__': method,
+                 'is_not_allowed': is_not_allowed_method }
                 )
 
             component.provideAdapter(

Modified: grok/branches/faassen-rest/src/grok/rest.py
===================================================================
--- grok/branches/faassen-rest/src/grok/rest.py	2007-09-20 22:26:54 UTC (rev 79775)
+++ grok/branches/faassen-rest/src/grok/rest.py	2007-09-20 23:50:45 UTC (rev 79776)
@@ -1,20 +1,48 @@
 import grok
 
+from zope import component
+
 from zope.traversing.namespace import skin
 from zope.interface import Interface
 from zope.interface.interfaces import IInterface
 from zope.publisher.interfaces.browser import IBrowserRequest
 from zope.publisher.interfaces.http import IHTTPRequest
-from zope.app.publication.http import MethodNotAllowed
 
+from grok.components import GrokMethodNotAllowed
+
 class IRESTSkinType(IInterface):
     """Skin for REST requests.
     """
+
+class MethodNotAllowedView(grok.MultiAdapter):
+    grok.adapts(GrokMethodNotAllowed, IHTTPRequest)
+    grok.name('index.html')
+    grok.implements(Interface)
     
+    def __init__(self, error, request):
+        self.error = error
+        self.request = request
+        self.allow = self._getAllow()
+        
+    def _getAllow(self):
+        allow = []
+        for method in ['GET', 'PUT', 'POST', 'DELETE']:
+            view = component.queryMultiAdapter(
+                (self.error.object, self.error.request),
+                name=method)
+            if view is not None and not view.is_not_allowed:
+                allow.append(method)
+        allow.sort()
+        return allow
+    
+    def __call__(self):
+        self.request.response.setHeader('Allow', ', '.join(self.allow))
+        self.request.response.setStatus(405)
+        return 'Method Not Allowed'
+    
 class rest_skin(skin):
     skin_type = IRESTSkinType
 
 class DefaultRest(grok.REST):
     grok.context(Interface)
     grok.layer(grok.IRESTLayer)
-    



More information about the Checkins mailing list