codekingpro/portable-devtools
114k
1import logging2 3from . import manager4from . import base_namespace5from . import packet6 7default_logger = logging.getLogger('socketio.server')8 9 10class BaseServer:11 reserved_events = ['connect', 'disconnect']12 13 def __init__(self, client_manager=None, logger=False, serializer='default',14 json=None, async_handlers=True, always_connect=False,15 namespaces=None, **kwargs):16 engineio_options = kwargs17 engineio_logger = engineio_options.pop('engineio_logger', None)18 if engineio_logger is not None:19 engineio_options['logger'] = engineio_logger20 if serializer == 'default':21 self.packet_class = packet.Packet22 elif serializer == 'msgpack':23 from . import msgpack_packet24 self.packet_class = msgpack_packet.MsgPackPacket25 else:26 self.packet_class = serializer27 if json is not None:28 self.packet_class.json = json29 engineio_options['json'] = json30 engineio_options['async_handlers'] = False31 self.eio = self._engineio_server_class()(**engineio_options)32 self.eio.on('connect', self._handle_eio_connect)33 self.eio.on('message', self._handle_eio_message)34 self.eio.on('disconnect', self._handle_eio_disconnect)35 36 self.environ = {}37 self.handlers = {}38 self.namespace_handlers = {}39 self.not_handled = object()40 41 self._binary_packet = {}42 43 if not isinstance(logger, bool):44 self.logger = logger45 else:46 self.logger = default_logger47 if self.logger.level == logging.NOTSET:48 if logger:49 self.logger.setLevel(logging.INFO)50 else:51 self.logger.setLevel(logging.ERROR)52 self.logger.addHandler(logging.StreamHandler())53 54 if client_manager is None:55 client_manager = manager.Manager()56 self.manager = client_manager57 self.manager.set_server(self)58 self.manager_initialized = False59 60 self.async_handlers = async_handlers61 self.always_connect = always_connect62 self.namespaces = namespaces or ['/']63 64 self.async_mode = self.eio.async_mode65 66 def is_asyncio_based(self):67 return False68 69 def on(self, event, handler=None, namespace=None):70 """Register an event handler.71 72 :param event: The event name. It can be any string. The event names73 ``'connect'``, ``'message'`` and ``'disconnect'`` are74 reserved and should not be used. The ``'*'`` event name75 can be used to define a catch-all event handler.76 :param handler: The function that should be invoked to handle the77 event. When this parameter is not given, the method78 acts as a decorator for the handler function.79 :param namespace: The Socket.IO namespace for the event. If this80 argument is omitted the handler is associated with81 the default namespace. A catch-all namespace can be82 defined by passing ``'*'`` as the namespace.83 84 Example usage::85 86 # as a decorator:87 @sio.on('connect', namespace='/chat')88 def connect_handler(sid, environ):89 print('Connection request')90 if environ['REMOTE_ADDR'] in blacklisted:91 return False # reject92 93 # as a method:94 def message_handler(sid, msg):95 print('Received message: ', msg)96 sio.send(sid, 'response')97 socket_io.on('message', namespace='/chat', handler=message_handler)98 99 The arguments passed to the handler function depend on the event type:100 101 - The ``'connect'`` event handler receives the ``sid`` (session ID) for102 the client and the WSGI environment dictionary as arguments.103 - The ``'disconnect'`` handler receives the ``sid`` for the client as104 only argument.105 - The ``'message'`` handler and handlers for custom event names receive106 the ``sid`` for the client and the message payload as arguments. Any107 values returned from a message handler will be passed to the client's108 acknowledgement callback function if it exists.109 - A catch-all event handler receives the event name as first argument,110 followed by any arguments specific to the event.111 - A catch-all namespace event handler receives the namespace as first112 argument, followed by any arguments specific to the event.113 - A combined catch-all namespace and catch-all event handler receives114 the event name as first argument and the namespace as second115 argument, followed by any arguments specific to the event.116 """117 namespace = namespace or '/'118 119 def set_handler(handler):120 if namespace not in self.handlers:121 self.handlers[namespace] = {}122 self.handlers[namespace][event] = handler123 return handler124 125 if handler is None:126 return set_handler127 set_handler(handler)128 129 def event(self, *args, **kwargs):130 """Decorator to register an event handler.131 132 This is a simplified version of the ``on()`` method that takes the133 event name from the decorated function.134 135 Example usage::136 137 @sio.event138 def my_event(data):139 print('Received data: ', data)140 141 The above example is equivalent to::142 143 @sio.on('my_event')144 def my_event(data):145 print('Received data: ', data)146 147 A custom namespace can be given as an argument to the decorator::148 149 @sio.event(namespace='/test')150 def my_event(data):151 print('Received data: ', data)152 """153 if len(args) == 1 and len(kwargs) == 0 and callable(args[0]):154 # the decorator was invoked without arguments155 # args[0] is the decorated function156 return self.on(args[0].__name__)(args[0])157 else:158 # the decorator was invoked with arguments159 def set_handler(handler):160 return self.on(handler.__name__, *args, **kwargs)(handler)161 162 return set_handler163 164 def register_namespace(self, namespace_handler):165 """Register a namespace handler object.166 167 :param namespace_handler: An instance of a :class:`Namespace`168 subclass that handles all the event traffic169 for a namespace.170 """171 if not isinstance(namespace_handler,172 base_namespace.BaseServerNamespace):173 raise ValueError('Not a namespace instance')174 if self.is_asyncio_based() != namespace_handler.is_asyncio_based():175 raise ValueError('Not a valid namespace class for this server')176 namespace_handler._set_server(self)177 self.namespace_handlers[namespace_handler.namespace] = \178 namespace_handler179 180 def rooms(self, sid, namespace=None):181 """Return the rooms a client is in.182 183 :param sid: Session ID of the client.184 :param namespace: The Socket.IO namespace for the event. If this185 argument is omitted the default namespace is used.186 """187 namespace = namespace or '/'188 return self.manager.get_rooms(sid, namespace)189 190 def transport(self, sid, namespace=None):191 """Return the name of the transport used by the client.192 193 The two possible values returned by this function are ``'polling'``194 and ``'websocket'``.195 196 :param sid: The session of the client.197 :param namespace: The Socket.IO namespace. If this argument is omitted198 the default namespace is used.199 """200 eio_sid = self.manager.eio_sid_from_sid(sid, namespace or '/')201 return self.eio.transport(eio_sid)202 203 def get_environ(self, sid, namespace=None):204 """Return the WSGI environ dictionary for a client.205 206 :param sid: The session of the client.207 :param namespace: The Socket.IO namespace. If this argument is omitted208 the default namespace is used.209 """210 eio_sid = self.manager.eio_sid_from_sid(sid, namespace or '/')211 return self.environ.get(eio_sid)212 213 def _get_event_handler(self, event, namespace, args):214 # Return the appropriate application event handler215 #216 # Resolution priority:217 # - self.handlers[namespace][event]218 # - self.handlers[namespace]["*"]219 # - self.handlers["*"][event]220 # - self.handlers["*"]["*"]221 handler = None222 if namespace in self.handlers:223 if event in self.handlers[namespace]:224 handler = self.handlers[namespace][event]225 elif event not in self.reserved_events and \226 '*' in self.handlers[namespace]:227 handler = self.handlers[namespace]['*']228 args = (event, *args)229 if handler is None and '*' in self.handlers:230 if event in self.handlers['*']:231 handler = self.handlers['*'][event]232 args = (namespace, *args)233 elif event not in self.reserved_events and \234 '*' in self.handlers['*']:235 handler = self.handlers['*']['*']236 args = (event, namespace, *args)237 return handler, args238 239 def _get_namespace_handler(self, namespace, args):240 # Return the appropriate application event handler.241 #242 # Resolution priority:243 # - self.namespace_handlers[namespace]244 # - self.namespace_handlers["*"]245 handler = None246 if namespace in self.namespace_handlers:247 handler = self.namespace_handlers[namespace]248 if handler is None and '*' in self.namespace_handlers:249 handler = self.namespace_handlers['*']250 args = (namespace, *args)251 return handler, args252 253 def _handle_eio_connect(self): # pragma: no cover254 raise NotImplementedError()255 256 def _handle_eio_message(self, data): # pragma: no cover257 raise NotImplementedError()258 259 def _handle_eio_disconnect(self): # pragma: no cover260 raise NotImplementedError()261 262 def _engineio_server_class(self): # pragma: no cover263 raise NotImplementedError('Must be implemented in subclasses')264 