platypush/platypush/backend/http/ws.py

76 lines
2.1 KiB
Python

from logging import getLogger
from threading import Thread
from typing_extensions import override
from redis import ConnectionError
from tornado.ioloop import IOLoop
from tornado.websocket import WebSocketHandler
from platypush.config import Config
from platypush.message.event import Event
from platypush.utils import get_redis
events_redis_topic = f'_platypush/{Config.get("device_id")}/events' # type: ignore
logger = getLogger(__name__)
class WSEventProxy(WebSocketHandler, Thread):
"""
Websocket event proxy mapped to ``/ws/events``.
"""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._sub = get_redis().pubsub()
self._io_loop = IOLoop.current()
@override
def open(self, *_, **__):
logger.info('Started websocket connection with %s', self.request.remote_ip)
self.name = f'ws:events@{self.request.remote_ip}'
self.start()
@override
def on_message(self, *_, **__):
pass
@override
def data_received(self, *_, **__):
pass
@override
def run(self) -> None:
super().run()
self._sub.subscribe(events_redis_topic)
try:
for msg in self._sub.listen():
if (
msg.get('type') != 'message'
and msg.get('channel').decode() != events_redis_topic
):
continue
try:
evt = Event.build(msg.get('data').decode())
except Exception as e:
logger.warning('Error parsing event: %s: %s', msg.get('data'), e)
continue
self._io_loop.asyncio_loop.call_soon_threadsafe( # type: ignore
self.write_message, str(evt)
)
except ConnectionError:
pass
@override
def on_close(self):
self._sub.unsubscribe(events_redis_topic)
self._sub.close()
logger.info(
'Websocket connection to %s closed, reason=%s, message=%s',
self.request.remote_ip,
self.close_code,
self.close_reason,
)