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')