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.
Files changed (75) hide show
  1. {python_engineio-4.10.1/src/python_engineio.egg-info → python_engineio-4.11.0}/PKG-INFO +1 -1
  2. {python_engineio-4.10.1 → python_engineio-4.11.0}/docs/client.rst +17 -6
  3. {python_engineio-4.10.1 → python_engineio-4.11.0}/docs/conf.py +0 -1
  4. {python_engineio-4.10.1 → python_engineio-4.11.0}/docs/server.rst +17 -6
  5. {python_engineio-4.10.1 → python_engineio-4.11.0}/pyproject.toml +5 -1
  6. {python_engineio-4.10.1 → python_engineio-4.11.0}/src/engineio/async_client.py +37 -9
  7. {python_engineio-4.10.1 → python_engineio-4.11.0}/src/engineio/async_drivers/_websocket_wsgi.py +1 -1
  8. {python_engineio-4.10.1 → python_engineio-4.11.0}/src/engineio/async_drivers/aiohttp.py +3 -3
  9. {python_engineio-4.10.1 → python_engineio-4.11.0}/src/engineio/async_drivers/asgi.py +20 -10
  10. {python_engineio-4.10.1 → python_engineio-4.11.0}/src/engineio/async_drivers/eventlet.py +0 -2
  11. {python_engineio-4.10.1 → python_engineio-4.11.0}/src/engineio/async_drivers/gevent_uwsgi.py +4 -4
  12. {python_engineio-4.10.1 → python_engineio-4.11.0}/src/engineio/async_drivers/sanic.py +4 -4
  13. {python_engineio-4.10.1 → python_engineio-4.11.0}/src/engineio/async_drivers/tornado.py +3 -3
  14. {python_engineio-4.10.1 → python_engineio-4.11.0}/src/engineio/async_server.py +69 -36
  15. {python_engineio-4.10.1 → python_engineio-4.11.0}/src/engineio/async_socket.py +16 -9
  16. {python_engineio-4.10.1 → python_engineio-4.11.0}/src/engineio/base_client.py +13 -1
  17. {python_engineio-4.10.1 → python_engineio-4.11.0}/src/engineio/base_server.py +13 -0
  18. {python_engineio-4.10.1 → python_engineio-4.11.0}/src/engineio/base_socket.py +0 -1
  19. {python_engineio-4.10.1 → python_engineio-4.11.0}/src/engineio/client.py +24 -8
  20. {python_engineio-4.10.1 → python_engineio-4.11.0}/src/engineio/middleware.py +2 -2
  21. {python_engineio-4.10.1 → python_engineio-4.11.0}/src/engineio/packet.py +1 -1
  22. {python_engineio-4.10.1 → python_engineio-4.11.0}/src/engineio/payload.py +1 -1
  23. {python_engineio-4.10.1 → python_engineio-4.11.0}/src/engineio/server.py +47 -30
  24. {python_engineio-4.10.1 → python_engineio-4.11.0}/src/engineio/socket.py +11 -6
  25. {python_engineio-4.10.1 → python_engineio-4.11.0/src/python_engineio.egg-info}/PKG-INFO +1 -1
  26. {python_engineio-4.10.1 → python_engineio-4.11.0}/src/python_engineio.egg-info/SOURCES.txt +0 -0
  27. {python_engineio-4.10.1 → python_engineio-4.11.0}/src/python_engineio.egg-info/dependency_links.txt +0 -0
  28. {python_engineio-4.10.1 → python_engineio-4.11.0}/src/python_engineio.egg-info/not-zip-safe +0 -0
  29. {python_engineio-4.10.1 → python_engineio-4.11.0}/src/python_engineio.egg-info/requires.txt +0 -0
  30. {python_engineio-4.10.1 → python_engineio-4.11.0}/src/python_engineio.egg-info/top_level.txt +0 -0
  31. {python_engineio-4.10.1 → python_engineio-4.11.0}/tests/async/test_aiohttp.py +1 -2
  32. python_engineio-4.11.0/tests/async/test_asgi.py +577 -0
  33. {python_engineio-4.10.1 → python_engineio-4.11.0}/tests/async/test_client.py +492 -500
  34. {python_engineio-4.10.1 → python_engineio-4.11.0}/tests/async/test_server.py +239 -219
  35. {python_engineio-4.10.1 → python_engineio-4.11.0}/tests/async/test_socket.py +184 -194
  36. {python_engineio-4.10.1 → python_engineio-4.11.0}/tests/async/test_tornado.py +6 -13
  37. {python_engineio-4.10.1 → python_engineio-4.11.0}/tests/common/test_client.py +34 -7
  38. {python_engineio-4.10.1 → python_engineio-4.11.0}/tests/common/test_middleware.py +1 -2
  39. {python_engineio-4.10.1 → python_engineio-4.11.0}/tests/common/test_packet.py +1 -3
  40. {python_engineio-4.10.1 → python_engineio-4.11.0}/tests/common/test_payload.py +1 -3
  41. {python_engineio-4.10.1 → python_engineio-4.11.0}/tests/common/test_server.py +28 -8
  42. {python_engineio-4.10.1 → python_engineio-4.11.0}/tests/common/test_socket.py +18 -9
  43. {python_engineio-4.10.1 → python_engineio-4.11.0}/tox.ini +1 -0
  44. python_engineio-4.10.1/tests/async/test_asgi.py +0 -586
  45. {python_engineio-4.10.1 → python_engineio-4.11.0}/LICENSE +0 -0
  46. {python_engineio-4.10.1 → python_engineio-4.11.0}/MANIFEST.in +0 -0
  47. {python_engineio-4.10.1 → python_engineio-4.11.0}/README.md +0 -0
  48. {python_engineio-4.10.1 → python_engineio-4.11.0}/docs/Makefile +0 -0
  49. {python_engineio-4.10.1 → python_engineio-4.11.0}/docs/_static/README.md +0 -0
  50. {python_engineio-4.10.1 → python_engineio-4.11.0}/docs/api.rst +0 -0
  51. {python_engineio-4.10.1 → python_engineio-4.11.0}/docs/index.rst +0 -0
  52. {python_engineio-4.10.1 → python_engineio-4.11.0}/docs/intro.rst +0 -0
  53. {python_engineio-4.10.1 → python_engineio-4.11.0}/docs/make.bat +0 -0
  54. {python_engineio-4.10.1 → python_engineio-4.11.0}/setup.cfg +0 -0
  55. {python_engineio-4.10.1 → python_engineio-4.11.0}/src/engineio/__init__.py +0 -0
  56. {python_engineio-4.10.1 → python_engineio-4.11.0}/src/engineio/async_drivers/__init__.py +0 -0
  57. {python_engineio-4.10.1 → python_engineio-4.11.0}/src/engineio/async_drivers/gevent.py +0 -0
  58. {python_engineio-4.10.1 → python_engineio-4.11.0}/src/engineio/async_drivers/threading.py +0 -0
  59. {python_engineio-4.10.1 → python_engineio-4.11.0}/src/engineio/exceptions.py +0 -0
  60. {python_engineio-4.10.1 → python_engineio-4.11.0}/src/engineio/json.py +0 -0
  61. {python_engineio-4.10.1 → python_engineio-4.11.0}/src/engineio/static_files.py +0 -0
  62. {python_engineio-4.10.1 → python_engineio-4.11.0}/tests/__init__.py +0 -0
  63. {python_engineio-4.10.1 → python_engineio-4.11.0}/tests/async/__init__.py +0 -0
  64. {python_engineio-4.10.1 → python_engineio-4.11.0}/tests/async/index.html +0 -0
  65. {python_engineio-4.10.1 → python_engineio-4.11.0}/tests/async/test_sanic.py +0 -0
  66. {python_engineio-4.10.1 → python_engineio-4.11.0}/tests/common/__init__.py +0 -0
  67. {python_engineio-4.10.1 → python_engineio-4.11.0}/tests/common/index.html +0 -0
  68. {python_engineio-4.10.1 → python_engineio-4.11.0}/tests/performance/README.md +0 -0
  69. {python_engineio-4.10.1 → python_engineio-4.11.0}/tests/performance/binary_b64_packet.py +0 -0
  70. {python_engineio-4.10.1 → python_engineio-4.11.0}/tests/performance/binary_packet.py +0 -0
  71. {python_engineio-4.10.1 → python_engineio-4.11.0}/tests/performance/json_packet.py +0 -0
  72. {python_engineio-4.10.1 → python_engineio-4.11.0}/tests/performance/payload.py +0 -0
  73. {python_engineio-4.10.1 → python_engineio-4.11.0}/tests/performance/run.sh +0 -0
  74. {python_engineio-4.10.1 → python_engineio-4.11.0}/tests/performance/server_receive.py +0 -0
  75. {python_engineio-4.10.1 → python_engineio-4.11.0}/tests/performance/text_packet.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: python-engineio
3
- Version: 4.10.1
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
@@ -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``. Note that the
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
 
@@ -1,4 +1,3 @@
1
- # -*- coding: utf-8 -*-
2
1
  #
3
2
  # Configuration file for the Sphinx documentation builder.
4
3
  #
@@ -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``. Note that the
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.10.1"
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.get_event_loop().stop()
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.get_event_loop().add_signal_handler(
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', run_async=False)
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
- ret = await self.handlers[event](*args)
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
- ret = self.handlers[event](*args)
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('disconnect', run_async=False)
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('disconnect', run_async=False)
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
@@ -25,7 +25,7 @@ class SimpleWebSocketWSGI: # pragma: no cover
25
25
  try:
26
26
  return self.ws.send(message)
27
27
  except simple_websocket.ConnectionClosed:
28
- raise IOError()
28
+ raise OSError()
29
29
 
30
30
  def wait(self):
31
31
  try:
@@ -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 = '%s,%s' % (environ[key], 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(object): # pragma: no cover
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 IOError()
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(object): # pragma: no cover
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'].encode('utf-8')
164
+ raw_uri = scope['path']
165
+ query_string = ''
165
166
  if 'query_string' in scope and scope['query_string']:
166
- raw_uri += b'?' + scope['query_string']
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': scope.get('query_string', b'').decode('utf-8'),
179
- 'RAW_URI': raw_uri.decode('utf-8'),
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
- hdr_name = hdr_name.upper().decode('utf-8')
193
- hdr_value = hdr_value.decode('utf-8')
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 = '%s,%s' % (environ[key], 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(object): # pragma: no cover
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 IOError()
282
+ raise OSError()
273
283
  return event.get('bytes') or event.get('text')
274
284
 
275
285
 
@@ -1,5 +1,3 @@
1
- from __future__ import absolute_import
2
-
3
1
  from eventlet.green.threading import Event
4
2
  from eventlet import queue, sleep, spawn
5
3
  from eventlet.websocket import WebSocketWSGI as _WebSocketWSGI
@@ -18,7 +18,7 @@ class Thread(gevent.Greenlet): # pragma: no cover
18
18
  return self.run()
19
19
 
20
20
 
21
- class uWSGIWebSocket(object): # pragma: no cover
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 IOError: # connection closed
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 IOError:
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 IOError: # connection closed
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(object):
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 = '%s,%s' % (environ[key], 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(object): # pragma: no cover
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 IOError()
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(object):
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(object): # pragma: no cover
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 IOError()
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, '/{}/'.format(engineio_path))
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(object):
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([asyncio.create_task(client.close())
200
- for client in self.sockets.values()])
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 ' + sid, 'bad-sid')
288
- r = self._bad_request('Invalid session ' + sid)
290
+ self._log_error_once(f'Invalid session {sid}', 'bad-sid')
291
+ r = self._bad_request(f'Invalid session {sid}')
289
292
  else:
290
- socket = self._get_socket(sid)
291
- if self.transport(sid) != transport and \
292
- transport != upgrade_header:
293
- self._log_error_once(
294
- 'Invalid transport for session ' + sid,
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
- try:
299
- packets = await socket.handle_get_request(environ)
300
- if isinstance(packets, list):
301
- r = self._ok(packets, jsonp_index=jsonp_index)
302
- else:
303
- r = packets
304
- except exceptions.EngineIOError:
305
- if sid in self.sockets: # pragma: no cover
306
- await self.disconnect(sid)
307
- r = self._bad_request()
308
- if sid in self.sockets and self.sockets[sid].closed:
309
- del self.sockets[sid]
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 ' + sid, 'bad-sid')
313
- r = self._bad_request('Invalid session ' + sid)
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
- packet.OPEN, {'sid': sid,
445
- 'upgrades': self._upgrades(sid, transport),
446
- 'pingTimeout': int(self.ping_timeout * 1000),
447
- 'pingInterval': int(self.ping_interval * 1000)})
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
- return await self.handlers[event](*args)
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
- return self.handlers[event](*args)
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 asyncio.get_event_loop().is_closed():
605
+ if loop.is_closed():
573
606
  self.logger.info('event loop is closed, exiting service '
574
607
  'task')
575
608
  break