diff options
Diffstat (limited to 'src/mailman/rest/wsgiapp.py')
| -rw-r--r-- | src/mailman/rest/wsgiapp.py | 101 |
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(): |
