forked from openlp/openlp
Added WebSocket engine
This commit is contained in:
parent
d2782e099f
commit
b58abe45b2
113
openlp/plugins/remotes/html/WebSocketEvents.js
Normal file
113
openlp/plugins/remotes/html/WebSocketEvents.js
Normal file
@ -0,0 +1,113 @@
|
||||
/******************************************************************************
|
||||
* OpenLP - Open Source Lyrics Projection *
|
||||
* --------------------------------------------------------------------------- *
|
||||
* Copyright (c) 2008-2014 Raoul Snyman *
|
||||
* Portions copyright (c) 2008-2014 Tim Bentley, Gerald Britton, Jonathan *
|
||||
* Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub, *
|
||||
* Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer. *
|
||||
* Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru, *
|
||||
* Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, *
|
||||
* Maikel Stuivenberg, Martin Thompson, Jon Tibble, Dave Warnock, *
|
||||
* Frode Woldsund, Martin Zibricky *
|
||||
* --------------------------------------------------------------------------- *
|
||||
* 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 *
|
||||
******************************************************************************/
|
||||
|
||||
/* Thanks to Ismael Celis for the original idea */
|
||||
|
||||
var wsEventEngine = function(url, polling_function, polling_interval)
|
||||
{
|
||||
this.polling_handle = null;
|
||||
this.polling_interval = polling_interval;
|
||||
this.polling_function = polling_function;
|
||||
this.retry_handle = null;
|
||||
this.callbacks = {};
|
||||
|
||||
this.fallback = function(){
|
||||
this.kill_polling();
|
||||
if(this.polling_function)
|
||||
this.polling_handle = window.setInterval(this.polling_function, this.polling_interval);
|
||||
this.kill_retries();
|
||||
var theEngine = this;
|
||||
this.retry_handle = window.setInterval(function(){theEngine.setup();}, 10000);
|
||||
}
|
||||
|
||||
this.kill_polling = function(){
|
||||
if(this.polling_handle)
|
||||
window.clearInterval(this.polling_handle);
|
||||
this.polling_handle = null;
|
||||
}
|
||||
|
||||
this.kill_retries = function(){
|
||||
if(this.retry_handle)
|
||||
window.clearInterval(this.retry_handle);
|
||||
}
|
||||
|
||||
this.bind = function(event_name, callback){
|
||||
this.callbacks[event_name] = this.callbacks[event_name] || [];
|
||||
this.callbacks[event_name].push(callback);
|
||||
return this;
|
||||
}
|
||||
|
||||
this.send = function(event_name, event_data){
|
||||
var payload = JSON.stringify({ event: event_name, data: event_data });
|
||||
this.websocket.send(payload);
|
||||
return this;
|
||||
}
|
||||
|
||||
this.dispatch = function(event_name, message){
|
||||
var chain = this.callbacks[event_name];
|
||||
if(typeof chain == 'undefined') return; // no callbacks
|
||||
for(var i = 0; i < chain.length; i++)
|
||||
chain[i](message);
|
||||
}
|
||||
|
||||
this.setup = function(){
|
||||
this.websocket = new WebSocket(url);
|
||||
this.websocket.engine = this;
|
||||
|
||||
this.websocket.onmessage = function(websocket_msg){
|
||||
if(this.engine.polling_function)
|
||||
this.engine.polling_function();
|
||||
if( websocket_msg.data.length > 0 ){
|
||||
try{
|
||||
var json = JSON.parse(websocket_msg.data);
|
||||
this.engine.dispatch(json.event, json.data);
|
||||
}
|
||||
catch(err){
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.websocket.onclose = function(){
|
||||
this.engine.dispatch('close', null);
|
||||
this.engine.fallback();
|
||||
}
|
||||
|
||||
this.websocket.onopen = function(){
|
||||
this.engine.dispatch('open', null);
|
||||
this.engine.kill_polling();
|
||||
this.engine.kill_retries();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if('WebSocket' in window){
|
||||
this.setup();
|
||||
}
|
||||
else{
|
||||
this.fallback();
|
||||
}
|
||||
|
||||
}
|
330
openlp/plugins/remotes/lib/websocket.py
Normal file
330
openlp/plugins/remotes/lib/websocket.py
Normal file
@ -0,0 +1,330 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
|
||||
|
||||
###############################################################################
|
||||
# OpenLP - Open Source Lyrics Projection #
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Copyright (c) 2008-2014 Raoul Snyman #
|
||||
# Portions copyright (c) 2008-2014 Tim Bentley, Gerald Britton, Jonathan #
|
||||
# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub, #
|
||||
# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer. #
|
||||
# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru, #
|
||||
# Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, #
|
||||
# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Dave Warnock, #
|
||||
# Frode Woldsund, Martin Zibricky, Patrick Zimmermann #
|
||||
# --------------------------------------------------------------------------- #
|
||||
# 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 #
|
||||
###############################################################################
|
||||
|
||||
"""
|
||||
Simple implementation of RFC 6455 for websocket protocol in a very simple and focused manner, just for the purposes
|
||||
of this application
|
||||
"""
|
||||
|
||||
import logging
|
||||
import re
|
||||
import socketserver
|
||||
import threading
|
||||
import time
|
||||
import socket
|
||||
import base64
|
||||
import uuid
|
||||
|
||||
from base64 import b64encode
|
||||
from hashlib import sha1
|
||||
|
||||
HOST, PORT = '', 8888
|
||||
WEB_SOCKETS_GUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'.encode('utf-8')
|
||||
WEB_SOCKETS_RESPONSE_TEMPLATE = (
|
||||
'HTTP/1.1 101 Switching Protocols',
|
||||
'Connection: Upgrade',
|
||||
'Sec-WebSocket-Accept: {key}',
|
||||
'Upgrade: websocket',
|
||||
'',
|
||||
'',
|
||||
)
|
||||
WEB_SOCKETS_HANDSHAKE_ERROR = 'Error: Handshake'.encode('utf-8')
|
||||
WEB_SOCKET_CLIENT_HEADERS = (
|
||||
"GET / HTTP/1.1",
|
||||
"Upgrade: websocket",
|
||||
"Connection: Upgrade",
|
||||
"Host: {host}:{port}",
|
||||
"Origin: null",
|
||||
"Sec-WebSocket-Key: {key}",
|
||||
"Sec-WebSocket-Version: 13",
|
||||
"",
|
||||
"",
|
||||
)
|
||||
|
||||
|
||||
class ThreadedWebSocketHandler(socketserver.BaseRequestHandler):
|
||||
"""
|
||||
ThreadedWebSocketHandler implements the upgrade handshake and continues to serve the socket
|
||||
"""
|
||||
def handle(self):
|
||||
"""
|
||||
Called once per connection, the connection will not be added to the list of clients
|
||||
until the handshake has succeeded
|
||||
"""
|
||||
has_upgraded = False
|
||||
data_buffer = ''
|
||||
while True:
|
||||
data_string = ''
|
||||
data_received = ''
|
||||
try:
|
||||
data_received = self.request.recv(1024)
|
||||
except Exception as e:
|
||||
#print(self.client_address, e.errno, e.strerror)
|
||||
if e.errno == 10053 or e.errno == 10054:
|
||||
self.server.remove_client(self)
|
||||
break
|
||||
if len(data_received) > 0:
|
||||
#print(" data_received: ", data_received)
|
||||
if has_upgraded:
|
||||
data_string = ThreadedWebSocketHandler.decode_websocket_message(data_received)
|
||||
else:
|
||||
data_string = data_received.decode('utf-8', 'ignore')
|
||||
if len(data_string) > 0:
|
||||
#print(" from: ", self.client_address, " data: ", data_string, " upgraded: ", has_upgraded)
|
||||
if not has_upgraded:
|
||||
data_buffer += data_string
|
||||
#print("x", data_buffer, "x")
|
||||
if data_buffer[0] != 'G':
|
||||
#print("return error")
|
||||
self.request.send(WEB_SOCKETS_HANDSHAKE_ERROR)
|
||||
break
|
||||
match = re.search('Sec-WebSocket-Key:\s+(.*?)[\n\r]+', data_buffer)
|
||||
#print("match: ", match)
|
||||
if match:
|
||||
received_key = (match.groups()[0].strip()).encode('utf-8')
|
||||
generated_key = sha1(received_key + WEB_SOCKETS_GUID).digest()
|
||||
response_key = b64encode(generated_key).decode('utf-8')
|
||||
response = ('\r\n'.join(WEB_SOCKETS_RESPONSE_TEMPLATE).format(key=response_key)).encode('utf-8')
|
||||
#print(response)
|
||||
self.request.send(response)
|
||||
has_upgraded = True
|
||||
data_buffer = ''
|
||||
self.server.add_client(self)
|
||||
|
||||
@staticmethod
|
||||
def decode_websocket_message(byte_array):
|
||||
"""
|
||||
decode_websocket_message decodes the messages sent from a websocket client according to RFC 6455
|
||||
:param byte_array: an array of bytes as received from the socket
|
||||
:return: returns a string
|
||||
"""
|
||||
data_length = byte_array[1] & 127
|
||||
index_first_mask = 2
|
||||
if data_length == 126:
|
||||
index_first_mask = 4
|
||||
elif data_length == 127:
|
||||
index_first_mask = 10
|
||||
masks = [m for m in byte_array[index_first_mask: index_first_mask + 4]]
|
||||
index_first_data_byte = index_first_mask + 4
|
||||
decoded_chars = []
|
||||
index = index_first_data_byte
|
||||
secondary_index = 0
|
||||
while index < len(byte_array):
|
||||
char = chr(byte_array[index] ^ masks[secondary_index % 4])
|
||||
#print(char)
|
||||
decoded_chars.append(char)
|
||||
index += 1
|
||||
secondary_index += 1
|
||||
return ''.join(decoded_chars)
|
||||
|
||||
@staticmethod
|
||||
def encode_websocket_message(message):
|
||||
"""
|
||||
encode_websocket_message encodes a message prior to sending to a websocket client according to RFC 6455
|
||||
:param message: string to be encoded
|
||||
:return: the message encoded into a byte array
|
||||
"""
|
||||
frame_head = bytearray(2)
|
||||
frame_head[0] = ThreadedWebSocketHandler.set_bit(frame_head[0], 7)
|
||||
frame_head[0] = ThreadedWebSocketHandler.set_bit(frame_head[0], 0)
|
||||
assert(len(message) < 126)
|
||||
frame_head[1] = len(message)
|
||||
frame = frame_head + message.encode('utf-8')
|
||||
return frame
|
||||
|
||||
@staticmethod
|
||||
def decode_client_websocket_message(received_broadcast):
|
||||
"""
|
||||
Helper to decode messages from the client side for testing purposes
|
||||
:param received_broadcast: the byte array received from the server
|
||||
:return: a decoded string
|
||||
"""
|
||||
decoded_broadcast = ''
|
||||
if received_broadcast[0] == 129:
|
||||
for c in received_broadcast[2:]:
|
||||
decoded_broadcast += chr(c)
|
||||
return decoded_broadcast
|
||||
|
||||
|
||||
@staticmethod
|
||||
def set_bit(int_type, offset):
|
||||
"""
|
||||
set_bit -- helper for bit operation
|
||||
:param int_type: the original value
|
||||
:param offset: which bit to set
|
||||
:return: the modified value
|
||||
"""
|
||||
return int_type | (1 << offset)
|
||||
|
||||
def finish(self):
|
||||
"""
|
||||
finish is called when the connection is done
|
||||
"""
|
||||
#print("finish:", self.client_address)
|
||||
# with self.server.lock:
|
||||
# self.server.remove_client(self)
|
||||
pass
|
||||
|
||||
|
||||
class ThreadedWebSocketServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
|
||||
"""
|
||||
ThreadedWebSocketServer overrides the standard implementation to add a client list
|
||||
"""
|
||||
daemon_threads = True
|
||||
allow_reuse_address = True
|
||||
|
||||
def __init__(self, host_port, handler):
|
||||
super().__init__(host_port, handler)
|
||||
self.clients = {}
|
||||
self.lock = threading.Lock()
|
||||
|
||||
def add_client(self, client):
|
||||
"""
|
||||
add_client inserts a reference to the client handler object into the server's list of clients
|
||||
:param client: reference to the client handler
|
||||
"""
|
||||
with self.lock:
|
||||
self.clients[client.client_address] = client
|
||||
#print("added: ", client.client_address)
|
||||
#print(self.clients.keys())
|
||||
|
||||
def remove_client(self, client):
|
||||
"""
|
||||
remove_client is called by the client handler when the client disconnects
|
||||
:param client: reference to the client handler
|
||||
"""
|
||||
with self.lock:
|
||||
if client.client_address in self.clients.keys():
|
||||
self.clients.pop(client.client_address)
|
||||
#print("removed: ", client.client_address)
|
||||
|
||||
def send_to_all_clients(self, msg):
|
||||
"""
|
||||
send_to_all_clients sends the same message to all the connected clients
|
||||
:param msg: string to be sent to all connected clients
|
||||
"""
|
||||
#print('send_to_all_clients')
|
||||
#print(self.clients.keys())
|
||||
with self.lock:
|
||||
for client in self.clients.values():
|
||||
#print("send_to:", client.client_address)
|
||||
client.request.send(ThreadedWebSocketHandler.encode_websocket_message(msg))
|
||||
|
||||
|
||||
class WebSocketManager():
|
||||
"""
|
||||
WebSocketManager implements the external interface to the WebSocket engine
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.server = None
|
||||
self.server_thread = None
|
||||
|
||||
def start(self):
|
||||
"""
|
||||
start
|
||||
starts the WebSocket engine
|
||||
"""
|
||||
self.server = ThreadedWebSocketServer((HOST, PORT), ThreadedWebSocketHandler)
|
||||
self.server_thread = socketserver.threading.Thread(target=self.server.serve_forever)
|
||||
self.server_thread.start()
|
||||
#print("started the WebSocket server")
|
||||
|
||||
def stop(self):
|
||||
"""
|
||||
stop
|
||||
stops the WebSocket engine
|
||||
"""
|
||||
self.server.shutdown()
|
||||
self.server.server_close()
|
||||
#print("stopped the WebSocket server")
|
||||
|
||||
def send(self, msg):
|
||||
"""
|
||||
sends a message to all clients via the websocket server
|
||||
:param msg: string to send
|
||||
"""
|
||||
#print(self.server.clients.keys())
|
||||
self.server.send_to_all_clients(msg)
|
||||
|
||||
if __name__ == "__main__":
|
||||
# The following code is helpful to test the server using a browser
|
||||
# Just paste the following code into an html file
|
||||
#<html>
|
||||
#<head>
|
||||
#<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
|
||||
#</head>
|
||||
#<body>
|
||||
# <div id="results">start:</div>
|
||||
#</body>
|
||||
#<script type="text/javascript">
|
||||
# appendMessage("testing...");
|
||||
# var ws = new WebSocket('ws://localhost:8888/Pres')
|
||||
# ws.onmessage = function(e){
|
||||
# appendMessage(e.data)
|
||||
# }
|
||||
# ws.onopen = function(){
|
||||
# appendMessage("open");
|
||||
# this.send("test send");
|
||||
# }
|
||||
# ws.onclose = function(){
|
||||
# appendMessage("closed");
|
||||
# }
|
||||
# function appendMessage(str)
|
||||
# {
|
||||
# $("#results").html($("#results").html() + "<br />" + str);
|
||||
# }
|
||||
#</script>
|
||||
#</html>
|
||||
|
||||
manager = WebSocketManager()
|
||||
manager.start()
|
||||
# Create a socket (SOCK_STREAM means a TCP socket)
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
# Fake a handshake
|
||||
uid = uuid.uuid4()
|
||||
key = base64.encodebytes(uid.bytes).strip()
|
||||
data = ('\r\n'.join(WEB_SOCKET_CLIENT_HEADERS).format(host='localhost', port='8888', key=key)).encode('utf-8')
|
||||
received = None
|
||||
try:
|
||||
# Connect to server and send data
|
||||
sock.connect(('localhost', PORT))
|
||||
sock.send(data)
|
||||
received = sock.recv(1024)
|
||||
time.sleep(5)
|
||||
manager.send("broadcast")
|
||||
print("received: ", ThreadedWebSocketHandler.decode_client_websocket_message(sock.recv(1024)))
|
||||
time.sleep(2)
|
||||
manager.send("\r\njust before kill")
|
||||
print("received: ", ThreadedWebSocketHandler.decode_client_websocket_message(sock.recv(1024)))
|
||||
time.sleep(2)
|
||||
finally:
|
||||
sock.close()
|
||||
manager.stop()
|
||||
|
123
tests/functional/openlp_plugins/remotes/test_websocket.py
Normal file
123
tests/functional/openlp_plugins/remotes/test_websocket.py
Normal file
@ -0,0 +1,123 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
|
||||
|
||||
###############################################################################
|
||||
# OpenLP - Open Source Lyrics Projection #
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Copyright (c) 2008-2014 Raoul Snyman #
|
||||
# Portions copyright (c) 2008-2014 Tim Bentley, Gerald Britton, Jonathan #
|
||||
# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub, #
|
||||
# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer. #
|
||||
# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru, #
|
||||
# Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, #
|
||||
# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Dave Warnock, #
|
||||
# Frode Woldsund, Martin Zibricky, Patrick Zimmermann #
|
||||
# --------------------------------------------------------------------------- #
|
||||
# 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 #
|
||||
###############################################################################
|
||||
"""
|
||||
This module contains tests for WebSockets
|
||||
"""
|
||||
import base64
|
||||
import uuid
|
||||
import socket
|
||||
import time
|
||||
from unittest import TestCase
|
||||
|
||||
from openlp.plugins.remotes.lib.websocket import WebSocketManager, ThreadedWebSocketHandler, \
|
||||
WEB_SOCKET_CLIENT_HEADERS
|
||||
from tests.functional import MagicMock, patch, mock_open
|
||||
|
||||
|
||||
class TestWebSockets(TestCase):
|
||||
"""
|
||||
Test the functions in the :mod:`lib` module.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
"""
|
||||
Setup the WebSocketsManager
|
||||
"""
|
||||
self.manager = WebSocketManager()
|
||||
self.manager.start()
|
||||
|
||||
def tearDown(self):
|
||||
self.manager.stop()
|
||||
|
||||
def attempt_to_talk_with_no_handshake_test(self):
|
||||
"""
|
||||
Test the websocket without handshaking first
|
||||
"""
|
||||
# GIVEN: A default configuration
|
||||
|
||||
# WHEN: attempts to talk without upgrading to websocket
|
||||
# Create a socket (SOCK_STREAM means a TCP socket)
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
data = bytes('No upgrade', 'utf-8')
|
||||
received = None
|
||||
try:
|
||||
# Connect to server and send data
|
||||
sock.connect(('localhost', 8888))
|
||||
sock.send(data)
|
||||
# Receive data from the server and shut down
|
||||
received = sock.recv(1024)
|
||||
finally:
|
||||
sock.close()
|
||||
|
||||
# THEN:
|
||||
self.assertIs(isinstance(self.manager, WebSocketManager), True,
|
||||
'It should be an object of WebSocketsManager type')
|
||||
self.assertRegexpMatches(received.decode('utf-8'), '.*Error:.*', 'Mismatch')
|
||||
|
||||
def handshake_and_talk_test(self):
|
||||
"""
|
||||
Test the websocket handshake
|
||||
"""
|
||||
# GIVEN: A default configuration
|
||||
|
||||
# WHEN: upgrade to websocket and then talk
|
||||
print("starting the websocket server")
|
||||
print("started")
|
||||
# Create a socket (SOCK_STREAM means a TCP socket)
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
# Fake a handshake
|
||||
uid = uuid.uuid4()
|
||||
key = base64.encodebytes(uid.bytes).strip()
|
||||
data = bytes('\r\n'.join(WEB_SOCKET_CLIENT_HEADERS).format(host='localhost', port='8888', key=key), 'utf-8')
|
||||
received = None
|
||||
try:
|
||||
# Connect to server and send data
|
||||
sock.connect(('localhost', 8888))
|
||||
print("connected")
|
||||
sock.send(data)
|
||||
#print("data sent: ", data.decode('utf-8'))
|
||||
# Receive data from the server and shut down
|
||||
time.sleep(1)
|
||||
received = sock.recv(1024)
|
||||
print("data received: ", received.decode('utf-8'))
|
||||
time.sleep(1)
|
||||
self.manager.send('broadcast')
|
||||
time.sleep(1)
|
||||
received_broadcast = sock.recv(1024)
|
||||
print(received_broadcast)
|
||||
decoded_broadcast = ThreadedWebSocketHandler.decode_client_websocket_message(received_broadcast)
|
||||
finally:
|
||||
time.sleep(1)
|
||||
sock.close()
|
||||
|
||||
# THEN:
|
||||
self.assertIs(isinstance(self.manager, WebSocketManager), True,
|
||||
'It should be an object of WebSocketsManager type')
|
||||
self.assertRegexpMatches(received.decode('utf-8'), '.*Upgrade: websocket.*', 'Handshake failed')
|
||||
self.assertRegexpMatches(decoded_broadcast, '.*broadcast', 'WebSocket did not receive correct string')
|
Loading…
Reference in New Issue
Block a user