././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1793228809.0379357 python_engineio-4.15.1/0000755000175000017500000000000015270500011014355 5ustar00miguelmiguel././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1791413117.673153 python_engineio-4.15.1/LICENSE0000644000175000017500000000207215261545576015414 0ustar00miguelmiguelThe MIT License (MIT) Copyright (c) 2015 Miguel Grinberg Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1791413117.6733665 python_engineio-4.15.1/MANIFEST.in0000644000175000017500000000011015261545576016134 0ustar00miguelmiguelinclude README.md LICENSE tox.ini tests/**/* docs/**/* exclude **/*.pyc ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1793228809.0377707 python_engineio-4.15.1/PKG-INFO0000644000175000017500000000511215270500011015451 0ustar00miguelmiguelMetadata-Version: 2.4 Name: python-engineio Version: 4.15.1 Summary: Engine.IO server and client for Python Author-email: Miguel Grinberg License-Expression: MIT Project-URL: Homepage, https://code.miguelgrinberg.com/miguelgrinberg/python-engineio Project-URL: Documentation, https://python-engineio.readthedocs.io/ Project-URL: Source Code, https://code.miguelgrinberg.com/miguelgrinberg/python-engineio Project-URL: Issue Tracker, https://code.miguelgrinberg.com/miguelgrinberg/python-engineio/issues Project-URL: Changelog, https://code.miguelgrinberg.com/miguelgrinberg/python-engineio/src/branch/main/CHANGES.md Project-URL: Funding, https://code.miguelgrinberg.com/miguelgrinberg/python-engineio#sponsor-this-project Classifier: Environment :: Web Environment Classifier: Intended Audience :: Developers Classifier: Programming Language :: Python :: 3 Classifier: Operating System :: OS Independent Requires-Python: >=3.8 Description-Content-Type: text/markdown License-File: LICENSE Requires-Dist: simple-websocket>=0.10.0 Provides-Extra: client Requires-Dist: requests>=2.21.0; extra == "client" Requires-Dist: websocket-client>=0.54.0; extra == "client" Provides-Extra: asyncio-client Requires-Dist: aiohttp>=3.11; extra == "asyncio-client" Provides-Extra: dev Requires-Dist: tox; extra == "dev" Provides-Extra: docs Requires-Dist: sphinx; extra == "docs" Requires-Dist: furo; extra == "docs" Dynamic: license-file python-engineio =============== [![tests](https://code.miguelgrinberg.com/miguelgrinberg/python-engineio/badges/workflows/tests.yml/badge.svg)](https://code.miguelgrinberg.com/miguelgrinberg/python-engineio/actions) Python implementation of the `Engine.IO` realtime client and server. Resources --------- - [git](https://code.miguelgrinberg.com/miguelgrinberg/python-engineio) - [Change Log](https://code.miguelgrinberg.com/miguelgrinberg/python-engineio/src/branch/main/CHANGES.md) - [Documentation](https://python-engineio.readthedocs.io/) - [PyPI](https://pypi.python.org/pypi/python-engineio) - [Contributor's guide](CONTRIBUTING.md) - [Security policy](SECURITY.md) Sponsor this project -------------------- This project relies on contributions from its users. If you benefit from it please consider making a single or ongoing monetary contribution in one of the following platforms: - [Github Sponsors](https://github.com/sponsors/miguelgrinberg) - [Patreon](https://patreon.com/miguelgrinberg) - [Buy me a Coffee](https://buymeacoffee.com/miguelgrinberg) - [thanks.dev](https://thanks.dev/u/gh/miguelgrinberg) - [PayPal](https://paypal.me/miguelgrinberg) Thank you! ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1791413117.673428 python_engineio-4.15.1/README.md0000644000175000017500000000223515261545576015667 0ustar00miguelmiguelpython-engineio =============== [![tests](https://code.miguelgrinberg.com/miguelgrinberg/python-engineio/badges/workflows/tests.yml/badge.svg)](https://code.miguelgrinberg.com/miguelgrinberg/python-engineio/actions) Python implementation of the `Engine.IO` realtime client and server. Resources --------- - [git](https://code.miguelgrinberg.com/miguelgrinberg/python-engineio) - [Change Log](https://code.miguelgrinberg.com/miguelgrinberg/python-engineio/src/branch/main/CHANGES.md) - [Documentation](https://python-engineio.readthedocs.io/) - [PyPI](https://pypi.python.org/pypi/python-engineio) - [Contributor's guide](CONTRIBUTING.md) - [Security policy](SECURITY.md) Sponsor this project -------------------- This project relies on contributions from its users. If you benefit from it please consider making a single or ongoing monetary contribution in one of the following platforms: - [Github Sponsors](https://github.com/sponsors/miguelgrinberg) - [Patreon](https://patreon.com/miguelgrinberg) - [Buy me a Coffee](https://buymeacoffee.com/miguelgrinberg) - [thanks.dev](https://thanks.dev/u/gh/miguelgrinberg) - [PayPal](https://paypal.me/miguelgrinberg) Thank you! ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1793228809.0345511 python_engineio-4.15.1/docs/0000755000175000017500000000000015270500011015305 5ustar00miguelmiguel././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1793228809.0350769 python_engineio-4.15.1/docs/_static/0000755000175000017500000000000015270500011016733 5ustar00miguelmiguel././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1791413117.6735742 python_engineio-4.15.1/docs/_static/README.md0000644000175000017500000000006315261545576020242 0ustar00miguelmiguelPlace static files used by the documentation here. ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1793228807.911037 python_engineio-4.15.1/pyproject.toml0000644000175000017500000000307015270500010017270 0ustar00miguelmiguel[project] name = "python-engineio" version = "4.15.1" authors = [{name = "Miguel Grinberg", email = "miguel.grinberg@gmail.com"}] description = "Engine.IO server and client for Python" classifiers = [ "Environment :: Web Environment", "Intended Audience :: Developers", "Programming Language :: Python :: 3", "Operating System :: OS Independent", ] license = "MIT" requires-python = ">=3.8" dependencies = ["simple-websocket >= 0.10.0"] [project.readme] file = "README.md" content-type = "text/markdown" [project.urls] Homepage = "https://code.miguelgrinberg.com/miguelgrinberg/python-engineio" Documentation = "https://python-engineio.readthedocs.io/" "Source Code" = "https://code.miguelgrinberg.com/miguelgrinberg/python-engineio" "Issue Tracker" = "https://code.miguelgrinberg.com/miguelgrinberg/python-engineio/issues" Changelog = "https://code.miguelgrinberg.com/miguelgrinberg/python-engineio/src/branch/main/CHANGES.md" Funding = "https://code.miguelgrinberg.com/miguelgrinberg/python-engineio#sponsor-this-project" [project.optional-dependencies] client = [ "requests >= 2.21.0", "websocket-client >= 0.54.0", ] asyncio_client = [ "aiohttp >= 3.11" ] dev = [ "tox", ] docs = [ "sphinx", "furo", ] [tool.setuptools] zip-safe = false include-package-data = true package-dir = {"" = "src"} [tool.setuptools.packages.find] where = ["src"] namespaces = false [build-system] requires = ["setuptools>=61.2"] build-backend = "setuptools.build_meta" [tool.pytest.ini_options] asyncio_mode = "auto" asyncio_default_fixture_loop_scope = "session" ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1793228809.0379357 python_engineio-4.15.1/setup.cfg0000644000175000017500000000004615270500011016176 0ustar00miguelmiguel[egg_info] tag_build = tag_date = 0 ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1793228809.0346885 python_engineio-4.15.1/src/0000755000175000017500000000000015270500011015144 5ustar00miguelmiguel././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1793228809.0357435 python_engineio-4.15.1/src/engineio/0000755000175000017500000000000015270500011016741 5ustar00miguelmiguel././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1793228807.9126515 python_engineio-4.15.1/src/engineio/__init__.py0000644000175000017500000000074115270500010021053 0ustar00miguelmiguelfrom .client import Client from .middleware import WSGIApp, Middleware from .server import Server from .async_server import AsyncServer from .async_client import AsyncClient from .async_drivers.asgi import ASGIApp try: from .async_drivers.tornado import get_tornado_handler except ImportError: # pragma: no cover get_tornado_handler = None __all__ = ['Server', 'WSGIApp', 'Middleware', 'Client', 'AsyncServer', 'ASGIApp', 'get_tornado_handler', 'AsyncClient'] ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1793127161.2733064 python_engineio-4.15.1/src/engineio/async_client.py0000644000175000017500000007442415270171371022016 0ustar00miguelmiguelimport asyncio from http.cookies import SimpleCookie import inspect import signal import ssl import threading try: import aiohttp except ImportError: # pragma: no cover aiohttp = None from . import base_client from . import exceptions from . import packet from . import payload async_signal_handler_set = False # this set is used to keep references to background tasks to prevent them from # being garbage collected mid-execution. Solution taken from # https://docs.python.org/3/library/asyncio-task.html#asyncio.create_task task_reference_holder = set() def async_signal_handler(): """SIGINT handler. Disconnect all active async clients. """ async def _handler(): # pragma: no cover for c in base_client.connected_clients[:]: if c.is_asyncio_based(): await c.disconnect() # cancel all running tasks tasks = [task for task in asyncio.all_tasks() if task is not asyncio.current_task()] for task in tasks: task.cancel() await asyncio.gather(*tasks, return_exceptions=True) asyncio.get_running_loop().stop() asyncio.ensure_future(_handler()) class AsyncClient(base_client.BaseClient): """An Engine.IO client for asyncio. This class implements a fully compliant Engine.IO web client with support for websocket and long-polling transports, compatible with the asyncio framework on Python 3.5 or newer. :param logger: To enable logging set to ``True`` or pass a logger object to use. To disable logging set to ``False``. The default is ``False``. Note that fatal errors are logged even when ``logger`` is ``False``. :param json: An alternative JSON module to use for encoding and decoding packets. Custom json modules must have ``dumps`` and ``loads`` functions that are compatible with the standard library versions. This is a process-wide setting, all instantiated servers and clients must use the same JSON module. :param request_timeout: A timeout in seconds for requests. The default is 5 seconds. :param http_session: an initialized ``aiohttp.ClientSession`` object to be used when sending requests to the server. Use it if you need to add special client options such as proxy servers, SSL certificates, custom CA bundle, etc. :param ssl_verify: ``True`` to verify SSL certificates, or ``False`` to skip SSL certificate verification, allowing connections to servers with self signed certificates. The default is ``True``. :param handle_sigint: Set to ``True`` to automatically handle disconnection when the process is interrupted, or to ``False`` to leave interrupt handling to the calling application. Interrupt handling can only be enabled when the client instance is created in the main thread. :param websocket_extra_options: Dictionary containing additional keyword arguments passed to ``aiohttp.ws_connect()``. :param timestamp_requests: If ``True`` a timestamp is added to the query string of Socket.IO requests as a cache-busting measure. Set to ``False`` to disable. """ def is_asyncio_based(self): return True async def connect(self, url, headers=None, transports=None, engineio_path='engine.io'): """Connect to an Engine.IO server. :param url: The URL of the Engine.IO server. It can include custom query string parameters if required by the server. :param headers: A dictionary with custom headers to send with the connection request. :param transports: The list of allowed transports. Valid transports are ``'polling'`` and ``'websocket'``. If not given, the polling transport is connected first, then an upgrade to websocket is attempted. :param engineio_path: The endpoint where the Engine.IO server is installed. The default value is appropriate for most cases. Note: this method is a coroutine. Example usage:: eio = engineio.Client() await eio.connect('http://localhost:5000') """ global async_signal_handler_set if self.handle_sigint and not async_signal_handler_set and \ threading.current_thread() == threading.main_thread(): try: asyncio.get_running_loop().add_signal_handler( signal.SIGINT, async_signal_handler) except NotImplementedError: # pragma: no cover self.logger.warning('Signal handler is unsupported') async_signal_handler_set = True if self.state != 'disconnected': raise ValueError('Client is not in a disconnected state') valid_transports = ['polling', 'websocket'] if transports is not None: if isinstance(transports, str): transports = [transports] transports = [transport for transport in transports if transport in valid_transports] if not transports: raise ValueError('No valid transports provided') self.transports = transports or valid_transports return await getattr(self, '_connect_' + self.transports[0])( url, headers or {}, engineio_path) async def wait(self): """Wait until the connection with the server ends. Client applications can use this function to block the main thread during the life of the connection. Note: this method is a coroutine. """ if self.read_loop_task: await self.read_loop_task async def send(self, data): """Send a message to the server. :param data: The data to send to the server. Data can be of type ``str``, ``bytes``, ``list`` or ``dict``. If a ``list`` or ``dict``, the data will be serialized as JSON. Note: this method is a coroutine. """ await self._send_packet(packet.Packet(packet.MESSAGE, data=data)) async def disconnect(self, abort=False, reason=None): """Disconnect from the server. :param abort: If set to ``True``, do not wait for background tasks associated with the connection to end. Note: this method is a coroutine. """ if self.state == 'connected': await self._send_packet(packet.Packet(packet.CLOSE)) await self.queue.put(None) self.state = 'disconnecting' await self._trigger_event('disconnect', reason or self.reason.CLIENT_DISCONNECT, run_async=False) if self.current_transport == 'websocket': await self.ws.close() if not abort: await self.read_loop_task self.state = 'disconnected' try: base_client.connected_clients.remove(self) except ValueError: # pragma: no cover pass await self._reset() def start_background_task(self, target, *args, **kwargs): """Start a background task. This is a utility function that applications can use to start a background task. :param target: the target function to execute. :param args: arguments to pass to the function. :param kwargs: keyword arguments to pass to the function. The return value is a ``asyncio.Task`` object. """ return asyncio.ensure_future(target(*args, **kwargs)) async def sleep(self, seconds=0): """Sleep for the requested amount of time. Note: this method is a coroutine. """ return await asyncio.sleep(seconds) def create_queue(self, *args, **kwargs): """Create a queue object.""" return asyncio.Queue(*args, **kwargs) def get_queue_empty_exception(self): """Return the queue empty exception raised by queues created by the ``create_queue()`` method. """ return asyncio.QueueEmpty def create_event(self): """Create an event object.""" return asyncio.Event() async def _reset(self): super()._reset() while True: # pragma: no cover try: self.queue.get_nowait() self.queue.task_done() except self.queue_empty: break if not self.external_http: # pragma: no cover if self.http and not self.http.closed: await self.http.close() self.http = None def __del__(self): # pragma: no cover # try to close the aiohttp session if it is still open if self.http and not self.http.closed: try: loop = asyncio.get_event_loop() if loop.is_running(): loop.ensure_future(self.http.close()) else: loop.run_until_complete(self.http.close()) except: pass async def _connect_polling(self, url, headers, engineio_path): """Establish a long-polling connection to the Engine.IO server.""" if aiohttp is None: # pragma: no cover self.logger.error('aiohttp not installed -- cannot make HTTP ' 'requests!') return self.base_url = self._get_engineio_url(url, engineio_path, 'polling') self.logger.info('Attempting polling connection to ' + self.base_url) r = await self._send_request( 'GET', self.base_url + self._get_url_timestamp(), headers=headers, timeout=self.request_timeout) if r is None or isinstance(r, str): await self._reset() raise exceptions.ConnectionError( r or 'Connection refused by the server') if r.status < 200 or r.status >= 300: await self._reset() try: arg = await r.json() except aiohttp.ClientError: arg = None raise exceptions.ConnectionError( 'Unexpected status code {} in server response'.format( r.status), arg) try: p = payload.Payload(encoded_payload=(await r.read()).decode( 'utf-8')) except ValueError: raise exceptions.ConnectionError( 'Unexpected response from server') from None open_packet = p.packets[0] if open_packet.packet_type != packet.OPEN: raise exceptions.ConnectionError( 'OPEN packet not returned by server') self.logger.info( 'Polling connection accepted with ' + str(open_packet.data)) self.sid = open_packet.data['sid'] self.upgrades = open_packet.data['upgrades'] self.ping_interval = int(open_packet.data['pingInterval']) / 1000.0 self.ping_timeout = int(open_packet.data['pingTimeout']) / 1000.0 self.current_transport = 'polling' self.base_url += '&sid=' + self.sid self.state = 'connected' base_client.connected_clients.append(self) try: await self._trigger_event('connect', run_async=False) except Exception as exc: base_client.connected_clients.remove(self) await self._reset() raise exceptions.ConnectionError( 'Connect handler failed: ' + str(exc)) for pkt in p.packets[1:]: await self._receive_packet(pkt) if 'websocket' in self.upgrades and 'websocket' in self.transports: # attempt to upgrade to websocket if await self._connect_websocket(url, headers, engineio_path): # upgrade to websocket succeeded, we're done here return self.write_loop_task = self.start_background_task(self._write_loop) self.read_loop_task = self.start_background_task( self._read_loop_polling) async def _connect_websocket(self, url, headers, engineio_path): """Establish or upgrade to a WebSocket connection with the server.""" if aiohttp is None: # pragma: no cover self.logger.error('aiohttp package not installed') return False websocket_url = self._get_engineio_url(url, engineio_path, 'websocket') if self.sid: self.logger.info( 'Attempting WebSocket upgrade to ' + websocket_url) upgrade = True websocket_url += '&sid=' + self.sid else: upgrade = False self.base_url = websocket_url self.logger.info( 'Attempting WebSocket connection to ' + websocket_url) if self.http is None or self.http.closed: if self.http_factory: self.http = self.http_factory() if inspect.iscoroutine(self.http): self.http = await self.http else: self.http = aiohttp.ClientSession() # extract any new cookies passed in a header so that they can also be # sent the the WebSocket route for header, value in headers.items(): if header.lower() == 'cookie': ck = SimpleCookie(headers[header]) self.http.cookie_jar.update_cookies( {k: m.value for k, m in ck.items()}) del headers[header] break extra_options = { 'timeout': aiohttp.ClientWSTimeout(ws_close=self.request_timeout)} if not self.ssl_verify: ssl_context = ssl.create_default_context() ssl_context.check_hostname = False ssl_context.verify_mode = ssl.CERT_NONE extra_options['ssl'] = ssl_context # combine internally generated options with the ones supplied by the # caller. The caller's options take precedence. headers.update(self.websocket_extra_options.pop('headers', {})) extra_options['headers'] = headers extra_options.update(self.websocket_extra_options) try: ws = await self.http.ws_connect( websocket_url + self._get_url_timestamp(), **extra_options) except (aiohttp.client_exceptions.WSServerHandshakeError, aiohttp.client_exceptions.ServerConnectionError, aiohttp.client_exceptions.ClientConnectionError): if upgrade: self.logger.warning( 'WebSocket upgrade failed: connection error') return False else: raise exceptions.ConnectionError('Connection error') if upgrade: p = packet.Packet(packet.PING, data='probe').encode() try: await ws.send_str(p) except Exception as e: # pragma: no cover self.logger.warning( 'WebSocket upgrade failed: unexpected send exception: %s', str(e)) return False try: p = (await ws.receive()).data except Exception as e: # pragma: no cover self.logger.warning( 'WebSocket upgrade failed: unexpected recv exception: %s', str(e)) return False pkt = packet.Packet(encoded_packet=p) if pkt.packet_type != packet.PONG or pkt.data != 'probe': self.logger.warning( 'WebSocket upgrade failed: no PONG packet') return False p = packet.Packet(packet.UPGRADE).encode() try: await ws.send_str(p) except Exception as e: # pragma: no cover self.logger.warning( 'WebSocket upgrade failed: unexpected send exception: %s', str(e)) return False self.current_transport = 'websocket' self.logger.info('WebSocket upgrade was successful') else: try: p = (await ws.receive()).data except Exception as e: # pragma: no cover raise exceptions.ConnectionError( 'Unexpected recv exception: ' + str(e)) open_packet = packet.Packet(encoded_packet=p) if open_packet.packet_type != packet.OPEN: raise exceptions.ConnectionError('no OPEN packet') self.logger.info( 'WebSocket connection accepted with ' + str(open_packet.data)) self.sid = open_packet.data['sid'] self.upgrades = open_packet.data['upgrades'] self.ping_interval = int(open_packet.data['pingInterval']) / 1000.0 self.ping_timeout = int(open_packet.data['pingTimeout']) / 1000.0 self.current_transport = 'websocket' self.state = 'connected' base_client.connected_clients.append(self) try: await self._trigger_event('connect', run_async=False) except Exception as exc: base_client.connected_clients.remove(self) await self._reset() raise exceptions.ConnectionError( 'Connect handler failed: ' + str(exc)) self.ws = ws self.write_loop_task = self.start_background_task(self._write_loop) self.read_loop_task = self.start_background_task( self._read_loop_websocket) return True async def _receive_packet(self, pkt): """Handle incoming packets from the server.""" packet_name = packet.packet_names[pkt.packet_type] \ if pkt.packet_type < len(packet.packet_names) else 'UNKNOWN' self.logger.info( 'Received packet %s data %s', packet_name, pkt.data if not isinstance(pkt.data, bytes) else '') if pkt.packet_type == packet.MESSAGE: await self._trigger_event('message', pkt.data, run_async=True) elif pkt.packet_type == packet.PING: await self._send_packet(packet.Packet(packet.PONG, pkt.data)) elif pkt.packet_type == packet.CLOSE: await self.disconnect(abort=True, reason=self.reason.SERVER_DISCONNECT) elif pkt.packet_type == packet.NOOP: pass else: self.logger.error('Received unexpected packet of type %s', pkt.packet_type) async def _send_packet(self, pkt): """Queue a packet to be sent to the server.""" if self.state != 'connected': return await self.queue.put(pkt) self.logger.info( 'Sending packet %s data %s', packet.packet_names[pkt.packet_type], pkt.data if not isinstance(pkt.data, bytes) else '') async def _send_request( self, method, url, headers=None, body=None, timeout=None): if self.http is None or self.http.closed: if self.http_factory: self.http = self.http_factory() if inspect.iscoroutine(self.http): self.http = await self.http else: self.http = aiohttp.ClientSession() http_method = getattr(self.http, method.lower()) try: # pragma: no cover if not self.ssl_verify: return await http_method( url, headers=headers, data=body, timeout=aiohttp.ClientTimeout(total=timeout), ssl=False) else: return await http_method( url, headers=headers, data=body, timeout=aiohttp.ClientTimeout(total=timeout)) except (aiohttp.ClientError, asyncio.TimeoutError) \ as exc: # pragma: no cover self.logger.info('HTTP %s request to %s failed with error %s.', method, url, exc) return str(exc) async def _trigger_event(self, event, *args, **kwargs): """Invoke an event handler.""" run_async = kwargs.pop('run_async', False) ret = None if event in self.handlers: if inspect.iscoroutinefunction(self.handlers[event]) is True: if run_async: task = self.start_background_task(self.handlers[event], *args) task_reference_holder.add(task) task.add_done_callback(task_reference_holder.discard) return task else: try: try: ret = await self.handlers[event](*args) except TypeError: if event == 'disconnect' and \ len(args) == 1: # pragma: no branch # legacy disconnect events do not have a reason # argument return await self.handlers[event]() else: # pragma: no cover raise except asyncio.CancelledError: # pragma: no cover pass except: self.logger.exception(event + ' async handler error') if event == 'connect': # for the connection event we reraise the exception # to stop the connection from completing raise else: if run_async: async def async_handler(): return self.handlers[event](*args) task = self.start_background_task(async_handler) task_reference_holder.add(task) task.add_done_callback(task_reference_holder.discard) return task else: try: try: ret = self.handlers[event](*args) except TypeError: if event == 'disconnect' and \ len(args) == 1: # pragma: no branch # legacy disconnect events do not have a reason # argument ret = self.handlers[event]() else: # pragma: no cover raise except: self.logger.exception(event + ' handler error') if event == 'connect': # for the connection event we reraise the exception # to stop the connection from completing raise return ret async def _read_loop_polling(self): """Read packets by polling the Engine.IO server.""" while self.state == 'connected' and self.write_loop_task: self.logger.info( 'Sending polling GET request to ' + self.base_url) r = await self._send_request( 'GET', self.base_url + self._get_url_timestamp(), timeout=max(self.ping_interval, self.ping_timeout) + 5) if r is None or isinstance(r, str): self.logger.warning( r or 'Connection refused by the server, aborting') await self.queue.put(None) break if r.status < 200 or r.status >= 300: self.logger.warning('Unexpected status code %s in server ' 'response, aborting', r.status) await self.queue.put(None) break try: p = payload.Payload(encoded_payload=(await r.read()).decode( 'utf-8')) except ValueError: self.logger.warning( 'Unexpected packet from server, aborting') await self.queue.put(None) break for pkt in p.packets: await self._receive_packet(pkt) if self.write_loop_task: # pragma: no branch self.logger.info('Waiting for write loop task to end') await self.write_loop_task if self.state == 'connected': await self._trigger_event( 'disconnect', self.reason.TRANSPORT_ERROR, run_async=False) try: base_client.connected_clients.remove(self) except ValueError: # pragma: no cover pass await self._reset() self.logger.info('Exiting read loop task') async def _read_loop_websocket(self): """Read packets from the Engine.IO WebSocket connection.""" while self.state == 'connected' and self.write_loop_task: p = None try: p = await asyncio.wait_for( self.ws.receive(), timeout=self.ping_interval + self.ping_timeout) if not isinstance(p.data, (str, bytes)): # pragma: no cover self.logger.warning( 'Server sent %s packet data %s, aborting', 'close' if p.type in [aiohttp.WSMsgType.CLOSE, aiohttp.WSMsgType.CLOSING] else str(p.type), str(p.data)) await self.queue.put(None) break # the connection is broken p = p.data except asyncio.TimeoutError: self.logger.warning( 'Server has stopped communicating, aborting') await self.queue.put(None) break except aiohttp.client_exceptions.ServerDisconnectedError: self.logger.info( 'Read loop: WebSocket connection was closed, aborting') await self.queue.put(None) break except Exception as e: self.logger.info( 'Unexpected error receiving packet: "%s", aborting', str(e)) await self.queue.put(None) break try: pkt = packet.Packet(encoded_packet=p) except Exception as e: # pragma: no cover self.logger.info( 'Unexpected error decoding packet: "%s", aborting', str(e)) await self.queue.put(None) break await self._receive_packet(pkt) if self.write_loop_task: # pragma: no branch self.logger.info('Waiting for write loop task to end') await self.write_loop_task if self.state == 'connected': await self._trigger_event( 'disconnect', self.reason.TRANSPORT_ERROR, run_async=False) try: base_client.connected_clients.remove(self) except ValueError: # pragma: no cover pass await self._reset() self.logger.info('Exiting read loop task') async def _write_loop(self): """This background task sends packages to the server as they are pushed to the send queue. """ while self.state == 'connected': # to simplify the timeout handling, use the maximum of the # ping interval and ping timeout as timeout, with an extra 5 # seconds grace period timeout = max(self.ping_interval, self.ping_timeout) + 5 packets = None try: packets = [await asyncio.wait_for(self.queue.get(), timeout)] except (self.queue_empty, asyncio.TimeoutError): self.logger.error('packet queue is empty, aborting') break except asyncio.CancelledError: # pragma: no cover break if packets == [None]: self.queue.task_done() packets = [] else: while True: try: packets.append(self.queue.get_nowait()) except self.queue_empty: break if packets[-1] is None: packets = packets[:-1] self.queue.task_done() break if not packets: # empty packet list returned -> connection closed break if self.current_transport == 'polling': p = payload.Payload(packets=packets) r = await self._send_request( 'POST', self.base_url, body=p.encode(), headers={'Content-Type': 'text/plain'}, timeout=self.request_timeout) for pkt in packets: self.queue.task_done() if r is None or isinstance(r, str): self.logger.warning( r or 'Connection refused by the server, aborting') break if r.status < 200 or r.status >= 300: self.logger.warning('Unexpected status code %s in server ' 'response, aborting', r.status) break else: # websocket try: for pkt in packets: if pkt.binary: await self.ws.send_bytes(pkt.encode()) else: await self.ws.send_str(pkt.encode()) self.queue.task_done() except (aiohttp.client_exceptions.ServerDisconnectedError, BrokenPipeError, OSError): self.logger.info( 'Write loop: WebSocket connection was closed, ' 'aborting') break self.logger.info('Exiting write loop task') self.write_loop_task = None ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1793228809.0360932 python_engineio-4.15.1/src/engineio/async_drivers/0000755000175000017500000000000015270500011021614 5ustar00miguelmiguel././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1791413117.6822586 python_engineio-4.15.1/src/engineio/async_drivers/__init__.py0000644000175000017500000000000015261545576023744 0ustar00miguelmiguel././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1791413117.6825724 python_engineio-4.15.1/src/engineio/async_drivers/_websocket_wsgi.py0000644000175000017500000000200215261545576025367 0ustar00miguelmiguelimport simple_websocket class SimpleWebSocketWSGI: # pragma: no cover """ This wrapper class provides a threading WebSocket interface that is compatible with eventlet's implementation. """ def __init__(self, handler, server, **kwargs): self.app = handler self.server_args = kwargs def __call__(self, environ, start_response): self.ws = simple_websocket.Server(environ, **self.server_args) ret = self.app(self) if self.ws.mode == 'gunicorn': raise StopIteration() elif self.ws.mode == 'werkzeug': raise ConnectionError() return ret def close(self): if self.ws.connected: self.ws.close() def send(self, message): try: return self.ws.send(message) except simple_websocket.ConnectionClosed: raise OSError() def wait(self): try: return self.ws.receive() except simple_websocket.ConnectionClosed: return None ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1791413117.6825724 python_engineio-4.15.1/src/engineio/async_drivers/aiohttp.py0000644000175000017500000000716115261545576023674 0ustar00miguelmiguelimport inspect import sys from aiohttp.web import Response, WebSocketResponse def create_route(app, engineio_server, engineio_endpoint): """This function sets up the engine.io endpoint as a route for the application. Note that both GET and POST requests must be hooked up on the engine.io endpoint. """ app.router.add_get(engineio_endpoint, engineio_server.handle_request) app.router.add_post(engineio_endpoint, engineio_server.handle_request) app.router.add_route('OPTIONS', engineio_endpoint, engineio_server.handle_request) def translate_request(request): """This function takes the arguments passed to the request handler and uses them to generate a WSGI compatible environ dictionary. """ environ = { 'wsgi.input': request.content, 'wsgi.errors': sys.stderr, 'wsgi.version': (1, 0), 'wsgi.async': True, 'wsgi.multithread': False, 'wsgi.multiprocess': False, 'wsgi.run_once': False, 'SERVER_SOFTWARE': 'aiohttp', 'REQUEST_METHOD': request.method, 'QUERY_STRING': request.query_string or '', 'RAW_URI': request.path_qs, 'SERVER_PROTOCOL': f'HTTP/{request.version[0]}.{request.version[1]}', 'REMOTE_ADDR': '127.0.0.1', 'REMOTE_PORT': '0', 'SERVER_NAME': 'aiohttp', 'SERVER_PORT': '0', 'aiohttp.request': request } for hdr_name, hdr_value in request.headers.items(): hdr_name = hdr_name.upper() if hdr_name == 'CONTENT-TYPE': environ['CONTENT_TYPE'] = hdr_value continue elif hdr_name == 'CONTENT-LENGTH': environ['CONTENT_LENGTH'] = hdr_value continue key = 'HTTP_%s' % hdr_name.replace('-', '_') if key in environ: hdr_value = f'{environ[key]},{hdr_value}' environ[key] = hdr_value environ['wsgi.url_scheme'] = environ.get('HTTP_X_FORWARDED_PROTO', 'http') environ['PATH_INFO'] = request.path environ['SCRIPT_NAME'] = '' return environ def make_response(status, headers, payload, environ): """This function generates an appropriate response object for this async mode. """ return Response(body=payload, status=int(status.split()[0]), headers=headers) class WebSocket: # pragma: no cover """ This wrapper class provides a aiohttp WebSocket interface that is somewhat compatible with eventlet's implementation. """ def __init__(self, handler, server): self.handler = handler self.server = server self._sock = None async def __call__(self, environ): request = environ['aiohttp.request'] self._sock = WebSocketResponse( max_msg_size=self.server.max_http_buffer_size) await self._sock.prepare(request) self.environ = environ await self.handler(self) return self._sock async def close(self): await self._sock.close() async def send(self, message): if isinstance(message, bytes): f = self._sock.send_bytes else: f = self._sock.send_str if inspect.iscoroutinefunction(f): await f(message) else: f(message) async def wait(self): msg = await self._sock.receive() if not isinstance(msg.data, bytes) and \ not isinstance(msg.data, str): raise OSError() return msg.data _async = { 'asyncio': True, 'create_route': create_route, 'translate_request': translate_request, 'make_response': make_response, 'websocket': WebSocket, } ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1791413117.6827126 python_engineio-4.15.1/src/engineio/async_drivers/asgi.py0000644000175000017500000002653415261545576023154 0ustar00miguelmiguelimport inspect import os import sys from engineio.static_files import get_static_file class ASGIApp: """ASGI application middleware for Engine.IO. This middleware dispatches traffic to an Engine.IO application. It can also serve a list of static files to the client, or forward unrelated HTTP traffic to another ASGI application. :param engineio_server: The Engine.IO server. Must be an instance of the ``engineio.AsyncServer`` class. :param static_files: A dictionary with static file mapping rules. See the documentation for details on this argument. :param other_asgi_app: A separate ASGI app that receives all other traffic. :param engineio_path: The endpoint where the Engine.IO application should be installed. The default value is appropriate for most cases. With a value of ``None``, all incoming traffic is directed to the Engine.IO server, with the assumption that routing, if necessary, is handled by a different layer. When this option is set to ``None``, ``static_files`` and ``other_asgi_app`` are ignored. :param on_startup: function to be called on application startup; can be coroutine :param on_shutdown: function to be called on application shutdown; can be coroutine Example usage:: import engineio import uvicorn eio = engineio.AsyncServer() app = engineio.ASGIApp(eio, static_files={ '/': {'content_type': 'text/html', 'filename': 'index.html'}, '/index.html': {'content_type': 'text/html', 'filename': 'index.html'}, }) uvicorn.run(app, '127.0.0.1', 5000) """ def __init__(self, engineio_server, other_asgi_app=None, static_files=None, engineio_path='engine.io', on_startup=None, on_shutdown=None): self.engineio_server = engineio_server self.other_asgi_app = other_asgi_app self.engineio_path = engineio_path if self.engineio_path is not None: if not self.engineio_path.startswith('/'): self.engineio_path = '/' + self.engineio_path if not self.engineio_path.endswith('/'): self.engineio_path += '/' self.static_files = static_files or {} self.on_startup = on_startup self.on_shutdown = on_shutdown async def __call__(self, scope, receive, send): if scope['type'] == 'lifespan': await self.lifespan(scope, receive, send) elif scope['type'] in ['http', 'websocket'] and ( self.engineio_path is None or self._ensure_trailing_slash(scope['path']).startswith( self.engineio_path)): await self.engineio_server.handle_request(scope, receive, send) else: static_file = get_static_file(scope['path'], self.static_files) \ if scope['type'] == 'http' and self.static_files else None if static_file and os.path.exists(static_file['filename']): await self.serve_static_file(static_file, receive, send) elif self.other_asgi_app is not None: await self.other_asgi_app(scope, receive, send) else: await self.not_found(receive, send) async def serve_static_file(self, static_file, receive, send): # pragma: no cover event = await receive() if event['type'] == 'http.request': with open(static_file['filename'], 'rb') as f: payload = f.read() await send({'type': 'http.response.start', 'status': 200, 'headers': [(b'Content-Type', static_file[ 'content_type'].encode('utf-8'))]}) await send({'type': 'http.response.body', 'body': payload}) async def lifespan(self, scope, receive, send): if self.other_asgi_app is not None and self.on_startup is None and \ self.on_shutdown is None: # let the other ASGI app handle lifespan events await self.other_asgi_app(scope, receive, send) return while True: event = await receive() if event['type'] == 'lifespan.startup': if self.on_startup: try: await self.on_startup() \ if inspect.iscoroutinefunction(self.on_startup) \ else self.on_startup() except: await send({'type': 'lifespan.startup.failed'}) return await send({'type': 'lifespan.startup.complete'}) elif event['type'] == 'lifespan.shutdown': if self.on_shutdown: try: await self.on_shutdown() \ if inspect.iscoroutinefunction(self.on_shutdown) \ else self.on_shutdown() except: await send({'type': 'lifespan.shutdown.failed'}) return await send({'type': 'lifespan.shutdown.complete'}) return async def not_found(self, receive, send): """Return a 404 Not Found error to the client.""" await send({'type': 'http.response.start', 'status': 404, 'headers': [(b'Content-Type', b'text/plain')]}) await send({'type': 'http.response.body', 'body': b'Not Found'}) def _ensure_trailing_slash(self, path): if not path.endswith('/'): path += '/' return path async def translate_request(scope, receive, send): class AwaitablePayload: # pragma: no cover def __init__(self, event): self.event = event self.payload = None async def read(self, length=None): if self.payload is None and event['type'] == 'http.request': # read payload from http request self.payload = self.event.get('body') or b'' while self.event.get('more_body'): self.event = await receive() if self.event['type'] == 'http.request': if length is None or len(self.payload) < length: self.payload += self.event.get('body') or b'' if length is None: r = self.payload self.payload = b'' else: r = self.payload[:length] self.payload = self.payload[length:] return r event = await receive() if event['type'] not in ['http.request', 'websocket.connect']: return {} raw_uri = scope['path'] query_string = '' if 'query_string' in scope and scope['query_string']: try: query_string = scope['query_string'].decode('utf-8') except UnicodeDecodeError: pass else: raw_uri += '?' + query_string environ = { 'wsgi.input': AwaitablePayload(event), 'wsgi.errors': sys.stderr, 'wsgi.version': (1, 0), 'wsgi.async': True, 'wsgi.multithread': False, 'wsgi.multiprocess': False, 'wsgi.run_once': False, 'SERVER_SOFTWARE': 'asgi', 'REQUEST_METHOD': scope.get('method', 'GET'), 'PATH_INFO': scope['path'], 'QUERY_STRING': query_string, 'RAW_URI': raw_uri, 'SCRIPT_NAME': '', 'SERVER_PROTOCOL': 'HTTP/1.1', 'REMOTE_ADDR': '127.0.0.1', 'REMOTE_PORT': '0', 'SERVER_NAME': 'asgi', 'SERVER_PORT': '0', 'asgi.receive': receive, 'asgi.send': send, 'asgi.scope': scope, } for hdr_name, hdr_value in scope['headers']: try: hdr_name = hdr_name.upper().decode('utf-8') hdr_value = hdr_value.decode('utf-8') except UnicodeDecodeError: # skip header if it cannot be decoded continue if hdr_name == 'CONTENT-TYPE': environ['CONTENT_TYPE'] = hdr_value continue elif hdr_name == 'CONTENT-LENGTH': environ['CONTENT_LENGTH'] = hdr_value continue key = 'HTTP_%s' % hdr_name.replace('-', '_') if key in environ: hdr_value = f'{environ[key]},{hdr_value}' environ[key] = hdr_value environ['wsgi.url_scheme'] = environ.get('HTTP_X_FORWARDED_PROTO', 'http') return environ async def make_response(status, headers, payload, environ): headers = [(h[0].encode('utf-8'), h[1].encode('utf-8')) for h in headers] if environ['asgi.scope']['type'] == 'websocket': if status.startswith('200 '): await environ['asgi.send']({'type': 'websocket.accept', 'headers': headers}) else: if payload: reason = payload.decode('utf-8') \ if isinstance(payload, bytes) else str(payload) await environ['asgi.send']({'type': 'websocket.close', 'reason': reason}) else: await environ['asgi.send']({'type': 'websocket.close'}) return await environ['asgi.send']({'type': 'http.response.start', 'status': int(status.split(' ')[0]), 'headers': headers}) await environ['asgi.send']({'type': 'http.response.body', 'body': payload}) class WebSocket: # pragma: no cover """ This wrapper class provides an asgi WebSocket interface that is somewhat compatible with eventlet's implementation. """ def __init__(self, handler, server): self.handler = handler self.asgi_receive = None self.asgi_send = None async def __call__(self, environ): self.asgi_receive = environ['asgi.receive'] self.asgi_send = environ['asgi.send'] await self.asgi_send({'type': 'websocket.accept'}) await self.handler(self) return '' # send nothing as response async def close(self): try: await self.asgi_send({'type': 'websocket.close'}) except Exception: # if the socket is already close we don't care pass async def send(self, message): msg_bytes = None msg_text = None if isinstance(message, bytes): msg_bytes = message else: msg_text = message await self.asgi_send({'type': 'websocket.send', 'bytes': msg_bytes, 'text': msg_text}) async def wait(self): event = await self.asgi_receive() if event['type'] != 'websocket.receive': raise OSError() if event.get('bytes', None) is not None: return event['bytes'] elif event.get('text', None) is not None: return event['text'] else: # pragma: no cover raise OSError() _async = { 'asyncio': True, 'translate_request': translate_request, 'make_response': make_response, 'websocket': WebSocket, } ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1791413117.6828265 python_engineio-4.15.1/src/engineio/async_drivers/eventlet.py0000644000175000017500000000330715261545576024050 0ustar00miguelmiguelfrom eventlet.green.threading import Event from eventlet import queue, sleep, spawn from eventlet.websocket import WebSocketWSGI as _WebSocketWSGI class EventletThread: # pragma: no cover """Thread class that uses eventlet green threads. Eventlet's own Thread class has a strange bug that causes _DummyThread objects to be created and leaked, since they are never garbage collected. """ def __init__(self, target, args=None, kwargs=None): self.target = target self.args = args or () self.kwargs = kwargs or {} self.g = None def start(self): self.g = spawn(self.target, *self.args, **self.kwargs) def join(self): if self.g: return self.g.wait() class WebSocketWSGI(_WebSocketWSGI): # pragma: no cover def __init__(self, handler, server): try: super().__init__( handler, max_frame_length=int(server.max_http_buffer_size)) except TypeError: # pragma: no cover # older versions of eventlet do not support a max frame size super().__init__(handler) self._sock = None def __call__(self, environ, start_response): if 'eventlet.input' not in environ: raise RuntimeError('You need to use the eventlet server. ' 'See the Deployment section of the ' 'documentation for more information.') self._sock = environ['eventlet.input'].get_socket() return super().__call__(environ, start_response) _async = { 'thread': EventletThread, 'queue': queue.Queue, 'queue_empty': queue.Empty, 'event': Event, 'websocket': WebSocketWSGI, 'sleep': sleep, } ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1791413117.6828785 python_engineio-4.15.1/src/engineio/async_drivers/gevent.py0000644000175000017500000000562215261545576023514 0ustar00miguelmiguelimport gevent from gevent import queue from gevent.event import Event try: # use gevent-websocket if installed import geventwebsocket # noqa SimpleWebSocketWSGI = None except ImportError: # pragma: no cover # fallback to simple_websocket when gevent-websocket is not installed from engineio.async_drivers._websocket_wsgi import SimpleWebSocketWSGI class Thread(gevent.Greenlet): # pragma: no cover """ This wrapper class provides gevent Greenlet interface that is compatible with the standard library's Thread class. """ def __init__(self, target, args=[], kwargs={}): super().__init__(target, *args, **kwargs) def _run(self): return self.run() if SimpleWebSocketWSGI is not None: class WebSocketWSGI(SimpleWebSocketWSGI): # pragma: no cover """ This wrapper class provides a gevent WebSocket interface that is compatible with eventlet's implementation, using the simple-websocket package. """ def __init__(self, handler, server): # to avoid the requirement that the standard library is # monkey-patched, here we pass the gevent versions of the # concurrency and networking classes required by simple-websocket import gevent.event import gevent.selectors super().__init__(handler, server, thread_class=Thread, event_class=gevent.event.Event, selector_class=gevent.selectors.DefaultSelector) else: class WebSocketWSGI: # pragma: no cover """ This wrapper class provides a gevent WebSocket interface that is compatible with eventlet's implementation, using the gevent-websocket package. """ def __init__(self, handler, server): self.app = handler def __call__(self, environ, start_response): if 'wsgi.websocket' not in environ: raise RuntimeError('The gevent-websocket server is not ' 'configured appropriately. ' 'See the Deployment section of the ' 'documentation for more information.') self._sock = environ['wsgi.websocket'] self.environ = environ self.version = self._sock.version self.path = self._sock.path self.origin = self._sock.origin self.protocol = self._sock.protocol return self.app(self) def close(self): return self._sock.close() def send(self, message): return self._sock.send(message) def wait(self): return self._sock.receive() _async = { 'thread': Thread, 'queue': queue.JoinableQueue, 'queue_empty': queue.Empty, 'event': Event, 'websocket': WebSocketWSGI, 'sleep': gevent.sleep, } ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1791413117.6829333 python_engineio-4.15.1/src/engineio/async_drivers/gevent_uwsgi.py0000644000175000017500000001350215261545576024726 0ustar00miguelmiguelimport gevent from gevent import queue from gevent.event import Event from gevent import selectors import uwsgi _websocket_available = hasattr(uwsgi, 'websocket_handshake') class Thread(gevent.Greenlet): # pragma: no cover """ This wrapper class provides gevent Greenlet interface that is compatible with the standard library's Thread class. """ def __init__(self, target, args=[], kwargs={}): super().__init__(target, *args, **kwargs) def _run(self): return self.run() class uWSGIWebSocket: # pragma: no cover """ This wrapper class provides a uWSGI WebSocket interface that is compatible with eventlet's implementation. """ def __init__(self, handler, server): self.app = handler self._sock = None self.received_messages = [] def __call__(self, environ, start_response): self._sock = uwsgi.connection_fd() self.environ = environ uwsgi.websocket_handshake() self._req_ctx = None if hasattr(uwsgi, 'request_context'): # uWSGI >= 2.1.x with support for api access across-greenlets self._req_ctx = uwsgi.request_context() else: # use event and queue for sending messages self._event = Event() self._send_queue = queue.Queue() # spawn a select greenlet def select_greenlet_runner(fd, event): """Sets event when data becomes available to read on fd.""" sel = selectors.DefaultSelector() sel.register(fd, selectors.EVENT_READ) try: while True: sel.select() event.set() except gevent.GreenletExit: sel.unregister(fd) self._select_greenlet = gevent.spawn( select_greenlet_runner, self._sock, self._event) self.app(self) uwsgi.disconnect() return '' # send nothing as response def close(self): """Disconnects uWSGI from the client.""" if self._req_ctx is None: # better kill it here in case wait() is not called again self._select_greenlet.kill() self._event.set() def _send(self, msg): """Transmits message either in binary or UTF-8 text mode, depending on its type.""" if isinstance(msg, bytes): method = uwsgi.websocket_send_binary else: method = uwsgi.websocket_send if self._req_ctx is not None: method(msg, request_context=self._req_ctx) else: method(msg) def _decode_received(self, msg): """Returns either bytes or str, depending on message type.""" if not isinstance(msg, bytes): # already decoded - do nothing return msg # only decode from utf-8 if message is not binary data type = ord(msg[0:1]) if type >= 48: # no binary return msg.decode('utf-8') # binary message, don't try to decode return msg def send(self, msg): """Queues a message for sending. Real transmission is done in wait method. Sends directly if uWSGI version is new enough.""" if self._req_ctx is not None: self._send(msg) else: self._send_queue.put(msg) self._event.set() def wait(self): """Waits and returns received messages. If running in compatibility mode for older uWSGI versions, it also sends messages that have been queued by send(). A return value of None means that connection was closed. This must be called repeatedly. For uWSGI < 2.1.x it must be called from the main greenlet.""" while True: if self._req_ctx is not None: try: msg = uwsgi.websocket_recv(request_context=self._req_ctx) except OSError: # connection closed self.close() return None return self._decode_received(msg) else: if self.received_messages: return self.received_messages.pop(0) # we wake up at least every 3 seconds to let uWSGI # do its ping/ponging event_set = self._event.wait(timeout=3) if event_set: self._event.clear() # maybe there is something to send msgs = [] while True: try: msgs.append(self._send_queue.get(block=False)) except gevent.queue.Empty: break for msg in msgs: try: self._send(msg) except OSError: self.close() return None # maybe there is something to receive, if not, at least # ensure uWSGI does its ping/ponging while True: try: msg = uwsgi.websocket_recv_nb() except OSError: # connection closed self.close() return None if msg: # message available self.received_messages.append( self._decode_received(msg)) else: break if self.received_messages: return self.received_messages.pop(0) _async = { 'thread': Thread, 'queue': queue.JoinableQueue, 'queue_empty': queue.Empty, 'event': Event, 'websocket': uWSGIWebSocket if _websocket_available else None, 'sleep': gevent.sleep, } ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1791413117.6830132 python_engineio-4.15.1/src/engineio/async_drivers/sanic.py0000644000175000017500000001061715261545576023321 0ustar00miguelmiguelimport sys from urllib.parse import urlsplit try: # pragma: no cover from sanic.response import HTTPResponse try: from sanic.server.protocols.websocket_protocol import WebSocketProtocol except ImportError: from sanic.websocket import WebSocketProtocol except ImportError: HTTPResponse = None WebSocketProtocol = None def create_route(app, engineio_server, engineio_endpoint): # pragma: no cover """This function sets up the engine.io endpoint as a route for the application. Note that both GET and POST requests must be hooked up on the engine.io endpoint. """ app.add_route(engineio_server.handle_request, engineio_endpoint, methods=['GET', 'POST', 'OPTIONS']) try: app.enable_websocket() except AttributeError: # ignore, this version does not support websocket pass def translate_request(request): # pragma: no cover """This function takes the arguments passed to the request handler and uses them to generate a WSGI compatible environ dictionary. """ class AwaitablePayload: def __init__(self, payload): self.payload = payload or b'' async def read(self, length=None): if length is None: r = self.payload self.payload = b'' else: r = self.payload[:length] self.payload = self.payload[length:] return r uri_parts = urlsplit(request.url) environ = { 'wsgi.input': AwaitablePayload(request.body), 'wsgi.errors': sys.stderr, 'wsgi.version': (1, 0), 'wsgi.async': True, 'wsgi.multithread': False, 'wsgi.multiprocess': False, 'wsgi.run_once': False, 'SERVER_SOFTWARE': 'sanic', 'REQUEST_METHOD': request.method, 'QUERY_STRING': uri_parts.query or '', 'RAW_URI': request.url, 'SERVER_PROTOCOL': 'HTTP/' + request.version, 'REMOTE_ADDR': '127.0.0.1', 'REMOTE_PORT': '0', 'SERVER_NAME': 'sanic', 'SERVER_PORT': '0', 'sanic.request': request } for hdr_name, hdr_value in request.headers.items(): hdr_name = hdr_name.upper() if hdr_name == 'CONTENT-TYPE': environ['CONTENT_TYPE'] = hdr_value continue elif hdr_name == 'CONTENT-LENGTH': environ['CONTENT_LENGTH'] = hdr_value continue key = 'HTTP_%s' % hdr_name.replace('-', '_') if key in environ: hdr_value = f'{environ[key]},{hdr_value}' environ[key] = hdr_value environ['wsgi.url_scheme'] = environ.get('HTTP_X_FORWARDED_PROTO', 'http') path_info = uri_parts.path environ['PATH_INFO'] = path_info environ['SCRIPT_NAME'] = '' return environ def make_response(status, headers, payload, environ): # pragma: no cover """This function generates an appropriate response object for this async mode. """ headers_dict = {} content_type = None for h in headers: if h[0].lower() == 'content-type': content_type = h[1] else: headers_dict[h[0]] = h[1] return HTTPResponse(body=payload, content_type=content_type, status=int(status.split()[0]), headers=headers_dict) class WebSocket: # pragma: no cover """ This wrapper class provides a sanic WebSocket interface that is somewhat compatible with eventlet's implementation. """ def __init__(self, handler, server): self.handler = handler self.server = server self._sock = None async def __call__(self, environ): request = environ['sanic.request'] protocol = request.transport.get_protocol() self._sock = await protocol.websocket_handshake(request) self.environ = environ await self.handler(self) return self.server._ok() async def close(self): await self._sock.close() async def send(self, message): await self._sock.send(message) async def wait(self): data = await self._sock.recv() if not isinstance(data, bytes) and \ not isinstance(data, str): raise OSError() return data _async = { 'asyncio': True, 'create_route': create_route, 'translate_request': translate_request, 'make_response': make_response, 'websocket': WebSocket if WebSocketProtocol else None, } ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1791413117.6830826 python_engineio-4.15.1/src/engineio/async_drivers/threading.py0000644000175000017500000000071715261545576024171 0ustar00miguelmiguelimport queue import threading import time from engineio.async_drivers._websocket_wsgi import SimpleWebSocketWSGI class DaemonThread(threading.Thread): # pragma: no cover def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs, daemon=True) _async = { 'thread': DaemonThread, 'queue': queue.Queue, 'queue_empty': queue.Empty, 'event': threading.Event, 'websocket': SimpleWebSocketWSGI, 'sleep': time.sleep, } ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1791413117.6831603 python_engineio-4.15.1/src/engineio/async_drivers/tornado.py0000644000175000017500000001342415261545576023671 0ustar00miguelmiguelimport asyncio import inspect import sys from urllib.parse import urlsplit from .. import exceptions import tornado.web import tornado.websocket def get_tornado_handler(engineio_server): class Handler(tornado.websocket.WebSocketHandler): # pragma: no cover def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) if isinstance(engineio_server.cors_allowed_origins, str): if engineio_server.cors_allowed_origins == '*': self.allowed_origins = None else: self.allowed_origins = [ engineio_server.cors_allowed_origins] else: self.allowed_origins = engineio_server.cors_allowed_origins self.receive_queue = asyncio.Queue() async def get(self, *args, **kwargs): if self.request.headers.get('Upgrade', '').lower() == 'websocket': ret = super().get(*args, **kwargs) if inspect.iscoroutine(ret): await ret else: await engineio_server.handle_request(self) async def open(self, *args, **kwargs): # this is the handler for the websocket request asyncio.ensure_future(engineio_server.handle_request(self)) async def post(self, *args, **kwargs): await engineio_server.handle_request(self) async def options(self, *args, **kwargs): await engineio_server.handle_request(self) async def on_message(self, message): await self.receive_queue.put(message) async def get_next_message(self): return await self.receive_queue.get() def on_close(self): self.receive_queue.put_nowait(None) def check_origin(self, origin): if self.allowed_origins is None or origin in self.allowed_origins: return True return super().check_origin(origin) def get_compression_options(self): # enable compression return {} return Handler def translate_request(handler): """This function takes the arguments passed to the request handler and uses them to generate a WSGI compatible environ dictionary. """ class AwaitablePayload: def __init__(self, payload): self.payload = payload or b'' async def read(self, length=None): if length is None: r = self.payload self.payload = b'' else: r = self.payload[:length] self.payload = self.payload[length:] return r payload = handler.request.body uri_parts = urlsplit(handler.request.path) full_uri = handler.request.path if handler.request.query: # pragma: no cover full_uri += '?' + handler.request.query environ = { 'wsgi.input': AwaitablePayload(payload), 'wsgi.errors': sys.stderr, 'wsgi.version': (1, 0), 'wsgi.async': True, 'wsgi.multithread': False, 'wsgi.multiprocess': False, 'wsgi.run_once': False, 'SERVER_SOFTWARE': 'aiohttp', 'REQUEST_METHOD': handler.request.method, 'QUERY_STRING': handler.request.query or '', 'RAW_URI': full_uri, 'SERVER_PROTOCOL': 'HTTP/%s' % handler.request.version, 'REMOTE_ADDR': '127.0.0.1', 'REMOTE_PORT': '0', 'SERVER_NAME': 'aiohttp', 'SERVER_PORT': '0', 'tornado.handler': handler } for hdr_name, hdr_value in handler.request.headers.items(): hdr_name = hdr_name.upper() if hdr_name == 'CONTENT-TYPE': environ['CONTENT_TYPE'] = hdr_value continue elif hdr_name == 'CONTENT-LENGTH': environ['CONTENT_LENGTH'] = hdr_value continue key = 'HTTP_%s' % hdr_name.replace('-', '_') environ[key] = hdr_value environ['wsgi.url_scheme'] = environ.get('HTTP_X_FORWARDED_PROTO', 'http') path_info = uri_parts.path environ['PATH_INFO'] = path_info environ['SCRIPT_NAME'] = '' return environ def make_response(status, headers, payload, environ): """This function generates an appropriate response object for this async mode. """ tornado_handler = environ['tornado.handler'] try: tornado_handler.set_status(int(status.split()[0])) except RuntimeError: # pragma: no cover # for websocket connections Tornado does not accept a response, since # it already emitted the 101 status code return for header, value in headers: tornado_handler.set_header(header, value) tornado_handler.write(payload) tornado_handler.finish() class WebSocket: # pragma: no cover """ This wrapper class provides a tornado WebSocket interface that is somewhat compatible with eventlet's implementation. """ def __init__(self, handler, server): self.handler = handler self.tornado_handler = None async def __call__(self, environ): self.tornado_handler = environ['tornado.handler'] self.environ = environ await self.handler(self) async def close(self): self.tornado_handler.close() async def send(self, message): try: self.tornado_handler.write_message( message, binary=isinstance(message, bytes)) except tornado.websocket.WebSocketClosedError: raise exceptions.EngineIOError() async def wait(self): msg = await self.tornado_handler.get_next_message() if not isinstance(msg, bytes) and \ not isinstance(msg, str): raise OSError() return msg _async = { 'asyncio': True, 'translate_request': translate_request, 'make_response': make_response, 'websocket': WebSocket, } ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1793219637.0057132 python_engineio-4.15.1/src/engineio/async_server.py0000644000175000017500000007052015270456065022045 0ustar00miguelmiguelimport asyncio import inspect import urllib from . import base_server from . import exceptions from . import packet from . import async_socket # this set is used to keep references to background tasks to prevent them from # being garbage collected mid-execution. Solution taken from # https://docs.python.org/3/library/asyncio-task.html#asyncio.create_task task_reference_holder = set() class AsyncServer(base_server.BaseServer): """An Engine.IO server for asyncio. This class implements a fully compliant Engine.IO web server with support for websocket and long-polling transports, compatible with the asyncio framework on Python 3.5 or newer. :param async_mode: The asynchronous model to use. See the Deployment section in the documentation for a description of the available options. Valid async modes are "aiohttp", "sanic", "tornado" and "asgi". If this argument is not given, "aiohttp" is tried first, followed by "sanic", "tornado", and finally "asgi". The first async mode that has all its dependencies installed is the one that is chosen. :param ping_interval: The interval in seconds at which the server pings the client. The default is 25 seconds. For advanced control, a two element tuple can be given, where the first number is the ping interval and the second is a grace period added by the server. :param ping_timeout: The time in seconds that the client waits for the server to respond before disconnecting. The default is 20 seconds. :param max_http_buffer_size: The maximum size that is accepted for incoming messages. The default is 1,000,000 bytes. In spite of its name, the value set in this argument is enforced for HTTP long-polling and WebSocket connections. :param allow_upgrades: Whether to allow transport upgrades or not. :param http_compression: Whether to compress packages when using the polling transport. :param compression_threshold: Only compress messages when their byte size is greater than this value. :param cookie: If set to a string, it is the name of the HTTP cookie the server sends back tot he client containing the client session id. If set to a dictionary, the ``'name'`` key contains the cookie name and other keys define cookie attributes, where the value of each attribute can be a string, a callable with no arguments, or a boolean. If set to ``None`` (the default), a cookie is not sent to the client. :param cors_allowed_origins: Origin or list of origins that are allowed to connect to this server. Only the same origin is allowed by default. Set this argument to ``'*'`` or ``['*']`` to allow all origins, or to ``[]`` to disable CORS handling. :param cors_credentials: Whether credentials (cookies, authentication) are allowed in requests to this server. :param logger: To enable logging set to ``True`` or pass a logger object to use. To disable logging set to ``False``. Note that fatal errors are logged even when ``logger`` is ``False``. :param json: An alternative JSON module to use for encoding and decoding packets. Custom JSON modules must have ``dumps`` and ``loads`` functions that are compatible with the standard library versions. This is a process-wide setting, all instantiated servers and clients must use the same JSON module. :param async_handlers: If set to ``True``, run message event handlers in non-blocking threads. To run handlers synchronously, set to ``False``. The default is ``True``. :param monitor_clients: If set to ``True``, a background task will ensure inactive clients are closed. Set to ``False`` to disable the monitoring task (not recommended). The default is ``True``. :param transports: The list of allowed transports. Valid transports are ``'polling'`` and ``'websocket'``. Defaults to ``['polling', 'websocket']``. :param kwargs: Reserved for future extensions, any additional parameters given as keyword arguments will be silently ignored. """ def is_asyncio_based(self): return True def async_modes(self): return ['aiohttp', 'sanic', 'tornado', 'asgi'] def attach(self, app, engineio_path='engine.io'): """Attach the Engine.IO server to an application.""" engineio_path = engineio_path.strip('/') self._async['create_route'](app, self, f'/{engineio_path}/') async def send(self, sid, data): """Send a message to a client. :param sid: The session id of the recipient client. :param data: The data to send to the client. Data can be of type ``str``, ``bytes``, ``list`` or ``dict``. If a ``list`` or ``dict``, the data will be serialized as JSON. Note: this method is a coroutine. """ await self.send_packet(sid, packet.Packet(packet.MESSAGE, data=data)) async def send_packet(self, sid, pkt): """Send a raw packet to a client. :param sid: The session id of the recipient client. :param pkt: The packet to send to the client. Note: this method is a coroutine. """ try: socket = self._get_socket(sid) except KeyError: # the socket is not available self.logger.warning('Cannot send to sid %s', sid) return await socket.send(pkt) async def get_session(self, sid): """Return the user session for a client. :param sid: The session id of the client. The return value is a dictionary. Modifications made to this dictionary are not guaranteed to be preserved. If you want to modify the user session, use the ``session`` context manager instead. """ socket = self._get_socket(sid) return socket.session async def save_session(self, sid, session): """Store the user session for a client. :param sid: The session id of the client. :param session: The session dictionary. """ socket = self._get_socket(sid) socket.session = session def session(self, sid): """Return the user session for a client with context manager syntax. :param sid: The session id of the client. This is a context manager that returns the user session dictionary for the client. Any changes that are made to this dictionary inside the context manager block are saved back to the session. Example usage:: @eio.on('connect') def on_connect(sid, environ): username = authenticate_user(environ) if not username: return False with eio.session(sid) as session: session['username'] = username @eio.on('message') def on_message(sid, msg): async with eio.session(sid) as session: print('received message from ', session['username']) """ class _session_context_manager: def __init__(self, server, sid): self.server = server self.sid = sid self.session = None async def __aenter__(self): self.session = await self.server.get_session(sid) return self.session async def __aexit__(self, *args): await self.server.save_session(sid, self.session) return _session_context_manager(self, sid) async def disconnect(self, sid=None): """Disconnect a client. :param sid: The session id of the client to close. If this parameter is not given, then all clients are closed. Note: this method is a coroutine. """ if sid is not None: try: socket = self._get_socket(sid) except KeyError: # pragma: no cover # the socket was already closed or gone pass else: await socket.close(reason=self.reason.SERVER_DISCONNECT) if sid in self.sockets: # pragma: no cover del self.sockets[sid] else: await asyncio.wait([ asyncio.create_task(client.close( reason=self.reason.SERVER_DISCONNECT)) for client in self.sockets.values() ]) self.sockets = {} async def handle_request(self, *args, **kwargs): """Handle an HTTP request from the client. This is the entry point of the Engine.IO application. This function returns the HTTP response to deliver to the client. Note: this method is a coroutine. """ translate_request = self._async['translate_request'] if inspect.iscoroutinefunction(translate_request): environ = await translate_request(*args, **kwargs) else: environ = translate_request(*args, **kwargs) if self.cors_allowed_origins != []: # Validate the origin header if present # This is important for WebSocket more than for HTTP, since # browsers only apply CORS controls to HTTP. origin = environ.get('HTTP_ORIGIN') if origin: allowed_origins = self._cors_allowed_origins(environ) if allowed_origins is not None and origin not in \ allowed_origins: self._log_error_once( origin + ' is not an accepted origin.', 'bad-origin') return await self._make_response( self._bad_request( origin + ' is not an accepted origin.'), environ) method = environ['REQUEST_METHOD'] query = urllib.parse.parse_qs(environ.get('QUERY_STRING', '')) sid = query['sid'][0] if 'sid' in query else None jsonp = False jsonp_index = None # make sure the client uses an allowed transport transport = query.get('transport', ['polling'])[0] if transport not in self.transports: self._log_error_once('Invalid transport', 'bad-transport') return await self._make_response( self._bad_request('Invalid transport'), environ) # make sure the client speaks a compatible Engine.IO version sid = query['sid'][0] if 'sid' in query else None if sid is None and query.get('EIO') != ['4']: self._log_error_once( 'The client is using an unsupported version of the Socket.IO ' 'or Engine.IO protocols', 'bad-version' ) return await self._make_response(self._bad_request( 'The client is using an unsupported version of the Socket.IO ' 'or Engine.IO protocols' ), environ) if 'j' in query: jsonp = True try: jsonp_index = int(query['j'][0]) except (ValueError, KeyError, IndexError): # Invalid JSONP index number pass if jsonp and jsonp_index is None: self._log_error_once('Invalid JSONP index number', 'bad-jsonp-index') r = self._bad_request('Invalid JSONP index number') elif method == 'GET': upgrade_header = environ.get('HTTP_UPGRADE').lower() \ if 'HTTP_UPGRADE' in environ else None if sid is None: # transport must be one of 'polling' or 'websocket'. # if 'websocket', the HTTP_UPGRADE header must match. if transport == 'polling' \ or transport == upgrade_header == 'websocket': r = await self._handle_connect(environ, transport, jsonp_index) else: self._log_error_once('Invalid websocket upgrade', 'bad-upgrade') r = self._bad_request('Invalid websocket upgrade') else: if sid not in self.sockets: self._log_error_once(f'Invalid session {sid}', 'bad-sid') r = self._bad_request(f'Invalid session {sid}') else: try: socket = self._get_socket(sid) except KeyError as e: # pragma: no cover self._log_error_once(f'{e} {sid}', 'bad-sid') r = self._bad_request(f'{e} {sid}') else: if not socket.connected: # cannot accept requests before connecting self._log_error_once( f'Socket for session {sid} is not connected', 'not-connected') r = self._bad_request('Not connected') elif socket.upgraded: # cannot accept HTTP requests once upgraded self._log_error_once( f'Unexpected HTTP request for session {sid}', 'unexpected-http') r = self._bad_request('Unexpected HTTP request') elif transport != 'polling' and \ transport != upgrade_header: self._log_error_once( f'Invalid transport for session {sid}', 'bad-transport') r = self._bad_request('Invalid transport') else: try: packets = await socket.handle_get_request( environ) if isinstance(packets, list): r = self._ok(packets, jsonp_index=jsonp_index) else: r = packets except exceptions.EngineIOError: if sid in self.sockets: # pragma: no cover await self.disconnect(sid) r = self._bad_request() if sid in self.sockets and \ self.sockets[sid].closed: del self.sockets[sid] elif method == 'POST': if sid is None or sid not in self.sockets: self._log_error_once(f'Invalid session {sid}', 'bad-sid') r = self._bad_request(f'Invalid session {sid}') else: socket = self._get_socket(sid) if not socket.connected: # cannot accept requests before connecting self._log_error_once( f'Socket for session {sid} is not connected', 'not-connected') r = self._bad_request('Not connected') elif not socket.upgraded: try: await socket.handle_post_request(environ) r = self._ok(jsonp_index=jsonp_index) except exceptions.EngineIOError: if sid in self.sockets: # pragma: no cover await self.disconnect(sid) r = self._bad_request() except: # pragma: no cover # for any other unexpected errors, we log the error # and keep going self.logger.exception('post request handler error') r = self._ok(jsonp_index=jsonp_index) else: # cannot accept HTTP requests once upgraded self._log_error_once( f'Unexpected HTTP request for session {sid}', 'unexpected-http') r = self._bad_request('Unexpected HTTP request') elif method == 'OPTIONS': r = self._ok() else: self.logger.warning('Method %s not supported', method) r = self._method_not_found() if not isinstance(r, dict): return r if self.http_compression and \ len(r['response']) >= self.compression_threshold: encodings = [e.split(';')[0].strip() for e in environ.get('HTTP_ACCEPT_ENCODING', '').split(',')] for encoding in encodings: if encoding in self.compression_methods: r['response'] = \ getattr(self, '_' + encoding)(r['response']) r['headers'] += [('Content-Encoding', encoding)] break return await self._make_response(r, environ) async def shutdown(self): """Stop Socket.IO background tasks. This method stops background activity initiated by the Socket.IO server. It must be called before shutting down the web server. """ self.logger.info('Socket.IO is shutting down') if self.service_task_event: # pragma: no cover self.service_task_event.set() await self.service_task_handle self.service_task_handle = None def start_background_task(self, target, *args, **kwargs): """Start a background task using the appropriate async model. This is a utility function that applications can use to start a background task using the method that is compatible with the selected async mode. :param target: the target function to execute. :param args: arguments to pass to the function. :param kwargs: keyword arguments to pass to the function. The return value is a ``asyncio.Task`` object. """ return asyncio.ensure_future(target(*args, **kwargs)) async def sleep(self, seconds=0): """Sleep for the requested amount of time using the appropriate async model. This is a utility function that applications can use to put a task to sleep without having to worry about using the correct call for the selected async mode. Note: this method is a coroutine. """ return await asyncio.sleep(seconds) def create_queue(self, *args, **kwargs): """Create a queue object using the appropriate async model. This is a utility function that applications can use to create a queue without having to worry about using the correct call for the selected async mode. For asyncio based async modes, this returns an instance of ``asyncio.Queue``. """ return asyncio.Queue(*args, **kwargs) def get_queue_empty_exception(self): """Return the queue empty exception for the appropriate async model. This is a utility function that applications can use to work with a queue without having to worry about using the correct call for the selected async mode. For asyncio based async modes, this returns an instance of ``asyncio.QueueEmpty``. """ return asyncio.QueueEmpty def create_event(self, *args, **kwargs): """Create an event object using the appropriate async model. This is a utility function that applications can use to create an event without having to worry about using the correct call for the selected async mode. For asyncio based async modes, this returns an instance of ``asyncio.Event``. """ return asyncio.Event(*args, **kwargs) async def _make_response(self, response_dict, environ): cors_headers = self._cors_headers(environ) make_response = self._async['make_response'] if inspect.iscoroutinefunction(make_response): response = await make_response( response_dict['status'], response_dict['headers'] + cors_headers, response_dict['response'], environ) else: response = make_response( response_dict['status'], response_dict['headers'] + cors_headers, response_dict['response'], environ) return response async def _handle_connect(self, environ, transport, jsonp_index=None): """Handle a client connection request.""" if self.start_service_task: # start the service task to monitor connected clients self.start_service_task = False self.service_task_handle = self.start_background_task( self._service_task) sid = self.generate_id() s = async_socket.AsyncSocket(self, sid) self.sockets[sid] = s pkt = packet.Packet(packet.OPEN, { 'sid': sid, 'upgrades': self._upgrades(sid, transport), 'pingTimeout': int(self.ping_timeout * 1000), 'pingInterval': int( self.ping_interval + self.ping_interval_grace_period) * 1000, 'maxPayload': self.max_http_buffer_size, }) await s.send(pkt) ret = await self._trigger_event('connect', sid, environ, run_async=False) if ret is not None and ret is not True: del self.sockets[sid] self.logger.warning('Application rejected connection') return self._unauthorized(ret or None) s.schedule_ping() if transport == 'websocket': ret = await s.handle_get_request(environ) if s.closed and sid in self.sockets: # websocket connection ended, so we are done del self.sockets[sid] return ret else: s.connected = True headers = None if self.cookie: if isinstance(self.cookie, dict): headers = [( 'Set-Cookie', self._generate_sid_cookie(sid, self.cookie) )] else: headers = [( 'Set-Cookie', self._generate_sid_cookie(sid, { 'name': self.cookie, 'path': '/', 'SameSite': 'Lax' }) )] try: return self._ok(await s.poll(), headers=headers, jsonp_index=jsonp_index) except exceptions.QueueEmpty: return self._bad_request() async def _trigger_event(self, event, *args, **kwargs): """Invoke an event handler.""" run_async = kwargs.pop('run_async', False) ret = None if event in self.handlers: if inspect.iscoroutinefunction(self.handlers[event]): async def run_async_handler(): try: try: return await self.handlers[event](*args) except TypeError: if event == 'disconnect' and \ len(args) == 2: # pragma: no branch # legacy disconnect events do not have a reason # argument return await self.handlers[event](args[0]) else: # pragma: no cover raise except asyncio.CancelledError: # pragma: no cover pass except: self.logger.exception(event + ' async handler error') if event == 'connect': # if connect handler raised error we reject the # connection return False if run_async: ret = self.start_background_task(run_async_handler) task_reference_holder.add(ret) ret.add_done_callback(task_reference_holder.discard) else: ret = await run_async_handler() else: async def run_sync_handler(): try: try: return self.handlers[event](*args) except TypeError: if event == 'disconnect' and \ len(args) == 2: # pragma: no branch # legacy disconnect events do not have a reason # argument return self.handlers[event](args[0]) else: # pragma: no cover raise except: self.logger.exception(event + ' handler error') if event == 'connect': # if connect handler raised error we reject the # connection return False if run_async: ret = self.start_background_task(run_sync_handler) task_reference_holder.add(ret) ret.add_done_callback(task_reference_holder.discard) else: ret = await run_sync_handler() return ret async def _service_task(self): # pragma: no cover """Monitor connected clients and clean up those that time out.""" loop = asyncio.get_running_loop() self.service_task_event = self.create_event() while not self.service_task_event.is_set(): if len(self.sockets) == 0: # nothing to do try: await asyncio.wait_for(self.service_task_event.wait(), timeout=self.ping_timeout) break except asyncio.TimeoutError: continue # go through the entire client list in a ping interval cycle sleep_interval = self.ping_timeout / len(self.sockets) try: # iterate over the current clients for s in self.sockets.copy().values(): if s.closed: try: del self.sockets[s.sid] except KeyError: # the socket could have also been removed by # the _get_socket() method from another thread pass elif not s.closing: await s.check_ping_timeout() try: await asyncio.wait_for(self.service_task_event.wait(), timeout=sleep_interval) raise KeyboardInterrupt() except asyncio.TimeoutError: continue except ( SystemExit, KeyboardInterrupt, asyncio.CancelledError, GeneratorExit, ): self.logger.info('service task canceled') break except: if loop.is_closed(): self.logger.info('event loop is closed, exiting service ' 'task') break # an unexpected exception has occurred, log it and continue self.logger.exception('service task exception') ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1793131846.9625921 python_engineio-4.15.1/src/engineio/async_socket.py0000644000175000017500000002512215270202507022013 0ustar00miguelmiguelimport asyncio import sys import time from . import base_socket from . import exceptions from . import packet from . import payload class AsyncSocket(base_socket.BaseSocket): async def poll(self): """Wait for packets to send to the client.""" try: packets = [await asyncio.wait_for( self.queue.get(), self.server.ping_interval + self.server.ping_timeout)] self.queue.task_done() except (asyncio.TimeoutError, asyncio.CancelledError): raise exceptions.QueueEmpty() if packets == [None]: return [] while True: try: pkt = self.queue.get_nowait() self.queue.task_done() if pkt is None: self.queue.put_nowait(None) break packets.append(pkt) except asyncio.QueueEmpty: break return packets async def receive(self, pkt): """Receive packet from the client.""" self.server.logger.info('%s: Received packet %s data %s', self.sid, packet.packet_names[pkt.packet_type], pkt.data if not isinstance(pkt.data, bytes) else '') if pkt.packet_type == packet.PONG: self.schedule_ping() elif pkt.packet_type == packet.MESSAGE: await self.server._trigger_event( 'message', self.sid, pkt.data, run_async=self.server.async_handlers) elif pkt.packet_type == packet.UPGRADE: await self.send(packet.Packet(packet.NOOP)) elif pkt.packet_type == packet.CLOSE: await self.close(wait=False, abort=True, reason=self.server.reason.CLIENT_DISCONNECT) else: raise exceptions.UnknownPacketError() async def check_ping_timeout(self): """Make sure the client is still sending pings.""" if self.closed: raise exceptions.SocketIsClosedError() last_ping = self.last_ping if last_ping and time.time() - last_ping > self.server.ping_timeout: self.server.logger.info('%s: Client is gone, closing socket', self.sid) # Passing abort=False here will cause close() to write a # CLOSE packet. This has the effect of updating half-open sockets # to their correct state of disconnected await self.close(wait=False, abort=False, reason=self.server.reason.PING_TIMEOUT) return False return True async def send(self, pkt): """Send a packet to the client.""" if not await self.check_ping_timeout(): return else: await self.queue.put(pkt) self.server.logger.info('%s: Sending packet %s data %s', self.sid, packet.packet_names[pkt.packet_type], pkt.data if not isinstance(pkt.data, bytes) else '') async def handle_get_request(self, environ): """Handle a long-polling GET request from the client.""" connections = [ s.strip() for s in environ.get('HTTP_CONNECTION', '').lower().split(',')] transport = environ.get('HTTP_UPGRADE', '').lower() if 'upgrade' in connections and transport in self.upgrade_protocols: self.server.logger.info('%s: Received request to upgrade to %s', self.sid, transport) return await getattr(self, '_upgrade_' + transport)(environ) if self.upgrading or self.upgraded: # we are upgrading to WebSocket, do not return any more packets # through the polling endpoint return [packet.Packet(packet.NOOP)] try: packets = await self.poll() except exceptions.QueueEmpty: exc = sys.exc_info() await self.close(wait=False, reason=self.server.reason.TRANSPORT_ERROR) raise exc[1].with_traceback(exc[2]) return packets async def handle_post_request(self, environ): """Handle a long-polling POST request from the client.""" length = int(environ.get('CONTENT_LENGTH', '0')) if length > self.server.max_http_buffer_size: raise exceptions.ContentTooLongError() else: body = (await environ['wsgi.input'].read(length)).decode('utf-8') p = payload.Payload(encoded_payload=body) for pkt in p.packets: await self.receive(pkt) async def close(self, wait=True, abort=False, reason=None): """Close the socket connection.""" if not self.closed and not self.closing: self.closing = True await self.server._trigger_event( 'disconnect', self.sid, reason or self.server.reason.SERVER_DISCONNECT, run_async=False) if not abort: await self.send(packet.Packet(packet.CLOSE)) self.closed = True if wait: await self.queue.join() def schedule_ping(self): # only schedule a new ping if the previous ping wait cycle completed if self.last_ping: self.last_ping = None self.server.start_background_task(self._send_ping) async def _send_ping(self): await asyncio.sleep(self.server.ping_interval) if not self.closing and not self.closed: self.last_ping = time.time() await self.send(packet.Packet(packet.PING)) async def _upgrade_websocket(self, environ): """Upgrade the connection from polling to websocket.""" if self.upgraded: raise OSError('Socket has been upgraded already') if self.server._async['websocket'] is None: # the selected async mode does not support websocket return self.server._bad_request() ws = self.server._async['websocket']( self._websocket_handler, self.server) return await ws(environ) async def _websocket_handler(self, ws): """Engine.IO handler for websocket transport.""" async def websocket_wait(): data = await ws.wait() if data and len(data) > self.server.max_http_buffer_size: raise ValueError('packet is too large') return data if self.connected: # the socket was already connected, so this is an upgrade self.upgrading = True # hold packet sends during the upgrade try: pkt = await websocket_wait() except OSError: # pragma: no cover return decoded_pkt = packet.Packet(encoded_packet=pkt) if decoded_pkt.packet_type != packet.PING or \ decoded_pkt.data != 'probe': self.server.logger.info( '%s: Failed websocket upgrade, no PING packet', self.sid) self.upgrading = False return await ws.send(packet.Packet(packet.PONG, data='probe').encode()) await self.queue.put(packet.Packet(packet.NOOP)) # end poll try: pkt = await websocket_wait() except OSError: # pragma: no cover self.upgrading = False return decoded_pkt = packet.Packet(encoded_packet=pkt) if decoded_pkt.packet_type != packet.UPGRADE: self.upgraded = False self.server.logger.info( ('%s: Failed websocket upgrade, expected UPGRADE packet, ' 'received %s instead.'), self.sid, pkt) self.upgrading = False return self.upgraded = True self.upgrading = False else: self.connected = True self.upgraded = True # start separate writer thread async def writer(): while True: packets = None try: packets = await self.poll() except exceptions.QueueEmpty: break if not packets: # empty packet list returned -> connection closed break try: for pkt in packets: await ws.send(pkt.encode()) except: break await ws.close() writer_task = asyncio.ensure_future(writer()) self.server.logger.info( '%s: Upgrade to websocket successful', self.sid) while True: p = None wait_task = asyncio.ensure_future(websocket_wait()) try: p = await asyncio.wait_for( wait_task, self.server.ping_interval + self.server.ping_timeout) except asyncio.CancelledError: # pragma: no cover # there is a bug (https://bugs.python.org/issue30508) in # asyncio that causes a "Task exception never retrieved" error # to appear when wait_task raises an exception before it gets # cancelled. Calling wait_task.exception() prevents the error # from being issued in Python 3.6, but causes other errors in # other versions, so we run it with all errors suppressed and # hope for the best. try: wait_task.exception() except: pass break except: break if p is None: # connection closed by client break pkt = packet.Packet(encoded_packet=p) try: await self.receive(pkt) except exceptions.UnknownPacketError: # pragma: no cover pass except exceptions.SocketIsClosedError: # pragma: no cover self.server.logger.info('Receive error -- socket is closed') break except: # pragma: no cover # if we get an unexpected exception we log the error and exit # the connection properly self.server.logger.exception('Unknown receive error') await self.queue.put(None) # unlock the writer task so it can exit await asyncio.wait_for(writer_task, timeout=None) await self.close(wait=False, abort=True, reason=self.server.reason.TRANSPORT_CLOSE) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1793120875.6907432 python_engineio-4.15.1/src/engineio/base_client.py0000644000175000017500000001362115270155154021604 0ustar00miguelmiguelimport logging import signal import threading import time import urllib from . import packet default_logger = logging.getLogger('engineio.client') connected_clients = [] def signal_handler(sig, frame): """SIGINT handler. Disconnect all active clients and then invoke the original signal handler. """ for client in connected_clients[:]: if not client.is_asyncio_based(): client.disconnect() if callable(original_signal_handler): return original_signal_handler(sig, frame) else: # pragma: no cover # Handle case where no original SIGINT handler was present. return signal.default_int_handler(sig, frame) original_signal_handler = None class BaseClient: event_names = ['connect', 'disconnect', 'message'] class reason: """Disconnection reasons.""" #: Client-initiated disconnection. CLIENT_DISCONNECT = 'client disconnect' #: Server-initiated disconnection. SERVER_DISCONNECT = 'server disconnect' #: Transport error. TRANSPORT_ERROR = 'transport error' def __init__(self, logger=False, json=None, request_timeout=5, http_session=None, ssl_verify=True, handle_sigint=True, websocket_extra_options=None, timestamp_requests=True): global original_signal_handler if handle_sigint and original_signal_handler is None and \ threading.current_thread() == threading.main_thread(): original_signal_handler = signal.signal(signal.SIGINT, signal_handler) self.handlers = {} self.base_url = None self.transports = None self.current_transport = None self.sid = None self.upgrades = None self.ping_interval = None self.ping_timeout = None self.http = None self.http_factory = None if http_session is not None: if callable(http_session): self.http_factory = http_session else: self.http = http_session self.external_http = self.http is not None self.handle_sigint = handle_sigint self.ws = None self.read_loop_task = None self.write_loop_task = None self.queue = self.create_queue() self.queue_empty = self.get_queue_empty_exception() self.state = 'disconnected' self.ssl_verify = ssl_verify self.websocket_extra_options = websocket_extra_options or {} self.timestamp_requests = timestamp_requests if json is not None: packet.Packet.json = json 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()) self.request_timeout = request_timeout def is_asyncio_based(self): return False def on(self, event, handler=None): """Register an event handler. :param event: The event name. Can be ``'connect'``, ``'message'`` or ``'disconnect'``. :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. Example usage:: # as a decorator: @eio.on('connect') def connect_handler(): print('Connection request') # as a method: def message_handler(msg): print('Received message: ', msg) eio.send('response') eio.on('message', message_handler) """ if event not in self.event_names: raise ValueError('Invalid event') def set_handler(handler): self.handlers[event] = handler return handler if handler is None: return set_handler set_handler(handler) def transport(self): """Return the name of the transport currently in use. The possible values returned by this function are ``'polling'`` and ``'websocket'``. """ return self.current_transport def _reset(self): self.state = 'disconnected' self.sid = None def _get_engineio_url(self, url, engineio_path, transport): """Generate the Engine.IO connection URL.""" engineio_path = engineio_path.strip('/') parsed_url = urllib.parse.urlparse(url) if transport == 'polling': scheme = 'http' elif transport == 'websocket': scheme = 'ws' else: # pragma: no cover raise ValueError('invalid transport') if parsed_url.scheme in ['https', 'wss']: scheme += 's' return ('{scheme}://{netloc}/{path}/?{query}' '{sep}transport={transport}&EIO=4').format( scheme=scheme, netloc=parsed_url.netloc, path=engineio_path, query=parsed_url.query, sep='&' if parsed_url.query else '', transport=transport) def _get_url_timestamp(self): """Generate the Engine.IO query string timestamp.""" if not self.timestamp_requests: return '' return '&t=' + str(time.time()) def create_queue(self, *args, **kwargs): # pragma: no cover """Create a queue object.""" raise NotImplementedError('must be implemented in a subclass') def get_queue_empty_exception(self): # pragma: no cover """Return the queue empty exception raised by queues created by the ``create_queue()`` method. """ raise NotImplementedError('must be implemented in a subclass') ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1791413117.6838403 python_engineio-4.15.1/src/engineio/base_server.py0000644000175000017500000003465315261545576021657 0ustar00miguelmiguelimport base64 import gzip import importlib import io import logging import secrets import zlib from . import packet from . import payload default_logger = logging.getLogger('engineio.server') class BaseServer: compression_methods = ['gzip', 'deflate'] event_names = ['connect', 'disconnect', 'message'] valid_transports = ['polling', 'websocket'] _default_monitor_clients = True sequence_number = 0 class reason: """Disconnection reasons.""" #: Server-initiated disconnection. SERVER_DISCONNECT = 'server disconnect' #: Client-initiated disconnection. CLIENT_DISCONNECT = 'client disconnect' #: Ping timeout. PING_TIMEOUT = 'ping timeout' #: Transport close. TRANSPORT_CLOSE = 'transport close' #: Transport error. TRANSPORT_ERROR = 'transport error' def __init__(self, async_mode=None, ping_interval=25, ping_timeout=20, max_http_buffer_size=1000000, allow_upgrades=True, http_compression=True, compression_threshold=1024, cookie=None, cors_allowed_origins=None, cors_credentials=True, logger=False, json=None, async_handlers=True, monitor_clients=None, transports=None, **kwargs): self.ping_timeout = ping_timeout if isinstance(ping_interval, tuple): self.ping_interval = ping_interval[0] self.ping_interval_grace_period = ping_interval[1] else: self.ping_interval = ping_interval self.ping_interval_grace_period = 0 self.max_http_buffer_size = max_http_buffer_size self.allow_upgrades = allow_upgrades self.http_compression = http_compression self.compression_threshold = compression_threshold self.cookie = cookie self.cors_allowed_origins = cors_allowed_origins self.cors_credentials = cors_credentials self.async_handlers = async_handlers self.sockets = {} self.handlers = {} self.log_message_keys = set() self.start_service_task = monitor_clients \ if monitor_clients is not None else self._default_monitor_clients self.service_task_handle = None self.service_task_event = None if json is not None: packet.Packet.json = json 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()) modes = self.async_modes() if async_mode is not None: modes = [async_mode] if async_mode in modes else [] self._async = None self.async_mode = None for mode in modes: try: self._async = importlib.import_module( 'engineio.async_drivers.' + mode)._async asyncio_based = self._async['asyncio'] \ if 'asyncio' in self._async else False if asyncio_based != self.is_asyncio_based(): continue # pragma: no cover self.async_mode = mode break except ImportError: pass if self.async_mode is None: raise ValueError('Invalid async_mode specified') if self.is_asyncio_based() and \ ('asyncio' not in self._async or not self._async['asyncio']): # pragma: no cover raise ValueError('The selected async_mode is not asyncio ' 'compatible') if not self.is_asyncio_based() and 'asyncio' in self._async and \ self._async['asyncio']: # pragma: no cover raise ValueError('The selected async_mode requires asyncio and ' 'must use the AsyncServer class') if transports is not None: if isinstance(transports, str): transports = [transports] transports = [transport for transport in transports if transport in self.valid_transports] if not transports: raise ValueError('No valid transports provided') self.transports = transports or self.valid_transports self.logger.info('Server initialized for %s.', self.async_mode) def is_asyncio_based(self): return False def async_modes(self): return ['eventlet', 'gevent_uwsgi', 'gevent', 'threading'] def on(self, event, handler=None): """Register an event handler. :param event: The event name. Can be ``'connect'``, ``'message'`` or ``'disconnect'``. :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. Example usage:: # as a decorator: @eio.on('connect') 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) eio.send(sid, 'response') eio.on('message', message_handler) The handler function receives the ``sid`` (session ID) for the client as first argument. The ``'connect'`` event handler receives the WSGI environment as a second argument, and can return ``False`` to reject the connection. The ``'message'`` handler receives the message payload as a second argument. The ``'disconnect'`` handler does not take a second argument. """ if event not in self.event_names: raise ValueError('Invalid event') def set_handler(handler): self.handlers[event] = handler return handler if handler is None: return set_handler set_handler(handler) def transport(self, sid): """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. """ return 'websocket' if self._get_socket(sid).upgraded else 'polling' def create_queue(self, *args, **kwargs): """Create a queue object using the appropriate async model. This is a utility function that applications can use to create a queue without having to worry about using the correct call for the selected async mode. """ return self._async['queue'](*args, **kwargs) def get_queue_empty_exception(self): """Return the queue empty exception for the appropriate async model. This is a utility function that applications can use to work with a queue without having to worry about using the correct call for the selected async mode. """ return self._async['queue_empty'] def create_event(self, *args, **kwargs): """Create an event object using the appropriate async model. This is a utility function that applications can use to create an event without having to worry about using the correct call for the selected async mode. """ return self._async['event'](*args, **kwargs) def generate_id(self): """Generate a unique session id.""" id = base64.b64encode( secrets.token_bytes(12) + self.sequence_number.to_bytes(3, 'big')) self.sequence_number = (self.sequence_number + 1) & 0xffffff return id.decode('utf-8').replace('/', '_').replace('+', '-') def _generate_sid_cookie(self, sid, attributes): """Generate the sid cookie.""" cookie = attributes.get('name', 'io') + '=' + sid for attribute, value in attributes.items(): if attribute == 'name': continue if callable(value): value = value() if value is True: cookie += '; ' + attribute else: cookie += '; ' + attribute + '=' + value return cookie def _upgrades(self, sid, transport): """Return the list of possible upgrades for a client connection.""" if not self.allow_upgrades or self._get_socket(sid).upgraded or \ transport == 'websocket': return [] if self._async['websocket'] is None: # pragma: no cover self._log_error_once( 'The WebSocket transport is not available, you must install a ' 'WebSocket server that is compatible with your async mode to ' 'enable it. See the documentation for details.', 'no-websocket') return [] return ['websocket'] def _get_socket(self, sid): """Return the socket object for a given session.""" try: s = self.sockets[sid] except KeyError: raise KeyError('Session not found') if s.closed: del self.sockets[sid] raise KeyError('Session is disconnected') return s def _ok(self, packets=None, headers=None, jsonp_index=None): """Generate a successful HTTP response.""" if packets is not None: if headers is None: headers = [] headers += [('Content-Type', 'text/plain; charset=UTF-8')] return {'status': '200 OK', 'headers': headers, 'response': payload.Payload(packets=packets).encode( jsonp_index=jsonp_index).encode('utf-8')} else: return {'status': '200 OK', 'headers': [('Content-Type', 'text/plain')], 'response': b'OK'} def _bad_request(self, message=None): """Generate a bad request HTTP error response.""" if message is None: message = 'Bad Request' message = packet.Packet.json.dumps(message) return {'status': '400 BAD REQUEST', 'headers': [('Content-Type', 'text/plain')], 'response': message.encode('utf-8')} def _method_not_found(self): """Generate a method not found HTTP error response.""" return {'status': '405 METHOD NOT FOUND', 'headers': [('Content-Type', 'text/plain')], 'response': b'Method Not Found'} def _unauthorized(self, message=None): """Generate a unauthorized HTTP error response.""" if message is None: message = 'Unauthorized' message = packet.Packet.json.dumps(message) return {'status': '401 UNAUTHORIZED', 'headers': [('Content-Type', 'application/json')], 'response': message.encode('utf-8')} def _cors_allowed_origins(self, environ): if self.cors_allowed_origins is None: allowed_origins = [] if 'wsgi.url_scheme' in environ and 'HTTP_HOST' in environ: allowed_origins.append('{scheme}://{host}'.format( scheme=environ['wsgi.url_scheme'], host=environ['HTTP_HOST'])) if 'HTTP_X_FORWARDED_PROTO' in environ or \ 'HTTP_X_FORWARDED_HOST' in environ: scheme = environ.get( 'HTTP_X_FORWARDED_PROTO', environ['wsgi.url_scheme']).split(',')[0].strip() allowed_origins.append('{scheme}://{host}'.format( scheme=scheme, host=environ.get( 'HTTP_X_FORWARDED_HOST', environ['HTTP_HOST']).split( ',')[0].strip())) elif self.cors_allowed_origins == '*': allowed_origins = None elif isinstance(self.cors_allowed_origins, str): allowed_origins = [self.cors_allowed_origins] elif callable(self.cors_allowed_origins): origin = environ.get('HTTP_ORIGIN') try: is_allowed = self.cors_allowed_origins(origin, environ) except TypeError: is_allowed = self.cors_allowed_origins(origin) allowed_origins = [origin] if is_allowed else [] else: if '*' in self.cors_allowed_origins: allowed_origins = None else: allowed_origins = self.cors_allowed_origins return allowed_origins def _cors_headers(self, environ): """Return the cross-origin-resource-sharing headers.""" if self.cors_allowed_origins == []: # special case, CORS handling is completely disabled return [] headers = [] allowed_origins = self._cors_allowed_origins(environ) if 'HTTP_ORIGIN' in environ and \ (allowed_origins is None or environ['HTTP_ORIGIN'] in allowed_origins): headers = [('Access-Control-Allow-Origin', environ['HTTP_ORIGIN'])] if environ['REQUEST_METHOD'] == 'OPTIONS': headers += [('Access-Control-Allow-Methods', 'OPTIONS, GET, POST')] if 'HTTP_ACCESS_CONTROL_REQUEST_HEADERS' in environ: headers += [('Access-Control-Allow-Headers', environ['HTTP_ACCESS_CONTROL_REQUEST_HEADERS'])] if self.cors_credentials: headers += [('Access-Control-Allow-Credentials', 'true')] return headers def _gzip(self, response): """Apply gzip compression to a response.""" bytesio = io.BytesIO() with gzip.GzipFile(fileobj=bytesio, mode='w') as gz: gz.write(response) return bytesio.getvalue() def _deflate(self, response): """Apply deflate compression to a response.""" return zlib.compress(response) def _log_error_once(self, message, message_key): """Log message with logging.ERROR level the first time, then log with given level.""" if message_key not in self.log_message_keys: self.logger.error(message + ' (further occurrences of this error ' 'will be logged with level INFO)') self.log_message_keys.add(message_key) else: self.logger.info(message) ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1791413117.683984 python_engineio-4.15.1/src/engineio/base_socket.py0000644000175000017500000000064415261545576021632 0ustar00miguelmiguelimport time class BaseSocket: upgrade_protocols = ['websocket'] def __init__(self, server, sid): self.server = server self.sid = sid self.queue = self.server.create_queue() self.last_ping = time.time() self.connected = False self.upgrading = False self.upgraded = False self.closing = False self.closed = False self.session = {} ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1793121108.4022167 python_engineio-4.15.1/src/engineio/client.py0000644000175000017500000007154315270155524020622 0ustar00miguelmiguelfrom base64 import b64encode from http.cookies import SimpleCookie import logging import queue import ssl import threading import time import urllib from engineio.json import JSONDecodeError try: import requests except ImportError: # pragma: no cover requests = None try: import websocket except ImportError: # pragma: no cover websocket = None from . import base_client from . import exceptions from . import packet from . import payload default_logger = logging.getLogger('engineio.client') class Client(base_client.BaseClient): """An Engine.IO client. This class implements a fully compliant Engine.IO web client with support for websocket and long-polling transports. :param logger: To enable logging set to ``True`` or pass a logger object to use. To disable logging set to ``False``. The default is ``False``. Note that fatal errors are logged even when ``logger`` is ``False``. :param json: An alternative JSON module to use for encoding and decoding packets. Custom json modules must have ``dumps`` and ``loads`` functions that are compatible with the standard library versions. This is a process-wide setting, all instantiated servers and clients must use the same JSON module. :param request_timeout: A timeout in seconds for requests. The default is 5 seconds. :param http_session: an initialized ``requests.Session`` object to be used when sending requests to the server. Use it if you need to add special client options such as proxy servers, SSL certificates, custom CA bundle, etc. :param ssl_verify: ``True`` to verify SSL certificates, or ``False`` to skip SSL certificate verification, allowing connections to servers with self signed certificates. The default is ``True``. :param handle_sigint: Set to ``True`` to automatically handle disconnection when the process is interrupted, or to ``False`` to leave interrupt handling to the calling application. Interrupt handling can only be enabled when the client instance is created in the main thread. :param websocket_extra_options: Dictionary containing additional keyword arguments passed to ``websocket.create_connection()``. :param timestamp_requests: If ``True`` a timestamp is added to the query string of Socket.IO requests as a cache-busting measure. Set to ``False`` to disable. """ def connect(self, url, headers=None, transports=None, engineio_path='engine.io'): """Connect to an Engine.IO server. :param url: The URL of the Engine.IO server. It can include custom query string parameters if required by the server. :param headers: A dictionary with custom headers to send with the connection request. :param transports: The list of allowed transports. Valid transports are ``'polling'`` and ``'websocket'``. If not given, the polling transport is connected first, then an upgrade to websocket is attempted. :param engineio_path: The endpoint where the Engine.IO server is installed. The default value is appropriate for most cases. Example usage:: eio = engineio.Client() eio.connect('http://localhost:5000') """ if self.state != 'disconnected': raise ValueError('Client is not in a disconnected state') valid_transports = ['polling', 'websocket'] if transports is not None: if isinstance(transports, str): transports = [transports] transports = [transport for transport in transports if transport in valid_transports] if not transports: raise ValueError('No valid transports provided') self.transports = transports or valid_transports return getattr(self, '_connect_' + self.transports[0])( url, headers or {}, engineio_path) def wait(self): """Wait until the connection with the server ends. Client applications can use this function to block the main thread during the life of the connection. """ if self.read_loop_task: self.read_loop_task.join() def send(self, data): """Send a message to the server. :param data: The data to send to the server. Data can be of type ``str``, ``bytes``, ``list`` or ``dict``. If a ``list`` or ``dict``, the data will be serialized as JSON. """ self._send_packet(packet.Packet(packet.MESSAGE, data=data)) def disconnect(self, abort=False, reason=None): """Disconnect from the server. :param abort: If set to ``True``, do not wait for background tasks associated with the connection to end. """ if self.state == 'connected': self._send_packet(packet.Packet(packet.CLOSE)) self.queue.put(None) self.state = 'disconnecting' self._trigger_event('disconnect', reason or self.reason.CLIENT_DISCONNECT, run_async=False) if self.current_transport == 'websocket': self.ws.close() if not abort: self.read_loop_task.join() self.state = 'disconnected' try: base_client.connected_clients.remove(self) except ValueError: # pragma: no cover pass self._reset() def start_background_task(self, target, *args, **kwargs): """Start a background task. This is a utility function that applications can use to start a background task. :param target: the target function to execute. :param args: arguments to pass to the function. :param kwargs: keyword arguments to pass to the function. This function returns an object that represents the background task, on which the ``join()`` method can be invoked to wait for the task to complete. """ th = threading.Thread(target=target, args=args, kwargs=kwargs, daemon=True) th.start() return th def sleep(self, seconds=0): """Sleep for the requested amount of time.""" return time.sleep(seconds) def create_queue(self, *args, **kwargs): """Create a queue object.""" return queue.Queue(*args, **kwargs) def get_queue_empty_exception(self): """Return the queue empty exception raised by queues created by the ``create_queue()`` method. """ return queue.Empty def create_event(self, *args, **kwargs): """Create an event object.""" return threading.Event(*args, **kwargs) def _reset(self): super()._reset() while True: # pragma: no cover try: self.queue.get_nowait() self.queue.task_done() except self.queue_empty: break if not self.external_http: # pragma: no cover self.http = None def _connect_polling(self, url, headers, engineio_path): """Establish a long-polling connection to the Engine.IO server.""" if requests is None: # pragma: no cover # not installed self.logger.error('requests package is not installed -- cannot ' 'send HTTP requests!') return self.base_url = self._get_engineio_url(url, engineio_path, 'polling') self.logger.info('Attempting polling connection to ' + self.base_url) r = self._send_request( 'GET', self.base_url + self._get_url_timestamp(), headers=headers, timeout=self.request_timeout) if r is None or isinstance(r, str): self._reset() raise exceptions.ConnectionError( r or 'Connection refused by the server') if r.status_code < 200 or r.status_code >= 300: self._reset() try: arg = r.json() except JSONDecodeError: arg = None raise exceptions.ConnectionError( 'Unexpected status code {} in server response'.format( r.status_code), arg) try: p = payload.Payload(encoded_payload=r.content.decode('utf-8')) except ValueError: raise exceptions.ConnectionError( 'Unexpected response from server') from None open_packet = p.packets[0] if open_packet.packet_type != packet.OPEN: raise exceptions.ConnectionError( 'OPEN packet not returned by server') self.logger.info( 'Polling connection accepted with ' + str(open_packet.data)) self.sid = open_packet.data['sid'] self.upgrades = open_packet.data['upgrades'] self.ping_interval = int(open_packet.data['pingInterval']) / 1000.0 self.ping_timeout = int(open_packet.data['pingTimeout']) / 1000.0 self.current_transport = 'polling' self.base_url += '&sid=' + self.sid self.state = 'connected' base_client.connected_clients.append(self) try: self._trigger_event('connect', run_async=False, reraise=True) except Exception as exc: base_client.connected_clients.remove(self) self._reset() raise exceptions.ConnectionError( 'Connect handler failed: ' + str(exc)) for pkt in p.packets[1:]: self._receive_packet(pkt) if 'websocket' in self.upgrades and 'websocket' in self.transports: # attempt to upgrade to websocket if self._connect_websocket(url, headers, engineio_path): # upgrade to websocket succeeded, we're done here return # start background tasks associated with this client self.write_loop_task = self.start_background_task(self._write_loop) self.read_loop_task = self.start_background_task( self._read_loop_polling) def _connect_websocket(self, url, headers, engineio_path): """Establish or upgrade to a WebSocket connection with the server.""" if websocket is None: # pragma: no cover # not installed self.logger.error('websocket-client package not installed, only ' 'polling transport is available') return False websocket_url = self._get_engineio_url(url, engineio_path, 'websocket') if self.sid: self.logger.info( 'Attempting WebSocket upgrade to ' + websocket_url) upgrade = True websocket_url += '&sid=' + self.sid else: upgrade = False self.base_url = websocket_url self.logger.info( 'Attempting WebSocket connection to ' + websocket_url) # get cookies and other settings from the long-polling connection # so that they are preserved when connecting to the WebSocket route cookies = None extra_options = {} if self.http is None and self.http_factory: self.http = self.http_factory() if self.http: # cookies ck = SimpleCookie() for cookie in self.http.cookies: ck[cookie.name] = cookie.value cookies = ck.output(header='', sep=';').strip() for header, value in headers.items(): if header.lower() == 'cookie': if cookies: cookies += '; ' cookies += value del headers[header] break # auth if 'Authorization' not in headers and self.http.auth is not None: if not isinstance(self.http.auth, tuple): # pragma: no cover raise ValueError('Only basic authentication is supported') basic_auth = '{}:{}'.format( self.http.auth[0], self.http.auth[1]).encode('utf-8') basic_auth = b64encode(basic_auth).decode('utf-8') headers['Authorization'] = 'Basic ' + basic_auth # cert # this can be given as ('certfile', 'keyfile') or just 'certfile' if isinstance(self.http.cert, tuple): extra_options['sslopt'] = { 'certfile': self.http.cert[0], 'keyfile': self.http.cert[1]} elif self.http.cert: extra_options['sslopt'] = {'certfile': self.http.cert} # proxies if self.http.proxies: proxy_url = None if websocket_url.startswith('ws://'): proxy_url = self.http.proxies.get( 'ws', self.http.proxies.get('http')) else: # wss:// proxy_url = self.http.proxies.get( 'wss', self.http.proxies.get('https')) if proxy_url: parsed_url = urllib.parse.urlparse( proxy_url if '://' in proxy_url else 'scheme://' + proxy_url) extra_options['http_proxy_host'] = parsed_url.hostname extra_options['http_proxy_port'] = parsed_url.port extra_options['http_proxy_auth'] = ( (parsed_url.username, parsed_url.password) if parsed_url.username or parsed_url.password else None) # verify if isinstance(self.http.verify, str): if 'sslopt' in extra_options: extra_options['sslopt']['ca_certs'] = self.http.verify else: extra_options['sslopt'] = {'ca_certs': self.http.verify} elif not self.http.verify: self.ssl_verify = False if not self.ssl_verify: if 'sslopt' in extra_options: extra_options['sslopt'].update({"cert_reqs": ssl.CERT_NONE}) else: extra_options['sslopt'] = {"cert_reqs": ssl.CERT_NONE} # combine internally generated options with the ones supplied by the # caller. The caller's options take precedence. headers.update(self.websocket_extra_options.pop('header', {})) extra_options['header'] = headers extra_options['cookie'] = cookies extra_options['enable_multithread'] = True extra_options['timeout'] = self.request_timeout extra_options.update(self.websocket_extra_options) try: ws = websocket.create_connection( websocket_url + self._get_url_timestamp(), **extra_options) except (ConnectionError, OSError, websocket.WebSocketException): if upgrade: self.logger.warning( 'WebSocket upgrade failed: connection error') return False else: raise exceptions.ConnectionError('Connection error') if upgrade: p = packet.Packet(packet.PING, data='probe').encode() try: ws.send(p) except Exception as e: # pragma: no cover self.logger.warning( 'WebSocket upgrade failed: unexpected send exception: %s', str(e)) return False try: p = ws.recv() except Exception as e: # pragma: no cover self.logger.warning( 'WebSocket upgrade failed: unexpected recv exception: %s', str(e)) return False pkt = packet.Packet(encoded_packet=p) if pkt.packet_type != packet.PONG or pkt.data != 'probe': self.logger.warning( 'WebSocket upgrade failed: no PONG packet') return False p = packet.Packet(packet.UPGRADE).encode() try: ws.send(p) except Exception as e: # pragma: no cover self.logger.warning( 'WebSocket upgrade failed: unexpected send exception: %s', str(e)) return False self.current_transport = 'websocket' self.logger.info('WebSocket upgrade was successful') else: try: p = ws.recv() except Exception as e: # pragma: no cover raise exceptions.ConnectionError( 'Unexpected recv exception: ' + str(e)) open_packet = packet.Packet(encoded_packet=p) if open_packet.packet_type != packet.OPEN: raise exceptions.ConnectionError('no OPEN packet') self.logger.info( 'WebSocket connection accepted with ' + str(open_packet.data)) self.sid = open_packet.data['sid'] self.upgrades = open_packet.data['upgrades'] self.ping_interval = int(open_packet.data['pingInterval']) / 1000.0 self.ping_timeout = int(open_packet.data['pingTimeout']) / 1000.0 self.current_transport = 'websocket' self.state = 'connected' base_client.connected_clients.append(self) try: self._trigger_event('connect', run_async=False, reraise=True) except Exception as exc: base_client.connected_clients.remove(self) self._reset() raise exceptions.ConnectionError( 'Connect handler failed: ' + str(exc)) self.ws = ws self.ws.settimeout(self.ping_interval + self.ping_timeout) # start background tasks associated with this client self.write_loop_task = self.start_background_task(self._write_loop) self.read_loop_task = self.start_background_task( self._read_loop_websocket) return True def _receive_packet(self, pkt): """Handle incoming packets from the server.""" packet_name = packet.packet_names[pkt.packet_type] \ if pkt.packet_type < len(packet.packet_names) else 'UNKNOWN' self.logger.info( 'Received packet %s data %s', packet_name, pkt.data if not isinstance(pkt.data, bytes) else '') if pkt.packet_type == packet.MESSAGE: self._trigger_event('message', pkt.data, run_async=True) elif pkt.packet_type == packet.PING: self._send_packet(packet.Packet(packet.PONG, pkt.data)) elif pkt.packet_type == packet.CLOSE: self.disconnect(abort=True, reason=self.reason.SERVER_DISCONNECT) elif pkt.packet_type == packet.NOOP: pass else: self.logger.error('Received unexpected packet of type %s', pkt.packet_type) def _send_packet(self, pkt): """Queue a packet to be sent to the server.""" if self.state != 'connected': return self.queue.put(pkt) self.logger.info( 'Sending packet %s data %s', packet.packet_names[pkt.packet_type], pkt.data if not isinstance(pkt.data, bytes) else '') def _send_request( self, method, url, headers=None, body=None, timeout=None): # pragma: no cover if self.http is None: if self.http_factory: self.http = self.http_factory() else: self.http = requests.Session() if not self.ssl_verify: self.http.verify = False try: return self.http.request(method, url, headers=headers, data=body, timeout=timeout) except requests.exceptions.RequestException as exc: self.logger.info('HTTP %s request to %s failed with error %s.', method, url, exc) return str(exc) def _trigger_event(self, event, *args, **kwargs): """Invoke an event handler.""" run_async = kwargs.pop('run_async', False) if event in self.handlers: if run_async: return self.start_background_task(self.handlers[event], *args) else: try: try: return self.handlers[event](*args) except TypeError: if event == 'disconnect' and \ len(args) == 1: # pragma: no branch # legacy disconnect events do not have a reason # argument return self.handlers[event]() else: # pragma: no cover raise except: self.logger.exception(event + ' handler error') if event == 'connect': # for the connection event we reraise the exception # to stop the connection from completing raise def _read_loop_polling(self): """Read packets by polling the Engine.IO server.""" while self.state == 'connected' and self.write_loop_task: self.logger.info( 'Sending polling GET request to ' + self.base_url) r = self._send_request( 'GET', self.base_url + self._get_url_timestamp(), timeout=max(self.ping_interval, self.ping_timeout) + 5) if r is None or isinstance(r, str): self.logger.warning( r or 'Connection refused by the server, aborting') self.queue.put(None) break if r.status_code < 200 or r.status_code >= 300: self.logger.warning('Unexpected status code %s in server ' 'response, aborting', r.status_code) self.queue.put(None) break try: p = payload.Payload(encoded_payload=r.content.decode('utf-8')) except ValueError: self.logger.warning( 'Unexpected packet from server, aborting') self.queue.put(None) break for pkt in p.packets: self._receive_packet(pkt) if self.write_loop_task: # pragma: no branch self.logger.info('Waiting for write loop task to end') try: # the write loop task cleans its own task when it ends, so we # avoid a race condition when using threads with a try/catch self.write_loop_task.join() except AttributeError: # pragma: no cover if self.write_loop_task is not None: raise # for any erros not related to task == None we raise if self.state == 'connected': self._trigger_event('disconnect', self.reason.TRANSPORT_ERROR, run_async=False) try: base_client.connected_clients.remove(self) except ValueError: # pragma: no cover pass self._reset() self.logger.info('Exiting read loop task') def _read_loop_websocket(self): """Read packets from the Engine.IO WebSocket connection.""" while self.state == 'connected' and self.write_loop_task: p = None try: p = self.ws.recv() if len(p) == 0 and not self.ws.connected: # pragma: no cover # websocket client can return an empty string after close raise websocket.WebSocketConnectionClosedException() except websocket.WebSocketTimeoutException: self.logger.warning( 'Server has stopped communicating, aborting') self.queue.put(None) break except websocket.WebSocketConnectionClosedException: self.logger.warning( 'WebSocket connection was closed, aborting') self.queue.put(None) break except Exception as e: # pragma: no cover if type(e) is OSError and e.errno == 9: self.logger.info( 'WebSocket connection is closing, aborting') else: self.logger.info( 'Unexpected error receiving packet: "%s", aborting', str(e)) self.queue.put(None) break try: pkt = packet.Packet(encoded_packet=p) except Exception as e: # pragma: no cover self.logger.info( 'Unexpected error decoding packet: "%s", aborting', str(e)) self.queue.put(None) break self._receive_packet(pkt) if self.write_loop_task: # pragma: no branch self.logger.info('Waiting for write loop task to end') try: # the write loop task cleans its own task when it ends, so we # avoid a race condition when using threads with a try/catch self.write_loop_task.join() except AttributeError: # pragma: no cover if self.write_loop_task is not None: raise # for any erros not related to task == None we raise if self.state == 'connected': self._trigger_event('disconnect', self.reason.TRANSPORT_ERROR, run_async=False) try: base_client.connected_clients.remove(self) except ValueError: # pragma: no cover pass self._reset() self.logger.info('Exiting read loop task') def _write_loop(self): """This background task sends packages to the server as they are pushed to the send queue. """ while self.state == 'connected': # to simplify the timeout handling, use the maximum of the # ping interval and ping timeout as timeout, with an extra 5 # seconds grace period timeout = max(self.ping_interval, self.ping_timeout) + 5 packets = None try: packets = [self.queue.get(timeout=timeout)] except self.queue_empty: self.logger.error('packet queue is empty, aborting') break if packets == [None]: self.queue.task_done() packets = [] else: while True: try: packets.append(self.queue.get(block=False)) except self.queue_empty: break if packets[-1] is None: packets = packets[:-1] self.queue.task_done() break if not packets: # empty packet list returned -> connection closed break if self.current_transport == 'polling': p = payload.Payload(packets=packets) r = self._send_request( 'POST', self.base_url, body=p.encode(), headers={'Content-Type': 'text/plain'}, timeout=self.request_timeout) for pkt in packets: self.queue.task_done() if r is None or isinstance(r, str): self.logger.warning( r or 'Connection refused by the server, aborting') break if r.status_code < 200 or r.status_code >= 300: self.logger.warning('Unexpected status code %s in server ' 'response, aborting', r.status_code) break else: # websocket try: for pkt in packets: encoded_packet = pkt.encode() if pkt.binary: self.ws.send_binary(encoded_packet) else: self.ws.send(encoded_packet) self.queue.task_done() except (websocket.WebSocketConnectionClosedException, BrokenPipeError, OSError): self.logger.warning( 'WebSocket connection was closed, aborting') break self.logger.info('Exiting write loop task') self.write_loop_task = None ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1791413117.6840394 python_engineio-4.15.1/src/engineio/exceptions.py0000644000175000017500000000044415261545576021527 0ustar00miguelmiguelclass EngineIOError(Exception): pass class ContentTooLongError(EngineIOError): pass class UnknownPacketError(EngineIOError): pass class QueueEmpty(EngineIOError): pass class SocketIsClosedError(EngineIOError): pass class ConnectionError(EngineIOError): pass ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1791413117.6844258 python_engineio-4.15.1/src/engineio/json.py0000644000175000017500000000062515261545576020320 0ustar00miguelmiguel"""JSON-compatible module with sane defaults.""" from json import * # noqa: F401, F403 from json import loads as original_loads def _safe_int(s): if len(s) > 100: raise ValueError('Integer is too large') return int(s) def loads(*args, **kwargs): if 'parse_int' not in kwargs: # pragma: no cover kwargs['parse_int'] = _safe_int return original_loads(*args, **kwargs) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1791413117.6844633 python_engineio-4.15.1/src/engineio/middleware.py0000644000175000017500000000724615261545576021472 0ustar00miguelmiguelimport os from engineio.static_files import get_static_file class WSGIApp: """WSGI application middleware for Engine.IO. This middleware dispatches traffic to an Engine.IO application. It can also serve a list of static files to the client, or forward unrelated HTTP traffic to another WSGI application. :param engineio_app: The Engine.IO server. Must be an instance of the ``engineio.Server`` class. :param wsgi_app: The WSGI app that receives all other traffic. :param static_files: A dictionary with static file mapping rules. See the documentation for details on this argument. :param engineio_path: The endpoint where the Engine.IO application should be installed. The default value is appropriate for most cases. Example usage:: import engineio import eventlet eio = engineio.Server() app = engineio.WSGIApp(eio, static_files={ '/': {'content_type': 'text/html', 'filename': 'index.html'}, '/index.html': {'content_type': 'text/html', 'filename': 'index.html'}, }) eventlet.wsgi.server(eventlet.listen(('', 8000)), app) """ def __init__(self, engineio_app, wsgi_app=None, static_files=None, engineio_path='engine.io'): self.engineio_app = engineio_app self.wsgi_app = wsgi_app self.engineio_path = engineio_path if not self.engineio_path.startswith('/'): self.engineio_path = '/' + self.engineio_path if not self.engineio_path.endswith('/'): self.engineio_path += '/' self.static_files = static_files or {} def __call__(self, environ, start_response): if 'gunicorn.socket' in environ: # gunicorn saves the socket under environ['gunicorn.socket'], while # eventlet saves it under environ['eventlet.input']. Eventlet also # stores the socket inside a wrapper class, while gunicon writes it # directly into the environment. To give eventlet's WebSocket # module access to this socket when running under gunicorn, here we # copy the socket to the eventlet format. class Input: def __init__(self, socket): self.socket = socket def get_socket(self): return self.socket environ['eventlet.input'] = Input(environ['gunicorn.socket']) path = environ['PATH_INFO'] if path is not None and path.startswith(self.engineio_path): return self.engineio_app.handle_request(environ, start_response) else: static_file = get_static_file(path, self.static_files) \ if self.static_files else None if static_file and os.path.exists(static_file['filename']): start_response( '200 OK', [('Content-Type', static_file['content_type'])]) with open(static_file['filename'], 'rb') as f: return [f.read()] elif self.wsgi_app is not None: return self.wsgi_app(environ, start_response) return self.not_found(start_response) def not_found(self, start_response): start_response("404 Not Found", [('Content-Type', 'text/plain')]) return [b'Not Found'] class Middleware(WSGIApp): """This class has been renamed to ``WSGIApp`` and is now deprecated.""" def __init__(self, engineio_app, wsgi_app=None, engineio_path='engine.io'): super().__init__(engineio_app, wsgi_app, engineio_path=engineio_path) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1791413117.6845343 python_engineio-4.15.1/src/engineio/packet.py0000644000175000017500000000617615261545576020625 0ustar00miguelmiguelimport base64 from engineio import json as _json (OPEN, CLOSE, PING, PONG, MESSAGE, UPGRADE, NOOP) = (0, 1, 2, 3, 4, 5, 6) packet_names = ['OPEN', 'CLOSE', 'PING', 'PONG', 'MESSAGE', 'UPGRADE', 'NOOP'] binary_types = (bytes, bytearray) class Packet: """Engine.IO packet.""" json = _json def __init__(self, packet_type=NOOP, data=None, encoded_packet=None): self.packet_type = packet_type self.data = data self.encode_cache = None if isinstance(data, str): self.binary = False elif isinstance(data, binary_types): self.binary = True else: self.binary = False if self.binary and self.packet_type != MESSAGE: raise ValueError('Binary packets can only be of type MESSAGE') if encoded_packet is not None: self.decode(encoded_packet) def encode(self, b64=False): """Encode the packet for transmission. Note: as a performance optimization, subsequent calls to this method will return a cached encoded packet, even if the data has changed. """ if self.encode_cache: return self.encode_cache if self.binary: if b64: encoded_packet = 'b' + base64.b64encode(self.data).decode( 'utf-8') else: encoded_packet = self.data else: encoded_packet = str(self.packet_type) if isinstance(self.data, str): encoded_packet += self.data elif isinstance(self.data, dict) or isinstance(self.data, list): encoded_packet += self.json.dumps(self.data, separators=(',', ':')) elif self.data is not None: encoded_packet += str(self.data) self.encode_cache = encoded_packet return encoded_packet def decode(self, encoded_packet): """Decode a transmitted package.""" self.binary = isinstance(encoded_packet, binary_types) if not self.binary and len(encoded_packet) == 0: raise ValueError('Invalid empty packet received') b64 = not self.binary and encoded_packet[0] == 'b' if b64: self.binary = True self.packet_type = MESSAGE self.data = base64.b64decode(encoded_packet[1:]) else: if self.binary and not isinstance(encoded_packet, bytes): encoded_packet = bytes(encoded_packet) if self.binary: self.packet_type = MESSAGE self.data = encoded_packet else: self.packet_type = int(encoded_packet[0]) try: if encoded_packet[1].isnumeric(): # do not allow integer payloads, see # github.com/miguelgrinberg/python-engineio/issues/75 # for background on this decision raise ValueError self.data = self.json.loads(encoded_packet[1:]) except (ValueError, IndexError): self.data = encoded_packet[1:] ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1791413117.684588 python_engineio-4.15.1/src/engineio/payload.py0000644000175000017500000000300315261545576020771 0ustar00miguelmiguelimport urllib from . import packet class Payload: """Engine.IO payload.""" max_decode_packets = 16 def __init__(self, packets=None, encoded_payload=None): self.packets = packets or [] if encoded_payload is not None: self.decode(encoded_payload) def encode(self, jsonp_index=None): """Encode the payload for transmission.""" encoded_payload = '' for pkt in self.packets: if encoded_payload: encoded_payload += '\x1e' encoded_payload += pkt.encode(b64=True) if jsonp_index is not None: encoded_payload = '___eio[' + \ str(jsonp_index) + \ ']("' + \ encoded_payload.replace('"', '\\"') + \ '");' return encoded_payload def decode(self, encoded_payload): """Decode a transmitted payload.""" self.packets = [] if len(encoded_payload) == 0: return # JSONP POST payload starts with 'd=' if encoded_payload.startswith('d='): encoded_payload = urllib.parse.parse_qs( encoded_payload)['d'][0] encoded_packets = encoded_payload.split('\x1e') if len(encoded_packets) > self.max_decode_packets: raise ValueError('Too many packets in payload') self.packets = [packet.Packet(encoded_packet=encoded_packet) for encoded_packet in encoded_packets] ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1793227308.355948 python_engineio-4.15.1/src/engineio/server.py0000644000175000017500000005772615270475054020664 0ustar00miguelmiguelimport logging import urllib from . import base_server from . import exceptions from . import packet from . import socket default_logger = logging.getLogger('engineio.server') class Server(base_server.BaseServer): """An Engine.IO server. This class implements a fully compliant Engine.IO web server with support for websocket and long-polling transports. :param async_mode: The asynchronous model to use. See the Deployment section in the documentation for a description of the available options. Valid async modes are "threading", "eventlet", "gevent" and "gevent_uwsgi". If this argument is not given, "eventlet" is tried first, then "gevent_uwsgi", then "gevent", and finally "threading". The first async mode that has all its dependencies installed is the one that is chosen. :param ping_interval: The interval in seconds at which the server pings the client. The default is 25 seconds. For advanced control, a two element tuple can be given, where the first number is the ping interval and the second is a grace period added by the server. :param ping_timeout: The time in seconds that the client waits for the server to respond before disconnecting. The default is 20 seconds. :param max_http_buffer_size: The maximum size that is accepted for incoming messages. The default is 1,000,000 bytes. In spite of its name, the value set in this argument is enforced for HTTP long-polling and WebSocket connections. :param allow_upgrades: Whether to allow transport upgrades or not. The default is ``True``. :param http_compression: Whether to compress packages when using the polling transport. The default is ``True``. :param compression_threshold: Only compress messages when their byte size is greater than this value. The default is 1024 bytes. :param cookie: If set to a string, it is the name of the HTTP cookie the server sends back tot he client containing the client session id. If set to a dictionary, the ``'name'`` key contains the cookie name and other keys define cookie attributes, where the value of each attribute can be a string, a callable with no arguments, or a boolean. If set to ``None`` (the default), a cookie is not sent to the client. :param cors_allowed_origins: Origin or list of origins that are allowed to connect to this server. Only the same origin is allowed by default. Set this argument to ``'*'`` or ``['*']`` to allow all origins, or to ``[]`` to disable CORS handling. :param cors_credentials: Whether credentials (cookies, authentication) are allowed in requests to this server. The default is ``True``. :param logger: To enable logging set to ``True`` or pass a logger object to use. To disable logging set to ``False``. The default is ``False``. Note that fatal errors are logged even when ``logger`` is ``False``. :param json: An alternative JSON module to use for encoding and decoding packets. Custom JSON modules must have ``dumps`` and ``loads`` functions that are compatible with the standard library versions. This is a process-wide setting, all instantiated servers and clients must use the same JSON module. :param async_handlers: If set to ``True``, run message event handlers in non-blocking threads. To run handlers synchronously, set to ``False``. The default is ``True``. :param monitor_clients: If set to ``True``, a background task will ensure inactive clients are closed. Set to ``False`` to disable the monitoring task (not recommended). The default is ``True``. :param transports: The list of allowed transports. Valid transports are ``'polling'`` and ``'websocket'``. Defaults to ``['polling', 'websocket']``. :param kwargs: Reserved for future extensions, any additional parameters given as keyword arguments will be silently ignored. """ def send(self, sid, data): """Send a message to a client. :param sid: The session id of the recipient client. :param data: The data to send to the client. Data can be of type ``str``, ``bytes``, ``list`` or ``dict``. If a ``list`` or ``dict``, the data will be serialized as JSON. """ self.send_packet(sid, packet.Packet(packet.MESSAGE, data=data)) def send_packet(self, sid, pkt): """Send a raw packet to a client. :param sid: The session id of the recipient client. :param pkt: The packet to send to the client. """ try: socket = self._get_socket(sid) except KeyError: # the socket is not available self.logger.warning('Cannot send to sid %s', sid) return socket.send(pkt) def get_session(self, sid): """Return the user session for a client. :param sid: The session id of the client. The return value is a dictionary. Modifications made to this dictionary are not guaranteed to be preserved unless ``save_session()`` is called, or when the ``session`` context manager is used. """ socket = self._get_socket(sid) return socket.session def save_session(self, sid, session): """Store the user session for a client. :param sid: The session id of the client. :param session: The session dictionary. """ socket = self._get_socket(sid) socket.session = session def session(self, sid): """Return the user session for a client with context manager syntax. :param sid: The session id of the client. This is a context manager that returns the user session dictionary for the client. Any changes that are made to this dictionary inside the context manager block are saved back to the session. Example usage:: @eio.on('connect') def on_connect(sid, environ): username = authenticate_user(environ) if not username: return False with eio.session(sid) as session: session['username'] = username @eio.on('message') def on_message(sid, msg): with eio.session(sid) as session: print('received message from ', session['username']) """ class _session_context_manager: def __init__(self, server, sid): self.server = server self.sid = sid self.session = None def __enter__(self): self.session = self.server.get_session(sid) return self.session def __exit__(self, *args): self.server.save_session(sid, self.session) return _session_context_manager(self, sid) def disconnect(self, sid=None): """Disconnect a client. :param sid: The session id of the client to close. If this parameter is not given, then all clients are closed. """ if sid is not None: try: socket = self._get_socket(sid) except KeyError: # pragma: no cover # the socket was already closed or gone pass else: socket.close(reason=self.reason.SERVER_DISCONNECT) if sid in self.sockets: # pragma: no cover del self.sockets[sid] else: for client in self.sockets.copy().values(): client.close(reason=self.reason.SERVER_DISCONNECT) self.sockets = {} def handle_request(self, environ, start_response): """Handle an HTTP request from the client. This is the entry point of the Engine.IO application, using the same interface as a WSGI application. For the typical usage, this function is invoked by the :class:`Middleware` instance, but it can be invoked directly when the middleware is not used. :param environ: The WSGI environment. :param start_response: The WSGI ``start_response`` function. This function returns the HTTP response body to deliver to the client as a byte sequence. """ if self.cors_allowed_origins != []: # Validate the origin header if present # This is important for WebSocket more than for HTTP, since # browsers only apply CORS controls to HTTP. origin = environ.get('HTTP_ORIGIN') if origin: allowed_origins = self._cors_allowed_origins(environ) if allowed_origins is not None and origin not in \ allowed_origins: self._log_error_once( origin + ' is not an accepted origin.', 'bad-origin') r = self._bad_request('Not an accepted origin.') start_response(r['status'], r['headers']) return [r['response']] method = environ['REQUEST_METHOD'] query = urllib.parse.parse_qs(environ.get('QUERY_STRING', '')) jsonp = False jsonp_index = None # make sure the client uses an allowed transport transport = query.get('transport', ['polling'])[0] if transport not in self.transports: self._log_error_once('Invalid transport', 'bad-transport') r = self._bad_request('Invalid transport') start_response(r['status'], r['headers']) return [r['response']] # make sure the client speaks a compatible Engine.IO version sid = query['sid'][0] if 'sid' in query else None if sid is None and query.get('EIO') != ['4']: self._log_error_once( 'The client is using an unsupported version of the Socket.IO ' 'or Engine.IO protocols', 'bad-version') r = self._bad_request( 'The client is using an unsupported version of the Socket.IO ' 'or Engine.IO protocols') start_response(r['status'], r['headers']) return [r['response']] if 'j' in query: jsonp = True try: jsonp_index = int(query['j'][0]) except (ValueError, KeyError, IndexError): # Invalid JSONP index number pass if jsonp and jsonp_index is None: self._log_error_once('Invalid JSONP index number', 'bad-jsonp-index') r = self._bad_request('Invalid JSONP index number') elif method == 'GET': upgrade_header = environ.get('HTTP_UPGRADE').lower() \ if 'HTTP_UPGRADE' in environ else None if sid is None: # transport must be one of 'polling' or 'websocket'. # if 'websocket', the HTTP_UPGRADE header must match. if transport == 'polling' \ or transport == upgrade_header == 'websocket': r = self._handle_connect(environ, start_response, transport, jsonp_index) else: self._log_error_once('Invalid websocket upgrade', 'bad-upgrade') r = self._bad_request('Invalid websocket upgrade') else: if sid not in self.sockets: self._log_error_once(f'Invalid session {sid}', 'bad-sid') r = self._bad_request(f'Invalid session {sid}') else: try: socket = self._get_socket(sid) except KeyError as e: # pragma: no cover self._log_error_once(f'{e} {sid}', 'bad-sid') r = self._bad_request(f'{e} {sid}') else: if not socket.connected: # cannot accept requests before connecting self._log_error_once( f'Socket for session {sid} is not connected', 'not-connected') r = self._bad_request('Not connected') elif socket.upgraded: # cannot accept HTTP requests once upgraded self._log_error_once( f'Unexpected HTTP request for session {sid}', 'unexpected-http') r = self._bad_request('Unexpected HTTP request') elif transport != 'polling' and \ transport != upgrade_header: self._log_error_once( f'Invalid transport for session {sid}', 'bad-transport') r = self._bad_request('Invalid transport') else: try: packets = socket.handle_get_request( environ, start_response) if isinstance(packets, list): r = self._ok(packets, jsonp_index=jsonp_index) else: r = packets except exceptions.EngineIOError: if sid in self.sockets: # pragma: no cover self.disconnect(sid) r = self._bad_request() if sid in self.sockets and \ self.sockets[sid].closed: del self.sockets[sid] elif method == 'POST': if sid is None or sid not in self.sockets: self._log_error_once(f'Invalid session {sid}', 'bad-sid') r = self._bad_request(f'Invalid session {sid}') else: socket = self._get_socket(sid) if not socket.connected: # cannot accept requests before connecting self._log_error_once( f'Socket for session {sid} is not connected', 'not-connected') r = self._bad_request('Not connected') elif not socket.upgraded: try: socket.handle_post_request(environ) r = self._ok(jsonp_index=jsonp_index) except exceptions.EngineIOError: if sid in self.sockets: # pragma: no cover self.disconnect(sid) r = self._bad_request() except: # pragma: no cover # for any other unexpected errors, we log the error # and keep going self.logger.exception('post request handler error') r = self._ok(jsonp_index=jsonp_index) else: # cannot accept HTTP requests once upgraded self._log_error_once( f'Unexpected HTTP request for session {sid}', 'unexpected-http') r = self._bad_request('Unexpected HTTP request') elif method == 'OPTIONS': r = self._ok() else: self.logger.warning('Method %s not supported', method) r = self._method_not_found() if not isinstance(r, dict): return r if self.http_compression and \ len(r['response']) >= self.compression_threshold: encodings = [e.split(';')[0].strip() for e in environ.get('HTTP_ACCEPT_ENCODING', '').split(',')] for encoding in encodings: if encoding in self.compression_methods: r['response'] = \ getattr(self, '_' + encoding)(r['response']) r['headers'] += [('Content-Encoding', encoding)] break cors_headers = self._cors_headers(environ) start_response(r['status'], r['headers'] + cors_headers) return [r['response']] def shutdown(self): """Stop Socket.IO background tasks. This method stops background activity initiated by the Socket.IO server. It must be called before shutting down the web server. """ self.logger.info('Socket.IO is shutting down') if self.service_task_event: # pragma: no cover self.service_task_event.set() self.service_task_handle.join() self.service_task_handle = None def start_background_task(self, target, *args, **kwargs): """Start a background task using the appropriate async model. This is a utility function that applications can use to start a background task using the method that is compatible with the selected async mode. :param target: the target function to execute. :param args: arguments to pass to the function. :param kwargs: keyword arguments to pass to the function. This function returns an object that represents the background task, on which the ``join()`` methond can be invoked to wait for the task to complete. """ th = self._async['thread'](target=target, args=args, kwargs=kwargs) th.start() return th # pragma: no cover def sleep(self, seconds=0): """Sleep for the requested amount of time using the appropriate async model. This is a utility function that applications can use to put a task to sleep without having to worry about using the correct call for the selected async mode. """ return self._async['sleep'](seconds) def _handle_connect(self, environ, start_response, transport, jsonp_index=None): """Handle a client connection request.""" if self.start_service_task: # start the service task to monitor connected clients self.start_service_task = False self.service_task_handle = self.start_background_task( self._service_task) sid = self.generate_id() s = socket.Socket(self, sid) self.sockets[sid] = s pkt = packet.Packet(packet.OPEN, { 'sid': sid, 'upgrades': self._upgrades(sid, transport), 'pingTimeout': int(self.ping_timeout * 1000), 'pingInterval': int( self.ping_interval + self.ping_interval_grace_period) * 1000, 'maxPayload': self.max_http_buffer_size, }) s.send(pkt) # NOTE: some sections below are marked as "no cover" to workaround # what seems to be a bug in the coverage package. All the lines below # are covered by tests, but some are not reported as such for some # reason ret = self._trigger_event('connect', sid, environ, run_async=False) if ret is not None and ret is not True: # pragma: no cover del self.sockets[sid] self.logger.warning('Application rejected connection') return self._unauthorized(ret or None) s.schedule_ping() if transport == 'websocket': # pragma: no cover ret = s.handle_get_request(environ, start_response) if s.closed and sid in self.sockets: # websocket connection ended, so we are done del self.sockets[sid] return ret else: # pragma: no cover s.connected = True headers = None if self.cookie: if isinstance(self.cookie, dict): headers = [( 'Set-Cookie', self._generate_sid_cookie(sid, self.cookie) )] else: headers = [( 'Set-Cookie', self._generate_sid_cookie(sid, { 'name': self.cookie, 'path': '/', 'SameSite': 'Lax' }) )] try: return self._ok(s.poll(), headers=headers, jsonp_index=jsonp_index) except exceptions.QueueEmpty: return self._bad_request() def _trigger_event(self, event, *args, **kwargs): """Invoke an event handler.""" run_async = kwargs.pop('run_async', False) if event in self.handlers: def run_handler(): try: try: return self.handlers[event](*args) except TypeError: if event == 'disconnect' and \ len(args) == 2: # pragma: no branch # legacy disconnect events do not have a reason # argument return self.handlers[event](args[0]) else: # pragma: no cover raise except: self.logger.exception(event + ' handler error') if event == 'connect': # if connect handler raised error we reject the # connection return False if run_async: return self.start_background_task(run_handler) else: return run_handler() def _service_task(self): # pragma: no cover """Monitor connected clients and clean up those that time out.""" self.service_task_event = self.create_event() while not self.service_task_event.is_set(): if len(self.sockets) == 0: # nothing to do if self.service_task_event.wait(timeout=self.ping_timeout): break continue # go through the entire client list in a ping interval cycle sleep_interval = float(self.ping_timeout) / len(self.sockets) try: # iterate over the current clients for s in self.sockets.copy().values(): if s.closed: try: del self.sockets[s.sid] except KeyError: # the socket could have also been removed by # the _get_socket() method from another thread pass elif not s.closing: s.check_ping_timeout() if self.service_task_event.wait(timeout=sleep_interval): raise KeyboardInterrupt() except (SystemExit, KeyboardInterrupt): self.logger.info('service task canceled') break except: # an unexpected exception has occurred, log it and continue self.logger.exception('service task exception') ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1793131822.634451 python_engineio-4.15.1/src/engineio/socket.py0000644000175000017500000002433515270202457020627 0ustar00miguelmiguelimport sys import time from . import base_socket from . import exceptions from . import packet from . import payload class Socket(base_socket.BaseSocket): """An Engine.IO socket.""" def poll(self): """Wait for packets to send to the client.""" queue_empty = self.server.get_queue_empty_exception() try: packets = [self.queue.get( timeout=self.server.ping_interval + self.server.ping_timeout)] self.queue.task_done() except queue_empty: raise exceptions.QueueEmpty() if packets == [None]: return [] while True: try: pkt = self.queue.get(block=False) self.queue.task_done() if pkt is None: self.queue.put(None) break packets.append(pkt) except queue_empty: break return packets def receive(self, pkt): """Receive packet from the client.""" packet_name = packet.packet_names[pkt.packet_type] \ if pkt.packet_type < len(packet.packet_names) else 'UNKNOWN' self.server.logger.info('%s: Received packet %s data %s', self.sid, packet_name, pkt.data if not isinstance(pkt.data, bytes) else '') if pkt.packet_type == packet.PONG: self.schedule_ping() elif pkt.packet_type == packet.MESSAGE: self.server._trigger_event('message', self.sid, pkt.data, run_async=self.server.async_handlers) elif pkt.packet_type == packet.UPGRADE: self.send(packet.Packet(packet.NOOP)) elif pkt.packet_type == packet.CLOSE: self.close(wait=False, abort=True, reason=self.server.reason.CLIENT_DISCONNECT) else: raise exceptions.UnknownPacketError() def check_ping_timeout(self): """Make sure the client is still responding to pings.""" if self.closed: raise exceptions.SocketIsClosedError() last_ping = self.last_ping if last_ping and time.time() - last_ping > self.server.ping_timeout: self.server.logger.info('%s: Client is gone, closing socket', self.sid) # Passing abort=False here will cause close() to write a # CLOSE packet. This has the effect of updating half-open sockets # to their correct state of disconnected self.close(wait=False, abort=False, reason=self.server.reason.PING_TIMEOUT) return False return True def send(self, pkt): """Send a packet to the client.""" if not self.check_ping_timeout(): return else: self.queue.put(pkt) self.server.logger.info('%s: Sending packet %s data %s', self.sid, packet.packet_names[pkt.packet_type], pkt.data if not isinstance(pkt.data, bytes) else '') def handle_get_request(self, environ, start_response): """Handle a long-polling GET request from the client.""" connections = [ s.strip() for s in environ.get('HTTP_CONNECTION', '').lower().split(',')] transport = environ.get('HTTP_UPGRADE', '').lower() if 'upgrade' in connections and transport in self.upgrade_protocols: self.server.logger.info('%s: Received request to upgrade to %s', self.sid, transport) return getattr(self, '_upgrade_' + transport)(environ, start_response) if self.upgrading or self.upgraded: # we are upgrading to WebSocket, do not return any more packets # through the polling endpoint return [packet.Packet(packet.NOOP)] try: packets = self.poll() except exceptions.QueueEmpty: exc = sys.exc_info() self.close(wait=False, reason=self.server.reason.TRANSPORT_ERROR) raise exc[1].with_traceback(exc[2]) return packets def handle_post_request(self, environ): """Handle a long-polling POST request from the client.""" length = int(environ.get('CONTENT_LENGTH', '0')) if length > self.server.max_http_buffer_size: raise exceptions.ContentTooLongError() else: body = environ['wsgi.input'].read(length).decode('utf-8') p = payload.Payload(encoded_payload=body) for pkt in p.packets: self.receive(pkt) def close(self, wait=True, abort=False, reason=None): """Close the socket connection.""" if not self.closed and not self.closing: self.closing = True self.server._trigger_event( 'disconnect', self.sid, reason or self.server.reason.SERVER_DISCONNECT, run_async=False) if not abort: self.send(packet.Packet(packet.CLOSE)) self.closed = True self.queue.put(None) if wait: self.queue.join() def schedule_ping(self): # only schedule a new ping if the previous ping wait cycle completed if self.last_ping: self.last_ping = None self.server.start_background_task(self._send_ping) def _send_ping(self): self.server.sleep(self.server.ping_interval) if not self.closing and not self.closed: self.last_ping = time.time() self.send(packet.Packet(packet.PING)) def _upgrade_websocket(self, environ, start_response): """Upgrade the connection from polling to websocket.""" if self.upgraded: raise OSError('Socket has been upgraded already') if self.server._async['websocket'] is None: # the selected async mode does not support websocket return self.server._bad_request() ws = self.server._async['websocket']( self._websocket_handler, self.server) return ws(environ, start_response) def _websocket_handler(self, ws): """Engine.IO handler for websocket transport.""" def websocket_wait(): data = ws.wait() if data and len(data) > self.server.max_http_buffer_size: raise ValueError('packet is too large') return data # try to set a socket timeout matching the configured ping interval # and timeout for attr in ['_sock', 'socket']: # pragma: no cover if hasattr(ws, attr) and hasattr(getattr(ws, attr), 'settimeout'): getattr(ws, attr).settimeout( self.server.ping_interval + self.server.ping_timeout) if self.connected: # the socket was already connected, so this is an upgrade self.upgrading = True # hold packet sends during the upgrade pkt = websocket_wait() decoded_pkt = packet.Packet(encoded_packet=pkt) if decoded_pkt.packet_type != packet.PING or \ decoded_pkt.data != 'probe': self.server.logger.info( '%s: Failed websocket upgrade, no PING packet', self.sid) self.upgrading = False return [] ws.send(packet.Packet(packet.PONG, data='probe').encode()) self.queue.put(packet.Packet(packet.NOOP)) # end poll pkt = websocket_wait() decoded_pkt = packet.Packet(encoded_packet=pkt) if decoded_pkt.packet_type != packet.UPGRADE: self.upgraded = False self.server.logger.info( ('%s: Failed websocket upgrade, expected UPGRADE packet, ' 'received %s instead.'), self.sid, pkt) self.upgrading = False return [] self.upgraded = True self.upgrading = False else: self.connected = True self.upgraded = True # start separate writer thread def writer(): while True: packets = None try: packets = self.poll() except exceptions.QueueEmpty: break if not packets: # empty packet list returned -> connection closed break try: for pkt in packets: ws.send(pkt.encode()) except: break ws.close() writer_task = self.server.start_background_task(writer) self.server.logger.info( '%s: Upgrade to websocket successful', self.sid) while True: p = None try: p = websocket_wait() except Exception as e: # if the socket is already closed, we can assume this is a # downstream error of that if not self.closed: # pragma: no cover self.server.logger.info( '%s: Unexpected error "%s", closing connection', self.sid, str(e)) break if p is None: # connection closed by client break pkt = packet.Packet(encoded_packet=p) try: self.receive(pkt) except exceptions.UnknownPacketError: # pragma: no cover pass except exceptions.SocketIsClosedError: # pragma: no cover self.server.logger.info('Receive error -- socket is closed') break except: # pragma: no cover # if we get an unexpected exception we log the error and exit # the connection properly self.server.logger.exception('Unknown receive error') break self.queue.put(None) # unlock the writer task so that it can exit writer_task.join() self.close(wait=False, abort=True, reason=self.server.reason.TRANSPORT_CLOSE) return [] ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1791413733.9576802 python_engineio-4.15.1/src/engineio/static_files.py0000644000175000017500000000410015261546746022010 0ustar00miguelmiguelcontent_types = { 'css': 'text/css', 'gif': 'image/gif', 'html': 'text/html', 'jpg': 'image/jpeg', 'js': 'application/javascript', 'json': 'application/json', 'png': 'image/png', 'txt': 'text/plain', } def get_static_file(path, static_files): """Return the local filename and content type for the requested static file URL. :param path: the path portion of the requested URL. :param static_files: a static file configuration dictionary. This function returns a dictionary with two keys, "filename" and "content_type". If the requested URL does not match any static file, the return value is None. """ extra_path = '' if path in static_files: f = static_files[path] else: f = None while path != '': path, last = path.rsplit('/', 1) extra_path = '/' + last + extra_path if path in static_files: f = static_files[path] break elif path + '/' in static_files: f = static_files[path + '/'] break if '../' in extra_path: return None if f: if isinstance(f, str): f = {'filename': f} else: f = f.copy() # in case it is mutated below if f['filename'].endswith('/') and extra_path.startswith('/'): extra_path = extra_path[1:] f['filename'] += extra_path if f['filename'].endswith('/'): if '' in static_files: if isinstance(static_files[''], str): f['filename'] += static_files[''] else: f['filename'] += static_files['']['filename'] if 'content_type' in static_files['']: f['content_type'] = static_files['']['content_type'] else: f['filename'] += 'index.html' if 'content_type' not in f: ext = f['filename'].rsplit('.')[-1] f['content_type'] = content_types.get( ext, 'application/octet-stream') return f ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1793228809.0363414 python_engineio-4.15.1/src/python_engineio.egg-info/0000755000175000017500000000000015270500011022034 5ustar00miguelmiguel././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1793228809.0280364 python_engineio-4.15.1/src/python_engineio.egg-info/PKG-INFO0000644000175000017500000000511215270500011023130 0ustar00miguelmiguelMetadata-Version: 2.4 Name: python-engineio Version: 4.15.1 Summary: Engine.IO server and client for Python Author-email: Miguel Grinberg License-Expression: MIT Project-URL: Homepage, https://code.miguelgrinberg.com/miguelgrinberg/python-engineio Project-URL: Documentation, https://python-engineio.readthedocs.io/ Project-URL: Source Code, https://code.miguelgrinberg.com/miguelgrinberg/python-engineio Project-URL: Issue Tracker, https://code.miguelgrinberg.com/miguelgrinberg/python-engineio/issues Project-URL: Changelog, https://code.miguelgrinberg.com/miguelgrinberg/python-engineio/src/branch/main/CHANGES.md Project-URL: Funding, https://code.miguelgrinberg.com/miguelgrinberg/python-engineio#sponsor-this-project Classifier: Environment :: Web Environment Classifier: Intended Audience :: Developers Classifier: Programming Language :: Python :: 3 Classifier: Operating System :: OS Independent Requires-Python: >=3.8 Description-Content-Type: text/markdown License-File: LICENSE Requires-Dist: simple-websocket>=0.10.0 Provides-Extra: client Requires-Dist: requests>=2.21.0; extra == "client" Requires-Dist: websocket-client>=0.54.0; extra == "client" Provides-Extra: asyncio-client Requires-Dist: aiohttp>=3.11; extra == "asyncio-client" Provides-Extra: dev Requires-Dist: tox; extra == "dev" Provides-Extra: docs Requires-Dist: sphinx; extra == "docs" Requires-Dist: furo; extra == "docs" Dynamic: license-file python-engineio =============== [![tests](https://code.miguelgrinberg.com/miguelgrinberg/python-engineio/badges/workflows/tests.yml/badge.svg)](https://code.miguelgrinberg.com/miguelgrinberg/python-engineio/actions) Python implementation of the `Engine.IO` realtime client and server. Resources --------- - [git](https://code.miguelgrinberg.com/miguelgrinberg/python-engineio) - [Change Log](https://code.miguelgrinberg.com/miguelgrinberg/python-engineio/src/branch/main/CHANGES.md) - [Documentation](https://python-engineio.readthedocs.io/) - [PyPI](https://pypi.python.org/pypi/python-engineio) - [Contributor's guide](CONTRIBUTING.md) - [Security policy](SECURITY.md) Sponsor this project -------------------- This project relies on contributions from its users. If you benefit from it please consider making a single or ongoing monetary contribution in one of the following platforms: - [Github Sponsors](https://github.com/sponsors/miguelgrinberg) - [Patreon](https://patreon.com/miguelgrinberg) - [Buy me a Coffee](https://buymeacoffee.com/miguelgrinberg) - [thanks.dev](https://thanks.dev/u/gh/miguelgrinberg) - [PayPal](https://paypal.me/miguelgrinberg) Thank you! ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1793228809.0326064 python_engineio-4.15.1/src/python_engineio.egg-info/SOURCES.txt0000644000175000017500000000353515270500011023726 0ustar00miguelmiguelLICENSE MANIFEST.in README.md pyproject.toml tox.ini docs/_static/README.md src/engineio/__init__.py src/engineio/async_client.py src/engineio/async_server.py src/engineio/async_socket.py src/engineio/base_client.py src/engineio/base_server.py src/engineio/base_socket.py src/engineio/client.py src/engineio/exceptions.py src/engineio/json.py src/engineio/middleware.py src/engineio/packet.py src/engineio/payload.py src/engineio/server.py src/engineio/socket.py src/engineio/static_files.py src/engineio/async_drivers/__init__.py src/engineio/async_drivers/_websocket_wsgi.py src/engineio/async_drivers/aiohttp.py src/engineio/async_drivers/asgi.py src/engineio/async_drivers/eventlet.py src/engineio/async_drivers/gevent.py src/engineio/async_drivers/gevent_uwsgi.py src/engineio/async_drivers/sanic.py src/engineio/async_drivers/threading.py src/engineio/async_drivers/tornado.py src/python_engineio.egg-info/PKG-INFO src/python_engineio.egg-info/SOURCES.txt src/python_engineio.egg-info/dependency_links.txt src/python_engineio.egg-info/not-zip-safe src/python_engineio.egg-info/requires.txt src/python_engineio.egg-info/top_level.txt tests/async/.test_server.py.swp tests/async/__init__.py tests/async/index.html tests/async/test_aiohttp.py tests/async/test_asgi.py tests/async/test_client.py tests/async/test_sanic.py tests/async/test_server.py tests/async/test_socket.py tests/async/test_tornado.py tests/common/__init__.py tests/common/index.html tests/common/test_client.py tests/common/test_middleware.py tests/common/test_packet.py tests/common/test_payload.py tests/common/test_server.py tests/common/test_socket.py tests/performance/README.md tests/performance/binary_b64_packet.py tests/performance/binary_packet.py tests/performance/json_packet.py tests/performance/payload.py tests/performance/run.sh tests/performance/server_receive.py tests/performance/text_packet.py././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1793228809.0281944 python_engineio-4.15.1/src/python_engineio.egg-info/dependency_links.txt0000644000175000017500000000000115270500011026102 0ustar00miguelmiguel ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1791413391.117708 python_engineio-4.15.1/src/python_engineio.egg-info/not-zip-safe0000644000175000017500000000000115261546217024304 0ustar00miguelmiguel ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1793228809.0285873 python_engineio-4.15.1/src/python_engineio.egg-info/requires.txt0000644000175000017500000000021415270500011024431 0ustar00miguelmiguelsimple-websocket>=0.10.0 [asyncio_client] aiohttp>=3.11 [client] requests>=2.21.0 websocket-client>=0.54.0 [dev] tox [docs] sphinx furo ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1793228809.0286598 python_engineio-4.15.1/src/python_engineio.egg-info/top_level.txt0000644000175000017500000000001115270500011024556 0ustar00miguelmiguelengineio ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1793228809.0347638 python_engineio-4.15.1/tests/0000755000175000017500000000000015270500011015517 5ustar00miguelmiguel././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1793228809.036771 python_engineio-4.15.1/tests/async/0000755000175000017500000000000015270500011016634 5ustar00miguelmiguel././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1793227800.4223492 python_engineio-4.15.1/tests/async/.test_server.py.swp0000644000175000017500000020000015270476030022446 0ustar00miguelmiguelb0VIM 8.3|âjDÛP¹G miguelframework~miguel/Documents/dev/python-engineio/tests/async/test_server.pyutf-8 3210#"! UtpgWh S¿ Y Vk OÁ P^`Y¾^Zu[Ïy*P£ad ÂgñåÛ̲¦¥—–t8þâÅÄìšK#í   ’ d G 2  ç ´ … g ] ä Ó Ò ² ‹ e B  ù Í “ X  ñ Ð µ ´ £ ‰ G F 5  ×Öº{z[!ÿþÓ¬ˆ‡a:ðï‡^;õ¥] ô³wP'&ûÓ—o5 Í¥hg< êÂÁ import_module.side_effect = [a] a = self.get_async_mock() async def test_attach(self, import_module): @mock.patch('importlib.import_module') async_server.AsyncServer(async_mode='threading') with pytest.raises(ValueError): async_server.AsyncServer(async_mode='gevent_uwsgi') with pytest.raises(ValueError): async_server.AsyncServer(async_mode='gevent') with pytest.raises(ValueError): async_server.AsyncServer(async_mode='eventlet') with pytest.raises(ValueError): async def test_async_modes_wsgi(self): assert s.async_mode == 'aiohttp' s = async_server.AsyncServer() import_module.side_effect = [self.get_async_mock()] async def test_async_mode_auto_aiohttp(self, import_module): @mock.patch('importlib.import_module') assert s._async['websocket'].__name__ == 'WebSocket' assert s._async['make_response'] == async_aiohttp.make_response assert s._async['translate_request'] == async_aiohttp.translate_request assert s._async['create_route'] == async_aiohttp.create_route assert s._async['asyncio'] assert s.async_mode == 'aiohttp' s = async_server.AsyncServer(async_mode='aiohttp') async def test_async_mode_aiohttp(self): assert s.async_modes() == ['aiohttp', 'sanic', 'tornado', 'asgi'] s = async_server.AsyncServer() async def test_async_modes(self): assert s.is_asyncio_based() s = async_server.AsyncServer() async def test_is_asyncio_based(self): packet.Packet.json = json # restore JSON encoder, in case a test changed it def teardown_method(self): logging.getLogger('engineio').setLevel(logging.NOTSET) def setup_method(self): async_server.AsyncServer._default_monitor_clients = True def teardown_class(cls): @classmethod async_server.AsyncServer._default_monitor_clients = False def setup_class(cls): @classmethod return mock_socket mock_socket.session = {} mock_socket.close = mock.AsyncMock() mock_socket.check_ping_timeout = mock.AsyncMock() mock_socket.handle_post_request = mock.AsyncMock() mock_socket.handle_get_request = mock.AsyncMock() mock_socket.send = mock.AsyncMock() mock_socket.upgraded = False mock_socket.closing = False mock_socket.closed = False mock_socket.connected = False mock_socket = mock.MagicMock() def _get_mock_socket(self): return a a._async['make_response'].return_value = 'response' a._async['translate_request'].return_value = environ } 'websocket': 'w', 'make_response': mock.MagicMock(), 'translate_request': mock.MagicMock(), 'create_route': mock.MagicMock(), 'asyncio': True, a._async = { a = mock.MagicMock() environ['QUERY_STRING'] = 'EIO=4' else: environ['QUERY_STRING'] = 'EIO=4&' + environ['QUERY_STRING'] if 'EIO=' not in environ['QUERY_STRING']: if environ.get('QUERY_STRING'): def get_async_mock(environ={'REQUEST_METHOD': 'GET', 'QUERY_STRING': ''}): @staticmethodclass TestAsyncServer:from engineio import payloadfrom engineio import packetfrom engineio import jsonfrom engineio import exceptionsfrom engineio.async_drivers import aiohttp as async_aiohttpfrom engineio import async_serverimport pytestimport zlibfrom unittest import mockimport loggingimport ioimport gzipimport asyncioadÖ2P®­‰jQP)üµ£‚�G í Ö Õ – | { ;   ÷ Û Ä Ã „ j i B  þ Ð ­ ’ ‘ j C &  ø Þ Ý ² t R * ï Ä š { H G  êÈ e;ëУ¢w8Ìš](¹€21 assert a._async['make_response'].call_args[0][0] == '400 BAD REQUEST' assert a._async['make_response'].call_count == 1 a._async['translate_request'].assert_called_once_with('request') assert response == 'response' response = await s.handle_request('request') s = async_server.AsyncServer(transports='websocket') import_module.side_effect = [a] ) {'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'transport=polling'} a = self.get_async_mock( async def test_transports_disallowed(self, import_module): @mock.patch('importlib.import_module') assert s.service_task_handle is None await s.shutdown() assert s.service_task_handle is not None await asyncio.sleep(0) await s.handle_request('request') s = async_server.AsyncServer(monitor_clients=True) import_module.side_effect = [a] a = self.get_async_mock() async def test_shutdown(self, import_module): @mock.patch('importlib.import_module') s._service_task.assert_awaited_once_with() await asyncio.sleep(0) await s.handle_request('request') s._service_task = mock.AsyncMock() s = async_server.AsyncServer(monitor_clients=True) import_module.side_effect = [a] a = self.get_async_mock() async def test_service_task_started(self, import_module): @mock.patch('importlib.import_module') assert e.is_set() e.set() assert not e.is_set() e = s.create_event() s = async_server.AsyncServer() async def test_create_event(self): q.get_nowait() with pytest.raises(empty): empty = s.get_queue_empty_exception() q = s.create_queue() s = async_server.AsyncServer() async def test_create_queue(self): assert r == 'foo' r = await s._trigger_event('disconnect', 'foo', 'bar') return sid async def baz(sid): @s.on('disconnect') s = async_server.AsyncServer() async def test_trigger_legacy_disconnect_event_async(self): assert r == 'foo' r = await s._trigger_event('disconnect', 'foo', 'bar') return sid def baz(sid): @s.on('disconnect') s = async_server.AsyncServer() async def test_trigger_legacy_disconnect_event(self): assert result == ['bar'] await fut fut = await s._trigger_event('message', 'bar', run_async=True) s.on('message', handler=foo_handler) s = async_server.AsyncServer() return 1 / 0 result.append(arg) async def foo_handler(arg): result = [] async def test_trigger_event_coroutine_async_error(self):ad4È^ßž”lEßÞ©ƒih.å ¿ ¾ “ X 7 ö ì Ä � \ 7 6  × Ö œ r $  Õ Ÿ ~ < 2 ã ¢ } S  âšy7-Þ«�32˪h^6Ω¨r?>ØŠ‰wZ:ýàß´zYK"÷¿±§Aȹ mock_socket.handle_get_request.return_value = [ s.sockets['foo'] = mock_socket = self._get_mock_socket() s = async_server.AsyncServer(compression_threshold=0) import_module.side_effect = [a] ) } 'HTTP_ACCEPT_ENCODING': 'gzip,deflate', 'QUERY_STRING': 'sid=foo', 'REQUEST_METHOD': 'GET', { a = self.get_async_mock( async def test_gzip_compression(self, import_module): @mock.patch('importlib.import_module') return gz.read() with gzip.GzipFile(fileobj=bytesio, mode='r') as gz: bytesio = io.BytesIO(b) def _gzip_decompress(b): @staticmethod assert a._async['make_response'].call_args[0][0] == '400 BAD REQUEST' await s.handle_request('request') mock_socket.handle_post_request = mock_post_request raise exceptions.ContentTooLongError() async def mock_post_request(*args, **kwargs): mock_socket.connected = True s.sockets['foo'] = mock_socket = self._get_mock_socket() s = async_server.AsyncServer() import_module.side_effect = [a] ) {'REQUEST_METHOD': 'POST', 'QUERY_STRING': 'sid=foo'} a = self.get_async_mock( async def test_post_request_error(self, import_module): @mock.patch('importlib.import_module') assert a._async['make_response'].call_args[0][0] == '400 BAD REQUEST' await s.handle_request('request') s.sockets['foo'] = self._get_mock_socket() s = async_server.AsyncServer() import_module.side_effect = [a] ) {'REQUEST_METHOD': 'POST', 'QUERY_STRING': 'sid=foo'} a = self.get_async_mock( async def test_post_request_before_connecting(self, import_module): @mock.patch('importlib.import_module') assert a._async['make_response'].call_args[0][0] == '200 OK' await s.handle_request('request') mock_socket.connected = True s.sockets['foo'] = mock_socket = self._get_mock_socket() s = async_server.AsyncServer() import_module.side_effect = [a] ) {'REQUEST_METHOD': 'POST', 'QUERY_STRING': 'sid=foo'} a = self.get_async_mock( async def test_post_request(self, import_module): @mock.patch('importlib.import_module') assert len(s.sockets) == 0 assert a._async['make_response'].call_args[0][0] == '400 BAD REQUEST' await s.handle_request('request') mock_socket.handle_get_request = mock_get_request raise exceptions.QueueEmpty() async def mock_get_request(*args, **kwargs): mock_socket.connected = True s.sockets['foo'] = mock_socket = self._get_mock_socket() s = async_server.AsyncServer() import_module.side_effect = [a] ) {'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'sid=foo'} a = self.get_async_mock( async def test_get_request_error(self, import_module): @mock.patch('importlib.import_module') assert 'foo' not in s.sockets assert r == 'resp' r = await s.handle_request('request') mock_socket.handle_get_request = mock_get_request return 'resp' mock_socket.closed = True async def mock_get_request(*args, **kwargs): mock_socket.connected = True s.sockets['foo'] = mock_socket = self._get_mock_socket() s = async_server.AsyncServer() import_module.side_effect = [a] ) {'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'sid=foo'} a = self.get_async_mock(adšZȾ”X!Ø×¬oN@ì ° ¢ ˜ p 2 ñ ¹ � w M  × ” “ h $  õ Ì ¡ q c Y 1 ð ¯ w ? 5 Ï©yTÊÉž`?1Ý­Ÿ•mG ÀˆPFຊe쯎€W,ýïå½>ÎÄš™ await s.handle_request('request') ] packet.Packet(packet.MESSAGE, data='hello') mock_socket.handle_get_request.return_value = [ s.sockets['foo'] = mock_socket = self._get_mock_socket() s = async_server.AsyncServer(compression_threshold=0) import_module.side_effect = [a] ) } 'HTTP_ACCEPT_ENCODING': 'rar', 'QUERY_STRING': 'sid=foo', 'REQUEST_METHOD': 'GET', { a = self.get_async_mock( async def test_compression_unknown(self, import_module): @mock.patch('importlib.import_module') self._gzip_decompress(a._async['make_response'].call_args[0][2]) with pytest.raises(IOError): assert header != 'Content-Encoding' for header, value in headers: headers = a._async['make_response'].call_args[0][1] await s.handle_request('request') ] packet.Packet(packet.MESSAGE, data='hello') mock_socket.handle_get_request.return_value = [ s.sockets['foo'] = mock_socket = self._get_mock_socket() ) http_compression=False, compression_threshold=0 s = async_server.AsyncServer( import_module.side_effect = [a] ) } 'HTTP_ACCEPT_ENCODING': 'gzip', 'QUERY_STRING': 'sid=foo', 'REQUEST_METHOD': 'GET', { a = self.get_async_mock( async def test_compression_disabled(self, import_module): @mock.patch('importlib.import_module') self._gzip_decompress(a._async['make_response'].call_args[0][2]) print(a._async['make_response'].call_args[0][2]) with pytest.raises(IOError): assert header != 'Content-Encoding' for header, value in headers: headers = a._async['make_response'].call_args[0][1] await s.handle_request('request') ] packet.Packet(packet.MESSAGE, data='hello') mock_socket.handle_get_request.return_value = [ s.sockets['foo'] = mock_socket = self._get_mock_socket() s = async_server.AsyncServer(compression_threshold=1000) import_module.side_effect = [a] ) } 'HTTP_ACCEPT_ENCODING': 'gzip', 'QUERY_STRING': 'sid=foo', 'REQUEST_METHOD': 'GET', { a = self.get_async_mock( async def test_gzip_compression_threshold(self, import_module): @mock.patch('importlib.import_module') zlib.decompress(a._async['make_response'].call_args[0][2]) assert ('Content-Encoding', 'deflate') in headers headers = a._async['make_response'].call_args[0][1] await s.handle_request('request') ] packet.Packet(packet.MESSAGE, data='hello') mock_socket.handle_get_request.return_value = [ s.sockets['foo'] = mock_socket = self._get_mock_socket() s = async_server.AsyncServer(compression_threshold=0) import_module.side_effect = [a] ) } 'HTTP_ACCEPT_ENCODING': 'deflate;q=1,gzip', 'QUERY_STRING': 'sid=foo', 'REQUEST_METHOD': 'GET', { a = self.get_async_mock( async def test_deflate_compression(self, import_module): @mock.patch('importlib.import_module') self._gzip_decompress(a._async['make_response'].call_args[0][2]) assert ('Content-Encoding', 'gzip') in headers headers = a._async['make_response'].call_args[0][1] await s.handle_request('request') ] packet.Packet(packet.MESSAGE, data='hello')adˆ[ÄžnIüûÐ�n`7 à Ò È   b ! é ± § } A  ë Æ y x M  û Ó   e ; ÿ µ ´ ‰ T < $ #  Ù«�qQ5 Ò¨l!úùΛyQäº~X.- Ùœr@Ù§h>ݸ·‘E óÙ±ˆ return '*** encoded ***' def dumps(*args, **kwargs): @staticmethod class CustomJSON: # changes the JSON encoding/decoding functions # Warning: this test cannot run in parallel with other tests, as it async def test_custom_json(self): assert s.logger == my_logger s = async_server.AsyncServer(logger=my_logger) my_logger = logging.Logger('foo') s.logger.setLevel(logging.NOTSET) assert s.logger.getEffectiveLevel() == logging.WARNING s = async_server.AsyncServer(logger=True) s.logger.setLevel(logging.WARNING) assert s.logger.getEffectiveLevel() == logging.INFO s = async_server.AsyncServer(logger=True) s.logger.setLevel(logging.NOTSET) assert s.logger.getEffectiveLevel() == logging.ERROR s = async_server.AsyncServer(logger=False) async def test_logger(self): assert header != 'Set-Cookie' for header, value in headers: headers = a._async['make_response'].call_args[0][1] await s.handle_request('request') s.generate_id = mock.MagicMock(return_value='123') s = async_server.AsyncServer(cookie=None) import_module.side_effect = [a] a = self.get_async_mock() async def test_no_cookie(self, import_module): @mock.patch('importlib.import_module') 'HttpOnly') in headers assert ('Set-Cookie', 'test=123; path=/a; SameSite=None; Secure; ' headers = a._async['make_response'].call_args[0][1] await s.handle_request('request') s.generate_id = mock.MagicMock(return_value='123') }) 'HttpOnly': True 'Secure': True, 'SameSite': 'None', 'path': get_path, 'name': 'test', s = async_server.AsyncServer(cookie={ import_module.side_effect = [a] a = self.get_async_mock() return '/a' def get_path(): async def test_cookie_dict(self, import_module): @mock.patch('importlib.import_module') assert ('Set-Cookie', 'sid=123; path=/; SameSite=Lax') in headers headers = a._async['make_response'].call_args[0][1] await s.handle_request('request') s.generate_id = mock.MagicMock(return_value='123') s = async_server.AsyncServer(cookie='sid') import_module.side_effect = [a] a = self.get_async_mock() async def test_cookie(self, import_module): @mock.patch('importlib.import_module') self._gzip_decompress(a._async['make_response'].call_args[0][2]) with pytest.raises(IOError): assert header != 'Content-Encoding' for header, value in headers: headers = a._async['make_response'].call_args[0][1] await s.handle_request('request') ] packet.Packet(packet.MESSAGE, data='hello') mock_socket.handle_get_request.return_value = [ s.sockets['foo'] = mock_socket = self._get_mock_socket() s = async_server.AsyncServer(compression_threshold=0) import_module.side_effect = [a] ) } 'HTTP_ACCEPT_ENCODING': '', 'QUERY_STRING': 'sid=foo', 'REQUEST_METHOD': 'GET', { a = self.get_async_mock( async def test_compression_no_encoding(self, import_module): @mock.patch('importlib.import_module') self._gzip_decompress(a._async['make_response'].call_args[0][2]) with pytest.raises(IOError): assert header != 'Content-Encoding' for header, value in headers: headers = a._async['make_response'].call_args[0][1]adyÿå½”“a î´†…[98 þ ý á Ç Æ ¬ � J 3 2   à ¹   Ÿ n Z Y ;  ü û Ô § v O N    ã à ¤ £ | O  ÷ ö ¿ � „ ƒ e L K $ óÆŠJIéÐÏ«’‘j9 Ð��XDC%æå¾‘J8ØÄß`_8 IJ‹ŠM98ûâáº�F4 assert result == ['bar'] await fut fut = await s._trigger_event('message', 'bar', run_async=True) s.on('message', handler=foo_handler) s = async_server.AsyncServer() return 1 / 0 result.append(arg) def foo_handler(arg): result = [] async def test_trigger_event_function_async_error(self): assert result == ['ok', 'bar'] await fut fut = await s._trigger_event('message', 'bar', run_async=True) s.on('message', handler=foo_handler) s = async_server.AsyncServer() result.append(arg) result.append('ok') async def foo_handler(arg): result = [] async def test_trigger_event_coroutine_async(self): assert result == ['ok', 'bar'] await fut fut = await s._trigger_event('message', 'bar', run_async=True) s.on('message', handler=foo_handler) s = async_server.AsyncServer() result.append(arg) result.append('ok') def foo_handler(arg): result = [] async def test_trigger_event_function_async(self): assert await s._trigger_event('message', 'bar') is None assert not await s._trigger_event('connect', '123') s.on('message', handler=foo_handler) s.on('connect', handler=connect_handler) s = async_server.AsyncServer() return 1 / 0 async def foo_handler(arg): return 1 / 0 async def connect_handler(arg): async def test_trigger_event_coroutine_error(self): assert await s._trigger_event('message', 'bar') is None assert not await s._trigger_event('connect', '123') s.on('message', handler=foo_handler) s.on('connect', handler=connect_handler) s = async_server.AsyncServer() return 1 / 0 def foo_handler(arg): return 1 / 0 def connect_handler(arg): async def test_trigger_event_function_error(self): assert result == ['ok', 'bar'] await s._trigger_event('message', 'bar') s.on('message', handler=foo_handler) s = async_server.AsyncServer() result.append(arg) result.append('ok') async def foo_handler(arg): result = [] async def test_trigger_event_coroutine(self): assert result == ['ok', 'bar'] await s._trigger_event('message', 'bar') s.on('message', handler=foo_handler) s = async_server.AsyncServer() result.append(arg) result.append('ok') def foo_handler(arg): result = [] async def test_trigger_event_function(self): await s.sleep(0) s = async_server.AsyncServer() async def test_sleep(self): assert r == ['bar'] await main() await task task = s.start_background_task(foo, 'bar') s = async_server.AsyncServer() async def main(): r.append(arg) async def foo(arg): r = [] async def test_background_tasks(self): packet.Packet.json = json # restore the default JSON module assert pkt2.data == '+++ decoded +++' pkt2 = packet.Packet(encoded_packet=pkt.encode()) assert pkt.encode() == '4*** encoded ***' pkt = packet.Packet(packet.MESSAGE, data={'foo': 'bar'}) async_server.AsyncServer(json=CustomJSON) return '+++ decoded +++' def loads(*args, **kwargs): @staticmethodad’WÙ¬e6ïÁzLâ » ˆ ‡ l 8  ë ¦ ¥ � Ž i B  ß ­ y J $ # ú Ó • W : Ü « | K  öÐϤgFýÕ®yS у‚W"¸�i4ÅŒG6óÉ¿utIöΧ}Z!Ü’‘ assert ('Content-Type', 'text/plain; charset=UTF-8') in a._async[ assert a._async['make_response'].call_args[0][0] == '200 OK' assert a._async['make_response'].call_count == 1 assert len(s.sockets) == 1 await s.handle_request('request') s = async_server.AsyncServer() import_module.side_effect = [a] a = self.get_async_mock() async def test_connect(self, import_module): @mock.patch('importlib.import_module') assert a._async['make_response'].call_args[0][2].endswith(b'");') ) .startswith(b'___eio[233]("') .call_args[0][2] a._async['make_response'] assert ( assert a._async['make_response'].call_args[0][0] == '200 OK' assert a._async['make_response'].call_count == 1 a._async['translate_request'].assert_called_once_with('request') assert response == 'response' response = await s.handle_request('request') s = async_server.AsyncServer() import_module.side_effect = [a] ) {'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'j=233'} a = self.get_async_mock( async def test_jsonp_index(self, import_module): @mock.patch('importlib.import_module') assert a._async['make_response'].call_args[0][0] == '400 BAD REQUEST' assert a._async['make_response'].call_count == 1 a._async['translate_request'].assert_called_once_with('request') assert response == 'response' response = await s.handle_request('request') s = async_server.AsyncServer() import_module.side_effect = [a] ) {'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'j=abc'} a = self.get_async_mock( async def test_jsonp_not_supported(self, import_module): @mock.patch('importlib.import_module') assert 'bar' not in s.sockets assert 'foo' not in s.sockets reason=s.reason.SERVER_DISCONNECT) mock_bar.close.assert_awaited_once_with( reason=s.reason.SERVER_DISCONNECT) mock_foo.close.assert_awaited_once_with( assert mock_bar.close.await_count == 1 assert mock_foo.close.await_count == 1 await s.disconnect() s.sockets['bar'] = mock_bar = self._get_mock_socket() s.sockets['foo'] = mock_foo = self._get_mock_socket() s = async_server.AsyncServer() async def test_disconnect_all(self): assert 'foo' not in s.sockets reason=s.reason.SERVER_DISCONNECT) mock_socket.close.assert_awaited_once_with( assert mock_socket.close.await_count == 1 await s.disconnect('foo') s.sockets['foo'] = mock_socket = self._get_mock_socket() s = async_server.AsyncServer() async def test_disconnect(self): await _func() assert await s.get_session('foo') == {'username': 'bar'} session['username'] = 'bar' await s.sleep(0) async with s.session('foo') as session: async def _func(): s.sockets['foo'] = self._get_mock_socket() s = async_server.AsyncServer() async def test_session(self): a._async['create_route'].assert_called_with('app', s, '/jkl/') s.attach('app', engineio_path='jkl/') a._async['create_route'].assert_called_with('app', s, '/ghi/') s.attach('app', engineio_path='/ghi') a._async['create_route'].assert_called_with('app', s, '/def/') s.attach('app', engineio_path='/def/') a._async['create_route'].assert_called_with('app', s, '/abc/') s.attach('app', engineio_path='abc') s = async_server.AsyncServer()adjSäʧY7á´xPá © ¨ } A  ý Å � w C  ù Ñ ª € ] # Ý “ w \ 9 ô È § r E áàµxV.óɦX6Ù˜w8.ßµ|.ÍÌ¡^<ʆ\9ëÉ‘Xó±j async def test_connect_bad_poll(self, AsyncSocket, import_module): @mock.patch('engineio.async_server.async_socket.AsyncSocket') @mock.patch('importlib.import_module') assert packets[0].data['maxPayload'] == 12345678 assert packets[0].data['pingInterval'] == 456000 assert packets[0].data['pingTimeout'] == 123000 'utf-8')).packets encoded_payload=a._async['make_response'].call_args[0][2].decode( packets = payload.Payload( await s.handle_request('request') max_http_buffer_size=12345678) s = async_server.AsyncServer(ping_timeout=123, ping_interval=456, import_module.side_effect = [a] a = self.get_async_mock() async def test_connect_custom_ping_times(self, import_module): @mock.patch('importlib.import_module') a._async['make_response'].call_args[0][2] assert b'unsupported version' in \ assert a._async['make_response'].call_args[0][0] == '400 BAD REQUEST' assert a._async['make_response'].call_count == 1 await s.handle_request('request') s = async_server.AsyncServer() import_module.side_effect = [a] ) {'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'EIO=1'} a = self.get_async_mock( async def test_connect_bad_eio_version(self, import_module): @mock.patch('importlib.import_module') assert packets[0].data['upgrades'] == [] 'utf-8')).packets encoded_payload=a._async['make_response'].call_args[0][2].decode( packets = payload.Payload( await s.handle_request('request') s = async_server.AsyncServer(allow_upgrades=False) import_module.side_effect = [a] a = self.get_async_mock() async def test_connect_no_upgrades(self, import_module): @mock.patch('importlib.import_module') assert 'sid' in packets[0].data assert packets[0].data['upgrades'] == ['websocket'] assert 'upgrades' in packets[0].data assert packets[0].packet_type == packet.OPEN assert len(packets) == 1 2].decode('utf-8')).packets encoded_payload=a._async['make_response'].await_args[0][ packets = payload.Payload( ].await_args[0][1] 'make_response' assert ('Content-Type', 'text/plain; charset=UTF-8') in a._async[ assert a._async['make_response'].await_args[0][0] == '200 OK' assert a._async['make_response'].await_count == 1 assert len(s.sockets) == 1 await s.handle_request('request') s = async_server.AsyncServer() import_module.side_effect = [a] ) return_value=a._async['make_response'].return_value a._async['make_response'] = mock.AsyncMock( ) return_value=a._async['translate_request'].return_value a._async['translate_request'] = mock.AsyncMock( a = self.get_async_mock() self, import_module): async def test_connect_async_request_response_handlers( @mock.patch('importlib.import_module') assert packets[0].data['maxPayload'] == 1000000 assert packets[0].data['pingInterval'] == 25000 assert packets[0].data['pingTimeout'] == 20000 assert 'sid' in packets[0].data assert packets[0].data['upgrades'] == ['websocket'] assert 'upgrades' in packets[0].data assert packets[0].packet_type == packet.OPEN assert len(packets) == 1 'utf-8')).packets encoded_payload=a._async['make_response'].call_args[0][2].decode( packets = payload.Payload( ].call_args[0][1] 'make_response'adJÊYÞ¶{/Þ¥WV+é § h G 9  Ù ¬ ž ” l 1 Ï Œ g = , í Ò È Ç œ Z  Õ ´ ¦ } F   Ùžw<ùÔª™Z?54 Ç�a@2 Ò¦˜Žf+ÉÈ|Z2Ï¥]\1î͆|T-ÊÉ assert a._async['make_response'].call_count == 1 await s.handle_request('request') s = async_server.AsyncServer() import_module.side_effect = [a] ) {'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'transport=foo'} a = self.get_async_mock( async def test_connect_transport_invalid(self, import_module): @mock.patch('importlib.import_module') assert '123' not in s.sockets # socket should close on its own await s.handle_request('request') AsyncSocket().handle_get_request = mock_handle s.sockets['123'].closed = True async def mock_handle(environ): # real websocket exchange # this mock handler just closes the socket, as it would happen on a s.generate_id = mock.MagicMock(return_value='123') s = async_server.AsyncServer() AsyncSocket.return_value = self._get_mock_socket() import_module.side_effect = [a] ) } 'HTTP_UPGRADE': 'websocket' 'QUERY_STRING': 'transport=websocket', 'REQUEST_METHOD': 'GET', { a = self.get_async_mock( self, AsyncSocket, import_module): async def test_connect_transport_websocket_closed( @mock.patch('engineio.async_server.async_socket.AsyncSocket') @mock.patch('importlib.import_module') ) == packet.OPEN s.sockets['123'].send.await_args[0][0].packet_type assert ( await s.handle_request('request') AsyncSocket().closed = False # force socket to stay open, so that we can check it later s.generate_id = mock.MagicMock(return_value='123') s = async_server.AsyncServer() AsyncSocket.return_value = self._get_mock_socket() import_module.side_effect = [a] ) } 'HTTP_UPGRADE': 'WebSocket', 'QUERY_STRING': 'transport=websocket', 'REQUEST_METHOD': 'GET', { a = self.get_async_mock( import_module): async def test_http_upgrade_case_insensitive(self, AsyncSocket, @mock.patch('engineio.async_server.async_socket.AsyncSocket') @mock.patch('importlib.import_module') ) == packet.OPEN s.sockets['123'].send.await_args[0][0].packet_type assert ( await s.handle_request('request') AsyncSocket().closed = False # force socket to stay open, so that we can check it later s.generate_id = mock.MagicMock(return_value='123') s = async_server.AsyncServer() AsyncSocket.return_value = self._get_mock_socket() import_module.side_effect = [a] ) } 'HTTP_UPGRADE': 'websocket', 'QUERY_STRING': 'transport=websocket', 'REQUEST_METHOD': 'GET', { a = self.get_async_mock( import_module): async def test_connect_transport_websocket(self, AsyncSocket, @mock.patch('engineio.async_server.async_socket.AsyncSocket') @mock.patch('importlib.import_module') assert a._async['make_response'].call_args[0][0] == '400 BAD REQUEST' assert a._async['make_response'].call_count == 1 await s.handle_request('request') s = async_server.AsyncServer() AsyncSocket.return_value.poll.side_effect = [exceptions.QueueEmpty] AsyncSocket.return_value = self._get_mock_socket() import_module.side_effect = [a] a = self.get_async_mock()ad‚V²±†F$¶¬„]3ú ¬ « € B ø Ñ § b & ß Þ ³ n M ÿ õ Í ‡ ]  Ü � œ q 3  ÷ÖÕÁœ|]S*Á—R×Ö±‡98 Ä£UK#ݳe)棢w4ð¢˜d$ò¬‚� await s.handle_request('request') s = async_server.AsyncServer(cors_allowed_origins=['a', 'b']) import_module.side_effect = [a] ) return_value=a._async['make_response'].return_value a._async['make_response'] = mock.AsyncMock( ) {'REQUEST_METHOD': 'GET', 'QUERY_STRING': '', 'HTTP_ORIGIN': 'c'} a = self.get_async_mock( ): self, import_module async def test_connect_cors_not_allowed_origin_async_response( @mock.patch('importlib.import_module') assert ('Access-Control-Allow-Origin', '*') not in headers assert ('Access-Control-Allow-Origin', 'c') not in headers headers = a._async['make_response'].call_args[0][1] assert a._async['make_response'].call_args[0][0] == '400 BAD REQUEST' await s.handle_request('request') s = async_server.AsyncServer(cors_allowed_origins=['a', 'b']) import_module.side_effect = [a] ) {'REQUEST_METHOD': 'GET', 'QUERY_STRING': '', 'HTTP_ORIGIN': 'c'} a = self.get_async_mock( async def test_connect_cors_not_allowed_origin(self, import_module): @mock.patch('importlib.import_module') assert a._async['make_response'].call_args[0][0] == '400 BAD REQUEST' await s.handle_request('request') environ['HTTP_ORIGIN'] = 'b' assert ('Access-Control-Allow-Origin', 'a') in headers headers = a._async['make_response'].call_args[0][1] assert a._async['make_response'].call_args[0][0] == '200 OK' await s.handle_request('request') s = async_server.AsyncServer(cors_allowed_origins=cors) import_module.side_effect = [a] a = self.get_async_mock(environ) } 'HTTP_ORIGIN': 'a' 'QUERY_STRING': '', 'REQUEST_METHOD': 'GET', environ = { return origin == 'a' def cors(origin): self, import_module): async def test_connect_cors_allowed_origin_with_callable( @mock.patch('importlib.import_module') assert ('Access-Control-Allow-Origin', 'b') in headers headers = a._async['make_response'].call_args[0][1] assert a._async['make_response'].call_args[0][0] == '200 OK' await s.handle_request('request') s = async_server.AsyncServer(cors_allowed_origins=['a', 'b']) import_module.side_effect = [a] ) {'REQUEST_METHOD': 'GET', 'QUERY_STRING': '', 'HTTP_ORIGIN': 'b'} a = self.get_async_mock( async def test_connect_cors_allowed_origin(self, import_module): @mock.patch('importlib.import_module') assert ('Access-Control-Allow-Credentials', 'true') in headers headers = a._async['make_response'].call_args[0][1] assert a._async['make_response'].call_args[0][0] == '200 OK' await s.handle_request('request') s = async_server.AsyncServer() import_module.side_effect = [a] a = self.get_async_mock() async def test_connect_cors_headers(self, import_module): @mock.patch('importlib.import_module') assert a._async['make_response'].call_args[0][0] == '400 BAD REQUEST' assert a._async['make_response'].call_count == 1 await s.handle_request('request') s = async_server.AsyncServer() import_module.side_effect = [a] ) {'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'transport=websocket'} a = self.get_async_mock( self, import_module): async def test_connect_transport_websocket_without_upgrade( @mock.patch('importlib.import_module') assert a._async['make_response'].call_args[0][0] == '400 BAD REQUEST'ad xO\ÖÕªdCó é Á ‚ X  × – O N # Ü » k a 9 ø Î ‰ M Å Ä ™ X 7 éß·xN ÍŽGFέ_U-îÄv:÷´³ˆ; ã¿”pC5+ܲm1éè½xw async def test_connect_cors_no_credentials(self, import_module): @mock.patch('importlib.import_module') assert ('Access-Control-Allow-Origin', 'http://foo') in headers headers = a._async['make_response'].call_args[0][1] assert a._async['make_response'].call_args[0][0] == '200 OK' await s.handle_request('request') s = async_server.AsyncServer() import_module.side_effect = [a] ) } 'HTTP_ORIGIN': 'http://foo', 'HTTP_HOST': 'foo', 'wsgi.url_scheme': 'http', 'QUERY_STRING': '', 'REQUEST_METHOD': 'GET', { a = self.get_async_mock( async def test_connect_cors_headers_default_origin(self, import_module): @mock.patch('importlib.import_module') assert ('Access-Control-Allow-Origin', '*') not in headers assert ('Access-Control-Allow-Origin', 'b') not in headers headers = a._async['make_response'].call_args[0][1] assert a._async['make_response'].call_args[0][0] == '400 BAD REQUEST' await s.handle_request('request') s = async_server.AsyncServer(cors_allowed_origins='a') import_module.side_effect = [a] ) {'REQUEST_METHOD': 'GET', 'QUERY_STRING': '', 'HTTP_ORIGIN': 'b'} a = self.get_async_mock( async def test_connect_cors_one_origin_not_allowed(self, import_module): @mock.patch('importlib.import_module') assert ('Access-Control-Allow-Credentials', 'true') in headers assert ('Access-Control-Allow-Origin', 'a') in headers headers = a._async['make_response'].call_args[0][1] assert a._async['make_response'].call_args[0][0] == '200 OK' await s.handle_request('request') s = async_server.AsyncServer(cors_allowed_origins='a') import_module.side_effect = [a] ) {'REQUEST_METHOD': 'GET', 'QUERY_STRING': '', 'HTTP_ORIGIN': 'a'} a = self.get_async_mock( async def test_connect_cors_one_origin(self, import_module): @mock.patch('importlib.import_module') assert ('Access-Control-Allow-Credentials', 'true') in headers assert ('Access-Control-Allow-Origin', 'foo') in headers headers = a._async['make_response'].call_args[0][1] assert a._async['make_response'].call_args[0][0] == '200 OK' await s.handle_request('request') s = async_server.AsyncServer(cors_allowed_origins=['*']) import_module.side_effect = [a] ) {'REQUEST_METHOD': 'GET', 'QUERY_STRING': '', 'HTTP_ORIGIN': 'foo'} a = self.get_async_mock( async def test_connect_list_cors_all_origins(self, import_module): @mock.patch('importlib.import_module') assert ('Access-Control-Allow-Credentials', 'true') in headers assert ('Access-Control-Allow-Origin', 'foo') in headers headers = a._async['make_response'].call_args[0][1] assert a._async['make_response'].call_args[0][0] == '200 OK' await s.handle_request('request') s = async_server.AsyncServer(cors_allowed_origins='*') import_module.side_effect = [a] ) {'REQUEST_METHOD': 'GET', 'QUERY_STRING': '', 'HTTP_ORIGIN': 'foo'} a = self.get_async_mock( async def test_connect_str_cors_all_origins(self, import_module): @mock.patch('importlib.import_module') assert ('Access-Control-Allow-Origin', '*') not in headers assert ('Access-Control-Allow-Origin', 'c') not in headers headers = a._async['make_response'].await_args[0][1] ) a._async['make_response'].await_args[0][0] == '400 BAD REQUEST' assert (ad`PÞ¶yO ΃‚Wø º ° ˆ K ! Ü   � c A , + Á   ’ i E  Ø š p + ï Ð ‘ � e  Φh>ù½ž`_4ì�u6 Ç‹l.-¹jB×’V7ùøÍ„5 Ï¥` assert a._async['make_response'].call_args[0][0] == '200 OK' await s.handle_request('request') s = async_server.AsyncServer(cors_allowed_origins=[]) import_module.side_effect = [a] a = self.get_async_mock({'REQUEST_METHOD': 'GET', 'QUERY_STRING': ''}) async def test_connect_cors_disabled_no_origin(self, import_module): @mock.patch('importlib.import_module') assert header[0] != 'Access-Control-Allow-Origin' for header in headers: headers = a._async['make_response'].call_args[0][1] assert a._async['make_response'].call_args[0][0] == '200 OK' await s.handle_request('request') s = async_server.AsyncServer(cors_allowed_origins=['*']) import_module.side_effect = [a] a = self.get_async_mock({'REQUEST_METHOD': 'GET', 'QUERY_STRING': ''}) async def test_connect_list_cors_all_no_origin(self, import_module): @mock.patch('importlib.import_module') assert header[0] != 'Access-Control-Allow-Origin' for header in headers: headers = a._async['make_response'].call_args[0][1] assert a._async['make_response'].call_args[0][0] == '200 OK' await s.handle_request('request') s = async_server.AsyncServer(cors_allowed_origins='*') import_module.side_effect = [a] a = self.get_async_mock({'REQUEST_METHOD': 'GET', 'QUERY_STRING': ''}) async def test_connect_str_cors_all_no_origin(self, import_module): @mock.patch('importlib.import_module') assert header[0] != 'Access-Control-Allow-Origin' for header in headers: headers = a._async['make_response'].call_args[0][1] assert a._async['make_response'].call_args[0][0] == '200 OK' await s.handle_request('request') s = async_server.AsyncServer(cors_allowed_origins=[]) import_module.side_effect = [a] a = self.get_async_mock({'REQUEST_METHOD': 'GET', 'QUERY_STRING': ''}) async def test_connect_cors_default_no_origin(self, import_module): @mock.patch('importlib.import_module') assert not header[0].startswith('Access-Control-') for header in headers: headers = a._async['make_response'].call_args[0][1] assert a._async['make_response'].call_args[0][0] == '200 OK' await s.handle_request('request') s = async_server.AsyncServer(cors_allowed_origins=[]) import_module.side_effect = [a] ) } 'HTTP_ORIGIN': 'http://foo', 'QUERY_STRING': '', 'REQUEST_METHOD': 'GET', { a = self.get_async_mock( async def test_connect_cors_disabled(self, import_module): @mock.patch('importlib.import_module') ) in headers 'OPTIONS, GET, POST', 'Access-Control-Allow-Methods', assert ( headers = a._async['make_response'].call_args[0][1] assert a._async['make_response'].call_args[0][0] == '200 OK' await s.handle_request('request') s = async_server.AsyncServer(cors_credentials=False) import_module.side_effect = [a] ) {'REQUEST_METHOD': 'OPTIONS', 'QUERY_STRING': ''} a = self.get_async_mock( async def test_connect_cors_options(self, import_module): @mock.patch('importlib.import_module') assert ('Access-Control-Allow-Credentials', 'true') not in headers headers = a._async['make_response'].call_args[0][1] assert a._async['make_response'].call_args[0][0] == '200 OK' await s.handle_request('request') s = async_server.AsyncServer(cors_credentials=False) import_module.side_effect = [a] a = self.get_async_mock()ad«^Ä¥gf;⺓XW/è ¾ › š o 0  æ ¿ „ ƒ [ B A  ñ Î  1 0  ¹ — o H ä ¾ ½ — m J ûꛑ�e+Ü´�c@/ßÕÔ©gFûÓ¬‚_å˜wQݶuP+³²‡<õµ«ª ) 'QUERY_STRING': 'EIO=4&transport=polling&sid=foo'} {'REQUEST_METHOD': 'GET', a = self.get_async_mock( async def test_get_request_bad_polling_transport(self, import_module): @mock.patch('importlib.import_module') assert a._async['make_response'].call_args[0][0] == '400 BAD REQUEST' await s.handle_request('request') mock_socket.upgraded = False mock_socket.connected = True s.sockets['foo'] = mock_socket = self._get_mock_socket() s = async_server.AsyncServer() import_module.side_effect = [a] ) 'QUERY_STRING': 'EIO=4&transport=websocket&sid=foo'} {'REQUEST_METHOD': 'GET', a = self.get_async_mock( async def test_get_request_bad_websocket_transport(self, import_module): @mock.patch('importlib.import_module') assert a._async['make_response'].call_args[0][0] == '400 BAD REQUEST' assert len(s.sockets) == 0 await s.handle_request('request') s = async_server.AsyncServer() import_module.side_effect = [a] ) {'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'sid=foo'} a = self.get_async_mock( async def test_get_request_with_bad_sid(self, import_module): @mock.patch('importlib.import_module') ) a._async['make_response'].call_args[0][0] == '405 METHOD NOT FOUND' assert ( assert len(s.sockets) == 0 await s.handle_request('request') s = async_server.AsyncServer() import_module.side_effect = [a] a = self.get_async_mock({'REQUEST_METHOD': 'PUT', 'QUERY_STRING': ''}) async def test_method_not_found(self, import_module): @mock.patch('importlib.import_module') ) a._async['make_response'].call_args[0][2] == b'{"not": "allowed"}' assert ( assert a._async['make_response'].call_args[0][0] == '401 UNAUTHORIZED' assert len(s.sockets) == 0 await s.handle_request('request') s.on('connect')(mock_connect) return {'not': 'allowed'} def mock_connect(sid, environ): s.generate_id = mock.MagicMock(return_value='123') s = async_server.AsyncServer() import_module.side_effect = [a] a = self.get_async_mock() async def test_connect_event_rejects_with_message(self, import_module): @mock.patch('importlib.import_module') assert a._async['make_response'].call_args[0][2] == b'"Unauthorized"' assert a._async['make_response'].call_args[0][0] == '401 UNAUTHORIZED' assert len(s.sockets) == 0 await s.handle_request('request') s.on('connect')(mock_connect) return False def mock_connect(sid, environ): s.generate_id = mock.MagicMock(return_value='123') s = async_server.AsyncServer() import_module.side_effect = [a] a = self.get_async_mock() async def test_connect_event_rejects(self, import_module): @mock.patch('importlib.import_module') assert len(s.sockets) == 1 await s.handle_request('request') s.on('connect', handler=mock_connect) return True def mock_connect(sid, environ): s.generate_id = mock.MagicMock(return_value='123') s = async_server.AsyncServer() import_module.side_effect = [a] a = self.get_async_mock() async def test_connect_event(self, import_module): @mock.patch('importlib.import_module') assert header[0] != 'Access-Control-Allow-Origin' for header in headers: headers = a._async['make_response'].call_args[0][1]ad  YرpK'ý¯®ƒ@Ý Ó « „ Z 7 é è ½ y X  ä ½ Š ` 7 ¿ ¾ “ e C  ô ³ Ž ] L öµ´‰L*Û¬‡†[&ĺ’k*Í•‹aù«’€_'&û¶•TJ"ûº•X*ã Ÿ async def test_get_request_closes_socket(self, import_module): @mock.patch('importlib.import_module') assert r == 'resp' r = await s.handle_request('request') mock_socket.handle_get_request.return_value = 'resp' mock_socket.connected = True s.sockets['foo'] = mock_socket = self._get_mock_socket() s = async_server.AsyncServer() import_module.side_effect = [a] ) {'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'sid=foo'} a = self.get_async_mock( async def test_get_request_custom_response(self, import_module): @mock.patch('importlib.import_module') assert packets[0].packet_type == packet.MESSAGE assert len(packets) == 1 ).packets 'utf-8') encoded_payload=a._async['make_response'].call_args[0][2].decode( packets = payload.Payload( assert a._async['make_response'].call_args[0][0] == '200 OK' await s.handle_request('request') ] packet.Packet(packet.MESSAGE, data='hello') mock_socket.handle_get_request.return_value = [ mock_socket.connected = True s.sockets['foo'] = mock_socket = self._get_mock_socket() s = async_server.AsyncServer() import_module.side_effect = [a] ) {'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'sid=foo'} a = self.get_async_mock( async def test_get_request(self, import_module): @mock.patch('importlib.import_module') await s.send('foo', 'hello') # just ensure no exceptions are raised s = async_server.AsyncServer() import_module.side_effect = [a] a = self.get_async_mock() async def test_send_unknown_socket(self, import_module): @mock.patch('importlib.import_module') assert mock_socket.send.await_args[0][0].data == 'hello' ) mock_socket.send.await_args[0][0].packet_type == packet.MESSAGE assert ( assert mock_socket.send.await_count == 1 await s.send('foo', 'hello') s.sockets['foo'] = mock_socket = self._get_mock_socket() s = async_server.AsyncServer() import_module.side_effect = [a] a = self.get_async_mock() async def test_send(self, import_module): @mock.patch('importlib.import_module') assert a._async['make_response'].call_args[0][0] == '400 BAD REQUEST' await s.handle_request('request') s.sockets['foo'].upgraded = True s.sockets['foo'].connected = True s.sockets['foo'] = self._get_mock_socket() s = async_server.AsyncServer() import_module.side_effect = [a] ) {'REQUEST_METHOD': 'POST', 'QUERY_STRING': 'sid=foo'} a = self.get_async_mock( async def test_post_request_after_upgrade(self, import_module): @mock.patch('importlib.import_module') assert a._async['make_response'].call_args[0][0] == '400 BAD REQUEST' assert len(s.sockets) == 0 await s.handle_request('request') s = async_server.AsyncServer() import_module.side_effect = [a] ) {'REQUEST_METHOD': 'POST', 'QUERY_STRING': 'sid=foo'} a = self.get_async_mock( async def test_post_request_with_bad_sid(self, import_module): @mock.patch('importlib.import_module') assert a._async['make_response'].call_args[0][0] == '400 BAD REQUEST' await s.handle_request('request') mock_socket.upgraded = True mock_socket.connected = True s.sockets['foo'] = mock_socket = self._get_mock_socket() s = async_server.AsyncServer() import_module.side_effect = [a]././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1791413117.6849148 python_engineio-4.15.1/tests/async/__init__.py0000644000175000017500000000000015261545576020764 0ustar00miguelmiguel././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1791413117.6849947 python_engineio-4.15.1/tests/async/index.html0000644000175000017500000000001615261545576020657 0ustar00miguelmiguel ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1791413117.6849947 python_engineio-4.15.1/tests/async/test_aiohttp.py0000644000175000017500000000414715261545576021754 0ustar00miguelmiguelfrom unittest import mock import yarl from aiohttp.web import Request from engineio.async_drivers import aiohttp as async_aiohttp class TestAiohttp: def test_create_route(self): app = mock.MagicMock() mock_server = mock.MagicMock() async_aiohttp.create_route(app, mock_server, '/foo') app.router.add_get.assert_any_call('/foo', mock_server.handle_request) app.router.add_post.assert_any_call('/foo', mock_server.handle_request) def test_translate_request(self): message = mock.MagicMock() message.url = yarl.URL('https://example.com/foo/bar?baz=1') message.method = 'PUT' message.path = '/foo/bar?baz=1' message.version = (1, 1) message.headers = { 'a': 'b', 'c-c': 'd', 'c_c': 'e', 'content-type': 'application/json', 'content-length': 123, } request = Request( message, payload=b'hello world', protocol=mock.MagicMock(), payload_writer=mock.MagicMock(), task=mock.MagicMock(), loop=mock.MagicMock(), ) environ = async_aiohttp.translate_request(request) expected_environ = { 'REQUEST_METHOD': 'PUT', 'PATH_INFO': '/foo/bar', 'QUERY_STRING': 'baz=1', 'CONTENT_TYPE': 'application/json', 'CONTENT_LENGTH': 123, 'HTTP_A': 'b', # 'HTTP_C_C': 'd,e', 'RAW_URI': '/foo/bar?baz=1', 'SERVER_PROTOCOL': 'HTTP/1.1', 'wsgi.input': b'hello world', 'aiohttp.request': request, } for k, v in expected_environ.items(): assert v == environ[k] assert environ['HTTP_C_C'] == 'd,e' or environ['HTTP_C_C'] == 'e,d' # @mock.patch('async_aiohttp.aiohttp.web.Response') def test_make_response(self): rv = async_aiohttp.make_response( '202 ACCEPTED', {'foo': 'bar'}, b'payload', {} ) assert rv.status == 202 assert rv.headers['foo'] == 'bar' assert rv.body == b'payload' ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1791414318.6077807 python_engineio-4.15.1/tests/async/test_asgi.py0000644000175000017500000005326515261550057021222 0ustar00miguelmiguelimport os from unittest import mock from engineio.async_drivers import asgi as async_asgi class TestAsgi: async def test_create_app(self): app = async_asgi.ASGIApp( 'eio', 'other_app', static_files='static_files', engineio_path='/foo/', ) assert app.engineio_server == 'eio' assert app.other_asgi_app == 'other_app' assert app.static_files == 'static_files' assert app.engineio_path == '/foo/' async def test_engineio_routing(self): mock_server = mock.MagicMock() mock_server.handle_request = mock.AsyncMock() app = async_asgi.ASGIApp(mock_server) scope = {'type': 'http', 'path': '/engine.io/'} await app(scope, 'receive', 'send') mock_server.handle_request.assert_awaited_once_with( scope, 'receive', 'send' ) mock_server.handle_request.reset_mock() scope = {'type': 'http', 'path': '/engine.io/'} await app(scope, 'receive', 'send') mock_server.handle_request.assert_awaited_once_with( scope, 'receive', 'send' ) mock_server.handle_request.reset_mock() scope = {'type': 'http', 'path': '/engine.iofoo/'} await app(scope, 'receive', mock.AsyncMock()) mock_server.handle_request.assert_not_awaited() app = async_asgi.ASGIApp(mock_server, engineio_path=None) mock_server.handle_request.reset_mock() scope = {'type': 'http', 'path': '/foo'} await app(scope, 'receive', 'send') mock_server.handle_request.assert_awaited_once_with( scope, 'receive', 'send' ) app = async_asgi.ASGIApp(mock_server, engineio_path='mysocket.io') mock_server.handle_request.reset_mock() scope = {'type': 'http', 'path': '/mysocket.io'} await app(scope, 'receive', 'send') mock_server.handle_request.assert_awaited_once_with( scope, 'receive', 'send' ) mock_server.handle_request.reset_mock() scope = {'type': 'http', 'path': '/mysocket.io/'} await app(scope, 'receive', 'send') mock_server.handle_request.assert_awaited_once_with( scope, 'receive', 'send' ) mock_server.handle_request.reset_mock() scope = {'type': 'http', 'path': '/mysocket.io/foo'} await app(scope, 'receive', 'send') mock_server.handle_request.assert_awaited_once_with( scope, 'receive', 'send' ) mock_server.handle_request.reset_mock() scope = {'type': 'http', 'path': '/mysocket.iofoo'} await app(scope, 'receive', mock.AsyncMock()) mock_server.handle_request.assert_not_awaited() async def test_other_app_routing(self): other_app = mock.AsyncMock() app = async_asgi.ASGIApp('eio', other_app) scope = {'type': 'http', 'path': '/foo'} await app(scope, 'receive', 'send') other_app.assert_awaited_once_with(scope, 'receive', 'send') async def test_other_app_lifespan_routing(self): other_app = mock.AsyncMock() app = async_asgi.ASGIApp('eio', other_app) scope = {'type': 'lifespan'} await app(scope, 'receive', 'send') other_app.assert_awaited_once_with(scope, 'receive', 'send') async def test_static_file_routing(self): root_dir = os.path.dirname(__file__) app = async_asgi.ASGIApp( 'eio', static_files={ '/': root_dir + '/index.html', '/foo': { 'content_type': 'text/plain', 'filename': root_dir + '/index.html', }, '/static': root_dir, '/static/test/': root_dir + '/', '/static2/test/': {'filename': root_dir + '/', 'content_type': 'image/gif'}, }, ) async def check_path(path, status_code, content_type, body): scope = {'type': 'http', 'path': path} receive = mock.AsyncMock(return_value={'type': 'http.request'}) send = mock.AsyncMock() await app(scope, receive, send) send.assert_any_await( { 'type': 'http.response.start', 'status': status_code, 'headers': [ (b'Content-Type', content_type.encode('utf-8')) ], } ) send.assert_any_await( {'type': 'http.response.body', 'body': body.encode('utf-8')} ) await check_path('/', 200, 'text/html', '\n') await check_path('/foo', 200, 'text/plain', '\n') await check_path('/foo/bar', 404, 'text/plain', 'Not Found') await check_path('/static/index.html', 200, 'text/html', '\n') await check_path('/static/foo.bar', 404, 'text/plain', 'Not Found') await check_path( '/static/test/index.html', 200, 'text/html', '\n' ) await check_path('/static/test/index.html', 200, 'text/html', '\n') await check_path('/static/test/files/', 200, 'text/html', 'file\n') await check_path('/static/test/files/file.txt', 200, 'text/plain', 'file\n') await check_path('/static/test/files/x.html', 404, 'text/plain', 'Not Found') await check_path('/static2/test/', 200, 'image/gif', '\n') await check_path('/static2/test/index.html', 200, 'image/gif', '\n') await check_path('/static2/test/files/', 200, 'image/gif', 'file\n') await check_path('/static2/test/files/file.txt', 200, 'image/gif', 'file\n') await check_path('/static2/test/files/x.html', 404, 'text/plain', 'Not Found') await check_path('/bar/foo', 404, 'text/plain', 'Not Found') await check_path('', 404, 'text/plain', 'Not Found') await check_path('/static/../../LICENSE', 404, 'text/plain', 'Not Found') app.static_files[''] = 'index.html' await check_path('/static/test/', 200, 'text/html', '\n') app.static_files[''] = {'filename': 'index.html'} await check_path('/static/test/', 200, 'text/html', '\n') app.static_files[''] = { 'filename': 'index.html', 'content_type': 'image/gif', } await check_path('/static/test/', 200, 'image/gif', '\n') app.static_files[''] = {'filename': 'test.gif'} await check_path('/static/test/', 404, 'text/plain', 'Not Found') app.static_files = {} await check_path('/static/test/index.html', 404, 'text/plain', 'Not Found') async def test_lifespan_startup(self): app = async_asgi.ASGIApp('eio') scope = {'type': 'lifespan'} receive = mock.AsyncMock(side_effect=[{'type': 'lifespan.startup'}, {'type': 'lifespan.shutdown'}]) send = mock.AsyncMock() await app(scope, receive, send) send.assert_any_await( {'type': 'lifespan.startup.complete'} ) async def test_lifespan_startup_sync_function(self): up = False def startup(): nonlocal up up = True app = async_asgi.ASGIApp('eio', on_startup=startup) scope = {'type': 'lifespan'} receive = mock.AsyncMock(side_effect=[{'type': 'lifespan.startup'}, {'type': 'lifespan.shutdown'}]) send = mock.AsyncMock() await app(scope, receive, send) send.assert_any_await( {'type': 'lifespan.startup.complete'} ) assert up async def test_lifespan_startup_async_function(self): up = False async def startup(): nonlocal up up = True app = async_asgi.ASGIApp('eio', on_startup=startup) scope = {'type': 'lifespan'} receive = mock.AsyncMock(side_effect=[{'type': 'lifespan.startup'}, {'type': 'lifespan.shutdown'}]) send = mock.AsyncMock() await app(scope, receive, send) send.assert_any_await( {'type': 'lifespan.startup.complete'} ) assert up async def test_lifespan_startup_function_exception(self): up = False def startup(): raise Exception app = async_asgi.ASGIApp('eio', on_startup=startup) scope = {'type': 'lifespan'} receive = mock.AsyncMock(side_effect=[{'type': 'lifespan.startup'}]) send = mock.AsyncMock() await app(scope, receive, send) send.assert_awaited_once_with({'type': 'lifespan.startup.failed'}) assert not up async def test_lifespan_shutdown(self): app = async_asgi.ASGIApp('eio') scope = {'type': 'lifespan'} receive = mock.AsyncMock(return_value={'type': 'lifespan.shutdown'}) send = mock.AsyncMock() await app(scope, receive, send) send.assert_awaited_once_with( {'type': 'lifespan.shutdown.complete'} ) async def test_lifespan_shutdown_sync_function(self): down = False def shutdown(): nonlocal down down = True app = async_asgi.ASGIApp('eio', on_shutdown=shutdown) scope = {'type': 'lifespan'} receive = mock.AsyncMock(return_value={'type': 'lifespan.shutdown'}) send = mock.AsyncMock() await app(scope, receive, send) send.assert_awaited_once_with( {'type': 'lifespan.shutdown.complete'} ) assert down async def test_lifespan_shutdown_async_function(self): down = False async def shutdown(): nonlocal down down = True app = async_asgi.ASGIApp('eio', on_shutdown=shutdown) scope = {'type': 'lifespan'} receive = mock.AsyncMock(return_value={'type': 'lifespan.shutdown'}) send = mock.AsyncMock() await app(scope, receive, send) send.assert_awaited_once_with( {'type': 'lifespan.shutdown.complete'} ) assert down async def test_lifespan_shutdown_function_exception(self): down = False def shutdown(): raise Exception app = async_asgi.ASGIApp('eio', on_shutdown=shutdown) scope = {'type': 'lifespan'} receive = mock.AsyncMock(return_value={'type': 'lifespan.shutdown'}) send = mock.AsyncMock() await app(scope, receive, send) send.assert_awaited_once_with({'type': 'lifespan.shutdown.failed'}) assert not down async def test_lifespan_invalid(self): app = async_asgi.ASGIApp('eio') scope = {'type': 'lifespan'} receive = mock.AsyncMock(side_effect=[{'type': 'lifespan.foo'}, {'type': 'lifespan.shutdown'}]) send = mock.AsyncMock() await app(scope, receive, send) send.assert_awaited_once_with( {'type': 'lifespan.shutdown.complete'} ) async def test_not_found(self): app = async_asgi.ASGIApp('eio') scope = {'type': 'http', 'path': '/foo'} receive = mock.AsyncMock(return_value={'type': 'http.request'}) send = mock.AsyncMock() await app(scope, receive, send) send.assert_any_await( { 'type': 'http.response.start', 'status': 404, 'headers': [(b'Content-Type', b'text/plain')], } ) send.assert_any_await( {'type': 'http.response.body', 'body': b'Not Found'} ) async def test_translate_request(self): receive = mock.AsyncMock( return_value={'type': 'http.request', 'body': b'hello world'} ) send = mock.AsyncMock() environ = await async_asgi.translate_request( { 'type': 'http', 'method': 'PUT', 'headers': [ (b'a', b'b'), (b'c-c', b'd'), (b'c_c', b'e'), (b'content-type', b'application/json'), (b'content-length', b'123'), ], 'path': '/foo/bar', 'query_string': b'baz=1', }, receive, send, ) expected_environ = { 'REQUEST_METHOD': 'PUT', 'PATH_INFO': '/foo/bar', 'QUERY_STRING': 'baz=1', 'CONTENT_TYPE': 'application/json', 'CONTENT_LENGTH': '123', 'HTTP_A': 'b', # 'HTTP_C_C': 'd,e', 'RAW_URI': '/foo/bar?baz=1', 'SERVER_PROTOCOL': 'HTTP/1.1', 'asgi.receive': receive, 'asgi.send': send, } for k, v in expected_environ.items(): assert v == environ[k] assert environ['HTTP_C_C'] == 'd,e' or environ['HTTP_C_C'] == 'e,d' body = await environ['wsgi.input'].read() assert body == b'hello world' async def test_translate_request_no_query_string(self): receive = mock.AsyncMock( return_value={'type': 'http.request', 'body': b'hello world'} ) send = mock.AsyncMock() environ = await async_asgi.translate_request( { 'type': 'http', 'method': 'PUT', 'headers': [ (b'a', b'b'), (b'c-c', b'd'), (b'c_c', b'e'), (b'content-type', b'application/json'), (b'content-length', b'123'), ], 'path': '/foo/bar', }, receive, send, ) expected_environ = { 'REQUEST_METHOD': 'PUT', 'PATH_INFO': '/foo/bar', 'QUERY_STRING': '', 'CONTENT_TYPE': 'application/json', 'CONTENT_LENGTH': '123', 'HTTP_A': 'b', # 'HTTP_C_C': 'd,e', 'RAW_URI': '/foo/bar', 'SERVER_PROTOCOL': 'HTTP/1.1', 'asgi.receive': receive, 'asgi.send': send, } for k, v in expected_environ.items(): assert v == environ[k] assert environ['HTTP_C_C'] == 'd,e' or environ['HTTP_C_C'] == 'e,d' body = await environ['wsgi.input'].read() assert body == b'hello world' async def test_translate_request_with_large_body(self): receive = mock.AsyncMock( side_effect=[ {'type': 'http.request', 'body': b'hello ', 'more_body': True}, {'type': 'http.request', 'body': b'world', 'more_body': True}, {'type': 'foo.bar'}, # should stop parsing here {'type': 'http.request', 'body': b'!!!'}, ] ) send = mock.AsyncMock() environ = await async_asgi.translate_request( { 'type': 'http', 'method': 'PUT', 'headers': [ (b'a', b'b'), (b'c-c', b'd'), (b'c_c', b'e'), (b'content-type', b'application/json'), (b'content-length', b'123'), ], 'path': '/foo/bar', 'query_string': b'baz=1', }, receive, send, ) expected_environ = { 'REQUEST_METHOD': 'PUT', 'PATH_INFO': '/foo/bar', 'QUERY_STRING': 'baz=1', 'CONTENT_TYPE': 'application/json', 'CONTENT_LENGTH': '123', 'HTTP_A': 'b', # 'HTTP_C_C': 'd,e', 'RAW_URI': '/foo/bar?baz=1', 'SERVER_PROTOCOL': 'HTTP/1.1', 'asgi.receive': receive, 'asgi.send': send, } for k, v in expected_environ.items(): assert v == environ[k] assert environ['HTTP_C_C'] == 'd,e' or environ['HTTP_C_C'] == 'e,d' body = await environ['wsgi.input'].read() assert body == b'hello world' async def test_translate_request_read_zero(self): receive = mock.AsyncMock( return_value={'type': 'http.request', 'body': b'hello world'} ) send = mock.AsyncMock() environ = await async_asgi.translate_request( { 'type': 'http', 'method': 'PUT', 'headers': [ (b'content-type', b'application/json'), (b'content-length', b'123'), ], 'path': '/foo/bar', }, receive, send, ) body = await environ['wsgi.input'].read(0) assert body == b'' async def test_translate_websocket_request(self): receive = mock.AsyncMock(return_value={'type': 'websocket.connect'}) send = mock.AsyncMock() await async_asgi.translate_request( { 'type': 'websocket', 'headers': [ (b'a', b'b'), (b'c-c', b'd'), (b'c_c', b'e'), (b'content-type', b'application/json'), (b'content-length', b'123'), ], 'path': '/foo/bar', 'query_string': b'baz=1', }, receive, send, ) send.assert_not_awaited() async def test_translate_unknown_request(self): receive = mock.AsyncMock(return_value={'type': 'http.foo'}) send = mock.AsyncMock() environ = await async_asgi.translate_request( {'type': 'http', 'path': '/foo/bar', 'query_string': b'baz=1'}, receive, send, ) assert environ == {} async def test_translate_request_bad_unicode(self): receive = mock.AsyncMock(return_value={'type': 'http.request', 'body': b'foo'}) send = mock.AsyncMock() environ = await async_asgi.translate_request( { 'type': 'http.request', 'headers': [ (b'a', b'b'), (b'c', b'\xa0'), (b'e', b'f'), ], 'path': '/foo/bar', 'query_string': b'baz=1&bad=\xa0', }, receive, send, ) assert environ['HTTP_A'] == 'b' assert environ['HTTP_E'] == 'f' assert 'HTTP_C' not in environ assert environ['QUERY_STRING'] == '' assert environ['RAW_URI'] == '/foo/bar' async def test_make_response(self): environ = {'asgi.send': mock.AsyncMock(), 'asgi.scope': {'type': 'http'}} await async_asgi.make_response( '202 ACCEPTED', [('foo', 'bar')], b'payload', environ ) environ['asgi.send'].assert_any_await( { 'type': 'http.response.start', 'status': 202, 'headers': [(b'foo', b'bar')], } ) environ['asgi.send'].assert_any_await( {'type': 'http.response.body', 'body': b'payload'} ) async def test_make_response_websocket_accept(self): environ = { 'asgi.send': mock.AsyncMock(), 'asgi.scope': {'type': 'websocket'}, } await async_asgi.make_response( '200 OK', [('foo', 'bar')], b'payload', environ ) environ['asgi.send'].assert_awaited_with( {'type': 'websocket.accept', 'headers': [(b'foo', b'bar')]} ) async def test_make_response_websocket_reject(self): environ = { 'asgi.send': mock.AsyncMock(), 'asgi.scope': {'type': 'websocket'}, } await async_asgi.make_response( '401 UNAUTHORIZED', [('foo', 'bar')], b'payload', environ ) environ['asgi.send'].assert_awaited_with( {'type': 'websocket.close', 'reason': 'payload'} ) async def test_make_response_websocket_reject_no_payload(self): environ = { 'asgi.send': mock.AsyncMock(), 'asgi.scope': {'type': 'websocket'}, } await async_asgi.make_response( '401 UNAUTHORIZED', [('foo', 'bar')], None, environ ) environ['asgi.send'].assert_awaited_with( {'type': 'websocket.close'} ) async def test_sub_app_routing(self): class ASGIDispatcher: def __init__(self, routes): self.routes = routes async def __call__(self, scope, receive, send): path = scope['path'] for prefix, app in self.routes.items(): if path.startswith(prefix): await app(scope, receive, send) return assert False, 'No route found' other_app = mock.AsyncMock() mock_server = mock.MagicMock() mock_server.handle_request = mock.AsyncMock() eio_app = async_asgi.ASGIApp(mock_server, engineio_path=None) root_app = ASGIDispatcher({'/foo': other_app, '/eio': eio_app}) scope = {'type': 'http', 'path': '/foo/bar'} await root_app(scope, 'receive', 'send') other_app.assert_awaited_once_with(scope, 'receive', 'send') scope = {'type': 'http', 'path': '/eio/'} await root_app(scope, 'receive', 'send') eio_app.engineio_server.handle_request.assert_awaited_once_with( scope, 'receive', 'send') ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1793129060.452342 python_engineio-4.15.1/tests/async/test_client.py0000644000175000017500000016447615270175144021564 0ustar00miguelmiguelimport asyncio import ssl from unittest import mock try: import aiohttp except ImportError: aiohttp = None import pytest from engineio import async_client from engineio import base_client from engineio import exceptions from engineio import packet from engineio import payload class TestAsyncClient: def mock_queue(self, client): client.queue = mock.MagicMock() client.queue_empty = RuntimeError client.queue.get_nowait.side_effect = client.queue_empty client.queue.get = mock.AsyncMock() client.queue.put = mock.AsyncMock() client.queue.join = mock.AsyncMock() @staticmethod def mock_ws_timeout(ws_close): return ws_close async def test_is_asyncio_based(self): c = async_client.AsyncClient() assert c.is_asyncio_based() async def test_already_connected(self): c = async_client.AsyncClient() c.state = 'connected' with pytest.raises(ValueError): await c.connect('http://foo') async def test_invalid_transports(self): c = async_client.AsyncClient() with pytest.raises(ValueError): await c.connect('http://foo', transports=['foo', 'bar']) async def test_some_invalid_transports(self): c = async_client.AsyncClient() c._connect_websocket = mock.AsyncMock() await c.connect('http://foo', transports=['foo', 'websocket', 'bar']) assert c.transports == ['websocket'] async def test_connect_polling(self): c = async_client.AsyncClient() c._connect_polling = mock.AsyncMock(return_value='foo') assert await c.connect('http://foo') == 'foo' c._connect_polling.assert_awaited_once_with( 'http://foo', {}, 'engine.io' ) c = async_client.AsyncClient() c._connect_polling = mock.AsyncMock(return_value='foo') assert await c.connect('http://foo', transports=['polling']) == 'foo' c._connect_polling.assert_awaited_once_with( 'http://foo', {}, 'engine.io' ) c = async_client.AsyncClient() c._connect_polling = mock.AsyncMock(return_value='foo') assert ( await c.connect('http://foo', transports=['polling', 'websocket']) == 'foo' ) c._connect_polling.assert_awaited_once_with( 'http://foo', {}, 'engine.io' ) async def test_connect_websocket(self): c = async_client.AsyncClient() c._connect_websocket = mock.AsyncMock(return_value='foo') assert await c.connect('http://foo', transports=['websocket']) == 'foo' c._connect_websocket.assert_awaited_once_with( 'http://foo', {}, 'engine.io' ) c = async_client.AsyncClient() c._connect_websocket = mock.AsyncMock(return_value='foo') assert await c.connect('http://foo', transports='websocket') == 'foo' c._connect_websocket.assert_awaited_once_with( 'http://foo', {}, 'engine.io' ) async def test_connect_query_string(self): c = async_client.AsyncClient() c._connect_polling = mock.AsyncMock(return_value='foo') assert await c.connect('http://foo?bar=baz') == 'foo' c._connect_polling.assert_awaited_once_with( 'http://foo?bar=baz', {}, 'engine.io' ) async def test_connect_custom_headers(self): c = async_client.AsyncClient() c._connect_polling = mock.AsyncMock(return_value='foo') assert await c.connect('http://foo', headers={'Foo': 'Bar'}) == 'foo' c._connect_polling.assert_awaited_once_with( 'http://foo', {'Foo': 'Bar'}, 'engine.io' ) async def test_connect_polling_handler_failure(self): c = async_client.AsyncClient() c._send_request = mock.AsyncMock() c._send_request.return_value.status = 200 c._send_request.return_value.read = mock.AsyncMock( return_value=payload.Payload( packets=[ packet.Packet( packet.OPEN, { 'sid': '123', 'upgrades': [], 'pingInterval': 1000, 'pingTimeout': 2000, }, ) ] ).encode().encode('utf-8') ) @c.on('connect') def connect(): raise ValueError('Failed on purpose') with pytest.raises(exceptions.ConnectionError, match='Connect handler failed: Failed on purpose'): await c.connect('http://foo') @mock.patch('engineio.async_client.aiohttp.ClientSession') async def test_connect_websocket_default_session(self, _session): _session().ws_connect = mock.AsyncMock() ws = _session().ws_connect.return_value ws.receive = mock.AsyncMock() ws.receive.return_value.data = packet.Packet( packet.MESSAGE, # invaid on purpose {}, ).encode() c = async_client.AsyncClient() with pytest.raises(exceptions.ConnectionError, match='no OPEN packet'): await c.connect('http://foo', transports='websocket') async def test_connect_websocket_external_session(self): c = async_client.AsyncClient() c.http = mock.Mock(closed=False) c.external_http = True c.http.ws_connect = mock.AsyncMock() ws = c.http.ws_connect.return_value ws.receive = mock.AsyncMock() ws.receive.return_value.data = packet.Packet( packet.MESSAGE, # invaid on purpose {}, ).encode() with pytest.raises(exceptions.ConnectionError, match='no OPEN packet'): await c.connect('http://foo', transports='websocket') async def test_connect_websocket_async_session_factory(self): async def get_session(): http = mock.MagicMock(closed=False) http.ws_connect = mock.AsyncMock() http.close = mock.AsyncMock() ws = http.ws_connect.return_value ws.receive = mock.AsyncMock() ws.receive.return_value.data = packet.Packet( packet.MESSAGE, # invaid on purpose {}, ).encode() return http c = async_client.AsyncClient(http_session=get_session) with pytest.raises(exceptions.ConnectionError, match='no OPEN packet'): await c.connect('http://foo', transports='websocket') @mock.patch('engineio.client.time.time', return_value=123.456) async def test_connect_websocket_handler_failure(self, _time): http = mock.MagicMock(closed=False) http.ws_connect = mock.AsyncMock() http.close = mock.AsyncMock() ws = http.ws_connect.return_value ws.receive = mock.AsyncMock() ws.receive.return_value.data = packet.Packet( packet.OPEN, { 'sid': '123', 'upgrades': [], 'pingInterval': 1000, 'pingTimeout': 2000, }, ).encode() c = async_client.AsyncClient(http_session=lambda: http) @c.on('connect') def connect(): raise ValueError('Failed on purpose') with pytest.raises(exceptions.ConnectionError, match='Connect handler failed: Failed on purpose'): await c.connect('http://foo', transports='websocket') @mock.patch('engineio.async_client.aiohttp.ClientSession') async def test_send_request_with_default_session(self, _session): _session().get = mock.AsyncMock() c = async_client.AsyncClient() await c._send_request('GET', '/socket.io') _session().get.assert_awaited_once() async def test_send_request_with_session_factory(self): http = mock.Mock() http.closed = False http.get = mock.AsyncMock() c = async_client.AsyncClient(http_session=lambda: http) await c._send_request('GET', '/socket.io') http.get.assert_awaited_once() async def test_send_request_with_async_session_factory(self): async def get_session(): http = mock.Mock() http.closed = False http.get = mock.AsyncMock() return http c = async_client.AsyncClient(http_session=get_session) await c._send_request('GET', '/socket.io') c.http.get.assert_awaited_once() async def test_send_request_with_external_session(self): c = async_client.AsyncClient() c.http = mock.Mock() c.http.closed = False c.http.get = mock.AsyncMock() c.external_http = True await c._send_request('GET', '/socket.io') c.http.get.assert_awaited_once() async def test_wait(self): c = async_client.AsyncClient() done = [] async def fake_read_look_task(): done.append(True) c.read_loop_task = fake_read_look_task() await c.wait() assert done == [True] async def test_wait_no_task(self): c = async_client.AsyncClient() c.read_loop_task = None await c.wait() async def test_send(self): c = async_client.AsyncClient() saved_packets = [] async def fake_send_packet(pkt): saved_packets.append(pkt) c._send_packet = fake_send_packet await c.send('foo') await c.send('foo') await c.send(b'foo') assert saved_packets[0].packet_type == packet.MESSAGE assert saved_packets[0].data == 'foo' assert not saved_packets[0].binary assert saved_packets[1].packet_type == packet.MESSAGE assert saved_packets[1].data == 'foo' assert not saved_packets[1].binary assert saved_packets[2].packet_type == packet.MESSAGE assert saved_packets[2].data == b'foo' assert saved_packets[2].binary async def test_disconnect_not_connected(self): c = async_client.AsyncClient() c.state = 'foo' c.sid = 'bar' await c.disconnect() assert c.state == 'disconnected' assert c.sid is None async def test_disconnect_polling(self): c = async_client.AsyncClient() base_client.connected_clients.append(c) c.state = 'connected' c.current_transport = 'polling' self.mock_queue(c) c.read_loop_task = mock.AsyncMock()() c.ws = mock.MagicMock() c.ws.close = mock.AsyncMock() c._trigger_event = mock.AsyncMock() await c.disconnect() c.ws.close.assert_not_awaited() assert c not in base_client.connected_clients c._trigger_event.assert_awaited_once_with( 'disconnect', c.reason.CLIENT_DISCONNECT, run_async=False ) async def test_disconnect_websocket(self): c = async_client.AsyncClient() base_client.connected_clients.append(c) c.state = 'connected' c.current_transport = 'websocket' self.mock_queue(c) c.read_loop_task = mock.AsyncMock()() c.ws = mock.MagicMock() c.ws.close = mock.AsyncMock() c._trigger_event = mock.AsyncMock() await c.disconnect() c.ws.close.assert_awaited_once_with() assert c not in base_client.connected_clients c._trigger_event.assert_awaited_once_with( 'disconnect', c.reason.CLIENT_DISCONNECT, run_async=False ) async def test_disconnect_polling_abort(self): c = async_client.AsyncClient() base_client.connected_clients.append(c) c.state = 'connected' c.current_transport = 'polling' self.mock_queue(c) c.read_loop_task = mock.AsyncMock()() c.ws = mock.MagicMock() c.ws.close = mock.AsyncMock() await c.disconnect(abort=True) c.queue.join.assert_not_awaited() c.ws.close.assert_not_awaited() assert c not in base_client.connected_clients async def test_disconnect_websocket_abort(self): c = async_client.AsyncClient() base_client.connected_clients.append(c) c.state = 'connected' c.current_transport = 'websocket' self.mock_queue(c) c.read_loop_task = mock.AsyncMock()() c.ws = mock.MagicMock() c.ws.close = mock.AsyncMock() await c.disconnect(abort=True) c.queue.join.assert_not_awaited() c.ws.assert_not_called() assert c not in base_client.connected_clients async def test_background_tasks(self): r = [] async def foo(arg): r.append(arg) c = async_client.AsyncClient() await c.start_background_task(foo, 'bar') assert r == ['bar'] async def test_sleep(self): c = async_client.AsyncClient() await c.sleep(0) async def test_create_queue(self): c = async_client.AsyncClient() q = c.create_queue() with pytest.raises(c.queue_empty): q.get_nowait() async def test_create_event(self): c = async_client.AsyncClient() e = c.create_event() assert not e.is_set() e.set() assert e.is_set() @mock.patch('engineio.client.time.time', return_value=123.456) async def test_polling_connection_failed(self, _time): c = async_client.AsyncClient() c._send_request = mock.AsyncMock(return_value=None) with pytest.raises(exceptions.ConnectionError): await c.connect('http://foo', headers={'Foo': 'Bar'}) c._send_request.assert_awaited_once_with( 'GET', 'http://foo/engine.io/?transport=polling&EIO=4&t=123.456', headers={'Foo': 'Bar'}, timeout=5, ) async def test_polling_connection_404(self): c = async_client.AsyncClient() c._send_request = mock.AsyncMock() c._send_request.return_value.status = 404 c._send_request.return_value.json = mock.AsyncMock( return_value={'foo': 'bar'} ) try: await c.connect('http://foo') except exceptions.ConnectionError as exc: assert len(exc.args) == 2 assert ( exc.args[0] == 'Unexpected status code 404 in server response' ) assert exc.args[1] == {'foo': 'bar'} async def test_polling_connection_404_no_json(self): c = async_client.AsyncClient() c._send_request = mock.AsyncMock() c._send_request.return_value.status = 404 c._send_request.return_value.json = mock.AsyncMock( side_effect=aiohttp.ContentTypeError('foo', 'bar') ) try: await c.connect('http://foo') except exceptions.ConnectionError as exc: assert len(exc.args) == 2 assert ( exc.args[0] == 'Unexpected status code 404 in server response' ) assert exc.args[1] is None async def test_polling_connection_invalid_packet(self): c = async_client.AsyncClient() c._send_request = mock.AsyncMock() c._send_request.return_value.status = 200 c._send_request.return_value.read = mock.AsyncMock(return_value=b'foo') with pytest.raises(exceptions.ConnectionError): await c.connect('http://foo') async def test_polling_connection_no_open_packet(self): c = async_client.AsyncClient() c._send_request = mock.AsyncMock() c._send_request.return_value.status = 200 c._send_request.return_value.read = mock.AsyncMock( return_value=payload.Payload( packets=[ packet.Packet( packet.CLOSE, { 'sid': '123', 'upgrades': [], 'pingInterval': 10, 'pingTimeout': 20, }, ) ] ).encode().encode('utf-8') ) with pytest.raises(exceptions.ConnectionError): await c.connect('http://foo') async def test_polling_connection_successful(self): c = async_client.AsyncClient() c._send_request = mock.AsyncMock() c._send_request.return_value.status = 200 c._send_request.return_value.read = mock.AsyncMock( return_value=payload.Payload( packets=[ packet.Packet( packet.OPEN, { 'sid': '123', 'upgrades': [], 'pingInterval': 1000, 'pingTimeout': 2000, }, ) ] ).encode().encode('utf-8') ) c._read_loop_polling = mock.AsyncMock() c._read_loop_websocket = mock.AsyncMock() c._write_loop = mock.AsyncMock() on_connect = mock.AsyncMock() c.on('connect', on_connect) await c.connect('http://foo') c._read_loop_polling.assert_called_once_with() c._read_loop_websocket.assert_not_called() c._write_loop.assert_called_once_with() on_connect.assert_awaited_once_with() assert c in base_client.connected_clients assert ( c.base_url == 'http://foo/engine.io/?transport=polling&EIO=4&sid=123' ) assert c.sid == '123' assert c.ping_interval == 1 assert c.ping_timeout == 2 assert c.upgrades == [] assert c.transport() == 'polling' async def test_polling_https_noverify_connection_successful(self): c = async_client.AsyncClient(ssl_verify=False) c._send_request = mock.AsyncMock() c._send_request.return_value.status = 200 c._send_request.return_value.read = mock.AsyncMock( return_value=payload.Payload( packets=[ packet.Packet( packet.OPEN, { 'sid': '123', 'upgrades': [], 'pingInterval': 1000, 'pingTimeout': 2000, }, ) ] ).encode().encode('utf-8') ) c._read_loop_polling = mock.AsyncMock() c._read_loop_websocket = mock.AsyncMock() c._write_loop = mock.AsyncMock() on_connect = mock.AsyncMock() c.on('connect', on_connect) await c.connect('https://foo') c._read_loop_polling.assert_called_once_with() c._read_loop_websocket.assert_not_called() c._write_loop.assert_called_once_with() on_connect.assert_awaited_once_with() assert c in base_client.connected_clients assert ( c.base_url == 'https://foo/engine.io/?transport=polling&EIO=4&sid=123' ) assert c.sid == '123' assert c.ping_interval == 1 assert c.ping_timeout == 2 assert c.upgrades == [] assert c.transport() == 'polling' async def test_polling_connection_with_more_packets(self): c = async_client.AsyncClient() c._send_request = mock.AsyncMock() c._send_request.return_value.status = 200 c._send_request.return_value.read = mock.AsyncMock( return_value=payload.Payload( packets=[ packet.Packet( packet.OPEN, { 'sid': '123', 'upgrades': [], 'pingInterval': 1000, 'pingTimeout': 2000, }, ), packet.Packet(packet.NOOP), ] ).encode().encode('utf-8') ) c._read_loop_polling = mock.AsyncMock() c._read_loop_websocket = mock.AsyncMock() c._write_loop = mock.AsyncMock() c._receive_packet = mock.AsyncMock() on_connect = mock.AsyncMock() c.on('connect', on_connect) await c.connect('http://foo') assert c._receive_packet.await_count == 1 assert ( c._receive_packet.await_args_list[0][0][0].packet_type == packet.NOOP ) async def test_polling_connection_upgraded(self): c = async_client.AsyncClient() c._send_request = mock.AsyncMock() c._send_request.return_value.status = 200 c._send_request.return_value.read = mock.AsyncMock( return_value=payload.Payload( packets=[ packet.Packet( packet.OPEN, { 'sid': '123', 'upgrades': ['websocket'], 'pingInterval': 1000, 'pingTimeout': 2000, }, ) ] ).encode().encode('utf-8') ) c._connect_websocket = mock.AsyncMock(return_value=True) on_connect = mock.MagicMock() c.on('connect', on_connect) await c.connect('http://foo') c._connect_websocket.assert_awaited_once_with( 'http://foo', {}, 'engine.io' ) on_connect.assert_called_once_with() assert c in base_client.connected_clients assert ( c.base_url == 'http://foo/engine.io/?transport=polling&EIO=4&sid=123' ) assert c.sid == '123' assert c.ping_interval == 1 assert c.ping_timeout == 2 assert c.upgrades == ['websocket'] async def test_polling_connection_not_upgraded(self): c = async_client.AsyncClient() c._send_request = mock.AsyncMock() c._send_request.return_value.status = 200 c._send_request.return_value.read = mock.AsyncMock( return_value=payload.Payload( packets=[ packet.Packet( packet.OPEN, { 'sid': '123', 'upgrades': ['websocket'], 'pingInterval': 1000, 'pingTimeout': 2000, }, ) ] ).encode().encode('utf-8') ) c._connect_websocket = mock.AsyncMock(return_value=False) c._read_loop_polling = mock.AsyncMock() c._read_loop_websocket = mock.AsyncMock() c._write_loop = mock.AsyncMock() on_connect = mock.MagicMock() c.on('connect', on_connect) await c.connect('http://foo') c._connect_websocket.assert_awaited_once_with( 'http://foo', {}, 'engine.io' ) c._read_loop_polling.assert_called_once_with() c._read_loop_websocket.assert_not_called() c._write_loop.assert_called_once_with() on_connect.assert_called_once_with() assert c in base_client.connected_clients @mock.patch('engineio.client.time.time', return_value=123.456) @mock.patch('engineio.async_client.aiohttp.ClientWSTimeout', new=mock_ws_timeout) async def test_websocket_connection_failed(self, _time): http = mock.MagicMock(closed=False) http.ws_connect = mock.AsyncMock( side_effect=[aiohttp.client_exceptions.ServerConnectionError()] ) c = async_client.AsyncClient(http_session=lambda: http) with pytest.raises(exceptions.ConnectionError): await c.connect( 'http://foo', transports=['websocket'], headers={'Foo': 'Bar'}, ) http.ws_connect.assert_awaited_once_with( 'ws://foo/engine.io/?transport=websocket&EIO=4&t=123.456', headers={'Foo': 'Bar'}, timeout=5 ) @mock.patch('engineio.client.time.time', return_value=123.456) async def test_websocket_connection_extra(self, _time): http = mock.MagicMock(closed=False) http.ws_connect = mock.AsyncMock( side_effect=[aiohttp.client_exceptions.ServerConnectionError()] ) c = async_client.AsyncClient( http_session=lambda: http, websocket_extra_options={ 'headers': {'Baz': 'Qux'}, 'timeout': 10 } ) with pytest.raises(exceptions.ConnectionError): await c.connect( 'http://foo', transports=['websocket'], headers={'Foo': 'Bar'}, ) http.ws_connect.assert_awaited_once_with( 'ws://foo/engine.io/?transport=websocket&EIO=4&t=123.456', headers={'Foo': 'Bar', 'Baz': 'Qux'}, timeout=10, ) @mock.patch('engineio.client.time.time', return_value=123.456) @mock.patch('engineio.async_client.aiohttp.ClientWSTimeout', new=mock_ws_timeout) async def test_websocket_upgrade_failed(self, _time): http = mock.MagicMock(closed=False) http.ws_connect = mock.AsyncMock( side_effect=[aiohttp.client_exceptions.ServerConnectionError()] ) c = async_client.AsyncClient(http_session=lambda: http) c.sid = '123' assert not await c.connect('http://foo', transports=['websocket']) http.ws_connect.assert_awaited_once_with( 'ws://foo/engine.io/?transport=websocket&EIO=4&sid=123&t=123.456', headers={}, timeout=5, ) async def test_websocket_connection_no_open_packet(self): http = mock.MagicMock(closed=False) http.ws_connect = mock.AsyncMock() ws = http.ws_connect.return_value ws.receive = mock.AsyncMock() ws.receive.return_value.data = packet.Packet( packet.CLOSE ).encode() c = async_client.AsyncClient(http_session=lambda: http) with pytest.raises(exceptions.ConnectionError): await c.connect('http://foo', transports=['websocket']) @mock.patch('engineio.client.time.time', return_value=123.456) @mock.patch('engineio.async_client.aiohttp.ClientWSTimeout', new=mock_ws_timeout) async def test_websocket_connection_successful(self, _time): http = mock.MagicMock(closed=False) http.ws_connect = mock.AsyncMock() ws = http.ws_connect.return_value ws.receive = mock.AsyncMock() ws.receive.return_value.data = packet.Packet( packet.OPEN, { 'sid': '123', 'upgrades': [], 'pingInterval': 1000, 'pingTimeout': 2000, }, ).encode() c = async_client.AsyncClient(http_session=lambda: http) c._read_loop_polling = mock.AsyncMock() c._read_loop_websocket = mock.AsyncMock() c._write_loop = mock.AsyncMock() on_connect = mock.MagicMock() c.on('connect', on_connect) await c.connect('ws://foo', transports=['websocket']) c._read_loop_polling.assert_not_called() c._read_loop_websocket.assert_called_once_with() c._write_loop.assert_called_once_with() on_connect.assert_called_once_with() assert c in base_client.connected_clients assert c.base_url == 'ws://foo/engine.io/?transport=websocket&EIO=4' assert c.sid == '123' assert c.ping_interval == 1 assert c.ping_timeout == 2 assert c.upgrades == [] assert c.transport() == 'websocket' assert c.ws == ws http.ws_connect.assert_awaited_once_with( 'ws://foo/engine.io/?transport=websocket&EIO=4&t=123.456', headers={}, timeout=5, ) @mock.patch('engineio.client.time.time', return_value=123.456) async def test_websocket_https_noverify_connection_successful(self, _time): http = mock.MagicMock(closed=False) http.ws_connect = mock.AsyncMock() ws = http.ws_connect.return_value ws.receive = mock.AsyncMock() ws.receive.return_value.data = packet.Packet( packet.OPEN, { 'sid': '123', 'upgrades': [], 'pingInterval': 1000, 'pingTimeout': 2000, }, ).encode() c = async_client.AsyncClient( http_session=lambda: http, ssl_verify=False) c._read_loop_polling = mock.AsyncMock() c._read_loop_websocket = mock.AsyncMock() c._write_loop = mock.AsyncMock() on_connect = mock.MagicMock() c.on('connect', on_connect) await c.connect('wss://foo', transports=['websocket']) c._read_loop_polling.assert_not_called() c._read_loop_websocket.assert_called_once_with() c._write_loop.assert_called_once_with() on_connect.assert_called_once_with() assert c in base_client.connected_clients assert c.base_url == 'wss://foo/engine.io/?transport=websocket&EIO=4' assert c.sid == '123' assert c.ping_interval == 1 assert c.ping_timeout == 2 assert c.upgrades == [] assert c.transport() == 'websocket' assert c.ws == ws _, kwargs = http.ws_connect.await_args assert 'ssl' in kwargs assert isinstance(kwargs['ssl'], ssl.SSLContext) assert kwargs['ssl'].verify_mode == ssl.CERT_NONE @mock.patch('engineio.client.time.time', return_value=123.456) @mock.patch('engineio.async_client.aiohttp.ClientWSTimeout', new=mock_ws_timeout) async def test_websocket_connection_with_cookies(self, _time): http = mock.MagicMock(closed=False) http.ws_connect = mock.AsyncMock() ws = http.ws_connect.return_value ws.receive = mock.AsyncMock() ws.receive.return_value.data = packet.Packet( packet.OPEN, { 'sid': '123', 'upgrades': [], 'pingInterval': 1000, 'pingTimeout': 2000, }, ).encode() http._cookie_jar = [mock.MagicMock(), mock.MagicMock()] http._cookie_jar[0].key = 'key' http._cookie_jar[0].value = 'value' http._cookie_jar[1].key = 'key2' http._cookie_jar[1].value = 'value2' c = async_client.AsyncClient(http_session=lambda: http) c._read_loop_polling = mock.AsyncMock() c._read_loop_websocket = mock.AsyncMock() c._write_loop = mock.AsyncMock() on_connect = mock.MagicMock() c.on('connect', on_connect) await c.connect('ws://foo', transports=['websocket']) http.ws_connect.assert_awaited_once_with( 'ws://foo/engine.io/?transport=websocket&EIO=4&t=123.456', headers={}, timeout=5, ) @mock.patch('engineio.client.time.time', return_value=123.456) @mock.patch('engineio.async_client.aiohttp.ClientWSTimeout', new=mock_ws_timeout) async def test_websocket_connection_with_cookie_header(self, _time): http = mock.MagicMock(closed=False) http.ws_connect = mock.AsyncMock() ws = http.ws_connect.return_value ws.receive = mock.AsyncMock() ws.receive.return_value.data = packet.Packet( packet.OPEN, { 'sid': '123', 'upgrades': [], 'pingInterval': 1000, 'pingTimeout': 2000, }, ).encode() http._cookie_jar = [] c = async_client.AsyncClient(http_session=lambda: http) c._read_loop_polling = mock.AsyncMock() c._read_loop_websocket = mock.AsyncMock() c._write_loop = mock.AsyncMock() on_connect = mock.MagicMock() c.on('connect', on_connect) await c.connect( 'ws://foo', headers={'Cookie': 'key=value; key2=value2; key3="value3="'}, transports=['websocket'], ) http.ws_connect.assert_awaited_once_with( 'ws://foo/engine.io/?transport=websocket&EIO=4&t=123.456', headers={}, timeout=5, ) http.cookie_jar.update_cookies.assert_called_once_with( {'key': 'value', 'key2': 'value2', 'key3': 'value3='} ) @mock.patch('engineio.client.time.time', return_value=123.456) @mock.patch('engineio.async_client.aiohttp.ClientWSTimeout', new=mock_ws_timeout) async def test_websocket_connection_with_cookies_and_headers(self, _time): http = mock.MagicMock(closed=False) http.ws_connect = mock.AsyncMock() ws = http.ws_connect.return_value ws.receive = mock.AsyncMock() ws.receive.return_value.data = packet.Packet( packet.OPEN, { 'sid': '123', 'upgrades': [], 'pingInterval': 1000, 'pingTimeout': 2000, }, ).encode() http._cookie_jar = [mock.MagicMock(), mock.MagicMock()] http._cookie_jar[0].key = 'key' http._cookie_jar[0].value = 'value' http._cookie_jar[1].key = 'key2' http._cookie_jar[1].value = 'value2' c = async_client.AsyncClient(http_session=lambda: http) c._read_loop_polling = mock.AsyncMock() c._read_loop_websocket = mock.AsyncMock() c._write_loop = mock.AsyncMock() on_connect = mock.MagicMock() c.on('connect', on_connect) await c.connect( 'ws://foo', headers={'Foo': 'Bar', 'Cookie': 'key3=value3'}, transports=['websocket'], ) http.ws_connect.assert_awaited_once_with( 'ws://foo/engine.io/?transport=websocket&EIO=4&t=123.456', headers={'Foo': 'Bar'}, timeout=5, ) http.cookie_jar.update_cookies.assert_called_once_with( {'key3': 'value3'} ) async def test_websocket_upgrade_no_pong(self): http = mock.MagicMock(closed=False) http.ws_connect = mock.AsyncMock() ws = http.ws_connect.return_value ws.receive = mock.AsyncMock() ws.receive.return_value.data = packet.Packet( packet.OPEN, { 'sid': '123', 'upgrades': [], 'pingInterval': 1000, 'pingTimeout': 2000, }, ).encode() ws.send_str = mock.AsyncMock() c = async_client.AsyncClient(http_session=lambda: http) c.sid = '123' c.current_transport = 'polling' c._read_loop_polling = mock.AsyncMock() c._read_loop_websocket = mock.AsyncMock() c._write_loop = mock.AsyncMock() on_connect = mock.MagicMock() c.on('connect', on_connect) assert not await c.connect('ws://foo', transports=['websocket']) c._read_loop_polling.assert_not_called() c._read_loop_websocket.assert_not_called() c._write_loop.assert_not_called() on_connect.assert_not_called() assert c.transport() == 'polling' ws.send_str.assert_awaited_once_with('2probe') async def test_websocket_upgrade_successful(self): http = mock.MagicMock(closed=False) http.ws_connect = mock.AsyncMock() ws = http.ws_connect.return_value ws.receive = mock.AsyncMock() ws.receive.return_value.data = packet.Packet( packet.PONG, 'probe' ).encode() ws.send_str = mock.AsyncMock() c = async_client.AsyncClient(http_session=lambda: http) c.sid = '123' c.base_url = 'http://foo' c.current_transport = 'polling' c._read_loop_polling = mock.AsyncMock() c._read_loop_websocket = mock.AsyncMock() c._write_loop = mock.AsyncMock() on_connect = mock.MagicMock() c.on('connect', on_connect) assert await c.connect('ws://foo', transports=['websocket']) c._read_loop_polling.assert_not_called() c._read_loop_websocket.assert_called_once_with() c._write_loop.assert_called_once_with() on_connect.assert_not_called() # was called by polling assert c not in base_client.connected_clients # was added by polling assert c.base_url == 'http://foo' # not changed assert c.sid == '123' # not changed assert c.transport() == 'websocket' assert c.ws == ws assert ws.send_str.await_args_list[0] == (('2probe',),) # ping assert ws.send_str.await_args_list[1] == (('5',),) # upgrade async def test_receive_unknown_packet(self): c = async_client.AsyncClient() await c._receive_packet(packet.Packet(encoded_packet='9')) # should be ignored async def test_receive_noop_packet(self): c = async_client.AsyncClient() await c._receive_packet(packet.Packet(packet.NOOP)) # should be ignored async def test_receive_ping_packet(self): c = async_client.AsyncClient() c._send_packet = mock.AsyncMock() await c._receive_packet(packet.Packet(packet.PING)) assert c._send_packet.await_args_list[0][0][0].encode() == '3' async def test_receive_message_packet(self): c = async_client.AsyncClient() c._trigger_event = mock.AsyncMock() await c._receive_packet(packet.Packet(packet.MESSAGE, {'foo': 'bar'})) c._trigger_event.assert_awaited_once_with( 'message', {'foo': 'bar'}, run_async=True ) async def test_receive_close_packet(self): c = async_client.AsyncClient() c.disconnect = mock.AsyncMock() await c._receive_packet(packet.Packet(packet.CLOSE)) c.disconnect.assert_awaited_once_with( abort=True, reason=c.reason.SERVER_DISCONNECT) async def test_send_packet_disconnected(self): c = async_client.AsyncClient() c.queue = c.create_queue() c.state = 'disconnected' await c._send_packet(packet.Packet(packet.NOOP)) assert c.queue.empty() async def test_send_packet(self): c = async_client.AsyncClient() c.queue = c.create_queue() c.state = 'connected' await c._send_packet(packet.Packet(packet.NOOP)) assert not c.queue.empty() pkt = await c.queue.get() assert pkt.packet_type == packet.NOOP async def test_trigger_event_function(self): result = [] def foo_handler(arg): result.append('ok') result.append(arg) c = async_client.AsyncClient() c.on('message', handler=foo_handler) await c._trigger_event('message', 'bar') assert result == ['ok', 'bar'] async def test_trigger_event_coroutine(self): result = [] async def foo_handler(arg): result.append('ok') result.append(arg) c = async_client.AsyncClient() c.on('message', handler=foo_handler) await c._trigger_event('message', 'bar') assert result == ['ok', 'bar'] async def test_trigger_event_function_error(self): def connect_handler(arg): return 1 / 0 def foo_handler(arg): return 1 / 0 c = async_client.AsyncClient() c.on('connect', handler=connect_handler) c.on('message', handler=foo_handler) with pytest.raises(ZeroDivisionError): await c._trigger_event('connect', '123') assert await c._trigger_event('message', 'bar') is None async def test_trigger_event_coroutine_error(self): async def connect_handler(arg): return 1 / 0 async def foo_handler(arg): return 1 / 0 c = async_client.AsyncClient() c.on('connect', handler=connect_handler) c.on('message', handler=foo_handler) with pytest.raises(ZeroDivisionError): await c._trigger_event('connect', '123') assert await c._trigger_event('message', 'bar') is None async def test_trigger_event_function_async(self): result = [] def foo_handler(arg): result.append('ok') result.append(arg) c = async_client.AsyncClient() c.on('message', handler=foo_handler) fut = await c._trigger_event('message', 'bar', run_async=True) await fut assert result == ['ok', 'bar'] async def test_trigger_event_coroutine_async(self): result = [] async def foo_handler(arg): result.append('ok') result.append(arg) c = async_client.AsyncClient() c.on('message', handler=foo_handler) fut = await c._trigger_event('message', 'bar', run_async=True) await fut assert result == ['ok', 'bar'] async def test_trigger_event_function_async_error(self): result = [] def foo_handler(arg): result.append(arg) return 1 / 0 c = async_client.AsyncClient() c.on('message', handler=foo_handler) fut = await c._trigger_event('message', 'bar', run_async=True) with pytest.raises(ZeroDivisionError): await fut assert result == ['bar'] async def test_trigger_event_coroutine_async_error(self): result = [] async def foo_handler(arg): result.append(arg) return 1 / 0 c = async_client.AsyncClient() c.on('message', handler=foo_handler) fut = await c._trigger_event('message', 'bar', run_async=True) with pytest.raises(ZeroDivisionError): await fut assert result == ['bar'] async def test_trigger_unknown_event(self): c = async_client.AsyncClient() await c._trigger_event('connect', run_async=False) await c._trigger_event('message', 123, run_async=True) # should do nothing async def test_trigger_legacy_disconnect_event(self): c = async_client.AsyncClient() @c.on('disconnect') def baz(): return 'baz' r = await c._trigger_event('disconnect', 'foo') assert r == 'baz' async def test_trigger_legacy_disconnect_event_async(self): c = async_client.AsyncClient() @c.on('disconnect') async def baz(): return 'baz' r = await c._trigger_event('disconnect', 'foo') assert r == 'baz' async def test_read_loop_polling_disconnected(self): c = async_client.AsyncClient() c.state = 'disconnected' c._trigger_event = mock.AsyncMock() c.write_loop_task = mock.AsyncMock()() await c._read_loop_polling() c._trigger_event.assert_not_awaited() # should not block @mock.patch('engineio.client.time.time', return_value=123.456) async def test_read_loop_polling_no_response(self, _time): c = async_client.AsyncClient() c.ping_interval = 25 c.ping_timeout = 5 c.state = 'connected' c.base_url = 'http://foo' self.mock_queue(c) c._send_request = mock.AsyncMock(return_value=None) c._trigger_event = mock.AsyncMock() c.write_loop_task = mock.AsyncMock()() await c._read_loop_polling() assert c.state == 'disconnected' c.queue.put.assert_awaited_once_with(None) c._send_request.assert_awaited_once_with( 'GET', 'http://foo&t=123.456', timeout=30 ) c._trigger_event.assert_awaited_once_with( 'disconnect', c.reason.TRANSPORT_ERROR, run_async=False ) @mock.patch('engineio.client.time.time', return_value=123.456) async def test_read_loop_polling_bad_status(self, _time): c = async_client.AsyncClient() c.ping_interval = 25 c.ping_timeout = 5 c.state = 'connected' c.base_url = 'http://foo' self.mock_queue(c) c._send_request = mock.AsyncMock() c._send_request.return_value.status = 400 c.write_loop_task = mock.AsyncMock()() await c._read_loop_polling() assert c.state == 'disconnected' c.queue.put.assert_awaited_once_with(None) c._send_request.assert_awaited_once_with( 'GET', 'http://foo&t=123.456', timeout=30 ) @mock.patch('engineio.client.time.time', return_value=123.456) async def test_read_loop_polling_bad_packet(self, _time): c = async_client.AsyncClient() c.ping_interval = 25 c.ping_timeout = 60 c.state = 'connected' c.base_url = 'http://foo' self.mock_queue(c) c._send_request = mock.AsyncMock() c._send_request.return_value.status = 200 c._send_request.return_value.read = mock.AsyncMock(return_value=b'foo') c.write_loop_task = mock.AsyncMock()() await c._read_loop_polling() assert c.state == 'disconnected' c.queue.put.assert_awaited_once_with(None) c._send_request.assert_awaited_once_with( 'GET', 'http://foo&t=123.456', timeout=65 ) async def test_read_loop_polling(self): c = async_client.AsyncClient() c.ping_interval = 25 c.ping_timeout = 5 c.state = 'connected' c.base_url = 'http://foo' self.mock_queue(c) c._send_request = mock.AsyncMock() c._send_request.side_effect = [ mock.MagicMock( status=200, read=mock.AsyncMock( return_value=payload.Payload( packets=[ packet.Packet(packet.PING), packet.Packet(packet.NOOP), ] ).encode().encode('utf-8') ), ), None, ] c.write_loop_task = mock.AsyncMock()() c._receive_packet = mock.AsyncMock() await c._read_loop_polling() assert c.state == 'disconnected' c.queue.put.assert_awaited_once_with(None) assert c._send_request.await_count == 2 assert c._receive_packet.await_count == 2 assert c._receive_packet.await_args_list[0][0][0].encode() == '2' assert c._receive_packet.await_args_list[1][0][0].encode() == '6' async def test_read_loop_websocket_disconnected(self): c = async_client.AsyncClient() c.state = 'disconnected' c.write_loop_task = mock.AsyncMock()() await c._read_loop_websocket() # should not block async def test_read_loop_websocket_timeout(self): c = async_client.AsyncClient() c.ping_interval = 1 c.ping_timeout = 2 c.base_url = 'ws://foo' c.state = 'connected' self.mock_queue(c) c.ws = mock.MagicMock() c.ws.receive = mock.AsyncMock(side_effect=asyncio.TimeoutError()) c.write_loop_task = mock.AsyncMock()() await c._read_loop_websocket() assert c.state == 'disconnected' c.queue.put.assert_awaited_once_with(None) async def test_read_loop_websocket_no_response(self): c = async_client.AsyncClient() c.ping_interval = 1 c.ping_timeout = 2 c.base_url = 'ws://foo' c.state = 'connected' self.mock_queue(c) c.ws = mock.MagicMock() c.ws.receive = mock.AsyncMock( side_effect=aiohttp.client_exceptions.ServerDisconnectedError() ) c.write_loop_task = mock.AsyncMock()() await c._read_loop_websocket() assert c.state == 'disconnected' c.queue.put.assert_awaited_once_with(None) async def test_read_loop_websocket_unexpected_error(self): c = async_client.AsyncClient() c.ping_interval = 1 c.ping_timeout = 2 c.base_url = 'ws://foo' c.state = 'connected' self.mock_queue(c) c.ws = mock.MagicMock() c.ws.receive = mock.AsyncMock(side_effect=ValueError) c.write_loop_task = mock.AsyncMock()() await c._read_loop_websocket() assert c.state == 'disconnected' c.queue.put.assert_awaited_once_with(None) async def test_read_loop_websocket(self): c = async_client.AsyncClient() c.ping_interval = 1 c.ping_timeout = 2 c.base_url = 'ws://foo' c.state = 'connected' self.mock_queue(c) c.ws = mock.MagicMock() c.ws.receive = mock.AsyncMock( side_effect=[ mock.MagicMock(data=packet.Packet(packet.PING).encode()), ValueError, ] ) c.write_loop_task = mock.AsyncMock()() c._receive_packet = mock.AsyncMock() await c._read_loop_websocket() assert c.state == 'disconnected' assert c._receive_packet.await_args_list[0][0][0].encode() == '2' c.queue.put.assert_awaited_once_with(None) async def test_write_loop_disconnected(self): c = async_client.AsyncClient() c.state = 'disconnected' await c._write_loop() # should not block async def test_write_loop_no_packets(self): c = async_client.AsyncClient() c.state = 'connected' c.ping_interval = 1 c.ping_timeout = 2 self.mock_queue(c) c.queue.get = mock.AsyncMock(return_value=None) await c._write_loop() c.queue.task_done.assert_called_once_with() c.queue.get.assert_awaited_once_with() async def test_write_loop_empty_queue(self): c = async_client.AsyncClient() c.state = 'connected' c.ping_interval = 1 c.ping_timeout = 2 self.mock_queue(c) c.queue.get = mock.AsyncMock(side_effect=c.queue_empty) await c._write_loop() c.queue.get.assert_awaited_once_with() async def test_write_loop_polling_one_packet(self): c = async_client.AsyncClient() c.base_url = 'http://foo' c.state = 'connected' c.ping_interval = 1 c.ping_timeout = 2 c.current_transport = 'polling' self.mock_queue(c) c.queue.get = mock.AsyncMock( side_effect=[ packet.Packet(packet.MESSAGE, {'foo': 'bar'}), c.queue_empty, ] ) c.queue.get_nowait = mock.MagicMock(side_effect=c.queue_empty) c._send_request = mock.AsyncMock() c._send_request.return_value.status = 200 await c._write_loop() assert c.queue.task_done.call_count == 1 p = payload.Payload( packets=[packet.Packet(packet.MESSAGE, {'foo': 'bar'})] ) c._send_request.assert_awaited_once_with( 'POST', 'http://foo', body=p.encode(), headers={'Content-Type': 'text/plain'}, timeout=5, ) async def test_write_loop_polling_three_packets(self): c = async_client.AsyncClient() c.base_url = 'http://foo' c.state = 'connected' c.ping_interval = 1 c.ping_timeout = 2 c.current_transport = 'polling' self.mock_queue(c) c.queue.get = mock.AsyncMock( side_effect=[ packet.Packet(packet.MESSAGE, {'foo': 'bar'}), c.queue_empty, ] ) c.queue.get_nowait = mock.MagicMock( side_effect=[ packet.Packet(packet.PING), packet.Packet(packet.NOOP), c.queue_empty, ] ) c._send_request = mock.AsyncMock() c._send_request.return_value.status = 200 await c._write_loop() assert c.queue.task_done.call_count == 3 p = payload.Payload( packets=[ packet.Packet(packet.MESSAGE, {'foo': 'bar'}), packet.Packet(packet.PING), packet.Packet(packet.NOOP), ] ) c._send_request.assert_awaited_once_with( 'POST', 'http://foo', body=p.encode(), headers={'Content-Type': 'text/plain'}, timeout=5, ) async def test_write_loop_polling_two_packets_done(self): c = async_client.AsyncClient() c.base_url = 'http://foo' c.state = 'connected' c.ping_interval = 1 c.ping_timeout = 2 c.current_transport = 'polling' self.mock_queue(c) c.queue.get = mock.AsyncMock( side_effect=[ packet.Packet(packet.MESSAGE, {'foo': 'bar'}), c.queue_empty, ] ) c.queue.get_nowait = mock.MagicMock( side_effect=[packet.Packet(packet.PING), None] ) c._send_request = mock.AsyncMock() c._send_request.return_value.status = 200 await c._write_loop() assert c.queue.task_done.call_count == 3 p = payload.Payload( packets=[ packet.Packet(packet.MESSAGE, {'foo': 'bar'}), packet.Packet(packet.PING), ] ) c._send_request.assert_awaited_once_with( 'POST', 'http://foo', body=p.encode(), headers={'Content-Type': 'text/plain'}, timeout=5, ) assert c.state == 'connected' async def test_write_loop_polling_bad_connection(self): c = async_client.AsyncClient() c.base_url = 'http://foo' c.state = 'connected' c.ping_interval = 1 c.ping_timeout = 2 c.current_transport = 'polling' self.mock_queue(c) c.queue.get = mock.AsyncMock( side_effect=[packet.Packet(packet.MESSAGE, {'foo': 'bar'})] ) c.queue.get_nowait = mock.MagicMock(side_effect=[c.queue_empty]) c._send_request = mock.AsyncMock(return_value=None) await c._write_loop() assert c.queue.task_done.call_count == 1 p = payload.Payload( packets=[packet.Packet(packet.MESSAGE, {'foo': 'bar'})] ) c._send_request.assert_awaited_once_with( 'POST', 'http://foo', body=p.encode(), headers={'Content-Type': 'text/plain'}, timeout=5, ) assert c.state == 'connected' async def test_write_loop_polling_bad_status(self): c = async_client.AsyncClient() c.base_url = 'http://foo' c.state = 'connected' c.ping_interval = 1 c.ping_timeout = 2 c.current_transport = 'polling' self.mock_queue(c) c.queue.get = mock.AsyncMock( side_effect=[packet.Packet(packet.MESSAGE, {'foo': 'bar'})] ) c.queue.get_nowait = mock.MagicMock(side_effect=[c.queue_empty]) c._send_request = mock.AsyncMock() c._send_request.return_value.status = 500 await c._write_loop() assert c.queue.task_done.call_count == 1 p = payload.Payload( packets=[packet.Packet(packet.MESSAGE, {'foo': 'bar'})] ) c._send_request.assert_awaited_once_with( 'POST', 'http://foo', body=p.encode(), headers={'Content-Type': 'text/plain'}, timeout=5, ) assert c.state == 'connected' assert c.write_loop_task is None async def test_write_loop_websocket_one_packet(self): c = async_client.AsyncClient() c.state = 'connected' c.ping_interval = 1 c.ping_timeout = 2 c.current_transport = 'websocket' self.mock_queue(c) c.queue.get = mock.AsyncMock( side_effect=[ packet.Packet(packet.MESSAGE, {'foo': 'bar'}), c.queue_empty, ] ) c.queue.get_nowait = mock.MagicMock(side_effect=[c.queue_empty]) c.ws = mock.MagicMock() c.ws.send_str = mock.AsyncMock() await c._write_loop() assert c.queue.task_done.call_count == 1 assert c.ws.send_str.await_count == 1 c.ws.send_str.assert_awaited_once_with('4{"foo":"bar"}') async def test_write_loop_websocket_three_packets(self): c = async_client.AsyncClient() c.state = 'connected' c.ping_interval = 1 c.ping_timeout = 2 c.current_transport = 'websocket' self.mock_queue(c) c.queue.get = mock.AsyncMock( side_effect=[ packet.Packet(packet.MESSAGE, {'foo': 'bar'}), c.queue_empty, ] ) c.queue.get_nowait = mock.MagicMock( side_effect=[ packet.Packet(packet.PING), packet.Packet(packet.NOOP), c.queue_empty, ] ) c.ws = mock.MagicMock() c.ws.send_str = mock.AsyncMock() await c._write_loop() assert c.queue.task_done.call_count == 3 assert c.ws.send_str.await_count == 3 assert c.ws.send_str.await_args_list[0][0][0] == '4{"foo":"bar"}' assert c.ws.send_str.await_args_list[1][0][0] == '2' assert c.ws.send_str.await_args_list[2][0][0] == '6' async def test_write_loop_websocket_one_packet_binary(self): c = async_client.AsyncClient() c.state = 'connected' c.ping_interval = 1 c.ping_timeout = 2 c.current_transport = 'websocket' self.mock_queue(c) c.queue.get = mock.AsyncMock( side_effect=[packet.Packet(packet.MESSAGE, b'foo'), c.queue_empty] ) c.queue.get_nowait = mock.MagicMock(side_effect=[c.queue_empty]) c.ws = mock.MagicMock() c.ws.send_bytes = mock.AsyncMock() await c._write_loop() assert c.queue.task_done.call_count == 1 assert c.ws.send_bytes.await_count == 1 c.ws.send_bytes.assert_awaited_once_with(b'foo') async def test_write_loop_websocket_bad_connection(self): c = async_client.AsyncClient() c.state = 'connected' c.ping_interval = 1 c.ping_timeout = 2 c.current_transport = 'websocket' self.mock_queue(c) c.queue.get = mock.AsyncMock( side_effect=[ packet.Packet(packet.MESSAGE, {'foo': 'bar'}), c.queue_empty, ] ) c.queue.get_nowait = mock.MagicMock(side_effect=[c.queue_empty]) c.ws = mock.MagicMock() c.ws.send_str = mock.AsyncMock( side_effect=aiohttp.client_exceptions.ServerDisconnectedError() ) await c._write_loop() assert c.state == 'connected' @mock.patch('engineio.base_client.original_signal_handler') async def test_signal_handler(self, original_handler): clients = [mock.MagicMock(), mock.MagicMock()] base_client.connected_clients = clients[:] base_client.connected_clients[0].is_asyncio_based.return_value = False base_client.connected_clients[1].is_asyncio_based.return_value = True base_client.connected_clients[1].disconnect = mock.AsyncMock() async_client.async_signal_handler() with pytest.raises(asyncio.CancelledError): await asyncio.sleep(0) clients[0].disconnect.assert_not_called() clients[1].disconnect.assert_called_once_with() ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1791413117.6856809 python_engineio-4.15.1/tests/async/test_sanic.py0000644000175000017500000000010615261545576021370 0ustar00miguelmiguelfrom engineio.async_drivers import sanic as async_sanic # noqa: F401 ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1793227792.372336 python_engineio-4.15.1/tests/async/test_server.py0000644000175000017500000014264515270476020021603 0ustar00miguelmiguelimport asyncio import gzip import io import logging from unittest import mock import zlib import pytest from engineio import async_server from engineio.async_drivers import aiohttp as async_aiohttp from engineio import exceptions from engineio import json from engineio import packet from engineio import payload class TestAsyncServer: @staticmethod def get_async_mock(environ={'REQUEST_METHOD': 'GET', 'QUERY_STRING': ''}): if environ.get('QUERY_STRING'): if 'EIO=' not in environ['QUERY_STRING']: environ['QUERY_STRING'] = 'EIO=4&' + environ['QUERY_STRING'] else: environ['QUERY_STRING'] = 'EIO=4' a = mock.MagicMock() a._async = { 'asyncio': True, 'create_route': mock.MagicMock(), 'translate_request': mock.MagicMock(), 'make_response': mock.MagicMock(), 'websocket': 'w', } a._async['translate_request'].return_value = environ a._async['make_response'].return_value = 'response' return a def _get_mock_socket(self): mock_socket = mock.MagicMock() mock_socket.connected = False mock_socket.closed = False mock_socket.closing = False mock_socket.upgraded = False mock_socket.send = mock.AsyncMock() mock_socket.handle_get_request = mock.AsyncMock() mock_socket.handle_post_request = mock.AsyncMock() mock_socket.check_ping_timeout = mock.AsyncMock() mock_socket.close = mock.AsyncMock() mock_socket.session = {} return mock_socket @classmethod def setup_class(cls): async_server.AsyncServer._default_monitor_clients = False @classmethod def teardown_class(cls): async_server.AsyncServer._default_monitor_clients = True def setup_method(self): logging.getLogger('engineio').setLevel(logging.NOTSET) def teardown_method(self): # restore JSON encoder, in case a test changed it packet.Packet.json = json async def test_is_asyncio_based(self): s = async_server.AsyncServer() assert s.is_asyncio_based() async def test_async_modes(self): s = async_server.AsyncServer() assert s.async_modes() == ['aiohttp', 'sanic', 'tornado', 'asgi'] async def test_async_mode_aiohttp(self): s = async_server.AsyncServer(async_mode='aiohttp') assert s.async_mode == 'aiohttp' assert s._async['asyncio'] assert s._async['create_route'] == async_aiohttp.create_route assert s._async['translate_request'] == async_aiohttp.translate_request assert s._async['make_response'] == async_aiohttp.make_response assert s._async['websocket'].__name__ == 'WebSocket' @mock.patch('importlib.import_module') async def test_async_mode_auto_aiohttp(self, import_module): import_module.side_effect = [self.get_async_mock()] s = async_server.AsyncServer() assert s.async_mode == 'aiohttp' async def test_async_modes_wsgi(self): with pytest.raises(ValueError): async_server.AsyncServer(async_mode='eventlet') with pytest.raises(ValueError): async_server.AsyncServer(async_mode='gevent') with pytest.raises(ValueError): async_server.AsyncServer(async_mode='gevent_uwsgi') with pytest.raises(ValueError): async_server.AsyncServer(async_mode='threading') @mock.patch('importlib.import_module') async def test_attach(self, import_module): a = self.get_async_mock() import_module.side_effect = [a] s = async_server.AsyncServer() s.attach('app', engineio_path='abc') a._async['create_route'].assert_called_with('app', s, '/abc/') s.attach('app', engineio_path='/def/') a._async['create_route'].assert_called_with('app', s, '/def/') s.attach('app', engineio_path='/ghi') a._async['create_route'].assert_called_with('app', s, '/ghi/') s.attach('app', engineio_path='jkl/') a._async['create_route'].assert_called_with('app', s, '/jkl/') async def test_session(self): s = async_server.AsyncServer() s.sockets['foo'] = self._get_mock_socket() async def _func(): async with s.session('foo') as session: await s.sleep(0) session['username'] = 'bar' assert await s.get_session('foo') == {'username': 'bar'} await _func() async def test_disconnect(self): s = async_server.AsyncServer() s.sockets['foo'] = mock_socket = self._get_mock_socket() await s.disconnect('foo') assert mock_socket.close.await_count == 1 mock_socket.close.assert_awaited_once_with( reason=s.reason.SERVER_DISCONNECT) assert 'foo' not in s.sockets async def test_disconnect_all(self): s = async_server.AsyncServer() s.sockets['foo'] = mock_foo = self._get_mock_socket() s.sockets['bar'] = mock_bar = self._get_mock_socket() await s.disconnect() assert mock_foo.close.await_count == 1 assert mock_bar.close.await_count == 1 mock_foo.close.assert_awaited_once_with( reason=s.reason.SERVER_DISCONNECT) mock_bar.close.assert_awaited_once_with( reason=s.reason.SERVER_DISCONNECT) assert 'foo' not in s.sockets assert 'bar' not in s.sockets @mock.patch('importlib.import_module') async def test_jsonp_not_supported(self, import_module): a = self.get_async_mock( {'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'j=abc'} ) import_module.side_effect = [a] s = async_server.AsyncServer() response = await s.handle_request('request') assert response == 'response' a._async['translate_request'].assert_called_once_with('request') assert a._async['make_response'].call_count == 1 assert a._async['make_response'].call_args[0][0] == '400 BAD REQUEST' @mock.patch('importlib.import_module') async def test_jsonp_index(self, import_module): a = self.get_async_mock( {'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'j=233'} ) import_module.side_effect = [a] s = async_server.AsyncServer() response = await s.handle_request('request') assert response == 'response' a._async['translate_request'].assert_called_once_with('request') assert a._async['make_response'].call_count == 1 assert a._async['make_response'].call_args[0][0] == '200 OK' assert ( a._async['make_response'] .call_args[0][2] .startswith(b'___eio[233]("') ) assert a._async['make_response'].call_args[0][2].endswith(b'");') @mock.patch('importlib.import_module') async def test_connect(self, import_module): a = self.get_async_mock() import_module.side_effect = [a] s = async_server.AsyncServer() await s.handle_request('request') assert len(s.sockets) == 1 assert a._async['make_response'].call_count == 1 assert a._async['make_response'].call_args[0][0] == '200 OK' assert ('Content-Type', 'text/plain; charset=UTF-8') in a._async[ 'make_response' ].call_args[0][1] packets = payload.Payload( encoded_payload=a._async['make_response'].call_args[0][2].decode( 'utf-8')).packets assert len(packets) == 1 assert packets[0].packet_type == packet.OPEN assert 'upgrades' in packets[0].data assert packets[0].data['upgrades'] == ['websocket'] assert 'sid' in packets[0].data assert packets[0].data['pingTimeout'] == 20000 assert packets[0].data['pingInterval'] == 25000 assert packets[0].data['maxPayload'] == 1000000 @mock.patch('importlib.import_module') async def test_connect_async_request_response_handlers( self, import_module): a = self.get_async_mock() a._async['translate_request'] = mock.AsyncMock( return_value=a._async['translate_request'].return_value ) a._async['make_response'] = mock.AsyncMock( return_value=a._async['make_response'].return_value ) import_module.side_effect = [a] s = async_server.AsyncServer() await s.handle_request('request') assert len(s.sockets) == 1 assert a._async['make_response'].await_count == 1 assert a._async['make_response'].await_args[0][0] == '200 OK' assert ('Content-Type', 'text/plain; charset=UTF-8') in a._async[ 'make_response' ].await_args[0][1] packets = payload.Payload( encoded_payload=a._async['make_response'].await_args[0][ 2].decode('utf-8')).packets assert len(packets) == 1 assert packets[0].packet_type == packet.OPEN assert 'upgrades' in packets[0].data assert packets[0].data['upgrades'] == ['websocket'] assert 'sid' in packets[0].data @mock.patch('importlib.import_module') async def test_connect_no_upgrades(self, import_module): a = self.get_async_mock() import_module.side_effect = [a] s = async_server.AsyncServer(allow_upgrades=False) await s.handle_request('request') packets = payload.Payload( encoded_payload=a._async['make_response'].call_args[0][2].decode( 'utf-8')).packets assert packets[0].data['upgrades'] == [] @mock.patch('importlib.import_module') async def test_connect_bad_eio_version(self, import_module): a = self.get_async_mock( {'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'EIO=1'} ) import_module.side_effect = [a] s = async_server.AsyncServer() await s.handle_request('request') assert a._async['make_response'].call_count == 1 assert a._async['make_response'].call_args[0][0] == '400 BAD REQUEST' assert b'unsupported version' in \ a._async['make_response'].call_args[0][2] @mock.patch('importlib.import_module') async def test_connect_custom_ping_times(self, import_module): a = self.get_async_mock() import_module.side_effect = [a] s = async_server.AsyncServer(ping_timeout=123, ping_interval=456, max_http_buffer_size=12345678) await s.handle_request('request') packets = payload.Payload( encoded_payload=a._async['make_response'].call_args[0][2].decode( 'utf-8')).packets assert packets[0].data['pingTimeout'] == 123000 assert packets[0].data['pingInterval'] == 456000 assert packets[0].data['maxPayload'] == 12345678 @mock.patch('importlib.import_module') @mock.patch('engineio.async_server.async_socket.AsyncSocket') async def test_connect_bad_poll(self, AsyncSocket, import_module): a = self.get_async_mock() import_module.side_effect = [a] AsyncSocket.return_value = self._get_mock_socket() AsyncSocket.return_value.poll.side_effect = [exceptions.QueueEmpty] s = async_server.AsyncServer() await s.handle_request('request') assert a._async['make_response'].call_count == 1 assert a._async['make_response'].call_args[0][0] == '400 BAD REQUEST' @mock.patch('importlib.import_module') @mock.patch('engineio.async_server.async_socket.AsyncSocket') async def test_connect_transport_websocket(self, AsyncSocket, import_module): a = self.get_async_mock( { 'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'transport=websocket', 'HTTP_UPGRADE': 'websocket', } ) import_module.side_effect = [a] AsyncSocket.return_value = self._get_mock_socket() s = async_server.AsyncServer() s.generate_id = mock.MagicMock(return_value='123') # force socket to stay open, so that we can check it later AsyncSocket().closed = False await s.handle_request('request') assert ( s.sockets['123'].send.await_args[0][0].packet_type == packet.OPEN ) @mock.patch('importlib.import_module') @mock.patch('engineio.async_server.async_socket.AsyncSocket') async def test_http_upgrade_case_insensitive(self, AsyncSocket, import_module): a = self.get_async_mock( { 'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'transport=websocket', 'HTTP_UPGRADE': 'WebSocket', } ) import_module.side_effect = [a] AsyncSocket.return_value = self._get_mock_socket() s = async_server.AsyncServer() s.generate_id = mock.MagicMock(return_value='123') # force socket to stay open, so that we can check it later AsyncSocket().closed = False await s.handle_request('request') assert ( s.sockets['123'].send.await_args[0][0].packet_type == packet.OPEN ) @mock.patch('importlib.import_module') @mock.patch('engineio.async_server.async_socket.AsyncSocket') async def test_connect_transport_websocket_closed( self, AsyncSocket, import_module): a = self.get_async_mock( { 'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'transport=websocket', 'HTTP_UPGRADE': 'websocket' } ) import_module.side_effect = [a] AsyncSocket.return_value = self._get_mock_socket() s = async_server.AsyncServer() s.generate_id = mock.MagicMock(return_value='123') # this mock handler just closes the socket, as it would happen on a # real websocket exchange async def mock_handle(environ): s.sockets['123'].closed = True AsyncSocket().handle_get_request = mock_handle await s.handle_request('request') assert '123' not in s.sockets # socket should close on its own @mock.patch('importlib.import_module') async def test_connect_transport_invalid(self, import_module): a = self.get_async_mock( {'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'transport=foo'} ) import_module.side_effect = [a] s = async_server.AsyncServer() await s.handle_request('request') assert a._async['make_response'].call_count == 1 assert a._async['make_response'].call_args[0][0] == '400 BAD REQUEST' @mock.patch('importlib.import_module') async def test_connect_transport_websocket_without_upgrade( self, import_module): a = self.get_async_mock( {'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'transport=websocket'} ) import_module.side_effect = [a] s = async_server.AsyncServer() await s.handle_request('request') assert a._async['make_response'].call_count == 1 assert a._async['make_response'].call_args[0][0] == '400 BAD REQUEST' @mock.patch('importlib.import_module') async def test_connect_cors_headers(self, import_module): a = self.get_async_mock() import_module.side_effect = [a] s = async_server.AsyncServer() await s.handle_request('request') assert a._async['make_response'].call_args[0][0] == '200 OK' headers = a._async['make_response'].call_args[0][1] assert ('Access-Control-Allow-Credentials', 'true') in headers @mock.patch('importlib.import_module') async def test_connect_cors_allowed_origin(self, import_module): a = self.get_async_mock( {'REQUEST_METHOD': 'GET', 'QUERY_STRING': '', 'HTTP_ORIGIN': 'b'} ) import_module.side_effect = [a] s = async_server.AsyncServer(cors_allowed_origins=['a', 'b']) await s.handle_request('request') assert a._async['make_response'].call_args[0][0] == '200 OK' headers = a._async['make_response'].call_args[0][1] assert ('Access-Control-Allow-Origin', 'b') in headers @mock.patch('importlib.import_module') async def test_connect_cors_allowed_origin_with_callable( self, import_module): def cors(origin): return origin == 'a' environ = { 'REQUEST_METHOD': 'GET', 'QUERY_STRING': '', 'HTTP_ORIGIN': 'a' } a = self.get_async_mock(environ) import_module.side_effect = [a] s = async_server.AsyncServer(cors_allowed_origins=cors) await s.handle_request('request') assert a._async['make_response'].call_args[0][0] == '200 OK' headers = a._async['make_response'].call_args[0][1] assert ('Access-Control-Allow-Origin', 'a') in headers environ['HTTP_ORIGIN'] = 'b' await s.handle_request('request') assert a._async['make_response'].call_args[0][0] == '400 BAD REQUEST' @mock.patch('importlib.import_module') async def test_connect_cors_not_allowed_origin(self, import_module): a = self.get_async_mock( {'REQUEST_METHOD': 'GET', 'QUERY_STRING': '', 'HTTP_ORIGIN': 'c'} ) import_module.side_effect = [a] s = async_server.AsyncServer(cors_allowed_origins=['a', 'b']) await s.handle_request('request') assert a._async['make_response'].call_args[0][0] == '400 BAD REQUEST' headers = a._async['make_response'].call_args[0][1] assert ('Access-Control-Allow-Origin', 'c') not in headers assert ('Access-Control-Allow-Origin', '*') not in headers @mock.patch('importlib.import_module') async def test_connect_cors_not_allowed_origin_async_response( self, import_module ): a = self.get_async_mock( {'REQUEST_METHOD': 'GET', 'QUERY_STRING': '', 'HTTP_ORIGIN': 'c'} ) a._async['make_response'] = mock.AsyncMock( return_value=a._async['make_response'].return_value ) import_module.side_effect = [a] s = async_server.AsyncServer(cors_allowed_origins=['a', 'b']) await s.handle_request('request') assert ( a._async['make_response'].await_args[0][0] == '400 BAD REQUEST' ) headers = a._async['make_response'].await_args[0][1] assert ('Access-Control-Allow-Origin', 'c') not in headers assert ('Access-Control-Allow-Origin', '*') not in headers @mock.patch('importlib.import_module') async def test_connect_str_cors_all_origins(self, import_module): a = self.get_async_mock( {'REQUEST_METHOD': 'GET', 'QUERY_STRING': '', 'HTTP_ORIGIN': 'foo'} ) import_module.side_effect = [a] s = async_server.AsyncServer(cors_allowed_origins='*') await s.handle_request('request') assert a._async['make_response'].call_args[0][0] == '200 OK' headers = a._async['make_response'].call_args[0][1] assert ('Access-Control-Allow-Origin', 'foo') in headers assert ('Access-Control-Allow-Credentials', 'true') in headers @mock.patch('importlib.import_module') async def test_connect_list_cors_all_origins(self, import_module): a = self.get_async_mock( {'REQUEST_METHOD': 'GET', 'QUERY_STRING': '', 'HTTP_ORIGIN': 'foo'} ) import_module.side_effect = [a] s = async_server.AsyncServer(cors_allowed_origins=['*']) await s.handle_request('request') assert a._async['make_response'].call_args[0][0] == '200 OK' headers = a._async['make_response'].call_args[0][1] assert ('Access-Control-Allow-Origin', 'foo') in headers assert ('Access-Control-Allow-Credentials', 'true') in headers @mock.patch('importlib.import_module') async def test_connect_cors_one_origin(self, import_module): a = self.get_async_mock( {'REQUEST_METHOD': 'GET', 'QUERY_STRING': '', 'HTTP_ORIGIN': 'a'} ) import_module.side_effect = [a] s = async_server.AsyncServer(cors_allowed_origins='a') await s.handle_request('request') assert a._async['make_response'].call_args[0][0] == '200 OK' headers = a._async['make_response'].call_args[0][1] assert ('Access-Control-Allow-Origin', 'a') in headers assert ('Access-Control-Allow-Credentials', 'true') in headers @mock.patch('importlib.import_module') async def test_connect_cors_one_origin_not_allowed(self, import_module): a = self.get_async_mock( {'REQUEST_METHOD': 'GET', 'QUERY_STRING': '', 'HTTP_ORIGIN': 'b'} ) import_module.side_effect = [a] s = async_server.AsyncServer(cors_allowed_origins='a') await s.handle_request('request') assert a._async['make_response'].call_args[0][0] == '400 BAD REQUEST' headers = a._async['make_response'].call_args[0][1] assert ('Access-Control-Allow-Origin', 'b') not in headers assert ('Access-Control-Allow-Origin', '*') not in headers @mock.patch('importlib.import_module') async def test_connect_cors_headers_default_origin(self, import_module): a = self.get_async_mock( { 'REQUEST_METHOD': 'GET', 'QUERY_STRING': '', 'wsgi.url_scheme': 'http', 'HTTP_HOST': 'foo', 'HTTP_ORIGIN': 'http://foo', } ) import_module.side_effect = [a] s = async_server.AsyncServer() await s.handle_request('request') assert a._async['make_response'].call_args[0][0] == '200 OK' headers = a._async['make_response'].call_args[0][1] assert ('Access-Control-Allow-Origin', 'http://foo') in headers @mock.patch('importlib.import_module') async def test_connect_cors_no_credentials(self, import_module): a = self.get_async_mock() import_module.side_effect = [a] s = async_server.AsyncServer(cors_credentials=False) await s.handle_request('request') assert a._async['make_response'].call_args[0][0] == '200 OK' headers = a._async['make_response'].call_args[0][1] assert ('Access-Control-Allow-Credentials', 'true') not in headers @mock.patch('importlib.import_module') async def test_connect_cors_options(self, import_module): a = self.get_async_mock( {'REQUEST_METHOD': 'OPTIONS', 'QUERY_STRING': ''} ) import_module.side_effect = [a] s = async_server.AsyncServer(cors_credentials=False) await s.handle_request('request') assert a._async['make_response'].call_args[0][0] == '200 OK' headers = a._async['make_response'].call_args[0][1] assert ( 'Access-Control-Allow-Methods', 'OPTIONS, GET, POST', ) in headers @mock.patch('importlib.import_module') async def test_connect_cors_disabled(self, import_module): a = self.get_async_mock( { 'REQUEST_METHOD': 'GET', 'QUERY_STRING': '', 'HTTP_ORIGIN': 'http://foo', } ) import_module.side_effect = [a] s = async_server.AsyncServer(cors_allowed_origins=[]) await s.handle_request('request') assert a._async['make_response'].call_args[0][0] == '200 OK' headers = a._async['make_response'].call_args[0][1] for header in headers: assert not header[0].startswith('Access-Control-') @mock.patch('importlib.import_module') async def test_connect_cors_default_no_origin(self, import_module): a = self.get_async_mock({'REQUEST_METHOD': 'GET', 'QUERY_STRING': ''}) import_module.side_effect = [a] s = async_server.AsyncServer(cors_allowed_origins=[]) await s.handle_request('request') assert a._async['make_response'].call_args[0][0] == '200 OK' headers = a._async['make_response'].call_args[0][1] for header in headers: assert header[0] != 'Access-Control-Allow-Origin' @mock.patch('importlib.import_module') async def test_connect_str_cors_all_no_origin(self, import_module): a = self.get_async_mock({'REQUEST_METHOD': 'GET', 'QUERY_STRING': ''}) import_module.side_effect = [a] s = async_server.AsyncServer(cors_allowed_origins='*') await s.handle_request('request') assert a._async['make_response'].call_args[0][0] == '200 OK' headers = a._async['make_response'].call_args[0][1] for header in headers: assert header[0] != 'Access-Control-Allow-Origin' @mock.patch('importlib.import_module') async def test_connect_list_cors_all_no_origin(self, import_module): a = self.get_async_mock({'REQUEST_METHOD': 'GET', 'QUERY_STRING': ''}) import_module.side_effect = [a] s = async_server.AsyncServer(cors_allowed_origins=['*']) await s.handle_request('request') assert a._async['make_response'].call_args[0][0] == '200 OK' headers = a._async['make_response'].call_args[0][1] for header in headers: assert header[0] != 'Access-Control-Allow-Origin' @mock.patch('importlib.import_module') async def test_connect_cors_disabled_no_origin(self, import_module): a = self.get_async_mock({'REQUEST_METHOD': 'GET', 'QUERY_STRING': ''}) import_module.side_effect = [a] s = async_server.AsyncServer(cors_allowed_origins=[]) await s.handle_request('request') assert a._async['make_response'].call_args[0][0] == '200 OK' headers = a._async['make_response'].call_args[0][1] for header in headers: assert header[0] != 'Access-Control-Allow-Origin' @mock.patch('importlib.import_module') async def test_connect_event(self, import_module): a = self.get_async_mock() import_module.side_effect = [a] s = async_server.AsyncServer() s.generate_id = mock.MagicMock(return_value='123') def mock_connect(sid, environ): return True s.on('connect', handler=mock_connect) await s.handle_request('request') assert len(s.sockets) == 1 @mock.patch('importlib.import_module') async def test_connect_event_rejects(self, import_module): a = self.get_async_mock() import_module.side_effect = [a] s = async_server.AsyncServer() s.generate_id = mock.MagicMock(return_value='123') def mock_connect(sid, environ): return False s.on('connect')(mock_connect) await s.handle_request('request') assert len(s.sockets) == 0 assert a._async['make_response'].call_args[0][0] == '401 UNAUTHORIZED' assert a._async['make_response'].call_args[0][2] == b'"Unauthorized"' @mock.patch('importlib.import_module') async def test_connect_event_rejects_with_message(self, import_module): a = self.get_async_mock() import_module.side_effect = [a] s = async_server.AsyncServer() s.generate_id = mock.MagicMock(return_value='123') def mock_connect(sid, environ): return {'not': 'allowed'} s.on('connect')(mock_connect) await s.handle_request('request') assert len(s.sockets) == 0 assert a._async['make_response'].call_args[0][0] == '401 UNAUTHORIZED' assert ( a._async['make_response'].call_args[0][2] == b'{"not": "allowed"}' ) @mock.patch('importlib.import_module') async def test_method_not_found(self, import_module): a = self.get_async_mock({'REQUEST_METHOD': 'PUT', 'QUERY_STRING': ''}) import_module.side_effect = [a] s = async_server.AsyncServer() await s.handle_request('request') assert len(s.sockets) == 0 assert ( a._async['make_response'].call_args[0][0] == '405 METHOD NOT FOUND' ) @mock.patch('importlib.import_module') async def test_get_request_with_bad_sid(self, import_module): a = self.get_async_mock( {'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'sid=foo'} ) import_module.side_effect = [a] s = async_server.AsyncServer() await s.handle_request('request') assert len(s.sockets) == 0 assert a._async['make_response'].call_args[0][0] == '400 BAD REQUEST' @mock.patch('importlib.import_module') async def test_get_request_bad_websocket_transport(self, import_module): a = self.get_async_mock( {'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'EIO=4&transport=websocket&sid=foo'} ) import_module.side_effect = [a] s = async_server.AsyncServer() s.sockets['foo'] = mock_socket = self._get_mock_socket() mock_socket.connected = True mock_socket.upgraded = False await s.handle_request('request') assert a._async['make_response'].call_args[0][0] == '400 BAD REQUEST' @mock.patch('importlib.import_module') async def test_get_request_bad_polling_transport(self, import_module): a = self.get_async_mock( {'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'EIO=4&transport=polling&sid=foo'} ) import_module.side_effect = [a] s = async_server.AsyncServer() s.sockets['foo'] = mock_socket = self._get_mock_socket() mock_socket.connected = True mock_socket.upgraded = True await s.handle_request('request') assert a._async['make_response'].call_args[0][0] == '400 BAD REQUEST' @mock.patch('importlib.import_module') async def test_post_request_with_bad_sid(self, import_module): a = self.get_async_mock( {'REQUEST_METHOD': 'POST', 'QUERY_STRING': 'sid=foo'} ) import_module.side_effect = [a] s = async_server.AsyncServer() await s.handle_request('request') assert len(s.sockets) == 0 assert a._async['make_response'].call_args[0][0] == '400 BAD REQUEST' @mock.patch('importlib.import_module') async def test_post_request_after_upgrade(self, import_module): a = self.get_async_mock( {'REQUEST_METHOD': 'POST', 'QUERY_STRING': 'sid=foo'} ) import_module.side_effect = [a] s = async_server.AsyncServer() s.sockets['foo'] = self._get_mock_socket() s.sockets['foo'].connected = True s.sockets['foo'].upgraded = True await s.handle_request('request') assert a._async['make_response'].call_args[0][0] == '400 BAD REQUEST' @mock.patch('importlib.import_module') async def test_send(self, import_module): a = self.get_async_mock() import_module.side_effect = [a] s = async_server.AsyncServer() s.sockets['foo'] = mock_socket = self._get_mock_socket() await s.send('foo', 'hello') assert mock_socket.send.await_count == 1 assert ( mock_socket.send.await_args[0][0].packet_type == packet.MESSAGE ) assert mock_socket.send.await_args[0][0].data == 'hello' @mock.patch('importlib.import_module') async def test_send_unknown_socket(self, import_module): a = self.get_async_mock() import_module.side_effect = [a] s = async_server.AsyncServer() # just ensure no exceptions are raised await s.send('foo', 'hello') @mock.patch('importlib.import_module') async def test_get_request(self, import_module): a = self.get_async_mock( {'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'sid=foo'} ) import_module.side_effect = [a] s = async_server.AsyncServer() s.sockets['foo'] = mock_socket = self._get_mock_socket() mock_socket.connected = True mock_socket.handle_get_request.return_value = [ packet.Packet(packet.MESSAGE, data='hello') ] await s.handle_request('request') assert a._async['make_response'].call_args[0][0] == '200 OK' packets = payload.Payload( encoded_payload=a._async['make_response'].call_args[0][2].decode( 'utf-8') ).packets assert len(packets) == 1 assert packets[0].packet_type == packet.MESSAGE @mock.patch('importlib.import_module') async def test_get_request_custom_response(self, import_module): a = self.get_async_mock( {'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'sid=foo'} ) import_module.side_effect = [a] s = async_server.AsyncServer() s.sockets['foo'] = mock_socket = self._get_mock_socket() mock_socket.connected = True mock_socket.handle_get_request.return_value = 'resp' r = await s.handle_request('request') assert r == 'resp' @mock.patch('importlib.import_module') async def test_get_request_closes_socket(self, import_module): a = self.get_async_mock( {'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'sid=foo'} ) import_module.side_effect = [a] s = async_server.AsyncServer() s.sockets['foo'] = mock_socket = self._get_mock_socket() mock_socket.connected = True async def mock_get_request(*args, **kwargs): mock_socket.closed = True return 'resp' mock_socket.handle_get_request = mock_get_request r = await s.handle_request('request') assert r == 'resp' assert 'foo' not in s.sockets @mock.patch('importlib.import_module') async def test_get_request_error(self, import_module): a = self.get_async_mock( {'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'sid=foo'} ) import_module.side_effect = [a] s = async_server.AsyncServer() s.sockets['foo'] = mock_socket = self._get_mock_socket() mock_socket.connected = True async def mock_get_request(*args, **kwargs): raise exceptions.QueueEmpty() mock_socket.handle_get_request = mock_get_request await s.handle_request('request') assert a._async['make_response'].call_args[0][0] == '400 BAD REQUEST' assert len(s.sockets) == 0 @mock.patch('importlib.import_module') async def test_post_request(self, import_module): a = self.get_async_mock( {'REQUEST_METHOD': 'POST', 'QUERY_STRING': 'sid=foo'} ) import_module.side_effect = [a] s = async_server.AsyncServer() s.sockets['foo'] = mock_socket = self._get_mock_socket() mock_socket.connected = True await s.handle_request('request') assert a._async['make_response'].call_args[0][0] == '200 OK' @mock.patch('importlib.import_module') async def test_post_request_before_connecting(self, import_module): a = self.get_async_mock( {'REQUEST_METHOD': 'POST', 'QUERY_STRING': 'sid=foo'} ) import_module.side_effect = [a] s = async_server.AsyncServer() s.sockets['foo'] = self._get_mock_socket() await s.handle_request('request') assert a._async['make_response'].call_args[0][0] == '400 BAD REQUEST' @mock.patch('importlib.import_module') async def test_post_request_error(self, import_module): a = self.get_async_mock( {'REQUEST_METHOD': 'POST', 'QUERY_STRING': 'sid=foo'} ) import_module.side_effect = [a] s = async_server.AsyncServer() s.sockets['foo'] = mock_socket = self._get_mock_socket() mock_socket.connected = True async def mock_post_request(*args, **kwargs): raise exceptions.ContentTooLongError() mock_socket.handle_post_request = mock_post_request await s.handle_request('request') assert a._async['make_response'].call_args[0][0] == '400 BAD REQUEST' @staticmethod def _gzip_decompress(b): bytesio = io.BytesIO(b) with gzip.GzipFile(fileobj=bytesio, mode='r') as gz: return gz.read() @mock.patch('importlib.import_module') async def test_gzip_compression(self, import_module): a = self.get_async_mock( { 'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'sid=foo', 'HTTP_ACCEPT_ENCODING': 'gzip,deflate', } ) import_module.side_effect = [a] s = async_server.AsyncServer(compression_threshold=0) s.sockets['foo'] = mock_socket = self._get_mock_socket() mock_socket.handle_get_request.return_value = [ packet.Packet(packet.MESSAGE, data='hello') ] await s.handle_request('request') headers = a._async['make_response'].call_args[0][1] assert ('Content-Encoding', 'gzip') in headers self._gzip_decompress(a._async['make_response'].call_args[0][2]) @mock.patch('importlib.import_module') async def test_deflate_compression(self, import_module): a = self.get_async_mock( { 'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'sid=foo', 'HTTP_ACCEPT_ENCODING': 'deflate;q=1,gzip', } ) import_module.side_effect = [a] s = async_server.AsyncServer(compression_threshold=0) s.sockets['foo'] = mock_socket = self._get_mock_socket() mock_socket.handle_get_request.return_value = [ packet.Packet(packet.MESSAGE, data='hello') ] await s.handle_request('request') headers = a._async['make_response'].call_args[0][1] assert ('Content-Encoding', 'deflate') in headers zlib.decompress(a._async['make_response'].call_args[0][2]) @mock.patch('importlib.import_module') async def test_gzip_compression_threshold(self, import_module): a = self.get_async_mock( { 'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'sid=foo', 'HTTP_ACCEPT_ENCODING': 'gzip', } ) import_module.side_effect = [a] s = async_server.AsyncServer(compression_threshold=1000) s.sockets['foo'] = mock_socket = self._get_mock_socket() mock_socket.handle_get_request.return_value = [ packet.Packet(packet.MESSAGE, data='hello') ] await s.handle_request('request') headers = a._async['make_response'].call_args[0][1] for header, value in headers: assert header != 'Content-Encoding' with pytest.raises(IOError): print(a._async['make_response'].call_args[0][2]) self._gzip_decompress(a._async['make_response'].call_args[0][2]) @mock.patch('importlib.import_module') async def test_compression_disabled(self, import_module): a = self.get_async_mock( { 'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'sid=foo', 'HTTP_ACCEPT_ENCODING': 'gzip', } ) import_module.side_effect = [a] s = async_server.AsyncServer( http_compression=False, compression_threshold=0 ) s.sockets['foo'] = mock_socket = self._get_mock_socket() mock_socket.handle_get_request.return_value = [ packet.Packet(packet.MESSAGE, data='hello') ] await s.handle_request('request') headers = a._async['make_response'].call_args[0][1] for header, value in headers: assert header != 'Content-Encoding' with pytest.raises(IOError): self._gzip_decompress(a._async['make_response'].call_args[0][2]) @mock.patch('importlib.import_module') async def test_compression_unknown(self, import_module): a = self.get_async_mock( { 'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'sid=foo', 'HTTP_ACCEPT_ENCODING': 'rar', } ) import_module.side_effect = [a] s = async_server.AsyncServer(compression_threshold=0) s.sockets['foo'] = mock_socket = self._get_mock_socket() mock_socket.handle_get_request.return_value = [ packet.Packet(packet.MESSAGE, data='hello') ] await s.handle_request('request') headers = a._async['make_response'].call_args[0][1] for header, value in headers: assert header != 'Content-Encoding' with pytest.raises(IOError): self._gzip_decompress(a._async['make_response'].call_args[0][2]) @mock.patch('importlib.import_module') async def test_compression_no_encoding(self, import_module): a = self.get_async_mock( { 'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'sid=foo', 'HTTP_ACCEPT_ENCODING': '', } ) import_module.side_effect = [a] s = async_server.AsyncServer(compression_threshold=0) s.sockets['foo'] = mock_socket = self._get_mock_socket() mock_socket.handle_get_request.return_value = [ packet.Packet(packet.MESSAGE, data='hello') ] await s.handle_request('request') headers = a._async['make_response'].call_args[0][1] for header, value in headers: assert header != 'Content-Encoding' with pytest.raises(IOError): self._gzip_decompress(a._async['make_response'].call_args[0][2]) @mock.patch('importlib.import_module') async def test_cookie(self, import_module): a = self.get_async_mock() import_module.side_effect = [a] s = async_server.AsyncServer(cookie='sid') s.generate_id = mock.MagicMock(return_value='123') await s.handle_request('request') headers = a._async['make_response'].call_args[0][1] assert ('Set-Cookie', 'sid=123; path=/; SameSite=Lax') in headers @mock.patch('importlib.import_module') async def test_cookie_dict(self, import_module): def get_path(): return '/a' a = self.get_async_mock() import_module.side_effect = [a] s = async_server.AsyncServer(cookie={ 'name': 'test', 'path': get_path, 'SameSite': 'None', 'Secure': True, 'HttpOnly': True }) s.generate_id = mock.MagicMock(return_value='123') await s.handle_request('request') headers = a._async['make_response'].call_args[0][1] assert ('Set-Cookie', 'test=123; path=/a; SameSite=None; Secure; ' 'HttpOnly') in headers @mock.patch('importlib.import_module') async def test_no_cookie(self, import_module): a = self.get_async_mock() import_module.side_effect = [a] s = async_server.AsyncServer(cookie=None) s.generate_id = mock.MagicMock(return_value='123') await s.handle_request('request') headers = a._async['make_response'].call_args[0][1] for header, value in headers: assert header != 'Set-Cookie' async def test_logger(self): s = async_server.AsyncServer(logger=False) assert s.logger.getEffectiveLevel() == logging.ERROR s.logger.setLevel(logging.NOTSET) s = async_server.AsyncServer(logger=True) assert s.logger.getEffectiveLevel() == logging.INFO s.logger.setLevel(logging.WARNING) s = async_server.AsyncServer(logger=True) assert s.logger.getEffectiveLevel() == logging.WARNING s.logger.setLevel(logging.NOTSET) my_logger = logging.Logger('foo') s = async_server.AsyncServer(logger=my_logger) assert s.logger == my_logger async def test_custom_json(self): # Warning: this test cannot run in parallel with other tests, as it # changes the JSON encoding/decoding functions class CustomJSON: @staticmethod def dumps(*args, **kwargs): return '*** encoded ***' @staticmethod def loads(*args, **kwargs): return '+++ decoded +++' async_server.AsyncServer(json=CustomJSON) pkt = packet.Packet(packet.MESSAGE, data={'foo': 'bar'}) assert pkt.encode() == '4*** encoded ***' pkt2 = packet.Packet(encoded_packet=pkt.encode()) assert pkt2.data == '+++ decoded +++' # restore the default JSON module packet.Packet.json = json async def test_background_tasks(self): r = [] async def foo(arg): r.append(arg) async def main(): s = async_server.AsyncServer() task = s.start_background_task(foo, 'bar') await task await main() assert r == ['bar'] async def test_sleep(self): s = async_server.AsyncServer() await s.sleep(0) async def test_trigger_event_function(self): result = [] def foo_handler(arg): result.append('ok') result.append(arg) s = async_server.AsyncServer() s.on('message', handler=foo_handler) await s._trigger_event('message', 'bar') assert result == ['ok', 'bar'] async def test_trigger_event_coroutine(self): result = [] async def foo_handler(arg): result.append('ok') result.append(arg) s = async_server.AsyncServer() s.on('message', handler=foo_handler) await s._trigger_event('message', 'bar') assert result == ['ok', 'bar'] async def test_trigger_event_function_error(self): def connect_handler(arg): return 1 / 0 def foo_handler(arg): return 1 / 0 s = async_server.AsyncServer() s.on('connect', handler=connect_handler) s.on('message', handler=foo_handler) assert not await s._trigger_event('connect', '123') assert await s._trigger_event('message', 'bar') is None async def test_trigger_event_coroutine_error(self): async def connect_handler(arg): return 1 / 0 async def foo_handler(arg): return 1 / 0 s = async_server.AsyncServer() s.on('connect', handler=connect_handler) s.on('message', handler=foo_handler) assert not await s._trigger_event('connect', '123') assert await s._trigger_event('message', 'bar') is None async def test_trigger_event_function_async(self): result = [] def foo_handler(arg): result.append('ok') result.append(arg) s = async_server.AsyncServer() s.on('message', handler=foo_handler) fut = await s._trigger_event('message', 'bar', run_async=True) await fut assert result == ['ok', 'bar'] async def test_trigger_event_coroutine_async(self): result = [] async def foo_handler(arg): result.append('ok') result.append(arg) s = async_server.AsyncServer() s.on('message', handler=foo_handler) fut = await s._trigger_event('message', 'bar', run_async=True) await fut assert result == ['ok', 'bar'] async def test_trigger_event_function_async_error(self): result = [] def foo_handler(arg): result.append(arg) return 1 / 0 s = async_server.AsyncServer() s.on('message', handler=foo_handler) fut = await s._trigger_event('message', 'bar', run_async=True) await fut assert result == ['bar'] async def test_trigger_event_coroutine_async_error(self): result = [] async def foo_handler(arg): result.append(arg) return 1 / 0 s = async_server.AsyncServer() s.on('message', handler=foo_handler) fut = await s._trigger_event('message', 'bar', run_async=True) await fut assert result == ['bar'] async def test_trigger_legacy_disconnect_event(self): s = async_server.AsyncServer() @s.on('disconnect') def baz(sid): return sid r = await s._trigger_event('disconnect', 'foo', 'bar') assert r == 'foo' async def test_trigger_legacy_disconnect_event_async(self): s = async_server.AsyncServer() @s.on('disconnect') async def baz(sid): return sid r = await s._trigger_event('disconnect', 'foo', 'bar') assert r == 'foo' async def test_create_queue(self): s = async_server.AsyncServer() q = s.create_queue() empty = s.get_queue_empty_exception() with pytest.raises(empty): q.get_nowait() async def test_create_event(self): s = async_server.AsyncServer() e = s.create_event() assert not e.is_set() e.set() assert e.is_set() @mock.patch('importlib.import_module') async def test_service_task_started(self, import_module): a = self.get_async_mock() import_module.side_effect = [a] s = async_server.AsyncServer(monitor_clients=True) s._service_task = mock.AsyncMock() await s.handle_request('request') await asyncio.sleep(0) s._service_task.assert_awaited_once_with() @mock.patch('importlib.import_module') async def test_shutdown(self, import_module): a = self.get_async_mock() import_module.side_effect = [a] s = async_server.AsyncServer(monitor_clients=True) await s.handle_request('request') await asyncio.sleep(0) assert s.service_task_handle is not None await s.shutdown() assert s.service_task_handle is None @mock.patch('importlib.import_module') async def test_transports_disallowed(self, import_module): a = self.get_async_mock( {'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'transport=polling'} ) import_module.side_effect = [a] s = async_server.AsyncServer(transports='websocket') response = await s.handle_request('request') assert response == 'response' a._async['translate_request'].assert_called_once_with('request') assert a._async['make_response'].call_count == 1 assert a._async['make_response'].call_args[0][0] == '400 BAD REQUEST' ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1791413117.6859045 python_engineio-4.15.1/tests/async/test_socket.py0000644000175000017500000005241115261545576021571 0ustar00miguelmiguelimport asyncio import time from unittest import mock import pytest from engineio import async_socket from engineio import exceptions from engineio import packet from engineio import payload class TestSocket: def _get_read_mock_coro(self, payload): mock_input = mock.MagicMock() mock_input.read = mock.AsyncMock() mock_input.read.return_value = payload return mock_input def _get_mock_server(self): mock_server = mock.Mock() mock_server.ping_timeout = 0.2 mock_server.ping_interval = 0.2 mock_server.ping_interval_grace_period = 0.001 mock_server.async_handlers = False mock_server.max_http_buffer_size = 128 mock_server._async = { 'asyncio': True, 'create_route': mock.MagicMock(), 'translate_request': mock.MagicMock(), 'make_response': mock.MagicMock(), 'websocket': 'w', } mock_server._async['translate_request'].return_value = 'request' mock_server._async['make_response'].return_value = 'response' mock_server._trigger_event = mock.AsyncMock() def bg_task(target, *args, **kwargs): return asyncio.ensure_future(target(*args, **kwargs)) def create_queue(*args, **kwargs): queue = asyncio.Queue(*args, **kwargs) queue.Empty = asyncio.QueueEmpty return queue mock_server.start_background_task = bg_task mock_server.create_queue = create_queue return mock_server async def test_create(self): mock_server = self._get_mock_server() s = async_socket.AsyncSocket(mock_server, 'sid') assert s.server == mock_server assert s.sid == 'sid' assert not s.upgraded assert not s.closed assert hasattr(s.queue, 'get') assert hasattr(s.queue, 'put') assert hasattr(s.queue, 'task_done') assert hasattr(s.queue, 'join') async def test_empty_poll(self): mock_server = self._get_mock_server() s = async_socket.AsyncSocket(mock_server, 'sid') with pytest.raises(exceptions.QueueEmpty): await s.poll() async def test_poll(self): mock_server = self._get_mock_server() s = async_socket.AsyncSocket(mock_server, 'sid') pkt1 = packet.Packet(packet.MESSAGE, data='hello') pkt2 = packet.Packet(packet.MESSAGE, data='bye') await s.send(pkt1) await s.send(pkt2) assert await s.poll() == [pkt1, pkt2] async def test_poll_none(self): mock_server = self._get_mock_server() s = async_socket.AsyncSocket(mock_server, 'sid') await s.queue.put(None) assert await s.poll() == [] async def test_poll_none_after_packet(self): mock_server = self._get_mock_server() s = async_socket.AsyncSocket(mock_server, 'sid') pkt = packet.Packet(packet.MESSAGE, data='hello') await s.send(pkt) await s.queue.put(None) assert await s.poll() == [pkt] assert await s.poll() == [] async def test_schedule_ping(self): mock_server = self._get_mock_server() mock_server.ping_interval = 0.01 s = async_socket.AsyncSocket(mock_server, 'sid') s.send = mock.AsyncMock() async def schedule_ping_and_sleep(): s.schedule_ping() await asyncio.sleep(0.05) await schedule_ping_and_sleep() assert s.last_ping is not None assert s.send.await_args_list[0][0][0].encode() == '2' async def test_schedule_ping_twice(self): mock_server = self._get_mock_server() mock_server.ping_interval = 0.1 s = async_socket.AsyncSocket(mock_server, 'sid') s.send = mock.AsyncMock() async def schedule_ping_and_sleep(): s.schedule_ping() await asyncio.sleep(0.2) s.schedule_ping() assert s.last_ping is None await schedule_ping_and_sleep() assert s.last_ping is not None assert s.send.await_count == 1 async def test_schedule_ping_closed_socket(self): mock_server = self._get_mock_server() mock_server.ping_interval = 0.01 s = async_socket.AsyncSocket(mock_server, 'sid') s.send = mock.AsyncMock() s.closed = True async def schedule_ping(): s.schedule_ping() await asyncio.sleep(0.05) await schedule_ping() assert s.last_ping is None s.send.assert_not_awaited() async def test_pong(self): mock_server = self._get_mock_server() s = async_socket.AsyncSocket(mock_server, 'sid') s.schedule_ping = mock.MagicMock() await s.receive(packet.Packet(packet.PONG, data='abc')) s.schedule_ping.assert_called_once_with() async def test_message_sync_handler(self): mock_server = self._get_mock_server() s = async_socket.AsyncSocket(mock_server, 'sid') await s.receive(packet.Packet(packet.MESSAGE, data='foo')) mock_server._trigger_event.assert_awaited_once_with( 'message', 'sid', 'foo', run_async=False ) async def test_message_async_handler(self): mock_server = self._get_mock_server() s = async_socket.AsyncSocket(mock_server, 'sid') mock_server.async_handlers = True await s.receive(packet.Packet(packet.MESSAGE, data='foo')) mock_server._trigger_event.assert_awaited_once_with( 'message', 'sid', 'foo', run_async=True ) async def test_invalid_packet(self): mock_server = self._get_mock_server() s = async_socket.AsyncSocket(mock_server, 'sid') with pytest.raises(exceptions.UnknownPacketError): await s.receive(packet.Packet(packet.OPEN)) async def test_timeout(self): mock_server = self._get_mock_server() mock_server.ping_interval = 6 mock_server.ping_interval_grace_period = 2 s = async_socket.AsyncSocket(mock_server, 'sid') s.last_ping = time.time() - 9 s.close = mock.AsyncMock() await s.send('packet') s.close.assert_awaited_once_with( wait=False, abort=False, reason=mock_server.reason.PING_TIMEOUT) async def test_polling_read(self): mock_server = self._get_mock_server() s = async_socket.AsyncSocket(mock_server, 'foo') pkt1 = packet.Packet(packet.MESSAGE, data='hello') pkt2 = packet.Packet(packet.MESSAGE, data='bye') await s.send(pkt1) await s.send(pkt2) environ = {'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'sid=foo'} packets = await s.handle_get_request(environ) assert packets == [pkt1, pkt2] async def test_polling_read_error(self): mock_server = self._get_mock_server() s = async_socket.AsyncSocket(mock_server, 'foo') environ = {'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'sid=foo'} with pytest.raises(exceptions.QueueEmpty): await s.handle_get_request(environ) async def test_polling_write(self): mock_server = self._get_mock_server() mock_server.max_http_buffer_size = 1000 pkt1 = packet.Packet(packet.MESSAGE, data='hello') pkt2 = packet.Packet(packet.MESSAGE, data='bye') p = payload.Payload(packets=[pkt1, pkt2]).encode().encode('utf-8') s = async_socket.AsyncSocket(mock_server, 'foo') s.receive = mock.AsyncMock() environ = { 'REQUEST_METHOD': 'POST', 'QUERY_STRING': 'sid=foo', 'CONTENT_LENGTH': len(p), 'wsgi.input': self._get_read_mock_coro(p), } await s.handle_post_request(environ) assert s.receive.await_count == 2 assert s.receive.call_args_list[0][0][0].data == 'hello' assert s.receive.call_args_list[1][0][0].data == 'bye' async def test_polling_write_too_large(self): mock_server = self._get_mock_server() pkt1 = packet.Packet(packet.MESSAGE, data='hello') pkt2 = packet.Packet(packet.MESSAGE, data='bye') p = payload.Payload(packets=[pkt1, pkt2]).encode().encode('utf-8') mock_server.max_http_buffer_size = len(p) - 1 s = async_socket.AsyncSocket(mock_server, 'foo') s.receive = mock.AsyncMock() environ = { 'REQUEST_METHOD': 'POST', 'QUERY_STRING': 'sid=foo', 'CONTENT_LENGTH': len(p), 'wsgi.input': self._get_read_mock_coro(p), } with pytest.raises(exceptions.ContentTooLongError): await s.handle_post_request(environ) async def test_polling_write_without_content_length(self): mock_server = self._get_mock_server() pkt1 = packet.Packet(packet.MESSAGE, data='hello') pkt2 = packet.Packet(packet.MESSAGE, data='bye') p = payload.Payload(packets=[pkt1, pkt2]).encode().encode('utf-8') s = async_socket.AsyncSocket(mock_server, 'foo') s.receive = mock.AsyncMock() environ = { 'REQUEST_METHOD': 'POST', 'QUERY_STRING': 'sid=foo', 'wsgi.input': self._get_read_mock_coro(p), } await s.handle_post_request(environ) assert s.receive.await_count == 2 assert environ['wsgi.input'].read.call_count == 1 assert environ['wsgi.input'].read.call_args[0] == (0,) async def test_upgrade_handshake(self): mock_server = self._get_mock_server() s = async_socket.AsyncSocket(mock_server, 'foo') s._upgrade_websocket = mock.AsyncMock() environ = { 'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'sid=foo', 'HTTP_CONNECTION': 'Foo,Upgrade,Bar', 'HTTP_UPGRADE': 'websocket', } await s.handle_get_request(environ) s._upgrade_websocket.assert_awaited_once_with(environ) async def test_upgrade(self): mock_server = self._get_mock_server() mock_server._async['websocket'] = mock.MagicMock() mock_ws = mock.AsyncMock() mock_server._async['websocket'].return_value = mock_ws s = async_socket.AsyncSocket(mock_server, 'sid') s.connected = True environ = "foo" await s._upgrade_websocket(environ) mock_server._async['websocket'].assert_called_once_with( s._websocket_handler, mock_server ) mock_ws.assert_awaited_once_with(environ) async def test_upgrade_twice(self): mock_server = self._get_mock_server() mock_server._async['websocket'] = mock.MagicMock() s = async_socket.AsyncSocket(mock_server, 'sid') s.connected = True s.upgraded = True environ = "foo" with pytest.raises(IOError): await s._upgrade_websocket(environ) async def test_upgrade_packet(self): mock_server = self._get_mock_server() s = async_socket.AsyncSocket(mock_server, 'sid') s.connected = True await s.receive(packet.Packet(packet.UPGRADE)) r = await s.poll() assert len(r) == 1 assert r[0].encode() == packet.Packet(packet.NOOP).encode() async def test_upgrade_no_probe(self): mock_server = self._get_mock_server() s = async_socket.AsyncSocket(mock_server, 'sid') s.connected = True ws = mock.MagicMock() ws.wait = mock.AsyncMock() ws.wait.return_value = packet.Packet(packet.NOOP).encode() await s._websocket_handler(ws) assert not s.upgraded async def test_upgrade_no_upgrade_packet(self): mock_server = self._get_mock_server() s = async_socket.AsyncSocket(mock_server, 'sid') s.connected = True s.queue.join = mock.AsyncMock(return_value=None) ws = mock.MagicMock() ws.send = mock.AsyncMock() ws.wait = mock.AsyncMock() probe = 'probe' ws.wait.side_effect = [ packet.Packet(packet.PING, data=probe).encode(), packet.Packet(packet.NOOP).encode(), ] await s._websocket_handler(ws) ws.send.assert_awaited_once_with( packet.Packet(packet.PONG, data=probe).encode() ) assert (await s.queue.get()).packet_type == packet.NOOP assert not s.upgraded async def test_upgrade_not_supported(self): mock_server = self._get_mock_server() mock_server._async['websocket'] = None s = async_socket.AsyncSocket(mock_server, 'sid') s.connected = True environ = "foo" await s._upgrade_websocket(environ) mock_server._bad_request.assert_called_once_with() async def test_close_packet(self): mock_server = self._get_mock_server() s = async_socket.AsyncSocket(mock_server, 'sid') s.connected = True s.close = mock.AsyncMock() await s.receive(packet.Packet(packet.CLOSE)) s.close.assert_awaited_once_with( wait=False, abort=True, reason=mock_server.reason.CLIENT_DISCONNECT) async def test_websocket_read_write(self): mock_server = self._get_mock_server() s = async_socket.AsyncSocket(mock_server, 'sid') s.connected = False s.queue.join = mock.AsyncMock(return_value=None) foo = 'foo' bar = 'bar' s.poll = mock.AsyncMock( side_effect=[[packet.Packet(packet.MESSAGE, data=bar)], None] ) ws = mock.MagicMock() ws.send = mock.AsyncMock() ws.wait = mock.AsyncMock() ws.wait.side_effect = [ packet.Packet(packet.MESSAGE, data=foo).encode(), None, ] ws.close = mock.AsyncMock() await s._websocket_handler(ws) assert s.connected assert s.upgraded assert mock_server._trigger_event.await_count == 2 mock_server._trigger_event.assert_has_awaits( [ mock.call('message', 'sid', 'foo', run_async=False), mock.call('disconnect', 'sid', mock_server.reason.TRANSPORT_CLOSE, run_async=False), ] ) ws.send.assert_awaited_with('4bar') ws.close.assert_awaited() async def test_websocket_upgrade_read_write(self): mock_server = self._get_mock_server() s = async_socket.AsyncSocket(mock_server, 'sid') s.connected = True s.queue.join = mock.AsyncMock(return_value=None) foo = 'foo' bar = 'bar' probe = 'probe' s.poll = mock.AsyncMock( side_effect=[ [packet.Packet(packet.MESSAGE, data=bar)], exceptions.QueueEmpty, ] ) ws = mock.MagicMock() ws.send = mock.AsyncMock() ws.wait = mock.AsyncMock() ws.wait.side_effect = [ packet.Packet(packet.PING, data=probe).encode(), packet.Packet(packet.UPGRADE).encode(), packet.Packet(packet.MESSAGE, data=foo).encode(), None, ] ws.close = mock.AsyncMock() await s._websocket_handler(ws) assert s.upgraded assert mock_server._trigger_event.await_count == 2 mock_server._trigger_event.assert_has_awaits( [ mock.call('message', 'sid', 'foo', run_async=False), mock.call('disconnect', 'sid', mock_server.reason.TRANSPORT_CLOSE, run_async=False), ] ) ws.send.assert_awaited_with('4bar') ws.close.assert_awaited() async def test_websocket_upgrade_with_payload(self): mock_server = self._get_mock_server() s = async_socket.AsyncSocket(mock_server, 'sid') s.connected = True s.queue.join = mock.AsyncMock(return_value=None) probe = 'probe' ws = mock.MagicMock() ws.send = mock.AsyncMock() ws.wait = mock.AsyncMock() ws.wait.side_effect = [ packet.Packet(packet.PING, data=probe).encode(), packet.Packet(packet.UPGRADE, data='2').encode(), ] ws.close = mock.AsyncMock() await s._websocket_handler(ws) assert s.upgraded async def test_websocket_upgrade_with_backlog(self): mock_server = self._get_mock_server() s = async_socket.AsyncSocket(mock_server, 'sid') s.connected = True s.queue.join = mock.AsyncMock(return_value=None) probe = 'probe' foo = 'foo' ws = mock.MagicMock() ws.send = mock.AsyncMock() ws.wait = mock.AsyncMock() ws.wait.side_effect = [ packet.Packet(packet.PING, data=probe).encode(), packet.Packet(packet.UPGRADE, data='2').encode(), ] ws.close = mock.AsyncMock() s.upgrading = True await s.send(packet.Packet(packet.MESSAGE, data=foo)) environ = {'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'sid=sid'} packets = await s.handle_get_request(environ) assert len(packets) == 1 assert packets[0].encode() == '6' packets = await s.poll() assert len(packets) == 1 assert packets[0].encode() == '4foo' await s._websocket_handler(ws) assert s.upgraded assert not s.upgrading packets = await s.handle_get_request(environ) assert len(packets) == 1 assert packets[0].encode() == '6' async def test_websocket_read_write_wait_fail(self): mock_server = self._get_mock_server() s = async_socket.AsyncSocket(mock_server, 'sid') s.connected = False s.queue.join = mock.AsyncMock(return_value=None) foo = 'foo' bar = 'bar' s.poll = mock.AsyncMock( side_effect=[ [packet.Packet(packet.MESSAGE, data=bar)], [packet.Packet(packet.MESSAGE, data=bar)], exceptions.QueueEmpty, ] ) ws = mock.MagicMock() ws.send = mock.AsyncMock() ws.wait = mock.AsyncMock() ws.wait.side_effect = [ packet.Packet(packet.MESSAGE, data=foo).encode(), RuntimeError, ] ws.send.side_effect = [None, RuntimeError] ws.close = mock.AsyncMock() await s._websocket_handler(ws) assert s.closed async def test_websocket_upgrade_with_large_packet(self): mock_server = self._get_mock_server() s = async_socket.AsyncSocket(mock_server, 'sid') s.connected = True s.queue.join = mock.AsyncMock(return_value=None) probe = 'probe' ws = mock.MagicMock() ws.send = mock.AsyncMock() ws.wait = mock.AsyncMock() ws.wait.side_effect = [ packet.Packet(packet.PING, data=probe).encode(), packet.Packet(packet.UPGRADE, data='2' * 128).encode(), ] with pytest.raises(ValueError): await s._websocket_handler(ws) assert not s.upgraded async def test_websocket_ignore_invalid_packet(self): mock_server = self._get_mock_server() s = async_socket.AsyncSocket(mock_server, 'sid') s.connected = False s.queue.join = mock.AsyncMock(return_value=None) foo = 'foo' bar = 'bar' s.poll = mock.AsyncMock( side_effect=[ [packet.Packet(packet.MESSAGE, data=bar)], exceptions.QueueEmpty, ] ) ws = mock.MagicMock() ws.send = mock.AsyncMock() ws.wait = mock.AsyncMock() ws.wait.side_effect = [ packet.Packet(packet.OPEN).encode(), packet.Packet(packet.MESSAGE, data=foo).encode(), None, ] ws.close = mock.AsyncMock() await s._websocket_handler(ws) assert s.connected assert mock_server._trigger_event.await_count == 2 mock_server._trigger_event.assert_has_awaits( [ mock.call('message', 'sid', foo, run_async=False), mock.call('disconnect', 'sid', mock_server.reason.TRANSPORT_CLOSE, run_async=False), ] ) ws.send.assert_awaited_with('4bar') ws.close.assert_awaited() async def test_send_after_close(self): mock_server = self._get_mock_server() s = async_socket.AsyncSocket(mock_server, 'sid') await s.close(wait=False) with pytest.raises(exceptions.SocketIsClosedError): await s.send(packet.Packet(packet.NOOP)) async def test_close_after_close(self): mock_server = self._get_mock_server() s = async_socket.AsyncSocket(mock_server, 'sid') await s.close(wait=False) assert s.closed assert mock_server._trigger_event.await_count == 1 mock_server._trigger_event.assert_awaited_once_with( 'disconnect', 'sid', mock_server.reason.SERVER_DISCONNECT, run_async=False ) await s.close() assert mock_server._trigger_event.await_count == 1 async def test_close_and_wait(self): mock_server = self._get_mock_server() s = async_socket.AsyncSocket(mock_server, 'sid') s.queue = mock.MagicMock() s.queue.put = mock.AsyncMock() s.queue.join = mock.AsyncMock() await s.close(wait=True) s.queue.join.assert_awaited_once_with() async def test_close_without_wait(self): mock_server = self._get_mock_server() s = async_socket.AsyncSocket(mock_server, 'sid') s.queue = mock.MagicMock() s.queue.put = mock.AsyncMock() s.queue.join = mock.AsyncMock() await s.close(wait=False) assert s.queue.join.await_count == 0 ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1791413117.6860478 python_engineio-4.15.1/tests/async/test_tornado.py0000644000175000017500000000413415261545576021746 0ustar00miguelmiguelfrom unittest import mock try: import tornado.web except ImportError: pass from engineio.async_drivers import tornado as async_tornado class TestTornado: async def test_get_tornado_handler(self): mock_server = mock.MagicMock() handler = async_tornado.get_tornado_handler(mock_server) assert issubclass(handler, tornado.websocket.WebSocketHandler) async def test_translate_request(self): mock_handler = mock.MagicMock() mock_handler.request.method = 'PUT' mock_handler.request.path = '/foo/bar' mock_handler.request.query = 'baz=1' mock_handler.request.version = '1.1' mock_handler.request.headers = { 'a': 'b', 'c': 'd', 'content-type': 'application/json', 'content-length': 123, } mock_handler.request.body = b'hello world' environ = async_tornado.translate_request(mock_handler) expected_environ = { 'REQUEST_METHOD': 'PUT', 'PATH_INFO': '/foo/bar', 'QUERY_STRING': 'baz=1', 'CONTENT_TYPE': 'application/json', 'CONTENT_LENGTH': 123, 'HTTP_A': 'b', 'HTTP_C': 'd', 'RAW_URI': '/foo/bar?baz=1', 'SERVER_PROTOCOL': 'HTTP/1.1', # 'wsgi.input': b'hello world', 'tornado.handler': mock_handler, } for k, v in expected_environ.items(): assert v == environ[k] payload = await environ['wsgi.input'].read(1) payload += await environ['wsgi.input'].read() assert payload == b'hello world' async def test_make_response(self): mock_handler = mock.MagicMock() mock_environ = {'tornado.handler': mock_handler} async_tornado.make_response( '202 ACCEPTED', [('foo', 'bar')], b'payload', mock_environ ) mock_handler.set_status.assert_called_once_with(202) mock_handler.set_header.assert_called_once_with('foo', 'bar') mock_handler.write.assert_called_once_with(b'payload') mock_handler.finish.assert_called_once_with() ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1793228809.0370674 python_engineio-4.15.1/tests/common/0000755000175000017500000000000015270500011017007 5ustar00miguelmiguel././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1791413117.6860478 python_engineio-4.15.1/tests/common/__init__.py0000644000175000017500000000000015261545576021137 0ustar00miguelmiguel././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1791413117.6861932 python_engineio-4.15.1/tests/common/index.html0000644000175000017500000000001615261545576021032 0ustar00miguelmiguel ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1793128983.3757467 python_engineio-4.15.1/tests/common/test_client.py0000644000175000017500000017612215270175027021726 0ustar00miguelmiguelimport logging import ssl import time from unittest import mock import pytest import websocket from engineio import base_client from engineio import client from engineio import exceptions from engineio import json from engineio import packet from engineio import payload class TestClient: def mock_queue(self, client): client.queue = mock.MagicMock() client.queue_empty = RuntimeError client.queue.get_nowait.side_effect = client.queue_empty def test_is_asyncio_based(self): c = client.Client() assert not c.is_asyncio_based() def test_create(self): c = client.Client() assert c.handlers == {} for attr in [ 'base_url', 'transports', 'sid', 'upgrades', 'ping_interval', 'ping_timeout', 'http', 'ws', 'read_loop_task', 'write_loop_task', ]: assert getattr(c, attr) is None, attr + ' is not None' assert c.state == 'disconnected' assert c.queue.__class__.__name__ == 'Queue' assert c.queue_empty.__name__ == 'Empty' def test_external_session(self): c = client.Client(http_session='foo') assert c.http == 'foo' assert c.external_http def test_callable_session(self): c = client.Client(http_session=lambda: 'foo') assert c.http is None assert c.http_factory() == 'foo' assert c.external_http is False def test_custom_json(self): client.Client() assert packet.Packet.json == json client.Client(json='foo') assert packet.Packet.json == 'foo' packet.Packet.json = json def test_logger(self): c = client.Client(logger=False) assert c.logger.getEffectiveLevel() == logging.ERROR c.logger.setLevel(logging.NOTSET) c = client.Client(logger=True) assert c.logger.getEffectiveLevel() == logging.INFO c.logger.setLevel(logging.WARNING) c = client.Client(logger=True) assert c.logger.getEffectiveLevel() == logging.WARNING c.logger.setLevel(logging.NOTSET) my_logger = logging.Logger('foo') c = client.Client(logger=my_logger) assert c.logger == my_logger def test_custom_timeout(self): c = client.Client() assert c.request_timeout == 5 c = client.Client(request_timeout=27) assert c.request_timeout == 27 def test_timestamp_requests(self): c = client.Client() assert c.timestamp_requests assert c._get_url_timestamp().startswith('&t=') c = client.Client(timestamp_requests=False) assert not c.timestamp_requests assert c._get_url_timestamp() == '' def test_on_event(self): c = client.Client() @c.on('connect') def foo(): pass c.on('disconnect', foo) assert c.handlers['connect'] == foo assert c.handlers['disconnect'] == foo def test_on_event_invalid(self): c = client.Client() with pytest.raises(ValueError): c.on('invalid') def test_already_connected(self): c = client.Client() c.state = 'connected' with pytest.raises(ValueError): c.connect('http://foo') def test_invalid_transports(self): c = client.Client() with pytest.raises(ValueError): c.connect('http://foo', transports=['foo', 'bar']) def test_some_invalid_transports(self): c = client.Client() c._connect_websocket = mock.MagicMock() c.connect('http://foo', transports=['foo', 'websocket', 'bar']) assert c.transports == ['websocket'] def test_connect_polling(self): c = client.Client() c._connect_polling = mock.MagicMock(return_value='foo') assert c.connect('http://foo') == 'foo' c._connect_polling.assert_called_once_with( 'http://foo', {}, 'engine.io' ) c = client.Client() c._connect_polling = mock.MagicMock(return_value='foo') assert c.connect('http://foo', transports=['polling']) == 'foo' c._connect_polling.assert_called_once_with( 'http://foo', {}, 'engine.io' ) c = client.Client() c._connect_polling = mock.MagicMock(return_value='foo') assert ( c.connect('http://foo', transports=['polling', 'websocket']) == 'foo' ) c._connect_polling.assert_called_once_with( 'http://foo', {}, 'engine.io' ) def test_connect_websocket(self): c = client.Client() c._connect_websocket = mock.MagicMock(return_value='foo') assert c.connect('http://foo', transports=['websocket']) == 'foo' c._connect_websocket.assert_called_once_with( 'http://foo', {}, 'engine.io' ) c = client.Client() c._connect_websocket = mock.MagicMock(return_value='foo') assert c.connect('http://foo', transports='websocket') == 'foo' c._connect_websocket.assert_called_once_with( 'http://foo', {}, 'engine.io' ) def test_connect_query_string(self): c = client.Client() c._connect_polling = mock.MagicMock(return_value='foo') assert c.connect('http://foo?bar=baz') == 'foo' c._connect_polling.assert_called_once_with( 'http://foo?bar=baz', {}, 'engine.io' ) def test_connect_custom_headers(self): c = client.Client() c._connect_polling = mock.MagicMock(return_value='foo') assert c.connect('http://foo', headers={'Foo': 'Bar'}) == 'foo' c._connect_polling.assert_called_once_with( 'http://foo', {'Foo': 'Bar'}, 'engine.io' ) @mock.patch('engineio.client.Client._send_request') def test_connect_polling_handler_failure(self, _send_request): _send_request.return_value.status_code = 200 _send_request.return_value.content = payload.Payload( packets=[ packet.Packet( packet.OPEN, { 'sid': '123', 'upgrades': [], 'pingInterval': 1000, 'pingTimeout': 2000, }, ) ] ).encode().encode('utf-8') c = client.Client() @c.on('connect') def connect(): raise ValueError('Failed on purpose') with pytest.raises(exceptions.ConnectionError, match='Connect handler failed: Failed on purpose'): c.connect('http://foo') @mock.patch('engineio.client.websocket.create_connection') def test_connect_websocket_handler_failure(self, create_connection): create_connection.return_value.recv.return_value = packet.Packet( packet.OPEN, { 'sid': '123', 'upgrades': [], 'pingInterval': 1000, 'pingTimeout': 2000, }, ).encode() c = client.Client() @c.on('connect') def connect(): raise ValueError('Failed on purpose') with pytest.raises(exceptions.ConnectionError, match='Connect handler failed: Failed on purpose'): c.connect('http://foo', transports='websocket') def test_wait(self): c = client.Client() c.read_loop_task = mock.MagicMock() c.wait() c.read_loop_task.join.assert_called_once_with() def test_wait_no_task(self): c = client.Client() c.read_loop_task = None c.wait() # should not block def test_send(self): c = client.Client() saved_packets = [] def fake_send_packet(pkt): saved_packets.append(pkt) c._send_packet = fake_send_packet c.send('foo') c.send('foo') c.send(b'foo') assert saved_packets[0].packet_type == packet.MESSAGE assert saved_packets[0].data == 'foo' assert not saved_packets[0].binary assert saved_packets[1].packet_type == packet.MESSAGE assert saved_packets[1].data == 'foo' assert not saved_packets[1].binary assert saved_packets[2].packet_type == packet.MESSAGE assert saved_packets[2].data == b'foo' assert saved_packets[2].binary def test_disconnect_not_connected(self): c = client.Client() c.state = 'foo' c.sid = 'bar' c.disconnect() assert c.state == 'disconnected' assert c.sid is None def test_disconnect_polling(self): c = client.Client() base_client.connected_clients.append(c) c.state = 'connected' c.current_transport = 'polling' self.mock_queue(c) c.read_loop_task = mock.MagicMock() c.ws = mock.MagicMock() c._trigger_event = mock.MagicMock() c.disconnect() c.read_loop_task.join.assert_called_once_with() c.ws.mock.assert_not_called() assert c not in base_client.connected_clients c._trigger_event.assert_called_once_with( 'disconnect', c.reason.CLIENT_DISCONNECT, run_async=False) def test_disconnect_websocket(self): c = client.Client() base_client.connected_clients.append(c) c.state = 'connected' c.current_transport = 'websocket' self.mock_queue(c) c.read_loop_task = mock.MagicMock() c.ws = mock.MagicMock() c._trigger_event = mock.MagicMock() c.disconnect() c.read_loop_task.join.assert_called_once_with() c.ws.close.assert_called_once_with() assert c not in base_client.connected_clients c._trigger_event.assert_called_once_with( 'disconnect', c.reason.CLIENT_DISCONNECT, run_async=False) def test_disconnect_polling_abort(self): c = client.Client() base_client.connected_clients.append(c) c.state = 'connected' c.current_transport = 'polling' self.mock_queue(c) c.read_loop_task = mock.MagicMock() c.ws = mock.MagicMock() c.disconnect(abort=True) c.queue.join.assert_not_called() c.read_loop_task.join.assert_not_called() c.ws.mock.assert_not_called() assert c not in base_client.connected_clients def test_disconnect_websocket_abort(self): c = client.Client() base_client.connected_clients.append(c) c.state = 'connected' c.current_transport = 'websocket' self.mock_queue(c) c.read_loop_task = mock.MagicMock() c.ws = mock.MagicMock() c.disconnect(abort=True) c.queue.join.assert_not_called() c.read_loop_task.join.assert_not_called() c.ws.mock.assert_not_called() assert c not in base_client.connected_clients def test_current_transport(self): c = client.Client() c.current_transport = 'foo' assert c.transport() == 'foo' def test_background_tasks(self): flag = {} def bg_task(): flag['task'] = True c = client.Client() task = c.start_background_task(bg_task) task.join() assert 'task' in flag assert flag['task'] def test_sleep(self): c = client.Client() t = time.time() c.sleep(0.1) assert time.time() - t > 0.1 def test_create_queue(self): c = client.Client() q = c.create_queue() with pytest.raises(c.queue_empty): q.get(timeout=0.01) def test_create_event(self): c = client.Client() e = c.create_event() assert not e.is_set() e.set() assert e.is_set() @mock.patch('engineio.client.requests.Session') def test_polling_connection_default_session(self, _session): _session.return_value.request.return_value.status_code = 200 _session.return_value.request.return_value.content = payload.Payload( packets=[ packet.Packet( packet.MESSAGE, # incorrect on purpose {}, ) ] ).encode().encode('utf-8') c = client.Client() with pytest.raises(exceptions.ConnectionError, match='OPEN packet not returned by server'): c.connect('http://foo', headers={'Foo': 'Bar'}) @mock.patch('engineio.client.time.time', return_value=123.456) @mock.patch('engineio.client.Client._send_request', return_value=None) def test_polling_connection_failed(self, _send_request, _time): c = client.Client() with pytest.raises(exceptions.ConnectionError): c.connect('http://foo', headers={'Foo': 'Bar'}) _send_request.assert_called_once_with( 'GET', 'http://foo/engine.io/?transport=polling&EIO=4&t=123.456', headers={'Foo': 'Bar'}, timeout=5, ) @mock.patch('engineio.client.Client._send_request') def test_polling_connection_404(self, _send_request): _send_request.return_value.status_code = 404 _send_request.return_value.json.return_value = {'foo': 'bar'} c = client.Client() try: c.connect('http://foo') except exceptions.ConnectionError as exc: assert len(exc.args) == 2 assert ( exc.args[0] == 'Unexpected status code 404 in server response' ) assert exc.args[1] == {'foo': 'bar'} @mock.patch('engineio.client.Client._send_request') def test_polling_connection_404_no_json(self, _send_request): _send_request.return_value.status_code = 404 _send_request.return_value.json.side_effect = json.JSONDecodeError( 'error', '', 0) c = client.Client() try: c.connect('http://foo') except exceptions.ConnectionError as exc: assert len(exc.args) == 2 assert ( exc.args[0] == 'Unexpected status code 404 in server response' ) assert exc.args[1] is None @mock.patch('engineio.client.Client._send_request') def test_polling_connection_invalid_packet(self, _send_request): _send_request.return_value.status_code = 200 _send_request.return_value.content = b'foo' c = client.Client() with pytest.raises(exceptions.ConnectionError): c.connect('http://foo') @mock.patch('engineio.client.Client._send_request') def test_polling_connection_no_open_packet(self, _send_request): _send_request.return_value.status_code = 200 _send_request.return_value.content = payload.Payload( packets=[ packet.Packet( packet.CLOSE, { 'sid': '123', 'upgrades': [], 'pingInterval': 10, 'pingTimeout': 20, }, ) ] ).encode().encode('utf-8') c = client.Client() with pytest.raises(exceptions.ConnectionError): c.connect('http://foo') @mock.patch('engineio.client.Client._send_request') def test_polling_connection_successful(self, _send_request): _send_request.return_value.status_code = 200 _send_request.return_value.content = payload.Payload( packets=[ packet.Packet( packet.OPEN, { 'sid': '123', 'upgrades': [], 'pingInterval': 1000, 'pingTimeout': 2000, }, ) ] ).encode().encode('utf-8') c = client.Client() c._read_loop_polling = mock.MagicMock() c._read_loop_websocket = mock.MagicMock() c._write_loop = mock.MagicMock() on_connect = mock.MagicMock() c.on('connect', on_connect) c.connect('http://foo') time.sleep(0.1) c._read_loop_polling.assert_called_once_with() c._read_loop_websocket.assert_not_called() c._write_loop.assert_called_once_with() on_connect.assert_called_once_with() assert c in base_client.connected_clients assert ( c.base_url == 'http://foo/engine.io/?transport=polling&EIO=4&sid=123' ) assert c.sid == '123' assert c.ping_interval == 1 assert c.ping_timeout == 2 assert c.upgrades == [] assert c.transport() == 'polling' @mock.patch('engineio.client.Client._send_request') def test_polling_https_noverify_connection_successful(self, _send_request): _send_request.return_value.status_code = 200 _send_request.return_value.content = payload.Payload( packets=[ packet.Packet( packet.OPEN, { 'sid': '123', 'upgrades': [], 'pingInterval': 1000, 'pingTimeout': 2000, }, ) ] ).encode().encode('utf-8') c = client.Client(ssl_verify=False) c._read_loop_polling = mock.MagicMock() c._read_loop_websocket = mock.MagicMock() c._write_loop = mock.MagicMock() on_connect = mock.MagicMock() c.on('connect', on_connect) c.connect('https://foo') time.sleep(0.1) c._read_loop_polling.assert_called_once_with() c._read_loop_websocket.assert_not_called() c._write_loop.assert_called_once_with() on_connect.assert_called_once_with() assert c in base_client.connected_clients assert ( c.base_url == 'https://foo/engine.io/?transport=polling&EIO=4&sid=123' ) assert c.sid == '123' assert c.ping_interval == 1 assert c.ping_timeout == 2 assert c.upgrades == [] assert c.transport() == 'polling' @mock.patch('engineio.client.Client._send_request') def test_polling_connection_with_more_packets(self, _send_request): _send_request.return_value.status_code = 200 _send_request.return_value.content = payload.Payload( packets=[ packet.Packet( packet.OPEN, { 'sid': '123', 'upgrades': [], 'pingInterval': 1000, 'pingTimeout': 2000, }, ), packet.Packet(packet.NOOP), ] ).encode().encode('utf-8') c = client.Client() c._read_loop_polling = mock.MagicMock() c._read_loop_websocket = mock.MagicMock() c._write_loop = mock.MagicMock() c._receive_packet = mock.MagicMock() on_connect = mock.MagicMock() c.on('connect', on_connect) c.connect('http://foo') time.sleep(0.1) assert c._receive_packet.call_count == 1 assert ( c._receive_packet.call_args_list[0][0][0].packet_type == packet.NOOP ) @mock.patch('engineio.client.Client._send_request') def test_polling_connection_upgraded(self, _send_request): _send_request.return_value.status_code = 200 _send_request.return_value.content = payload.Payload( packets=[ packet.Packet( packet.OPEN, { 'sid': '123', 'upgrades': ['websocket'], 'pingInterval': 1000, 'pingTimeout': 2000, }, ) ] ).encode().encode('utf-8') c = client.Client() c._connect_websocket = mock.MagicMock(return_value=True) on_connect = mock.MagicMock() c.on('connect', on_connect) c.connect('http://foo') c._connect_websocket.assert_called_once_with( 'http://foo', {}, 'engine.io' ) on_connect.assert_called_once_with() assert c in base_client.connected_clients assert ( c.base_url == 'http://foo/engine.io/?transport=polling&EIO=4&sid=123' ) assert c.sid == '123' assert c.ping_interval == 1 assert c.ping_timeout == 2 assert c.upgrades == ['websocket'] @mock.patch('engineio.client.Client._send_request') def test_polling_connection_not_upgraded(self, _send_request): _send_request.return_value.status_code = 200 _send_request.return_value.content = payload.Payload( packets=[ packet.Packet( packet.OPEN, { 'sid': '123', 'upgrades': ['websocket'], 'pingInterval': 1000, 'pingTimeout': 2000, }, ) ] ).encode().encode('utf-8') c = client.Client() c._connect_websocket = mock.MagicMock(return_value=False) c._read_loop_polling = mock.MagicMock() c._read_loop_websocket = mock.MagicMock() c._write_loop = mock.MagicMock() on_connect = mock.MagicMock() c.on('connect', on_connect) c.connect('http://foo') time.sleep(0.1) c._connect_websocket.assert_called_once_with( 'http://foo', {}, 'engine.io' ) c._read_loop_polling.assert_called_once_with() c._read_loop_websocket.assert_not_called() c._write_loop.assert_called_once_with() on_connect.assert_called_once_with() assert c in base_client.connected_clients @mock.patch('engineio.client.time.time', return_value=123.456) @mock.patch( 'engineio.client.websocket.create_connection', side_effect=[ConnectionError], ) def test_websocket_connection_failed(self, create_connection, _time): c = client.Client() with pytest.raises(exceptions.ConnectionError): c.connect( 'http://foo', transports=['websocket'], headers={'Foo': 'Bar'} ) create_connection.assert_called_once_with( 'ws://foo/engine.io/?transport=websocket&EIO=4&t=123.456', header={'Foo': 'Bar'}, cookie=None, enable_multithread=True, timeout=5 ) @mock.patch('engineio.client.time.time', return_value=123.456) @mock.patch( 'engineio.client.websocket.create_connection', side_effect=[ConnectionError], ) def test_websocket_connection_extra(self, create_connection, _time): c = client.Client(websocket_extra_options={'header': {'Baz': 'Qux'}, 'timeout': 10}) with pytest.raises(exceptions.ConnectionError): c.connect( 'http://foo', transports=['websocket'], headers={'Foo': 'Bar'} ) create_connection.assert_called_once_with( 'ws://foo/engine.io/?transport=websocket&EIO=4&t=123.456', header={'Foo': 'Bar', 'Baz': 'Qux'}, cookie=None, enable_multithread=True, timeout=10 ) @mock.patch('engineio.client.time.time', return_value=123.456) @mock.patch( 'engineio.client.websocket.create_connection', side_effect=[websocket.WebSocketException], ) def test_websocket_connection_failed_with_websocket_error( self, create_connection, _time ): c = client.Client() with pytest.raises(exceptions.ConnectionError): c.connect( 'http://foo', transports=['websocket'], headers={'Foo': 'Bar'} ) create_connection.assert_called_once_with( 'ws://foo/engine.io/?transport=websocket&EIO=4&t=123.456', header={'Foo': 'Bar'}, cookie=None, enable_multithread=True, timeout=5 ) @mock.patch('engineio.client.time.time', return_value=123.456) @mock.patch( 'engineio.client.websocket.create_connection', side_effect=[ConnectionError], ) def test_websocket_upgrade_failed(self, create_connection, _time): c = client.Client() c.ping_interval = 1 c.ping_timeout = 2 c.sid = '123' assert not c.connect('http://foo', transports=['websocket']) create_connection.assert_called_once_with( 'ws://foo/engine.io/?transport=websocket&EIO=4&sid=123&t=123.456', header={}, cookie=None, enable_multithread=True, timeout=5 ) @mock.patch('engineio.client.websocket.create_connection') def test_websocket_connection_no_open_packet(self, create_connection): create_connection.return_value.recv.return_value = packet.Packet( packet.CLOSE ).encode() c = client.Client() with pytest.raises(exceptions.ConnectionError): c.connect('http://foo', transports=['websocket']) @mock.patch('engineio.client.websocket.create_connection') def test_websocket_connection_successful(self, create_connection): create_connection.return_value.recv.return_value = packet.Packet( packet.OPEN, { 'sid': '123', 'upgrades': [], 'pingInterval': 1000, 'pingTimeout': 2000, }, ).encode() c = client.Client() c._read_loop_polling = mock.MagicMock() c._read_loop_websocket = mock.MagicMock() c._write_loop = mock.MagicMock() on_connect = mock.MagicMock() c.on('connect', on_connect) c.connect('ws://foo', transports=['websocket']) time.sleep(0.1) c._read_loop_polling.assert_not_called() c._read_loop_websocket.assert_called_once_with() c._write_loop.assert_called_once_with() on_connect.assert_called_once_with() assert c in base_client.connected_clients assert c.base_url == 'ws://foo/engine.io/?transport=websocket&EIO=4' assert c.sid == '123' assert c.ping_interval == 1 assert c.ping_timeout == 2 assert c.upgrades == [] assert c.transport() == 'websocket' assert c.ws == create_connection.return_value assert len(create_connection.call_args_list) == 1 assert create_connection.call_args[1] == { 'header': {}, 'cookie': None, 'enable_multithread': True, 'timeout': 5, } @mock.patch('engineio.client.websocket.create_connection') def test_websocket_https_noverify_connection_successful( self, create_connection ): create_connection.return_value.recv.return_value = packet.Packet( packet.OPEN, { 'sid': '123', 'upgrades': [], 'pingInterval': 1000, 'pingTimeout': 2000, }, ).encode() c = client.Client(ssl_verify=False) c._read_loop_polling = mock.MagicMock() c._read_loop_websocket = mock.MagicMock() c._write_loop = mock.MagicMock() on_connect = mock.MagicMock() c.on('connect', on_connect) c.connect('wss://foo', transports=['websocket']) time.sleep(0.1) c._read_loop_polling.assert_not_called() c._read_loop_websocket.assert_called_once_with() c._write_loop.assert_called_once_with() on_connect.assert_called_once_with() assert c in base_client.connected_clients assert c.base_url == 'wss://foo/engine.io/?transport=websocket&EIO=4' assert c.sid == '123' assert c.ping_interval == 1 assert c.ping_timeout == 2 assert c.upgrades == [] assert c.transport() == 'websocket' assert c.ws == create_connection.return_value assert len(create_connection.call_args_list) == 1 assert create_connection.call_args[1] == { 'header': {}, 'cookie': None, 'enable_multithread': True, 'timeout': 5, 'sslopt': {'cert_reqs': ssl.CERT_NONE}, } @mock.patch('engineio.client.websocket.create_connection') def test_websocket_connection_with_cookies(self, create_connection): create_connection.return_value.recv.return_value = packet.Packet( packet.OPEN, { 'sid': '123', 'upgrades': [], 'pingInterval': 1000, 'pingTimeout': 2000, }, ).encode() http = mock.MagicMock() http.cookies = [mock.MagicMock(), mock.MagicMock()] http.cookies[0].name = 'key' http.cookies[0].value = 'value' http.cookies[1].name = 'key2' http.cookies[1].value = 'value2' http.auth = None http.proxies = None http.cert = None c = client.Client(http_session=lambda: http) c._read_loop_polling = mock.MagicMock() c._read_loop_websocket = mock.MagicMock() c._write_loop = mock.MagicMock() on_connect = mock.MagicMock() c.on('connect', on_connect) c.connect('ws://foo', transports=['websocket']) time.sleep(0.1) assert len(create_connection.call_args_list) == 1 assert create_connection.call_args[1] == { 'header': {}, 'cookie': 'key=value; key2=value2', 'enable_multithread': True, 'timeout': 5, } @mock.patch('engineio.client.websocket.create_connection') def test_websocket_connection_with_cookie_header(self, create_connection): create_connection.return_value.recv.return_value = packet.Packet( packet.OPEN, { 'sid': '123', 'upgrades': [], 'pingInterval': 1000, 'pingTimeout': 2000, }, ).encode() http = mock.MagicMock() http.cookies = [] http.auth = None http.proxies = None http.cert = None c = client.Client(http_session=lambda: http) c._read_loop_polling = mock.MagicMock() c._read_loop_websocket = mock.MagicMock() c._write_loop = mock.MagicMock() on_connect = mock.MagicMock() c.on('connect', on_connect) c.connect( 'ws://foo', headers={'Foo': 'bar', 'Cookie': 'key=value'}, transports=['websocket'], ) time.sleep(0.1) assert len(create_connection.call_args_list) == 1 assert create_connection.call_args[1] == { 'header': {'Foo': 'bar'}, 'cookie': 'key=value', 'enable_multithread': True, 'timeout': 5, } @mock.patch('engineio.client.websocket.create_connection') def test_websocket_connection_with_cookies_and_headers( self, create_connection ): create_connection.return_value.recv.return_value = packet.Packet( packet.OPEN, { 'sid': '123', 'upgrades': [], 'pingInterval': 1000, 'pingTimeout': 2000, }, ).encode() http = mock.MagicMock() http.cookies = [mock.MagicMock(), mock.MagicMock()] http.cookies[0].name = 'key' http.cookies[0].value = 'value' http.cookies[1].name = 'key2' http.cookies[1].value = 'value2' http.auth = None http.proxies = None http.cert = None c = client.Client(http_session=lambda: http) c._read_loop_polling = mock.MagicMock() c._read_loop_websocket = mock.MagicMock() c._write_loop = mock.MagicMock() on_connect = mock.MagicMock() c.on('connect', on_connect) c.connect( 'ws://foo', headers={'Cookie': 'key3=value3'}, transports=['websocket'], ) time.sleep(0.1) assert len(create_connection.call_args_list) == 1 assert create_connection.call_args[1] == { 'header': {}, 'enable_multithread': True, 'timeout': 5, 'cookie': 'key=value; key2=value2; key3=value3', } @mock.patch('engineio.client.websocket.create_connection') def test_websocket_connection_with_auth(self, create_connection): create_connection.return_value.recv.return_value = packet.Packet( packet.OPEN, { 'sid': '123', 'upgrades': [], 'pingInterval': 1000, 'pingTimeout': 2000, }, ).encode() http = mock.MagicMock() http.cookies = [] http.auth = ('foo', 'bar') http.proxies = None http.cert = None c = client.Client(http_session=lambda: http) c._read_loop_polling = mock.MagicMock() c._read_loop_websocket = mock.MagicMock() c._write_loop = mock.MagicMock() on_connect = mock.MagicMock() c.on('connect', on_connect) c.connect('ws://foo', transports=['websocket']) assert len(create_connection.call_args_list) == 1 assert create_connection.call_args[1] == { 'header': {'Authorization': 'Basic Zm9vOmJhcg=='}, 'cookie': '', 'enable_multithread': True, 'timeout': 5, } @mock.patch('engineio.client.websocket.create_connection') def test_websocket_connection_with_cert(self, create_connection): create_connection.return_value.recv.return_value = packet.Packet( packet.OPEN, { 'sid': '123', 'upgrades': [], 'pingInterval': 1000, 'pingTimeout': 2000, }, ).encode() http = mock.MagicMock() http.cookies = [] http.auth = None http.proxies = None http.cert = 'foo.crt' c = client.Client(http_session=lambda: http, ssl_verify=False) c._read_loop_polling = mock.MagicMock() c._read_loop_websocket = mock.MagicMock() c._write_loop = mock.MagicMock() on_connect = mock.MagicMock() c.on('connect', on_connect) c.connect('ws://foo', transports=['websocket']) assert len(create_connection.call_args_list) == 1 assert create_connection.call_args[1] == { 'sslopt': {'cert_reqs': ssl.CERT_NONE, 'certfile': 'foo.crt'}, 'header': {}, 'cookie': '', 'enable_multithread': True, 'timeout': 5, } @mock.patch('engineio.client.websocket.create_connection') def test_websocket_connection_with_cert_and_key(self, create_connection): create_connection.return_value.recv.return_value = packet.Packet( packet.OPEN, { 'sid': '123', 'upgrades': [], 'pingInterval': 1000, 'pingTimeout': 2000, }, ).encode() http = mock.MagicMock() http.cookies = [] http.auth = None http.proxies = None http.cert = ('foo.crt', 'key.pem') c = client.Client(http_session=lambda: http) c._read_loop_polling = mock.MagicMock() c._read_loop_websocket = mock.MagicMock() c._write_loop = mock.MagicMock() on_connect = mock.MagicMock() c.on('connect', on_connect) c.connect('ws://foo', transports=['websocket']) assert len(create_connection.call_args_list) == 1 assert create_connection.call_args[1] == { 'sslopt': {'certfile': 'foo.crt', 'keyfile': 'key.pem'}, 'header': {}, 'cookie': '', 'enable_multithread': True, 'timeout': 5, } @mock.patch('engineio.client.websocket.create_connection') def test_websocket_connection_verify_with_cert_and_key( self, create_connection ): create_connection.return_value.recv.return_value = packet.Packet( packet.OPEN, { 'sid': '123', 'upgrades': [], 'pingInterval': 1000, 'pingTimeout': 2000, }, ).encode() http = mock.MagicMock() http.cookies = [] http.auth = None http.proxies = None http.cert = ('foo.crt', 'key.pem') http.verify = 'ca-bundle.crt' c = client.Client(http_session=lambda: http) c._read_loop_polling = mock.MagicMock() c._read_loop_websocket = mock.MagicMock() c._write_loop = mock.MagicMock() on_connect = mock.MagicMock() c.on('connect', on_connect) c.connect('ws://foo', transports=['websocket']) assert len(create_connection.call_args_list) == 1 assert create_connection.call_args[1] == { 'sslopt': { 'certfile': 'foo.crt', 'keyfile': 'key.pem', 'ca_certs': 'ca-bundle.crt' }, 'header': {}, 'cookie': '', 'enable_multithread': True, 'timeout': 5, } @mock.patch('engineio.client.websocket.create_connection') def test_websocket_connection_with_proxies(self, create_connection): all_urls = [ 'ws://foo', 'ws://foo', 'ws://foo', 'ws://foo', 'ws://foo', 'wss://foo', 'wss://foo', ] all_proxies = [ {'http': 'foo.com:1234'}, {'https': 'foo.com:1234'}, {'http': 'foo.com:1234', 'ws': 'bar.com:4321'}, {}, {'http': 'user:pass@foo.com:1234'}, {'https': 'foo.com:1234'}, {'https': 'foo.com:1234', 'wss': 'bar.com:4321'}, ] all_results = [ ('foo.com', 1234, None), None, ('bar.com', 4321, None), None, ('foo.com', 1234, ('user', 'pass')), ('foo.com', 1234, None), ('bar.com', 4321, None), ] for url, proxies, results in zip(all_urls, all_proxies, all_results): create_connection.reset_mock() create_connection.return_value.recv.return_value = packet.Packet( packet.OPEN, { 'sid': '123', 'upgrades': [], 'pingInterval': 1000, 'pingTimeout': 2000, }, ).encode() http = mock.MagicMock() http.cookies = [] http.auth = None http.proxies = proxies http.cert = None c = client.Client(http_session=lambda: http) c._read_loop_polling = mock.MagicMock() c._read_loop_websocket = mock.MagicMock() c._write_loop = mock.MagicMock() on_connect = mock.MagicMock() c.on('connect', on_connect) c.connect(url, transports=['websocket']) assert len(create_connection.call_args_list) == 1 expected_results = { 'header': {}, 'cookie': '', 'enable_multithread': True, 'timeout': 5, } if results: expected_results.update({ 'http_proxy_host': results[0], 'http_proxy_port': results[1], 'http_proxy_auth': results[2]}) assert create_connection.call_args[1] == expected_results @mock.patch('engineio.client.websocket.create_connection') def test_websocket_connection_without_verify(self, create_connection): create_connection.return_value.recv.return_value = packet.Packet( packet.OPEN, { 'sid': '123', 'upgrades': [], 'pingInterval': 1000, 'pingTimeout': 2000, }, ).encode() http = mock.MagicMock() http.cookies = [] http.auth = None http.proxies = None http.cert = None http.verify = False c = client.Client(http_session=lambda: http) c._read_loop_polling = mock.MagicMock() c._read_loop_websocket = mock.MagicMock() c._write_loop = mock.MagicMock() on_connect = mock.MagicMock() c.on('connect', on_connect) c.connect('ws://foo', transports=['websocket']) assert len(create_connection.call_args_list) == 1 assert create_connection.call_args[1] == { 'sslopt': {"cert_reqs": ssl.CERT_NONE}, 'header': {}, 'cookie': '', 'enable_multithread': True, 'timeout': 5, } @mock.patch('engineio.client.websocket.create_connection') def test_websocket_connection_with_verify(self, create_connection): create_connection.return_value.recv.return_value = packet.Packet( packet.OPEN, { 'sid': '123', 'upgrades': [], 'pingInterval': 1000, 'pingTimeout': 2000, }, ).encode() http = mock.MagicMock() http.cookies = [] http.auth = None http.proxies = None http.cert = None http.verify = 'ca-bundle.crt' c = client.Client(http_session=lambda: http) c._read_loop_polling = mock.MagicMock() c._read_loop_websocket = mock.MagicMock() c._write_loop = mock.MagicMock() on_connect = mock.MagicMock() c.on('connect', on_connect) c.connect('ws://foo', transports=['websocket']) assert len(create_connection.call_args_list) == 1 assert create_connection.call_args[1] == { 'sslopt': {'ca_certs': 'ca-bundle.crt'}, 'header': {}, 'cookie': '', 'enable_multithread': True, 'timeout': 5, } @mock.patch('engineio.client.websocket.create_connection') def test_websocket_upgrade_no_pong(self, create_connection): create_connection.return_value.recv.return_value = packet.Packet( packet.OPEN, { 'sid': '123', 'upgrades': [], 'pingInterval': 1000, 'pingTimeout': 2000, }, ).encode() c = client.Client() c.sid = '123' c.current_transport = 'polling' c._read_loop_polling = mock.MagicMock() c._read_loop_websocket = mock.MagicMock() c._write_loop = mock.MagicMock() on_connect = mock.MagicMock() c.on('connect', on_connect) assert not c.connect('ws://foo', transports=['websocket']) c._read_loop_polling.assert_not_called() c._read_loop_websocket.assert_not_called() c._write_loop.assert_not_called() on_connect.assert_not_called() assert c.transport() == 'polling' @mock.patch('engineio.client.websocket.create_connection') def test_websocket_upgrade_successful(self, create_connection): create_connection.return_value.recv.return_value = packet.Packet( packet.PONG, 'probe').encode() c = client.Client() c.ping_interval = 1 c.ping_timeout = 2 c.sid = '123' c.base_url = 'http://foo' c.current_transport = 'polling' c._read_loop_polling = mock.MagicMock() c._read_loop_websocket = mock.MagicMock() c._write_loop = mock.MagicMock() on_connect = mock.MagicMock() c.on('connect', on_connect) assert c.connect('ws://foo', transports=['websocket']) time.sleep(0.1) c._read_loop_polling.assert_not_called() c._read_loop_websocket.assert_called_once_with() c._write_loop.assert_called_once_with() on_connect.assert_not_called() # was called by polling assert c not in base_client.connected_clients # was added by polling assert c.base_url == 'http://foo' # not changed assert c.sid == '123' # not changed assert c.transport() == 'websocket' assert c.ws == create_connection.return_value assert create_connection.return_value.send.call_args_list[0] == ( (packet.Packet(packet.PING, 'probe').encode(),), ) # ping assert create_connection.return_value.send.call_args_list[1] == ( (packet.Packet(packet.UPGRADE).encode(),), ) # upgrade def test_receive_unknown_packet(self): c = client.Client() c._receive_packet(packet.Packet(encoded_packet='9')) # should be ignored def test_receive_noop_packet(self): c = client.Client() c._receive_packet(packet.Packet(packet.NOOP)) # should be ignored def test_receive_ping_packet(self): c = client.Client() c._send_packet = mock.MagicMock() c._receive_packet(packet.Packet(packet.PING)) assert c._send_packet.call_args_list[0][0][0].encode() == '3' # PONG def test_receive_message_packet(self): c = client.Client() c._trigger_event = mock.MagicMock() c._receive_packet(packet.Packet(packet.MESSAGE, {'foo': 'bar'})) c._trigger_event.assert_called_once_with( 'message', {'foo': 'bar'}, run_async=True ) def test_receive_close_packet(self): c = client.Client() c.disconnect = mock.MagicMock() c._receive_packet(packet.Packet(packet.CLOSE)) c.disconnect.assert_called_once_with( abort=True, reason=c.reason.SERVER_DISCONNECT) def test_send_packet_disconnected(self): c = client.Client() c.queue = c.create_queue() c.state = 'disconnected' c._send_packet(packet.Packet(packet.NOOP)) assert c.queue.empty() def test_send_packet(self): c = client.Client() c.queue = c.create_queue() c.state = 'connected' c._send_packet(packet.Packet(packet.NOOP)) assert not c.queue.empty() pkt = c.queue.get() assert pkt.packet_type == packet.NOOP def test_trigger_event(self): c = client.Client() f = {} @c.on('connect') def foo(): return 'foo' @c.on('message') def bar(data): f['bar'] = data return 'bar' @c.on('disconnect') def baz(reason): return reason r = c._trigger_event('connect', run_async=False) assert r == 'foo' r = c._trigger_event('message', 123, run_async=True) r.join() assert f['bar'] == 123 r = c._trigger_event('message', 321) assert r == 'bar' r = c._trigger_event('disconnect', 'foo') assert r == 'foo' def test_trigger_legacy_disconnect_event(self): c = client.Client() @c.on('disconnect') def baz(): return 'baz' r = c._trigger_event('disconnect', 'foo') assert r == 'baz' def test_trigger_unknown_event(self): c = client.Client() c._trigger_event('connect', run_async=False) c._trigger_event('message', 123, run_async=True) # should do nothing def test_trigger_event_error(self): c = client.Client() @c.on('connect') def foo(): return 1 / 0 @c.on('message') def bar(data): return 1 / 0 with pytest.raises(ZeroDivisionError): r = c._trigger_event('connect', run_async=False) r = c._trigger_event('message', 123, run_async=False) assert r is None def test_engineio_url(self): c = client.Client() assert ( c._get_engineio_url('http://foo', 'bar', 'polling') == 'http://foo/bar/?transport=polling&EIO=4' ) assert ( c._get_engineio_url('http://foo', 'bar', 'websocket') == 'ws://foo/bar/?transport=websocket&EIO=4' ) assert ( c._get_engineio_url('ws://foo', 'bar', 'polling') == 'http://foo/bar/?transport=polling&EIO=4' ) assert ( c._get_engineio_url('ws://foo', 'bar', 'websocket') == 'ws://foo/bar/?transport=websocket&EIO=4' ) assert ( c._get_engineio_url('https://foo', 'bar', 'polling') == 'https://foo/bar/?transport=polling&EIO=4' ) assert ( c._get_engineio_url('https://foo', 'bar', 'websocket') == 'wss://foo/bar/?transport=websocket&EIO=4' ) assert ( c._get_engineio_url('http://foo?baz=1', 'bar', 'polling') == 'http://foo/bar/?baz=1&transport=polling&EIO=4' ) assert ( c._get_engineio_url('http://foo#baz', 'bar', 'polling') == 'http://foo/bar/?transport=polling&EIO=4' ) def test_read_loop_polling_disconnected(self): c = client.Client() c.state = 'disconnected' c._trigger_event = mock.MagicMock() c.write_loop_task = mock.MagicMock() c._read_loop_polling() c.write_loop_task.join.assert_called_once_with() c._trigger_event.assert_not_called() @mock.patch('engineio.client.time.time', return_value=123.456) def test_read_loop_polling_no_response(self, _time): c = client.Client() c.ping_interval = 25 c.ping_timeout = 5 c.state = 'connected' c.base_url = 'http://foo' self.mock_queue(c) c._send_request = mock.MagicMock(return_value=None) c._trigger_event = mock.MagicMock() c.write_loop_task = mock.MagicMock() c._read_loop_polling() assert c.state == 'disconnected' c.queue.put.assert_called_once_with(None) c.write_loop_task.join.assert_called_once_with() c._send_request.assert_called_once_with( 'GET', 'http://foo&t=123.456', timeout=30 ) c._trigger_event.assert_called_once_with( 'disconnect', c.reason.TRANSPORT_ERROR, run_async=False) @mock.patch('engineio.client.time.time', return_value=123.456) def test_read_loop_polling_bad_status(self, _time): c = client.Client() c.ping_interval = 25 c.ping_timeout = 5 c.state = 'connected' c.base_url = 'http://foo' self.mock_queue(c) c._send_request = mock.MagicMock() c._send_request.return_value.status_code = 400 c.write_loop_task = mock.MagicMock() c._read_loop_polling() assert c.state == 'disconnected' c.queue.put.assert_called_once_with(None) c.write_loop_task.join.assert_called_once_with() c._send_request.assert_called_once_with( 'GET', 'http://foo&t=123.456', timeout=30 ) @mock.patch('engineio.client.time.time', return_value=123.456) def test_read_loop_polling_bad_packet(self, _time): c = client.Client() c.ping_interval = 25 c.ping_timeout = 60 c.state = 'connected' c.base_url = 'http://foo' self.mock_queue(c) c._send_request = mock.MagicMock() c._send_request.return_value.status_code = 200 c._send_request.return_value.content = b'foo' c.write_loop_task = mock.MagicMock() c._read_loop_polling() assert c.state == 'disconnected' c.queue.put.assert_called_once_with(None) c.write_loop_task.join.assert_called_once_with() c._send_request.assert_called_once_with( 'GET', 'http://foo&t=123.456', timeout=65 ) def test_read_loop_polling(self): c = client.Client() c.ping_interval = 25 c.ping_timeout = 5 c.state = 'connected' c.base_url = 'http://foo' self.mock_queue(c) c._send_request = mock.MagicMock() c._send_request.side_effect = [ mock.MagicMock( status_code=200, content=payload.Payload( packets=[ packet.Packet(packet.PING), packet.Packet(packet.NOOP), ] ).encode().encode('utf-8'), ), None, ] c.write_loop_task = mock.MagicMock() c._receive_packet = mock.MagicMock() c._read_loop_polling() assert c.state == 'disconnected' c.queue.put.assert_called_once_with(None) assert c._send_request.call_count == 2 assert c._receive_packet.call_count == 2 assert c._receive_packet.call_args_list[0][0][0].encode() == '2' assert c._receive_packet.call_args_list[1][0][0].encode() == '6' def test_read_loop_websocket_disconnected(self): c = client.Client() c.state = 'disconnected' c.write_loop_task = mock.MagicMock() c._read_loop_websocket() c.write_loop_task.join.assert_called_once_with() def test_read_loop_websocket_timeout(self): c = client.Client() c.state = 'connected' self.mock_queue(c) c.ws = mock.MagicMock() c.ws.recv.side_effect = websocket.WebSocketTimeoutException c.write_loop_task = mock.MagicMock() c._read_loop_websocket() assert c.state == 'disconnected' c.queue.put.assert_called_once_with(None) c.write_loop_task.join.assert_called_once_with() def test_read_loop_websocket_no_response(self): c = client.Client() c.state = 'connected' self.mock_queue(c) c.ws = mock.MagicMock() c.ws.recv.side_effect = websocket.WebSocketConnectionClosedException c.write_loop_task = mock.MagicMock() c._read_loop_websocket() assert c.state == 'disconnected' c.queue.put.assert_called_once_with(None) c.write_loop_task.join.assert_called_once_with() def test_read_loop_websocket_unexpected_error(self): c = client.Client() c.state = 'connected' self.mock_queue(c) c.ws = mock.MagicMock() c.ws.recv.side_effect = ValueError c.write_loop_task = mock.MagicMock() c._read_loop_websocket() assert c.state == 'disconnected' c.queue.put.assert_called_once_with(None) c.write_loop_task.join.assert_called_once_with() def test_read_loop_websocket(self): c = client.Client() c.ping_interval = 1 c.ping_timeout = 2 c.state = 'connected' self.mock_queue(c) c.ws = mock.MagicMock() c.ws.recv.side_effect = [ packet.Packet(packet.PING).encode(), ValueError, ] c.write_loop_task = mock.MagicMock() c._receive_packet = mock.MagicMock() c._read_loop_websocket() assert c.state == 'disconnected' c.queue.put.assert_called_once_with(None) c.write_loop_task.join.assert_called_once_with() assert c._receive_packet.call_args_list[0][0][0].encode() == '2' def test_write_loop_disconnected(self): c = client.Client() c.state = 'disconnected' c._write_loop() # should not block def test_write_loop_no_packets(self): c = client.Client() c.state = 'connected' c.ping_interval = 1 c.ping_timeout = 2 self.mock_queue(c) c.queue.get.return_value = None c._write_loop() c.queue.task_done.assert_called_once_with() c.queue.get.assert_called_once_with(timeout=7) def test_write_loop_empty_queue(self): c = client.Client() c.state = 'connected' c.ping_interval = 1 c.ping_timeout = 2 self.mock_queue(c) c.queue.get.side_effect = c.queue_empty c._write_loop() c.queue.get.assert_called_once_with(timeout=7) def test_write_loop_polling_one_packet(self): c = client.Client() c.base_url = 'http://foo' c.state = 'connected' c.ping_interval = 1 c.ping_timeout = 2 c.current_transport = 'polling' self.mock_queue(c) c.queue.get.side_effect = [ packet.Packet(packet.MESSAGE, {'foo': 'bar'}), c.queue_empty, c.queue_empty, ] c._send_request = mock.MagicMock() c._send_request.return_value.status_code = 200 c._write_loop() assert c.queue.task_done.call_count == 1 p = payload.Payload( packets=[packet.Packet(packet.MESSAGE, {'foo': 'bar'})] ) c._send_request.assert_called_once_with( 'POST', 'http://foo', body=p.encode(), headers={'Content-Type': 'text/plain'}, timeout=5, ) def test_write_loop_polling_three_packets(self): c = client.Client() c.base_url = 'http://foo' c.state = 'connected' c.ping_interval = 1 c.ping_timeout = 2 c.current_transport = 'polling' self.mock_queue(c) c.queue.get.side_effect = [ packet.Packet(packet.MESSAGE, {'foo': 'bar'}), packet.Packet(packet.PING), packet.Packet(packet.NOOP), c.queue_empty, c.queue_empty, ] c._send_request = mock.MagicMock() c._send_request.return_value.status_code = 200 c._write_loop() assert c.queue.task_done.call_count == 3 p = payload.Payload( packets=[ packet.Packet(packet.MESSAGE, {'foo': 'bar'}), packet.Packet(packet.PING), packet.Packet(packet.NOOP), ] ) c._send_request.assert_called_once_with( 'POST', 'http://foo', body=p.encode(), headers={'Content-Type': 'text/plain'}, timeout=5, ) def test_write_loop_polling_two_packets_done(self): c = client.Client() c.base_url = 'http://foo' c.state = 'connected' c.ping_interval = 1 c.ping_timeout = 2 c.current_transport = 'polling' self.mock_queue(c) c.queue.get.side_effect = [ packet.Packet(packet.MESSAGE, {'foo': 'bar'}), packet.Packet(packet.PING), None, c.queue_empty, ] c._send_request = mock.MagicMock() c._send_request.return_value.status_code = 200 c._write_loop() assert c.queue.task_done.call_count == 3 p = payload.Payload( packets=[ packet.Packet(packet.MESSAGE, {'foo': 'bar'}), packet.Packet(packet.PING), ] ) c._send_request.assert_called_once_with( 'POST', 'http://foo', body=p.encode(), headers={'Content-Type': 'text/plain'}, timeout=5, ) assert c.state == 'connected' def test_write_loop_polling_bad_connection(self): c = client.Client() c.base_url = 'http://foo' c.state = 'connected' c.ping_interval = 1 c.ping_timeout = 2 c.current_transport = 'polling' self.mock_queue(c) c.queue.get.side_effect = [ packet.Packet(packet.MESSAGE, {'foo': 'bar'}), c.queue_empty, ] c._send_request = mock.MagicMock() c._send_request.return_value = None c._write_loop() assert c.queue.task_done.call_count == 1 p = payload.Payload( packets=[packet.Packet(packet.MESSAGE, {'foo': 'bar'})] ) c._send_request.assert_called_once_with( 'POST', 'http://foo', body=p.encode(), headers={'Content-Type': 'text/plain'}, timeout=5, ) assert c.state == 'connected' def test_write_loop_polling_bad_status(self): c = client.Client() c.base_url = 'http://foo' c.state = 'connected' c.ping_interval = 1 c.ping_timeout = 2 c.current_transport = 'polling' self.mock_queue(c) c.queue.get.side_effect = [ packet.Packet(packet.MESSAGE, {'foo': 'bar'}), c.queue_empty, ] c._send_request = mock.MagicMock() c._send_request.return_value.status_code = 500 c._write_loop() assert c.queue.task_done.call_count == 1 p = payload.Payload( packets=[packet.Packet(packet.MESSAGE, {'foo': 'bar'})] ) c._send_request.assert_called_once_with( 'POST', 'http://foo', body=p.encode(), headers={'Content-Type': 'text/plain'}, timeout=5, ) assert c.state == 'connected' assert c.write_loop_task is None def test_write_loop_websocket_one_packet(self): c = client.Client() c.state = 'connected' c.ping_interval = 1 c.ping_timeout = 2 c.current_transport = 'websocket' self.mock_queue(c) c.queue.get.side_effect = [ packet.Packet(packet.MESSAGE, {'foo': 'bar'}), c.queue_empty, c.queue_empty, ] c.ws = mock.MagicMock() c._write_loop() assert c.queue.task_done.call_count == 1 assert c.ws.send.call_count == 1 assert c.ws.send_binary.call_count == 0 c.ws.send.assert_called_once_with('4{"foo":"bar"}') def test_write_loop_websocket_three_packets(self): c = client.Client() c.state = 'connected' c.ping_interval = 1 c.ping_timeout = 2 c.current_transport = 'websocket' self.mock_queue(c) c.queue.get.side_effect = [ packet.Packet(packet.MESSAGE, {'foo': 'bar'}), packet.Packet(packet.PING), packet.Packet(packet.NOOP), c.queue_empty, c.queue_empty, ] c.ws = mock.MagicMock() c._write_loop() assert c.queue.task_done.call_count == 3 assert c.ws.send.call_count == 3 assert c.ws.send_binary.call_count == 0 assert c.ws.send.call_args_list[0][0][0] == '4{"foo":"bar"}' assert c.ws.send.call_args_list[1][0][0] == '2' assert c.ws.send.call_args_list[2][0][0] == '6' def test_write_loop_websocket_one_packet_binary(self): c = client.Client() c.state = 'connected' c.ping_interval = 1 c.ping_timeout = 2 c.current_transport = 'websocket' self.mock_queue(c) c.queue.get.side_effect = [ packet.Packet(packet.MESSAGE, b'foo'), c.queue_empty, c.queue_empty, ] c.ws = mock.MagicMock() c._write_loop() assert c.queue.task_done.call_count == 1 assert c.ws.send.call_count == 0 assert c.ws.send_binary.call_count == 1 c.ws.send_binary.assert_called_once_with(b'foo') def test_write_loop_websocket_bad_connection(self): c = client.Client() c.state = 'connected' c.ping_interval = 1 c.ping_timeout = 2 c.current_transport = 'websocket' self.mock_queue(c) c.queue.get.side_effect = [ packet.Packet(packet.MESSAGE, {'foo': 'bar'}), c.queue_empty, c.queue_empty, ] c.ws = mock.MagicMock() c.ws.send.side_effect = websocket.WebSocketConnectionClosedException c._write_loop() assert c.state == 'connected' @mock.patch('engineio.base_client.original_signal_handler') def test_signal_handler(self, original_handler): clients = [mock.MagicMock(), mock.MagicMock()] base_client.connected_clients = clients[:] base_client.connected_clients[0].is_asyncio_based.return_value = False base_client.connected_clients[1].is_asyncio_based.return_value = True base_client.signal_handler('sig', 'frame') clients[0].disconnect.assert_called_once_with() clients[1].disconnect.assert_not_called() ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1791414216.751095 python_engineio-4.15.1/tests/common/test_middleware.py0000644000175000017500000001671715261547711022573 0ustar00miguelmiguelimport os from unittest import mock import engineio class TestWSGIApp: def test_wsgi_routing(self): mock_wsgi_app = mock.MagicMock() mock_eio_app = 'foo' m = engineio.WSGIApp(mock_eio_app, mock_wsgi_app) environ = {'PATH_INFO': '/foo'} start_response = "foo" m(environ, start_response) mock_wsgi_app.assert_called_once_with(environ, start_response) def test_eio_routing(self): mock_wsgi_app = 'foo' mock_eio_app = mock.Mock() mock_eio_app.handle_request = mock.MagicMock() m = engineio.WSGIApp(mock_eio_app, mock_wsgi_app) environ = {'PATH_INFO': '/engine.io/'} start_response = "foo" m(environ, start_response) mock_eio_app.handle_request.assert_called_once_with( environ, start_response ) def test_static_files(self): root_dir = os.path.dirname(__file__) m = engineio.WSGIApp( 'foo', None, static_files={ '/': root_dir + '/index.html', '/foo': { 'content_type': 'text/plain', 'filename': root_dir + '/index.html', }, '/static': root_dir, '/static/test/': root_dir + '/', '/static2/test/': {'filename': root_dir + '/', 'content_type': 'image/gif'}, }, ) def check_path(path, status_code, content_type, body): environ = {'PATH_INFO': path} start_response = mock.MagicMock() r = m(environ, start_response) assert r == [body.encode('utf-8')] start_response.assert_called_once_with( status_code, [('Content-Type', content_type)] ) check_path('/', '200 OK', 'text/html', '\n') check_path('/foo', '200 OK', 'text/plain', '\n') check_path('/foo/bar', '404 Not Found', 'text/plain', 'Not Found') check_path( '/static/index.html', '200 OK', 'text/html', '\n' ) check_path( '/static/foo.bar', '404 Not Found', 'text/plain', 'Not Found' ) check_path( '/static/test/index.html', '200 OK', 'text/html', '\n' ) check_path('/static/test/', '200 OK', 'text/html', '\n') check_path('/static/test/index.html', '200 OK', 'text/html', '\n') check_path('/static/test/files/', '200 OK', 'text/html', 'file\n') check_path('/static/test/files/file.txt', '200 OK', 'text/plain', 'file\n') check_path('/static/test/files/x.html', '404 Not Found', 'text/plain', 'Not Found') check_path('/static2/test/', '200 OK', 'image/gif', '\n') check_path('/static2/test/index.html', '200 OK', 'image/gif', '\n') check_path('/static2/test/files/', '200 OK', 'image/gif', 'file\n') check_path('/static2/test/files/file.txt', '200 OK', 'image/gif', 'file\n') check_path('/static2/test/files/x.html', '404 Not Found', 'text/plain', 'Not Found') check_path('/bar/foo', '404 Not Found', 'text/plain', 'Not Found') check_path('', '404 Not Found', 'text/plain', 'Not Found') check_path('/static/../../LICENSE', '404 Not Found', 'text/plain', 'Not Found') m.static_files[''] = 'index.html' check_path('/static/test/', '200 OK', 'text/html', '\n') m.static_files[''] = {'filename': 'index.html'} check_path('/static/test/', '200 OK', 'text/html', '\n') m.static_files[''] = { 'filename': 'index.html', 'content_type': 'image/gif', } check_path('/static/test/', '200 OK', 'image/gif', '\n') m.static_files[''] = {'filename': 'test.gif'} check_path('/static/test/', '404 Not Found', 'text/plain', 'Not Found') m.static_files = {} check_path( '/static/test/index.html', '404 Not Found', 'text/plain', 'Not Found', ) def test_404(self): mock_wsgi_app = None mock_eio_app = mock.Mock() m = engineio.WSGIApp(mock_eio_app, mock_wsgi_app) environ = {'PATH_INFO': '/foo/bar'} start_response = mock.MagicMock() r = m(environ, start_response) assert r == [b'Not Found'] start_response.assert_called_once_with( "404 Not Found", [('Content-Type', 'text/plain')] ) def test_custom_eio_path(self): mock_wsgi_app = None mock_eio_app = mock.Mock() mock_eio_app.handle_request = mock.MagicMock() m = engineio.WSGIApp(mock_eio_app, mock_wsgi_app, engineio_path='foo') environ = {'PATH_INFO': '/engine.io/'} start_response = mock.MagicMock() r = m(environ, start_response) assert r == [b'Not Found'] start_response.assert_called_once_with( "404 Not Found", [('Content-Type', 'text/plain')] ) environ = {'PATH_INFO': '/foo/'} m(environ, start_response) mock_eio_app.handle_request.assert_called_once_with( environ, start_response ) def test_custom_eio_path_slashes(self): mock_wsgi_app = None mock_eio_app = mock.Mock() mock_eio_app.handle_request = mock.MagicMock() m = engineio.WSGIApp( mock_eio_app, mock_wsgi_app, engineio_path='/foo/' ) environ = {'PATH_INFO': '/foo/'} start_response = mock.MagicMock() m(environ, start_response) mock_eio_app.handle_request.assert_called_once_with( environ, start_response ) def test_custom_eio_path_leading_slash(self): mock_wsgi_app = None mock_eio_app = mock.Mock() mock_eio_app.handle_request = mock.MagicMock() m = engineio.WSGIApp(mock_eio_app, mock_wsgi_app, engineio_path='/foo') environ = {'PATH_INFO': '/foo/'} start_response = mock.MagicMock() m(environ, start_response) mock_eio_app.handle_request.assert_called_once_with( environ, start_response ) def test_custom_eio_path_trailing_slash(self): mock_wsgi_app = None mock_eio_app = mock.Mock() mock_eio_app.handle_request = mock.MagicMock() m = engineio.WSGIApp(mock_eio_app, mock_wsgi_app, engineio_path='foo/') environ = {'PATH_INFO': '/foo/'} start_response = mock.MagicMock() m(environ, start_response) mock_eio_app.handle_request.assert_called_once_with( environ, start_response ) def test_gunicorn_socket(self): mock_wsgi_app = None mock_eio_app = mock.Mock() m = engineio.WSGIApp(mock_eio_app, mock_wsgi_app) environ = {'gunicorn.socket': 123, 'PATH_INFO': '/foo/bar'} start_response = mock.MagicMock() m(environ, start_response) assert 'eventlet.input' in environ assert environ['eventlet.input'].get_socket() == 123 def test_legacy_middleware_class(self): m = engineio.Middleware('eio', 'wsgi', 'eio_path') assert m.engineio_app == 'eio' assert m.wsgi_app == 'wsgi' assert m.static_files == {} assert m.engineio_path == '/eio_path/' ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1791413117.6863568 python_engineio-4.15.1/tests/common/test_packet.py0000644000175000017500000001173015261545576021722 0ustar00miguelmiguelimport pytest from engineio import packet class TestPacket: def test_encode_default_packet(self): pkt = packet.Packet() assert pkt.packet_type == packet.NOOP assert pkt.data is None assert not pkt.binary assert pkt.encode() == '6' def test_decode_default_packet(self): pkt = packet.Packet(encoded_packet='6') assert pkt.encode() == '6' def test_encode_text_packet(self): data = 'text' pkt = packet.Packet(packet.MESSAGE, data=data) assert pkt.packet_type == packet.MESSAGE assert pkt.data == data assert not pkt.binary assert pkt.encode() == '4text' def test_decode_text_packet(self): pkt = packet.Packet(encoded_packet=b'4text') assert pkt.encode() == b'4text' def test_encode_empty_text_packet(self): data = '' pkt = packet.Packet(packet.MESSAGE, data=data) assert pkt.packet_type == packet.MESSAGE assert pkt.data == data assert not pkt.binary assert pkt.encode() == '4' def test_decode_empty_text_packet(self): pkt = packet.Packet(encoded_packet=b'4') assert pkt.encode() == b'4' def test_encode_binary_packet(self): pkt = packet.Packet(packet.MESSAGE, data=b'\x01\x02\x03') assert pkt.packet_type == packet.MESSAGE assert pkt.data == b'\x01\x02\x03' assert pkt.binary assert pkt.encode() == b'\x01\x02\x03' def test_encode_binary_bytearray_packet(self): pkt = packet.Packet(packet.MESSAGE, data=bytearray(b'\x01\x02\x03')) assert pkt.packet_type == packet.MESSAGE assert pkt.data == b'\x01\x02\x03' assert pkt.binary assert pkt.encode() == b'\x01\x02\x03' def test_encode_binary_b64_packet(self): pkt = packet.Packet(packet.MESSAGE, data=b'\x01\x02\x03\x04') assert pkt.packet_type == packet.MESSAGE assert pkt.data == b'\x01\x02\x03\x04' assert pkt.binary assert pkt.encode(b64=True) == 'bAQIDBA==' def test_encode_empty_binary_packet(self): pkt = packet.Packet(packet.MESSAGE, data=b'') assert pkt.packet_type == packet.MESSAGE assert pkt.data == b'' assert pkt.binary assert pkt.encode() == b'' def test_decode_binary_packet(self): pkt = packet.Packet(encoded_packet=b'\x04\x01\x02\x03') assert pkt.encode() == b'\x04\x01\x02\x03' def test_decode_binary_bytearray_packet(self): pkt = packet.Packet(encoded_packet=bytearray(b'\x04\x01\x02\x03')) assert pkt.encode() == b'\x04\x01\x02\x03' def test_decode_binary_b64_packet(self): pkt = packet.Packet(encoded_packet='bBAECAw==') assert pkt.encode() == b'\x04\x01\x02\x03' def test_decode_empty_binary_packet(self): pkt = packet.Packet(encoded_packet=b'') assert pkt.encode() == b'' def test_encode_json_packet(self): pkt = packet.Packet(packet.MESSAGE, data={'a': 123, 'b': '456'}) assert pkt.packet_type == packet.MESSAGE assert pkt.data == {'a': 123, 'b': '456'} assert not pkt.binary assert pkt.encode() in [ '4{"a":123,"b":"456"}', '4{"b":"456","a":123}', ] def test_decode_json_packet(self): pkt = packet.Packet(encoded_packet='4{"a":123,"b":"456"}') assert pkt.encode() in [ '4{"a":123,"b":"456"}', '4{"b":"456","a":123}', ] def test_decode_json_packet_long_int(self): pkt = packet.Packet(encoded_packet='4{"a":' + '1' * 100 + '}') assert pkt.packet_type == packet.MESSAGE assert pkt.data == {'a': int('1' * 100)} pkt = packet.Packet(encoded_packet='4{"a":' + '1' * 101 + '}') assert pkt.packet_type == packet.MESSAGE assert pkt.data == '{"a":' + '1' * 101 + '}' def test_encode_number_packet(self): pkt = packet.Packet(packet.MESSAGE, data=123) assert pkt.packet_type == packet.MESSAGE assert pkt.data == 123 assert not pkt.binary assert pkt.encode() == '4123' def test_decode_number_packet(self): pkt = packet.Packet(encoded_packet='4123') assert pkt.packet_type == packet.MESSAGE # integer payloads are parsed as strings, see # https://github.com/miguelgrinberg/python-engineio/issues/75 # for background on this decision assert pkt.data == '123' assert not pkt.binary assert pkt.encode() == '4123' def test_binary_non_message_packet(self): with pytest.raises(ValueError): packet.Packet(packet.NOOP, b'\x01\x02\x03') def test_decode_invalid_empty_text_packet(self): with pytest.raises(ValueError): packet.Packet(encoded_packet='') def test_encode_cache(self): pkt = packet.Packet(packet.MESSAGE, data=123) assert pkt.encode() == '4123' pkt.data = 456 assert pkt.encode() == '4123' pkt.encode_cache = None assert pkt.encode() == '4456' ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1791413117.6864126 python_engineio-4.15.1/tests/common/test_payload.py0000644000175000017500000000456215261545576022111 0ustar00miguelmiguelimport pytest from engineio import packet from engineio import payload class TestPayload: def test_encode_empty_payload(self): p = payload.Payload() assert p.packets == [] assert p.encode() == '' def test_decode_empty_payload(self): p = payload.Payload(encoded_payload='') assert p.encode() == '' def test_encode_payload_text(self): pkt = packet.Packet(packet.MESSAGE, data='abc') p = payload.Payload([pkt]) assert p.packets == [pkt] assert p.encode() == '4abc' def test_encode_payload_text_multiple(self): pkt = packet.Packet(packet.MESSAGE, data='abc') pkt2 = packet.Packet(packet.MESSAGE, data='def') p = payload.Payload([pkt, pkt2]) assert p.packets == [pkt, pkt2] assert p.encode() == '4abc\x1e4def' def test_encode_payload_binary(self): pkt = packet.Packet(packet.MESSAGE, data=b'\x00\x01\x02') p = payload.Payload([pkt]) assert p.packets == [pkt] assert p.encode() == 'bAAEC' def test_encode_payload_binary_multiple(self): pkt = packet.Packet(packet.MESSAGE, data=b'\x00\x01\x02') pkt2 = packet.Packet(packet.MESSAGE, data=b'\x03\x04\x05\x06') p = payload.Payload([pkt, pkt2]) assert p.packets == [pkt, pkt2] assert p.encode() == 'bAAEC\x1ebAwQFBg==' def test_encode_payload_text_binary_multiple(self): pkt = packet.Packet(packet.MESSAGE, data='abc') pkt2 = packet.Packet(packet.MESSAGE, data=b'\x03\x04\x05\x06') p = payload.Payload([pkt, pkt2, pkt2, pkt]) assert p.packets == [pkt, pkt2, pkt2, pkt] assert p.encode() == '4abc\x1ebAwQFBg==\x1ebAwQFBg==\x1e4abc' def test_encode_jsonp_payload(self): pkt = packet.Packet(packet.MESSAGE, data='abc') p = payload.Payload([pkt]) assert p.packets == [pkt] assert p.encode(jsonp_index=233) == '___eio[233]("4abc");' def test_decode_jsonp_payload(self): p = payload.Payload(encoded_payload='d=4abc') assert p.encode() == '4abc' def test_decode_invalid_payload(self): with pytest.raises(ValueError): payload.Payload(encoded_payload='bad payload') def test_decode_multi_payload_with_too_many_packets(self): with pytest.raises(ValueError): payload.Payload(encoded_payload='4abc\x1e4def\x1e' * 9 + '6') ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1793227445.7651277 python_engineio-4.15.1/tests/common/test_server.py0000644000175000017500000013766515270475266021777 0ustar00miguelmiguelimport gzip import importlib import io import logging import sys import time from unittest import mock import zlib import pytest from engineio import exceptions from engineio import json from engineio import packet from engineio import payload from engineio import server original_import_module = importlib.import_module def _mock_import(module, *args, **kwargs): if module.startswith('engineio.'): return original_import_module(module, *args, **kwargs) return module class TestServer: _mock_async = mock.MagicMock() _mock_async._async = { 'thread': 't', 'queue': 'q', 'queue_empty': RuntimeError, 'websocket': 'w', } def _get_mock_socket(self): mock_socket = mock.MagicMock() mock_socket.connected = False mock_socket.closed = False mock_socket.closing = False mock_socket.upgraded = False mock_socket.session = {} return mock_socket @classmethod def setup_class(cls): server.Server._default_monitor_clients = False @classmethod def teardown_class(cls): server.Server._default_monitor_clients = True def setup_method(self): logging.getLogger('engineio').setLevel(logging.NOTSET) def teardown_method(self): # restore JSON encoder, in case a test changed it packet.Packet.json = json def test_is_asyncio_based(self): s = server.Server() assert not s.is_asyncio_based() def test_async_modes(self): s = server.Server() assert s.async_modes() == [ 'eventlet', 'gevent_uwsgi', 'gevent', 'threading', ] def test_create(self): kwargs = { 'ping_timeout': 1, 'ping_interval': 2, 'max_http_buffer_size': 3, 'allow_upgrades': False, 'http_compression': False, 'compression_threshold': 4, 'cookie': 'foo', 'cors_allowed_origins': ['foo', 'bar', 'baz'], 'cors_credentials': False, 'async_handlers': False, } s = server.Server(**kwargs) for arg in kwargs.keys(): assert getattr(s, arg) == kwargs[arg] assert s.ping_interval_grace_period == 0 def test_create_with_grace_period(self): s = server.Server(ping_interval=(1, 2)) assert s.ping_interval == 1 assert s.ping_interval_grace_period == 2 def test_create_ignores_kwargs(self): server.Server(foo='bar') # this should not raise def test_async_mode_threading(self): sys.modules['simple_websocket'] = mock.MagicMock() s = server.Server(async_mode='threading') assert s.async_mode == 'threading' from engineio.async_drivers import threading as async_threading import queue assert s._async['thread'] == async_threading.DaemonThread assert s._async['queue'] == queue.Queue assert s._async['websocket'] == async_threading.SimpleWebSocketWSGI del sys.modules['simple_websocket'] del sys.modules['engineio.async_drivers.threading'] def test_async_mode_eventlet(self): sys.modules['eventlet'] = mock.MagicMock() sys.modules['eventlet'].green = mock.MagicMock() sys.modules['eventlet.green'] = sys.modules['eventlet'].green sys.modules['eventlet.green'].threading = mock.MagicMock() sys.modules['eventlet.green.threading'] = \ sys.modules['eventlet.green'].threading sys.modules['eventlet'].websocket = mock.MagicMock() sys.modules['eventlet.websocket'] = sys.modules['eventlet'].websocket s = server.Server(async_mode='eventlet') assert s.async_mode == 'eventlet' from eventlet import queue from engineio.async_drivers import eventlet as async_eventlet assert s._async['thread'] == async_eventlet.EventletThread assert s._async['queue'] == queue.Queue assert s._async['websocket'] == async_eventlet.WebSocketWSGI del sys.modules['eventlet'] del sys.modules['eventlet.green'] del sys.modules['eventlet.green.threading'] del sys.modules['eventlet.websocket'] del sys.modules['engineio.async_drivers.eventlet'] @mock.patch('importlib.import_module', side_effect=_mock_import) def test_async_mode_gevent_uwsgi(self, import_module): sys.modules['gevent'] = mock.MagicMock() sys.modules['gevent'].queue = mock.MagicMock() sys.modules['gevent.queue'] = sys.modules['gevent'].queue sys.modules['gevent.queue'].JoinableQueue = 'foo' sys.modules['gevent.queue'].Empty = RuntimeError sys.modules['gevent.event'] = mock.MagicMock() sys.modules['gevent.event'].Event = 'bar' sys.modules['uwsgi'] = mock.MagicMock() s = server.Server(async_mode='gevent_uwsgi') assert s.async_mode == 'gevent_uwsgi' from engineio.async_drivers import gevent_uwsgi as async_gevent_uwsgi assert s._async['thread'] == async_gevent_uwsgi.Thread assert s._async['queue'] == 'foo' assert s._async['queue_empty'] == RuntimeError assert s._async['event'] == 'bar' assert s._async['websocket'] == async_gevent_uwsgi.uWSGIWebSocket del sys.modules['gevent'] del sys.modules['gevent.queue'] del sys.modules['gevent.event'] del sys.modules['uwsgi'] del sys.modules['engineio.async_drivers.gevent_uwsgi'] @mock.patch('importlib.import_module', side_effect=_mock_import) def test_async_mode_gevent_uwsgi_without_uwsgi(self, import_module): sys.modules['gevent'] = mock.MagicMock() sys.modules['gevent'].queue = mock.MagicMock() sys.modules['gevent.queue'] = sys.modules['gevent'].queue sys.modules['gevent.queue'].JoinableQueue = 'foo' sys.modules['gevent.queue'].Empty = RuntimeError sys.modules['gevent.event'] = mock.MagicMock() sys.modules['gevent.event'].Event = 'bar' sys.modules['uwsgi'] = None with pytest.raises(ValueError): server.Server(async_mode='gevent_uwsgi') del sys.modules['gevent'] del sys.modules['gevent.queue'] del sys.modules['gevent.event'] del sys.modules['uwsgi'] @mock.patch('importlib.import_module', side_effect=_mock_import) def test_async_mode_gevent_uwsgi_without_websocket(self, import_module): sys.modules['gevent'] = mock.MagicMock() sys.modules['gevent'].queue = mock.MagicMock() sys.modules['gevent.queue'] = sys.modules['gevent'].queue sys.modules['gevent.queue'].JoinableQueue = 'foo' sys.modules['gevent.queue'].Empty = RuntimeError sys.modules['gevent.event'] = mock.MagicMock() sys.modules['gevent.event'].Event = 'bar' sys.modules['uwsgi'] = mock.MagicMock() del sys.modules['uwsgi'].websocket_handshake s = server.Server(async_mode='gevent_uwsgi') assert s.async_mode == 'gevent_uwsgi' from engineio.async_drivers import gevent_uwsgi as async_gevent_uwsgi assert s._async['thread'] == async_gevent_uwsgi.Thread assert s._async['queue'] == 'foo' assert s._async['queue_empty'] == RuntimeError assert s._async['event'] == 'bar' assert s._async['websocket'] is None del sys.modules['gevent'] del sys.modules['gevent.queue'] del sys.modules['gevent.event'] del sys.modules['uwsgi'] del sys.modules['engineio.async_drivers.gevent_uwsgi'] @mock.patch('importlib.import_module', side_effect=_mock_import) def test_async_mode_gevent(self, import_module): sys.modules['gevent'] = mock.MagicMock() sys.modules['gevent'].queue = mock.MagicMock() sys.modules['gevent.queue'] = sys.modules['gevent'].queue sys.modules['gevent.queue'].JoinableQueue = 'foo' sys.modules['gevent.queue'].Empty = RuntimeError sys.modules['gevent.event'] = mock.MagicMock() sys.modules['gevent.event'].Event = 'bar' sys.modules['geventwebsocket'] = 'geventwebsocket' s = server.Server(async_mode='gevent') assert s.async_mode == 'gevent' from engineio.async_drivers import gevent as async_gevent assert s._async['thread'] == async_gevent.Thread assert s._async['queue'] == 'foo' assert s._async['queue_empty'] == RuntimeError assert s._async['event'] == 'bar' assert s._async['websocket'] == async_gevent.WebSocketWSGI del sys.modules['gevent'] del sys.modules['gevent.queue'] del sys.modules['gevent.event'] del sys.modules['geventwebsocket'] del sys.modules['engineio.async_drivers.gevent'] @mock.patch('importlib.import_module', side_effect=_mock_import) def test_async_mode_aiohttp(self, import_module): sys.modules['aiohttp'] = mock.MagicMock() with pytest.raises(ValueError): server.Server(async_mode='aiohttp') @mock.patch('importlib.import_module', side_effect=[ImportError]) def test_async_mode_invalid(self, import_module): with pytest.raises(ValueError): server.Server(async_mode='foo') @mock.patch( 'importlib.import_module', side_effect=[_mock_async], ) def test_async_mode_auto_eventlet(self, import_module): s = server.Server() assert s.async_mode == 'eventlet' @mock.patch( 'importlib.import_module', side_effect=[ImportError, _mock_async] ) def test_async_mode_auto_gevent_uwsgi(self, import_module): s = server.Server() assert s.async_mode == 'gevent_uwsgi' @mock.patch( 'importlib.import_module', side_effect=[ImportError, ImportError, _mock_async], ) def test_async_mode_auto_gevent(self, import_module): s = server.Server() assert s.async_mode == 'gevent' @mock.patch( 'importlib.import_module', side_effect=[ImportError, ImportError, ImportError, _mock_async], ) def test_async_mode_auto_threading(self, import_module): s = server.Server() assert s.async_mode == 'threading' def test_generate_id(self): s = server.Server() assert s.generate_id() != s.generate_id() def test_on_event(self): s = server.Server() @s.on('connect') def foo(): pass s.on('disconnect', foo) assert s.handlers['connect'] == foo assert s.handlers['disconnect'] == foo def test_on_event_invalid(self): s = server.Server() with pytest.raises(ValueError): s.on('invalid') def test_trigger_event(self): s = server.Server(async_mode='threading') f = {} @s.on('connect') def foo(sid, environ): return sid + environ @s.on('message') def bar(sid, data): f['bar'] = sid + data return 'bar' @s.on('disconnect') def baz(sid, reason): return sid + reason r = s._trigger_event('connect', 1, 2, run_async=False) assert r == 3 r = s._trigger_event('message', 3, 4, run_async=True) r.join() assert f['bar'] == 7 r = s._trigger_event('message', 5, 6) assert r == 'bar' r = s._trigger_event('disconnect', 'foo', 'bar') assert r == 'foobar' def test_trigger_legacy_disconnect_event(self): s = server.Server(async_mode='threading') @s.on('disconnect') def baz(sid): return sid r = s._trigger_event('disconnect', 'foo', 'bar') assert r == 'foo' def test_trigger_event_error(self): s = server.Server() @s.on('connect') def foo(sid, environ): return 1 / 0 @s.on('message') def bar(sid, data): return 1 / 0 r = s._trigger_event('connect', 1, 2, run_async=False) assert not r r = s._trigger_event('message', 3, 4, run_async=False) assert r is None def test_session(self): s = server.Server() mock_socket = self._get_mock_socket() s.sockets['foo'] = mock_socket with s.session('foo') as session: assert session == {} session['username'] = 'bar' assert s.get_session('foo') == {'username': 'bar'} def test_close_one_socket(self): s = server.Server() mock_socket = self._get_mock_socket() s.sockets['foo'] = mock_socket s.disconnect('foo') assert mock_socket.close.call_count == 1 assert 'foo' not in s.sockets def test_close_all_sockets(self): s = server.Server() mock_sockets = {} for sid in ['foo', 'bar', 'baz']: mock_sockets[sid] = self._get_mock_socket() s.sockets[sid] = mock_sockets[sid] s.disconnect() for socket in mock_sockets.values(): assert socket.close.call_count == 1 assert s.sockets == {} def test_upgrades(self): s = server.Server() s.sockets['foo'] = self._get_mock_socket() assert s._upgrades('foo', 'polling') == ['websocket'] assert s._upgrades('foo', 'websocket') == [] s.sockets['foo'].upgraded = True assert s._upgrades('foo', 'polling') == [] assert s._upgrades('foo', 'websocket') == [] s.allow_upgrades = False s.sockets['foo'].upgraded = True assert s._upgrades('foo', 'polling') == [] assert s._upgrades('foo', 'websocket') == [] def test_transport(self): s = server.Server() s.sockets['foo'] = self._get_mock_socket() s.sockets['foo'].upgraded = False s.sockets['bar'] = self._get_mock_socket() s.sockets['bar'].upgraded = True assert s.transport('foo') == 'polling' assert s.transport('bar') == 'websocket' def test_bad_session(self): s = server.Server() s.sockets['foo'] = 'client' with pytest.raises(KeyError): s._get_socket('bar') def test_closed_socket(self): s = server.Server() s.sockets['foo'] = self._get_mock_socket() s.sockets['foo'].closed = True with pytest.raises(KeyError): s._get_socket('foo') def test_jsonp_with_bad_index(self): s = server.Server() environ = {'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'EIO=4&j=abc'} start_response = mock.MagicMock() s.handle_request(environ, start_response) assert start_response.call_args[0][0] == '400 BAD REQUEST' def test_jsonp_index(self): s = server.Server() environ = {'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'EIO=4&j=233'} start_response = mock.MagicMock() r = s.handle_request(environ, start_response) assert start_response.call_args[0][0] == '200 OK' assert r[0].startswith(b'___eio[233]("') assert r[0].endswith(b'");') def test_connect(self): s = server.Server() environ = {'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'EIO=4'} start_response = mock.MagicMock() r = s.handle_request(environ, start_response) assert len(s.sockets) == 1 assert start_response.call_count == 1 assert start_response.call_args[0][0] == '200 OK' assert ( 'Content-Type', 'text/plain; charset=UTF-8', ) in start_response.call_args[0][1] assert len(r) == 1 packets = payload.Payload(encoded_payload=r[0].decode('utf-8')).packets assert len(packets) == 1 assert packets[0].packet_type == packet.OPEN assert 'upgrades' in packets[0].data assert packets[0].data['upgrades'] == ['websocket'] assert 'sid' in packets[0].data assert packets[0].data['pingTimeout'] == 20000 assert packets[0].data['pingInterval'] == 25000 assert packets[0].data['maxPayload'] == 1000000 def test_connect_no_upgrades(self): s = server.Server(allow_upgrades=False) environ = {'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'EIO=4'} start_response = mock.MagicMock() r = s.handle_request(environ, start_response) packets = payload.Payload(encoded_payload=r[0].decode('utf-8')).packets assert packets[0].data['upgrades'] == [] def test_connect_bad_eio_version(self): s = server.Server() environ = {'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'EIO=1'} start_response = mock.MagicMock() r = s.handle_request(environ, start_response) assert start_response.call_args[0][0], '400 BAD REQUEST' assert b'unsupported version' in r[0] def test_connect_custom_ping_times(self): s = server.Server(ping_timeout=123, ping_interval=456, max_http_buffer_size=12345678) environ = {'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'EIO=4'} start_response = mock.MagicMock() r = s.handle_request(environ, start_response) packets = payload.Payload(encoded_payload=r[0].decode('utf-8')).packets assert packets[0].data['pingTimeout'] == 123000 assert packets[0].data['pingInterval'] == 456000 assert packets[0].data['maxPayload'] == 12345678 @mock.patch( 'engineio.socket.Socket.poll', side_effect=exceptions.QueueEmpty ) def test_connect_bad_poll(self, poll): s = server.Server() environ = {'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'EIO=4'} start_response = mock.MagicMock() s.handle_request(environ, start_response) assert start_response.call_args[0][0] == '400 BAD REQUEST' @mock.patch( 'engineio.socket.Socket', return_value=mock.MagicMock(connected=False, closed=False), ) def test_connect_transport_websocket(self, Socket): s = server.Server() s.generate_id = mock.MagicMock(return_value='123') environ = { 'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'EIO=4&transport=websocket', 'HTTP_UPGRADE': 'websocket', } start_response = mock.MagicMock() # force socket to stay open, so that we can check it later Socket().closed = False s.handle_request(environ, start_response) assert s.sockets['123'].send.call_args[0][0].packet_type == packet.OPEN @mock.patch( 'engineio.socket.Socket', return_value=mock.MagicMock(connected=False, closed=False), ) def test_http_upgrade_case_insensitive(self, Socket): s = server.Server() s.generate_id = mock.MagicMock(return_value='123') environ = { 'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'EIO=4&transport=websocket', 'HTTP_UPGRADE': 'WebSocket', } start_response = mock.MagicMock() # force socket to stay open, so that we can check it later Socket().closed = False s.handle_request(environ, start_response) assert s.sockets['123'].send.call_args[0][0].packet_type == packet.OPEN @mock.patch( 'engineio.socket.Socket', return_value=mock.MagicMock(connected=False, closed=False), ) def test_connect_transport_websocket_closed(self, Socket): s = server.Server() s.generate_id = mock.MagicMock(return_value='123') environ = { 'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'EIO=4&transport=websocket', 'HTTP_UPGRADE': 'websocket', } start_response = mock.MagicMock() def mock_handle(environ, start_response): s.sockets['123'].closed = True Socket().handle_get_request = mock_handle s.handle_request(environ, start_response) assert '123' not in s.sockets def test_connect_transport_invalid(self): s = server.Server() environ = {'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'EIO=4&transport=foo'} start_response = mock.MagicMock() s.handle_request(environ, start_response) assert start_response.call_args[0][0] == '400 BAD REQUEST' def test_connect_transport_websocket_without_upgrade(self): s = server.Server() environ = { 'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'EIO=4&transport=websocket', } start_response = mock.MagicMock() s.handle_request(environ, start_response) assert start_response.call_args[0][0] == '400 BAD REQUEST' def test_connect_cors_headers(self): s = server.Server() environ = {'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'EIO=4'} start_response = mock.MagicMock() s.handle_request(environ, start_response) assert start_response.call_args[0][0] == '200 OK' headers = start_response.call_args[0][1] assert ('Access-Control-Allow-Credentials', 'true') in headers def test_connect_cors_allowed_origin(self): s = server.Server(cors_allowed_origins=['a', 'b']) environ = { 'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'EIO=4', 'HTTP_ORIGIN': 'b', } start_response = mock.MagicMock() s.handle_request(environ, start_response) assert start_response.call_args[0][0] == '200 OK' headers = start_response.call_args[0][1] assert ('Access-Control-Allow-Origin', 'b') in headers def test_connect_cors_allowed_origin_with_callable(self): def cors(origin): return origin == 'a' s = server.Server(cors_allowed_origins=cors) environ = { 'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'EIO=4', 'HTTP_ORIGIN': 'a', } start_response = mock.MagicMock() s.handle_request(environ, start_response) assert start_response.call_args[0][0] == '200 OK' headers = start_response.call_args[0][1] assert ('Access-Control-Allow-Origin', 'a') in headers environ['HTTP_ORIGIN'] = 'b' s.handle_request(environ, start_response) assert start_response.call_args[0][0] == '400 BAD REQUEST' def test_connect_cors_not_allowed_origin(self): s = server.Server(cors_allowed_origins=['a', 'b']) environ = { 'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'EIO=4', 'HTTP_ORIGIN': 'c', } start_response = mock.MagicMock() s.handle_request(environ, start_response) assert start_response.call_args[0][0] == '400 BAD REQUEST' headers = start_response.call_args[0][1] assert ('Access-Control-Allow-Origin', 'c') not in headers assert ('Access-Control-Allow-Origin', '*') not in headers def test_connect_str_cors_headers_all_origins(self): s = server.Server(cors_allowed_origins='*') environ = { 'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'EIO=4', 'HTTP_ORIGIN': 'foo', } start_response = mock.MagicMock() s.handle_request(environ, start_response) assert start_response.call_args[0][0] == '200 OK' headers = start_response.call_args[0][1] assert ('Access-Control-Allow-Origin', 'foo') in headers assert ('Access-Control-Allow-Credentials', 'true') in headers def test_connect_list_cors_headers_all_origins(self): s = server.Server(cors_allowed_origins=['*']) environ = { 'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'EIO=4', 'HTTP_ORIGIN': 'foo', } start_response = mock.MagicMock() s.handle_request(environ, start_response) assert start_response.call_args[0][0] == '200 OK' headers = start_response.call_args[0][1] assert ('Access-Control-Allow-Origin', 'foo') in headers assert ('Access-Control-Allow-Credentials', 'true') in headers def test_connect_cors_headers_one_origin(self): s = server.Server(cors_allowed_origins='a') environ = { 'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'EIO=4', 'HTTP_ORIGIN': 'a', } start_response = mock.MagicMock() s.handle_request(environ, start_response) assert start_response.call_args[0][0] == '200 OK' headers = start_response.call_args[0][1] assert ('Access-Control-Allow-Origin', 'a') in headers assert ('Access-Control-Allow-Credentials', 'true') in headers def test_connect_cors_headers_one_origin_not_allowed(self): s = server.Server(cors_allowed_origins='a') environ = { 'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'EIO=4', 'HTTP_ORIGIN': 'b', } start_response = mock.MagicMock() s.handle_request(environ, start_response) assert start_response.call_args[0][0] == '400 BAD REQUEST' headers = start_response.call_args[0][1] assert ('Access-Control-Allow-Origin', 'b') not in headers assert ('Access-Control-Allow-Origin', '*') not in headers def test_connect_cors_headers_default_origin(self): s = server.Server() environ = { 'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'EIO=4', 'wsgi.url_scheme': 'http', 'HTTP_HOST': 'foo', 'HTTP_ORIGIN': 'http://foo', } start_response = mock.MagicMock() s.handle_request(environ, start_response) assert start_response.call_args[0][0] == '200 OK' headers = start_response.call_args[0][1] assert ('Access-Control-Allow-Origin', 'http://foo') in headers def test_connect_cors_headers_default_origin_proxy_server(self): s = server.Server() environ = { 'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'EIO=4', 'wsgi.url_scheme': 'http', 'HTTP_HOST': 'foo', 'HTTP_ORIGIN': 'https://foo', 'HTTP_X_FORWARDED_PROTO': 'https, ftp', } start_response = mock.MagicMock() s.handle_request(environ, start_response) assert start_response.call_args[0][0] == '200 OK' headers = start_response.call_args[0][1] assert ('Access-Control-Allow-Origin', 'https://foo') in headers def test_connect_cors_headers_default_origin_proxy_server2(self): s = server.Server() environ = { 'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'EIO=4', 'wsgi.url_scheme': 'http', 'HTTP_HOST': 'foo', 'HTTP_ORIGIN': 'https://bar', 'HTTP_X_FORWARDED_PROTO': 'https, ftp', 'HTTP_X_FORWARDED_HOST': 'bar , baz', } start_response = mock.MagicMock() s.handle_request(environ, start_response) assert start_response.call_args[0][0] == '200 OK' headers = start_response.call_args[0][1] assert ('Access-Control-Allow-Origin', 'https://bar') in headers def test_connect_cors_no_credentials(self): s = server.Server(cors_credentials=False) environ = {'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'EIO=4'} start_response = mock.MagicMock() s.handle_request(environ, start_response) assert start_response.call_args[0][0] == '200 OK' headers = start_response.call_args[0][1] assert ('Access-Control-Allow-Credentials', 'true') not in headers def test_cors_options(self): s = server.Server() environ = {'REQUEST_METHOD': 'OPTIONS', 'QUERY_STRING': 'EIO=4'} start_response = mock.MagicMock() s.handle_request(environ, start_response) assert start_response.call_args[0][0] == '200 OK' headers = start_response.call_args[0][1] assert ( 'Access-Control-Allow-Methods', 'OPTIONS, GET, POST', ) in headers def test_cors_request_headers(self): s = server.Server() environ = { 'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'EIO=4', 'HTTP_ACCESS_CONTROL_REQUEST_HEADERS': 'Foo, Bar', } start_response = mock.MagicMock() s.handle_request(environ, start_response) assert start_response.call_args[0][0] == '200 OK' headers = start_response.call_args[0][1] assert ('Access-Control-Allow-Headers', 'Foo, Bar') in headers def test_connect_cors_disabled(self): s = server.Server(cors_allowed_origins=[]) environ = { 'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'EIO=4', 'HTTP_ORIGIN': 'http://foo', } start_response = mock.MagicMock() s.handle_request(environ, start_response) assert start_response.call_args[0][0] == '200 OK' headers = start_response.call_args[0][1] for header in headers: assert not header[0].startswith('Access-Control-') def test_connect_cors_default_no_origin(self): s = server.Server() environ = {'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'EIO=4'} start_response = mock.MagicMock() s.handle_request(environ, start_response) headers = start_response.call_args[0][1] for header in headers: assert header[0] != 'Access-Control-Allow-Origin' def test_connect_str_cors_all_no_origin(self): s = server.Server(cors_allowed_origins='*') environ = {'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'EIO=4'} start_response = mock.MagicMock() s.handle_request(environ, start_response) headers = start_response.call_args[0][1] for header in headers: assert header[0] != 'Access-Control-Allow-Origin' def test_connect_list_cors_all_no_origin(self): s = server.Server(cors_allowed_origins=['*']) environ = {'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'EIO=4'} start_response = mock.MagicMock() s.handle_request(environ, start_response) headers = start_response.call_args[0][1] for header in headers: assert header[0] != 'Access-Control-Allow-Origin' def test_connect_cors_disabled_no_origin(self): s = server.Server(cors_allowed_origins=[]) environ = {'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'EIO=4'} start_response = mock.MagicMock() s.handle_request(environ, start_response) headers = start_response.call_args[0][1] for header in headers: assert header[0] != 'Access-Control-Allow-Origin' def test_connect_event(self): s = server.Server() s.generate_id = mock.MagicMock(return_value='123') mock_event = mock.MagicMock(return_value=None) s.on('connect')(mock_event) environ = {'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'EIO=4'} start_response = mock.MagicMock() s.handle_request(environ, start_response) mock_event.assert_called_once_with('123', environ) assert len(s.sockets) == 1 def test_connect_event_rejects(self): s = server.Server() s.generate_id = mock.MagicMock(return_value='123') mock_event = mock.MagicMock(return_value=False) s.on('connect')(mock_event) environ = {'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'EIO=4'} start_response = mock.MagicMock() ret = s.handle_request(environ, start_response) assert len(s.sockets) == 0 assert start_response.call_args[0][0] == '401 UNAUTHORIZED' assert ret == [b'"Unauthorized"'] def test_connect_event_rejects_with_message(self): s = server.Server() s.generate_id = mock.MagicMock(return_value='123') mock_event = mock.MagicMock(return_value='not allowed') s.on('connect')(mock_event) environ = {'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'EIO=4'} start_response = mock.MagicMock() ret = s.handle_request(environ, start_response) assert len(s.sockets) == 0 assert start_response.call_args[0][0] == '401 UNAUTHORIZED' assert ret == [b'"not allowed"'] def test_method_not_found(self): s = server.Server() environ = {'REQUEST_METHOD': 'PUT', 'QUERY_STRING': 'EIO=4'} start_response = mock.MagicMock() s.handle_request(environ, start_response) assert start_response.call_args[0][0] == '405 METHOD NOT FOUND' def test_get_request_with_bad_sid(self): s = server.Server() environ = {'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'EIO=4&sid=foo'} start_response = mock.MagicMock() s.handle_request(environ, start_response) assert start_response.call_args[0][0] == '400 BAD REQUEST' def test_post_request_with_bad_sid(self): s = server.Server() environ = {'REQUEST_METHOD': 'POST', 'QUERY_STRING': 'EIO=4&sid=foo'} start_response = mock.MagicMock() s.handle_request(environ, start_response) assert start_response.call_args[0][0] == '400 BAD REQUEST' def test_post_request_after_upgrade(self): s = server.Server() mock_socket = self._get_mock_socket() mock_socket.connected = True mock_socket.upgraded = True mock_socket.handle_post_request = mock.MagicMock() s.sockets['foo'] = mock_socket environ = {'REQUEST_METHOD': 'POST', 'QUERY_STRING': 'EIO=4&sid=foo'} start_response = mock.MagicMock() s.handle_request(environ, start_response) assert start_response.call_args[0][0] == '400 BAD REQUEST' def test_send(self): s = server.Server() mock_socket = self._get_mock_socket() s.sockets['foo'] = mock_socket s.send('foo', 'hello') assert mock_socket.send.call_count == 1 assert mock_socket.send.call_args[0][0].packet_type == packet.MESSAGE assert mock_socket.send.call_args[0][0].data == 'hello' def test_send_unknown_socket(self): s = server.Server() # just ensure no exceptions are raised s.send('foo', 'hello') def test_get_request(self): s = server.Server() mock_socket = self._get_mock_socket() mock_socket.connected = True mock_socket.handle_get_request = mock.MagicMock( return_value=[packet.Packet(packet.MESSAGE, data='hello')] ) s.sockets['foo'] = mock_socket environ = {'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'EIO=4&sid=foo'} start_response = mock.MagicMock() r = s.handle_request(environ, start_response) assert start_response.call_args[0][0] == '200 OK' assert len(r) == 1 packets = payload.Payload(encoded_payload=r[0].decode('utf-8')).packets assert len(packets) == 1 assert packets[0].packet_type == packet.MESSAGE def test_get_request_custom_response(self): s = server.Server() mock_socket = self._get_mock_socket() mock_socket.connected = True mock_socket.handle_get_request = mock.MagicMock(side_effect=['resp']) s.sockets['foo'] = mock_socket environ = {'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'EIO=4&sid=foo'} start_response = mock.MagicMock() assert s.handle_request(environ, start_response) == 'resp' def test_get_request_closes_socket(self): s = server.Server() mock_socket = self._get_mock_socket() mock_socket.connected = True def mock_get_request(*args, **kwargs): mock_socket.closed = True return 'resp' mock_socket.handle_get_request = mock_get_request s.sockets['foo'] = mock_socket environ = {'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'EIO=4&sid=foo'} start_response = mock.MagicMock() assert s.handle_request(environ, start_response) == 'resp' assert 'foo' not in s.sockets def test_get_request_error(self): s = server.Server() mock_socket = self._get_mock_socket() mock_socket.connected = True mock_socket.handle_get_request = mock.MagicMock( side_effect=[exceptions.QueueEmpty] ) s.sockets['foo'] = mock_socket environ = {'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'EIO=4&sid=foo'} start_response = mock.MagicMock() s.handle_request(environ, start_response) assert start_response.call_args[0][0] == '400 BAD REQUEST' assert len(s.sockets) == 0 def test_get_request_bad_websocket_transport(self): s = server.Server() mock_socket = self._get_mock_socket() mock_socket.connected = True mock_socket.upgraded = False s.sockets['foo'] = mock_socket environ = {'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'EIO=4&transport=websocket&sid=foo'} start_response = mock.MagicMock() s.handle_request(environ, start_response) assert start_response.call_args[0][0] == '400 BAD REQUEST' def test_get_request_bad_polling_transport(self): s = server.Server() mock_socket = self._get_mock_socket() mock_socket.connected = True mock_socket.upgraded = True s.sockets['foo'] = mock_socket environ = {'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'EIO=4&transport=polling&sid=foo'} start_response = mock.MagicMock() s.handle_request(environ, start_response) assert start_response.call_args[0][0] == '400 BAD REQUEST' def test_post_request(self): s = server.Server() mock_socket = self._get_mock_socket() mock_socket.connected = True mock_socket.handle_post_request = mock.MagicMock() s.sockets['foo'] = mock_socket environ = {'REQUEST_METHOD': 'POST', 'QUERY_STRING': 'EIO=4&sid=foo'} start_response = mock.MagicMock() s.handle_request(environ, start_response) assert start_response.call_args[0][0] == '200 OK' def test_post_request_before_connecting(self): s = server.Server() mock_socket = self._get_mock_socket() mock_socket.handle_post_request = mock.MagicMock() s.sockets['foo'] = mock_socket environ = {'REQUEST_METHOD': 'POST', 'QUERY_STRING': 'EIO=4&sid=foo'} start_response = mock.MagicMock() s.handle_request(environ, start_response) assert start_response.call_args[0][0] == '400 BAD REQUEST' def test_post_request_error(self): s = server.Server() mock_socket = self._get_mock_socket() mock_socket.connected = True mock_socket.handle_post_request = mock.MagicMock( side_effect=[exceptions.EngineIOError] ) s.sockets['foo'] = mock_socket environ = {'REQUEST_METHOD': 'POST', 'QUERY_STRING': 'EIO=4&sid=foo'} start_response = mock.MagicMock() s.handle_request(environ, start_response) assert start_response.call_args[0][0] == '400 BAD REQUEST' assert 'foo' not in s.sockets @staticmethod def _gzip_decompress(b): bytesio = io.BytesIO(b) with gzip.GzipFile(fileobj=bytesio, mode='r') as gz: return gz.read() def test_gzip_compression(self): s = server.Server(compression_threshold=0) mock_socket = self._get_mock_socket() mock_socket.handle_get_request = mock.MagicMock( return_value=[packet.Packet(packet.MESSAGE, data='hello')] ) s.sockets['foo'] = mock_socket environ = { 'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'EIO=4&sid=foo', 'HTTP_ACCEPT_ENCODING': 'gzip,deflate', } start_response = mock.MagicMock() r = s.handle_request(environ, start_response) assert ('Content-Encoding', 'gzip') in start_response.call_args[0][1] self._gzip_decompress(r[0]) def test_deflate_compression(self): s = server.Server(compression_threshold=0) mock_socket = self._get_mock_socket() mock_socket.handle_get_request = mock.MagicMock( return_value=[packet.Packet(packet.MESSAGE, data='hello')] ) s.sockets['foo'] = mock_socket environ = { 'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'EIO=4&sid=foo', 'HTTP_ACCEPT_ENCODING': 'deflate;q=1,gzip', } start_response = mock.MagicMock() r = s.handle_request(environ, start_response) assert ('Content-Encoding', 'deflate') in start_response.call_args[0][ 1 ] zlib.decompress(r[0]) def test_gzip_compression_threshold(self): s = server.Server(compression_threshold=1000) mock_socket = self._get_mock_socket() mock_socket.handle_get_request = mock.MagicMock( return_value=[packet.Packet(packet.MESSAGE, data='hello')] ) s.sockets['foo'] = mock_socket environ = { 'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'EIO=4&sid=foo', 'HTTP_ACCEPT_ENCODING': 'gzip', } start_response = mock.MagicMock() r = s.handle_request(environ, start_response) for header, value in start_response.call_args[0][1]: assert header != 'Content-Encoding' with pytest.raises(IOError): self._gzip_decompress(r[0]) def test_compression_disabled(self): s = server.Server(http_compression=False, compression_threshold=0) mock_socket = self._get_mock_socket() mock_socket.handle_get_request = mock.MagicMock( return_value=[packet.Packet(packet.MESSAGE, data='hello')] ) s.sockets['foo'] = mock_socket environ = { 'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'EIO=4&sid=foo', 'HTTP_ACCEPT_ENCODING': 'gzip', } start_response = mock.MagicMock() r = s.handle_request(environ, start_response) for header, value in start_response.call_args[0][1]: assert header != 'Content-Encoding' with pytest.raises(IOError): self._gzip_decompress(r[0]) def test_compression_unknown(self): s = server.Server(compression_threshold=0) mock_socket = self._get_mock_socket() mock_socket.handle_get_request = mock.MagicMock( return_value=[packet.Packet(packet.MESSAGE, data='hello')] ) s.sockets['foo'] = mock_socket environ = { 'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'EIO=4&sid=foo', 'HTTP_ACCEPT_ENCODING': 'rar', } start_response = mock.MagicMock() r = s.handle_request(environ, start_response) for header, value in start_response.call_args[0][1]: assert header != 'Content-Encoding' with pytest.raises(IOError): self._gzip_decompress(r[0]) def test_compression_no_encoding(self): s = server.Server(compression_threshold=0) mock_socket = self._get_mock_socket() mock_socket.handle_get_request = mock.MagicMock( return_value=[packet.Packet(packet.MESSAGE, data='hello')] ) s.sockets['foo'] = mock_socket environ = { 'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'EIO=4&sid=foo', 'HTTP_ACCEPT_ENCODING': '', } start_response = mock.MagicMock() r = s.handle_request(environ, start_response) for header, value in start_response.call_args[0][1]: assert header != 'Content-Encoding' with pytest.raises(IOError): self._gzip_decompress(r[0]) def test_cookie(self): s = server.Server(cookie='sid') s.generate_id = mock.MagicMock(return_value='123') environ = {'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'EIO=4'} start_response = mock.MagicMock() s.handle_request(environ, start_response) assert ('Set-Cookie', 'sid=123; path=/; SameSite=Lax') \ in start_response.call_args[0][1] def test_cookie_dict(self): def get_path(): return '/a' s = server.Server(cookie={ 'name': 'test', 'path': get_path, 'SameSite': 'None', 'Secure': True, 'HttpOnly': True }) s.generate_id = mock.MagicMock(return_value='123') environ = {'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'EIO=4'} start_response = mock.MagicMock() s.handle_request(environ, start_response) assert ('Set-Cookie', 'test=123; path=/a; SameSite=None; Secure; ' 'HttpOnly') in start_response.call_args[0][1] def test_no_cookie(self): s = server.Server(cookie=None) s.generate_id = mock.MagicMock(return_value='123') environ = {'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'EIO=4'} start_response = mock.MagicMock() s.handle_request(environ, start_response) for header, value in start_response.call_args[0][1]: assert header != 'Set-Cookie' def test_logger(self): s = server.Server(logger=False) assert s.logger.getEffectiveLevel() == logging.ERROR s.logger.setLevel(logging.NOTSET) s = server.Server(logger=True) assert s.logger.getEffectiveLevel() == logging.INFO s.logger.setLevel(logging.WARNING) s = server.Server(logger=True) assert s.logger.getEffectiveLevel() == logging.WARNING s.logger.setLevel(logging.NOTSET) my_logger = logging.Logger('foo') s = server.Server(logger=my_logger) assert s.logger == my_logger def test_custom_json(self): # Warning: this test cannot run in parallel with other tests, as it # changes the JSON encoding/decoding functions class CustomJSON: @staticmethod def dumps(*args, **kwargs): return '*** encoded ***' @staticmethod def loads(*args, **kwargs): return '+++ decoded +++' server.Server(json=CustomJSON) pkt = packet.Packet(packet.MESSAGE, data={'foo': 'bar'}) assert pkt.encode() == '4*** encoded ***' pkt2 = packet.Packet(encoded_packet=pkt.encode()) assert pkt2.data == '+++ decoded +++' # restore the default JSON module packet.Packet.json = json def test_background_tasks(self): flag = {} def bg_task(): flag['task'] = True s = server.Server(async_mode='threading') task = s.start_background_task(bg_task) task.join() assert 'task' in flag assert flag['task'] def test_sleep(self): s = server.Server() t = time.time() s.sleep(0.1) assert time.time() - t > 0.1 def test_create_queue(self): s = server.Server() q = s.create_queue() empty = s.get_queue_empty_exception() with pytest.raises(empty): q.get(timeout=0.01) def test_create_event(self): s = server.Server() e = s.create_event() assert not e.is_set() e.set() assert e.is_set() def test_log_error_once(self): s = server.Server(logger=mock.MagicMock()) s._log_error_once('foo', 'foo-key') s._log_error_once('foo', 'foo-key') s.logger.error.assert_called_with( 'foo (further occurrences of this error will be logged with ' 'level INFO)') s.logger.info.assert_called_with('foo') def test_service_task_started(self): s = server.Server(async_mode='threading', monitor_clients=True) s._service_task = mock.MagicMock() environ = {'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'EIO=4'} start_response = mock.MagicMock() s.handle_request(environ, start_response) for _ in range(3): if s._service_task.call_count > 0: break time.sleep(0.05) s._service_task.assert_called_once_with() def test_shutdown(self): s = server.Server(async_mode='threading', monitor_clients=True) environ = {'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'EIO=4'} start_response = mock.MagicMock() s.handle_request(environ, start_response) assert s.service_task_handle is not None s.shutdown() assert s.service_task_handle is None def test_transports_invalid(self): with pytest.raises(ValueError): server.Server(transports='invalid') with pytest.raises(ValueError): server.Server(transports=['invalid', 'foo']) def test_transports_disallowed(self): s = server.Server(transports='websocket') environ = { 'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'transport=polling', } start_response = mock.MagicMock() s.handle_request(environ, start_response) assert start_response.call_args[0][0] == '400 BAD REQUEST' ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1791413117.6869013 python_engineio-4.15.1/tests/common/test_socket.py0000644000175000017500000004624715261545576021756 0ustar00miguelmiguelimport io import time from unittest import mock import pytest from engineio import exceptions from engineio import packet from engineio import payload from engineio import socket class TestSocket: def setup_method(self): self.bg_tasks = [] def _get_mock_server(self): mock_server = mock.Mock() mock_server.ping_timeout = 0.2 mock_server.ping_interval = 0.2 mock_server.ping_interval_grace_period = 0.001 mock_server.async_handlers = True mock_server.max_http_buffer_size = 128 mock_server.sleep = time.sleep try: import queue except ImportError: import Queue as queue import threading mock_server._async = { 'threading': threading.Thread, 'queue': queue.Queue, 'websocket': None, } def bg_task(target, *args, **kwargs): th = threading.Thread(target=target, args=args, kwargs=kwargs) self.bg_tasks.append(th) th.start() return th def create_queue(*args, **kwargs): return queue.Queue(*args, **kwargs) mock_server.start_background_task = bg_task mock_server.create_queue = create_queue mock_server.get_queue_empty_exception.return_value = queue.Empty return mock_server def _join_bg_tasks(self): for task in self.bg_tasks: task.join() def test_create(self): mock_server = self._get_mock_server() s = socket.Socket(mock_server, 'sid') assert s.server == mock_server assert s.sid == 'sid' assert not s.upgraded assert not s.closed assert hasattr(s.queue, 'get') assert hasattr(s.queue, 'put') assert hasattr(s.queue, 'task_done') assert hasattr(s.queue, 'join') def test_empty_poll(self): mock_server = self._get_mock_server() s = socket.Socket(mock_server, 'sid') with pytest.raises(exceptions.QueueEmpty): s.poll() def test_poll(self): mock_server = self._get_mock_server() s = socket.Socket(mock_server, 'sid') pkt1 = packet.Packet(packet.MESSAGE, data='hello') pkt2 = packet.Packet(packet.MESSAGE, data='bye') s.send(pkt1) s.send(pkt2) assert s.poll() == [pkt1, pkt2] def test_poll_none(self): mock_server = self._get_mock_server() s = socket.Socket(mock_server, 'sid') s.queue.put(None) assert s.poll() == [] def test_poll_none_after_packet(self): mock_server = self._get_mock_server() s = socket.Socket(mock_server, 'sid') pkt = packet.Packet(packet.MESSAGE, data='hello') s.send(pkt) s.queue.put(None) assert s.poll() == [pkt] assert s.poll() == [] def test_schedule_ping(self): mock_server = self._get_mock_server() mock_server.ping_interval = 0.01 s = socket.Socket(mock_server, 'sid') s.send = mock.MagicMock() s.schedule_ping() for _ in range(10): time.sleep(0.05) if s.last_ping is not None: break assert s.last_ping is not None assert s.send.call_args_list[0][0][0].encode() == '2' def test_schedule_ping_twice(self): mock_server = self._get_mock_server() mock_server.ping_interval = 0.1 s = socket.Socket(mock_server, 'sid') s.send = mock.MagicMock() s.schedule_ping() assert s.last_ping is None time.sleep(0.01) s.schedule_ping() time.sleep(0.2) assert s.last_ping is not None assert s.send.call_count == 1 def test_schedule_ping_closed_socket(self): mock_server = self._get_mock_server() mock_server.ping_interval = 0.01 s = socket.Socket(mock_server, 'sid') s.send = mock.MagicMock() s.closed = True s.schedule_ping() time.sleep(0.05) assert s.last_ping is None s.send.assert_not_called() def test_pong(self): mock_server = self._get_mock_server() s = socket.Socket(mock_server, 'sid') s.schedule_ping = mock.MagicMock() s.receive(packet.Packet(packet.PONG)) s.schedule_ping.assert_called_once_with() def test_message_async_handler(self): mock_server = self._get_mock_server() s = socket.Socket(mock_server, 'sid') s.receive(packet.Packet(packet.MESSAGE, data='foo')) mock_server._trigger_event.assert_called_once_with( 'message', 'sid', 'foo', run_async=True ) def test_message_sync_handler(self): mock_server = self._get_mock_server() mock_server.async_handlers = False s = socket.Socket(mock_server, 'sid') s.receive(packet.Packet(packet.MESSAGE, data='foo')) mock_server._trigger_event.assert_called_once_with( 'message', 'sid', 'foo', run_async=False ) def test_invalid_packet(self): mock_server = self._get_mock_server() s = socket.Socket(mock_server, 'sid') with pytest.raises(exceptions.UnknownPacketError): s.receive(packet.Packet(packet.OPEN)) def test_timeout(self): mock_server = self._get_mock_server() mock_server.ping_interval = 6 mock_server.ping_interval_grace_period = 2 s = socket.Socket(mock_server, 'sid') s.last_ping = time.time() - 9 s.close = mock.MagicMock() s.send('packet') s.close.assert_called_once_with(wait=False, abort=False, reason=mock_server.reason.PING_TIMEOUT) def test_polling_read(self): mock_server = self._get_mock_server() s = socket.Socket(mock_server, 'foo') pkt1 = packet.Packet(packet.MESSAGE, data='hello') pkt2 = packet.Packet(packet.MESSAGE, data='bye') s.send(pkt1) s.send(pkt2) environ = {'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'sid=foo'} start_response = mock.MagicMock() packets = s.handle_get_request(environ, start_response) assert packets == [pkt1, pkt2] def test_polling_read_error(self): mock_server = self._get_mock_server() s = socket.Socket(mock_server, 'foo') environ = {'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'sid=foo'} start_response = mock.MagicMock() with pytest.raises(exceptions.QueueEmpty): s.handle_get_request(environ, start_response) def test_polling_write(self): mock_server = self._get_mock_server() mock_server.max_http_buffer_size = 1000 pkt1 = packet.Packet(packet.MESSAGE, data='hello') pkt2 = packet.Packet(packet.MESSAGE, data='bye') p = payload.Payload(packets=[pkt1, pkt2]).encode().encode('utf-8') s = socket.Socket(mock_server, 'foo') s.receive = mock.MagicMock() environ = { 'REQUEST_METHOD': 'POST', 'QUERY_STRING': 'sid=foo', 'CONTENT_LENGTH': len(p), 'wsgi.input': io.BytesIO(p), } s.handle_post_request(environ) assert s.receive.call_count == 2 def test_polling_write_too_large(self): mock_server = self._get_mock_server() pkt1 = packet.Packet(packet.MESSAGE, data='hello') pkt2 = packet.Packet(packet.MESSAGE, data='bye') p = payload.Payload(packets=[pkt1, pkt2]).encode().encode('utf-8') mock_server.max_http_buffer_size = len(p) - 1 s = socket.Socket(mock_server, 'foo') s.receive = mock.MagicMock() environ = { 'REQUEST_METHOD': 'POST', 'QUERY_STRING': 'sid=foo', 'CONTENT_LENGTH': len(p), 'wsgi.input': io.BytesIO(p), } with pytest.raises(exceptions.ContentTooLongError): s.handle_post_request(environ) def test_upgrade_handshake(self): mock_server = self._get_mock_server() s = socket.Socket(mock_server, 'foo') s._upgrade_websocket = mock.MagicMock() environ = { 'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'sid=foo', 'HTTP_CONNECTION': 'Foo,Upgrade,Bar', 'HTTP_UPGRADE': 'websocket', } start_response = mock.MagicMock() s.handle_get_request(environ, start_response) s._upgrade_websocket.assert_called_once_with(environ, start_response) def test_upgrade(self): mock_server = self._get_mock_server() mock_server._async['websocket'] = mock.MagicMock() mock_ws = mock.MagicMock() mock_server._async['websocket'].return_value = mock_ws s = socket.Socket(mock_server, 'sid') s.connected = True environ = "foo" start_response = "bar" s._upgrade_websocket(environ, start_response) mock_server._async['websocket'].assert_called_once_with( s._websocket_handler, mock_server ) mock_ws.assert_called_once_with(environ, start_response) def test_upgrade_twice(self): mock_server = self._get_mock_server() mock_server._async['websocket'] = mock.MagicMock() s = socket.Socket(mock_server, 'sid') s.connected = True s.upgraded = True environ = "foo" start_response = "bar" with pytest.raises(IOError): s._upgrade_websocket(environ, start_response) def test_upgrade_packet(self): mock_server = self._get_mock_server() s = socket.Socket(mock_server, 'sid') s.connected = True s.receive(packet.Packet(packet.UPGRADE)) r = s.poll() assert len(r) == 1 assert r[0].encode() == packet.Packet(packet.NOOP).encode() def test_upgrade_no_probe(self): mock_server = self._get_mock_server() s = socket.Socket(mock_server, 'sid') s.connected = True ws = mock.MagicMock() ws.wait.return_value = packet.Packet(packet.NOOP).encode() s._websocket_handler(ws) assert not s.upgraded def test_upgrade_no_upgrade_packet(self): mock_server = self._get_mock_server() s = socket.Socket(mock_server, 'sid') s.connected = True s.queue.join = mock.MagicMock(return_value=None) ws = mock.MagicMock() probe = 'probe' ws.wait.side_effect = [ packet.Packet(packet.PING, data=probe).encode(), packet.Packet(packet.NOOP).encode(), ] s._websocket_handler(ws) ws.send.assert_called_once_with( packet.Packet(packet.PONG, data=probe).encode() ) assert s.queue.get().packet_type == packet.NOOP assert not s.upgraded def test_close_packet(self): mock_server = self._get_mock_server() s = socket.Socket(mock_server, 'sid') s.connected = True s.close = mock.MagicMock() s.receive(packet.Packet(packet.CLOSE)) s.close.assert_called_once_with( wait=False, abort=True, reason=mock_server.reason.CLIENT_DISCONNECT) def test_invalid_packet_type(self): mock_server = self._get_mock_server() s = socket.Socket(mock_server, 'sid') pkt = packet.Packet(packet_type=99) with pytest.raises(exceptions.UnknownPacketError): s.receive(pkt) def test_upgrade_not_supported(self): mock_server = self._get_mock_server() mock_server._async['websocket'] = None s = socket.Socket(mock_server, 'sid') s.connected = True environ = "foo" start_response = "bar" s._upgrade_websocket(environ, start_response) mock_server._bad_request.assert_called_once_with() def test_websocket_read_write(self): mock_server = self._get_mock_server() s = socket.Socket(mock_server, 'sid') s.connected = False s.queue.join = mock.MagicMock(return_value=None) foo = 'foo' bar = 'bar' s.poll = mock.MagicMock( side_effect=[ [packet.Packet(packet.MESSAGE, data=bar)], exceptions.QueueEmpty, ] ) ws = mock.MagicMock() ws.wait.side_effect = [ packet.Packet(packet.MESSAGE, data=foo).encode(), None, ] s._websocket_handler(ws) self._join_bg_tasks() assert s.connected assert s.upgraded assert mock_server._trigger_event.call_count == 2 mock_server._trigger_event.assert_has_calls( [ mock.call('message', 'sid', 'foo', run_async=True), mock.call('disconnect', 'sid', mock_server.reason.TRANSPORT_CLOSE, run_async=False) ] ) ws.send.assert_called_with('4bar') def test_websocket_upgrade_read_write(self): mock_server = self._get_mock_server() s = socket.Socket(mock_server, 'sid') s.connected = True s.queue.join = mock.MagicMock(return_value=None) foo = 'foo' bar = 'bar' probe = 'probe' s.poll = mock.MagicMock( side_effect=[ [packet.Packet(packet.MESSAGE, data=bar)], exceptions.QueueEmpty, ] ) ws = mock.MagicMock() ws.wait.side_effect = [ packet.Packet(packet.PING, data=probe).encode(), packet.Packet(packet.UPGRADE).encode(), packet.Packet(packet.MESSAGE, data=foo).encode(), None, ] s._websocket_handler(ws) self._join_bg_tasks() assert s.upgraded assert mock_server._trigger_event.call_count == 2 mock_server._trigger_event.assert_has_calls( [ mock.call('message', 'sid', 'foo', run_async=True), mock.call('disconnect', 'sid', mock_server.reason.TRANSPORT_CLOSE, run_async=False) ] ) ws.send.assert_called_with('4bar') def test_websocket_upgrade_with_payload(self): mock_server = self._get_mock_server() s = socket.Socket(mock_server, 'sid') s.connected = True s.queue.join = mock.MagicMock(return_value=None) probe = 'probe' ws = mock.MagicMock() ws.wait.side_effect = [ packet.Packet(packet.PING, data=probe).encode(), packet.Packet(packet.UPGRADE, data='2').encode(), ] s._websocket_handler(ws) self._join_bg_tasks() assert s.upgraded def test_websocket_upgrade_with_backlog(self): mock_server = self._get_mock_server() s = socket.Socket(mock_server, 'sid') s.connected = True s.queue.join = mock.MagicMock(return_value=None) probe = 'probe' foo = 'foo' ws = mock.MagicMock() ws.wait.side_effect = [ packet.Packet(packet.PING, data=probe).encode(), packet.Packet(packet.UPGRADE, data='2').encode(), ] s.upgrading = True s.send(packet.Packet(packet.MESSAGE, data=foo)) environ = {'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'sid=sid'} start_response = mock.MagicMock() packets = s.handle_get_request(environ, start_response) assert len(packets) == 1 assert packets[0].encode() == '6' packets = s.poll() assert len(packets) == 1 assert packets[0].encode() == '4foo' s._websocket_handler(ws) self._join_bg_tasks() assert s.upgraded assert not s.upgrading packets = s.handle_get_request(environ, start_response) assert len(packets) == 1 assert packets[0].encode() == '6' def test_websocket_read_write_wait_fail(self): mock_server = self._get_mock_server() s = socket.Socket(mock_server, 'sid') s.connected = False s.queue.join = mock.MagicMock(return_value=None) foo = 'foo' bar = 'bar' s.poll = mock.MagicMock( side_effect=[ [packet.Packet(packet.MESSAGE, data=bar)], [packet.Packet(packet.MESSAGE, data=bar)], exceptions.QueueEmpty, ] ) ws = mock.MagicMock() ws.wait.side_effect = [ packet.Packet(packet.MESSAGE, data=foo).encode(), RuntimeError, ] ws.send.side_effect = [None, RuntimeError] s._websocket_handler(ws) self._join_bg_tasks() assert s.closed def test_websocket_upgrade_with_large_packet(self): mock_server = self._get_mock_server() s = socket.Socket(mock_server, 'sid') s.connected = True s.queue.join = mock.MagicMock(return_value=None) probe = 'probe' ws = mock.MagicMock() ws.wait.side_effect = [ packet.Packet(packet.PING, data=probe).encode(), packet.Packet(packet.UPGRADE, data='2' * 128).encode(), ] with pytest.raises(ValueError): s._websocket_handler(ws) self._join_bg_tasks() assert not s.upgraded def test_websocket_ignore_invalid_packet(self): mock_server = self._get_mock_server() s = socket.Socket(mock_server, 'sid') s.connected = False s.queue.join = mock.MagicMock(return_value=None) foo = 'foo' bar = 'bar' s.poll = mock.MagicMock( side_effect=[ [packet.Packet(packet.MESSAGE, data=bar)], exceptions.QueueEmpty, ] ) ws = mock.MagicMock() ws.wait.side_effect = [ packet.Packet(packet.OPEN).encode(), packet.Packet(packet.MESSAGE, data=foo).encode(), None, ] s._websocket_handler(ws) self._join_bg_tasks() assert s.connected assert mock_server._trigger_event.call_count == 2 mock_server._trigger_event.assert_has_calls( [ mock.call('message', 'sid', foo, run_async=True), mock.call('disconnect', 'sid', mock_server.reason.TRANSPORT_CLOSE, run_async=False) ] ) ws.send.assert_called_with('4bar') def test_send_after_close(self): mock_server = self._get_mock_server() s = socket.Socket(mock_server, 'sid') s.close(wait=False) with pytest.raises(exceptions.SocketIsClosedError): s.send(packet.Packet(packet.NOOP)) def test_close_after_close(self): mock_server = self._get_mock_server() s = socket.Socket(mock_server, 'sid') s.close(wait=False) assert s.closed assert mock_server._trigger_event.call_count == 1 mock_server._trigger_event.assert_called_once_with( 'disconnect', 'sid', mock_server.reason.SERVER_DISCONNECT, run_async=False ) s.close() assert mock_server._trigger_event.call_count == 1 def test_close_and_wait(self): mock_server = self._get_mock_server() s = socket.Socket(mock_server, 'sid') s.queue = mock.MagicMock() s.close(wait=True) s.queue.join.assert_called_once_with() def test_close_without_wait(self): mock_server = self._get_mock_server() s = socket.Socket(mock_server, 'sid') s.queue = mock.MagicMock() s.close(wait=False) assert s.queue.join.call_count == 0 ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1793228809.037347 python_engineio-4.15.1/tests/performance/0000755000175000017500000000000015270500011020020 5ustar00miguelmiguel././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1791413117.6869013 python_engineio-4.15.1/tests/performance/README.md0000644000175000017500000000016315261545576021330 0ustar00miguelmiguelPerformance =========== This directory contains several scripts and tools to test the performance of the project. ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1791413117.6871285 python_engineio-4.15.1/tests/performance/binary_b64_packet.py0000644000175000017500000000064715261545576023720 0ustar00miguelmiguelimport time from engineio import packet def test(): p = packet.Packet(packet.MESSAGE, b'hello world') start = time.time() count = 0 while True: p = packet.Packet(encoded_packet=p.encode(b64=True)) count += 1 if time.time() - start >= 5: break return count if __name__ == '__main__': count = test() print('binary_b64_packet:', count, 'packets processed.') ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1791413117.6871285 python_engineio-4.15.1/tests/performance/binary_packet.py0000644000175000017500000000063315261545576023240 0ustar00miguelmiguelimport time from engineio import packet def test(): p = packet.Packet(packet.MESSAGE, b'hello world') start = time.time() count = 0 while True: p = packet.Packet(encoded_packet=p.encode()) count += 1 if time.time() - start >= 5: break return count if __name__ == '__main__': count = test() print('binary_packet:', count, 'packets processed.') ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1791413117.6871836 python_engineio-4.15.1/tests/performance/json_packet.py0000644000175000017500000000063515261545576022727 0ustar00miguelmiguelimport time from engineio import packet def test(): p = packet.Packet(packet.MESSAGE, {'hello': 'world'}) start = time.time() count = 0 while True: p = packet.Packet(encoded_packet=p.encode()) count += 1 if time.time() - start >= 5: break return count if __name__ == '__main__': count = test() print('json_packet:', count, 'packets processed.') ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1791413117.6872044 python_engineio-4.15.1/tests/performance/payload.py0000644000175000017500000000071315261545576022055 0ustar00miguelmiguelimport time from engineio import packet, payload def test(): p = payload.Payload( packets=[packet.Packet(packet.MESSAGE, b'hello world')] * 10) start = time.time() count = 0 while True: p = payload.Payload(encoded_payload=p.encode()) count += 1 if time.time() - start >= 5: break return count if __name__ == '__main__': count = test() print('payload:', count, 'payloads processed.') ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1791413117.6872277 python_engineio-4.15.1/tests/performance/run.sh0000755000175000017500000000022715261545576021215 0ustar00miguelmiguel#!/bin/bash python text_packet.py python binary_packet.py python binary_b64_packet.py python json_packet.py python payload.py python server_receive.py ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1791413117.6872492 python_engineio-4.15.1/tests/performance/server_receive.py0000644000175000017500000000170215261545576023433 0ustar00miguelmiguelimport io import sys import time import engineio def test(eio_version, payload): s = engineio.Server() start = time.time() count = 0 s.handle_request({ 'REQUEST_METHOD': 'GET', 'QUERY_STRING': eio_version, }, lambda s, h: None) sid = list(s.sockets.keys())[0] while True: environ = { 'REQUEST_METHOD': 'POST', 'QUERY_STRING': eio_version + '&sid=' + sid, 'CONTENT_LENGTH': '6', 'wsgi.input': io.BytesIO(payload) } s.handle_request(environ, lambda s, h: None) count += 1 if time.time() - start >= 5: break return count if __name__ == '__main__': eio_version = 'EIO=4' payload = b'4hello' if len(sys.argv) > 1 and sys.argv[1] == '3': eio_version = 'EIO=3' payload = b'\x00\x06\xff4hello' count = test(eio_version, payload) print('server_receive:', count, 'packets received.') ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1791413117.6872873 python_engineio-4.15.1/tests/performance/text_packet.py0000644000175000017500000000063015261545576022735 0ustar00miguelmiguelimport time from engineio import packet def test(): p = packet.Packet(packet.MESSAGE, 'hello world') start = time.time() count = 0 while True: p = packet.Packet(encoded_packet=p.encode()) count += 1 if time.time() - start >= 5: break return count if __name__ == '__main__': count = test() print('text_packet:', count, 'packets processed.') ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1791413117.6872873 python_engineio-4.15.1/tox.ini0000644000175000017500000000150015261545576015715 0ustar00miguelmiguel[testenv] commands= pip install -e . pytest -p no:logging --cov=engineio --cov-branch --cov-report=term-missing --cov-report=xml deps= pytest pytest-cov pytest-asyncio aiohttp tornado requests websocket-client [testenv:flake8] deps= flake8 commands= flake8 --exclude=".*" --ignore=W503,E402,E722 src/engineio tests examples [testenv:zizmor] deps= zizmor commands= zizmor --offline .forgejo/workflows/tests.yml [testenv:docs] changedir=docs deps= sphinx furo allowlist_externals= make commands= make html [testenv:lint] deps= flake8 zizmor sphinx furo allowlist_externals= make commands= flake8 --exclude=".*" --ignore=W503,E402,E722 src/engineio tests examples zizmor --offline .forgejo/workflows/tests.yml make -C docs html