Start of websockets in remote

This commit is contained in:
Tim Bentley 2016-06-04 11:50:43 +01:00
parent e78fd2f503
commit b3c8b722e2
5 changed files with 152 additions and 25 deletions

View File

@ -20,8 +20,10 @@
# Temple Place, Suite 330, Boston, MA 02111-1307 USA # # Temple Place, Suite 330, Boston, MA 02111-1307 USA #
############################################################################### ###############################################################################
from .poll import OpenLPPoll
from .remotetab import RemoteTab from .remotetab import RemoteTab
from .httprouter import HttpRouter from .httprouter import HttpRouter
from .httpserver import OpenLPServer from .httpserver import OpenLPServer
__all__ = ['RemoteTab', 'OpenLPServer', 'HttpRouter']
__all__ = ['RemoteTab', 'OpenLPServer', 'HttpRouter', 'OpenLPPoll']

View File

@ -25,17 +25,19 @@ The :mod:`http` module contains the API web server. This is a lightweight web se
with OpenLP. It uses JSON to communicate with the remotes. with OpenLP. It uses JSON to communicate with the remotes.
""" """
import asyncio
import ssl import ssl
import socket import socket
import websockets
import os import os
import logging import logging
import time import time
from PyQt5 import QtCore from PyQt5 import QtCore
from openlp.core.common import AppLocation, Settings, RegistryProperties from openlp.core.common import AppLocation, Settings, RegistryProperties, OpenLPMixin
from openlp.plugins.remotes.lib import HttpRouter from openlp.plugins.remotes.lib import HttpRouter, OpenLPPoll
from socketserver import BaseServer, ThreadingMixIn from socketserver import BaseServer, ThreadingMixIn
from http.server import BaseHTTPRequestHandler, HTTPServer from http.server import BaseHTTPRequestHandler, HTTPServer
@ -43,7 +45,7 @@ from http.server import BaseHTTPRequestHandler, HTTPServer
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
class CustomHandler(BaseHTTPRequestHandler, HttpRouter): class WebHandler(BaseHTTPRequestHandler, HttpRouter):
""" """
Stateless session handler to handle the HTTP request and process it. Stateless session handler to handle the HTTP request and process it.
This class handles just the overrides to the base methods and the logic to invoke the methods within the HttpRouter This class handles just the overrides to the base methods and the logic to invoke the methods within the HttpRouter
@ -88,18 +90,21 @@ class HttpThread(QtCore.QThread):
self.http_server.start_server() self.http_server.start_server()
def stop(self): def stop(self):
log.debug("stop called")
self.http_server.stop = True self.http_server.stop = True
class OpenLPServer(RegistryProperties): class OpenLPServer(RegistryProperties, OpenLPMixin):
def __init__(self): """
Wrapper round a server instance
"""
def __init__(self, websocket=False, secure=False):
""" """
Initialise the http server, and start the server of the correct type http / https Initialise the http server, and start the server of the correct type http / https
""" """
super(OpenLPServer, self).__init__() super(OpenLPServer, self).__init__()
log.debug('Initialise OpenLP')
self.settings_section = 'remotes' self.settings_section = 'remotes'
self.secure = secure
self.websocket = websocket
self.http_thread = HttpThread(self) self.http_thread = HttpThread(self)
self.http_thread.start() self.http_thread.start()
@ -108,21 +113,34 @@ class OpenLPServer(RegistryProperties):
Start the correct server and save the handler Start the correct server and save the handler
""" """
address = Settings().value(self.settings_section + '/ip address') address = Settings().value(self.settings_section + '/ip address')
self.address = address is_secure = Settings().value(self.settings_section + '/https enabled')
self.is_secure = Settings().value(self.settings_section + '/https enabled') # Try to start secure server but not enabled.
self.needs_authentication = Settings().value(self.settings_section + '/authentication enabled') if self.secure and not is_secure:
if self.is_secure: return
if self.secure:
port = Settings().value(self.settings_section + '/https port') port = Settings().value(self.settings_section + '/https port')
self.port = port
self.start_server_instance(address, port, HTTPSServer)
else: else:
port = Settings().value(self.settings_section + '/port') port = Settings().value(self.settings_section + '/port')
self.port = port if self.secure:
self.start_server_instance(address, port, HTTPSServer)
else:
if self.websocket:
self.start_websocket_instance(address, port)
else:
self.start_server_instance(address, port, ThreadingHTTPServer) self.start_server_instance(address, port, ThreadingHTTPServer)
# If HTTP server start listening
if hasattr(self, 'httpd') and self.httpd: if hasattr(self, 'httpd') and self.httpd:
self.httpd.serve_forever() self.httpd.serve_forever()
else: else:
log.debug('Failed to start server') log.debug('Failed to start http server on port {port}'.format(port=port))
# If web socket server start listening
if hasattr(self, 'ws_server') and self.ws_server:
event_loop = asyncio.new_event_loop()
asyncio.set_event_loop(event_loop)
event_loop.run_until_complete(self.ws_server)
event_loop.run_forever()
else:
log.debug('Failed to start ws server on port {port}'.format(port=port))
def start_server_instance(self, address, port, server_class): def start_server_instance(self, address, port, server_class):
""" """
@ -135,7 +153,7 @@ class OpenLPServer(RegistryProperties):
loop = 1 loop = 1
while loop < 4: while loop < 4:
try: try:
self.httpd = server_class((address, port), CustomHandler) self.httpd = server_class((address, port), WebHandler)
log.debug("Server started for class {name} {address} {port:d}".format(name=server_class, log.debug("Server started for class {name} {address} {port:d}".format(name=server_class,
address=address, address=address,
port=port)) port=port))
@ -145,11 +163,50 @@ class OpenLPServer(RegistryProperties):
"{loop:d} {running}".format(loop=loop, running=self.http_thread.isRunning())) "{loop:d} {running}".format(loop=loop, running=self.http_thread.isRunning()))
loop += 1 loop += 1
time.sleep(0.1) time.sleep(0.1)
except: except Exception as e:
log.error('Failed to start server ') log.error('Failed to start http server {why}'.format(why=e))
loop += 1 loop += 1
time.sleep(0.1) time.sleep(0.1)
def start_websocket_instance(self, address, port):
"""
Start the server
:param address: The server address
:param port: The run port
"""
loop = 1
while loop < 4:
try:
self.ws_server = websockets.serve(self.handle_websocket, address, '4318')
log.debug("Web Socket Server started for class {address} {port:d}".format(address=address, port=port))
break
except Exception as e:
log.error('Failed to start ws server {why}'.format(why=e))
loop += 1
time.sleep(0.1)
@staticmethod
async def handle_websocket(request, path):
"""
Handle web socket requests and return the poll information.
Check ever 0.5 seconds to get the latest postion and send if changed.
Only gets triggered when 1st client attaches
:param request: request from client
:param path: not used - future to register for a different end point
:return:
"""
log.debug("web socket handler registered with client")
print(path)
previous_poll = None
if path == '/poll':
while True:
current_poll = OpenLPPoll().poll()
if current_poll != previous_poll:
await request.send(current_poll)
previous_poll = current_poll
await asyncio.sleep(0.2)
def stop_server(self): def stop_server(self):
""" """
Stop the server Stop the server
@ -166,11 +223,20 @@ class HTTPSServer(HTTPServer):
Initialise the secure handlers for the SSL server if required.s Initialise the secure handlers for the SSL server if required.s
""" """
BaseServer.__init__(self, address, handler) BaseServer.__init__(self, address, handler)
local_data = AppLocation.get_directory(AppLocation.DataDir)
self.socket = ssl.SSLSocket( self.socket = ssl.SSLSocket(
sock=socket.socket(self.address_family, self.socket_type), sock=socket.socket(self.address_family, self.socket_type),
certfile=os.path.join(local_data, 'remotes', 'openlp.crt'), certfile=get_cert_file('crt'),
keyfile=os.path.join(local_data, 'remotes', 'openlp.key'), keyfile=get_cert_file('key'),
server_side=True) server_side=True)
self.server_bind() self.server_bind()
self.server_activate() self.server_activate()
def get_cert_file(file_type):
"""
Helper method to get certificate files
:param file_type: file suffix key, cert or pem
:return: full path to file
"""
local_data = AppLocation.get_directory(AppLocation.DataDir)
return os.path.join(local_data, 'remotes', 'openlp.{type}'.format(type=file_type))

View File

@ -0,0 +1,52 @@
# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
# Copyright (c) 2008-2016 OpenLP Developers #
# --------------------------------------------------------------------------- #
# This program is free software; you can redistribute it and/or modify it #
# under the terms of the GNU General Public License as published by the Free #
# Software Foundation; version 2 of the License. #
# #
# This program is distributed in the hope that it will be useful, but WITHOUT #
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
# more details. #
# #
# You should have received a copy of the GNU General Public License along #
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
import json
from openlp.core.common import RegistryProperties, Settings
class OpenLPPoll(RegistryProperties):
def __init__(self):
"""
Constructor for the poll builder class.
"""
super(OpenLPPoll, self).__init__()
def poll(self):
"""
Poll OpenLP to determine the current slide number and item name.
"""
result = {
'service': self.service_manager.service_id,
'slide': self.live_controller.selected_row or 0,
'item': self.live_controller.service_item.unique_identifier if self.live_controller.service_item else '',
'twelve': Settings().value('remotes/twelve hour'),
'blank': self.live_controller.blank_screen.isChecked(),
'theme': self.live_controller.theme_screen.isChecked(),
'display': self.live_controller.desktop_screen.isChecked(),
'version': 2,
'isSecure': Settings().value('remotes/authentication enabled'),
'isAuthorised': False
}
return json.dumps({'results': result}).encode()

View File

@ -24,6 +24,7 @@ import logging
from PyQt5 import QtWidgets from PyQt5 import QtWidgets
from openlp.core.common import OpenLPMixin
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, OpenLPServer from openlp.plugins.remotes.lib import RemoteTab, OpenLPServer
@ -42,7 +43,7 @@ __default_settings__ = {
} }
class RemotesPlugin(Plugin): class RemotesPlugin(Plugin, OpenLPMixin):
log.info('Remote Plugin loaded') log.info('Remote Plugin loaded')
def __init__(self): def __init__(self):
@ -59,9 +60,11 @@ class RemotesPlugin(Plugin):
""" """
Initialise the remotes plugin, and start the http server Initialise the remotes plugin, and start the http server
""" """
log.debug('initialise') log.debug('Initialise Remote Plugin')
super(RemotesPlugin, self).initialise() super(RemotesPlugin, self).initialise()
self.server = OpenLPServer() self.server = OpenLPServer()
self.server_ws = OpenLPServer(websocket=True)
self.server_secure = OpenLPServer(secure=True)
if not hasattr(self, 'remote_server_icon'): if not hasattr(self, 'remote_server_icon'):
self.remote_server_icon = QtWidgets.QLabel(self.main_window.status_bar) self.remote_server_icon = QtWidgets.QLabel(self.main_window.status_bar)
size_policy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) size_policy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed)
@ -87,6 +90,9 @@ class RemotesPlugin(Plugin):
if self.server: if self.server:
self.server.stop_server() self.server.stop_server()
self.server = None self.server = None
if self.server_secure:
self.server_secure.stop_server()
self.server_secure = None
@staticmethod @staticmethod
def about(): def about():

View File

@ -93,6 +93,7 @@ MODULES = [
'bs4', 'bs4',
'mako', 'mako',
'uno', 'uno',
'websockets',
'six' 'six'
] ]