Skip to content

Commit

Permalink
v5 protocol: do not connect the default namespace unless requested ex…
Browse files Browse the repository at this point in the history
…plicitly
  • Loading branch information
miguelgrinberg committed Dec 5, 2020
1 parent 798ab6a commit 49822e6
Show file tree
Hide file tree
Showing 5 changed files with 53 additions and 54 deletions.
45 changes: 19 additions & 26 deletions socketio/asyncio_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,16 +71,19 @@ async def connect(self, url, headers={}, transports=None,
are ``'polling'`` and ``'websocket'``. If not
given, the polling transport is connected first,
then an upgrade to websocket is attempted.
:param namespaces: The list of custom namespaces to connect, in
addition to the default namespace. If not given,
the namespace list is obtained from the registered
event handlers.
:param namespaces: The namespaces to connect as a string or list of
strings. If not given, the namespaces that have
registered event handlers are connected.
:param socketio_path: The endpoint where the Socket.IO server is
installed. The default value is appropriate for
most cases.
Note: this method is a coroutine.
Note: The connection mechannism occurs in the background and will
complete at some point after this function returns. The connection
will be established when the ``connect`` event is invoked.
Example usage::
sio = socketio.AsyncClient()
Expand All @@ -97,8 +100,7 @@ async def connect(self, url, headers={}, transports=None,
set(self.namespace_handlers.keys()))
elif isinstance(namespaces, six.string_types):
namespaces = [namespaces]
self.connection_namespaces = namespaces
self.namespaces = [n for n in namespaces if n != '/']
self.connection_namespaces = namespaces

This comment has been minimized.

Copy link
@ventaquil

ventaquil Jan 4, 2021

@miguelgrinberg why you set self.connection_namespaces twice? Previously in line 95.

In previous version self.namespaces was set too.

Currently I need to wait for connect event because emmiting something earlier to the namespace raises BadNamespaceError.

This comment has been minimized.

Copy link
@miguelgrinberg

miguelgrinberg Jan 4, 2021

Author Owner

If you emit to a namespace before it is connected the event will not make it to the other side. The BadNamespaceError alerts you of that.

try:
await self.eio.connect(url, headers=headers,
transports=transports,
Expand Down Expand Up @@ -154,7 +156,7 @@ async def emit(self, event, data=None, namespace=None, callback=None):
Note 2: this method is a coroutine.
"""
namespace = namespace or '/'
if namespace != '/' and namespace not in self.namespaces:
if namespace not in self.namespaces:
raise exceptions.BadNamespaceError(
namespace + ' is not a connected namespace.')
self.logger.info('Emitting event "%s" [%s]', event, namespace)
Expand Down Expand Up @@ -248,8 +250,6 @@ async def disconnect(self):
for n in self.namespaces:
await self._send_packet(packet.Packet(packet.DISCONNECT,
namespace=n))
await self._send_packet(packet.Packet(
packet.DISCONNECT, namespace='/'))
self.connected = False
await self.eio.disconnect(abort=True)

Expand Down Expand Up @@ -291,30 +291,22 @@ async def _send_packet(self, pkt):
else:
await self.eio.send(encoded_packet)

async def _handle_connect(self, namespace):
async def _handle_connect(self, namespace, data):
namespace = namespace or '/'
self.logger.info('Namespace {} is connected'.format(namespace))
if namespace not in self.namespaces:
self.namespaces[namespace] = (data or {}).get('sid', self.sid)
await self._trigger_event('connect', namespace=namespace)
if namespace == '/':
for n in self.namespaces:
await self._send_packet(packet.Packet(packet.CONNECT,
namespace=n))
elif namespace not in self.namespaces:
self.namespaces.append(namespace)

async def _handle_disconnect(self, namespace):
if not self.connected:
return
namespace = namespace or '/'
if namespace == '/':
for n in self.namespaces:
await self._trigger_event('disconnect', namespace=n)
self.namespaces = []
await self._trigger_event('disconnect', namespace=namespace)
if namespace in self.namespaces:
self.namespaces.remove(namespace)
if namespace == '/':
self.connected = False
del self.namespaces[namespace]
if not self.namespaces:
await self.eio.disconnect(abort=True)

async def _handle_event(self, namespace, id, data):
namespace = namespace or '/'
Expand Down Expand Up @@ -422,10 +414,12 @@ async def _handle_reconnect(self):
break
client.reconnecting_clients.remove(self)

def _handle_eio_connect(self):
async def _handle_eio_connect(self):
"""Handle the Engine.IO connection event."""
self.logger.info('Engine.IO connection established')
self.sid = self.eio.sid
for n in self.connection_namespaces:
await self._send_packet(packet.Packet(packet.CONNECT, namespace=n))

async def _handle_eio_message(self, data):
"""Dispatch Engine.IO messages."""
Expand All @@ -440,7 +434,7 @@ async def _handle_eio_message(self, data):
else:
pkt = packet.Packet(encoded_packet=data)
if pkt.packet_type == packet.CONNECT:
await self._handle_connect(pkt.namespace)
await self._handle_connect(pkt.namespace, pkt.data)
elif pkt.packet_type == packet.DISCONNECT:
await self._handle_disconnect(pkt.namespace)
elif pkt.packet_type == packet.EVENT:
Expand All @@ -462,7 +456,6 @@ async def _handle_eio_disconnect(self):
if self.connected:
for n in self.namespaces:
await self._trigger_event('disconnect', namespace=n)
await self._trigger_event('disconnect', namespace='/')
self.namespaces = []
self.connected = False
self.callbacks = {}
Expand Down
1 change: 0 additions & 1 deletion socketio/asyncio_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -515,7 +515,6 @@ async def _handle_eio_connect(self, sid, environ):
self.manager_initialized = True
self.manager.initialize()
self.environ[sid] = environ
return await self._handle_connect(sid, '/')

async def _handle_eio_message(self, sid, data):
"""Dispatch Engine.IO messages."""
Expand Down
60 changes: 34 additions & 26 deletions socketio/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ def __init__(self, reconnection=True, reconnection_attempts=0,
self.sid = None

self.connected = False
self.namespaces = []
self.namespaces = {}
self.handlers = {}
self.namespace_handlers = {}
self.callbacks = {}
Expand Down Expand Up @@ -242,14 +242,17 @@ def connect(self, url, headers={}, transports=None,
are ``'polling'`` and ``'websocket'``. If not
given, the polling transport is connected first,
then an upgrade to websocket is attempted.
:param namespaces: The list of custom namespaces to connect, in
addition to the default namespace. If not given,
the namespace list is obtained from the registered
event handlers.
:param namespaces: The namespaces to connect as a string or list of
strings. If not given, the namespaces that have
registered event handlers are connected.
:param socketio_path: The endpoint where the Socket.IO server is
installed. The default value is appropriate for
most cases.
Note: The connection mechannism occurs in the background and will
complete at some point after this function returns. The connection
will be established when the ``connect`` event is invoked.
Example usage::
sio = socketio.Client()
Expand All @@ -266,8 +269,7 @@ def connect(self, url, headers={}, transports=None,
set(self.namespace_handlers.keys()))
elif isinstance(namespaces, six.string_types):
namespaces = [namespaces]
self.connection_namespaces = namespaces
self.namespaces = [n for n in namespaces if n != '/']
self.connection_namespaces = namespaces
try:
self.eio.connect(url, headers=headers, transports=transports,
engineio_path=socketio_path)
Expand Down Expand Up @@ -318,7 +320,7 @@ def emit(self, event, data=None, namespace=None, callback=None):
situation.
"""
namespace = namespace or '/'
if namespace != '/' and namespace not in self.namespaces:
if namespace not in self.namespaces:
raise exceptions.BadNamespaceError(
namespace + ' is not a connected namespace.')
self.logger.info('Emitting event "%s" [%s]', event, namespace)
Expand Down Expand Up @@ -402,11 +404,23 @@ def disconnect(self):
# later in _handle_eio_disconnect we invoke the disconnect handler
for n in self.namespaces:
self._send_packet(packet.Packet(packet.DISCONNECT, namespace=n))
self._send_packet(packet.Packet(
packet.DISCONNECT, namespace='/'))
self.connected = False
self.eio.disconnect(abort=True)

def get_sid(self, namespace=None):
"""Return the ``sid`` associated with a connection.
:param namespace: The Socket.IO namespace. If this argument is omitted
the handler is associated with the default
namespace. Note that unlike previous versions, the
current version of the Socket.IO protocol uses
different ``sid`` values per namespace.
This method returns the ``sid`` for the requested namespace as a
string.
"""
return self.namespaces.get(namespace or '/')

def transport(self):
"""Return the name of the transport used by the client.
Expand Down Expand Up @@ -460,29 +474,22 @@ def _generate_ack_id(self, namespace, callback):
self.callbacks[namespace][id] = callback
return id

def _handle_connect(self, namespace):
def _handle_connect(self, namespace, data):
namespace = namespace or '/'
self.logger.info('Namespace {} is connected'.format(namespace))
if namespace not in self.namespaces:
self.namespaces[namespace] = (data or {}).get('sid', self.sid)
self._trigger_event('connect', namespace=namespace)
if namespace == '/':
for n in self.namespaces:
self._send_packet(packet.Packet(packet.CONNECT, namespace=n))
elif namespace not in self.namespaces:
self.namespaces.append(namespace)

def _handle_disconnect(self, namespace):
if not self.connected:
return
namespace = namespace or '/'
if namespace == '/':
for n in self.namespaces:
self._trigger_event('disconnect', namespace=n)
self.namespaces = []
self._trigger_event('disconnect', namespace=namespace)
if namespace in self.namespaces:
self.namespaces.remove(namespace)
if namespace == '/':
self.connected = False
del self.namespaces[namespace]
if not self.namespaces:
self.eio.disconnect(abort=True)

def _handle_event(self, namespace, id, data):
namespace = namespace or '/'
Expand Down Expand Up @@ -524,7 +531,7 @@ def _handle_error(self, namespace, data):
data = (data,)
self._trigger_event('connect_error', namespace, *data)
if namespace in self.namespaces:
self.namespaces.remove(namespace)
del self.namespaces[namespace]
if namespace == '/':
self.namespaces = []
self.connected = False
Expand Down Expand Up @@ -581,6 +588,8 @@ def _handle_eio_connect(self):
"""Handle the Engine.IO connection event."""
self.logger.info('Engine.IO connection established')
self.sid = self.eio.sid
for n in self.connection_namespaces:
self._send_packet(packet.Packet(packet.CONNECT, namespace=n))

def _handle_eio_message(self, data):
"""Dispatch Engine.IO messages."""
Expand All @@ -595,7 +604,7 @@ def _handle_eio_message(self, data):
else:
pkt = packet.Packet(encoded_packet=data)
if pkt.packet_type == packet.CONNECT:
self._handle_connect(pkt.namespace)
self._handle_connect(pkt.namespace, pkt.data)
elif pkt.packet_type == packet.DISCONNECT:
self._handle_disconnect(pkt.namespace)
elif pkt.packet_type == packet.EVENT:
Expand All @@ -616,7 +625,6 @@ def _handle_eio_disconnect(self):
if self.connected:
for n in self.namespaces:
self._trigger_event('disconnect', namespace=n)
self._trigger_event('disconnect', namespace='/')
self.namespaces = []
self.connected = False
self.callbacks = {}
Expand Down
1 change: 0 additions & 1 deletion socketio/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -705,7 +705,6 @@ def _handle_eio_connect(self, sid, environ):
self.manager_initialized = True
self.manager.initialize()
self.environ[sid] = environ
return self._handle_connect(sid, '/')

def _handle_eio_message(self, sid, data):
"""Dispatch Engine.IO messages."""
Expand Down

0 comments on commit 49822e6

Please sign in to comment.