-
-
Save thalesmaoa/2b2d87fbd132dbf5bb8b to your computer and use it in GitHub Desktop.
Minimal WebSocket Broadcast Server in Python
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html> | |
<head> | |
<title>Websocket Demo</title> | |
<style> | |
#messages { | |
border: dotted 1px #444444; | |
font: 12px arial,sans-serif; | |
} | |
#messages > p { | |
padding: 0px; | |
margin: 0px; | |
} | |
</style> | |
<script> | |
var messages; | |
var form; | |
var inputBox; | |
function log_msg(msg) { | |
var p = document.createElement("p"); | |
p.innerHTML = msg; | |
messages.appendChild(p); | |
} | |
function doInit() { | |
inputBox = document.getElementById("message"); | |
messages = document.getElementById("messages"); | |
form = document.getElementById("form"); | |
var s; | |
try { | |
var host = "ws://localhost:4545/"; | |
if(window.location.hostname) { | |
host = "ws://" + window.location.hostname + ":4545/"; | |
} | |
s = new WebSocket(host); | |
s.onopen = function (e) { log_msg("connected..."); }; | |
s.onclose = function (e) { log_msg("connection closed."); }; | |
s.onerror = function (e) { log_msg("connection error."); }; | |
s.onmessage = function (e) { log_msg("message: " + e.data); }; | |
} catch (ex) { | |
log_msg("connection exception:" + ex); | |
} | |
form.addEventListener("submit", function (e) { | |
e.preventDefault(); | |
s.send(inputBox.value); | |
inputBox.value = ""; | |
}, false); | |
} | |
</script> | |
</head> | |
<body onload="doInit()"> | |
<form id="form"> | |
<input type="text" id="message"> | |
<button type="submit">Send</button> | |
</form> | |
<br/> | |
<div id="messages"></div> | |
</body> | |
</html> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python | |
import socket, hashlib, base64, threading | |
class PyWSock: | |
MAGIC = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11' | |
HSHAKE_RESP = "HTTP/1.1 101 Switching Protocols\r\n" + \ | |
"Upgrade: websocket\r\n" + \ | |
"Connection: Upgrade\r\n" + \ | |
"Sec-WebSocket-Accept: %s\r\n" + \ | |
"\r\n" | |
LOCK = threading.Lock() | |
clients = [] | |
def recv_data (self, client): | |
# as a simple server, we expect to receive: | |
# - all data at one go and one frame | |
# - one frame at a time | |
# - text protocol | |
# - no ping pong messages | |
data = bytearray(client.recv(512)) | |
if(len(data) < 6): | |
raise Exception("Error reading data") | |
# FIN bit must be set to indicate end of frame | |
assert(0x1 == (0xFF & data[0]) >> 7) | |
# data must be a text frame | |
# 0x8 (close connection) is handled with assertion failure | |
assert(0x1 == (0xF & data[0])) | |
# assert that data is masked | |
assert(0x1 == (0xFF & data[1]) >> 7) | |
datalen = (0x7F & data[1]) | |
#print("received data len %d" %(datalen,)) | |
str_data = '' | |
if(datalen > 0): | |
mask_key = data[2:6] | |
masked_data = data[6:(6+datalen)] | |
unmasked_data = [masked_data[i] ^ mask_key[i%4] for i in range(len(masked_data))] | |
str_data = str(bytearray(unmasked_data)) | |
return str_data | |
def broadcast_resp(self, data): | |
# 1st byte: fin bit set. text frame bits set. | |
# 2nd byte: no mask. length set in 1 byte. | |
resp = bytearray([0b10000001, len(data)]) | |
# append the data bytes | |
for d in bytearray(data): | |
resp.append(d) | |
self.LOCK.acquire() | |
for client in self.clients: | |
try: | |
client.send(resp) | |
except: | |
print("error sending to a client") | |
self.LOCK.release() | |
def parse_headers (self, data): | |
headers = {} | |
lines = data.splitlines() | |
for l in lines: | |
parts = l.split(": ", 1) | |
if len(parts) == 2: | |
headers[parts[0]] = parts[1] | |
headers['code'] = lines[len(lines) - 1] | |
return headers | |
def handshake (self, client): | |
print('Handshaking...') | |
data = client.recv(2048) | |
headers = self.parse_headers(data) | |
print('Got headers:') | |
for k, v in headers.iteritems(): | |
print k, ':', v | |
key = headers['Sec-WebSocket-Key'] | |
resp_data = self.HSHAKE_RESP % ((base64.b64encode(hashlib.sha1(key+self.MAGIC).digest()),)) | |
print('Response: [%s]' % (resp_data,)) | |
return client.send(resp_data) | |
def handle_client (self, client, addr): | |
self.handshake(client) | |
try: | |
while 1: | |
data = self.recv_data(client) | |
print("received [%s]" % (data,)) | |
self.broadcast_resp(data) | |
except Exception as e: | |
print("Exception %s" % (str(e))) | |
print('Client closed: ' + str(addr)) | |
self.LOCK.acquire() | |
self.clients.remove(client) | |
self.LOCK.release() | |
client.close() | |
def start_server (self, port): | |
s = socket.socket() | |
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) | |
s.bind(('', port)) | |
s.listen(5) | |
while(1): | |
print ('Waiting for connection...') | |
conn, addr = s.accept() | |
print ('Connection from: ' + str(addr)) | |
threading.Thread(target = self.handle_client, args = (conn, addr)).start() | |
self.LOCK.acquire() | |
self.clients.append(conn) | |
self.LOCK.release() | |
ws = PyWSock() | |
ws.start_server(4545) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment