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.
264 lines
10 KiB
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')
|