summaryrefslogtreecommitdiff
path: root/src/mailman/rest/wsgiapp.py
diff options
context:
space:
mode:
Diffstat (limited to 'src/mailman/rest/wsgiapp.py')
-rw-r--r--src/mailman/rest/wsgiapp.py101
1 files changed, 93 insertions, 8 deletions
diff --git a/src/mailman/rest/wsgiapp.py b/src/mailman/rest/wsgiapp.py
index b7ad3d698..698c4269d 100644
--- a/src/mailman/rest/wsgiapp.py
+++ b/src/mailman/rest/wsgiapp.py
@@ -26,18 +26,22 @@ __all__ = [
]
+import re
import logging
-from restish.app import RestishApp
-from wsgiref.simple_server import WSGIRequestHandler
-from wsgiref.simple_server import make_server as wsgi_server
-
+from falcon import API
+from falcon.responders import path_not_found
+from falcon.routing import create_http_method_map
from mailman.config import config
from mailman.database.transaction import transactional
from mailman.rest.root import Root
+from wsgiref.simple_server import WSGIRequestHandler
+from wsgiref.simple_server import make_server as wsgi_server
log = logging.getLogger('mailman.http')
+_missing = object()
+SLASH = '/'
@@ -49,15 +53,96 @@ class AdminWebServiceWSGIRequestHandler(WSGIRequestHandler):
log.info('%s - - %s', self.address_string(), format % args)
-class AdminWebServiceApplication(RestishApp):
- """Connect the restish WSGI application to Mailman's database."""
+class RootedAPI(API):
+ def __init__(self, root, *args, **kws):
+ self._root = root
+ super(RootedAPI, self).__init__(*args, **kws)
@transactional
def __call__(self, environ, start_response):
"""See `RestishApp`."""
- return super(AdminWebServiceApplication, self).__call__(
+ return super(RootedAPI, self).__call__(
environ, start_response)
+ def _get_responder(self, req):
+ path = req.path
+ method = req.method
+ path_segments = path.split('/')
+ # Since the path is always rooted at /, skip the first segment, which
+ # will always be the empty string.
+ path_segments.pop(0)
+ this_segment = path_segments.pop(0)
+ resource = self._root
+ while True:
+ # See if there's a child matching the current segment.
+ # See if any of the resource's child links match the next segment.
+ for name in dir(resource):
+ if name.startswith('__') and name.endswith('__'):
+ continue
+ attribute = getattr(resource, name, _missing)
+ assert attribute is not _missing, name
+ matcher = getattr(attribute, '__matcher__', _missing)
+ if matcher is _missing:
+ continue
+ result = None
+ if isinstance(matcher, basestring):
+ # Is the matcher string a regular expression or plain
+ # string? If it starts with a caret, it's a regexp.
+ if matcher.startswith('^'):
+ cre = re.compile(matcher)
+ # Search against the entire remaining path.
+ tmp_path_segments = path_segments[:]
+ tmp_path_segments.insert(0, this_segment)
+ remaining_path = SLASH.join(tmp_path_segments)
+ mo = cre.match(remaining_path)
+ if mo:
+ result = attribute(
+ req, path_segments, **mo.groupdict())
+ elif matcher == this_segment:
+ result = attribute(req, path_segments)
+ else:
+ # The matcher is a callable. It returns None if it
+ # doesn't match, and if it does, it returns a 3-tuple
+ # containing the positional arguments, the keyword
+ # arguments, and the remaining segments. The attribute is
+ # then called with these arguments. Note that the matcher
+ # wants to see the full remaining path components, which
+ # includes the current hop.
+ tmp_path_segments = path_segments[:]
+ tmp_path_segments.insert(0, this_segment)
+ matcher_result = matcher(req, tmp_path_segments)
+ if matcher_result is not None:
+ positional, keyword, path_segments = matcher_result
+ result = attribute(
+ req, path_segments, *positional, **keyword)
+ # The attribute could return a 2-tuple giving the resource and
+ # remaining path segments, or it could just return the
+ # result. Of course, if the result is None, then the matcher
+ # did not match.
+ if result is None:
+ continue
+ elif isinstance(result, tuple):
+ resource, path_segments = result
+ else:
+ resource = result
+ # The method could have truncated the remaining segments,
+ # meaning, it's consumed all the path segments, or this is the
+ # last path segment. In that case the resource we're left at
+ # is the responder.
+ if len(path_segments) == 0:
+ # We're at the end of the path, so the root must be the
+ # responder.
+ method_map = create_http_method_map(
+ resource, None, None, None)
+ responder = method_map[method]
+ return responder, {}, resource
+ this_segment = path_segments.pop(0)
+ break
+ else:
+ # None of the attributes matched this path component, so the
+ # response is a 404.
+ return path_not_found, {}, None
+
def make_application():
@@ -66,7 +151,7 @@ def make_application():
Use this if you want to integrate Mailman's REST server with your own WSGI
server.
"""
- return AdminWebServiceApplication(Root())
+ return RootedAPI(Root())
def make_server():