feat(WebSocket): add logging for connection events and message handling

This commit is contained in:
Jaime Idolpx 2026-06-11 17:49:12 -04:00
parent 24ea5594b6
commit e89a4b25c0

View File

@ -51,6 +51,56 @@ _ws_clients: set = set() # connected WebSocket sockets
# Debug message ( True / False )
sys_debug = False
# ── Logging ────────────────────────────────────────────────────────────────────
def _log(tag: str, msg: str):
print(f'[{strftime("%H:%M:%S")}] [{tag}] {msg}', flush=True)
# ── WebSocket broadcast (module-level so the REPL can call it) ─────────────────
def ws_broadcast(text: str) -> int:
"""Send a UTF-8 text frame to every connected WebSocket client."""
payload = text.encode('utf-8')
n = len(payload)
if n < 126:
header = bytes([0x81, n])
elif n < 65536:
header = bytes([0x81, 126]) + struct.pack('>H', n)
else:
header = bytes([0x81, 127]) + struct.pack('>Q', n)
frame = header + payload
with _ws_lock:
clients = set(_ws_clients)
sent = 0
for sock in clients:
try:
sock.send(frame)
sent += 1
except Exception:
with _ws_lock:
_ws_clients.discard(sock)
return sent
# ── REPL ───────────────────────────────────────────────────────────────────────
def _repl_thread():
"""Read lines from stdin and broadcast them to all connected WS clients."""
_log('REPL', 'ready — type a message and press Enter to broadcast, Ctrl-D to quit')
while True:
try:
sys.stdout.write('> ')
sys.stdout.flush()
line = sys.stdin.readline()
if not line: # EOF / Ctrl-D
break
line = line.rstrip('\r\n')
if not line:
continue
n = ws_broadcast(line)
_log('REPL', f'sent to {n} client(s): {line}')
except (EOFError, KeyboardInterrupt):
break
# get localhost IP address
def get_localip():
try:
@ -704,6 +754,7 @@ class DAVRequestHandler(BaseHTTPRequestHandler):
return message # echo by default
def _ws_broadcast(self, payload: bytes):
# Thin wrapper kept for binary payloads; text goes through ws_broadcast().
with _ws_lock:
clients = set(_ws_clients)
for sock in clients:
@ -737,7 +788,7 @@ class DAVRequestHandler(BaseHTTPRequestHandler):
sock.settimeout(None)
with _ws_lock:
_ws_clients.add(sock)
print(f'[WS] {client} connected (total: {len(_ws_clients)})')
_log('WS', f'{client} connected (clients: {len(_ws_clients)})')
try:
while True:
b0, b1 = self._ws_recv(sock, 2)
@ -753,23 +804,23 @@ class DAVRequestHandler(BaseHTTPRequestHandler):
if masked:
payload = bytes(b ^ mask[i % 4] for i, b in enumerate(payload))
if opcode == 0x8: # close
print(f'[WS] {client} closed')
_log('WS', f'{client} close frame')
sock.send(b'\x88\x00')
break
elif opcode == 0x9: # ping → pong
sock.send(bytes([0x8A, len(payload)]) + payload)
elif opcode in (0x1, 0x2): # text or binary
message = payload.decode('utf-8', errors='replace')
print(f'[WS] {client} recv ({length}b): {message}')
_log('WS', f'{client} recv: {message}')
response = self.ws_process_message(message)
if response is not None:
self._ws_broadcast(response.encode('utf-8'))
ws_broadcast(response)
except Exception:
pass
finally:
with _ws_lock:
_ws_clients.discard(sock)
print(f'[WS] {client} disconnected (total: {len(_ws_clients)})')
_log('WS', f'{client} disconnected (clients: {len(_ws_clients)})')
def do_GET(self, onlyhead=False):
if (self.path == '/ws'
@ -902,9 +953,16 @@ class DAVRequestHandler(BaseHTTPRequestHandler):
break
return (path, elem)
# disable log info output to screen
def log_message(self, format, *args):
pass
# args: requestline, status_code, content_length (from log_request)
try:
parts = args[0].split() # e.g. ['GET', '/path', 'HTTP/1.1']
method = parts[0]
path = urllib.parse.unquote(parts[1]) if len(parts) > 1 else '?'
status = args[1]
_log('DAV', f'{method} {path}{status}')
except Exception:
_log('DAV', format % args) # fallback for unexpected call shapes
class BufWriter:
def __init__(self, w, debug=False):
@ -964,7 +1022,9 @@ if __name__ == '__main__':
# **** Change First ./ to your dir , etc :/mnt/flash/public, d:/share_file/
root = DirCollection('files/', '/')
httpd = DAVServer(server_address, DAVRequestHandler, root, userpwd)
repl = threading.Thread(target=_repl_thread, daemon=True, name='ws-repl')
repl.start()
try:
httpd.serve_forever() # todo: add some control over starting and stopping the server
httpd.serve_forever()
except:
print('# WebDav Server Stop.')
_log('SRV', 'stopped')