http: Move Mopidy request handlers to a MopidyHttpRouter

This commit is contained in:
Stein Magnus Jodal 2014-05-20 23:49:22 +02:00
parent 5d1f8f2203
commit 4b383c1762
5 changed files with 49 additions and 47 deletions

View File

@ -34,11 +34,12 @@ class Extension(ext.Extension):
raise exceptions.ExtensionError('tornado library not found', e) raise exceptions.ExtensionError('tornado library not found', e)
def setup(self, registry): def setup(self, registry):
from .actor import HttpFrontend from .actor import HttpFrontend, MopidyHttpRouter
HttpFrontend.routers = registry['http:router'] HttpFrontend.routers = registry['http:router']
registry.add('frontend', HttpFrontend) registry.add('frontend', HttpFrontend)
registry.add('http:router', MopidyHttpRouter)
class Router(object): class Router(object):

View File

@ -11,13 +11,15 @@ import tornado.ioloop
import tornado.web import tornado.web
import tornado.websocket import tornado.websocket
from mopidy import models, zeroconf from mopidy import http, models, zeroconf
from mopidy.core import CoreListener from mopidy.core import CoreListener
from mopidy.http import handlers from mopidy.http import handlers
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
mopidy_data_dir = os.path.join(os.path.dirname(__file__), 'data')
class HttpFrontend(pykka.ThreadingActor, CoreListener): class HttpFrontend(pykka.ThreadingActor, CoreListener):
routers = [] routers = []
@ -32,7 +34,6 @@ class HttpFrontend(pykka.ThreadingActor, CoreListener):
self.zeroconf_name = config['http']['zeroconf'] self.zeroconf_name = config['http']['zeroconf']
self.zeroconf_service = None self.zeroconf_service = None
self.app = None self.app = None
self.websocket_clients = set()
def on_start(self): def on_start(self):
threading.Thread(target=self._startup).start() threading.Thread(target=self._startup).start()
@ -59,15 +60,13 @@ class HttpFrontend(pykka.ThreadingActor, CoreListener):
event = data event = data
event['event'] = name event['event'] = name
message = json.dumps(event, cls=models.ModelJSONEncoder) message = json.dumps(event, cls=models.ModelJSONEncoder)
handlers.WebSocketHandler.broadcast(self.websocket_clients, message) handlers.WebSocketHandler.broadcast(message)
def _get_request_handlers(self): def _get_request_handlers(self):
mopidy_dir = os.path.join(os.path.dirname(__file__), 'data')
# Either default Mopidy or user defined path to files # Either default Mopidy or user defined path to files
static_dir = self.config['http']['static_dir'] static_dir = self.config['http']['static_dir']
root_dir = (r'/(.*)', handlers.StaticFileHandler, { root_dir = (r'/(.*)', handlers.StaticFileHandler, {
'path': static_dir if static_dir else mopidy_dir, 'path': static_dir if static_dir else mopidy_data_dir,
'default_filename': 'index.html' 'default_filename': 'index.html'
}) })
@ -76,15 +75,7 @@ class HttpFrontend(pykka.ThreadingActor, CoreListener):
'HTTP routes from extensions: %s', 'HTTP routes from extensions: %s',
list((l[0], l[1]) for l in request_handlers)) list((l[0], l[1]) for l in request_handlers))
# TODO: Dynamically define all endpoints request_handlers.append(root_dir)
request_handlers.extend([
(r'/mopidy/ws/?', handlers.WebSocketHandler, {'actor': self}),
(r'/mopidy/rpc', handlers.JsonRpcHandler, {'actor': self}),
(r'/mopidy/(.*)', handlers.StaticFileHandler, {
'path': mopidy_dir, 'default_filename': 'mopidy.html'
}),
root_dir,
])
return request_handlers return request_handlers
def _get_extension_request_handlers(self): def _get_extension_request_handlers(self):
@ -128,3 +119,16 @@ class HttpFrontend(pykka.ThreadingActor, CoreListener):
if self.zeroconf_mopidy_http_service: if self.zeroconf_mopidy_http_service:
self.zeroconf_mopidy_http_service.unpublish() self.zeroconf_mopidy_http_service.unpublish()
class MopidyHttpRouter(http.Router):
name = 'mopidy'
def get_request_handlers(self):
return [
(r'/mopidy/ws/?', handlers.WebSocketHandler, {'core': self.core}),
(r'/mopidy/rpc', handlers.JsonRpcHandler, {'core': self.core}),
(r'/mopidy/(.*)', handlers.StaticFileHandler, {
'path': mopidy_data_dir, 'default_filename': 'mopidy.html'
}),
]

View File

@ -14,7 +14,7 @@ from mopidy.utils import jsonrpc
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
def construct_rpc(actor): def make_jsonrpc_wrapper(core_actor):
inspector = jsonrpc.JsonRpcInspector( inspector = jsonrpc.JsonRpcInspector(
objects={ objects={
'core.get_uri_schemes': core.Core.get_uri_schemes, 'core.get_uri_schemes': core.Core.get_uri_schemes,
@ -27,12 +27,12 @@ def construct_rpc(actor):
return jsonrpc.JsonRpcWrapper( return jsonrpc.JsonRpcWrapper(
objects={ objects={
'core.describe': inspector.describe, 'core.describe': inspector.describe,
'core.get_uri_schemes': actor.core.get_uri_schemes, 'core.get_uri_schemes': core_actor.get_uri_schemes,
'core.get_version': actor.core.get_version, 'core.get_version': core_actor.get_version,
'core.library': actor.core.library, 'core.library': core_actor.library,
'core.playback': actor.core.playback, 'core.playback': core_actor.playback,
'core.playlists': actor.core.playlists, 'core.playlists': core_actor.playlists,
'core.tracklist': actor.core.tracklist, 'core.tracklist': core_actor.tracklist,
}, },
decoders=[models.model_json_decoder], decoders=[models.model_json_decoder],
encoders=[models.ModelJSONEncoder] encoders=[models.ModelJSONEncoder]
@ -40,23 +40,28 @@ def construct_rpc(actor):
class WebSocketHandler(tornado.websocket.WebSocketHandler): class WebSocketHandler(tornado.websocket.WebSocketHandler):
def initialize(self, actor):
self.actor = actor # XXX This set is shared by all WebSocketHandler objects. This isn't
self.jsonrpc = construct_rpc(actor) # optimal, but there's currently no use case for having more than one of
# these anyway.
clients = set()
@classmethod @classmethod
def broadcast(cls, clients, msg): def broadcast(cls, msg):
for client in clients: for client in cls.clients:
client.write_message(msg) client.write_message(msg)
def initialize(self, core):
self.jsonrpc = make_jsonrpc_wrapper(core)
def open(self): def open(self):
self.set_nodelay(True) self.set_nodelay(True)
self.actor.websocket_clients.add(self) self.clients.add(self)
logger.debug( logger.debug(
'New WebSocket connection from %s', self.request.remote_ip) 'New WebSocket connection from %s', self.request.remote_ip)
def on_close(self): def on_close(self):
self.actor.websocket_clients.discard(self) self.clients.discard(self)
logger.debug( logger.debug(
'Closed WebSocket connection from %s', 'Closed WebSocket connection from %s',
self.request.remote_ip) self.request.remote_ip)
@ -82,8 +87,8 @@ class WebSocketHandler(tornado.websocket.WebSocketHandler):
class JsonRpcHandler(tornado.web.RequestHandler): class JsonRpcHandler(tornado.web.RequestHandler):
def initialize(self, actor): def initialize(self, core):
self.jsonrpc = construct_rpc(actor) self.jsonrpc = make_jsonrpc_wrapper(core)
def head(self): def head(self):
self.set_extra_headers() self.set_extra_headers()

View File

@ -5,17 +5,9 @@ import unittest
import mock import mock
from mopidy.http import actor
try:
import tornado
except ImportError:
tornado = False
if tornado:
from mopidy.http import actor
@unittest.skipUnless(tornado, 'tornado is missing')
@mock.patch('mopidy.http.handlers.WebSocketHandler.broadcast') @mock.patch('mopidy.http.handlers.WebSocketHandler.broadcast')
class HttpEventsTest(unittest.TestCase): class HttpEventsTest(unittest.TestCase):
def setUp(self): def setUp(self):
@ -32,9 +24,8 @@ class HttpEventsTest(unittest.TestCase):
def test_track_playback_paused_is_broadcasted(self, broadcast): def test_track_playback_paused_is_broadcasted(self, broadcast):
broadcast.reset_mock() broadcast.reset_mock()
self.http.on_event('track_playback_paused', foo='bar') self.http.on_event('track_playback_paused', foo='bar')
self.assertEqual(broadcast.call_args[0][0], set([]))
self.assertDictEqual( self.assertDictEqual(
json.loads(str(broadcast.call_args[0][1])), { json.loads(str(broadcast.call_args[0][0])), {
'event': 'track_playback_paused', 'event': 'track_playback_paused',
'foo': 'bar', 'foo': 'bar',
}) })
@ -42,9 +33,8 @@ class HttpEventsTest(unittest.TestCase):
def test_track_playback_resumed_is_broadcasted(self, broadcast): def test_track_playback_resumed_is_broadcasted(self, broadcast):
broadcast.reset_mock() broadcast.reset_mock()
self.http.on_event('track_playback_resumed', foo='bar') self.http.on_event('track_playback_resumed', foo='bar')
self.assertEqual(broadcast.call_args[0][0], set([]))
self.assertDictEqual( self.assertDictEqual(
json.loads(str(broadcast.call_args[0][1])), { json.loads(str(broadcast.call_args[0][0])), {
'event': 'track_playback_resumed', 'event': 'track_playback_resumed',
'foo': 'bar', 'foo': 'bar',
}) })

View File

@ -22,8 +22,10 @@ class HttpServerTest(tornado.testing.AsyncHTTPTestCase):
core.get_version = mock.MagicMock(name='get_version') core.get_version = mock.MagicMock(name='get_version')
core.get_version.return_value = mopidy.__version__ core.get_version.return_value = mopidy.__version__
actor_http = actor.HttpFrontend(config=config, core=core) http_frontend = actor.HttpFrontend(config=config, core=core)
return tornado.web.Application(actor_http._get_request_handlers()) http_frontend.routers = [actor.MopidyHttpRouter]
return tornado.web.Application(http_frontend._get_request_handlers())
def test_root_should_return_index(self): def test_root_should_return_index(self):
response = self.fetch('/', method='GET') response = self.fetch('/', method='GET')