diff --git a/docs/api/http-server.rst b/docs/api/http-server.rst index 67c1d138..af401a24 100644 --- a/docs/api/http-server.rst +++ b/docs/api/http-server.rst @@ -17,9 +17,9 @@ The HTTP server side API can be used to: To host static files using the web server, an extension needs to register a name and a file path in the extension registry under the ``http:static`` key. -To extend the web server with a web application, an extension must create a -subclass of :class:`mopidy.http.Router` and register the subclass with the -extension registry under the ``http:router`` key. +To extend the web server with a web application, an extension must register a +name and a factory function in the extension registry under the ``http:app`` +key. For details on how to make a Mopidy extension, see the :ref:`extensiondev` guide. @@ -31,7 +31,7 @@ Static web client example To serve static files, you just need to register an ``http:static`` dictionary in the extension registry. The dictionary must have two keys: ``name`` and ``path``. The ``name`` is used to build the URL the static files will be -served on. By convention, it should be identical with the extension's +served on. By convention, it should be identical with the extension's :attr:`~mopidy.ext.Extension.ext_name`, like in the following example. The ``path`` tells Mopidy where on the disk the static files are located. @@ -55,7 +55,7 @@ available at http://localhost:6680/mywebclient/foo.html. def setup(self, registry): registry.add('http:static', { - 'name': 'mywebclient', + 'name': self.ext_name, 'path': os.path.join(os.path.dirname(__file__), 'static'), }) @@ -71,18 +71,27 @@ for Tornado request handlers. In the following example, we create a :class:`tornado.web.RequestHandler` called :class:`MyRequestHandler` that responds to HTTP GET requests with the -string ``Hello, world! This is Mopidy $version``. +string ``Hello, world! This is Mopidy $version``, where it gets the Mopidy +version from Mopidy's core API. -Then a :class:`~mopidy.http.Router` subclass registers this Tornado request -handler on the root URL, ``/``. The URLs returned from -:meth:`~mopidy.http.Router.get_request_handlers` are combined with the -:attr:`~mopidy.http.Router.name`` attribute of the router, so the full absolute -URL for the request handler becomes ``/mywebclient/``. +To hook the request handler into Mopidy's web server, we must register a +dictionary under the ``http:app`` key in the extension registry. The +dictionary must have two keys: ``name`` and ``factory``. -The router is added to the extension registry by -:meth:`MyWebClientExtension.setup` under the key ``http:router``. When the -extension is installed, Mopidy will respond to requests to -http://localhost:6680/mywebclient/ with the string ``Hello, world!``. +The ``name`` is used to build the URL the app will be served on. By convention, +it should be identical with the extension's +:attr:`~mopidy.ext.Extension.ext_name`, like in the following example. + +The ``factory`` must be a function that accepts two arguments, ``config`` and +``core``, respectively a dict structure of Mopidy's config and a +:class:`pykka.ActorProxy` to the full Mopidy core API. The ``factory`` function +must return a list of Tornado request handlers. The URL patterns of the request +handlers should not include the ``name``, as that will be prepended to the URL +patterns by the web server. + +When the extension is installed, Mopidy will respond to requests to +http://localhost:6680/mywebclient/ with the string ``Hello, world! This is +Mopidy $version``. :: @@ -92,7 +101,7 @@ http://localhost:6680/mywebclient/ with the string ``Hello, world!``. import tornado.web - from mopidy import ext, http + from mopidy import ext class MyRequestHandler(tornado.web.RequestHandler): @@ -105,20 +114,20 @@ http://localhost:6680/mywebclient/ with the string ``Hello, world!``. self.core.get_version().get()) - class MyTornadoRouter(http.Router): - name = 'mywebclient' - - def get_request_handlers(self): - return [ - ('/', MyRequestHandler, dict(core=self.core)) - ] + def my_app_factory(config, core): + return [ + ('/', MyRequestHandler, {'core': core}) + ] class MyWebClientExtension(ext.Extension): ext_name = 'mywebclient' def setup(self, registry): - registry.add('http:router', MyTornadoRouter) + registry.add('http:app', { + 'name': self.ext_name, + 'factory': my_app_factory, + }) # See the Extension API for the full details on this class @@ -147,34 +156,35 @@ http://localhost:6680/mywebclient/. import tornado.web import tornado.wsgi - from mopidy import ext, http + from mopidy import ext - class MyWSGIRouter(http.Router): - name = 'mywebclient' - - def get_request_handlers(self): - def wsgi_app(environ, start_response): - status = '200 OK' - response_headers = [('Content-type', 'text/plain')] - start_response(status, response_headers) - return [ - 'Hello, world! This is Mopidy %s\n' % - self.core.get_version().get() - ] + def my_app_factory(config, core): + def wsgi_app(environ, start_response): + status = '200 OK' + response_headers = [('Content-type', 'text/plain')] + start_response(status, response_headers) return [ - ('(.*)', tornado.web.FallbackHandler, { - 'fallback': tornado.wsgi.WSGIContainer(wsgi_app), - }), + 'Hello, world! This is Mopidy %s\n' % + self.core.get_version().get() ] + return [ + ('(.*)', tornado.web.FallbackHandler, { + 'fallback': tornado.wsgi.WSGIContainer(wsgi_app), + }), + ] + class MyWebClientExtension(ext.Extension): ext_name = 'mywebclient' def setup(self, registry): - registry.add('http:router', MyWSGIRouter) + registry.add('http:app', { + 'name': self.ext_name, + 'factory': my_app_factory, + }) # See the Extension API for the full details on this class diff --git a/mopidy/http/__init__.py b/mopidy/http/__init__.py index 072abcff..7718c2b6 100644 --- a/mopidy/http/__init__.py +++ b/mopidy/http/__init__.py @@ -35,13 +35,16 @@ class Extension(ext.Extension): def setup(self, registry): from .actor import HttpFrontend - from .handlers import MopidyHttpRouter + from .handlers import mopidy_app_factory - HttpFrontend.routers = registry['http:router'] + HttpFrontend.apps = registry['http:app'] HttpFrontend.statics = registry['http:static'] registry.add('frontend', HttpFrontend) - registry.add('http:router', MopidyHttpRouter) + registry.add('http:app', { + 'name': 'mopidy', + 'factory': mopidy_app_factory, + }) class Router(object): diff --git a/mopidy/http/actor.py b/mopidy/http/actor.py index 2243fe9d..f30532c6 100644 --- a/mopidy/http/actor.py +++ b/mopidy/http/actor.py @@ -20,7 +20,7 @@ logger = logging.getLogger(__name__) class HttpFrontend(pykka.ThreadingActor, CoreListener): - routers = [] + apps = [] statics = [] def __init__(self, config, core): @@ -64,7 +64,7 @@ class HttpFrontend(pykka.ThreadingActor, CoreListener): def _get_request_handlers(self): request_handlers = [] - request_handlers.extend(self._get_router_request_handlers()) + request_handlers.extend(self._get_app_request_handlers()) request_handlers.extend(self._get_static_request_handlers()) # Either default Mopidy or user defined path to files @@ -81,16 +81,15 @@ class HttpFrontend(pykka.ThreadingActor, CoreListener): list((l[0], l[1]) for l in request_handlers)) return request_handlers - def _get_router_request_handlers(self): + def _get_app_request_handlers(self): result = [] - for router_class in self.routers: - router = router_class(self.config, self.core) - request_handlers = router.get_request_handlers() + for app in self.apps: + request_handlers = app['factory'](self.config, self.core) for handler in request_handlers: handler = list(handler) - handler[0] = '/%s%s' % (router.name, handler[0]) + handler[0] = '/%s%s' % (app['name'], handler[0]) result.append(tuple(handler)) - logger.debug('Loaded HTTP extension: %s', router.name) + logger.debug('Loaded HTTP extension: %s', app['name']) return result def _get_static_request_handlers(self): diff --git a/mopidy/http/handlers.py b/mopidy/http/handlers.py index 9bc92953..52ee7524 100644 --- a/mopidy/http/handlers.py +++ b/mopidy/http/handlers.py @@ -8,25 +8,26 @@ import tornado.web import tornado.websocket import mopidy -from mopidy import core, http, models +from mopidy import core, models from mopidy.utils import jsonrpc logger = logging.getLogger(__name__) -class MopidyHttpRouter(http.Router): - name = 'mopidy' - - def get_request_handlers(self): - data_dir = os.path.join(os.path.dirname(__file__), 'data') - return [ - (r'/ws/?', WebSocketHandler, {'core': self.core}), - (r'/rpc', JsonRpcHandler, {'core': self.core}), - (r'/(.*)', StaticFileHandler, { - 'path': data_dir, 'default_filename': 'mopidy.html' - }), - ] +def mopidy_app_factory(config, core): + return [ + (r'/ws/?', WebSocketHandler, { + 'core': core, + }), + (r'/rpc', JsonRpcHandler, { + 'core': core, + }), + (r'/(.*)', StaticFileHandler, { + 'path': os.path.join(os.path.dirname(__file__), 'data'), + 'default_filename': 'mopidy.html' + }), + ] def make_jsonrpc_wrapper(core_actor): diff --git a/tests/http/test_handlers.py b/tests/http/test_handlers.py index 8b918e41..28e53855 100644 --- a/tests/http/test_handlers.py +++ b/tests/http/test_handlers.py @@ -14,12 +14,12 @@ class StaticFileHandlerTest(tornado.testing.AsyncHTTPTestCase): return tornado.web.Application([ (r'/(.*)', handlers.StaticFileHandler, { 'path': os.path.dirname(__file__), - 'default_filename': 'test_router.py' + 'default_filename': 'test_handlers.py' }) ]) def test_static_handler(self): - response = self.fetch('/test_router.py', method='GET') + response = self.fetch('/test_handlers.py', method='GET') self.assertEqual(200, response.code) self.assertEqual( diff --git a/tests/http/test_server.py b/tests/http/test_server.py index 8afe8759..d9fa6e80 100644 --- a/tests/http/test_server.py +++ b/tests/http/test_server.py @@ -8,7 +8,6 @@ import tornado.testing import tornado.wsgi import mopidy -from mopidy import http from mopidy.http import actor, handlers @@ -27,7 +26,10 @@ class HttpServerTest(tornado.testing.AsyncHTTPTestCase): core.get_version.return_value = mopidy.__version__ http_frontend = actor.HttpFrontend(config=config, core=core) - http_frontend.routers = [handlers.MopidyHttpRouter] + http_frontend.apps = [{ + 'name': 'mopidy', + 'factory': handlers.mopidy_app_factory, + }] return tornado.web.Application(http_frontend._get_request_handlers()) @@ -148,21 +150,19 @@ class HttpServerWithStaticFilesTest(tornado.testing.AsyncHTTPTestCase): response.headers['Cache-Control'], 'no-cache') -class WsgiAppRouter(http.Router): - name = 'wsgi' +def wsgi_app_factory(config, core): - def get_request_handlers(self): - def wsgi_app(environ, start_response): - status = '200 OK' - response_headers = [('Content-type', 'text/plain')] - start_response(status, response_headers) - return ['Hello, world!\n'] + def wsgi_app(environ, start_response): + status = '200 OK' + response_headers = [('Content-type', 'text/plain')] + start_response(status, response_headers) + return ['Hello, world!\n'] - return [ - ('(.*)', tornado.web.FallbackHandler, { - 'fallback': tornado.wsgi.WSGIContainer(wsgi_app), - }), - ] + return [ + ('(.*)', tornado.web.FallbackHandler, { + 'fallback': tornado.wsgi.WSGIContainer(wsgi_app), + }), + ] class HttpServerWithWsgiAppTest(tornado.testing.AsyncHTTPTestCase): @@ -178,7 +178,10 @@ class HttpServerWithWsgiAppTest(tornado.testing.AsyncHTTPTestCase): core = mock.Mock() http_frontend = actor.HttpFrontend(config=config, core=core) - http_frontend.routers = [WsgiAppRouter] + http_frontend.apps = [{ + 'name': 'wsgi', + 'factory': wsgi_app_factory, + }] return tornado.web.Application(http_frontend._get_request_handlers())