Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
async_server.py695 linesDownload Raw Back to socketio
1import asyncio2 3import engineio4 5from . import async_manager6from . import base_server7from . import exceptions8from . import packet9 10# this set is used to keep references to background tasks to prevent them from11# being garbage collected mid-execution. Solution taken from12# https://docs.python.org/3/library/asyncio-task.html#asyncio.create_task13task_reference_holder = set()14 15 16class AsyncServer(base_server.BaseServer):17    """A Socket.IO server for asyncio.18 19    This class implements a fully compliant Socket.IO web server with support20    for websocket and long-polling transports, compatible with the asyncio21    framework.22 23    :param client_manager: The client manager instance that will manage the24                           client list. When this is omitted, the client list25                           is stored in an in-memory structure, so the use of26                           multiple connected servers is not possible.27    :param logger: To enable logging set to ``True`` or pass a logger object to28                   use. To disable logging set to ``False``. Note that fatal29                   errors are logged even when ``logger`` is ``False``.30    :param json: An alternative json module to use for encoding and decoding31                 packets. Custom json modules must have ``dumps`` and ``loads``32                 functions that are compatible with the standard library33                 versions.34    :param async_handlers: If set to ``True``, event handlers for a client are35                           executed in separate threads. To run handlers for a36                           client synchronously, set to ``False``. The default37                           is ``True``.38    :param always_connect: When set to ``False``, new connections are39                           provisory until the connect handler returns40                           something other than ``False``, at which point they41                           are accepted. When set to ``True``, connections are42                           immediately accepted, and then if the connect43                           handler returns ``False`` a disconnect is issued.44                           Set to ``True`` if you need to emit events from the45                           connect handler and your client is confused when it46                           receives events before the connection acceptance.47                           In any other case use the default of ``False``.48    :param namespaces: a list of namespaces that are accepted, in addition to49                       any namespaces for which handlers have been defined. The50                       default is `['/']`, which always accepts connections to51                       the default namespace. Set to `'*'` to accept all52                       namespaces.53    :param kwargs: Connection parameters for the underlying Engine.IO server.54 55    The Engine.IO configuration supports the following settings:56 57    :param async_mode: The asynchronous model to use. See the Deployment58                       section in the documentation for a description of the59                       available options. Valid async modes are "aiohttp",60                       "sanic", "tornado" and "asgi". If this argument is not61                       given, "aiohttp" is tried first, followed by "sanic",62                       "tornado", and finally "asgi". The first async mode that63                       has all its dependencies installed is the one that is64                       chosen.65    :param ping_interval: The interval in seconds at which the server pings66                          the client. The default is 25 seconds. For advanced67                          control, a two element tuple can be given, where68                          the first number is the ping interval and the second69                          is a grace period added by the server.70    :param ping_timeout: The time in seconds that the client waits for the71                         server to respond before disconnecting. The default72                         is 20 seconds.73    :param max_http_buffer_size: The maximum size that is accepted for incoming74                                 messages.  The default is 1,000,000 bytes. In75                                 spite of its name, the value set in this76                                 argument is enforced for HTTP long-polling and77                                 WebSocket connections.78    :param allow_upgrades: Whether to allow transport upgrades or not. The79                           default is ``True``.80    :param http_compression: Whether to compress packages when using the81                             polling transport. The default is ``True``.82    :param compression_threshold: Only compress messages when their byte size83                                  is greater than this value. The default is84                                  1024 bytes.85    :param cookie: If set to a string, it is the name of the HTTP cookie the86                   server sends back to the client containing the client87                   session id. If set to a dictionary, the ``'name'`` key88                   contains the cookie name and other keys define cookie89                   attributes, where the value of each attribute can be a90                   string, a callable with no arguments, or a boolean. If set91                   to ``None`` (the default), a cookie is not sent to the92                   client.93    :param cors_allowed_origins: Origin or list of origins that are allowed to94                                 connect to this server. Only the same origin95                                 is allowed by default. Set this argument to96                                 ``'*'`` to allow all origins, or to ``[]`` to97                                 disable CORS handling.98    :param cors_credentials: Whether credentials (cookies, authentication) are99                             allowed in requests to this server. The default is100                             ``True``.101    :param monitor_clients: If set to ``True``, a background task will ensure102                            inactive clients are closed. Set to ``False`` to103                            disable the monitoring task (not recommended). The104                            default is ``True``.105    :param engineio_logger: To enable Engine.IO logging set to ``True`` or pass106                            a logger object to use. To disable logging set to107                            ``False``. The default is ``False``. Note that108                            fatal errors are logged even when109                            ``engineio_logger`` is ``False``.110    """111    def __init__(self, client_manager=None, logger=False, json=None,112                 async_handlers=True, namespaces=None, **kwargs):113        if client_manager is None:114            client_manager = async_manager.AsyncManager()115        super().__init__(client_manager=client_manager, logger=logger,116                         json=json, async_handlers=async_handlers,117                         namespaces=namespaces, **kwargs)118 119    def is_asyncio_based(self):120        return True121 122    def attach(self, app, socketio_path='socket.io'):123        """Attach the Socket.IO server to an application."""124        self.eio.attach(app, socketio_path)125 126    async def emit(self, event, data=None, to=None, room=None, skip_sid=None,127                   namespace=None, callback=None, ignore_queue=False):128        """Emit a custom event to one or more connected clients.129 130        :param event: The event name. It can be any string. The event names131                      ``'connect'``, ``'message'`` and ``'disconnect'`` are132                      reserved and should not be used.133        :param data: The data to send to the client or clients. Data can be of134                     type ``str``, ``bytes``, ``list`` or ``dict``. To send135                     multiple arguments, use a tuple where each element is of136                     one of the types indicated above.137        :param to: The recipient of the message. This can be set to the138                   session ID of a client to address only that client, to any139                   any custom room created by the application to address all140                   the clients in that room, or to a list of custom room141                   names. If this argument is omitted the event is broadcasted142                   to all connected clients.143        :param room: Alias for the ``to`` parameter.144        :param skip_sid: The session ID of a client to skip when broadcasting145                         to a room or to all clients. This can be used to146                         prevent a message from being sent to the sender.147        :param namespace: The Socket.IO namespace for the event. If this148                          argument is omitted the event is emitted to the149                          default namespace.150        :param callback: If given, this function will be called to acknowledge151                         the client has received the message. The arguments152                         that will be passed to the function are those provided153                         by the client. Callback functions can only be used154                         when addressing an individual client.155        :param ignore_queue: Only used when a message queue is configured. If156                             set to ``True``, the event is emitted to the157                             clients directly, without going through the queue.158                             This is more efficient, but only works when a159                             single server process is used. It is recommended160                             to always leave this parameter with its default161                             value of ``False``.162 163        Note: this method is not designed to be used concurrently. If multiple164        tasks are emitting at the same time to the same client connection, then165        messages composed of multiple packets may end up being sent in an166        incorrect sequence. Use standard concurrency solutions (such as a Lock167        object) to prevent this situation.168 169        Note 2: this method is a coroutine.170        """171        namespace = namespace or '/'172        room = to or room173        self.logger.info('emitting event "%s" to %s [%s]', event,174                         room or 'all', namespace)175        await self.manager.emit(event, data, namespace, room=room,176                                skip_sid=skip_sid, callback=callback,177                                ignore_queue=ignore_queue)178 179    async def send(self, data, to=None, room=None, skip_sid=None,180                   namespace=None, callback=None, ignore_queue=False):181        """Send a message to one or more connected clients.182 183        This function emits an event with the name ``'message'``. Use184        :func:`emit` to issue custom event names.185 186        :param data: The data to send to the client or clients. Data can be of187                     type ``str``, ``bytes``, ``list`` or ``dict``. To send188                     multiple arguments, use a tuple where each element is of189                     one of the types indicated above.190        :param to: The recipient of the message. This can be set to the191                   session ID of a client to address only that client, to any192                   any custom room created by the application to address all193                   the clients in that room, or to a list of custom room194                   names. If this argument is omitted the event is broadcasted195                   to all connected clients.196        :param room: Alias for the ``to`` parameter.197        :param skip_sid: The session ID of a client to skip when broadcasting198                         to a room or to all clients. This can be used to199                         prevent a message from being sent to the sender.200        :param namespace: The Socket.IO namespace for the event. If this201                          argument is omitted the event is emitted to the202                          default namespace.203        :param callback: If given, this function will be called to acknowledge204                         the client has received the message. The arguments205                         that will be passed to the function are those provided206                         by the client. Callback functions can only be used207                         when addressing an individual client.208        :param ignore_queue: Only used when a message queue is configured. If209                             set to ``True``, the event is emitted to the210                             clients directly, without going through the queue.211                             This is more efficient, but only works when a212                             single server process is used. It is recommended213                             to always leave this parameter with its default214                             value of ``False``.215 216        Note: this method is a coroutine.217        """218        await self.emit('message', data=data, to=to, room=room,219                        skip_sid=skip_sid, namespace=namespace,220                        callback=callback, ignore_queue=ignore_queue)221 222    async def call(self, event, data=None, to=None, sid=None, namespace=None,223                   timeout=60, ignore_queue=False):224        """Emit a custom event to a client and wait for the response.225 226        This method issues an emit with a callback and waits for the callback227        to be invoked before returning. If the callback isn't invoked before228        the timeout, then a ``TimeoutError`` exception is raised. If the229        Socket.IO connection drops during the wait, this method still waits230        until the specified timeout.231 232        :param event: The event name. It can be any string. The event names233                      ``'connect'``, ``'message'`` and ``'disconnect'`` are234                      reserved and should not be used.235        :param data: The data to send to the client or clients. Data can be of236                     type ``str``, ``bytes``, ``list`` or ``dict``. To send237                     multiple arguments, use a tuple where each element is of238                     one of the types indicated above.239        :param to: The session ID of the recipient client.240        :param sid: Alias for the ``to`` parameter.241        :param namespace: The Socket.IO namespace for the event. If this242                          argument is omitted the event is emitted to the243                          default namespace.244        :param timeout: The waiting timeout. If the timeout is reached before245                        the client acknowledges the event, then a246                        ``TimeoutError`` exception is raised.247        :param ignore_queue: Only used when a message queue is configured. If248                             set to ``True``, the event is emitted to the249                             client directly, without going through the queue.250                             This is more efficient, but only works when a251                             single server process is used. It is recommended252                             to always leave this parameter with its default253                             value of ``False``.254 255        Note: this method is not designed to be used concurrently. If multiple256        tasks are emitting at the same time to the same client connection, then257        messages composed of multiple packets may end up being sent in an258        incorrect sequence. Use standard concurrency solutions (such as a Lock259        object) to prevent this situation.260 261        Note 2: this method is a coroutine.262        """263        if to is None and sid is None:264            raise ValueError('Cannot use call() to broadcast.')265        if not self.async_handlers:266            raise RuntimeError(267                'Cannot use call() when async_handlers is False.')268        callback_event = self.eio.create_event()269        callback_args = []270 271        def event_callback(*args):272            callback_args.append(args)273            callback_event.set()274 275        await self.emit(event, data=data, room=to or sid, namespace=namespace,276                        callback=event_callback, ignore_queue=ignore_queue)277        try:278            await asyncio.wait_for(callback_event.wait(), timeout)279        except asyncio.TimeoutError:280            raise exceptions.TimeoutError() from None281        return callback_args[0] if len(callback_args[0]) > 1 \282            else callback_args[0][0] if len(callback_args[0]) == 1 \283            else None284 285    async def enter_room(self, sid, room, namespace=None):286        """Enter a room.287 288        This function adds the client to a room. The :func:`emit` and289        :func:`send` functions can optionally broadcast events to all the290        clients in a room.291 292        :param sid: Session ID of the client.293        :param room: Room name. If the room does not exist it is created.294        :param namespace: The Socket.IO namespace for the event. If this295                          argument is omitted the default namespace is used.296 297        Note: this method is a coroutine.298        """299        namespace = namespace or '/'300        self.logger.info('%s is entering room %s [%s]', sid, room, namespace)301        await self.manager.enter_room(sid, namespace, room)302 303    async def leave_room(self, sid, room, namespace=None):304        """Leave a room.305 306        This function removes the client from a room.307 308        :param sid: Session ID of the client.309        :param room: Room name.310        :param namespace: The Socket.IO namespace for the event. If this311                          argument is omitted the default namespace is used.312 313        Note: this method is a coroutine.314        """315        namespace = namespace or '/'316        self.logger.info('%s is leaving room %s [%s]', sid, room, namespace)317        await self.manager.leave_room(sid, namespace, room)318 319    async def close_room(self, room, namespace=None):320        """Close a room.321 322        This function removes all the clients from the given room.323 324        :param room: Room name.325        :param namespace: The Socket.IO namespace for the event. If this326                          argument is omitted the default namespace is used.327 328        Note: this method is a coroutine.329        """330        namespace = namespace or '/'331        self.logger.info('room %s is closing [%s]', room, namespace)332        await self.manager.close_room(room, namespace)333 334    async def get_session(self, sid, namespace=None):335        """Return the user session for a client.336 337        :param sid: The session id of the client.338        :param namespace: The Socket.IO namespace. If this argument is omitted339                          the default namespace is used.340 341        The return value is a dictionary. Modifications made to this342        dictionary are not guaranteed to be preserved. If you want to modify343        the user session, use the ``session`` context manager instead.344        """345        namespace = namespace or '/'346        eio_sid = self.manager.eio_sid_from_sid(sid, namespace)347        eio_session = await self.eio.get_session(eio_sid)348        return eio_session.setdefault(namespace, {})349 350    async def save_session(self, sid, session, namespace=None):351        """Store the user session for a client.352 353        :param sid: The session id of the client.354        :param session: The session dictionary.355        :param namespace: The Socket.IO namespace. If this argument is omitted356                          the default namespace is used.357        """358        namespace = namespace or '/'359        eio_sid = self.manager.eio_sid_from_sid(sid, namespace)360        eio_session = await self.eio.get_session(eio_sid)361        eio_session[namespace] = session362 363    def session(self, sid, namespace=None):364        """Return the user session for a client with context manager syntax.365 366        :param sid: The session id of the client.367 368        This is a context manager that returns the user session dictionary for369        the client. Any changes that are made to this dictionary inside the370        context manager block are saved back to the session. Example usage::371 372            @eio.on('connect')373            def on_connect(sid, environ):374                username = authenticate_user(environ)375                if not username:376                    return False377                with eio.session(sid) as session:378                    session['username'] = username379 380            @eio.on('message')381            def on_message(sid, msg):382                async with eio.session(sid) as session:383                    print('received message from ', session['username'])384        """385        class _session_context_manager(object):386            def __init__(self, server, sid, namespace):387                self.server = server388                self.sid = sid389                self.namespace = namespace390                self.session = None391 392            async def __aenter__(self):393                self.session = await self.server.get_session(394                    sid, namespace=self.namespace)395                return self.session396 397            async def __aexit__(self, *args):398                await self.server.save_session(sid, self.session,399                                               namespace=self.namespace)400 401        return _session_context_manager(self, sid, namespace)402 403    async def disconnect(self, sid, namespace=None, ignore_queue=False):404        """Disconnect a client.405 406        :param sid: Session ID of the client.407        :param namespace: The Socket.IO namespace to disconnect. If this408                          argument is omitted the default namespace is used.409        :param ignore_queue: Only used when a message queue is configured. If410                             set to ``True``, the disconnect is processed411                             locally, without broadcasting on the queue. It is412                             recommended to always leave this parameter with413                             its default value of ``False``.414 415        Note: this method is a coroutine.416        """417        namespace = namespace or '/'418        if ignore_queue:419            delete_it = self.manager.is_connected(sid, namespace)420        else:421            delete_it = await self.manager.can_disconnect(sid, namespace)422        if delete_it:423            self.logger.info('Disconnecting %s [%s]', sid, namespace)424            eio_sid = self.manager.pre_disconnect(sid, namespace=namespace)425            await self._send_packet(eio_sid, self.packet_class(426                packet.DISCONNECT, namespace=namespace))427            await self._trigger_event('disconnect', namespace, sid)428            await self.manager.disconnect(sid, namespace=namespace,429                                          ignore_queue=True)430 431    async def shutdown(self):432        """Stop Socket.IO background tasks.433 434        This method stops all background activity initiated by the Socket.IO435        server. It must be called before shutting down the web server.436        """437        self.logger.info('Socket.IO is shutting down')438        await self.eio.shutdown()439 440    async def handle_request(self, *args, **kwargs):441        """Handle an HTTP request from the client.442 443        This is the entry point of the Socket.IO application. This function444        returns the HTTP response body to deliver to the client.445 446        Note: this method is a coroutine.447        """448        return await self.eio.handle_request(*args, **kwargs)449 450    def start_background_task(self, target, *args, **kwargs):451        """Start a background task using the appropriate async model.452 453        This is a utility function that applications can use to start a454        background task using the method that is compatible with the455        selected async mode.456 457        :param target: the target function to execute. Must be a coroutine.458        :param args: arguments to pass to the function.459        :param kwargs: keyword arguments to pass to the function.460 461        The return value is a ``asyncio.Task`` object.462        """463        return self.eio.start_background_task(target, *args, **kwargs)464 465    async def sleep(self, seconds=0):466        """Sleep for the requested amount of time using the appropriate async467        model.468 469        This is a utility function that applications can use to put a task to470        sleep without having to worry about using the correct call for the471        selected async mode.472 473        Note: this method is a coroutine.474        """475        return await self.eio.sleep(seconds)476 477    def instrument(self, auth=None, mode='development', read_only=False,478                   server_id=None, namespace='/admin',479                   server_stats_interval=2):480        """Instrument the Socket.IO server for monitoring with the `Socket.IO481        Admin UI <https://socket.io/docs/v4/admin-ui/>`_.482 483        :param auth: Authentication credentials for Admin UI access. Set to a484                     dictionary with the expected login (usually ``username``485                     and ``password``) or a list of dictionaries if more than486                     one set of credentials need to be available. For more487                     complex authentication methods, set to a callable that488                     receives the authentication dictionary as an argument and489                     returns ``True`` if the user is allowed or ``False``490                     otherwise. To disable authentication, set this argument to491                     ``False`` (not recommended, never do this on a production492                     server).493        :param mode: The reporting mode. The default is ``'development'``,494                     which is best used while debugging, as it may have a495                     significant performance effect. Set to ``'production'`` to496                     reduce the amount of information that is reported to the497                     admin UI.498        :param read_only: If set to ``True``, the admin interface will be499                          read-only, with no option to modify room assignments500                          or disconnect clients. The default is ``False``.501        :param server_id: The server name to use for this server. If this502                          argument is omitted, the server generates its own503                          name.504        :param namespace: The Socket.IO namespace to use for the admin505                          interface. The default is ``/admin``.506        :param server_stats_interval: The interval in seconds at which the507                                      server emits a summary of it stats to all508                                      connected admins.509        """510        from .async_admin import InstrumentedAsyncServer511        return InstrumentedAsyncServer(512            self, auth=auth, mode=mode, read_only=read_only,513            server_id=server_id, namespace=namespace,514            server_stats_interval=server_stats_interval)515 516    async def _send_packet(self, eio_sid, pkt):517        """Send a Socket.IO packet to a client."""518        encoded_packet = pkt.encode()519        if isinstance(encoded_packet, list):520            for ep in encoded_packet:521                await self.eio.send(eio_sid, ep)522        else:523            await self.eio.send(eio_sid, encoded_packet)524 525    async def _send_eio_packet(self, eio_sid, eio_pkt):526        """Send a raw Engine.IO packet to a client."""527        await self.eio.send_packet(eio_sid, eio_pkt)528 529    async def _handle_connect(self, eio_sid, namespace, data):530        """Handle a client connection request."""531        namespace = namespace or '/'532        sid = None533        if namespace in self.handlers or namespace in self.namespace_handlers \534                or self.namespaces == '*' or namespace in self.namespaces:535            sid = await self.manager.connect(eio_sid, namespace)536        if sid is None:537            await self._send_packet(eio_sid, self.packet_class(538                packet.CONNECT_ERROR, data='Unable to connect',539                namespace=namespace))540            return541 542        if self.always_connect:543            await self._send_packet(eio_sid, self.packet_class(544                packet.CONNECT, {'sid': sid}, namespace=namespace))545        fail_reason = exceptions.ConnectionRefusedError().error_args546        try:547            if data:548                success = await self._trigger_event(549                    'connect', namespace, sid, self.environ[eio_sid], data)550            else:551                try:552                    success = await self._trigger_event(553                        'connect', namespace, sid, self.environ[eio_sid])554                except TypeError:555                    success = await self._trigger_event(556                        'connect', namespace, sid, self.environ[eio_sid], None)557        except exceptions.ConnectionRefusedError as exc:558            fail_reason = exc.error_args559            success = False560 561        if success is False:562            if self.always_connect:563                self.manager.pre_disconnect(sid, namespace)564                await self._send_packet(eio_sid, self.packet_class(565                    packet.DISCONNECT, data=fail_reason, namespace=namespace))566            else:567                await self._send_packet(eio_sid, self.packet_class(568                    packet.CONNECT_ERROR, data=fail_reason,569                    namespace=namespace))570            await self.manager.disconnect(sid, namespace, ignore_queue=True)571        elif not self.always_connect:572            await self._send_packet(eio_sid, self.packet_class(573                packet.CONNECT, {'sid': sid}, namespace=namespace))574 575    async def _handle_disconnect(self, eio_sid, namespace):576        """Handle a client disconnect."""577        namespace = namespace or '/'578        sid = self.manager.sid_from_eio_sid(eio_sid, namespace)579        if not self.manager.is_connected(sid, namespace):  # pragma: no cover580            return581        self.manager.pre_disconnect(sid, namespace=namespace)582        await self._trigger_event('disconnect', namespace, sid)583        await self.manager.disconnect(sid, namespace, ignore_queue=True)584 585    async def _handle_event(self, eio_sid, namespace, id, data):586        """Handle an incoming client event."""587        namespace = namespace or '/'588        sid = self.manager.sid_from_eio_sid(eio_sid, namespace)589        self.logger.info('received event "%s" from %s [%s]', data[0], sid,590                         namespace)591        if not self.manager.is_connected(sid, namespace):592            self.logger.warning('%s is not connected to namespace %s',593                                sid, namespace)594            return595        if self.async_handlers:596            task = self.start_background_task(597                self._handle_event_internal, self, sid, eio_sid, data,598                namespace, id)599            task_reference_holder.add(task)600            task.add_done_callback(task_reference_holder.discard)601        else:602            await self._handle_event_internal(self, sid, eio_sid, data,603                                              namespace, id)604 605    async def _handle_event_internal(self, server, sid, eio_sid, data,606                                     namespace, id):607        r = await server._trigger_event(data[0], namespace, sid, *data[1:])608        if r != self.not_handled and id is not None:609            # send ACK packet with the response returned by the handler610            # tuples are expanded as multiple arguments611            if r is None:612                data = []613            elif isinstance(r, tuple):614                data = list(r)615            else:616                data = [r]617            await server._send_packet(eio_sid, self.packet_class(618                packet.ACK, namespace=namespace, id=id, data=data))619 620    async def _handle_ack(self, eio_sid, namespace, id, data):621        """Handle ACK packets from the client."""622        namespace = namespace or '/'623        sid = self.manager.sid_from_eio_sid(eio_sid, namespace)624        self.logger.info('received ack from %s [%s]', sid, namespace)625        await self.manager.trigger_callback(sid, id, data)626 627    async def _trigger_event(self, event, namespace, *args):628        """Invoke an application event handler."""629        # first see if we have an explicit handler for the event630        handler, args = self._get_event_handler(event, namespace, args)631        if handler:632            if asyncio.iscoroutinefunction(handler):633                try:634                    ret = await handler(*args)635                except asyncio.CancelledError:  # pragma: no cover636                    ret = None637            else:638                ret = handler(*args)639            return ret640        # or else, forward the event to a namespace handler if one exists641        handler, args = self._get_namespace_handler(namespace, args)642        if handler:643            return await handler.trigger_event(event, *args)644        else:645            return self.not_handled646 647    async def _handle_eio_connect(self, eio_sid, environ):648        """Handle the Engine.IO connection event."""649        if not self.manager_initialized:650            self.manager_initialized = True651            self.manager.initialize()652        self.environ[eio_sid] = environ653 654    async def _handle_eio_message(self, eio_sid, data):655        """Dispatch Engine.IO messages."""656        if eio_sid in self._binary_packet:657            pkt = self._binary_packet[eio_sid]658            if pkt.add_attachment(data):659                del self._binary_packet[eio_sid]660                if pkt.packet_type == packet.BINARY_EVENT:661                    await self._handle_event(eio_sid, pkt.namespace, pkt.id,662                                             pkt.data)663                else:664                    await self._handle_ack(eio_sid, pkt.namespace, pkt.id,665                                           pkt.data)666        else:667            pkt = self.packet_class(encoded_packet=data)668            if pkt.packet_type == packet.CONNECT:669                await self._handle_connect(eio_sid, pkt.namespace, pkt.data)670            elif pkt.packet_type == packet.DISCONNECT:671                await self._handle_disconnect(eio_sid, pkt.namespace)672            elif pkt.packet_type == packet.EVENT:673                await self._handle_event(eio_sid, pkt.namespace, pkt.id,674                                         pkt.data)675            elif pkt.packet_type == packet.ACK:676                await self._handle_ack(eio_sid, pkt.namespace, pkt.id,677                                       pkt.data)678            elif pkt.packet_type == packet.BINARY_EVENT or \679                    pkt.packet_type == packet.BINARY_ACK:680                self._binary_packet[eio_sid] = pkt681            elif pkt.packet_type == packet.CONNECT_ERROR:682                raise ValueError('Unexpected CONNECT_ERROR packet.')683            else:684                raise ValueError('Unknown packet type.')685 686    async def _handle_eio_disconnect(self, eio_sid):687        """Handle Engine.IO disconnect event."""688        for n in list(self.manager.get_namespaces()).copy():689            await self._handle_disconnect(eio_sid, n)690        if eio_sid in self.environ:691            del self.environ[eio_sid]692 693    def _engineio_server_class(self):694        return engineio.AsyncServer695 
codekingpro/portable-devtools · Team Ai