http: Simplify app registration
This commit is contained in:
parent
b6ab29eed4
commit
33228f2528
@ -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
|
||||
|
||||
|
||||
@ -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):
|
||||
|
||||
@ -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):
|
||||
|
||||
@ -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):
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -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())
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user