python-engineio 4.13.5__tar.gz → 4.15.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- python_engineio-4.15.0/PKG-INFO +61 -0
- python_engineio-4.15.0/README.md +29 -0
- {python_engineio-4.13.5 → python_engineio-4.15.0}/pyproject.toml +8 -4
- {python_engineio-4.13.5 → python_engineio-4.15.0}/src/engineio/async_client.py +38 -14
- {python_engineio-4.13.5 → python_engineio-4.15.0}/src/engineio/async_server.py +26 -13
- {python_engineio-4.13.5 → python_engineio-4.15.0}/src/engineio/base_client.py +8 -2
- {python_engineio-4.13.5 → python_engineio-4.15.0}/src/engineio/client.py +26 -3
- {python_engineio-4.13.5 → python_engineio-4.15.0}/src/engineio/server.py +26 -13
- {python_engineio-4.13.5 → python_engineio-4.15.0}/src/engineio/static_files.py +2 -0
- python_engineio-4.15.0/src/python_engineio.egg-info/PKG-INFO +61 -0
- {python_engineio-4.13.5 → python_engineio-4.15.0}/src/python_engineio.egg-info/SOURCES.txt +1 -0
- python_engineio-4.15.0/tests/async/.test_client.py.swp +0 -0
- {python_engineio-4.13.5 → python_engineio-4.15.0}/tests/async/test_asgi.py +2 -0
- {python_engineio-4.13.5 → python_engineio-4.15.0}/tests/async/test_client.py +209 -67
- {python_engineio-4.13.5 → python_engineio-4.15.0}/tests/async/test_server.py +12 -0
- {python_engineio-4.13.5 → python_engineio-4.15.0}/tests/common/test_client.py +86 -12
- {python_engineio-4.13.5 → python_engineio-4.15.0}/tests/common/test_middleware.py +2 -0
- {python_engineio-4.13.5 → python_engineio-4.15.0}/tests/common/test_server.py +11 -0
- {python_engineio-4.13.5 → python_engineio-4.15.0}/tox.ini +19 -0
- python_engineio-4.13.5/PKG-INFO +0 -42
- python_engineio-4.13.5/README.md +0 -14
- python_engineio-4.13.5/src/python_engineio.egg-info/PKG-INFO +0 -42
- {python_engineio-4.13.5 → python_engineio-4.15.0}/LICENSE +0 -0
- {python_engineio-4.13.5 → python_engineio-4.15.0}/MANIFEST.in +0 -0
- {python_engineio-4.13.5 → python_engineio-4.15.0}/docs/_static/README.md +0 -0
- {python_engineio-4.13.5 → python_engineio-4.15.0}/setup.cfg +0 -0
- {python_engineio-4.13.5 → python_engineio-4.15.0}/src/engineio/__init__.py +0 -0
- {python_engineio-4.13.5 → python_engineio-4.15.0}/src/engineio/async_drivers/__init__.py +0 -0
- {python_engineio-4.13.5 → python_engineio-4.15.0}/src/engineio/async_drivers/_websocket_wsgi.py +0 -0
- {python_engineio-4.13.5 → python_engineio-4.15.0}/src/engineio/async_drivers/aiohttp.py +0 -0
- {python_engineio-4.13.5 → python_engineio-4.15.0}/src/engineio/async_drivers/asgi.py +0 -0
- {python_engineio-4.13.5 → python_engineio-4.15.0}/src/engineio/async_drivers/eventlet.py +0 -0
- {python_engineio-4.13.5 → python_engineio-4.15.0}/src/engineio/async_drivers/gevent.py +0 -0
- {python_engineio-4.13.5 → python_engineio-4.15.0}/src/engineio/async_drivers/gevent_uwsgi.py +0 -0
- {python_engineio-4.13.5 → python_engineio-4.15.0}/src/engineio/async_drivers/sanic.py +0 -0
- {python_engineio-4.13.5 → python_engineio-4.15.0}/src/engineio/async_drivers/threading.py +0 -0
- {python_engineio-4.13.5 → python_engineio-4.15.0}/src/engineio/async_drivers/tornado.py +0 -0
- {python_engineio-4.13.5 → python_engineio-4.15.0}/src/engineio/async_socket.py +0 -0
- {python_engineio-4.13.5 → python_engineio-4.15.0}/src/engineio/base_server.py +0 -0
- {python_engineio-4.13.5 → python_engineio-4.15.0}/src/engineio/base_socket.py +0 -0
- {python_engineio-4.13.5 → python_engineio-4.15.0}/src/engineio/exceptions.py +0 -0
- {python_engineio-4.13.5 → python_engineio-4.15.0}/src/engineio/json.py +0 -0
- {python_engineio-4.13.5 → python_engineio-4.15.0}/src/engineio/middleware.py +0 -0
- {python_engineio-4.13.5 → python_engineio-4.15.0}/src/engineio/packet.py +0 -0
- {python_engineio-4.13.5 → python_engineio-4.15.0}/src/engineio/payload.py +0 -0
- {python_engineio-4.13.5 → python_engineio-4.15.0}/src/engineio/socket.py +0 -0
- {python_engineio-4.13.5 → python_engineio-4.15.0}/src/python_engineio.egg-info/dependency_links.txt +0 -0
- {python_engineio-4.13.5 → python_engineio-4.15.0}/src/python_engineio.egg-info/not-zip-safe +0 -0
- {python_engineio-4.13.5 → python_engineio-4.15.0}/src/python_engineio.egg-info/requires.txt +0 -0
- {python_engineio-4.13.5 → python_engineio-4.15.0}/src/python_engineio.egg-info/top_level.txt +0 -0
- {python_engineio-4.13.5 → python_engineio-4.15.0}/tests/async/__init__.py +0 -0
- {python_engineio-4.13.5 → python_engineio-4.15.0}/tests/async/index.html +0 -0
- {python_engineio-4.13.5 → python_engineio-4.15.0}/tests/async/test_aiohttp.py +0 -0
- {python_engineio-4.13.5 → python_engineio-4.15.0}/tests/async/test_sanic.py +0 -0
- {python_engineio-4.13.5 → python_engineio-4.15.0}/tests/async/test_socket.py +0 -0
- {python_engineio-4.13.5 → python_engineio-4.15.0}/tests/async/test_tornado.py +0 -0
- {python_engineio-4.13.5 → python_engineio-4.15.0}/tests/common/__init__.py +0 -0
- {python_engineio-4.13.5 → python_engineio-4.15.0}/tests/common/index.html +0 -0
- {python_engineio-4.13.5 → python_engineio-4.15.0}/tests/common/test_packet.py +0 -0
- {python_engineio-4.13.5 → python_engineio-4.15.0}/tests/common/test_payload.py +0 -0
- {python_engineio-4.13.5 → python_engineio-4.15.0}/tests/common/test_socket.py +0 -0
- {python_engineio-4.13.5 → python_engineio-4.15.0}/tests/performance/README.md +0 -0
- {python_engineio-4.13.5 → python_engineio-4.15.0}/tests/performance/binary_b64_packet.py +0 -0
- {python_engineio-4.13.5 → python_engineio-4.15.0}/tests/performance/binary_packet.py +0 -0
- {python_engineio-4.13.5 → python_engineio-4.15.0}/tests/performance/json_packet.py +0 -0
- {python_engineio-4.13.5 → python_engineio-4.15.0}/tests/performance/payload.py +0 -0
- {python_engineio-4.13.5 → python_engineio-4.15.0}/tests/performance/run.sh +0 -0
- {python_engineio-4.13.5 → python_engineio-4.15.0}/tests/performance/server_receive.py +0 -0
- {python_engineio-4.13.5 → python_engineio-4.15.0}/tests/performance/text_packet.py +0 -0
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: python-engineio
|
|
3
|
+
Version: 4.15.0
|
|
4
|
+
Summary: Engine.IO server and client for Python
|
|
5
|
+
Author-email: Miguel Grinberg <miguel.grinberg@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://code.miguelgrinberg.com/miguelgrinberg/python-engineio
|
|
8
|
+
Project-URL: Documentation, https://python-engineio.readthedocs.io/
|
|
9
|
+
Project-URL: Source Code, https://code.miguelgrinberg.com/miguelgrinberg/python-engineio
|
|
10
|
+
Project-URL: Issue Tracker, https://code.miguelgrinberg.com/miguelgrinberg/python-engineio/issues
|
|
11
|
+
Project-URL: Changelog, https://code.miguelgrinberg.com/miguelgrinberg/python-engineio/src/branch/main/CHANGES.md
|
|
12
|
+
Project-URL: Funding, https://code.miguelgrinberg.com/miguelgrinberg/python-engineio#sponsor-this-project
|
|
13
|
+
Classifier: Environment :: Web Environment
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Requires-Python: >=3.8
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
License-File: LICENSE
|
|
20
|
+
Requires-Dist: simple-websocket>=0.10.0
|
|
21
|
+
Provides-Extra: client
|
|
22
|
+
Requires-Dist: requests>=2.21.0; extra == "client"
|
|
23
|
+
Requires-Dist: websocket-client>=0.54.0; extra == "client"
|
|
24
|
+
Provides-Extra: asyncio-client
|
|
25
|
+
Requires-Dist: aiohttp>=3.11; extra == "asyncio-client"
|
|
26
|
+
Provides-Extra: dev
|
|
27
|
+
Requires-Dist: tox; extra == "dev"
|
|
28
|
+
Provides-Extra: docs
|
|
29
|
+
Requires-Dist: sphinx; extra == "docs"
|
|
30
|
+
Requires-Dist: furo; extra == "docs"
|
|
31
|
+
Dynamic: license-file
|
|
32
|
+
|
|
33
|
+
python-engineio
|
|
34
|
+
===============
|
|
35
|
+
|
|
36
|
+
[](https://code.miguelgrinberg.com/miguelgrinberg/python-engineio/actions)
|
|
37
|
+
|
|
38
|
+
Python implementation of the `Engine.IO` realtime client and server.
|
|
39
|
+
|
|
40
|
+
Resources
|
|
41
|
+
---------
|
|
42
|
+
|
|
43
|
+
- [git](https://code.miguelgrinberg.com/miguelgrinberg/python-engineio)
|
|
44
|
+
- [Change Log](https://code.miguelgrinberg.com/miguelgrinberg/python-engineio/src/branch/main/CHANGES.md)
|
|
45
|
+
- [Documentation](https://python-engineio.readthedocs.io/)
|
|
46
|
+
- [PyPI](https://pypi.python.org/pypi/python-engineio)
|
|
47
|
+
- [Contributor's guide](CONTRIBUTING.md)
|
|
48
|
+
- [Security policy](SECURITY.md)
|
|
49
|
+
|
|
50
|
+
Sponsor this project
|
|
51
|
+
--------------------
|
|
52
|
+
|
|
53
|
+
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:
|
|
54
|
+
|
|
55
|
+
- [Github Sponsors](https://github.com/sponsors/miguelgrinberg)
|
|
56
|
+
- [Patreon](https://patreon.com/miguelgrinberg)
|
|
57
|
+
- [Buy me a Coffee](https://buymeacoffee.com/miguelgrinberg)
|
|
58
|
+
- [thanks.dev](https://thanks.dev/u/gh/miguelgrinberg)
|
|
59
|
+
- [PayPal](https://paypal.me/miguelgrinberg)
|
|
60
|
+
|
|
61
|
+
Thank you!
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
python-engineio
|
|
2
|
+
===============
|
|
3
|
+
|
|
4
|
+
[](https://code.miguelgrinberg.com/miguelgrinberg/python-engineio/actions)
|
|
5
|
+
|
|
6
|
+
Python implementation of the `Engine.IO` realtime client and server.
|
|
7
|
+
|
|
8
|
+
Resources
|
|
9
|
+
---------
|
|
10
|
+
|
|
11
|
+
- [git](https://code.miguelgrinberg.com/miguelgrinberg/python-engineio)
|
|
12
|
+
- [Change Log](https://code.miguelgrinberg.com/miguelgrinberg/python-engineio/src/branch/main/CHANGES.md)
|
|
13
|
+
- [Documentation](https://python-engineio.readthedocs.io/)
|
|
14
|
+
- [PyPI](https://pypi.python.org/pypi/python-engineio)
|
|
15
|
+
- [Contributor's guide](CONTRIBUTING.md)
|
|
16
|
+
- [Security policy](SECURITY.md)
|
|
17
|
+
|
|
18
|
+
Sponsor this project
|
|
19
|
+
--------------------
|
|
20
|
+
|
|
21
|
+
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:
|
|
22
|
+
|
|
23
|
+
- [Github Sponsors](https://github.com/sponsors/miguelgrinberg)
|
|
24
|
+
- [Patreon](https://patreon.com/miguelgrinberg)
|
|
25
|
+
- [Buy me a Coffee](https://buymeacoffee.com/miguelgrinberg)
|
|
26
|
+
- [thanks.dev](https://thanks.dev/u/gh/miguelgrinberg)
|
|
27
|
+
- [PayPal](https://paypal.me/miguelgrinberg)
|
|
28
|
+
|
|
29
|
+
Thank you!
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "python-engineio"
|
|
3
|
-
version = "4.
|
|
4
|
-
license = {text = "MIT"}
|
|
3
|
+
version = "4.15.0"
|
|
5
4
|
authors = [{name = "Miguel Grinberg", email = "miguel.grinberg@gmail.com"}]
|
|
6
5
|
description = "Engine.IO server and client for Python"
|
|
7
6
|
classifiers = [
|
|
@@ -10,6 +9,7 @@ classifiers = [
|
|
|
10
9
|
"Programming Language :: Python :: 3",
|
|
11
10
|
"Operating System :: OS Independent",
|
|
12
11
|
]
|
|
12
|
+
license = "MIT"
|
|
13
13
|
requires-python = ">=3.8"
|
|
14
14
|
dependencies = ["simple-websocket >= 0.10.0"]
|
|
15
15
|
|
|
@@ -18,8 +18,12 @@ file = "README.md"
|
|
|
18
18
|
content-type = "text/markdown"
|
|
19
19
|
|
|
20
20
|
[project.urls]
|
|
21
|
-
Homepage = "https://
|
|
22
|
-
|
|
21
|
+
Homepage = "https://code.miguelgrinberg.com/miguelgrinberg/python-engineio"
|
|
22
|
+
Documentation = "https://python-engineio.readthedocs.io/"
|
|
23
|
+
"Source Code" = "https://code.miguelgrinberg.com/miguelgrinberg/python-engineio"
|
|
24
|
+
"Issue Tracker" = "https://code.miguelgrinberg.com/miguelgrinberg/python-engineio/issues"
|
|
25
|
+
Changelog = "https://code.miguelgrinberg.com/miguelgrinberg/python-engineio/src/branch/main/CHANGES.md"
|
|
26
|
+
Funding = "https://code.miguelgrinberg.com/miguelgrinberg/python-engineio#sponsor-this-project"
|
|
23
27
|
|
|
24
28
|
[project.optional-dependencies]
|
|
25
29
|
client = [
|
|
@@ -226,6 +226,7 @@ class AsyncClient(base_client.BaseClient):
|
|
|
226
226
|
if not self.external_http: # pragma: no cover
|
|
227
227
|
if self.http and not self.http.closed:
|
|
228
228
|
await self.http.close()
|
|
229
|
+
self.http = None
|
|
229
230
|
|
|
230
231
|
def __del__(self): # pragma: no cover
|
|
231
232
|
# try to close the aiohttp session if it is still open
|
|
@@ -284,7 +285,13 @@ class AsyncClient(base_client.BaseClient):
|
|
|
284
285
|
|
|
285
286
|
self.state = 'connected'
|
|
286
287
|
base_client.connected_clients.append(self)
|
|
287
|
-
|
|
288
|
+
try:
|
|
289
|
+
await self._trigger_event('connect', run_async=False)
|
|
290
|
+
except Exception as exc:
|
|
291
|
+
base_client.connected_clients.remove(self)
|
|
292
|
+
await self._reset()
|
|
293
|
+
raise exceptions.ConnectionError(
|
|
294
|
+
'Connect handler failed: ' + str(exc))
|
|
288
295
|
|
|
289
296
|
for pkt in p.packets[1:]:
|
|
290
297
|
await self._receive_packet(pkt)
|
|
@@ -317,8 +324,13 @@ class AsyncClient(base_client.BaseClient):
|
|
|
317
324
|
self.logger.info(
|
|
318
325
|
'Attempting WebSocket connection to ' + websocket_url)
|
|
319
326
|
|
|
320
|
-
if self.http is None or self.http.closed:
|
|
321
|
-
self.
|
|
327
|
+
if self.http is None or self.http.closed:
|
|
328
|
+
if self.http_factory:
|
|
329
|
+
self.http = self.http_factory()
|
|
330
|
+
if inspect.iscoroutine(self.http):
|
|
331
|
+
self.http = await self.http
|
|
332
|
+
else:
|
|
333
|
+
self.http = aiohttp.ClientSession()
|
|
322
334
|
|
|
323
335
|
# extract any new cookies passed in a header so that they can also be
|
|
324
336
|
# sent the the WebSocket route
|
|
@@ -406,7 +418,13 @@ class AsyncClient(base_client.BaseClient):
|
|
|
406
418
|
|
|
407
419
|
self.state = 'connected'
|
|
408
420
|
base_client.connected_clients.append(self)
|
|
409
|
-
|
|
421
|
+
try:
|
|
422
|
+
await self._trigger_event('connect', run_async=False)
|
|
423
|
+
except Exception as exc:
|
|
424
|
+
base_client.connected_clients.remove(self)
|
|
425
|
+
await self._reset()
|
|
426
|
+
raise exceptions.ConnectionError(
|
|
427
|
+
'Connect handler failed: ' + str(exc))
|
|
410
428
|
|
|
411
429
|
self.ws = ws
|
|
412
430
|
self.write_loop_task = self.start_background_task(self._write_loop)
|
|
@@ -446,12 +464,17 @@ class AsyncClient(base_client.BaseClient):
|
|
|
446
464
|
|
|
447
465
|
async def _send_request(
|
|
448
466
|
self, method, url, headers=None, body=None,
|
|
449
|
-
timeout=None):
|
|
467
|
+
timeout=None):
|
|
450
468
|
if self.http is None or self.http.closed:
|
|
451
|
-
self.
|
|
469
|
+
if self.http_factory:
|
|
470
|
+
self.http = self.http_factory()
|
|
471
|
+
if inspect.iscoroutine(self.http):
|
|
472
|
+
self.http = await self.http
|
|
473
|
+
else:
|
|
474
|
+
self.http = aiohttp.ClientSession()
|
|
452
475
|
http_method = getattr(self.http, method.lower())
|
|
453
476
|
|
|
454
|
-
try:
|
|
477
|
+
try: # pragma: no cover
|
|
455
478
|
if not self.ssl_verify:
|
|
456
479
|
return await http_method(
|
|
457
480
|
url, headers=headers, data=body,
|
|
@@ -461,7 +484,8 @@ class AsyncClient(base_client.BaseClient):
|
|
|
461
484
|
url, headers=headers, data=body,
|
|
462
485
|
timeout=aiohttp.ClientTimeout(total=timeout))
|
|
463
486
|
|
|
464
|
-
except (aiohttp.ClientError, asyncio.TimeoutError)
|
|
487
|
+
except (aiohttp.ClientError, asyncio.TimeoutError) \
|
|
488
|
+
as exc: # pragma: no cover
|
|
465
489
|
self.logger.info('HTTP %s request to %s failed with error %s.',
|
|
466
490
|
method, url, exc)
|
|
467
491
|
return str(exc)
|
|
@@ -495,9 +519,9 @@ class AsyncClient(base_client.BaseClient):
|
|
|
495
519
|
except:
|
|
496
520
|
self.logger.exception(event + ' async handler error')
|
|
497
521
|
if event == 'connect':
|
|
498
|
-
#
|
|
499
|
-
# connection
|
|
500
|
-
|
|
522
|
+
# for the connection event we reraise the exception
|
|
523
|
+
# to stop the connection from completing
|
|
524
|
+
raise
|
|
501
525
|
else:
|
|
502
526
|
if run_async:
|
|
503
527
|
async def async_handler():
|
|
@@ -522,9 +546,9 @@ class AsyncClient(base_client.BaseClient):
|
|
|
522
546
|
except:
|
|
523
547
|
self.logger.exception(event + ' handler error')
|
|
524
548
|
if event == 'connect':
|
|
525
|
-
#
|
|
526
|
-
# connection
|
|
527
|
-
|
|
549
|
+
# for the connection event we reraise the exception
|
|
550
|
+
# to stop the connection from completing
|
|
551
|
+
raise
|
|
528
552
|
return ret
|
|
529
553
|
|
|
530
554
|
async def _read_loop_polling(self):
|
|
@@ -298,7 +298,13 @@ class AsyncServer(base_server.BaseServer):
|
|
|
298
298
|
self._log_error_once(f'{e} {sid}', 'bad-sid')
|
|
299
299
|
r = self._bad_request(f'{e} {sid}')
|
|
300
300
|
else:
|
|
301
|
-
if
|
|
301
|
+
if socket.upgraded:
|
|
302
|
+
# cannot accept HTTP requests once upgraded
|
|
303
|
+
self._log_error_once(
|
|
304
|
+
f'Unexpected HTTP request for session {sid}',
|
|
305
|
+
'unexpected-http')
|
|
306
|
+
r = self._bad_request('Unexpected HTTP request')
|
|
307
|
+
elif transport != 'polling' and \
|
|
302
308
|
transport != upgrade_header:
|
|
303
309
|
self._log_error_once(
|
|
304
310
|
f'Invalid transport for session {sid}',
|
|
@@ -326,18 +332,25 @@ class AsyncServer(base_server.BaseServer):
|
|
|
326
332
|
r = self._bad_request(f'Invalid session {sid}')
|
|
327
333
|
else:
|
|
328
334
|
socket = self._get_socket(sid)
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
#
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
335
|
+
if not socket.upgraded:
|
|
336
|
+
try:
|
|
337
|
+
await socket.handle_post_request(environ)
|
|
338
|
+
r = self._ok(jsonp_index=jsonp_index)
|
|
339
|
+
except exceptions.EngineIOError:
|
|
340
|
+
if sid in self.sockets: # pragma: no cover
|
|
341
|
+
await self.disconnect(sid)
|
|
342
|
+
r = self._bad_request()
|
|
343
|
+
except: # pragma: no cover
|
|
344
|
+
# for any other unexpected errors, we log the error
|
|
345
|
+
# and keep going
|
|
346
|
+
self.logger.exception('post request handler error')
|
|
347
|
+
r = self._ok(jsonp_index=jsonp_index)
|
|
348
|
+
else:
|
|
349
|
+
# cannot accept HTTP requests once upgraded
|
|
350
|
+
self._log_error_once(
|
|
351
|
+
f'Unexpected HTTP request for session {sid}',
|
|
352
|
+
'unexpected-http')
|
|
353
|
+
r = self._bad_request('Unexpected HTTP request')
|
|
341
354
|
elif method == 'OPTIONS':
|
|
342
355
|
r = self._ok()
|
|
343
356
|
else:
|
|
@@ -55,8 +55,14 @@ class BaseClient:
|
|
|
55
55
|
self.upgrades = None
|
|
56
56
|
self.ping_interval = None
|
|
57
57
|
self.ping_timeout = None
|
|
58
|
-
self.http =
|
|
59
|
-
self.
|
|
58
|
+
self.http = None
|
|
59
|
+
self.http_factory = None
|
|
60
|
+
if http_session is not None:
|
|
61
|
+
if callable(http_session):
|
|
62
|
+
self.http_factory = http_session
|
|
63
|
+
else:
|
|
64
|
+
self.http = http_session
|
|
65
|
+
self.external_http = self.http is not None
|
|
60
66
|
self.handle_sigint = handle_sigint
|
|
61
67
|
self.ws = None
|
|
62
68
|
self.read_loop_task = None
|
|
@@ -183,6 +183,8 @@ class Client(base_client.BaseClient):
|
|
|
183
183
|
self.queue.task_done()
|
|
184
184
|
except self.queue_empty:
|
|
185
185
|
break
|
|
186
|
+
if not self.external_http: # pragma: no cover
|
|
187
|
+
self.http = None
|
|
186
188
|
|
|
187
189
|
def _connect_polling(self, url, headers, engineio_path):
|
|
188
190
|
"""Establish a long-polling connection to the Engine.IO server."""
|
|
@@ -229,7 +231,13 @@ class Client(base_client.BaseClient):
|
|
|
229
231
|
|
|
230
232
|
self.state = 'connected'
|
|
231
233
|
base_client.connected_clients.append(self)
|
|
232
|
-
|
|
234
|
+
try:
|
|
235
|
+
self._trigger_event('connect', run_async=False, reraise=True)
|
|
236
|
+
except Exception as exc:
|
|
237
|
+
base_client.connected_clients.remove(self)
|
|
238
|
+
self._reset()
|
|
239
|
+
raise exceptions.ConnectionError(
|
|
240
|
+
'Connect handler failed: ' + str(exc))
|
|
233
241
|
|
|
234
242
|
for pkt in p.packets[1:]:
|
|
235
243
|
self._receive_packet(pkt)
|
|
@@ -268,6 +276,8 @@ class Client(base_client.BaseClient):
|
|
|
268
276
|
# so that they are preserved when connecting to the WebSocket route
|
|
269
277
|
cookies = None
|
|
270
278
|
extra_options = {}
|
|
279
|
+
if self.http is None and self.http_factory:
|
|
280
|
+
self.http = self.http_factory()
|
|
271
281
|
if self.http:
|
|
272
282
|
# cookies
|
|
273
283
|
ck = SimpleCookie()
|
|
@@ -403,7 +413,13 @@ class Client(base_client.BaseClient):
|
|
|
403
413
|
|
|
404
414
|
self.state = 'connected'
|
|
405
415
|
base_client.connected_clients.append(self)
|
|
406
|
-
|
|
416
|
+
try:
|
|
417
|
+
self._trigger_event('connect', run_async=False, reraise=True)
|
|
418
|
+
except Exception as exc:
|
|
419
|
+
base_client.connected_clients.remove(self)
|
|
420
|
+
self._reset()
|
|
421
|
+
raise exceptions.ConnectionError(
|
|
422
|
+
'Connect handler failed: ' + str(exc))
|
|
407
423
|
self.ws = ws
|
|
408
424
|
self.ws.settimeout(self.ping_interval + self.ping_timeout)
|
|
409
425
|
|
|
@@ -446,7 +462,10 @@ class Client(base_client.BaseClient):
|
|
|
446
462
|
self, method, url, headers=None, body=None,
|
|
447
463
|
timeout=None): # pragma: no cover
|
|
448
464
|
if self.http is None:
|
|
449
|
-
self.
|
|
465
|
+
if self.http_factory:
|
|
466
|
+
self.http = self.http_factory()
|
|
467
|
+
else:
|
|
468
|
+
self.http = requests.Session()
|
|
450
469
|
if not self.ssl_verify:
|
|
451
470
|
self.http.verify = False
|
|
452
471
|
try:
|
|
@@ -477,6 +496,10 @@ class Client(base_client.BaseClient):
|
|
|
477
496
|
raise
|
|
478
497
|
except:
|
|
479
498
|
self.logger.exception(event + ' handler error')
|
|
499
|
+
if event == 'connect':
|
|
500
|
+
# for the connection event we reraise the exception
|
|
501
|
+
# to stop the connection from completing
|
|
502
|
+
raise
|
|
480
503
|
|
|
481
504
|
def _read_loop_polling(self):
|
|
482
505
|
"""Read packets by polling the Engine.IO server."""
|
|
@@ -276,7 +276,13 @@ class Server(base_server.BaseServer):
|
|
|
276
276
|
self._log_error_once(f'{e} {sid}', 'bad-sid')
|
|
277
277
|
r = self._bad_request(f'{e} {sid}')
|
|
278
278
|
else:
|
|
279
|
-
if
|
|
279
|
+
if socket.upgraded:
|
|
280
|
+
# cannot accept HTTP requests once upgraded
|
|
281
|
+
self._log_error_once(
|
|
282
|
+
f'Unexpected HTTP request for session {sid}',
|
|
283
|
+
'unexpected-http')
|
|
284
|
+
r = self._bad_request('Unexpected HTTP request')
|
|
285
|
+
elif transport != 'polling' and \
|
|
280
286
|
transport != upgrade_header:
|
|
281
287
|
self._log_error_once(
|
|
282
288
|
f'Invalid transport for session {sid}',
|
|
@@ -304,18 +310,25 @@ class Server(base_server.BaseServer):
|
|
|
304
310
|
r = self._bad_request(f'Invalid session {sid}')
|
|
305
311
|
else:
|
|
306
312
|
socket = self._get_socket(sid)
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
self.
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
#
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
313
|
+
if not socket.upgraded:
|
|
314
|
+
try:
|
|
315
|
+
socket.handle_post_request(environ)
|
|
316
|
+
r = self._ok(jsonp_index=jsonp_index)
|
|
317
|
+
except exceptions.EngineIOError:
|
|
318
|
+
if sid in self.sockets: # pragma: no cover
|
|
319
|
+
self.disconnect(sid)
|
|
320
|
+
r = self._bad_request()
|
|
321
|
+
except: # pragma: no cover
|
|
322
|
+
# for any other unexpected errors, we log the error
|
|
323
|
+
# and keep going
|
|
324
|
+
self.logger.exception('post request handler error')
|
|
325
|
+
r = self._ok(jsonp_index=jsonp_index)
|
|
326
|
+
else:
|
|
327
|
+
# cannot accept HTTP requests once upgraded
|
|
328
|
+
self._log_error_once(
|
|
329
|
+
f'Unexpected HTTP request for session {sid}',
|
|
330
|
+
'unexpected-http')
|
|
331
|
+
r = self._bad_request('Unexpected HTTP request')
|
|
319
332
|
elif method == 'OPTIONS':
|
|
320
333
|
r = self._ok()
|
|
321
334
|
else:
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: python-engineio
|
|
3
|
+
Version: 4.15.0
|
|
4
|
+
Summary: Engine.IO server and client for Python
|
|
5
|
+
Author-email: Miguel Grinberg <miguel.grinberg@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://code.miguelgrinberg.com/miguelgrinberg/python-engineio
|
|
8
|
+
Project-URL: Documentation, https://python-engineio.readthedocs.io/
|
|
9
|
+
Project-URL: Source Code, https://code.miguelgrinberg.com/miguelgrinberg/python-engineio
|
|
10
|
+
Project-URL: Issue Tracker, https://code.miguelgrinberg.com/miguelgrinberg/python-engineio/issues
|
|
11
|
+
Project-URL: Changelog, https://code.miguelgrinberg.com/miguelgrinberg/python-engineio/src/branch/main/CHANGES.md
|
|
12
|
+
Project-URL: Funding, https://code.miguelgrinberg.com/miguelgrinberg/python-engineio#sponsor-this-project
|
|
13
|
+
Classifier: Environment :: Web Environment
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Requires-Python: >=3.8
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
License-File: LICENSE
|
|
20
|
+
Requires-Dist: simple-websocket>=0.10.0
|
|
21
|
+
Provides-Extra: client
|
|
22
|
+
Requires-Dist: requests>=2.21.0; extra == "client"
|
|
23
|
+
Requires-Dist: websocket-client>=0.54.0; extra == "client"
|
|
24
|
+
Provides-Extra: asyncio-client
|
|
25
|
+
Requires-Dist: aiohttp>=3.11; extra == "asyncio-client"
|
|
26
|
+
Provides-Extra: dev
|
|
27
|
+
Requires-Dist: tox; extra == "dev"
|
|
28
|
+
Provides-Extra: docs
|
|
29
|
+
Requires-Dist: sphinx; extra == "docs"
|
|
30
|
+
Requires-Dist: furo; extra == "docs"
|
|
31
|
+
Dynamic: license-file
|
|
32
|
+
|
|
33
|
+
python-engineio
|
|
34
|
+
===============
|
|
35
|
+
|
|
36
|
+
[](https://code.miguelgrinberg.com/miguelgrinberg/python-engineio/actions)
|
|
37
|
+
|
|
38
|
+
Python implementation of the `Engine.IO` realtime client and server.
|
|
39
|
+
|
|
40
|
+
Resources
|
|
41
|
+
---------
|
|
42
|
+
|
|
43
|
+
- [git](https://code.miguelgrinberg.com/miguelgrinberg/python-engineio)
|
|
44
|
+
- [Change Log](https://code.miguelgrinberg.com/miguelgrinberg/python-engineio/src/branch/main/CHANGES.md)
|
|
45
|
+
- [Documentation](https://python-engineio.readthedocs.io/)
|
|
46
|
+
- [PyPI](https://pypi.python.org/pypi/python-engineio)
|
|
47
|
+
- [Contributor's guide](CONTRIBUTING.md)
|
|
48
|
+
- [Security policy](SECURITY.md)
|
|
49
|
+
|
|
50
|
+
Sponsor this project
|
|
51
|
+
--------------------
|
|
52
|
+
|
|
53
|
+
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:
|
|
54
|
+
|
|
55
|
+
- [Github Sponsors](https://github.com/sponsors/miguelgrinberg)
|
|
56
|
+
- [Patreon](https://patreon.com/miguelgrinberg)
|
|
57
|
+
- [Buy me a Coffee](https://buymeacoffee.com/miguelgrinberg)
|
|
58
|
+
- [thanks.dev](https://thanks.dev/u/gh/miguelgrinberg)
|
|
59
|
+
- [PayPal](https://paypal.me/miguelgrinberg)
|
|
60
|
+
|
|
61
|
+
Thank you!
|
|
@@ -36,6 +36,7 @@ src/python_engineio.egg-info/dependency_links.txt
|
|
|
36
36
|
src/python_engineio.egg-info/not-zip-safe
|
|
37
37
|
src/python_engineio.egg-info/requires.txt
|
|
38
38
|
src/python_engineio.egg-info/top_level.txt
|
|
39
|
+
tests/async/.test_client.py.swp
|
|
39
40
|
tests/async/__init__.py
|
|
40
41
|
tests/async/index.html
|
|
41
42
|
tests/async/test_aiohttp.py
|
|
Binary file
|
|
@@ -147,6 +147,8 @@ class TestAsgi:
|
|
|
147
147
|
'Not Found')
|
|
148
148
|
await check_path('/bar/foo', 404, 'text/plain', 'Not Found')
|
|
149
149
|
await check_path('', 404, 'text/plain', 'Not Found')
|
|
150
|
+
await check_path('/static/../../LICENSE', 404, 'text/plain',
|
|
151
|
+
'Not Found')
|
|
150
152
|
|
|
151
153
|
app.static_files[''] = 'index.html'
|
|
152
154
|
await check_path('/static/test/', 200, 'text/html', '<html></html>\n')
|