diff --git a/aiocoap/resource.py b/aiocoap/resource.py index 69db8c45..e6ae6a24 100644 --- a/aiocoap/resource.py +++ b/aiocoap/resource.py @@ -26,6 +26,7 @@ dispatch requests based on the Uri-Path header. """ +import re import hashlib import asyncio @@ -161,6 +162,9 @@ def render_get(self, request): response.opt.content_format = self.ct return response +class PathRegex(str): + """Regular expression match in resource path components""" + class Site(interfaces.ObservableResource): """Typical root element that gets passed to a :class:`Context` and contains all the resources that can be found when the endpoint gets accessed as a @@ -185,12 +189,34 @@ def needs_blockwise_assembly(self, request): @asyncio.coroutine def render(self, request): - try: - child = self._resources[request.opt.uri_path] - except KeyError: + path = tuple(request.opt.uri_path) + # Only compare against resources with the same number of path components + matches = [(key, res) for key, res in self._resources.items() if len(key) == len(path)] + # Filter routes matching the path, one level at a time + for level in range(len(path)): + # Try to find exact string matches + string_matches = [ + (key, res) for key, res in matches \ + if not isinstance(key[level], PathRegex) and \ + key[level] == path[level] + ] + if string_matches: + # string matches have a higher priority than regex matches + matches = string_matches + continue + # Try to find any regex matches + matches = [ + (key, res) for key, res in matches \ + if isinstance(key[level], PathRegex) and \ + re.fullmatch(key[level], path[level]) + ] + if len(matches) == 0: raise error.NoResource() - else: - return child.render(request) + elif len(matches) > 1: + raise error.RenderableError( + "Ambiguous matches: {} = {}".format(repr(path), repr(matches))) + child = matches[0][1] + return child.render(request) @asyncio.coroutine def add_observation(self, request, serverobservation): diff --git a/server.py b/server.py index 207c9201..5da6b1f8 100755 --- a/server.py +++ b/server.py @@ -13,6 +13,7 @@ import asyncio +from aiocoap.resource import PathRegex import aiocoap.resource as resource import aiocoap @@ -90,6 +91,15 @@ def render_get(self, request): payload = datetime.datetime.now().strftime("%Y-%m-%d %H:%M").encode('ascii') return aiocoap.Message(code=aiocoap.CONTENT, payload=payload) +class RegexResource(resource.Resource): + """Example wildcard matching resource, use request.opt.uri_path to find the + actual target of the request""" + + @asyncio.coroutine + def render_get(self, request): + payload = 'Regular expression match example\nrequest.opt.uri_path = {}'.format(repr(request.opt.uri_path)) + return aiocoap.Message(code=aiocoap.CONTENT, payload=payload.encode('utf-8')) + #class CoreResource(resource.Resource): # """ # Example Resource that provides list of links hosted by a server. @@ -129,6 +139,10 @@ def main(): root.add_resource(('other', 'separate'), SeparateLargeResource()) + root.add_resource(('regex', 'anything', PathRegex(r'.*')), RegexResource()) + root.add_resource(('regex', 'numbers', PathRegex(r'[0-9]*')), RegexResource()) + root.add_resource(('regex', 'pattern', PathRegex(r'[a-zA-Z][0-9]')), RegexResource()) + asyncio.async(aiocoap.Context.create_server_context(root)) asyncio.get_event_loop().run_forever() diff --git a/tests/server.py b/tests/server.py index c5b711ff..86b53fd0 100644 --- a/tests/server.py +++ b/tests/server.py @@ -77,6 +77,12 @@ def render_post(self, request): response = request.payload.replace(b'0', b'O') return aiocoap.Message(code=aiocoap.CONTENT, payload=response) +class PathResource(aiocoap.resource.Resource): + @asyncio.coroutine + def render_get(self, request): + payload = repr(request.opt.uri_path).encode('utf-8') + return aiocoap.Message(code=aiocoap.CONTENT, payload=payload) + class TestingSite(aiocoap.resource.Site): def __init__(self): super(TestingSite, self).__init__() @@ -86,6 +92,8 @@ def __init__(self): self.add_resource(('big',), BigResource()) self.add_resource(('slowbig',), SlowBigResource()) self.add_resource(('replacing',), ReplacingResource()) + self.add_resource(('wildcard', aiocoap.resource.PathRegex('.*')), PathResource()) + self.add_resource(('4digits', aiocoap.resource.PathRegex('[0-9]{4}')), PathResource()) # helpers