Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

resource/Site: Add regular expression support for path names #38

Open
wants to merge 4 commits into
base: master
Choose a base branch
from

Conversation

jnohlgard
Copy link

This PR adds an option to specify path components as regular expressions to handle dynamic sub resources without having to register each instance individually.

The example server has been updated with some new resources to demonstrate the added functionality.

Below are some example requests:

% ./aiocoap-client -m get 'coap://[::1]/regex/anything/blah'   
Regular expression match example
request.opt.uri_path = ('regex', 'anything', 'blah')
(No newline at end of message)
Exception ignored in: 
% ./aiocoap-client -m get 'coap://[::1]/regex/numbers/123456'
Regular expression match example
request.opt.uri_path = ('regex', 'numbers', '123456')
(No newline at end of message)
Exception ignored in: 
% ./aiocoap-client -m get 'coap://[::1]/regex/numbers/abcdef'
4.04 Not Found
Error: Resource not found!
% ./aiocoap-client -m get 'coap://[::1]/regex/pattern/a1'    
Regular expression match example
request.opt.uri_path = ('regex', 'pattern', 'a1')
(No newline at end of message)
Exception ignored in:
% ./aiocoap-client -m get 'coap://[::1]/regex/pattern/b7'
Regular expression match example
request.opt.uri_path = ('regex', 'pattern', 'b7')
(No newline at end of message)
Exception ignored in:
% ./aiocoap-client -m get 'coap://[::1]/regex/pattern/ba'
4.04 Not Found
Error: Resource not found!

jnohlgard pushed a commit to eistec/arrowhead-python that referenced this pull request Jun 17, 2016
@lcoudeville
Copy link

Why is this pull request never reviewed? This seems to be a very useful feature.

Flask has a similar feature to support variable components in an url: http://flask.pocoo.org/docs/1.0/api/#url-route-registrations .

From the information I have there is no such way to describe an variable item in a resource path. Consider a resource /user which stores it's user data in an underlying database. It should be strange to add an resource for each user that exists in that database. In practice this should mean that you're holding a copy of you user id's in the aiocoap site.

@lcoudeville
Copy link

I guess there should be a route/path oriented site instead of a resource based site. The werkzeug routing module could be reused: http://werkzeug.pocoo.org/docs/0.14/routing/ .

@lcoudeville
Copy link

lcoudeville commented Jan 17, 2019

FYI: I made a similar example using werkzeug. I use url mapping/rules to link an uri/url to a resource. This allows me to define paths, usings werkzeugs Converters, to variable resources. Without the need to define each resource in the site. The existence of a resource is checked by the render method of a resource. If a resource doesn't exists the resource must return a codes.NotFound.

http://werkzeug.pocoo.org/docs/0.14/routing/ explains how werkzeugs url routing works. It's also used in flask. werkzeug is pretty decent framework.

I made an path based variant on the original site by implementing the same interfaces. It's less code because a bit of the work is done by werkzeug. I'm having to will to make a pull request if there are people interested in this.

import logging
from os import path

import werkzeug.exceptions
from aiocoap import resource, error, interfaces
from werkzeug.routing import Map, Rule

from aiocoap.resource import PathCapable

class UrlRuleSite(interfaces.ObservableResource, PathCapable):

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        self.url_map = Map([
            Rule('/.well-known/core', endpoint=resource.WKCResource(self.get_resources_as_linkheader)),
            Rule('/asl', endpoint=ASL()),
            Rule("/peripheral/", endpoint=PeripheralResource()),
            Rule("/peripheral/<string:mac_addr>", endpoint=PeripheralResource()),
        ], strict_slashes=False)

        self.urls = self.url_map.bind("", "/")

    def _find_resource_and_add_args(self, request):
        try:
            uri_path = path.join(*request.opt.uri_path)  # FIXME reassembling uri_path should not be required
            endpoint, args = self.urls.match(uri_path)
            request._uri_path_args = args  # dirty hack because Resource.render can't handle args/kwargs.
            return endpoint  # Endpoint contains the designated resource
        except werkzeug.exceptions.NotFound:
            raise KeyError()

    async def render(self, request):
        try:
            endpoint = self._find_resource_and_add_args(request)
            return await endpoint.render(request)
        except KeyError:
            raise error.NotFound()

    async def needs_blockwise_assembly(self, request):
        try:
            child = self._find_resource_and_add_args(request)
        except KeyError:
            return True
        else:
            return await child.needs_blockwise_assembly(request)

    async def add_observation(self, request, serverobservation):
        try:
            child, _ = self._find_resource_and_add_args(request)
        except KeyError:
            return

        try:
            await child.add_observation(request, serverobservation)
        except AttributeError:
            pass

    def get_resources_as_linkheader(self):
        from aiocoap.util.linkformat import Link, LinkFormat

        links = []

        for _path, _resource in self.url_map.iter_rules():
            if hasattr(_resource, "get_link_description"):
                details = _resource.get_link_description()
            else:
                details = {}
            lh = Link(_path, **details)

            links.append(lh)

        return LinkFormat(links)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants