forked from openlp/openlp
Refactor http server
This commit is contained in:
parent
ab011a22c1
commit
8cf886971c
@ -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
|
||||||
|
@ -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']
|
||||||
|
@ -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())
|
||||||
|
|
||||||
|
@ -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.'))
|
||||||
|
Loading…
Reference in New Issue
Block a user