python-engineio 4.10.0__py3-none-any.whl → 4.11.0__py3-none-any.whl
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.
- engineio/async_client.py +37 -9
- engineio/async_drivers/_websocket_wsgi.py +1 -1
- engineio/async_drivers/aiohttp.py +3 -3
- engineio/async_drivers/asgi.py +20 -10
- engineio/async_drivers/eventlet.py +0 -2
- engineio/async_drivers/gevent_uwsgi.py +4 -4
- engineio/async_drivers/sanic.py +4 -4
- engineio/async_drivers/tornado.py +3 -3
- engineio/async_server.py +71 -37
- engineio/async_socket.py +16 -9
- engineio/base_client.py +13 -1
- engineio/base_server.py +13 -0
- engineio/base_socket.py +0 -1
- engineio/client.py +24 -8
- engineio/middleware.py +2 -2
- engineio/packet.py +1 -1
- engineio/payload.py +1 -1
- engineio/server.py +49 -31
- engineio/socket.py +11 -6
- {python_engineio-4.10.0.dist-info → python_engineio-4.11.0.dist-info}/METADATA +7 -7
- python_engineio-4.11.0.dist-info/RECORD +31 -0
- {python_engineio-4.10.0.dist-info → python_engineio-4.11.0.dist-info}/WHEEL +1 -1
- {python_engineio-4.10.0.dist-info → python_engineio-4.11.0.dist-info}/top_level.txt +0 -0
- python_engineio-4.10.0.dist-info/RECORD +0 -31
- {python_engineio-4.10.0.dist-info → python_engineio-4.11.0.dist-info}/LICENSE +0 -0
engineio/async_client.py
CHANGED
|
@@ -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
|
|
engineio/async_drivers/asgi.py
CHANGED
|
@@ -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
|
|
|
@@ -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
|
engineio/async_drivers/sanic.py
CHANGED
|
@@ -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
|
|
engineio/async_server.py
CHANGED
|
@@ -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):
|
|
@@ -269,11 +272,11 @@ class AsyncServer(base_server.BaseServer):
|
|
|
269
272
|
'bad-jsonp-index')
|
|
270
273
|
r = self._bad_request('Invalid JSONP index number')
|
|
271
274
|
elif method == 'GET':
|
|
275
|
+
upgrade_header = environ.get('HTTP_UPGRADE').lower() \
|
|
276
|
+
if 'HTTP_UPGRADE' in environ else None
|
|
272
277
|
if sid is None:
|
|
273
278
|
# transport must be one of 'polling' or 'websocket'.
|
|
274
279
|
# if 'websocket', the HTTP_UPGRADE header must match.
|
|
275
|
-
upgrade_header = environ.get('HTTP_UPGRADE').lower() \
|
|
276
|
-
if 'HTTP_UPGRADE' in environ else None
|
|
277
280
|
if transport == 'polling' \
|
|
278
281
|
or transport == upgrade_header == 'websocket':
|
|
279
282
|
r = await self._handle_connect(environ, transport,
|
|
@@ -284,32 +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
|
-
|
|
294
|
-
|
|
295
|
-
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}')
|
|
296
298
|
else:
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
await
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
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]
|
|
309
321
|
elif method == 'POST':
|
|
310
322
|
if sid is None or sid not in self.sockets:
|
|
311
|
-
self._log_error_once('Invalid session '
|
|
312
|
-
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}')
|
|
313
325
|
else:
|
|
314
326
|
socket = self._get_socket(sid)
|
|
315
327
|
try:
|
|
@@ -439,11 +451,14 @@ class AsyncServer(base_server.BaseServer):
|
|
|
439
451
|
s = async_socket.AsyncSocket(self, sid)
|
|
440
452
|
self.sockets[sid] = s
|
|
441
453
|
|
|
442
|
-
pkt = packet.Packet(
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
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
|
+
})
|
|
447
462
|
await s.send(pkt)
|
|
448
463
|
s.schedule_ping()
|
|
449
464
|
|
|
@@ -490,7 +505,16 @@ class AsyncServer(base_server.BaseServer):
|
|
|
490
505
|
if asyncio.iscoroutinefunction(self.handlers[event]):
|
|
491
506
|
async def run_async_handler():
|
|
492
507
|
try:
|
|
493
|
-
|
|
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
|
|
494
518
|
except asyncio.CancelledError: # pragma: no cover
|
|
495
519
|
pass
|
|
496
520
|
except:
|
|
@@ -509,7 +533,16 @@ class AsyncServer(base_server.BaseServer):
|
|
|
509
533
|
else:
|
|
510
534
|
async def run_sync_handler():
|
|
511
535
|
try:
|
|
512
|
-
|
|
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
|
|
513
546
|
except:
|
|
514
547
|
self.logger.exception(event + ' handler error')
|
|
515
548
|
if event == 'connect':
|
|
@@ -527,6 +560,7 @@ class AsyncServer(base_server.BaseServer):
|
|
|
527
560
|
|
|
528
561
|
async def _service_task(self): # pragma: no cover
|
|
529
562
|
"""Monitor connected clients and clean up those that time out."""
|
|
563
|
+
loop = asyncio.get_running_loop()
|
|
530
564
|
self.service_task_event = self.create_event()
|
|
531
565
|
while not self.service_task_event.is_set():
|
|
532
566
|
if len(self.sockets) == 0:
|
|
@@ -568,7 +602,7 @@ class AsyncServer(base_server.BaseServer):
|
|
|
568
602
|
self.logger.info('service task canceled')
|
|
569
603
|
break
|
|
570
604
|
except:
|
|
571
|
-
if
|
|
605
|
+
if loop.is_closed():
|
|
572
606
|
self.logger.info('event loop is closed, exiting service '
|
|
573
607
|
'task')
|
|
574
608
|
break
|
engineio/async_socket.py
CHANGED
|
@@ -47,7 +47,8 @@ class AsyncSocket(base_socket.BaseSocket):
|
|
|
47
47
|
elif pkt.packet_type == packet.UPGRADE:
|
|
48
48
|
await self.send(packet.Packet(packet.NOOP))
|
|
49
49
|
elif pkt.packet_type == packet.CLOSE:
|
|
50
|
-
await self.close(wait=False, abort=True
|
|
50
|
+
await self.close(wait=False, abort=True,
|
|
51
|
+
reason=self.server.reason.CLIENT_DISCONNECT)
|
|
51
52
|
else:
|
|
52
53
|
raise exceptions.UnknownPacketError()
|
|
53
54
|
|
|
@@ -62,7 +63,8 @@ class AsyncSocket(base_socket.BaseSocket):
|
|
|
62
63
|
# Passing abort=False here will cause close() to write a
|
|
63
64
|
# CLOSE packet. This has the effect of updating half-open sockets
|
|
64
65
|
# to their correct state of disconnected
|
|
65
|
-
await self.close(wait=False, abort=False
|
|
66
|
+
await self.close(wait=False, abort=False,
|
|
67
|
+
reason=self.server.reason.PING_TIMEOUT)
|
|
66
68
|
return False
|
|
67
69
|
return True
|
|
68
70
|
|
|
@@ -95,7 +97,8 @@ class AsyncSocket(base_socket.BaseSocket):
|
|
|
95
97
|
packets = await self.poll()
|
|
96
98
|
except exceptions.QueueEmpty:
|
|
97
99
|
exc = sys.exc_info()
|
|
98
|
-
await self.close(wait=False
|
|
100
|
+
await self.close(wait=False,
|
|
101
|
+
reason=self.server.reason.TRANSPORT_ERROR)
|
|
99
102
|
raise exc[1].with_traceback(exc[2])
|
|
100
103
|
return packets
|
|
101
104
|
|
|
@@ -110,11 +113,14 @@ class AsyncSocket(base_socket.BaseSocket):
|
|
|
110
113
|
for pkt in p.packets:
|
|
111
114
|
await self.receive(pkt)
|
|
112
115
|
|
|
113
|
-
async def close(self, wait=True, abort=False):
|
|
116
|
+
async def close(self, wait=True, abort=False, reason=None):
|
|
114
117
|
"""Close the socket connection."""
|
|
115
118
|
if not self.closed and not self.closing:
|
|
116
119
|
self.closing = True
|
|
117
|
-
await self.server._trigger_event(
|
|
120
|
+
await self.server._trigger_event(
|
|
121
|
+
'disconnect', self.sid,
|
|
122
|
+
reason or self.server.reason.SERVER_DISCONNECT,
|
|
123
|
+
run_async=False)
|
|
118
124
|
if not abort:
|
|
119
125
|
await self.send(packet.Packet(packet.CLOSE))
|
|
120
126
|
self.closed = True
|
|
@@ -134,7 +140,7 @@ class AsyncSocket(base_socket.BaseSocket):
|
|
|
134
140
|
async def _upgrade_websocket(self, environ):
|
|
135
141
|
"""Upgrade the connection from polling to websocket."""
|
|
136
142
|
if self.upgraded:
|
|
137
|
-
raise
|
|
143
|
+
raise OSError('Socket has been upgraded already')
|
|
138
144
|
if self.server._async['websocket'] is None:
|
|
139
145
|
# the selected async mode does not support websocket
|
|
140
146
|
return self.server._bad_request()
|
|
@@ -156,7 +162,7 @@ class AsyncSocket(base_socket.BaseSocket):
|
|
|
156
162
|
|
|
157
163
|
try:
|
|
158
164
|
pkt = await websocket_wait()
|
|
159
|
-
except
|
|
165
|
+
except OSError: # pragma: no cover
|
|
160
166
|
return
|
|
161
167
|
decoded_pkt = packet.Packet(encoded_packet=pkt)
|
|
162
168
|
if decoded_pkt.packet_type != packet.PING or \
|
|
@@ -170,7 +176,7 @@ class AsyncSocket(base_socket.BaseSocket):
|
|
|
170
176
|
|
|
171
177
|
try:
|
|
172
178
|
pkt = await websocket_wait()
|
|
173
|
-
except
|
|
179
|
+
except OSError: # pragma: no cover
|
|
174
180
|
self.upgrading = False
|
|
175
181
|
return
|
|
176
182
|
decoded_pkt = packet.Packet(encoded_packet=pkt)
|
|
@@ -251,4 +257,5 @@ class AsyncSocket(base_socket.BaseSocket):
|
|
|
251
257
|
|
|
252
258
|
await self.queue.put(None) # unlock the writer task so it can exit
|
|
253
259
|
await asyncio.wait_for(writer_task, timeout=None)
|
|
254
|
-
await self.close(wait=False, abort=True
|
|
260
|
+
await self.close(wait=False, abort=True,
|
|
261
|
+
reason=self.server.reason.TRANSPORT_CLOSE)
|
engineio/base_client.py
CHANGED
|
@@ -30,9 +30,18 @@ original_signal_handler = None
|
|
|
30
30
|
class BaseClient:
|
|
31
31
|
event_names = ['connect', 'disconnect', 'message']
|
|
32
32
|
|
|
33
|
+
class reason:
|
|
34
|
+
"""Disconnection reasons."""
|
|
35
|
+
#: Client-initiated disconnection.
|
|
36
|
+
CLIENT_DISCONNECT = 'client disconnect'
|
|
37
|
+
#: Server-initiated disconnection.
|
|
38
|
+
SERVER_DISCONNECT = 'server disconnect'
|
|
39
|
+
#: Transport error.
|
|
40
|
+
TRANSPORT_ERROR = 'transport error'
|
|
41
|
+
|
|
33
42
|
def __init__(self, logger=False, json=None, request_timeout=5,
|
|
34
43
|
http_session=None, ssl_verify=True, handle_sigint=True,
|
|
35
|
-
websocket_extra_options=None):
|
|
44
|
+
websocket_extra_options=None, timestamp_requests=True):
|
|
36
45
|
global original_signal_handler
|
|
37
46
|
if handle_sigint and original_signal_handler is None and \
|
|
38
47
|
threading.current_thread() == threading.main_thread():
|
|
@@ -56,6 +65,7 @@ class BaseClient:
|
|
|
56
65
|
self.state = 'disconnected'
|
|
57
66
|
self.ssl_verify = ssl_verify
|
|
58
67
|
self.websocket_extra_options = websocket_extra_options or {}
|
|
68
|
+
self.timestamp_requests = timestamp_requests
|
|
59
69
|
|
|
60
70
|
if json is not None:
|
|
61
71
|
packet.Packet.json = json
|
|
@@ -143,4 +153,6 @@ class BaseClient:
|
|
|
143
153
|
|
|
144
154
|
def _get_url_timestamp(self):
|
|
145
155
|
"""Generate the Engine.IO query string timestamp."""
|
|
156
|
+
if not self.timestamp_requests:
|
|
157
|
+
return ''
|
|
146
158
|
return '&t=' + str(time.time())
|
engineio/base_server.py
CHANGED
|
@@ -19,6 +19,19 @@ class BaseServer:
|
|
|
19
19
|
_default_monitor_clients = True
|
|
20
20
|
sequence_number = 0
|
|
21
21
|
|
|
22
|
+
class reason:
|
|
23
|
+
"""Disconnection reasons."""
|
|
24
|
+
#: Server-initiated disconnection.
|
|
25
|
+
SERVER_DISCONNECT = 'server disconnect'
|
|
26
|
+
#: Client-initiated disconnection.
|
|
27
|
+
CLIENT_DISCONNECT = 'client disconnect'
|
|
28
|
+
#: Ping timeout.
|
|
29
|
+
PING_TIMEOUT = 'ping timeout'
|
|
30
|
+
#: Transport close.
|
|
31
|
+
TRANSPORT_CLOSE = 'transport close'
|
|
32
|
+
#: Transport error.
|
|
33
|
+
TRANSPORT_ERROR = 'transport error'
|
|
34
|
+
|
|
22
35
|
def __init__(self, async_mode=None, ping_interval=25, ping_timeout=20,
|
|
23
36
|
max_http_buffer_size=1000000, allow_upgrades=True,
|
|
24
37
|
http_compression=True, compression_threshold=1024,
|
engineio/base_socket.py
CHANGED
engineio/client.py
CHANGED
|
@@ -55,6 +55,9 @@ class Client(base_client.BaseClient):
|
|
|
55
55
|
:param websocket_extra_options: Dictionary containing additional keyword
|
|
56
56
|
arguments passed to
|
|
57
57
|
``websocket.create_connection()``.
|
|
58
|
+
:param timestamp_requests: If ``True`` a timestamp is added to the query
|
|
59
|
+
string of Socket.IO requests as a cache-busting
|
|
60
|
+
measure. Set to ``False`` to disable.
|
|
58
61
|
"""
|
|
59
62
|
def connect(self, url, headers=None, transports=None,
|
|
60
63
|
engineio_path='engine.io'):
|
|
@@ -110,7 +113,7 @@ class Client(base_client.BaseClient):
|
|
|
110
113
|
"""
|
|
111
114
|
self._send_packet(packet.Packet(packet.MESSAGE, data=data))
|
|
112
115
|
|
|
113
|
-
def disconnect(self, abort=False):
|
|
116
|
+
def disconnect(self, abort=False, reason=None):
|
|
114
117
|
"""Disconnect from the server.
|
|
115
118
|
|
|
116
119
|
:param abort: If set to ``True``, do not wait for background tasks
|
|
@@ -120,7 +123,9 @@ class Client(base_client.BaseClient):
|
|
|
120
123
|
self._send_packet(packet.Packet(packet.CLOSE))
|
|
121
124
|
self.queue.put(None)
|
|
122
125
|
self.state = 'disconnecting'
|
|
123
|
-
self._trigger_event('disconnect',
|
|
126
|
+
self._trigger_event('disconnect',
|
|
127
|
+
reason or self.reason.CLIENT_DISCONNECT,
|
|
128
|
+
run_async=False)
|
|
124
129
|
if self.current_transport == 'websocket':
|
|
125
130
|
self.ws.close()
|
|
126
131
|
if not abort:
|
|
@@ -251,7 +256,7 @@ class Client(base_client.BaseClient):
|
|
|
251
256
|
extra_options = {}
|
|
252
257
|
if self.http:
|
|
253
258
|
# cookies
|
|
254
|
-
cookies = '; '.join(["{}={
|
|
259
|
+
cookies = '; '.join([f"{cookie.name}={cookie.value}"
|
|
255
260
|
for cookie in self.http.cookies])
|
|
256
261
|
for header, value in headers.items():
|
|
257
262
|
if header.lower() == 'cookie':
|
|
@@ -325,7 +330,7 @@ class Client(base_client.BaseClient):
|
|
|
325
330
|
try:
|
|
326
331
|
ws = websocket.create_connection(
|
|
327
332
|
websocket_url + self._get_url_timestamp(), **extra_options)
|
|
328
|
-
except (ConnectionError,
|
|
333
|
+
except (ConnectionError, OSError, websocket.WebSocketException):
|
|
329
334
|
if upgrade:
|
|
330
335
|
self.logger.warning(
|
|
331
336
|
'WebSocket upgrade failed: connection error')
|
|
@@ -404,7 +409,7 @@ class Client(base_client.BaseClient):
|
|
|
404
409
|
elif pkt.packet_type == packet.PING:
|
|
405
410
|
self._send_packet(packet.Packet(packet.PONG, pkt.data))
|
|
406
411
|
elif pkt.packet_type == packet.CLOSE:
|
|
407
|
-
self.disconnect(abort=True)
|
|
412
|
+
self.disconnect(abort=True, reason=self.reason.SERVER_DISCONNECT)
|
|
408
413
|
elif pkt.packet_type == packet.NOOP:
|
|
409
414
|
pass
|
|
410
415
|
else:
|
|
@@ -444,7 +449,16 @@ class Client(base_client.BaseClient):
|
|
|
444
449
|
return self.start_background_task(self.handlers[event], *args)
|
|
445
450
|
else:
|
|
446
451
|
try:
|
|
447
|
-
|
|
452
|
+
try:
|
|
453
|
+
return self.handlers[event](*args)
|
|
454
|
+
except TypeError:
|
|
455
|
+
if event == 'disconnect' and \
|
|
456
|
+
len(args) == 1: # pragma: no branch
|
|
457
|
+
# legacy disconnect events do not have a reason
|
|
458
|
+
# argument
|
|
459
|
+
return self.handlers[event]()
|
|
460
|
+
else: # pragma: no cover
|
|
461
|
+
raise
|
|
448
462
|
except:
|
|
449
463
|
self.logger.exception(event + ' handler error')
|
|
450
464
|
|
|
@@ -480,7 +494,8 @@ class Client(base_client.BaseClient):
|
|
|
480
494
|
self.logger.info('Waiting for write loop task to end')
|
|
481
495
|
self.write_loop_task.join()
|
|
482
496
|
if self.state == 'connected':
|
|
483
|
-
self._trigger_event('disconnect',
|
|
497
|
+
self._trigger_event('disconnect', self.reason.TRANSPORT_ERROR,
|
|
498
|
+
run_async=False)
|
|
484
499
|
try:
|
|
485
500
|
base_client.connected_clients.remove(self)
|
|
486
501
|
except ValueError: # pragma: no cover
|
|
@@ -530,7 +545,8 @@ class Client(base_client.BaseClient):
|
|
|
530
545
|
self.logger.info('Waiting for write loop task to end')
|
|
531
546
|
self.write_loop_task.join()
|
|
532
547
|
if self.state == 'connected':
|
|
533
|
-
self._trigger_event('disconnect',
|
|
548
|
+
self._trigger_event('disconnect', self.reason.TRANSPORT_ERROR,
|
|
549
|
+
run_async=False)
|
|
534
550
|
try:
|
|
535
551
|
base_client.connected_clients.remove(self)
|
|
536
552
|
except ValueError: # pragma: no cover
|
engineio/middleware.py
CHANGED
|
@@ -2,7 +2,7 @@ import os
|
|
|
2
2
|
from engineio.static_files import get_static_file
|
|
3
3
|
|
|
4
4
|
|
|
5
|
-
class WSGIApp
|
|
5
|
+
class WSGIApp:
|
|
6
6
|
"""WSGI application middleware for Engine.IO.
|
|
7
7
|
|
|
8
8
|
This middleware dispatches traffic to an Engine.IO application. It can
|
|
@@ -50,7 +50,7 @@ class WSGIApp(object):
|
|
|
50
50
|
# directly into the environment. To give eventlet's WebSocket
|
|
51
51
|
# module access to this socket when running under gunicorn, here we
|
|
52
52
|
# copy the socket to the eventlet format.
|
|
53
|
-
class Input
|
|
53
|
+
class Input:
|
|
54
54
|
def __init__(self, socket):
|
|
55
55
|
self.socket = socket
|
|
56
56
|
|
engineio/packet.py
CHANGED
engineio/payload.py
CHANGED
engineio/server.py
CHANGED
|
@@ -148,7 +148,7 @@ class Server(base_server.BaseServer):
|
|
|
148
148
|
with eio.session(sid) as session:
|
|
149
149
|
print('received message from ', session['username'])
|
|
150
150
|
"""
|
|
151
|
-
class _session_context_manager
|
|
151
|
+
class _session_context_manager:
|
|
152
152
|
def __init__(self, server, sid):
|
|
153
153
|
self.server = server
|
|
154
154
|
self.sid = sid
|
|
@@ -176,12 +176,12 @@ class Server(base_server.BaseServer):
|
|
|
176
176
|
# the socket was already closed or gone
|
|
177
177
|
pass
|
|
178
178
|
else:
|
|
179
|
-
socket.close()
|
|
179
|
+
socket.close(reason=self.reason.SERVER_DISCONNECT)
|
|
180
180
|
if sid in self.sockets: # pragma: no cover
|
|
181
181
|
del self.sockets[sid]
|
|
182
182
|
else:
|
|
183
183
|
for client in self.sockets.copy().values():
|
|
184
|
-
client.close()
|
|
184
|
+
client.close(reason=self.reason.SERVER_DISCONNECT)
|
|
185
185
|
self.sockets = {}
|
|
186
186
|
|
|
187
187
|
def handle_request(self, environ, start_response):
|
|
@@ -251,11 +251,11 @@ class Server(base_server.BaseServer):
|
|
|
251
251
|
'bad-jsonp-index')
|
|
252
252
|
r = self._bad_request('Invalid JSONP index number')
|
|
253
253
|
elif method == 'GET':
|
|
254
|
+
upgrade_header = environ.get('HTTP_UPGRADE').lower() \
|
|
255
|
+
if 'HTTP_UPGRADE' in environ else None
|
|
254
256
|
if sid is None:
|
|
255
257
|
# transport must be one of 'polling' or 'websocket'.
|
|
256
258
|
# if 'websocket', the HTTP_UPGRADE header must match.
|
|
257
|
-
upgrade_header = environ.get('HTTP_UPGRADE').lower() \
|
|
258
|
-
if 'HTTP_UPGRADE' in environ else None
|
|
259
259
|
if transport == 'polling' \
|
|
260
260
|
or transport == upgrade_header == 'websocket':
|
|
261
261
|
r = self._handle_connect(environ, start_response,
|
|
@@ -266,34 +266,41 @@ class Server(base_server.BaseServer):
|
|
|
266
266
|
r = self._bad_request('Invalid websocket upgrade')
|
|
267
267
|
else:
|
|
268
268
|
if sid not in self.sockets:
|
|
269
|
-
self._log_error_once('Invalid session '
|
|
270
|
-
r = self._bad_request('Invalid session')
|
|
269
|
+
self._log_error_once(f'Invalid session {sid}', 'bad-sid')
|
|
270
|
+
r = self._bad_request(f'Invalid session {sid}')
|
|
271
271
|
else:
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
r = self._bad_request('Invalid transport')
|
|
272
|
+
try:
|
|
273
|
+
socket = self._get_socket(sid)
|
|
274
|
+
except KeyError as e: # pragma: no cover
|
|
275
|
+
self._log_error_once(f'{e} {sid}', 'bad-sid')
|
|
276
|
+
r = self._bad_request(f'{e} {sid}')
|
|
278
277
|
else:
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
278
|
+
if self.transport(sid) != transport and \
|
|
279
|
+
transport != upgrade_header:
|
|
280
|
+
self._log_error_once(
|
|
281
|
+
f'Invalid transport for session {sid}',
|
|
282
|
+
'bad-transport')
|
|
283
|
+
r = self._bad_request('Invalid transport')
|
|
284
|
+
else:
|
|
285
|
+
try:
|
|
286
|
+
packets = socket.handle_get_request(
|
|
287
|
+
environ, start_response)
|
|
288
|
+
if isinstance(packets, list):
|
|
289
|
+
r = self._ok(packets,
|
|
290
|
+
jsonp_index=jsonp_index)
|
|
291
|
+
else:
|
|
292
|
+
r = packets
|
|
293
|
+
except exceptions.EngineIOError:
|
|
294
|
+
if sid in self.sockets: # pragma: no cover
|
|
295
|
+
self.disconnect(sid)
|
|
296
|
+
r = self._bad_request()
|
|
297
|
+
if sid in self.sockets and \
|
|
298
|
+
self.sockets[sid].closed:
|
|
299
|
+
del self.sockets[sid]
|
|
292
300
|
elif method == 'POST':
|
|
293
301
|
if sid is None or sid not in self.sockets:
|
|
294
|
-
self._log_error_once(
|
|
295
|
-
|
|
296
|
-
r = self._bad_request('Invalid session')
|
|
302
|
+
self._log_error_once(f'Invalid session {sid}', 'bad-sid')
|
|
303
|
+
r = self._bad_request(f'Invalid session {sid}')
|
|
297
304
|
else:
|
|
298
305
|
socket = self._get_socket(sid)
|
|
299
306
|
try:
|
|
@@ -389,7 +396,9 @@ class Server(base_server.BaseServer):
|
|
|
389
396
|
'upgrades': self._upgrades(sid, transport),
|
|
390
397
|
'pingTimeout': int(self.ping_timeout * 1000),
|
|
391
398
|
'pingInterval': int(
|
|
392
|
-
self.ping_interval + self.ping_interval_grace_period) * 1000
|
|
399
|
+
self.ping_interval + self.ping_interval_grace_period) * 1000,
|
|
400
|
+
'maxPayload': self.max_http_buffer_size,
|
|
401
|
+
})
|
|
393
402
|
s.send(pkt)
|
|
394
403
|
s.schedule_ping()
|
|
395
404
|
|
|
@@ -437,7 +446,16 @@ class Server(base_server.BaseServer):
|
|
|
437
446
|
if event in self.handlers:
|
|
438
447
|
def run_handler():
|
|
439
448
|
try:
|
|
440
|
-
|
|
449
|
+
try:
|
|
450
|
+
return self.handlers[event](*args)
|
|
451
|
+
except TypeError:
|
|
452
|
+
if event == 'disconnect' and \
|
|
453
|
+
len(args) == 2: # pragma: no branch
|
|
454
|
+
# legacy disconnect events do not have a reason
|
|
455
|
+
# argument
|
|
456
|
+
return self.handlers[event](args[0])
|
|
457
|
+
else: # pragma: no cover
|
|
458
|
+
raise
|
|
441
459
|
except:
|
|
442
460
|
self.logger.exception(event + ' handler error')
|
|
443
461
|
if event == 'connect':
|
engineio/socket.py
CHANGED
|
@@ -48,7 +48,8 @@ class Socket(base_socket.BaseSocket):
|
|
|
48
48
|
elif pkt.packet_type == packet.UPGRADE:
|
|
49
49
|
self.send(packet.Packet(packet.NOOP))
|
|
50
50
|
elif pkt.packet_type == packet.CLOSE:
|
|
51
|
-
self.close(wait=False, abort=True
|
|
51
|
+
self.close(wait=False, abort=True,
|
|
52
|
+
reason=self.server.reason.CLIENT_DISCONNECT)
|
|
52
53
|
else:
|
|
53
54
|
raise exceptions.UnknownPacketError()
|
|
54
55
|
|
|
@@ -63,7 +64,8 @@ class Socket(base_socket.BaseSocket):
|
|
|
63
64
|
# Passing abort=False here will cause close() to write a
|
|
64
65
|
# CLOSE packet. This has the effect of updating half-open sockets
|
|
65
66
|
# to their correct state of disconnected
|
|
66
|
-
self.close(wait=False, abort=False
|
|
67
|
+
self.close(wait=False, abort=False,
|
|
68
|
+
reason=self.server.reason.PING_TIMEOUT)
|
|
67
69
|
return False
|
|
68
70
|
return True
|
|
69
71
|
|
|
@@ -97,7 +99,7 @@ class Socket(base_socket.BaseSocket):
|
|
|
97
99
|
packets = self.poll()
|
|
98
100
|
except exceptions.QueueEmpty:
|
|
99
101
|
exc = sys.exc_info()
|
|
100
|
-
self.close(wait=False)
|
|
102
|
+
self.close(wait=False, reason=self.server.reason.TRANSPORT_ERROR)
|
|
101
103
|
raise exc[1].with_traceback(exc[2])
|
|
102
104
|
return packets
|
|
103
105
|
|
|
@@ -112,11 +114,14 @@ class Socket(base_socket.BaseSocket):
|
|
|
112
114
|
for pkt in p.packets:
|
|
113
115
|
self.receive(pkt)
|
|
114
116
|
|
|
115
|
-
def close(self, wait=True, abort=False):
|
|
117
|
+
def close(self, wait=True, abort=False, reason=None):
|
|
116
118
|
"""Close the socket connection."""
|
|
117
119
|
if not self.closed and not self.closing:
|
|
118
120
|
self.closing = True
|
|
119
|
-
self.server._trigger_event(
|
|
121
|
+
self.server._trigger_event(
|
|
122
|
+
'disconnect', self.sid,
|
|
123
|
+
reason or self.server.reason.SERVER_DISCONNECT,
|
|
124
|
+
run_async=False)
|
|
120
125
|
if not abort:
|
|
121
126
|
self.send(packet.Packet(packet.CLOSE))
|
|
122
127
|
self.closed = True
|
|
@@ -137,7 +142,7 @@ class Socket(base_socket.BaseSocket):
|
|
|
137
142
|
def _upgrade_websocket(self, environ, start_response):
|
|
138
143
|
"""Upgrade the connection from polling to websocket."""
|
|
139
144
|
if self.upgraded:
|
|
140
|
-
raise
|
|
145
|
+
raise OSError('Socket has been upgraded already')
|
|
141
146
|
if self.server._async['websocket'] is None:
|
|
142
147
|
# the selected async mode does not support websocket
|
|
143
148
|
return self.server._bad_request()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: python-engineio
|
|
3
|
-
Version: 4.
|
|
3
|
+
Version: 4.11.0
|
|
4
4
|
Summary: Engine.IO server and client for Python
|
|
5
5
|
Author-email: Miguel Grinberg <miguel.grinberg@gmail.com>
|
|
6
6
|
Project-URL: Homepage, https://github.com/miguelgrinberg/python-engineio
|
|
@@ -13,14 +13,14 @@ Classifier: Operating System :: OS Independent
|
|
|
13
13
|
Requires-Python: >=3.6
|
|
14
14
|
Description-Content-Type: text/markdown
|
|
15
15
|
License-File: LICENSE
|
|
16
|
-
Requires-Dist: simple-websocket
|
|
17
|
-
Provides-Extra: asyncio_client
|
|
18
|
-
Requires-Dist: aiohttp >=3.4 ; extra == 'asyncio_client'
|
|
16
|
+
Requires-Dist: simple-websocket>=0.10.0
|
|
19
17
|
Provides-Extra: client
|
|
20
|
-
Requires-Dist: requests
|
|
21
|
-
Requires-Dist: websocket-client
|
|
18
|
+
Requires-Dist: requests>=2.21.0; extra == "client"
|
|
19
|
+
Requires-Dist: websocket-client>=0.54.0; extra == "client"
|
|
20
|
+
Provides-Extra: asyncio-client
|
|
21
|
+
Requires-Dist: aiohttp>=3.4; extra == "asyncio-client"
|
|
22
22
|
Provides-Extra: docs
|
|
23
|
-
Requires-Dist: sphinx
|
|
23
|
+
Requires-Dist: sphinx; extra == "docs"
|
|
24
24
|
|
|
25
25
|
python-engineio
|
|
26
26
|
===============
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
engineio/__init__.py,sha256=0R2PY1EXu3sicP7mkA0_QxEVGRlFlgvsxfhByqREE1A,481
|
|
2
|
+
engineio/async_client.py,sha256=jBy7ytWawPpP3b_52U7YU7G85wbWjMsdw_29y3e_BqA,29548
|
|
3
|
+
engineio/async_server.py,sha256=8Af_uwf8mKOCJGqVKa3QN0kz7_B2K1cdpSmsvkAoBR4,27412
|
|
4
|
+
engineio/async_socket.py,sha256=nHY0DPPk0FtI9djUnQWtzZ3ce2OD184Tu-Dop7JLg9I,10715
|
|
5
|
+
engineio/base_client.py,sha256=uEI6OglvhdIGD1DAZuptbn0KXQMk_o3w0AH4wJvPwZ0,5322
|
|
6
|
+
engineio/base_server.py,sha256=H-aKQ9hMQjiIrjDaqbw_edw9yUVIbCQMzyIy9egLv-g,14458
|
|
7
|
+
engineio/base_socket.py,sha256=sQqbNSfGhMQG3xzwar6IXMal28C7Q5TIAQRGp74Wt2o,399
|
|
8
|
+
engineio/client.py,sha256=wfgB_MrpEh-v4dBt6W2eW6i0CRNEP0CIJ6d4Ap4CnZE,27227
|
|
9
|
+
engineio/exceptions.py,sha256=FyuMb5qhX9CUYP3fEoe1m-faU96ApdQTSbblaaoo8LA,292
|
|
10
|
+
engineio/json.py,sha256=SG5FTojqd1ix6u0dKXJsZVqqdYioZLO4S2GPL7BKl3U,405
|
|
11
|
+
engineio/middleware.py,sha256=5NKBXz-ftuFErUB_V9IDvRHaSOsjhtW-NnuJtquB1nc,3750
|
|
12
|
+
engineio/packet.py,sha256=Tejm9U5JcYs5LwZ_n_Xh0PIRv-U_JbHwGEivNXQN4eg,3181
|
|
13
|
+
engineio/payload.py,sha256=GIWu0Vnay4WNZlDxHqVgP34tKTBXX58OArJ-mO5zD3E,1539
|
|
14
|
+
engineio/server.py,sha256=4_xdtH0tyusigQurwOeDFrHtodthFTOIbOVZ-_ckh5U,22957
|
|
15
|
+
engineio/socket.py,sha256=K5gSY5PTi6rZy4meP9CtTYsNlEU2JmsonYDDHfJVFI4,10280
|
|
16
|
+
engineio/static_files.py,sha256=pwez9LQFaSQXMbtI0vLyD6UDiokQ4rNfmRYgVLKOthc,2064
|
|
17
|
+
engineio/async_drivers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
18
|
+
engineio/async_drivers/_websocket_wsgi.py,sha256=LuOEfKhbAw8SplB5PMpYKIUqfCPEadQEpqeiq_leOIA,949
|
|
19
|
+
engineio/async_drivers/aiohttp.py,sha256=OBDGhaNXWHxQkwhzZT2vlTAOqWReGS6Sjk9u3BEh_Mc,3754
|
|
20
|
+
engineio/async_drivers/asgi.py,sha256=mBu109j7R6esH-wI62jvTnfVAEjWvw2YNp0dZ74NyHg,11210
|
|
21
|
+
engineio/async_drivers/eventlet.py,sha256=n1y4OjPdj4J2GIep5N56O29oa5NQgFJVcTBjyO1C-Gs,1735
|
|
22
|
+
engineio/async_drivers/gevent.py,sha256=hnJHeWdDQE2jfoLCP5DnwVPzsQlcTLJUMA5EVf1UL-k,2962
|
|
23
|
+
engineio/async_drivers/gevent_uwsgi.py,sha256=m6ay5dov9FDQl0fbeiKeE-Orh5LiF6zLlYQ64Oa3T5g,5954
|
|
24
|
+
engineio/async_drivers/sanic.py,sha256=VvHZlkOBdHPkA620-Z9EdzpPp-O-KhMgS-h7RCuw_a4,4516
|
|
25
|
+
engineio/async_drivers/threading.py,sha256=ywmG59d4H6OHZjKarBN97-9BHEsRxFEz9YN-E9QAu_I,463
|
|
26
|
+
engineio/async_drivers/tornado.py,sha256=mbVHs1mECfzFSNv33uigkpTBtNPT0u49k5zaybewdIo,5893
|
|
27
|
+
python_engineio-4.11.0.dist-info/LICENSE,sha256=yel9Pbwfu82094CLKCzWRtuIev9PUxP-a76NTDFAWpw,1082
|
|
28
|
+
python_engineio-4.11.0.dist-info/METADATA,sha256=ptID9L-diLLf_PVNRydyfripogmQvTtlqerR9yURhMA,2237
|
|
29
|
+
python_engineio-4.11.0.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
|
|
30
|
+
python_engineio-4.11.0.dist-info/top_level.txt,sha256=u8PmNisCZLwRYcWrNLe9wutQ2tt4zNi8IH362c-HWuA,9
|
|
31
|
+
python_engineio-4.11.0.dist-info/RECORD,,
|
|
File without changes
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
engineio/__init__.py,sha256=0R2PY1EXu3sicP7mkA0_QxEVGRlFlgvsxfhByqREE1A,481
|
|
2
|
-
engineio/async_client.py,sha256=jC7T6r_ya9LV46q4OdIQ041-3uLlRr6aUt__YmHNPEM,27950
|
|
3
|
-
engineio/async_server.py,sha256=q29JCvIpCriUXZoqHwjJUwV2bK2MRypMQptROGw6imI,25738
|
|
4
|
-
engineio/async_socket.py,sha256=P8OZW1N5y7jr2hW1qLLK2y_Clt7aHdv2ucYW69mbU4Q,10305
|
|
5
|
-
engineio/base_client.py,sha256=Q_w0Stvy89wPHwWk7411dRxpGjxNPypm4xy_4XMPZ9M,4872
|
|
6
|
-
engineio/base_server.py,sha256=Em2RRpbohulKjmYTWHnf_lALKvTT_jHr442Rr2g_BEQ,14013
|
|
7
|
-
engineio/base_socket.py,sha256=Bw6TWv1pnlXcDdqT59C5CyTaBi7l-_mkykIUuZLjz-g,400
|
|
8
|
-
engineio/client.py,sha256=m_eLSes3b-mE5MYajELquSktkAWceCNGZOtUU78as50,26285
|
|
9
|
-
engineio/exceptions.py,sha256=FyuMb5qhX9CUYP3fEoe1m-faU96ApdQTSbblaaoo8LA,292
|
|
10
|
-
engineio/json.py,sha256=SG5FTojqd1ix6u0dKXJsZVqqdYioZLO4S2GPL7BKl3U,405
|
|
11
|
-
engineio/middleware.py,sha256=BF_qHAIZZnIbfiP256SD1CX3kzNWbSuto1cpih8oIFg,3766
|
|
12
|
-
engineio/packet.py,sha256=ETMeLgdpZghXK9fth93IZO8pIft6Sg3d1QGpyTx4xBE,3189
|
|
13
|
-
engineio/payload.py,sha256=2iLIFgIweTWkLok_UZ5zCgELmRSGyUUI5eeYcEerFSs,1547
|
|
14
|
-
engineio/server.py,sha256=v0QnoLCD1ReVAhzQRn5keHmjZ61jEJXNQeikfXaELl0,21926
|
|
15
|
-
engineio/socket.py,sha256=dasec3jXoV2eR1jmIrhEIfHzyUuuv8FwvxLB3DpyjeY,9996
|
|
16
|
-
engineio/static_files.py,sha256=pwez9LQFaSQXMbtI0vLyD6UDiokQ4rNfmRYgVLKOthc,2064
|
|
17
|
-
engineio/async_drivers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
18
|
-
engineio/async_drivers/_websocket_wsgi.py,sha256=FGoRBGOUsEtlfklYNylR0o3oLOGmXIR8QzaCsGLgr3I,949
|
|
19
|
-
engineio/async_drivers/aiohttp.py,sha256=zJdujjO6dSL_oKDDr4xfO4ID_Vd8faEp1AOIW4ogKME,3768
|
|
20
|
-
engineio/async_drivers/asgi.py,sha256=mvCErNpYw1T7XE_1Ixb0YQN0YqJkKMpWqNV5_tZQYMg,11011
|
|
21
|
-
engineio/async_drivers/eventlet.py,sha256=_ZgPg0HTx_yhSm5iBcd3ez01yMg3KRFmb2ycKD8o8pY,1775
|
|
22
|
-
engineio/async_drivers/gevent.py,sha256=hnJHeWdDQE2jfoLCP5DnwVPzsQlcTLJUMA5EVf1UL-k,2962
|
|
23
|
-
engineio/async_drivers/gevent_uwsgi.py,sha256=cnjCsnDHTa6rKgwDKD6rLvIw1Yun-g4c1QbujOC_bMY,5962
|
|
24
|
-
engineio/async_drivers/sanic.py,sha256=SY0HIp5DUHF7B55tJCBB_8qDjrRTD_FyNQ2cIwRIGR8,4538
|
|
25
|
-
engineio/async_drivers/threading.py,sha256=ywmG59d4H6OHZjKarBN97-9BHEsRxFEz9YN-E9QAu_I,463
|
|
26
|
-
engineio/async_drivers/tornado.py,sha256=9bB7FvY47Snx_h4rsNwRk5wIINf2ju7hXWTAqF3intA,5909
|
|
27
|
-
python_engineio-4.10.0.dist-info/LICENSE,sha256=yel9Pbwfu82094CLKCzWRtuIev9PUxP-a76NTDFAWpw,1082
|
|
28
|
-
python_engineio-4.10.0.dist-info/METADATA,sha256=SaBVFlF_cqRa2i_sQ3Fdiaz6GdxT1qASxZpSugLD57A,2245
|
|
29
|
-
python_engineio-4.10.0.dist-info/WHEEL,sha256=GV9aMThwP_4oNCtvEC2ec3qUYutgWeAzklro_0m4WJQ,91
|
|
30
|
-
python_engineio-4.10.0.dist-info/top_level.txt,sha256=u8PmNisCZLwRYcWrNLe9wutQ2tt4zNi8IH362c-HWuA,9
|
|
31
|
-
python_engineio-4.10.0.dist-info/RECORD,,
|
|
File without changes
|