feat(WebSocket): add logging for connection events and message handling
This commit is contained in:
parent
24ea5594b6
commit
e89a4b25c0
80
webdav3.py
80
webdav3.py
|
|
@ -51,6 +51,56 @@ _ws_clients: set = set() # connected WebSocket sockets
|
||||||
# Debug message ( True / False )
|
# Debug message ( True / False )
|
||||||
sys_debug = 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
|
# get localhost IP address
|
||||||
def get_localip():
|
def get_localip():
|
||||||
try:
|
try:
|
||||||
|
|
@ -704,6 +754,7 @@ class DAVRequestHandler(BaseHTTPRequestHandler):
|
||||||
return message # echo by default
|
return message # echo by default
|
||||||
|
|
||||||
def _ws_broadcast(self, payload: bytes):
|
def _ws_broadcast(self, payload: bytes):
|
||||||
|
# Thin wrapper kept for binary payloads; text goes through ws_broadcast().
|
||||||
with _ws_lock:
|
with _ws_lock:
|
||||||
clients = set(_ws_clients)
|
clients = set(_ws_clients)
|
||||||
for sock in clients:
|
for sock in clients:
|
||||||
|
|
@ -737,7 +788,7 @@ class DAVRequestHandler(BaseHTTPRequestHandler):
|
||||||
sock.settimeout(None)
|
sock.settimeout(None)
|
||||||
with _ws_lock:
|
with _ws_lock:
|
||||||
_ws_clients.add(sock)
|
_ws_clients.add(sock)
|
||||||
print(f'[WS] {client} connected (total: {len(_ws_clients)})')
|
_log('WS', f'{client} connected (clients: {len(_ws_clients)})')
|
||||||
try:
|
try:
|
||||||
while True:
|
while True:
|
||||||
b0, b1 = self._ws_recv(sock, 2)
|
b0, b1 = self._ws_recv(sock, 2)
|
||||||
|
|
@ -753,23 +804,23 @@ class DAVRequestHandler(BaseHTTPRequestHandler):
|
||||||
if masked:
|
if masked:
|
||||||
payload = bytes(b ^ mask[i % 4] for i, b in enumerate(payload))
|
payload = bytes(b ^ mask[i % 4] for i, b in enumerate(payload))
|
||||||
if opcode == 0x8: # close
|
if opcode == 0x8: # close
|
||||||
print(f'[WS] {client} closed')
|
_log('WS', f'{client} close frame')
|
||||||
sock.send(b'\x88\x00')
|
sock.send(b'\x88\x00')
|
||||||
break
|
break
|
||||||
elif opcode == 0x9: # ping → pong
|
elif opcode == 0x9: # ping → pong
|
||||||
sock.send(bytes([0x8A, len(payload)]) + payload)
|
sock.send(bytes([0x8A, len(payload)]) + payload)
|
||||||
elif opcode in (0x1, 0x2): # text or binary
|
elif opcode in (0x1, 0x2): # text or binary
|
||||||
message = payload.decode('utf-8', errors='replace')
|
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)
|
response = self.ws_process_message(message)
|
||||||
if response is not None:
|
if response is not None:
|
||||||
self._ws_broadcast(response.encode('utf-8'))
|
ws_broadcast(response)
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
finally:
|
finally:
|
||||||
with _ws_lock:
|
with _ws_lock:
|
||||||
_ws_clients.discard(sock)
|
_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):
|
def do_GET(self, onlyhead=False):
|
||||||
if (self.path == '/ws'
|
if (self.path == '/ws'
|
||||||
|
|
@ -902,9 +953,16 @@ class DAVRequestHandler(BaseHTTPRequestHandler):
|
||||||
break
|
break
|
||||||
return (path, elem)
|
return (path, elem)
|
||||||
|
|
||||||
# disable log info output to screen
|
def log_message(self, format, *args):
|
||||||
def log_message(self,format,*args):
|
# args: requestline, status_code, content_length (from log_request)
|
||||||
pass
|
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:
|
class BufWriter:
|
||||||
def __init__(self, w, debug=False):
|
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/
|
# **** Change First ./ to your dir , etc :/mnt/flash/public, d:/share_file/
|
||||||
root = DirCollection('files/', '/')
|
root = DirCollection('files/', '/')
|
||||||
httpd = DAVServer(server_address, DAVRequestHandler, root, userpwd)
|
httpd = DAVServer(server_address, DAVRequestHandler, root, userpwd)
|
||||||
|
repl = threading.Thread(target=_repl_thread, daemon=True, name='ws-repl')
|
||||||
|
repl.start()
|
||||||
try:
|
try:
|
||||||
httpd.serve_forever() # todo: add some control over starting and stopping the server
|
httpd.serve_forever()
|
||||||
except:
|
except:
|
||||||
print('# WebDav Server Stop.')
|
_log('SRV', 'stopped')
|
||||||
Loading…
Reference in New Issue
Block a user