python-engineio 4.10.1__tar.gz → 4.11.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.10.1/src/python_engineio.egg-info → python_engineio-4.11.0}/PKG-INFO +1 -1
- {python_engineio-4.10.1 → python_engineio-4.11.0}/docs/client.rst +17 -6
- {python_engineio-4.10.1 → python_engineio-4.11.0}/docs/conf.py +0 -1
- {python_engineio-4.10.1 → python_engineio-4.11.0}/docs/server.rst +17 -6
- {python_engineio-4.10.1 → python_engineio-4.11.0}/pyproject.toml +5 -1
- {python_engineio-4.10.1 → python_engineio-4.11.0}/src/engineio/async_client.py +37 -9
- {python_engineio-4.10.1 → python_engineio-4.11.0}/src/engineio/async_drivers/_websocket_wsgi.py +1 -1
- {python_engineio-4.10.1 → python_engineio-4.11.0}/src/engineio/async_drivers/aiohttp.py +3 -3
- {python_engineio-4.10.1 → python_engineio-4.11.0}/src/engineio/async_drivers/asgi.py +20 -10
- {python_engineio-4.10.1 → python_engineio-4.11.0}/src/engineio/async_drivers/eventlet.py +0 -2
- {python_engineio-4.10.1 → python_engineio-4.11.0}/src/engineio/async_drivers/gevent_uwsgi.py +4 -4
- {python_engineio-4.10.1 → python_engineio-4.11.0}/src/engineio/async_drivers/sanic.py +4 -4
- {python_engineio-4.10.1 → python_engineio-4.11.0}/src/engineio/async_drivers/tornado.py +3 -3
- {python_engineio-4.10.1 → python_engineio-4.11.0}/src/engineio/async_server.py +69 -36
- {python_engineio-4.10.1 → python_engineio-4.11.0}/src/engineio/async_socket.py +16 -9
- {python_engineio-4.10.1 → python_engineio-4.11.0}/src/engineio/base_client.py +13 -1
- {python_engineio-4.10.1 → python_engineio-4.11.0}/src/engineio/base_server.py +13 -0
- {python_engineio-4.10.1 → python_engineio-4.11.0}/src/engineio/base_socket.py +0 -1
- {python_engineio-4.10.1 → python_engineio-4.11.0}/src/engineio/client.py +24 -8
- {python_engineio-4.10.1 → python_engineio-4.11.0}/src/engineio/middleware.py +2 -2
- {python_engineio-4.10.1 → python_engineio-4.11.0}/src/engineio/packet.py +1 -1
- {python_engineio-4.10.1 → python_engineio-4.11.0}/src/engineio/payload.py +1 -1
- {python_engineio-4.10.1 → python_engineio-4.11.0}/src/engineio/server.py +47 -30
- {python_engineio-4.10.1 → python_engineio-4.11.0}/src/engineio/socket.py +11 -6
- {python_engineio-4.10.1 → python_engineio-4.11.0/src/python_engineio.egg-info}/PKG-INFO +1 -1
- {python_engineio-4.10.1 → python_engineio-4.11.0}/src/python_engineio.egg-info/SOURCES.txt +0 -0
- {python_engineio-4.10.1 → python_engineio-4.11.0}/src/python_engineio.egg-info/dependency_links.txt +0 -0
- {python_engineio-4.10.1 → python_engineio-4.11.0}/src/python_engineio.egg-info/not-zip-safe +0 -0
- {python_engineio-4.10.1 → python_engineio-4.11.0}/src/python_engineio.egg-info/requires.txt +0 -0
- {python_engineio-4.10.1 → python_engineio-4.11.0}/src/python_engineio.egg-info/top_level.txt +0 -0
- {python_engineio-4.10.1 → python_engineio-4.11.0}/tests/async/test_aiohttp.py +1 -2
- python_engineio-4.11.0/tests/async/test_asgi.py +577 -0
- {python_engineio-4.10.1 → python_engineio-4.11.0}/tests/async/test_client.py +492 -500
- {python_engineio-4.10.1 → python_engineio-4.11.0}/tests/async/test_server.py +239 -219
- {python_engineio-4.10.1 → python_engineio-4.11.0}/tests/async/test_socket.py +184 -194
- {python_engineio-4.10.1 → python_engineio-4.11.0}/tests/async/test_tornado.py +6 -13
- {python_engineio-4.10.1 → python_engineio-4.11.0}/tests/common/test_client.py +34 -7
- {python_engineio-4.10.1 → python_engineio-4.11.0}/tests/common/test_middleware.py +1 -2
- {python_engineio-4.10.1 → python_engineio-4.11.0}/tests/common/test_packet.py +1 -3
- {python_engineio-4.10.1 → python_engineio-4.11.0}/tests/common/test_payload.py +1 -3
- {python_engineio-4.10.1 → python_engineio-4.11.0}/tests/common/test_server.py +28 -8
- {python_engineio-4.10.1 → python_engineio-4.11.0}/tests/common/test_socket.py +18 -9
- {python_engineio-4.10.1 → python_engineio-4.11.0}/tox.ini +1 -0
- python_engineio-4.10.1/tests/async/test_asgi.py +0 -586
- {python_engineio-4.10.1 → python_engineio-4.11.0}/LICENSE +0 -0
- {python_engineio-4.10.1 → python_engineio-4.11.0}/MANIFEST.in +0 -0
- {python_engineio-4.10.1 → python_engineio-4.11.0}/README.md +0 -0
- {python_engineio-4.10.1 → python_engineio-4.11.0}/docs/Makefile +0 -0
- {python_engineio-4.10.1 → python_engineio-4.11.0}/docs/_static/README.md +0 -0
- {python_engineio-4.10.1 → python_engineio-4.11.0}/docs/api.rst +0 -0
- {python_engineio-4.10.1 → python_engineio-4.11.0}/docs/index.rst +0 -0
- {python_engineio-4.10.1 → python_engineio-4.11.0}/docs/intro.rst +0 -0
- {python_engineio-4.10.1 → python_engineio-4.11.0}/docs/make.bat +0 -0
- {python_engineio-4.10.1 → python_engineio-4.11.0}/setup.cfg +0 -0
- {python_engineio-4.10.1 → python_engineio-4.11.0}/src/engineio/__init__.py +0 -0
- {python_engineio-4.10.1 → python_engineio-4.11.0}/src/engineio/async_drivers/__init__.py +0 -0
- {python_engineio-4.10.1 → python_engineio-4.11.0}/src/engineio/async_drivers/gevent.py +0 -0
- {python_engineio-4.10.1 → python_engineio-4.11.0}/src/engineio/async_drivers/threading.py +0 -0
- {python_engineio-4.10.1 → python_engineio-4.11.0}/src/engineio/exceptions.py +0 -0
- {python_engineio-4.10.1 → python_engineio-4.11.0}/src/engineio/json.py +0 -0
- {python_engineio-4.10.1 → python_engineio-4.11.0}/src/engineio/static_files.py +0 -0
- {python_engineio-4.10.1 → python_engineio-4.11.0}/tests/__init__.py +0 -0
- {python_engineio-4.10.1 → python_engineio-4.11.0}/tests/async/__init__.py +0 -0
- {python_engineio-4.10.1 → python_engineio-4.11.0}/tests/async/index.html +0 -0
- {python_engineio-4.10.1 → python_engineio-4.11.0}/tests/async/test_sanic.py +0 -0
- {python_engineio-4.10.1 → python_engineio-4.11.0}/tests/common/__init__.py +0 -0
- {python_engineio-4.10.1 → python_engineio-4.11.0}/tests/common/index.html +0 -0
- {python_engineio-4.10.1 → python_engineio-4.11.0}/tests/performance/README.md +0 -0
- {python_engineio-4.10.1 → python_engineio-4.11.0}/tests/performance/binary_b64_packet.py +0 -0
- {python_engineio-4.10.1 → python_engineio-4.11.0}/tests/performance/binary_packet.py +0 -0
- {python_engineio-4.10.1 → python_engineio-4.11.0}/tests/performance/json_packet.py +0 -0
- {python_engineio-4.10.1 → python_engineio-4.11.0}/tests/performance/payload.py +0 -0
- {python_engineio-4.10.1 → python_engineio-4.11.0}/tests/performance/run.sh +0 -0
- {python_engineio-4.10.1 → python_engineio-4.11.0}/tests/performance/server_receive.py +0 -0
- {python_engineio-4.10.1 → python_engineio-4.11.0}/tests/performance/text_packet.py +0 -0
|
@@ -52,8 +52,8 @@ functions must be defined using the ``on`` decorator::
|
|
|
52
52
|
print('I received a message!')
|
|
53
53
|
|
|
54
54
|
@eio.on('disconnect')
|
|
55
|
-
def on_disconnect():
|
|
56
|
-
print('I'm disconnected!')
|
|
55
|
+
def on_disconnect(reason):
|
|
56
|
+
print('I'm disconnected! reason:', reason)
|
|
57
57
|
|
|
58
58
|
For the ``asyncio`` server, event handlers can be regular functions as above,
|
|
59
59
|
or can also be coroutines::
|
|
@@ -63,14 +63,25 @@ or can also be coroutines::
|
|
|
63
63
|
print('I received a message!')
|
|
64
64
|
|
|
65
65
|
The argument given to the ``on`` decorator is the event name. The events that
|
|
66
|
-
are supported are ``connect``, ``message`` and ``disconnect``.
|
|
67
|
-
``disconnect`` handler is invoked for application initiated disconnects,
|
|
68
|
-
server initiated disconnects, or accidental disconnects, for example due to
|
|
69
|
-
networking failures.
|
|
66
|
+
are supported are ``connect``, ``message`` and ``disconnect``.
|
|
70
67
|
|
|
71
68
|
The ``data`` argument passed to the ``'message'`` event handler contains
|
|
72
69
|
application-specific data provided by the server with the event.
|
|
73
70
|
|
|
71
|
+
The ``disconnect`` handler is invoked for client initiated disconnects, server
|
|
72
|
+
initiated disconnects, or accidental disconnects, for example due to
|
|
73
|
+
networking failures. The argument passed to this handler provides the
|
|
74
|
+
disconnect reason. Example::
|
|
75
|
+
|
|
76
|
+
@eio.on('disconnect')
|
|
77
|
+
def on_disconnect(reason):
|
|
78
|
+
if reason == eio.reason.CLIENT_DISCONNECT:
|
|
79
|
+
print('client disconnection')
|
|
80
|
+
elif reason == eio.reason.SERVER_DISCONNECT:
|
|
81
|
+
print('the server kicked me out')
|
|
82
|
+
else:
|
|
83
|
+
print(f'disconnect reason: {reason}')
|
|
84
|
+
|
|
74
85
|
Connecting to a Server
|
|
75
86
|
----------------------
|
|
76
87
|
|
|
@@ -161,8 +161,8 @@ functions must be defined using the ``on`` decorator::
|
|
|
161
161
|
print('I received a message!')
|
|
162
162
|
|
|
163
163
|
@eio.on('disconnect')
|
|
164
|
-
def on_disconnect(sid):
|
|
165
|
-
print('Client disconnected!')
|
|
164
|
+
def on_disconnect(sid, reason):
|
|
165
|
+
print('Client disconnected! reason:', reason)
|
|
166
166
|
|
|
167
167
|
For the ``asyncio`` server, event handlers can be regular functions as above,
|
|
168
168
|
or can also be coroutines::
|
|
@@ -172,10 +172,7 @@ or can also be coroutines::
|
|
|
172
172
|
print('I received a message!')
|
|
173
173
|
|
|
174
174
|
The argument given to the ``on`` decorator is the event name. The events that
|
|
175
|
-
are supported are ``connect``, ``message`` and ``disconnect``.
|
|
176
|
-
``disconnect`` handler is invoked for client initiated disconnects,
|
|
177
|
-
server initiated disconnects, or accidental disconnects, for example due to
|
|
178
|
-
networking failures.
|
|
175
|
+
are supported are ``connect``, ``message`` and ``disconnect``.
|
|
179
176
|
|
|
180
177
|
The ``sid`` argument passed into all the event handlers is a connection
|
|
181
178
|
identifier for the client. All the events from a client will use the same
|
|
@@ -192,6 +189,20 @@ rejected. A rejected connection triggers a response with a 401 status code.
|
|
|
192
189
|
The ``data`` argument passed to the ``'message'`` event handler contains
|
|
193
190
|
application-specific data provided by the client with the event.
|
|
194
191
|
|
|
192
|
+
The ``disconnect`` handler is invoked for client initiated disconnects,
|
|
193
|
+
server initiated disconnects, or accidental disconnects, for example due to
|
|
194
|
+
networking failures. The second argument passed to this handler provides the
|
|
195
|
+
disconnect reason. Example::
|
|
196
|
+
|
|
197
|
+
@eio.on('disconnect')
|
|
198
|
+
def on_disconnect(sid, reason):
|
|
199
|
+
if reason == eio.reason.CLIENT_DISCONNECT:
|
|
200
|
+
print('the client went away')
|
|
201
|
+
elif reason == eio.reason.SERVER_DISCONNECT:
|
|
202
|
+
print('the client was kicked out')
|
|
203
|
+
else:
|
|
204
|
+
print(f'disconnect reason: {reason}')
|
|
205
|
+
|
|
195
206
|
Sending Messages
|
|
196
207
|
----------------
|
|
197
208
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "python-engineio"
|
|
3
|
-
version = "4.
|
|
3
|
+
version = "4.11.0"
|
|
4
4
|
authors = [{name = "Miguel Grinberg", email = "miguel.grinberg@gmail.com"}]
|
|
5
5
|
description = "Engine.IO server and client for Python"
|
|
6
6
|
classifiers = [
|
|
@@ -41,3 +41,7 @@ namespaces = false
|
|
|
41
41
|
[build-system]
|
|
42
42
|
requires = ["setuptools>=61.2"]
|
|
43
43
|
build-backend = "setuptools.build_meta"
|
|
44
|
+
|
|
45
|
+
[tool.pytest.ini_options]
|
|
46
|
+
asyncio_mode = "auto"
|
|
47
|
+
asyncio_default_fixture_loop_scope = "session"
|
|
@@ -37,7 +37,7 @@ def async_signal_handler():
|
|
|
37
37
|
for task in tasks:
|
|
38
38
|
task.cancel()
|
|
39
39
|
await asyncio.gather(*tasks, return_exceptions=True)
|
|
40
|
-
asyncio.
|
|
40
|
+
asyncio.get_running_loop().stop()
|
|
41
41
|
|
|
42
42
|
asyncio.ensure_future(_handler())
|
|
43
43
|
|
|
@@ -75,6 +75,9 @@ class AsyncClient(base_client.BaseClient):
|
|
|
75
75
|
:param websocket_extra_options: Dictionary containing additional keyword
|
|
76
76
|
arguments passed to
|
|
77
77
|
``aiohttp.ws_connect()``.
|
|
78
|
+
:param timestamp_requests: If ``True`` a timestamp is added to the query
|
|
79
|
+
string of Socket.IO requests as a cache-busting
|
|
80
|
+
measure. Set to ``False`` to disable.
|
|
78
81
|
"""
|
|
79
82
|
def is_asyncio_based(self):
|
|
80
83
|
return True
|
|
@@ -106,7 +109,7 @@ class AsyncClient(base_client.BaseClient):
|
|
|
106
109
|
if self.handle_sigint and not async_signal_handler_set and \
|
|
107
110
|
threading.current_thread() == threading.main_thread():
|
|
108
111
|
try:
|
|
109
|
-
asyncio.
|
|
112
|
+
asyncio.get_running_loop().add_signal_handler(
|
|
110
113
|
signal.SIGINT, async_signal_handler)
|
|
111
114
|
except NotImplementedError: # pragma: no cover
|
|
112
115
|
self.logger.warning('Signal handler is unsupported')
|
|
@@ -149,7 +152,7 @@ class AsyncClient(base_client.BaseClient):
|
|
|
149
152
|
"""
|
|
150
153
|
await self._send_packet(packet.Packet(packet.MESSAGE, data=data))
|
|
151
154
|
|
|
152
|
-
async def disconnect(self, abort=False):
|
|
155
|
+
async def disconnect(self, abort=False, reason=None):
|
|
153
156
|
"""Disconnect from the server.
|
|
154
157
|
|
|
155
158
|
:param abort: If set to ``True``, do not wait for background tasks
|
|
@@ -161,7 +164,9 @@ class AsyncClient(base_client.BaseClient):
|
|
|
161
164
|
await self._send_packet(packet.Packet(packet.CLOSE))
|
|
162
165
|
await self.queue.put(None)
|
|
163
166
|
self.state = 'disconnecting'
|
|
164
|
-
await self._trigger_event('disconnect',
|
|
167
|
+
await self._trigger_event('disconnect',
|
|
168
|
+
reason or self.reason.CLIENT_DISCONNECT,
|
|
169
|
+
run_async=False)
|
|
165
170
|
if self.current_transport == 'websocket':
|
|
166
171
|
await self.ws.close()
|
|
167
172
|
if not abort:
|
|
@@ -409,7 +414,8 @@ class AsyncClient(base_client.BaseClient):
|
|
|
409
414
|
elif pkt.packet_type == packet.PING:
|
|
410
415
|
await self._send_packet(packet.Packet(packet.PONG, pkt.data))
|
|
411
416
|
elif pkt.packet_type == packet.CLOSE:
|
|
412
|
-
await self.disconnect(abort=True
|
|
417
|
+
await self.disconnect(abort=True,
|
|
418
|
+
reason=self.reason.SERVER_DISCONNECT)
|
|
413
419
|
elif pkt.packet_type == packet.NOOP:
|
|
414
420
|
pass
|
|
415
421
|
else:
|
|
@@ -462,7 +468,17 @@ class AsyncClient(base_client.BaseClient):
|
|
|
462
468
|
return task
|
|
463
469
|
else:
|
|
464
470
|
try:
|
|
465
|
-
|
|
471
|
+
try:
|
|
472
|
+
ret = await self.handlers[event](*args)
|
|
473
|
+
except TypeError:
|
|
474
|
+
print(args, len(args))
|
|
475
|
+
if event == 'disconnect' and \
|
|
476
|
+
len(args) == 1: # pragma: no branch
|
|
477
|
+
# legacy disconnect events do not have a reason
|
|
478
|
+
# argument
|
|
479
|
+
return await self.handlers[event]()
|
|
480
|
+
else: # pragma: no cover
|
|
481
|
+
raise
|
|
466
482
|
except asyncio.CancelledError: # pragma: no cover
|
|
467
483
|
pass
|
|
468
484
|
except:
|
|
@@ -482,7 +498,17 @@ class AsyncClient(base_client.BaseClient):
|
|
|
482
498
|
return task
|
|
483
499
|
else:
|
|
484
500
|
try:
|
|
485
|
-
|
|
501
|
+
try:
|
|
502
|
+
ret = self.handlers[event](*args)
|
|
503
|
+
except TypeError:
|
|
504
|
+
print(args, len(args))
|
|
505
|
+
if event == 'disconnect' and \
|
|
506
|
+
len(args) == 1: # pragma: no branch
|
|
507
|
+
# legacy disconnect events do not have a reason
|
|
508
|
+
# argument
|
|
509
|
+
ret = self.handlers[event]()
|
|
510
|
+
else: # pragma: no cover
|
|
511
|
+
raise
|
|
486
512
|
except:
|
|
487
513
|
self.logger.exception(event + ' handler error')
|
|
488
514
|
if event == 'connect':
|
|
@@ -524,7 +550,8 @@ class AsyncClient(base_client.BaseClient):
|
|
|
524
550
|
self.logger.info('Waiting for write loop task to end')
|
|
525
551
|
await self.write_loop_task
|
|
526
552
|
if self.state == 'connected':
|
|
527
|
-
await self._trigger_event(
|
|
553
|
+
await self._trigger_event(
|
|
554
|
+
'disconnect', self.reason.TRANSPORT_ERROR, run_async=False)
|
|
528
555
|
try:
|
|
529
556
|
base_client.connected_clients.remove(self)
|
|
530
557
|
except ValueError: # pragma: no cover
|
|
@@ -578,7 +605,8 @@ class AsyncClient(base_client.BaseClient):
|
|
|
578
605
|
self.logger.info('Waiting for write loop task to end')
|
|
579
606
|
await self.write_loop_task
|
|
580
607
|
if self.state == 'connected':
|
|
581
|
-
await self._trigger_event(
|
|
608
|
+
await self._trigger_event(
|
|
609
|
+
'disconnect', self.reason.TRANSPORT_ERROR, run_async=False)
|
|
582
610
|
try:
|
|
583
611
|
base_client.connected_clients.remove(self)
|
|
584
612
|
except ValueError: # pragma: no cover
|
|
@@ -57,7 +57,7 @@ def translate_request(request):
|
|
|
57
57
|
|
|
58
58
|
key = 'HTTP_%s' % hdr_name.replace('-', '_')
|
|
59
59
|
if key in environ:
|
|
60
|
-
hdr_value = '
|
|
60
|
+
hdr_value = f'{environ[key]},{hdr_value}'
|
|
61
61
|
|
|
62
62
|
environ[key] = hdr_value
|
|
63
63
|
|
|
@@ -79,7 +79,7 @@ def make_response(status, headers, payload, environ):
|
|
|
79
79
|
headers=headers)
|
|
80
80
|
|
|
81
81
|
|
|
82
|
-
class WebSocket
|
|
82
|
+
class WebSocket: # pragma: no cover
|
|
83
83
|
"""
|
|
84
84
|
This wrapper class provides a aiohttp WebSocket interface that is
|
|
85
85
|
somewhat compatible with eventlet's implementation.
|
|
@@ -114,7 +114,7 @@ class WebSocket(object): # pragma: no cover
|
|
|
114
114
|
msg = await self._sock.receive()
|
|
115
115
|
if not isinstance(msg.data, bytes) and \
|
|
116
116
|
not isinstance(msg.data, str):
|
|
117
|
-
raise
|
|
117
|
+
raise OSError()
|
|
118
118
|
return msg.data
|
|
119
119
|
|
|
120
120
|
|
|
@@ -135,7 +135,7 @@ class ASGIApp:
|
|
|
135
135
|
|
|
136
136
|
|
|
137
137
|
async def translate_request(scope, receive, send):
|
|
138
|
-
class AwaitablePayload
|
|
138
|
+
class AwaitablePayload: # pragma: no cover
|
|
139
139
|
def __init__(self, payload):
|
|
140
140
|
self.payload = payload or b''
|
|
141
141
|
|
|
@@ -161,9 +161,15 @@ async def translate_request(scope, receive, send):
|
|
|
161
161
|
else:
|
|
162
162
|
return {}
|
|
163
163
|
|
|
164
|
-
raw_uri = scope['path']
|
|
164
|
+
raw_uri = scope['path']
|
|
165
|
+
query_string = ''
|
|
165
166
|
if 'query_string' in scope and scope['query_string']:
|
|
166
|
-
|
|
167
|
+
try:
|
|
168
|
+
query_string = scope['query_string'].decode('utf-8')
|
|
169
|
+
except UnicodeDecodeError:
|
|
170
|
+
pass
|
|
171
|
+
else:
|
|
172
|
+
raw_uri += '?' + query_string
|
|
167
173
|
environ = {
|
|
168
174
|
'wsgi.input': AwaitablePayload(payload),
|
|
169
175
|
'wsgi.errors': sys.stderr,
|
|
@@ -175,8 +181,8 @@ async def translate_request(scope, receive, send):
|
|
|
175
181
|
'SERVER_SOFTWARE': 'asgi',
|
|
176
182
|
'REQUEST_METHOD': scope.get('method', 'GET'),
|
|
177
183
|
'PATH_INFO': scope['path'],
|
|
178
|
-
'QUERY_STRING':
|
|
179
|
-
'RAW_URI': raw_uri
|
|
184
|
+
'QUERY_STRING': query_string,
|
|
185
|
+
'RAW_URI': raw_uri,
|
|
180
186
|
'SCRIPT_NAME': '',
|
|
181
187
|
'SERVER_PROTOCOL': 'HTTP/1.1',
|
|
182
188
|
'REMOTE_ADDR': '127.0.0.1',
|
|
@@ -189,8 +195,12 @@ async def translate_request(scope, receive, send):
|
|
|
189
195
|
}
|
|
190
196
|
|
|
191
197
|
for hdr_name, hdr_value in scope['headers']:
|
|
192
|
-
|
|
193
|
-
|
|
198
|
+
try:
|
|
199
|
+
hdr_name = hdr_name.upper().decode('utf-8')
|
|
200
|
+
hdr_value = hdr_value.decode('utf-8')
|
|
201
|
+
except UnicodeDecodeError:
|
|
202
|
+
# skip header if it cannot be decoded
|
|
203
|
+
continue
|
|
194
204
|
if hdr_name == 'CONTENT-TYPE':
|
|
195
205
|
environ['CONTENT_TYPE'] = hdr_value
|
|
196
206
|
continue
|
|
@@ -200,7 +210,7 @@ async def translate_request(scope, receive, send):
|
|
|
200
210
|
|
|
201
211
|
key = 'HTTP_%s' % hdr_name.replace('-', '_')
|
|
202
212
|
if key in environ:
|
|
203
|
-
hdr_value = '
|
|
213
|
+
hdr_value = f'{environ[key]},{hdr_value}'
|
|
204
214
|
|
|
205
215
|
environ[key] = hdr_value
|
|
206
216
|
|
|
@@ -231,7 +241,7 @@ async def make_response(status, headers, payload, environ):
|
|
|
231
241
|
'body': payload})
|
|
232
242
|
|
|
233
243
|
|
|
234
|
-
class WebSocket
|
|
244
|
+
class WebSocket: # pragma: no cover
|
|
235
245
|
"""
|
|
236
246
|
This wrapper class provides an asgi WebSocket interface that is
|
|
237
247
|
somewhat compatible with eventlet's implementation.
|
|
@@ -269,7 +279,7 @@ class WebSocket(object): # pragma: no cover
|
|
|
269
279
|
async def wait(self):
|
|
270
280
|
event = await self.asgi_receive()
|
|
271
281
|
if event['type'] != 'websocket.receive':
|
|
272
|
-
raise
|
|
282
|
+
raise OSError()
|
|
273
283
|
return event.get('bytes') or event.get('text')
|
|
274
284
|
|
|
275
285
|
|
{python_engineio-4.10.1 → python_engineio-4.11.0}/src/engineio/async_drivers/gevent_uwsgi.py
RENAMED
|
@@ -18,7 +18,7 @@ class Thread(gevent.Greenlet): # pragma: no cover
|
|
|
18
18
|
return self.run()
|
|
19
19
|
|
|
20
20
|
|
|
21
|
-
class uWSGIWebSocket
|
|
21
|
+
class uWSGIWebSocket: # pragma: no cover
|
|
22
22
|
"""
|
|
23
23
|
This wrapper class provides a uWSGI WebSocket interface that is
|
|
24
24
|
compatible with eventlet's implementation.
|
|
@@ -115,7 +115,7 @@ class uWSGIWebSocket(object): # pragma: no cover
|
|
|
115
115
|
if self._req_ctx is not None:
|
|
116
116
|
try:
|
|
117
117
|
msg = uwsgi.websocket_recv(request_context=self._req_ctx)
|
|
118
|
-
except
|
|
118
|
+
except OSError: # connection closed
|
|
119
119
|
self.close()
|
|
120
120
|
return None
|
|
121
121
|
return self._decode_received(msg)
|
|
@@ -138,7 +138,7 @@ class uWSGIWebSocket(object): # pragma: no cover
|
|
|
138
138
|
for msg in msgs:
|
|
139
139
|
try:
|
|
140
140
|
self._send(msg)
|
|
141
|
-
except
|
|
141
|
+
except OSError:
|
|
142
142
|
self.close()
|
|
143
143
|
return None
|
|
144
144
|
# maybe there is something to receive, if not, at least
|
|
@@ -146,7 +146,7 @@ class uWSGIWebSocket(object): # pragma: no cover
|
|
|
146
146
|
while True:
|
|
147
147
|
try:
|
|
148
148
|
msg = uwsgi.websocket_recv_nb()
|
|
149
|
-
except
|
|
149
|
+
except OSError: # connection closed
|
|
150
150
|
self.close()
|
|
151
151
|
return None
|
|
152
152
|
if msg: # message available
|
|
@@ -33,7 +33,7 @@ def translate_request(request): # pragma: no cover
|
|
|
33
33
|
"""This function takes the arguments passed to the request handler and
|
|
34
34
|
uses them to generate a WSGI compatible environ dictionary.
|
|
35
35
|
"""
|
|
36
|
-
class AwaitablePayload
|
|
36
|
+
class AwaitablePayload:
|
|
37
37
|
def __init__(self, payload):
|
|
38
38
|
self.payload = payload or b''
|
|
39
39
|
|
|
@@ -78,7 +78,7 @@ def translate_request(request): # pragma: no cover
|
|
|
78
78
|
|
|
79
79
|
key = 'HTTP_%s' % hdr_name.replace('-', '_')
|
|
80
80
|
if key in environ:
|
|
81
|
-
hdr_value = '
|
|
81
|
+
hdr_value = f'{environ[key]},{hdr_value}'
|
|
82
82
|
|
|
83
83
|
environ[key] = hdr_value
|
|
84
84
|
|
|
@@ -107,7 +107,7 @@ def make_response(status, headers, payload, environ): # pragma: no cover
|
|
|
107
107
|
status=int(status.split()[0]), headers=headers_dict)
|
|
108
108
|
|
|
109
109
|
|
|
110
|
-
class WebSocket
|
|
110
|
+
class WebSocket: # pragma: no cover
|
|
111
111
|
"""
|
|
112
112
|
This wrapper class provides a sanic WebSocket interface that is
|
|
113
113
|
somewhat compatible with eventlet's implementation.
|
|
@@ -136,7 +136,7 @@ class WebSocket(object): # pragma: no cover
|
|
|
136
136
|
data = await self._sock.recv()
|
|
137
137
|
if not isinstance(data, bytes) and \
|
|
138
138
|
not isinstance(data, str):
|
|
139
|
-
raise
|
|
139
|
+
raise OSError()
|
|
140
140
|
return data
|
|
141
141
|
|
|
142
142
|
|
|
@@ -64,7 +64,7 @@ def translate_request(handler):
|
|
|
64
64
|
"""This function takes the arguments passed to the request handler and
|
|
65
65
|
uses them to generate a WSGI compatible environ dictionary.
|
|
66
66
|
"""
|
|
67
|
-
class AwaitablePayload
|
|
67
|
+
class AwaitablePayload:
|
|
68
68
|
def __init__(self, payload):
|
|
69
69
|
self.payload = payload or b''
|
|
70
70
|
|
|
@@ -142,7 +142,7 @@ def make_response(status, headers, payload, environ):
|
|
|
142
142
|
tornado_handler.finish()
|
|
143
143
|
|
|
144
144
|
|
|
145
|
-
class WebSocket
|
|
145
|
+
class WebSocket: # pragma: no cover
|
|
146
146
|
"""
|
|
147
147
|
This wrapper class provides a tornado WebSocket interface that is
|
|
148
148
|
somewhat compatible with eventlet's implementation.
|
|
@@ -170,7 +170,7 @@ class WebSocket(object): # pragma: no cover
|
|
|
170
170
|
msg = await self.tornado_handler.get_next_message()
|
|
171
171
|
if not isinstance(msg, bytes) and \
|
|
172
172
|
not isinstance(msg, str):
|
|
173
|
-
raise
|
|
173
|
+
raise OSError()
|
|
174
174
|
return msg
|
|
175
175
|
|
|
176
176
|
|
|
@@ -89,7 +89,7 @@ class AsyncServer(base_server.BaseServer):
|
|
|
89
89
|
def attach(self, app, engineio_path='engine.io'):
|
|
90
90
|
"""Attach the Engine.IO server to an application."""
|
|
91
91
|
engineio_path = engineio_path.strip('/')
|
|
92
|
-
self._async['create_route'](app, self, '/{}/'
|
|
92
|
+
self._async['create_route'](app, self, f'/{engineio_path}/')
|
|
93
93
|
|
|
94
94
|
async def send(self, sid, data):
|
|
95
95
|
"""Send a message to a client.
|
|
@@ -162,7 +162,7 @@ class AsyncServer(base_server.BaseServer):
|
|
|
162
162
|
async with eio.session(sid) as session:
|
|
163
163
|
print('received message from ', session['username'])
|
|
164
164
|
"""
|
|
165
|
-
class _session_context_manager
|
|
165
|
+
class _session_context_manager:
|
|
166
166
|
def __init__(self, server, sid):
|
|
167
167
|
self.server = server
|
|
168
168
|
self.sid = sid
|
|
@@ -192,12 +192,15 @@ class AsyncServer(base_server.BaseServer):
|
|
|
192
192
|
# the socket was already closed or gone
|
|
193
193
|
pass
|
|
194
194
|
else:
|
|
195
|
-
await socket.close()
|
|
195
|
+
await socket.close(reason=self.reason.SERVER_DISCONNECT)
|
|
196
196
|
if sid in self.sockets: # pragma: no cover
|
|
197
197
|
del self.sockets[sid]
|
|
198
198
|
else:
|
|
199
|
-
await asyncio.wait([
|
|
200
|
-
|
|
199
|
+
await asyncio.wait([
|
|
200
|
+
asyncio.create_task(client.close(
|
|
201
|
+
reason=self.reason.SERVER_DISCONNECT))
|
|
202
|
+
for client in self.sockets.values()
|
|
203
|
+
])
|
|
201
204
|
self.sockets = {}
|
|
202
205
|
|
|
203
206
|
async def handle_request(self, *args, **kwargs):
|
|
@@ -284,33 +287,41 @@ class AsyncServer(base_server.BaseServer):
|
|
|
284
287
|
r = self._bad_request('Invalid websocket upgrade')
|
|
285
288
|
else:
|
|
286
289
|
if sid not in self.sockets:
|
|
287
|
-
self._log_error_once('Invalid session '
|
|
288
|
-
r = self._bad_request('Invalid session '
|
|
290
|
+
self._log_error_once(f'Invalid session {sid}', 'bad-sid')
|
|
291
|
+
r = self._bad_request(f'Invalid session {sid}')
|
|
289
292
|
else:
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
self._log_error_once(
|
|
294
|
-
|
|
295
|
-
'bad-transport')
|
|
296
|
-
r = self._bad_request('Invalid transport')
|
|
293
|
+
try:
|
|
294
|
+
socket = self._get_socket(sid)
|
|
295
|
+
except KeyError as e: # pragma: no cover
|
|
296
|
+
self._log_error_once(f'{e} {sid}', 'bad-sid')
|
|
297
|
+
r = self._bad_request(f'{e} {sid}')
|
|
297
298
|
else:
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
await
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
299
|
+
if self.transport(sid) != transport and \
|
|
300
|
+
transport != upgrade_header:
|
|
301
|
+
self._log_error_once(
|
|
302
|
+
f'Invalid transport for session {sid}',
|
|
303
|
+
'bad-transport')
|
|
304
|
+
r = self._bad_request('Invalid transport')
|
|
305
|
+
else:
|
|
306
|
+
try:
|
|
307
|
+
packets = await socket.handle_get_request(
|
|
308
|
+
environ)
|
|
309
|
+
if isinstance(packets, list):
|
|
310
|
+
r = self._ok(packets,
|
|
311
|
+
jsonp_index=jsonp_index)
|
|
312
|
+
else:
|
|
313
|
+
r = packets
|
|
314
|
+
except exceptions.EngineIOError:
|
|
315
|
+
if sid in self.sockets: # pragma: no cover
|
|
316
|
+
await self.disconnect(sid)
|
|
317
|
+
r = self._bad_request()
|
|
318
|
+
if sid in self.sockets and \
|
|
319
|
+
self.sockets[sid].closed:
|
|
320
|
+
del self.sockets[sid]
|
|
310
321
|
elif method == 'POST':
|
|
311
322
|
if sid is None or sid not in self.sockets:
|
|
312
|
-
self._log_error_once('Invalid session '
|
|
313
|
-
r = self._bad_request('Invalid session '
|
|
323
|
+
self._log_error_once(f'Invalid session {sid}', 'bad-sid')
|
|
324
|
+
r = self._bad_request(f'Invalid session {sid}')
|
|
314
325
|
else:
|
|
315
326
|
socket = self._get_socket(sid)
|
|
316
327
|
try:
|
|
@@ -440,11 +451,14 @@ class AsyncServer(base_server.BaseServer):
|
|
|
440
451
|
s = async_socket.AsyncSocket(self, sid)
|
|
441
452
|
self.sockets[sid] = s
|
|
442
453
|
|
|
443
|
-
pkt = packet.Packet(
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
454
|
+
pkt = packet.Packet(packet.OPEN, {
|
|
455
|
+
'sid': sid,
|
|
456
|
+
'upgrades': self._upgrades(sid, transport),
|
|
457
|
+
'pingTimeout': int(self.ping_timeout * 1000),
|
|
458
|
+
'pingInterval': int(
|
|
459
|
+
self.ping_interval + self.ping_interval_grace_period) * 1000,
|
|
460
|
+
'maxPayload': self.max_http_buffer_size,
|
|
461
|
+
})
|
|
448
462
|
await s.send(pkt)
|
|
449
463
|
s.schedule_ping()
|
|
450
464
|
|
|
@@ -491,7 +505,16 @@ class AsyncServer(base_server.BaseServer):
|
|
|
491
505
|
if asyncio.iscoroutinefunction(self.handlers[event]):
|
|
492
506
|
async def run_async_handler():
|
|
493
507
|
try:
|
|
494
|
-
|
|
508
|
+
try:
|
|
509
|
+
return await self.handlers[event](*args)
|
|
510
|
+
except TypeError:
|
|
511
|
+
if event == 'disconnect' and \
|
|
512
|
+
len(args) == 2: # pragma: no branch
|
|
513
|
+
# legacy disconnect events do not have a reason
|
|
514
|
+
# argument
|
|
515
|
+
return await self.handlers[event](args[0])
|
|
516
|
+
else: # pragma: no cover
|
|
517
|
+
raise
|
|
495
518
|
except asyncio.CancelledError: # pragma: no cover
|
|
496
519
|
pass
|
|
497
520
|
except:
|
|
@@ -510,7 +533,16 @@ class AsyncServer(base_server.BaseServer):
|
|
|
510
533
|
else:
|
|
511
534
|
async def run_sync_handler():
|
|
512
535
|
try:
|
|
513
|
-
|
|
536
|
+
try:
|
|
537
|
+
return self.handlers[event](*args)
|
|
538
|
+
except TypeError:
|
|
539
|
+
if event == 'disconnect' and \
|
|
540
|
+
len(args) == 2: # pragma: no branch
|
|
541
|
+
# legacy disconnect events do not have a reason
|
|
542
|
+
# argument
|
|
543
|
+
return self.handlers[event](args[0])
|
|
544
|
+
else: # pragma: no cover
|
|
545
|
+
raise
|
|
514
546
|
except:
|
|
515
547
|
self.logger.exception(event + ' handler error')
|
|
516
548
|
if event == 'connect':
|
|
@@ -528,6 +560,7 @@ class AsyncServer(base_server.BaseServer):
|
|
|
528
560
|
|
|
529
561
|
async def _service_task(self): # pragma: no cover
|
|
530
562
|
"""Monitor connected clients and clean up those that time out."""
|
|
563
|
+
loop = asyncio.get_running_loop()
|
|
531
564
|
self.service_task_event = self.create_event()
|
|
532
565
|
while not self.service_task_event.is_set():
|
|
533
566
|
if len(self.sockets) == 0:
|
|
@@ -569,7 +602,7 @@ class AsyncServer(base_server.BaseServer):
|
|
|
569
602
|
self.logger.info('service task canceled')
|
|
570
603
|
break
|
|
571
604
|
except:
|
|
572
|
-
if
|
|
605
|
+
if loop.is_closed():
|
|
573
606
|
self.logger.info('event loop is closed, exiting service '
|
|
574
607
|
'task')
|
|
575
608
|
break
|