Merge pull request #784 from jodal/feature/http-startup
http: Improve error handling at HTTP server startup
This commit is contained in:
commit
03f2e0e322
@ -7,14 +7,16 @@ import threading
|
||||
|
||||
import pykka
|
||||
|
||||
import tornado.httpserver
|
||||
import tornado.ioloop
|
||||
import tornado.netutil
|
||||
import tornado.web
|
||||
import tornado.websocket
|
||||
|
||||
from mopidy import models, zeroconf
|
||||
from mopidy import exceptions, models, zeroconf
|
||||
from mopidy.core import CoreListener
|
||||
from mopidy.http import handlers
|
||||
from mopidy.utils import formatting
|
||||
from mopidy.utils import encoding, formatting, network
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@ -26,20 +28,32 @@ class HttpFrontend(pykka.ThreadingActor, CoreListener):
|
||||
|
||||
def __init__(self, config, core):
|
||||
super(HttpFrontend, self).__init__()
|
||||
self.config = config
|
||||
self.core = core
|
||||
|
||||
self.hostname = config['http']['hostname']
|
||||
self.hostname = network.format_hostname(config['http']['hostname'])
|
||||
self.port = config['http']['port']
|
||||
tornado_hostname = config['http']['hostname']
|
||||
if tornado_hostname == '::':
|
||||
tornado_hostname = None
|
||||
|
||||
try:
|
||||
logger.debug('Starting HTTP server')
|
||||
sockets = tornado.netutil.bind_sockets(self.port, tornado_hostname)
|
||||
self.server = HttpServer(
|
||||
config=config, core=core, sockets=sockets,
|
||||
apps=self.apps, statics=self.statics)
|
||||
except IOError as error:
|
||||
raise exceptions.FrontendError(
|
||||
'HTTP server startup failed: %s' %
|
||||
encoding.locale_decode(error))
|
||||
|
||||
self.zeroconf_name = config['http']['zeroconf']
|
||||
self.zeroconf_http = None
|
||||
self.zeroconf_mopidy_http = None
|
||||
|
||||
self.app = None
|
||||
|
||||
def on_start(self):
|
||||
threading.Thread(target=self._startup).start()
|
||||
logger.info(
|
||||
'HTTP server running at [%s]:%s', self.hostname, self.port)
|
||||
self.server.start()
|
||||
|
||||
if self.zeroconf_name:
|
||||
self.zeroconf_http = zeroconf.Zeroconf(
|
||||
@ -57,56 +71,59 @@ class HttpFrontend(pykka.ThreadingActor, CoreListener):
|
||||
if self.zeroconf_mopidy_http:
|
||||
self.zeroconf_mopidy_http.unpublish()
|
||||
|
||||
tornado.ioloop.IOLoop.instance().add_callback(self._shutdown)
|
||||
|
||||
def _startup(self):
|
||||
logger.debug('Starting HTTP server')
|
||||
self.app = tornado.web.Application(self._get_request_handlers())
|
||||
self.app.listen(self.port,
|
||||
self.hostname if self.hostname != '::' else None)
|
||||
logger.info(
|
||||
'HTTP server running at http://%s:%s', self.hostname, self.port)
|
||||
tornado.ioloop.IOLoop.instance().start()
|
||||
|
||||
def _shutdown(self):
|
||||
logger.debug('Stopping HTTP server')
|
||||
tornado.ioloop.IOLoop.instance().stop()
|
||||
logger.debug('Stopped HTTP server')
|
||||
self.server.stop()
|
||||
|
||||
def on_event(self, name, **data):
|
||||
event = data
|
||||
event['event'] = name
|
||||
message = json.dumps(event, cls=models.ModelJSONEncoder)
|
||||
handlers.WebSocketHandler.broadcast(message)
|
||||
on_event(name, **data)
|
||||
|
||||
|
||||
def on_event(name, **data):
|
||||
event = data
|
||||
event['event'] = name
|
||||
message = json.dumps(event, cls=models.ModelJSONEncoder)
|
||||
handlers.WebSocketHandler.broadcast(message)
|
||||
|
||||
|
||||
class HttpServer(threading.Thread):
|
||||
name = 'HttpServer'
|
||||
|
||||
def __init__(self, config, core, sockets, apps, statics):
|
||||
super(HttpServer, self).__init__()
|
||||
|
||||
self.config = config
|
||||
self.core = core
|
||||
self.sockets = sockets
|
||||
self.apps = apps
|
||||
self.statics = statics
|
||||
|
||||
self.app = None
|
||||
self.server = None
|
||||
|
||||
def run(self):
|
||||
self.app = tornado.web.Application(self._get_request_handlers())
|
||||
self.server = tornado.httpserver.HTTPServer(self.app)
|
||||
self.server.add_sockets(self.sockets)
|
||||
|
||||
tornado.ioloop.IOLoop.instance().start()
|
||||
|
||||
logger.debug('Stopped HTTP server')
|
||||
|
||||
def stop(self):
|
||||
logger.debug('Stopping HTTP server')
|
||||
tornado.ioloop.IOLoop.instance().add_callback(
|
||||
tornado.ioloop.IOLoop.instance().stop)
|
||||
|
||||
def _get_request_handlers(self):
|
||||
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
|
||||
static_dir = self.config['http']['static_dir']
|
||||
if static_dir and not os.path.exists(static_dir):
|
||||
logger.warning(
|
||||
'Configured http/static_dir %s does not exist. '
|
||||
'Falling back to default HTTP handler.', static_dir)
|
||||
static_dir = None
|
||||
if static_dir:
|
||||
request_handlers.append((r'/(.*)', handlers.StaticFileHandler, {
|
||||
'path': self.config['http']['static_dir'],
|
||||
'default_filename': 'index.html',
|
||||
}))
|
||||
else:
|
||||
request_handlers.append((r'/', tornado.web.RedirectHandler, {
|
||||
'url': '/mopidy/',
|
||||
'permanent': False,
|
||||
}))
|
||||
request_handlers.extend(self._get_mopidy_request_handlers())
|
||||
|
||||
logger.debug(
|
||||
'HTTP routes from extensions: %s',
|
||||
formatting.indent('\n'.join(
|
||||
'%r: %r' % (r[0], r[1]) for r in request_handlers)))
|
||||
|
||||
return request_handlers
|
||||
|
||||
def _get_app_request_handlers(self):
|
||||
@ -141,3 +158,25 @@ class HttpFrontend(pykka.ThreadingActor, CoreListener):
|
||||
))
|
||||
logger.debug('Loaded static HTTP extension: %s', static['name'])
|
||||
return result
|
||||
|
||||
def _get_mopidy_request_handlers(self):
|
||||
# Either default Mopidy or user defined path to files
|
||||
|
||||
static_dir = self.config['http']['static_dir']
|
||||
|
||||
if static_dir and not os.path.exists(static_dir):
|
||||
logger.warning(
|
||||
'Configured http/static_dir %s does not exist. '
|
||||
'Falling back to default HTTP handler.', static_dir)
|
||||
static_dir = None
|
||||
|
||||
if static_dir:
|
||||
return [(r'/(.*)', handlers.StaticFileHandler, {
|
||||
'path': self.config['http']['static_dir'],
|
||||
'default_filename': 'index.html',
|
||||
})]
|
||||
else:
|
||||
return [(r'/', tornado.web.RedirectHandler, {
|
||||
'url': '/mopidy/',
|
||||
'permanent': False,
|
||||
})]
|
||||
|
||||
@ -10,20 +10,12 @@ from mopidy.http import actor
|
||||
|
||||
@mock.patch('mopidy.http.handlers.WebSocketHandler.broadcast')
|
||||
class HttpEventsTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
config = {
|
||||
'http': {
|
||||
'hostname': '127.0.0.1',
|
||||
'port': 6680,
|
||||
'static_dir': None,
|
||||
'zeroconf': '',
|
||||
}
|
||||
}
|
||||
self.http = actor.HttpFrontend(config=config, core=mock.Mock())
|
||||
|
||||
def test_track_playback_paused_is_broadcasted(self, broadcast):
|
||||
broadcast.reset_mock()
|
||||
self.http.on_event('track_playback_paused', foo='bar')
|
||||
|
||||
actor.on_event('track_playback_paused', foo='bar')
|
||||
|
||||
self.assertDictEqual(
|
||||
json.loads(str(broadcast.call_args[0][0])), {
|
||||
'event': 'track_playback_paused',
|
||||
@ -32,7 +24,9 @@ class HttpEventsTest(unittest.TestCase):
|
||||
|
||||
def test_track_playback_resumed_is_broadcasted(self, broadcast):
|
||||
broadcast.reset_mock()
|
||||
self.http.on_event('track_playback_resumed', foo='bar')
|
||||
|
||||
actor.on_event('track_playback_resumed', foo='bar')
|
||||
|
||||
self.assertDictEqual(
|
||||
json.loads(str(broadcast.call_args[0][0])), {
|
||||
'event': 'track_playback_resumed',
|
||||
|
||||
@ -27,16 +27,19 @@ 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')]
|
||||
testapps = [dict(name='testapp')]
|
||||
teststatics = [dict(name='teststatic')]
|
||||
|
||||
http_frontend = actor.HttpFrontend(config=self.get_config(), core=core)
|
||||
http_frontend.apps = [{
|
||||
apps = [{
|
||||
'name': 'mopidy',
|
||||
'factory': handlers.make_mopidy_app_factory(apps, statics),
|
||||
'factory': handlers.make_mopidy_app_factory(testapps, teststatics),
|
||||
}]
|
||||
|
||||
return tornado.web.Application(http_frontend._get_request_handlers())
|
||||
http_server = actor.HttpServer(
|
||||
config=self.get_config(), core=core, sockets=[],
|
||||
apps=apps, statics=[])
|
||||
|
||||
return tornado.web.Application(http_server._get_request_handlers())
|
||||
|
||||
|
||||
class RootRedirectTest(HttpServerTest):
|
||||
@ -172,12 +175,12 @@ class HttpServerWithStaticFilesTest(tornado.testing.AsyncHTTPTestCase):
|
||||
}
|
||||
core = mock.Mock()
|
||||
|
||||
http_frontend = actor.HttpFrontend(config=config, core=core)
|
||||
http_frontend.statics = [
|
||||
dict(name='static', path=os.path.dirname(__file__)),
|
||||
]
|
||||
statics = [dict(name='static', path=os.path.dirname(__file__))]
|
||||
|
||||
return tornado.web.Application(http_frontend._get_request_handlers())
|
||||
http_server = actor.HttpServer(
|
||||
config=config, core=core, sockets=[], apps=[], statics=statics)
|
||||
|
||||
return tornado.web.Application(http_server._get_request_handlers())
|
||||
|
||||
def test_without_slash_should_redirect(self):
|
||||
response = self.fetch('/static', method='GET', follow_redirects=False)
|
||||
@ -222,13 +225,15 @@ class HttpServerWithWsgiAppTest(tornado.testing.AsyncHTTPTestCase):
|
||||
}
|
||||
core = mock.Mock()
|
||||
|
||||
http_frontend = actor.HttpFrontend(config=config, core=core)
|
||||
http_frontend.apps = [{
|
||||
apps = [{
|
||||
'name': 'wsgi',
|
||||
'factory': wsgi_app_factory,
|
||||
}]
|
||||
|
||||
return tornado.web.Application(http_frontend._get_request_handlers())
|
||||
http_server = actor.HttpServer(
|
||||
config=config, core=core, sockets=[], apps=apps, statics=[])
|
||||
|
||||
return tornado.web.Application(http_server._get_request_handlers())
|
||||
|
||||
def test_without_slash_should_redirect(self):
|
||||
response = self.fetch('/wsgi', method='GET', follow_redirects=False)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user