[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