Refactor http server

This commit is contained in:
Tim Bentley 2013-03-29 07:48:55 +00:00
parent ab011a22c1
commit 8cf886971c
4 changed files with 114 additions and 69 deletions

View File

@ -96,6 +96,7 @@ class SettingsForm(QtGui.QDialog, Ui_SettingsDialog):
""" """
Process the form saving the settings Process the form saving the settings
""" """
log.debug(u'Processing settings exit')
for tabIndex in range(self.stacked_layout.count()): for tabIndex in range(self.stacked_layout.count()):
self.stacked_layout.widget(tabIndex).save() self.stacked_layout.widget(tabIndex).save()
# if the display of image background are changing we need to regenerate the image cache # if the display of image background are changing we need to regenerate the image cache

View File

@ -126,54 +126,115 @@ from PyQt4 import QtCore
from openlp.core.lib import Registry, Settings, PluginStatus, StringContent from openlp.core.lib import Registry, Settings, PluginStatus, StringContent
from openlp.core.utils import AppLocation, translate from openlp.core.utils import AppLocation, translate
from openlp.plugins.remotes.lib.httpauth import AuthController, require_auth
from cherrypy._cpcompat import md5, sha, ntob
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
def sha_password_encrypter(password):
return sha(ntob(password)).hexdigest()
def fetch_password(username):
if username != Settings().value(u'remotes/user id'):
return None
print "fetch password", username
return sha(ntob(Settings().value(u'remotes/password'))).hexdigest()
class HttpServer(object): class HttpServer(object):
""" """
Ability to control OpenLP via a web browser. Ability to control OpenLP via a web browser.
""" """
_cp_config = {
'tools.sessions.on': True,
'tools.auth.on': True
}
def __init__(self, plugin): def __init__(self, plugin):
""" """
Initialise the http server, and start the server. Initialise the http server, and start the server.
""" """
log.debug(u'Initialise httpserver') log.debug(u'Initialise httpserver')
self.plugin = plugin self.plugin = plugin
self.html_dir = os.path.join(AppLocation.get_directory(AppLocation.PluginsDir), u'remotes', u'html') self.router = HttpRouter()
self.connections = []
self.conf = {'/files': {u'tools.staticdir.on': True,
u'tools.staticdir.dir': self.html_dir}}
def start_server(self): def start_server(self):
""" """
Start the http server, use the port in the settings default to 4316. Start the http server based on configuration.
Listen out for slide and song changes so they can be broadcast to clients. Listen out for socket connections.
""" """
log.debug(u'Start CherryPy server') log.debug(u'Start CherryPy server')
# Define to security levels and add the router code
self.root = self.Public()
self.root.files = self.Files()
self.root.stage = self.Stage()
self.root.router = self.router
self.root.files.router = self.router
self.root.stage.router = self.router
cherrypy.tree.mount(self.root, '/', config=self.define_config())
# Turn off the flood of access messages cause by poll
cherrypy.log.access_log.propagate = False
cherrypy.engine.start()
def define_config(self):
if Settings().value(self.plugin.settings_section + u'/https enabled'): if Settings().value(self.plugin.settings_section + u'/https enabled'):
port = Settings().value(self.plugin.settings_section + u'/https port') port = Settings().value(self.plugin.settings_section + u'/https port')
address = Settings().value(self.plugin.settings_section + u'/ip address') address = Settings().value(self.plugin.settings_section + u'/ip address')
shared_data = AppLocation.get_directory(AppLocation.SharedData) shared_data = AppLocation.get_directory(AppLocation.SharedData)
server_config = {u'server.socket_host': str(address), cherrypy.config.update({u'server.socket_host': str(address),
u'server.socket_port': port, u'server.socket_port': port,
u'server.ssl_certificate': os.path.join(shared_data, u'openlp.crt'), u'server.ssl_certificate': os.path.join(shared_data, u'openlp.crt'),
u'server.ssl_private_key': os.path.join(shared_data, u'openlp.key')} u'server.ssl_private_key': os.path.join(shared_data, u'openlp.key')})
else: else:
port = Settings().value(self.plugin.settings_section + u'/port') port = Settings().value(self.plugin.settings_section + u'/port')
address = Settings().value(self.plugin.settings_section + u'/ip address') address = Settings().value(self.plugin.settings_section + u'/ip address')
server_config = {u'server.socket_host': str(address), cherrypy.config.update({u'server.socket_host': str(address)})
u'server.socket_port': port} cherrypy.config.update({u'server.socket_port': port})
cherrypy.config.update(server_config) cherrypy.config.update({u'environment': u'embedded'})
cherrypy.config.update({'environment': 'embedded'}) cherrypy.config.update({u'engine.autoreload_on': False})
cherrypy.config.update({'engine.autoreload_on': False}) directory_config = {u'/': {u'tools.staticdir.on': True,
cherrypy.tree.mount(HttpConnection(self), '/', config=self.conf) u'tools.staticdir.dir': self.router.html_dir,
# Turn off the flood of access messages cause by poll u'tools.basic_auth.on': Settings().value(u'remotes/authentication enabled'),
cherrypy.log.access_log.propagate = False u'tools.basic_auth.realm': u'OpenLP Remote Login',
cherrypy.engine.start() u'tools.basic_auth.users': fetch_password,
log.debug(u'TCP listening on port %d' % port) u'tools.basic_auth.encrypt': sha_password_encrypter},
u'/files': {u'tools.staticdir.on': True,
u'tools.staticdir.dir': self.router.html_dir,
u'tools.basic_auth.on': False},
u'/stage': {u'tools.staticdir.on': True,
u'tools.staticdir.dir': self.router.html_dir,
u'tools.basic_auth.on': False}}
return directory_config
def reload_config(self):
cherrypy.tree.mount(self.root, '/', config=self.define_config())
cherrypy.config.reset()
class Public:
@cherrypy.expose
def default(self, *args, **kwargs):
print "public"
self.router.request_data = None
if isinstance(kwargs, dict):
self.router.request_data = kwargs.get(u'data', None)
url = urlparse.urlparse(cherrypy.url())
return self.router.process_http_request(url.path, *args)
class Files:
@cherrypy.expose
def default(self, *args, **kwargs):
print "files"
url = urlparse.urlparse(cherrypy.url())
return self.router.process_http_request(url.path, *args)
class Stage:
@cherrypy.expose
def default(self, *args, **kwargs):
url = urlparse.urlparse(cherrypy.url())
return self.router.process_http_request(url.path, *args)
def close(self): def close(self):
""" """
@ -181,25 +242,17 @@ class HttpServer(object):
""" """
log.debug(u'close http server') log.debug(u'close http server')
cherrypy.engine.exit() cherrypy.engine.exit()
cherrypy.engine.stop()
class HttpConnection(object): class HttpRouter(object):
""" """
A single connection, this handles communication between the server and the client. A single connection, this handles communication between the server and the client.
""" """
_cp_config = {
'tools.sessions.on': True,
'tools.auth.on': True
}
auth = AuthController() def __init__(self):
def __init__(self, parent):
""" """
Initialise the CherryPy Server Initialise the CherryPy Server
""" """
self.parent = parent
self.routes = [ self.routes = [
(u'^/$', self.serve_file), (u'^/$', self.serve_file),
(u'^/(stage)$', self.serve_file), (u'^/(stage)$', self.serve_file),
@ -218,33 +271,9 @@ class HttpConnection(object):
(r'^/api/(.*)/add$', self.add_to_service) (r'^/api/(.*)/add$', self.add_to_service)
] ]
self.translate() self.translate()
self.html_dir = os.path.join(AppLocation.get_directory(AppLocation.PluginsDir), u'remotes', u'html')
@cherrypy.expose def process_http_request(self, url_path, *args):
@require_auth()
def default(self, *args, **kwargs):
"""
Handles the requests for the main url. This is secure depending on settings in config.
"""
self.request_data = None
if isinstance(kwargs, dict):
self.request_data = kwargs.get(u'data', None)
return self._process_http_request(args, kwargs)
@cherrypy.expose
def stage(self, *args, **kwargs):
"""
Handles the requests for the /stage url. This is not secure.
"""
return self._process_http_request(args, kwargs)
@cherrypy.expose
def files(self, *args, **kwargs):
"""
Handles the requests for the /files url. This is not secure.
"""
return self._process_http_request(args, kwargs)
def _process_http_request(self, args, kwargs):
""" """
Common function to process HTTP requests where secure or insecure Common function to process HTTP requests where secure or insecure
""" """
@ -253,7 +282,7 @@ class HttpConnection(object):
for route, func in self.routes: for route, func in self.routes:
match = re.match(route, url.path) match = re.match(route, url.path)
if match: if match:
log.debug('Route "%s" matched "%s"', route, url.path) log.debug('Route "%s" matched "%s"', route, url_path)
args = [] args = []
for param in match.groups(): for param in match.groups():
args.append(param) args.append(param)
@ -332,8 +361,8 @@ class HttpConnection(object):
filename = u'index.html' filename = u'index.html'
elif filename == u'stage': elif filename == u'stage':
filename = u'stage.html' filename = u'stage.html'
path = os.path.normpath(os.path.join(self.parent.html_dir, filename)) path = os.path.normpath(os.path.join(self.html_dir, filename))
if not path.startswith(self.parent.html_dir): if not path.startswith(self.html_dir):
return self._http_not_found() return self._http_not_found()
ext = os.path.splitext(filename)[1] ext = os.path.splitext(filename)[1]
html = None html = None
@ -450,7 +479,6 @@ class HttpConnection(object):
if current_item: if current_item:
json_data[u'results'][u'item'] = self.live_controller.service_item.unique_identifier json_data[u'results'][u'item'] = self.live_controller.service_item.unique_identifier
else: else:
print event
if self.request_data: if self.request_data:
try: try:
data = json.loads(self.request_data)[u'request'][u'id'] data = json.loads(self.request_data)[u'request'][u'id']

View File

@ -155,6 +155,7 @@ class RemoteTab(SettingsTab):
self.address_edit.textChanged.connect(self.set_urls) self.address_edit.textChanged.connect(self.set_urls)
self.port_spin_box.valueChanged.connect(self.set_urls) self.port_spin_box.valueChanged.connect(self.set_urls)
self.https_port_spin_box.valueChanged.connect(self.set_urls) self.https_port_spin_box.valueChanged.connect(self.set_urls)
self.https_settings_group_box.clicked.connect(self.https_changed)
def retranslateUi(self): def retranslateUi(self):
self.server_settings_group_box.setTitle(translate('RemotePlugin.RemoteTab', 'Server Settings')) self.server_settings_group_box.setTitle(translate('RemotePlugin.RemoteTab', 'Server Settings'))
@ -224,15 +225,22 @@ class RemoteTab(SettingsTab):
self.user_id.setText(Settings().value(self.settings_section + u'/user id')) self.user_id.setText(Settings().value(self.settings_section + u'/user id'))
self.password.setText(Settings().value(self.settings_section + u'/password')) self.password.setText(Settings().value(self.settings_section + u'/password'))
self.set_urls() self.set_urls()
self.https_changed()
def save(self): def save(self):
changed = False """
Save the configuration and update the server configuration if necessary
"""
if Settings().value(self.settings_section + u'/ip address') != self.address_edit.text() or \ if Settings().value(self.settings_section + u'/ip address') != self.address_edit.text() or \
Settings().value(self.settings_section + u'/port') != self.port_spin_box.value() or \ Settings().value(self.settings_section + u'/port') != self.port_spin_box.value() or \
Settings().value(self.settings_section + u'/https port') != self.https_port_spin_box.value() or \ Settings().value(self.settings_section + u'/https port') != self.https_port_spin_box.value() or \
Settings().value(self.settings_section + u'/https enabled') != \ Settings().value(self.settings_section + u'/https enabled') != \
self.https_settings_group_box.isChecked(): self.https_settings_group_box.isChecked() or \
changed = True Settings().value(self.settings_section + u'/authentication enabled') != \
self.user_login_group_box.isChecked() or \
Settings().value(self.settings_section + u'/user id') != self.user_id.text() or \
Settings().value(self.settings_section + u'/password') != self.password.text():
self.settings_form.register_post_process(u'remotes_config_updated')
Settings().setValue(self.settings_section + u'/port', self.port_spin_box.value()) Settings().setValue(self.settings_section + u'/port', self.port_spin_box.value())
Settings().setValue(self.settings_section + u'/https port', self.https_port_spin_box.value()) Settings().setValue(self.settings_section + u'/https port', self.https_port_spin_box.value())
Settings().setValue(self.settings_section + u'/https enabled', self.https_settings_group_box.isChecked()) Settings().setValue(self.settings_section + u'/https enabled', self.https_settings_group_box.isChecked())
@ -241,12 +249,16 @@ class RemoteTab(SettingsTab):
Settings().setValue(self.settings_section + u'/authentication enabled', self.user_login_group_box.isChecked()) Settings().setValue(self.settings_section + u'/authentication enabled', self.user_login_group_box.isChecked())
Settings().setValue(self.settings_section + u'/user id', self.user_id.text()) Settings().setValue(self.settings_section + u'/user id', self.user_id.text())
Settings().setValue(self.settings_section + u'/password', self.password.text()) Settings().setValue(self.settings_section + u'/password', self.password.text())
if changed:
Registry().execute(u'remotes_config_updated')
def on_twelve_hour_check_box_changed(self, check_state): def on_twelve_hour_check_box_changed(self, check_state):
self.twelve_hour = False self.twelve_hour = False
# we have a set value convert to True/False # we have a set value convert to True/False
if check_state == QtCore.Qt.Checked: if check_state == QtCore.Qt.Checked:
self.twelve_hour = True self.twelve_hour = True
def https_changed(self):
"""
Invert the HTTP group box based on Https group settings
"""
self.http_settings_group_box.setEnabled(not self.https_settings_group_box.isChecked())

View File

@ -29,6 +29,8 @@
import logging import logging
from PyQt4 import QtGui
from openlp.core.lib import Plugin, StringContent, translate, build_icon from openlp.core.lib import Plugin, StringContent, translate, build_icon
from openlp.plugins.remotes.lib import RemoteTab, HttpServer from openlp.plugins.remotes.lib import RemoteTab, HttpServer
@ -76,6 +78,7 @@ class RemotesPlugin(Plugin):
Plugin.finalise(self) Plugin.finalise(self)
if self.server: if self.server:
self.server.close() self.server.close()
self.server = None
def about(self): def about(self):
""" """
@ -105,5 +108,6 @@ class RemotesPlugin(Plugin):
""" """
Called when Config is changed to restart the server on new address or port Called when Config is changed to restart the server on new address or port
""" """
self.finalise() log.debug(u'remote config changed')
self.initialise() self.main_window.information_message(translate('RemotePlugin', 'Configuration Change'),
translate('RemotePlugin', 'OpenLP will need to be restarted for the Remote changes to become active.'))