mirror of https://gitlab.com/openlp/openlp.git
188 lines
6.6 KiB
Python
188 lines
6.6 KiB
Python
# -*- coding: utf-8 -*-
|
|
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
|
|
# pylint: disable=logging-format-interpolation
|
|
|
|
##########################################################################
|
|
# OpenLP - Open Source Lyrics Projection #
|
|
# ---------------------------------------------------------------------- #
|
|
# Copyright (c) 2008-2019 OpenLP Developers #
|
|
# ---------------------------------------------------------------------- #
|
|
# This program is free software: you can redistribute it and/or modify #
|
|
# it under the terms of the GNU General Public License as published by #
|
|
# the Free Software Foundation, either version 3 of the License, or #
|
|
# (at your option) any later version. #
|
|
# #
|
|
# This program is distributed in the hope that it will be useful, #
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
|
|
# GNU General Public License for more details. #
|
|
# #
|
|
# You should have received a copy of the GNU General Public License #
|
|
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
|
|
##########################################################################
|
|
"""
|
|
App stuff
|
|
"""
|
|
import json
|
|
import logging
|
|
import re
|
|
|
|
from webob import Request, Response
|
|
from webob.static import DirectoryApp
|
|
|
|
from openlp.core.api.http.errors import HttpError, NotFound, ServerError
|
|
from openlp.core.common.applocation import AppLocation
|
|
|
|
|
|
ARGS_REGEX = re.compile(r'''\{(\w+)(?::([^}]+))?\}''', re.VERBOSE)
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
|
|
def _route_to_regex(route):
|
|
r"""
|
|
Convert a route to a regular expression
|
|
|
|
For example:
|
|
|
|
'songs/{song_id}' becomes 'songs/(?P<song_id>[^/]+)'
|
|
|
|
and
|
|
|
|
'songs/{song_id:\d+}' becomes 'songs/(?P<song_id>\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
|
|
"""
|
|
log.debug("in Make response")
|
|
if isinstance(view_result, Response):
|
|
return view_result
|
|
elif isinstance(view_result, tuple):
|
|
content_type = 'text/html'
|
|
body = view_result[0]
|
|
if isinstance(body, dict):
|
|
content_type = 'application/json'
|
|
body = json.dumps(body)
|
|
response = Response(body=body, status=view_result[1],
|
|
content_type=content_type, charset='utf8')
|
|
if len(view_result) >= 3:
|
|
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):
|
|
return Response(body=view_result, status=200,
|
|
content_type='text/html', charset='utf8')
|
|
else:
|
|
return Response(body=view_result, status=200,
|
|
content_type='text/plain', charset='utf8')
|
|
|
|
|
|
def _handle_exception(error):
|
|
"""
|
|
Handle exceptions
|
|
"""
|
|
log.exception(error)
|
|
if isinstance(error, HttpError):
|
|
return error.to_response()
|
|
else:
|
|
return ServerError().to_response()
|
|
|
|
|
|
class WSGIApplication(object):
|
|
"""
|
|
This is the core of the API, the WSGI app
|
|
"""
|
|
def __init__(self, name):
|
|
"""
|
|
Create the app object
|
|
"""
|
|
self.name = name
|
|
self.static_routes = {}
|
|
self.route_map = {}
|
|
|
|
def initialise(self):
|
|
"""
|
|
Set up generic roots for the whole application
|
|
:return: None
|
|
"""
|
|
self.add_static_route('/assets(.*)', '')
|
|
self.add_static_route('/images(.*)', '')
|
|
pass
|
|
|
|
def add_route(self, route, view_func, method):
|
|
"""
|
|
Add a route
|
|
"""
|
|
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 route not in self.static_routes:
|
|
static_path = AppLocation.get_section_data_path('remotes') / static_dir
|
|
if not static_path.exists():
|
|
log.error('Static path "%s" does not exist. Skipping creating static route/', static_path)
|
|
return
|
|
self.static_routes[route] = DirectoryApp(str(static_path.resolve()))
|
|
|
|
def dispatch(self, request):
|
|
"""
|
|
Find the appropriate URL and run the view function
|
|
"""
|
|
# 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))
|
|
# 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):
|
|
return request.get_response(static_app)
|
|
log.error('URL {url} - Not found'.format(url=request.path))
|
|
raise NotFound()
|
|
|
|
def wsgi_app(self, environ, start_response):
|
|
"""
|
|
The actual WSGI application.
|
|
"""
|
|
request = Request(environ)
|
|
try:
|
|
response = self.dispatch(request)
|
|
except Exception as e:
|
|
response = _make_response(_handle_exception(e))
|
|
response.headers.add("cache-control", "no-cache, no-store, must-revalidate")
|
|
response.headers.add("pragma", "no-cache")
|
|
response.headers.add("expires", "0")
|
|
return response(environ, start_response)
|
|
|
|
def __call__(self, environ, start_response):
|
|
"""
|
|
Shortcut for wsgi_app.
|
|
"""
|
|
return self.wsgi_app(environ, start_response)
|