http: Simplify app registration

This commit is contained in:
Stein Magnus Jodal 2014-06-04 21:32:19 +02:00
parent b6ab29eed4
commit 33228f2528
6 changed files with 99 additions and 83 deletions

View File

@ -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.
@ -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,12 +114,9 @@ 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):
def my_app_factory(config, core):
return [
('/', MyRequestHandler, dict(core=self.core))
('/', MyRequestHandler, {'core': core})
]
@ -118,7 +124,10 @@ http://localhost:6680/mywebclient/ with the string ``Hello, world!``.
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,13 +156,11 @@ 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 my_app_factory(config, core):
def get_request_handlers(self):
def wsgi_app(environ, start_response):
status = '200 OK'
response_headers = [('Content-type', 'text/plain')]
@ -174,7 +181,10 @@ http://localhost:6680/mywebclient/.
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

View File

@ -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):

View File

@ -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):

View File

@ -8,23 +8,24 @@ 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')
def mopidy_app_factory(config, core):
return [
(r'/ws/?', WebSocketHandler, {'core': self.core}),
(r'/rpc', JsonRpcHandler, {'core': self.core}),
(r'/ws/?', WebSocketHandler, {
'core': core,
}),
(r'/rpc', JsonRpcHandler, {
'core': core,
}),
(r'/(.*)', StaticFileHandler, {
'path': data_dir, 'default_filename': 'mopidy.html'
'path': os.path.join(os.path.dirname(__file__), 'data'),
'default_filename': 'mopidy.html'
}),
]

View File

@ -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(

View File

@ -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,10 +150,8 @@ 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')]
@ -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())