You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
bazarr/libs/socketio/base_server.py

264 lines
10 KiB

import logging
from . import manager
from . import base_namespace
from . import packet
default_logger = logging.getLogger('socketio.server')
class BaseServer:
reserved_events = ['connect', 'disconnect']
def __init__(self, client_manager=None, logger=False, serializer='default',
json=None, async_handlers=True, always_connect=False,
namespaces=None, **kwargs):
engineio_options = kwargs
engineio_logger = engineio_options.pop('engineio_logger', None)
if engineio_logger is not None:
engineio_options['logger'] = engineio_logger
if serializer == 'default':
self.packet_class = packet.Packet
elif serializer == 'msgpack':
from . import msgpack_packet
self.packet_class = msgpack_packet.MsgPackPacket
else:
self.packet_class = serializer
if json is not None:
self.packet_class.json = json
engineio_options['json'] = json
engineio_options['async_handlers'] = False
self.eio = self._engineio_server_class()(**engineio_options)
self.eio.on('connect', self._handle_eio_connect)
self.eio.on('message', self._handle_eio_message)
self.eio.on('disconnect', self._handle_eio_disconnect)
self.environ = {}
self.handlers = {}
self.namespace_handlers = {}
self.not_handled = object()
self._binary_packet = {}
if not isinstance(logger, bool):
self.logger = logger
else:
self.logger = default_logger
if self.logger.level == logging.NOTSET:
if logger:
self.logger.setLevel(logging.INFO)
else:
self.logger.setLevel(logging.ERROR)
self.logger.addHandler(logging.StreamHandler())
if client_manager is None:
client_manager = manager.Manager()
self.manager = client_manager
self.manager.set_server(self)
self.manager_initialized = False
self.async_handlers = async_handlers
self.always_connect = always_connect
self.namespaces = namespaces or ['/']
self.async_mode = self.eio.async_mode
def is_asyncio_based(self):
return False
def on(self, event, handler=None, namespace=None):
"""Register an event handler.
:param event: The event name. It can be any string. The event names
``'connect'``, ``'message'`` and ``'disconnect'`` are
reserved and should not be used. The ``'*'`` event name
can be used to define a catch-all event handler.
:param handler: The function that should be invoked to handle the
event. When this parameter is not given, the method
acts as a decorator for the handler function.
:param namespace: The Socket.IO namespace for the event. If this
argument is omitted the handler is associated with
the default namespace. A catch-all namespace can be
defined by passing ``'*'`` as the namespace.
Example usage::
# as a decorator:
@sio.on('connect', namespace='/chat')
def connect_handler(sid, environ):
print('Connection request')
if environ['REMOTE_ADDR'] in blacklisted:
return False # reject
# as a method:
def message_handler(sid, msg):
print('Received message: ', msg)
sio.send(sid, 'response')
socket_io.on('message', namespace='/chat', handler=message_handler)
The arguments passed to the handler function depend on the event type:
- The ``'connect'`` event handler receives the ``sid`` (session ID) for
the client and the WSGI environment dictionary as arguments.
- The ``'disconnect'`` handler receives the ``sid`` for the client as
only argument.
- The ``'message'`` handler and handlers for custom event names receive
the ``sid`` for the client and the message payload as arguments. Any
values returned from a message handler will be passed to the client's
acknowledgement callback function if it exists.
- A catch-all event handler receives the event name as first argument,
followed by any arguments specific to the event.
- A catch-all namespace event handler receives the namespace as first
argument, followed by any arguments specific to the event.
- A combined catch-all namespace and catch-all event handler receives
the event name as first argument and the namespace as second
argument, followed by any arguments specific to the event.
"""
namespace = namespace or '/'
def set_handler(handler):
if namespace not in self.handlers:
self.handlers[namespace] = {}
self.handlers[namespace][event] = handler
return handler
if handler is None:
return set_handler
set_handler(handler)
def event(self, *args, **kwargs):
"""Decorator to register an event handler.
This is a simplified version of the ``on()`` method that takes the
event name from the decorated function.
Example usage::
@sio.event
def my_event(data):
print('Received data: ', data)
The above example is equivalent to::
@sio.on('my_event')
def my_event(data):
print('Received data: ', data)
A custom namespace can be given as an argument to the decorator::
@sio.event(namespace='/test')
def my_event(data):
print('Received data: ', data)
"""
if len(args) == 1 and len(kwargs) == 0 and callable(args[0]):
# the decorator was invoked without arguments
# args[0] is the decorated function
return self.on(args[0].__name__)(args[0])
else:
# the decorator was invoked with arguments
def set_handler(handler):
return self.on(handler.__name__, *args, **kwargs)(handler)
return set_handler
def register_namespace(self, namespace_handler):
"""Register a namespace handler object.
:param namespace_handler: An instance of a :class:`Namespace`
subclass that handles all the event traffic
for a namespace.
"""
if not isinstance(namespace_handler,
base_namespace.BaseServerNamespace):
raise ValueError('Not a namespace instance')
if self.is_asyncio_based() != namespace_handler.is_asyncio_based():
raise ValueError('Not a valid namespace class for this server')
namespace_handler._set_server(self)
self.namespace_handlers[namespace_handler.namespace] = \
namespace_handler
def rooms(self, sid, namespace=None):
"""Return the rooms a client is in.
:param sid: Session ID of the client.
:param namespace: The Socket.IO namespace for the event. If this
argument is omitted the default namespace is used.
"""
namespace = namespace or '/'
return self.manager.get_rooms(sid, namespace)
def transport(self, sid, namespace=None):
"""Return the name of the transport used by the client.
The two possible values returned by this function are ``'polling'``
and ``'websocket'``.
:param sid: The session of the client.
:param namespace: The Socket.IO namespace. If this argument is omitted
the default namespace is used.
"""
eio_sid = self.manager.eio_sid_from_sid(sid, namespace or '/')
return self.eio.transport(eio_sid)
def get_environ(self, sid, namespace=None):
"""Return the WSGI environ dictionary for a client.
:param sid: The session of the client.
:param namespace: The Socket.IO namespace. If this argument is omitted
the default namespace is used.
"""
eio_sid = self.manager.eio_sid_from_sid(sid, namespace or '/')
return self.environ.get(eio_sid)
def _get_event_handler(self, event, namespace, args):
# Return the appropriate application event handler
#
# Resolution priority:
# - self.handlers[namespace][event]
# - self.handlers[namespace]["*"]
# - self.handlers["*"][event]
# - self.handlers["*"]["*"]
handler = None
if namespace in self.handlers:
if event in self.handlers[namespace]:
handler = self.handlers[namespace][event]
elif event not in self.reserved_events and \
'*' in self.handlers[namespace]:
handler = self.handlers[namespace]['*']
args = (event, *args)
elif '*' in self.handlers:
if event in self.handlers['*']:
handler = self.handlers['*'][event]
args = (namespace, *args)
elif event not in self.reserved_events and \
'*' in self.handlers['*']:
handler = self.handlers['*']['*']
args = (event, namespace, *args)
return handler, args
def _get_namespace_handler(self, namespace, args):
# Return the appropriate application event handler.
#
# Resolution priority:
# - self.namespace_handlers[namespace]
# - self.namespace_handlers["*"]
handler = None
if namespace in self.namespace_handlers:
handler = self.namespace_handlers[namespace]
elif '*' in self.namespace_handlers:
handler = self.namespace_handlers['*']
args = (namespace, *args)
return handler, args
def _handle_eio_connect(self): # pragma: no cover
raise NotImplementedError()
def _handle_eio_message(self, data): # pragma: no cover
raise NotImplementedError()
def _handle_eio_disconnect(self): # pragma: no cover
raise NotImplementedError()
def _engineio_server_class(self): # pragma: no cover
raise NotImplementedError('Must be implemented in subclasses')