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.
Files changed (69) hide show
  1. python_engineio-4.15.0/PKG-INFO +61 -0
  2. python_engineio-4.15.0/README.md +29 -0
  3. {python_engineio-4.13.5 → python_engineio-4.15.0}/pyproject.toml +8 -4
  4. {python_engineio-4.13.5 → python_engineio-4.15.0}/src/engineio/async_client.py +38 -14
  5. {python_engineio-4.13.5 → python_engineio-4.15.0}/src/engineio/async_server.py +26 -13
  6. {python_engineio-4.13.5 → python_engineio-4.15.0}/src/engineio/base_client.py +8 -2
  7. {python_engineio-4.13.5 → python_engineio-4.15.0}/src/engineio/client.py +26 -3
  8. {python_engineio-4.13.5 → python_engineio-4.15.0}/src/engineio/server.py +26 -13
  9. {python_engineio-4.13.5 → python_engineio-4.15.0}/src/engineio/static_files.py +2 -0
  10. python_engineio-4.15.0/src/python_engineio.egg-info/PKG-INFO +61 -0
  11. {python_engineio-4.13.5 → python_engineio-4.15.0}/src/python_engineio.egg-info/SOURCES.txt +1 -0
  12. python_engineio-4.15.0/tests/async/.test_client.py.swp +0 -0
  13. {python_engineio-4.13.5 → python_engineio-4.15.0}/tests/async/test_asgi.py +2 -0
  14. {python_engineio-4.13.5 → python_engineio-4.15.0}/tests/async/test_client.py +209 -67
  15. {python_engineio-4.13.5 → python_engineio-4.15.0}/tests/async/test_server.py +12 -0
  16. {python_engineio-4.13.5 → python_engineio-4.15.0}/tests/common/test_client.py +86 -12
  17. {python_engineio-4.13.5 → python_engineio-4.15.0}/tests/common/test_middleware.py +2 -0
  18. {python_engineio-4.13.5 → python_engineio-4.15.0}/tests/common/test_server.py +11 -0
  19. {python_engineio-4.13.5 → python_engineio-4.15.0}/tox.ini +19 -0
  20. python_engineio-4.13.5/PKG-INFO +0 -42
  21. python_engineio-4.13.5/README.md +0 -14
  22. python_engineio-4.13.5/src/python_engineio.egg-info/PKG-INFO +0 -42
  23. {python_engineio-4.13.5 → python_engineio-4.15.0}/LICENSE +0 -0
  24. {python_engineio-4.13.5 → python_engineio-4.15.0}/MANIFEST.in +0 -0
  25. {python_engineio-4.13.5 → python_engineio-4.15.0}/docs/_static/README.md +0 -0
  26. {python_engineio-4.13.5 → python_engineio-4.15.0}/setup.cfg +0 -0
  27. {python_engineio-4.13.5 → python_engineio-4.15.0}/src/engineio/__init__.py +0 -0
  28. {python_engineio-4.13.5 → python_engineio-4.15.0}/src/engineio/async_drivers/__init__.py +0 -0
  29. {python_engineio-4.13.5 → python_engineio-4.15.0}/src/engineio/async_drivers/_websocket_wsgi.py +0 -0
  30. {python_engineio-4.13.5 → python_engineio-4.15.0}/src/engineio/async_drivers/aiohttp.py +0 -0
  31. {python_engineio-4.13.5 → python_engineio-4.15.0}/src/engineio/async_drivers/asgi.py +0 -0
  32. {python_engineio-4.13.5 → python_engineio-4.15.0}/src/engineio/async_drivers/eventlet.py +0 -0
  33. {python_engineio-4.13.5 → python_engineio-4.15.0}/src/engineio/async_drivers/gevent.py +0 -0
  34. {python_engineio-4.13.5 → python_engineio-4.15.0}/src/engineio/async_drivers/gevent_uwsgi.py +0 -0
  35. {python_engineio-4.13.5 → python_engineio-4.15.0}/src/engineio/async_drivers/sanic.py +0 -0
  36. {python_engineio-4.13.5 → python_engineio-4.15.0}/src/engineio/async_drivers/threading.py +0 -0
  37. {python_engineio-4.13.5 → python_engineio-4.15.0}/src/engineio/async_drivers/tornado.py +0 -0
  38. {python_engineio-4.13.5 → python_engineio-4.15.0}/src/engineio/async_socket.py +0 -0
  39. {python_engineio-4.13.5 → python_engineio-4.15.0}/src/engineio/base_server.py +0 -0
  40. {python_engineio-4.13.5 → python_engineio-4.15.0}/src/engineio/base_socket.py +0 -0
  41. {python_engineio-4.13.5 → python_engineio-4.15.0}/src/engineio/exceptions.py +0 -0
  42. {python_engineio-4.13.5 → python_engineio-4.15.0}/src/engineio/json.py +0 -0
  43. {python_engineio-4.13.5 → python_engineio-4.15.0}/src/engineio/middleware.py +0 -0
  44. {python_engineio-4.13.5 → python_engineio-4.15.0}/src/engineio/packet.py +0 -0
  45. {python_engineio-4.13.5 → python_engineio-4.15.0}/src/engineio/payload.py +0 -0
  46. {python_engineio-4.13.5 → python_engineio-4.15.0}/src/engineio/socket.py +0 -0
  47. {python_engineio-4.13.5 → python_engineio-4.15.0}/src/python_engineio.egg-info/dependency_links.txt +0 -0
  48. {python_engineio-4.13.5 → python_engineio-4.15.0}/src/python_engineio.egg-info/not-zip-safe +0 -0
  49. {python_engineio-4.13.5 → python_engineio-4.15.0}/src/python_engineio.egg-info/requires.txt +0 -0
  50. {python_engineio-4.13.5 → python_engineio-4.15.0}/src/python_engineio.egg-info/top_level.txt +0 -0
  51. {python_engineio-4.13.5 → python_engineio-4.15.0}/tests/async/__init__.py +0 -0
  52. {python_engineio-4.13.5 → python_engineio-4.15.0}/tests/async/index.html +0 -0
  53. {python_engineio-4.13.5 → python_engineio-4.15.0}/tests/async/test_aiohttp.py +0 -0
  54. {python_engineio-4.13.5 → python_engineio-4.15.0}/tests/async/test_sanic.py +0 -0
  55. {python_engineio-4.13.5 → python_engineio-4.15.0}/tests/async/test_socket.py +0 -0
  56. {python_engineio-4.13.5 → python_engineio-4.15.0}/tests/async/test_tornado.py +0 -0
  57. {python_engineio-4.13.5 → python_engineio-4.15.0}/tests/common/__init__.py +0 -0
  58. {python_engineio-4.13.5 → python_engineio-4.15.0}/tests/common/index.html +0 -0
  59. {python_engineio-4.13.5 → python_engineio-4.15.0}/tests/common/test_packet.py +0 -0
  60. {python_engineio-4.13.5 → python_engineio-4.15.0}/tests/common/test_payload.py +0 -0
  61. {python_engineio-4.13.5 → python_engineio-4.15.0}/tests/common/test_socket.py +0 -0
  62. {python_engineio-4.13.5 → python_engineio-4.15.0}/tests/performance/README.md +0 -0
  63. {python_engineio-4.13.5 → python_engineio-4.15.0}/tests/performance/binary_b64_packet.py +0 -0
  64. {python_engineio-4.13.5 → python_engineio-4.15.0}/tests/performance/binary_packet.py +0 -0
  65. {python_engineio-4.13.5 → python_engineio-4.15.0}/tests/performance/json_packet.py +0 -0
  66. {python_engineio-4.13.5 → python_engineio-4.15.0}/tests/performance/payload.py +0 -0
  67. {python_engineio-4.13.5 → python_engineio-4.15.0}/tests/performance/run.sh +0 -0
  68. {python_engineio-4.13.5 → python_engineio-4.15.0}/tests/performance/server_receive.py +0 -0
  69. {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
+ [![tests](https://code.miguelgrinberg.com/miguelgrinberg/python-engineio/badges/workflows/tests.yml/badge.svg)](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
+ [![tests](https://code.miguelgrinberg.com/miguelgrinberg/python-engineio/badges/workflows/tests.yml/badge.svg)](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.13.5"
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://github.com/miguelgrinberg/python-engineio"
22
- "Bug Tracker" = "https://github.com/miguelgrinberg/python-engineio/issues"
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
- await self._trigger_event('connect', run_async=False)
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: # pragma: no cover
321
- self.http = aiohttp.ClientSession()
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
- await self._trigger_event('connect', run_async=False)
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): # pragma: no cover
467
+ timeout=None):
450
468
  if self.http is None or self.http.closed:
451
- self.http = aiohttp.ClientSession()
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) as exc:
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
- # if connect handler raised error we reject the
499
- # connection
500
- return False
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
- # if connect handler raised error we reject the
526
- # connection
527
- return False
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 self.transport(sid) != transport and \
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
- try:
330
- await socket.handle_post_request(environ)
331
- r = self._ok(jsonp_index=jsonp_index)
332
- except exceptions.EngineIOError:
333
- if sid in self.sockets: # pragma: no cover
334
- await self.disconnect(sid)
335
- r = self._bad_request()
336
- except: # pragma: no cover
337
- # for any other unexpected errors, we log the error
338
- # and keep going
339
- self.logger.exception('post request handler error')
340
- r = self._ok(jsonp_index=jsonp_index)
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 = http_session
59
- self.external_http = http_session is not None
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
- self._trigger_event('connect', run_async=False)
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
- self._trigger_event('connect', run_async=False)
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.http = requests.Session()
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 self.transport(sid) != transport and \
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
- try:
308
- socket.handle_post_request(environ)
309
- r = self._ok(jsonp_index=jsonp_index)
310
- except exceptions.EngineIOError:
311
- if sid in self.sockets: # pragma: no cover
312
- self.disconnect(sid)
313
- r = self._bad_request()
314
- except: # pragma: no cover
315
- # for any other unexpected errors, we log the error
316
- # and keep going
317
- self.logger.exception('post request handler error')
318
- r = self._ok(jsonp_index=jsonp_index)
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:
@@ -35,6 +35,8 @@ def get_static_file(path, static_files):
35
35
  elif path + '/' in static_files:
36
36
  f = static_files[path + '/']
37
37
  break
38
+ if '../' in extra_path:
39
+ return None
38
40
  if f:
39
41
  if isinstance(f, str):
40
42
  f = {'filename': f}
@@ -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
+ [![tests](https://code.miguelgrinberg.com/miguelgrinberg/python-engineio/badges/workflows/tests.yml/badge.svg)](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
@@ -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')