Skip to content

Instantly share code, notes, and snippets.

@CardealRusso
Last active July 16, 2024 14:18
Show Gist options
  • Save CardealRusso/b651a081a5735d58025670ea8f2f4159 to your computer and use it in GitHub Desktop.
Save CardealRusso/b651a081a5735d58025670ea8f2f4159 to your computer and use it in GitHub Desktop.
simple micropython unsafe (ws) multithreaded websocket server
import binascii, socket, _thread, hashlib
class WebSocketServer:
def __init__(self, host='0.0.0.0', port=8443, on_connect=None, on_disconnect=None, on_message=None):
self.host = host
self.port = port
self.clients = []
self.on_connect = on_connect
self.on_disconnect = on_disconnect
self.on_message = on_message
def generate_accept(self, key):
return binascii.b2a_base64(hashlib.sha1(key + b'258EAFA5-E914-47DA-95CA-C5AB0DC85B11').digest()).strip()
def decode_frame(self, frame):
length = frame[1] & 127
mask = frame[2:6] if length < 126 else frame[4:8]
data = frame[6:] if length < 126 else frame[8:]
return ''.join(chr(b ^ mask[i % 4]) for i, b in enumerate(data))
def encode_frame(self, msg):
msg_bytes = msg.encode('utf-8')
length = len(msg_bytes)
frame = bytearray([129])
if length <= 125:
frame.append(length)
elif length <= 65535:
frame.extend([126, (length >> 8) & 255, length & 255])
else:
frame.extend([127, 0, 0, 0, 0, 0, (length >> 24) & 255, (length >> 16) & 255, (length >> 8) & 255, length & 255])
frame.extend(msg_bytes)
return frame
def close(self, client_s):
self.clients.remove(client_s)
client_s.close()
#Perhaps it would be appropriate to pick up the client thread and close it? I don't know if that's necessary, maybe it does it by itself.
def handle_client(self, client_s, addr):
ip_addr = '.'.join(map(str, addr[4:8]))
#client_s will spit out errors if the client is connecting through a secure connection.
method, path, protocol = client_s.readline().decode().strip().split()
headers = {}
while True:
line = client_s.readline().decode().strip()
if not line:
break
if ": " in line:
key, value = line.split(": ", 1)
headers[key] = value
accept = self.generate_accept(headers["Sec-WebSocket-Key"].encode('utf-8'))
client_s.send(f'HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: {accept.decode()}\r\n\r\n'.encode())
self.clients.append(client_s)
if self.on_connect:self.on_connect(client_s, ip_addr)
try:
while True:
#This will mess current and subsequent messages if the current one exceeds the limit of 1024
data = client_s.recv(1024)
if not data:break
if self.on_message:self.on_message(client_s, ip_addr, self.decode_frame(data))
finally:
self.close(client_s)
if self.on_disconnect:self.on_disconnect(client_s, ip_addr)
def start(self):
s = socket.socket()
ai = socket.getaddrinfo(self.host, self.port)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind(ai[0][-1])
s.listen(0)
while True:_thread.start_new_thread(self.handle_client, (s.accept()))
from unws import WebSocketServer
def on_connect(client, ip):
print(f'New client: {ip}')
def on_disconnect(client, ip):
print(f'{ip} left')
def on_message(client, ip, message):
print(f'{ip} : {message}')
if message == "Send this to everyone":
for clientp in ws.clients:
clientp.send(ws.encode_frame("this"))
if message == "bye":
client.send(ws.encode_frame("Goodbye, despite the fact that I don't know if you'll get this message before disconnect.")
ws.close(client)
ws = WebSocketServer(host="0.0.0.0", port=8080, on_connect=on_connect, on_disconnect=on_disconnect, on_message=on_message)
ws.start()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment