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 )
|
||||
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
|
||||
def log_message(self, format, *args):
|
||||
# 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')
|
||||
Loading…
Reference in New Issue
Block a user