diff --git a/mopidy/http/__init__.py b/mopidy/http/__init__.py
index 2e131817..95675386 100644
--- a/mopidy/http/__init__.py
+++ b/mopidy/http/__init__.py
@@ -35,7 +35,7 @@ class Extension(ext.Extension):
def setup(self, registry):
from .actor import HttpFrontend
- from .handlers import mopidy_app_factory
+ from .handlers import make_mopidy_app_factory
HttpFrontend.apps = registry['http:app']
HttpFrontend.statics = registry['http:static']
@@ -43,5 +43,6 @@ class Extension(ext.Extension):
registry.add('frontend', HttpFrontend)
registry.add('http:app', {
'name': 'mopidy',
- 'factory': mopidy_app_factory,
+ 'factory': make_mopidy_app_factory(
+ registry['http:app'], registry['http:static']),
})
diff --git a/mopidy/http/data/clients.html b/mopidy/http/data/clients.html
new file mode 100644
index 00000000..feff4fee
--- /dev/null
+++ b/mopidy/http/data/clients.html
@@ -0,0 +1,31 @@
+
+
+
+
+
+ Mopidy
+
+
+
+
+
Mopidy
+
+
This web server is a part of the Mopidy music server. To learn more
+ about Mopidy, please visit
+ www.mopidy.com.
+
+
+
+
Web clients
+
+
+
+
Web clients which are installed as Mopidy extensions will
+ automatically appear here.
+
+
+
diff --git a/mopidy/http/handlers.py b/mopidy/http/handlers.py
index 5c5d481f..00fbf083 100644
--- a/mopidy/http/handlers.py
+++ b/mopidy/http/handlers.py
@@ -15,19 +15,24 @@ from mopidy.utils import jsonrpc
logger = logging.getLogger(__name__)
-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': 'index.html'
- }),
- ]
+def make_mopidy_app_factory(apps, statics):
+ 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'),
+ }),
+ (r'/', ClientListHandler, {
+ 'apps': apps,
+ 'statics': statics,
+ }),
+ ]
+ return mopidy_app_factory
def make_jsonrpc_wrapper(core_actor):
@@ -142,6 +147,24 @@ class JsonRpcHandler(tornado.web.RequestHandler):
self.set_header('Content-Type', 'application/json; utf-8')
+class ClientListHandler(tornado.web.RequestHandler):
+ def initialize(self, apps, statics):
+ self.apps = apps
+ self.statics = statics
+
+ def get(self):
+ set_mopidy_headers(self)
+
+ names = set()
+ for app in self.apps:
+ names.add(app['name'])
+ for static in self.statics:
+ names.add(static['name'])
+ names.discard('mopidy')
+
+ self.render('data/clients.html', apps=sorted(list(names)))
+
+
class StaticFileHandler(tornado.web.StaticFileHandler):
def set_extra_headers(self, path):
set_mopidy_headers(self)
diff --git a/tests/http/test_server.py b/tests/http/test_server.py
index f8e1b74e..a3ef4997 100644
--- a/tests/http/test_server.py
+++ b/tests/http/test_server.py
@@ -25,10 +25,13 @@ class HttpServerTest(tornado.testing.AsyncHTTPTestCase):
core.get_version = mock.MagicMock(name='get_version')
core.get_version.return_value = mopidy.__version__
+ apps = [dict(name='testapp')]
+ statics = [dict(name='teststatic')]
+
http_frontend = actor.HttpFrontend(config=config, core=core)
http_frontend.apps = [{
'name': 'mopidy',
- 'factory': handlers.mopidy_app_factory,
+ 'factory': handlers.make_mopidy_app_factory(apps, statics),
}]
return tornado.web.Application(http_frontend._get_request_handlers())
@@ -57,10 +60,12 @@ class RootAppTest(HttpServerTest):
class MopidyAppTest(HttpServerTest):
def test_should_return_index(self):
response = self.fetch('/mopidy/', method='GET')
+ body = tornado.escape.to_unicode(response.body)
self.assertIn(
- 'This web server is a part of the Mopidy music server.',
- tornado.escape.to_unicode(response.body))
+ 'This web server is a part of the Mopidy music server.', body)
+ self.assertIn('testapp', body)
+ self.assertIn('teststatic', body)
self.assertEqual(
response.headers['X-Mopidy-Version'], mopidy.__version__)
self.assertEqual(response.headers['Cache-Control'], 'no-cache')