diff --git a/doc/protocol.md b/doc/protocol.md new file mode 100644 index 0000000..fa48057 --- /dev/null +++ b/doc/protocol.md @@ -0,0 +1,49 @@ +InstantChat WebSocket Protocol +============================== + +Messages from client to server (commands) +----------------------------------------- + +JSON objects. Key `action` determines type of message. + +### init +Initialize the connection, set nickname and join/create a chat. + +**Parameters:** +* `action` = `"init"` +* `chat_id`: either an existing ID of a chat instance or *[TODO: ?]* empty string to + create a new chat +* `nickname`: user's chosen nickname *[TODO: valid characters?]* + +**Example:** + + { + "action": "init", + "chat_id": "42", + "nickname": "Alice" + } + +### send (?) +Send a message... + +*[TODO]* + + +Messages from server to client (responses/events) +------------------------------------------------- + +JSON objects. Key `type` determines type of message. + +### init +Response to `init` command. Confirms initialization and chat join. + +**Parameters:** +* `type` = `"init"` +* ***[TODO]*** + +**Example:** + + { + "type": "init", + TODO ??? + } diff --git a/server/chatserver.py b/server/chatserver.py index 54891b9..aee63b6 100755 --- a/server/chatserver.py +++ b/server/chatserver.py @@ -1,33 +1,59 @@ #!/usr/bin/env python -# Example code from: -# https://websockets.readthedocs.io/en/stable/intro.html - import asyncio +import json import websockets +def log_message_error(error, message_text): + print(f"[E] {error}") + print(f" Message: {message_text}") + + async def parse_client_message(websocket, message_text): """Parse a message (JSON object) from a client.""" - # TODO parse JSON - response = f"Hello {message_text}!" + try: + # Parse JSON + message = json.loads(message_text) - await websocket.send(response) + # Handle message types + if message['action'] == 'init': + await handle_client_init(websocket, message) + else: + log_message_error(f"Unknown action '{message['action']}'", message_text) + except json.decoder.JSONDecodeError as e: + log_message_error(f"JSON decode error: {e}", message_text) + except KeyError as e: + log_message_error(f"Missing key {e} in JSON message", message_text) + + +async def handle_client_init(websocket, message): + """Handle client 'init' message.""" + + print(f"< init: chat_id='{message['chat_id']}', nickname='{message['nickname']}'") + + # Send response to client + response = json.dumps({ + 'type': 'init' + }) print(f"> {response}") + await websocket.send(response) async def client_handler(websocket, path): """Handle client connection.""" - print(f"++ New client") + print(f"++ New client {websocket.remote_address}") try: # Read and parse messages until connection is closed async for message_text in websocket: - print(f"< {message_text}") await parse_client_message(websocket, message_text) + except websockets.exceptions.ConnectionClosed: + # Ignore ConnectionClosed exceptions because we handle this in finally + pass finally: - print(f"-- Client connection closed") + print(f"-- Client connection closed {websocket.remote_address}") # Create WebSocket listener start_server = websockets.serve(client_handler, '0.0.0.0', 32715)