-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Websockets handler and functional test (#8)
* Rename `WebSocketMessageHandler` to `WebSocketMessagesHandler` * WebsocketsHandler and functional tests
- Loading branch information
Showing
15 changed files
with
393 additions
and
31 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
import asyncio | ||
import signal | ||
|
||
import websockets | ||
|
||
from app import conf | ||
from handlers.websockets_handler import WebSocketsHandler | ||
from storage.subscription_storage import SubscriptionStorage | ||
|
||
|
||
def create_stop_signal() -> asyncio.Future[None]: | ||
loop = asyncio.get_running_loop() | ||
stop_signal = loop.create_future() | ||
loop.add_signal_handler(signal.SIGTERM, stop_signal.set_result, None) | ||
return stop_signal | ||
|
||
|
||
async def app_runner(settings: conf.Settings, websockets_handler: WebSocketsHandler) -> None: | ||
async with websockets.serve( | ||
ws_handler=websockets_handler.websockets_handler, | ||
host=settings.WEBSOCKETS_HOST, | ||
port=settings.WEBSOCKETS_PORT, | ||
): | ||
await asyncio.Future() | ||
|
||
|
||
async def main() -> None: | ||
settings = conf.get_app_settings() | ||
storage = SubscriptionStorage() | ||
websockets_handler = WebSocketsHandler(storage=storage) | ||
|
||
await app_runner( | ||
settings=settings, | ||
websockets_handler=websockets_handler, | ||
) | ||
|
||
|
||
if __name__ == "__main__": | ||
asyncio.run(main()) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,5 @@ | ||
from handlers.message_handler import WebSocketMessageHandler | ||
from handlers.websockets_handler import WebSocketsHandler | ||
|
||
__all__ = [ | ||
"WebSocketMessageHandler", | ||
"WebSocketsHandler", | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
12 changes: 6 additions & 6 deletions
12
...e_handler/tests_message_handler_common.py → ...s_handler/tests_message_handler_common.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
4 changes: 2 additions & 2 deletions
4
...andler/tests_subscirbe_message_handler.py → ...andler/tests_subscirbe_message_handler.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
4 changes: 2 additions & 2 deletions
4
...dler/tests_unsubscirbe_message_handler.py → ...dler/tests_unsubscirbe_message_handler.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
from dataclasses import dataclass | ||
import logging | ||
from typing import Annotated | ||
|
||
import pydantic | ||
from pydantic import Field | ||
from pydantic import TypeAdapter | ||
from websockets import WebSocketServerProtocol | ||
from websockets.exceptions import ConnectionClosedError | ||
|
||
from app import conf | ||
from handlers.dto import AuthMessage | ||
from handlers.dto import ErrorResponseMessage | ||
from handlers.dto import IncomingMessage | ||
from handlers.dto import SuccessResponseMessage | ||
from handlers.exceptions import WebsocketMessageException | ||
from storage.storage_updaters import StorageWebSocketRemover | ||
from storage.subscription_storage import SubscriptionStorage | ||
from handlers.messages_handler import WebSocketMessagesHandler | ||
|
||
logger = logging.getLogger(__name__) | ||
|
||
IncomingMessageAdapter = TypeAdapter(Annotated[IncomingMessage, Field(discriminator="message_type")]) | ||
AuthMessageAdapter = TypeAdapter(Annotated[AuthMessage, Field(discriminator="message_type")]) | ||
|
||
|
||
@dataclass | ||
class WebSocketsHandler: | ||
storage: SubscriptionStorage | ||
|
||
def __post_init__(self) -> None: | ||
settings = conf.get_app_settings() | ||
self.websockets_path = settings.WEBSOCKETS_PATH | ||
|
||
self.messages_handler = WebSocketMessagesHandler(storage=self.storage) | ||
|
||
async def websockets_handler(self, websocket: WebSocketServerProtocol) -> None: | ||
if websocket.path != self.websockets_path: | ||
return | ||
|
||
try: | ||
async for message in websocket: | ||
response_message = await self.process_message(websocket=websocket, raw_message=message) | ||
await websocket.send(response_message.model_dump_json(exclude_none=True)) | ||
except ConnectionClosedError: | ||
logger.warning("Trying to send message to closed connection. Connection id: '%s'", websocket.id) | ||
finally: | ||
StorageWebSocketRemover(storage=self.storage, websocket=websocket)() | ||
|
||
async def process_message(self, websocket: WebSocketServerProtocol, raw_message: str | bytes) -> SuccessResponseMessage | ErrorResponseMessage: | ||
try: | ||
message = self.parse_raw_message(websocket, raw_message) | ||
except pydantic.ValidationError as exc: | ||
return ErrorResponseMessage.model_construct(errors=exc.errors(include_url=False, include_context=False), incoming_message=None) | ||
|
||
try: | ||
success_response = await self.messages_handler.handle_message(websocket, message) | ||
except WebsocketMessageException as exc: | ||
return exc.as_error_message() | ||
|
||
return success_response | ||
|
||
def parse_raw_message(self, websocket: WebSocketServerProtocol, raw_message: str | bytes) -> IncomingMessage: | ||
adapter = self.get_message_adapter(websocket) | ||
return adapter.validate_json(raw_message) | ||
|
||
def get_message_adapter(self, websocket: WebSocketServerProtocol) -> TypeAdapter: | ||
"""Only registered websockets can send all messages. Unregistered websockets can only send Auth messages.""" | ||
if self.storage.is_websocket_registered(websocket): | ||
return IncomingMessageAdapter | ||
|
||
return AuthMessageAdapter |
Oops, something went wrong.