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
|
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.
|
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
|
To extend the web server with a web application, an extension must register a
|
||||||
subclass of :class:`mopidy.http.Router` and register the subclass with the
|
name and a factory function in the extension registry under the ``http:app``
|
||||||
extension registry under the ``http:router`` key.
|
key.
|
||||||
|
|
||||||
For details on how to make a Mopidy extension, see the :ref:`extensiondev`
|
For details on how to make a Mopidy extension, see the :ref:`extensiondev`
|
||||||
guide.
|
guide.
|
||||||
@ -31,7 +31,7 @@ Static web client example
|
|||||||
To serve static files, you just need to register an ``http:static`` dictionary
|
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
|
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
|
``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
|
:attr:`~mopidy.ext.Extension.ext_name`, like in the following example. The
|
||||||
``path`` tells Mopidy where on the disk the static files are located.
|
``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):
|
def setup(self, registry):
|
||||||
registry.add('http:static', {
|
registry.add('http:static', {
|
||||||
'name': 'mywebclient',
|
'name': self.ext_name,
|
||||||
'path': os.path.join(os.path.dirname(__file__), 'static'),
|
'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`
|
In the following example, we create a :class:`tornado.web.RequestHandler`
|
||||||
called :class:`MyRequestHandler` that responds to HTTP GET requests with the
|
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
|
To hook the request handler into Mopidy's web server, we must register a
|
||||||
handler on the root URL, ``/``. The URLs returned from
|
dictionary under the ``http:app`` key in the extension registry. The
|
||||||
:meth:`~mopidy.http.Router.get_request_handlers` are combined with the
|
dictionary must have two keys: ``name`` and ``factory``.
|
||||||
:attr:`~mopidy.http.Router.name`` attribute of the router, so the full absolute
|
|
||||||
URL for the request handler becomes ``/mywebclient/``.
|
|
||||||
|
|
||||||
The router is added to the extension registry by
|
The ``name`` is used to build the URL the app will be served on. By convention,
|
||||||
:meth:`MyWebClientExtension.setup` under the key ``http:router``. When the
|
it should be identical with the extension's
|
||||||
extension is installed, Mopidy will respond to requests to
|
:attr:`~mopidy.ext.Extension.ext_name`, like in the following example.
|
||||||
http://localhost:6680/mywebclient/ with the string ``Hello, world!``.
|
|
||||||
|
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
|
import tornado.web
|
||||||
|
|
||||||
from mopidy import ext, http
|
from mopidy import ext
|
||||||
|
|
||||||
|
|
||||||
class MyRequestHandler(tornado.web.RequestHandler):
|
class MyRequestHandler(tornado.web.RequestHandler):
|
||||||
@ -105,20 +114,20 @@ http://localhost:6680/mywebclient/ with the string ``Hello, world!``.
|
|||||||
self.core.get_version().get())
|
self.core.get_version().get())
|
||||||
|
|
||||||
|
|
||||||
class MyTornadoRouter(http.Router):
|
def my_app_factory(config, core):
|
||||||
name = 'mywebclient'
|
return [
|
||||||
|
('/', MyRequestHandler, {'core': core})
|
||||||
def get_request_handlers(self):
|
]
|
||||||
return [
|
|
||||||
('/', MyRequestHandler, dict(core=self.core))
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
class MyWebClientExtension(ext.Extension):
|
class MyWebClientExtension(ext.Extension):
|
||||||
ext_name = 'mywebclient'
|
ext_name = 'mywebclient'
|
||||||
|
|
||||||
def setup(self, registry):
|
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
|
# See the Extension API for the full details on this class
|
||||||
|
|
||||||
@ -147,34 +156,35 @@ http://localhost:6680/mywebclient/.
|
|||||||
import tornado.web
|
import tornado.web
|
||||||
import tornado.wsgi
|
import tornado.wsgi
|
||||||
|
|
||||||
from mopidy import ext, http
|
from mopidy import ext
|
||||||
|
|
||||||
|
|
||||||
class MyWSGIRouter(http.Router):
|
def my_app_factory(config, core):
|
||||||
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 wsgi_app(environ, start_response):
|
||||||
|
status = '200 OK'
|
||||||
|
response_headers = [('Content-type', 'text/plain')]
|
||||||
|
start_response(status, response_headers)
|
||||||
return [
|
return [
|
||||||
('(.*)', tornado.web.FallbackHandler, {
|
'Hello, world! This is Mopidy %s\n' %
|
||||||
'fallback': tornado.wsgi.WSGIContainer(wsgi_app),
|
self.core.get_version().get()
|
||||||
}),
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
return [
|
||||||
|
('(.*)', tornado.web.FallbackHandler, {
|
||||||
|
'fallback': tornado.wsgi.WSGIContainer(wsgi_app),
|
||||||
|
}),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class MyWebClientExtension(ext.Extension):
|
class MyWebClientExtension(ext.Extension):
|
||||||
ext_name = 'mywebclient'
|
ext_name = 'mywebclient'
|
||||||
|
|
||||||
def setup(self, registry):
|
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
|
# See the Extension API for the full details on this class
|
||||||
|
|
||||||
|
|||||||
@ -35,13 +35,16 @@ class Extension(ext.Extension):
|
|||||||
|
|
||||||
def setup(self, registry):
|
def setup(self, registry):
|
||||||
from .actor import HttpFrontend
|
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']
|
HttpFrontend.statics = registry['http:static']
|
||||||
|
|
||||||
registry.add('frontend', HttpFrontend)
|
registry.add('frontend', HttpFrontend)
|
||||||
registry.add('http:router', MopidyHttpRouter)
|
registry.add('http:app', {
|
||||||
|
'name': 'mopidy',
|
||||||
|
'factory': mopidy_app_factory,
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
class Router(object):
|
class Router(object):
|
||||||
|
|||||||
@ -20,7 +20,7 @@ logger = logging.getLogger(__name__)
|
|||||||
|
|
||||||
|
|
||||||
class HttpFrontend(pykka.ThreadingActor, CoreListener):
|
class HttpFrontend(pykka.ThreadingActor, CoreListener):
|
||||||
routers = []
|
apps = []
|
||||||
statics = []
|
statics = []
|
||||||
|
|
||||||
def __init__(self, config, core):
|
def __init__(self, config, core):
|
||||||
@ -64,7 +64,7 @@ class HttpFrontend(pykka.ThreadingActor, CoreListener):
|
|||||||
def _get_request_handlers(self):
|
def _get_request_handlers(self):
|
||||||
request_handlers = []
|
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())
|
request_handlers.extend(self._get_static_request_handlers())
|
||||||
|
|
||||||
# Either default Mopidy or user defined path to files
|
# 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))
|
list((l[0], l[1]) for l in request_handlers))
|
||||||
return request_handlers
|
return request_handlers
|
||||||
|
|
||||||
def _get_router_request_handlers(self):
|
def _get_app_request_handlers(self):
|
||||||
result = []
|
result = []
|
||||||
for router_class in self.routers:
|
for app in self.apps:
|
||||||
router = router_class(self.config, self.core)
|
request_handlers = app['factory'](self.config, self.core)
|
||||||
request_handlers = router.get_request_handlers()
|
|
||||||
for handler in request_handlers:
|
for handler in request_handlers:
|
||||||
handler = list(handler)
|
handler = list(handler)
|
||||||
handler[0] = '/%s%s' % (router.name, handler[0])
|
handler[0] = '/%s%s' % (app['name'], handler[0])
|
||||||
result.append(tuple(handler))
|
result.append(tuple(handler))
|
||||||
logger.debug('Loaded HTTP extension: %s', router.name)
|
logger.debug('Loaded HTTP extension: %s', app['name'])
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def _get_static_request_handlers(self):
|
def _get_static_request_handlers(self):
|
||||||
|
|||||||
@ -8,25 +8,26 @@ import tornado.web
|
|||||||
import tornado.websocket
|
import tornado.websocket
|
||||||
|
|
||||||
import mopidy
|
import mopidy
|
||||||
from mopidy import core, http, models
|
from mopidy import core, models
|
||||||
from mopidy.utils import jsonrpc
|
from mopidy.utils import jsonrpc
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class MopidyHttpRouter(http.Router):
|
def mopidy_app_factory(config, core):
|
||||||
name = 'mopidy'
|
return [
|
||||||
|
(r'/ws/?', WebSocketHandler, {
|
||||||
def get_request_handlers(self):
|
'core': core,
|
||||||
data_dir = os.path.join(os.path.dirname(__file__), 'data')
|
}),
|
||||||
return [
|
(r'/rpc', JsonRpcHandler, {
|
||||||
(r'/ws/?', WebSocketHandler, {'core': self.core}),
|
'core': core,
|
||||||
(r'/rpc', JsonRpcHandler, {'core': self.core}),
|
}),
|
||||||
(r'/(.*)', StaticFileHandler, {
|
(r'/(.*)', StaticFileHandler, {
|
||||||
'path': data_dir, 'default_filename': 'mopidy.html'
|
'path': os.path.join(os.path.dirname(__file__), 'data'),
|
||||||
}),
|
'default_filename': 'mopidy.html'
|
||||||
]
|
}),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
def make_jsonrpc_wrapper(core_actor):
|
def make_jsonrpc_wrapper(core_actor):
|
||||||
|
|||||||
@ -14,12 +14,12 @@ class StaticFileHandlerTest(tornado.testing.AsyncHTTPTestCase):
|
|||||||
return tornado.web.Application([
|
return tornado.web.Application([
|
||||||
(r'/(.*)', handlers.StaticFileHandler, {
|
(r'/(.*)', handlers.StaticFileHandler, {
|
||||||
'path': os.path.dirname(__file__),
|
'path': os.path.dirname(__file__),
|
||||||
'default_filename': 'test_router.py'
|
'default_filename': 'test_handlers.py'
|
||||||
})
|
})
|
||||||
])
|
])
|
||||||
|
|
||||||
def test_static_handler(self):
|
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(200, response.code)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
|
|||||||
@ -8,7 +8,6 @@ import tornado.testing
|
|||||||
import tornado.wsgi
|
import tornado.wsgi
|
||||||
|
|
||||||
import mopidy
|
import mopidy
|
||||||
from mopidy import http
|
|
||||||
from mopidy.http import actor, handlers
|
from mopidy.http import actor, handlers
|
||||||
|
|
||||||
|
|
||||||
@ -27,7 +26,10 @@ class HttpServerTest(tornado.testing.AsyncHTTPTestCase):
|
|||||||
core.get_version.return_value = mopidy.__version__
|
core.get_version.return_value = mopidy.__version__
|
||||||
|
|
||||||
http_frontend = actor.HttpFrontend(config=config, core=core)
|
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())
|
return tornado.web.Application(http_frontend._get_request_handlers())
|
||||||
|
|
||||||
@ -148,21 +150,19 @@ class HttpServerWithStaticFilesTest(tornado.testing.AsyncHTTPTestCase):
|
|||||||
response.headers['Cache-Control'], 'no-cache')
|
response.headers['Cache-Control'], 'no-cache')
|
||||||
|
|
||||||
|
|
||||||
class WsgiAppRouter(http.Router):
|
def wsgi_app_factory(config, core):
|
||||||
name = 'wsgi'
|
|
||||||
|
|
||||||
def get_request_handlers(self):
|
def wsgi_app(environ, start_response):
|
||||||
def wsgi_app(environ, start_response):
|
status = '200 OK'
|
||||||
status = '200 OK'
|
response_headers = [('Content-type', 'text/plain')]
|
||||||
response_headers = [('Content-type', 'text/plain')]
|
start_response(status, response_headers)
|
||||||
start_response(status, response_headers)
|
return ['Hello, world!\n']
|
||||||
return ['Hello, world!\n']
|
|
||||||
|
|
||||||
return [
|
return [
|
||||||
('(.*)', tornado.web.FallbackHandler, {
|
('(.*)', tornado.web.FallbackHandler, {
|
||||||
'fallback': tornado.wsgi.WSGIContainer(wsgi_app),
|
'fallback': tornado.wsgi.WSGIContainer(wsgi_app),
|
||||||
}),
|
}),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class HttpServerWithWsgiAppTest(tornado.testing.AsyncHTTPTestCase):
|
class HttpServerWithWsgiAppTest(tornado.testing.AsyncHTTPTestCase):
|
||||||
@ -178,7 +178,10 @@ class HttpServerWithWsgiAppTest(tornado.testing.AsyncHTTPTestCase):
|
|||||||
core = mock.Mock()
|
core = mock.Mock()
|
||||||
|
|
||||||
http_frontend = actor.HttpFrontend(config=config, core=core)
|
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())
|
return tornado.web.Application(http_frontend._get_request_handlers())
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user