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

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

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,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())