From 968a0ab87a4bad94067102485e67b31656995242 Mon Sep 17 00:00:00 2001 From: Raoul Snyman Date: Wed, 15 Jun 2016 23:52:46 +0200 Subject: [PATCH] Add static routes for serving files, and template rendering built into endpoints --- openlp/core/api/http/__init__.py | 10 ++- openlp/core/api/http/endpoint/__init__.py | 26 ++++++-- openlp/core/api/http/wsgiapp.py | 79 +++++++++++++++++------ 3 files changed, 89 insertions(+), 26 deletions(-) diff --git a/openlp/core/api/http/__init__.py b/openlp/core/api/http/__init__.py index 485fd7753..b53f927f2 100644 --- a/openlp/core/api/http/__init__.py +++ b/openlp/core/api/http/__init__.py @@ -42,7 +42,13 @@ def register_endpoint(end_point): """ Register an endpoint with the app """ - for url, view_func, method, secure in end_point.routes: + for url, view_func, method in end_point.routes: + # Set the view functions route = _route_from_url(end_point.url_prefix, url) - application.add_route(route, view_func, method, secure) + application.add_route(route, view_func, method) + # Add a static route if necessary + if end_point.static_dir: + static_route = _route_from_url(end_point.url_prefix, 'static') + static_route += '(.*)' + application.add_static_route(static_route, end_point.static_dir) diff --git a/openlp/core/api/http/endpoint/__init__.py b/openlp/core/api/http/endpoint/__init__.py index aa5ee175e..272707c99 100644 --- a/openlp/core/api/http/endpoint/__init__.py +++ b/openlp/core/api/http/endpoint/__init__.py @@ -20,29 +20,34 @@ # Temple Place, Suite 330, Boston, MA 02111-1307 USA # ############################################################################### """ -openlp/core/api/endpoint.py: Endpoint stuff +The Endpoint class, which provides plugins with a way to serve their own portion of the API """ +import os + +from mako.template import Template class Endpoint(object): """ This is an endpoint for the HTTP API """ - def __init__(self, url_prefix): + def __init__(self, url_prefix, template_dir=None, static_dir=None): """ Create an endpoint with a URL prefix """ print("init") self.url_prefix = url_prefix + self.static_dir = static_dir + self.template_dir = template_dir self.routes = [] - def add_url_route(self, url, view_func, method, secure): + def add_url_route(self, url, view_func, method): """ Add a url route to the list of routes """ - self.routes.append((url, view_func, method, secure)) + self.routes.append((url, view_func, method)) - def route(self, rule, method='GET', secure=False): + def route(self, rule, method='GET'): """ Set up a URL route """ @@ -50,6 +55,15 @@ class Endpoint(object): """ Make this a decorator """ - self.add_url_route(rule, func, method, secure) + self.add_url_route(rule, func, method) return func return decorator + + def render_template(self, filename, **kwargs): + """ + Render a mako template + """ + if not self.template_dir: + raise Exception('No template directory specified') + path = os.path.abspath(os.path.join(self.template_dir, filename)) + return Template(filename=path, input_encoding='utf-8').render(**kwargs) diff --git a/openlp/core/api/http/wsgiapp.py b/openlp/core/api/http/wsgiapp.py index 9e631a40f..975acb69d 100644 --- a/openlp/core/api/http/wsgiapp.py +++ b/openlp/core/api/http/wsgiapp.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- # vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4 +# pylint: disable=logging-format-interpolation ############################################################################### # OpenLP - Open Source Lyrics Projection # @@ -22,17 +23,48 @@ """ App stuff """ -import logging import json +import logging +import os import re from webob import Request, Response +from webob.static import DirectoryApp from openlp.core.api.http.errors import HttpError, NotFound, ServerError +ARGS_REGEX = re.compile(r'''\{(\w+)(?::([^}]+))?\}''', re.VERBOSE) + log = logging.getLogger(__name__) +def _route_to_regex(route): + """ + Convert a route to a regular expression + + For example: + + 'songs/{song_id}' becomes 'songs/(?P[^/]+)' + + and + + 'songs/{song_id:\d+}' becomes 'songs/(?P\d+)' + + """ + route_regex = '' + last_pos = 0 + for match in ARGS_REGEX.finditer(route): + route_regex += re.escape(route[last_pos:match.start()]) + arg_name = match.group(1) + expr = match.group(2) or '[^/]+' + expr = '(?P<%s>%s)' % (arg_name, expr) + route_regex += expr + last_pos = match.end() + route_regex += re.escape(route[last_pos:]) + route_regex = '^%s$' % route_regex + return route_regex + + def _make_response(view_result): """ Create a Response object from response @@ -49,18 +81,11 @@ def _make_response(view_result): response = Response(body=body, status=view_result[1], content_type=content_type, charset='utf8') if len(view_result) >= 3: - response.headers = view_result[2] + response.headers.update(view_result[2]) return response elif isinstance(view_result, dict): return Response(body=json.dumps(view_result), status=200, content_type='application/json', charset='utf8') - elif isinstance(view_result, str): - if 'body {' in view_result: - return Response(body=view_result, status=200, - content_type='text/css', charset='utf8') - else: - return Response(body=view_result, status=200, - content_type='text/html', charset='utf8') def _handle_exception(error): @@ -83,26 +108,44 @@ class WSGIApplication(object): Create the app object """ self.name = name + self.static_routes = {} self.route_map = {} - def add_route(self, route, view_func, method, secure): + def add_route(self, route, view_func, method): """ Add a route """ - if route not in self.route_map: - self.route_map[route] = {} - self.route_map[route][method.upper()] = {'function': view_func, 'secure': secure} + route_regex = _route_to_regex(route) + if route_regex not in self.route_map: + self.route_map[route_regex] = {} + self.route_map[route_regex][method.upper()] = view_func + + def add_static_route(self, route, static_dir): + """ + Add a static directory as a route + """ + if not route in self.static_routes: + self.static_routes[route] = DirectoryApp(os.path.abspath(static_dir)) def dispatch(self, request): """ Find the appropriate URL and run the view function """ - for route, views in self.route_map.items(): + # First look to see if this is a static file request + for route, static_app in self.static_routes.items(): if re.match(route, request.path): - if request.method.upper() in views: - log.debug('Found {method} {url}'.format(method=request.method, url=request.path)) - view_func = views[request.method.upper()]['function'] - return _make_response(view_func(request)) + # Pop the path info twice in order to get rid of the "//static" + request.path_info_pop() + request.path_info_pop() + return request.get_response(static_app) + # If not a static route, try the views + for route, views in self.route_map.items(): + match = re.match(route, request.path) + if match and request.method.upper() in views: + kwargs = match.groupdict() + log.debug('Found {method} {url}'.format(method=request.method, url=request.path)) + view_func = views[request.method.upper()] + return _make_response(view_func(request, **kwargs)) log.error('Not Found url {url} '.format(url=request.path)) raise NotFound()