diff --git a/examples/server/asgi/app.py b/examples/server/asgi/app.py
index 22180bb..b3a216d 100644
--- a/examples/server/asgi/app.py
+++ b/examples/server/asgi/app.py
@@ -1,9 +1,25 @@
#!/usr/bin/env python
-import uvicorn
+# set instrument to `True` to accept connections from the official Socket.IO
+# Admin UI hosted at https://admin.socket.io
+instrument = True
+admin_login = {
+ 'username': 'admin',
+ 'password': 'python', # change this to a strong secret for production use!
+}
+
+import uvicorn
import socketio
-sio = socketio.AsyncServer(async_mode='asgi')
+sio = socketio.AsyncServer(
+ async_mode='asgi',
+ cors_allowed_origins=None if not instrument else [
+ 'http://localhost:5000',
+ 'https://admin.socket.io', # edit the allowed origins if necessary
+ ])
+if instrument:
+ sio.instrument(auth=admin_login)
+
app = socketio.ASGIApp(sio, static_files={
'/': 'app.html',
})
diff --git a/examples/server/wsgi/app.py b/examples/server/wsgi/app.py
index 3339826..7b019fd 100644
--- a/examples/server/wsgi/app.py
+++ b/examples/server/wsgi/app.py
@@ -3,10 +3,26 @@
# installed
async_mode = None
+# set instrument to `True` to accept connections from the official Socket.IO
+# Admin UI hosted at https://admin.socket.io
+instrument = False
+admin_login = {
+ 'username': 'admin',
+ 'password': 'python', # change this to a strong secret for production use!
+}
+
from flask import Flask, render_template
import socketio
-sio = socketio.Server(logger=True, async_mode=async_mode)
+sio = socketio.Server(
+ async_mode=async_mode,
+ cors_allowed_origins=None if not instrument else [
+ 'http://localhost:5000',
+ 'https://admin.socket.io', # edit the allowed origins if necessary
+ ])
+if instrument:
+ sio.instrument(auth=admin_login)
+
app = Flask(__name__)
app.wsgi_app = socketio.WSGIApp(sio, app.wsgi_app)
app.config['SECRET_KEY'] = 'secret!'
diff --git a/src/socketio/admin.py b/src/socketio/admin.py
index 720883c..dc45c4b 100644
--- a/src/socketio/admin.py
+++ b/src/socketio/admin.py
@@ -4,6 +4,7 @@ import os
import socket
import time
from urllib.parse import parse_qs
+from .exceptions import ConnectionRefusedError
HOSTNAME = socket.gethostname()
PID = os.getpid()
@@ -92,8 +93,8 @@ class InstrumentedServer:
self.sio.manager.leave_room = self._leave_room
# report emit events
- self.sio.__emit_internal = self.sio._emit_internal
- self.sio._emit_internal = self._emit_internal
+ self.sio.manager.__emit = self.sio.manager.emit
+ self.sio.manager.emit = self._emit
# report receive events
self.sio.__handle_event_internal = self.sio._handle_event_internal
@@ -117,9 +118,16 @@ class InstrumentedServer:
self.__class__._eio_websocket_handler, self)
def admin_connect(self, sid, environ, client_auth):
- if self.auth:
- if not self.auth(client_auth):
- raise ConnectionRefusedError('Invalid credentials')
+ if self.auth != None:
+ authenticated = False
+ if isinstance(self.auth, dict):
+ authenticated = client_auth == self.auth
+ elif isinstance(self.auth, list):
+ authenticated = client_auth in self.auth
+ else:
+ authenticated = self.auth(client_auth)
+ if not authenticated:
+ raise ConnectionRefusedError('authentication failed')
def config(sid):
self.sio.sleep(0.1)
@@ -183,13 +191,16 @@ class InstrumentedServer:
def check_for_upgrade():
for _ in range(5):
self.sio.sleep(5)
- if self.sio.eio._get_socket(eio_sid).upgraded:
- self.sio.emit('socket_updated', {
- 'id': sid,
- 'nsp': namespace,
- 'transport': 'websocket',
- }, namespace=self.admin_namespace)
- break
+ try:
+ if self.sio.eio._get_socket(eio_sid).upgraded:
+ self.sio.emit('socket_updated', {
+ 'id': sid,
+ 'nsp': namespace,
+ 'transport': 'websocket',
+ }, namespace=self.admin_namespace)
+ break
+ except KeyError:
+ pass
if serialized_socket['transport'] == 'polling':
self.sio.start_background_task(check_for_upgrade)
@@ -226,17 +237,24 @@ class InstrumentedServer:
), namespace=self.admin_namespace)
return self.sio.manager.__leave_room(sid, namespace, room)
- def _emit_internal(self, eio_sid, event, data, namespace=None, id=None):
- ret = self.sio.__emit_internal(eio_sid, event, data,
- namespace=namespace, id=id)
+ def _emit(self, event, data, namespace, room=None, skip_sid=None,
+ callback=None, **kwargs):
+ ret = self.sio.manager.__emit(event, data, namespace, room=room,
+ skip_sid=skip_sid, callback=callback,
+ **kwargs)
if namespace != self.admin_namespace:
- sid = self.sio.manager.sid_from_eio_sid(eio_sid, namespace)
- self.sio.emit('event_sent', (
- namespace,
- sid,
- [event] + list(data) if isinstance(data, tuple) else [data],
- datetime.utcnow().isoformat() + 'Z',
- ), namespace=self.admin_namespace)
+ event_data = [event] + list(data) if isinstance(data, tuple) \
+ else [data]
+ if not isinstance(skip_sid, list):
+ skip_sid = [skip_sid]
+ for sid, _ in self.sio.manager.get_participants(namespace, room):
+ if sid not in skip_sid:
+ self.sio.emit('event_sent', (
+ namespace,
+ sid,
+ event_data,
+ datetime.utcnow().isoformat() + 'Z',
+ ), namespace=self.admin_namespace)
return ret
def _handle_event_internal(self, server, sid, eio_sid, data, namespace,
@@ -282,7 +300,7 @@ class InstrumentedServer:
def _wait(ws):
ret = ws.__wait()
self.event_buffer.push('packetsIn')
- self.event_buffer.push('bytesIn', len(ret))
+ self.event_buffer.push('bytesIn', len(ret or ''))
return ret
ws.__send = ws.send
diff --git a/src/socketio/async_server.py b/src/socketio/async_server.py
index 442edaf..07757f1 100644
--- a/src/socketio/async_server.py
+++ b/src/socketio/async_server.py
@@ -472,11 +472,16 @@ class AsyncServer(base_server.BaseServer):
"""Instrument the Socket.IO server for monitoring with the `Socket.IO
Admin UI `_.
- :param auth: A function that receives a dictionary with the credentials
- provided by the client (usually ``username`` and
- ``password``) and returns ``True`` if the user is allowed.
- To disable authentication, set this argument to ``False``
- (not recommended, never do this on a production server).
+ :param auth: Authentication credentials for Admin UI access. Set to a
+ dictionary with the expected login (usually ``username``
+ and ``password``) or a list of dictionaries if more than
+ one set of credentials need to be available. For more
+ complex authentication methods, set to a callable that
+ receives the authentication dictionary as an argument and
+ returns ``True`` if the user is allowed or ``False``
+ otherwise. To disable authentication, set this argument to
+ ``False`` (not recommended, never do this on a production
+ server).
:param mode: The reporting mode. The default is ``'development'``,
which is best used while debugging, as it may have a
significant performance effect. Set to ``'production'`` to
diff --git a/src/socketio/asyncio_admin.py b/src/socketio/asyncio_admin.py
index 7000122..b491e34 100644
--- a/src/socketio/asyncio_admin.py
+++ b/src/socketio/asyncio_admin.py
@@ -6,6 +6,7 @@ import socket
import time
from urllib.parse import parse_qs
from .admin import EventBuffer
+from .exceptions import ConnectionRefusedError
HOSTNAME = socket.gethostname()
PID = os.getpid()
@@ -73,8 +74,8 @@ class InstrumentedAsyncServer:
self.sio.manager.leave_room = self._leave_room
# report emit events
- self.sio.__emit_internal = self.sio._emit_internal
- self.sio._emit_internal = self._emit_internal
+ self.sio.manager.__emit = self.sio.manager.emit
+ self.sio.manager.emit = self._emit
# report receive events
self.sio.__handle_event_internal = self.sio._handle_event_internal
@@ -98,13 +99,19 @@ class InstrumentedAsyncServer:
async def admin_connect(self, sid, environ, client_auth):
authenticated = True
- if self.auth:
- if asyncio.iscoroutinefunction(self.auth):
- authenticated = await self.auth(client_auth)
+ if self.auth != None:
+ authenticated = False
+ if isinstance(self.auth, dict):
+ authenticated = client_auth == self.auth
+ elif isinstance(self.auth, list):
+ authenticated = client_auth in self.auth
else:
- authenticated = self.auth(client_auth)
- if not authenticated:
- raise ConnectionRefusedError('Invalid credentials')
+ if asyncio.iscoroutinefunction(self.auth):
+ authenticated = await self.auth(client_auth)
+ else:
+ authenticated = self.auth(client_auth)
+ if not authenticated:
+ raise ConnectionRefusedError('authentication failed')
async def config(sid):
await self.sio.sleep(0.1)
@@ -137,7 +144,6 @@ class InstrumentedAsyncServer:
await self.sio.emit(event, data, to=room_filter, namespace=namespace)
def admin_enter_room(self, _, namespace, room, room_filter=None):
- print(namespace, room, room_filter)
for sid, _ in self.sio.manager.get_participants(
namespace, room_filter):
self.sio.enter_room(sid, room, namespace=namespace)
@@ -169,13 +175,16 @@ class InstrumentedAsyncServer:
async def check_for_upgrade():
for _ in range(5):
await self.sio.sleep(5)
- if self.sio.eio._get_socket(eio_sid).upgraded:
- await self.sio.emit('socket_updated', {
- 'id': sid,
- 'nsp': namespace,
- 'transport': 'websocket',
- }, namespace=self.admin_namespace)
- break
+ try:
+ if self.sio.eio._get_socket(eio_sid).upgraded:
+ await self.sio.emit('socket_updated', {
+ 'id': sid,
+ 'nsp': namespace,
+ 'transport': 'websocket',
+ }, namespace=self.admin_namespace)
+ break
+ except KeyError:
+ pass
if serialized_socket['transport'] == 'polling':
self.sio.start_background_task(check_for_upgrade)
@@ -212,18 +221,24 @@ class InstrumentedAsyncServer:
)))
return self.sio.manager.__leave_room(sid, namespace, room)
- async def _emit_internal(self, eio_sid, event, data, namespace=None,
- id=None):
- ret = await self.sio.__emit_internal(eio_sid, event, data,
- namespace=namespace, id=id)
+ async def _emit(self, event, data, namespace, room=None, skip_sid=None,
+ callback=None, **kwargs):
+ ret = await self.sio.manager.__emit(event, data, namespace, room=room,
+ skip_sid=skip_sid, callback=callback,
+ **kwargs)
if namespace != self.admin_namespace:
- sid = self.sio.manager.sid_from_eio_sid(eio_sid, namespace)
- await self.sio.emit('event_sent', (
- namespace,
- sid,
- [event] + list(data) if isinstance(data, tuple) else [data],
- datetime.utcnow().isoformat() + 'Z',
- ), namespace=self.admin_namespace)
+ event_data = [event] + list(data) if isinstance(data, tuple) \
+ else [data]
+ if not isinstance(skip_sid, list):
+ skip_sid = [skip_sid]
+ for sid, _ in self.sio.manager.get_participants(namespace, room):
+ if sid not in skip_sid:
+ await self.sio.emit('event_sent', (
+ namespace,
+ sid,
+ event_data,
+ datetime.utcnow().isoformat() + 'Z',
+ ), namespace=self.admin_namespace)
return ret
async def _handle_event_internal(self, server, sid, eio_sid, data,
@@ -269,7 +284,7 @@ class InstrumentedAsyncServer:
async def _wait(ws):
ret = await ws.__wait()
self.event_buffer.push('packetsIn')
- self.event_buffer.push('bytesIn', len(ret))
+ self.event_buffer.push('bytesIn', len(ret or ''))
return ret
ws.__send = ws.send
diff --git a/src/socketio/server.py b/src/socketio/server.py
index 041dbe7..2bc82e0 100644
--- a/src/socketio/server.py
+++ b/src/socketio/server.py
@@ -459,11 +459,16 @@ class Server(base_server.BaseServer):
"""Instrument the Socket.IO server for monitoring with the `Socket.IO
Admin UI `_.
- :param auth: A function that receives a dictionary with the credentials
- provided by the client (usually ``username`` and
- ``password``) and returns ``True`` if the user is allowed.
- To disable authentication, set this argument to ``False``
- (not recommended, never do this on a production server).
+ :param auth: Authentication credentials for Admin UI access. Set to a
+ dictionary with the expected login (usually ``username``
+ and ``password``) or a list of dictionaries if more than
+ one set of credentials need to be available. For more
+ complex authentication methods, set to a callable that
+ receives the authentication dictionary as an argument and
+ returns ``True`` if the user is allowed or ``False``
+ otherwise. To disable authentication, set this argument to
+ ``False`` (not recommended, never do this on a production
+ server).
:param mode: The reporting mode. The default is ``'development'``,
which is best used while debugging, as it may have a
significant performance effect. Set to ``'production'`` to