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
"""
log.debug(u'Processing settings exit')
for tabIndex in range(self.stacked_layout.count()):
self.stacked_layout.widget(tabIndex).save()
# 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.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__)
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):
"""
Ability to control OpenLP via a web browser.
"""
_cp_config = {
'tools.sessions.on': True,
'tools.auth.on': True
}
def __init__(self, plugin):
"""
Initialise the http server, and start the server.
"""
log.debug(u'Initialise httpserver')
self.plugin = plugin
self.html_dir = os.path.join(AppLocation.get_directory(AppLocation.PluginsDir), u'remotes', u'html')
self.connections = []
self.conf = {'/files': {u'tools.staticdir.on': True,
u'tools.staticdir.dir': self.html_dir}}
self.router = HttpRouter()
def start_server(self):
"""
Start the http server, use the port in the settings default to 4316.
Listen out for slide and song changes so they can be broadcast to clients. Listen out for socket connections.
Start the http server based on configuration.
"""
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'):
port = Settings().value(self.plugin.settings_section + u'/https port')
address = Settings().value(self.plugin.settings_section + u'/ip address')
shared_data = AppLocation.get_directory(AppLocation.SharedData)
server_config = {u'server.socket_host': str(address),
u'server.socket_port': port,
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')}
cherrypy.config.update({u'server.socket_host': str(address),
u'server.socket_port': port,
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')})
else:
port = Settings().value(self.plugin.settings_section + u'/port')
address = Settings().value(self.plugin.settings_section + u'/ip address')
server_config = {u'server.socket_host': str(address),
u'server.socket_port': port}
cherrypy.config.update(server_config)
cherrypy.config.update({'environment': 'embedded'})
cherrypy.config.update({'engine.autoreload_on': False})
cherrypy.tree.mount(HttpConnection(self), '/', config=self.conf)
# Turn off the flood of access messages cause by poll
cherrypy.log.access_log.propagate = False
cherrypy.engine.start()
log.debug(u'TCP listening on port %d' % port)
cherrypy.config.update({u'server.socket_host': str(address)})
cherrypy.config.update({u'server.socket_port': port})
cherrypy.config.update({u'environment': u'embedded'})
cherrypy.config.update({u'engine.autoreload_on': False})
directory_config = {u'/': {u'tools.staticdir.on': True,
u'tools.staticdir.dir': self.router.html_dir,
u'tools.basic_auth.on': Settings().value(u'remotes/authentication enabled'),
u'tools.basic_auth.realm': u'OpenLP Remote Login',
u'tools.basic_auth.users': fetch_password,
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):
"""
@ -181,25 +242,17 @@ class HttpServer(object):
"""
log.debug(u'close http server')
cherrypy.engine.exit()
cherrypy.engine.stop()
class HttpConnection(object):
class HttpRouter(object):
"""
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, parent):
def __init__(self):
"""
Initialise the CherryPy Server
"""
self.parent = parent
self.routes = [
(u'^/$', self.serve_file),
(u'^/(stage)$', self.serve_file),
@ -218,33 +271,9 @@ class HttpConnection(object):
(r'^/api/(.*)/add$', self.add_to_service)
]
self.translate()
self.html_dir = os.path.join(AppLocation.get_directory(AppLocation.PluginsDir), u'remotes', u'html')
@cherrypy.expose
@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):
def process_http_request(self, url_path, *args):
"""
Common function to process HTTP requests where secure or insecure
"""
@ -253,7 +282,7 @@ class HttpConnection(object):
for route, func in self.routes:
match = re.match(route, url.path)
if match:
log.debug('Route "%s" matched "%s"', route, url.path)
log.debug('Route "%s" matched "%s"', route, url_path)
args = []
for param in match.groups():
args.append(param)
@ -332,8 +361,8 @@ class HttpConnection(object):
filename = u'index.html'
elif filename == u'stage':
filename = u'stage.html'
path = os.path.normpath(os.path.join(self.parent.html_dir, filename))
if not path.startswith(self.parent.html_dir):
path = os.path.normpath(os.path.join(self.html_dir, filename))
if not path.startswith(self.html_dir):
return self._http_not_found()
ext = os.path.splitext(filename)[1]
html = None
@ -450,7 +479,6 @@ class HttpConnection(object):
if current_item:
json_data[u'results'][u'item'] = self.live_controller.service_item.unique_identifier
else:
print event
if self.request_data:
try:
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.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):
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.password.setText(Settings().value(self.settings_section + u'/password'))
self.set_urls()
self.https_changed()
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 \
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 enabled') != \
self.https_settings_group_box.isChecked():
changed = True
self.https_settings_group_box.isChecked() or \
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'/https port', self.https_port_spin_box.value())
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'/user id', self.user_id.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):
self.twelve_hour = False
# we have a set value convert to True/False
if check_state == QtCore.Qt.Checked:
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
from PyQt4 import QtGui
from openlp.core.lib import Plugin, StringContent, translate, build_icon
from openlp.plugins.remotes.lib import RemoteTab, HttpServer
@ -76,6 +78,7 @@ class RemotesPlugin(Plugin):
Plugin.finalise(self)
if self.server:
self.server.close()
self.server = None
def about(self):
"""
@ -105,5 +108,6 @@ class RemotesPlugin(Plugin):
"""
Called when Config is changed to restart the server on new address or port
"""
self.finalise()
self.initialise()
log.debug(u'remote config changed')
self.main_window.information_message(translate('RemotePlugin', 'Configuration Change'),
translate('RemotePlugin', 'OpenLP will need to be restarted for the Remote changes to become active.'))