From 4bf48776ca08acba255bf660f5ca61810202826a Mon Sep 17 00:00:00 2001 From: Miguel Grinberg Date: Sun, 15 Oct 2023 12:34:49 +0100 Subject: [PATCH] Reporting to Socket.IO Admin UI (#1164) --- .github/workflows/tests.yml | 2 +- docs/server.rst | 35 ++ examples/server/asgi/app.py | 20 +- examples/server/wsgi/app.py | 18 +- examples/server/wsgi/templates/index.html | 2 +- src/socketio/admin.py | 405 ++++++++++++++++++++++ src/socketio/async_admin.py | 398 +++++++++++++++++++++ src/socketio/async_manager.py | 7 + src/socketio/async_server.py | 41 ++- src/socketio/async_simple_client.py | 10 +- src/socketio/base_manager.py | 4 +- src/socketio/server.py | 39 +++ src/socketio/simple_client.py | 10 +- tests/async/test_admin.py | 311 +++++++++++++++++ tests/async/test_manager.py | 116 +++---- tests/async/test_pubsub_manager.py | 14 +- tests/async/test_server.py | 24 +- tests/async/test_simple_client.py | 12 +- tests/asyncio_web_server.py | 57 +++ tests/common/test_admin.py | 286 +++++++++++++++ tests/common/test_simple_client.py | 12 +- tests/web_server.py | 81 +++++ tox.ini | 8 +- 23 files changed, 1810 insertions(+), 102 deletions(-) create mode 100644 src/socketio/admin.py create mode 100644 src/socketio/async_admin.py create mode 100644 tests/async/test_admin.py create mode 100644 tests/asyncio_web_server.py create mode 100644 tests/common/test_admin.py create mode 100644 tests/web_server.py diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index af896fb4..ddc190c8 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -26,7 +26,7 @@ jobs: exclude: # pypy3 currently fails to run on Windows - os: windows-latest - python: pypy-3.8 + python: pypy-3.9 fail-fast: false runs-on: ${{ matrix.os }} steps: diff --git a/docs/server.rst b/docs/server.rst index 5a6798c2..5ce59f00 100644 --- a/docs/server.rst +++ b/docs/server.rst @@ -617,6 +617,41 @@ callbacks when emitting. When the external process needs to receive callbacks, using a client to connect to the server with read and write support is a better option than a write-only client manager. +Monitoring and Administration +----------------------------- + +The Socket.IO server can be configured to accept connections from the official +`Socket.IO Admin UI `_. This tool provides +real-time information about currently connected clients, rooms in use and +events being emitted. It also allows an administrator to manually emit events, +change room assignments and disconnect clients. The hosted version of this tool +is available at `https://admin.socket.io `_. + +Given that enabling this feature can affect the performance of the server, it +is disabled by default. To enable it, call the +:func:`instrument() ` method. For example:: + + import os + import socketio + + sio = socketio.Server(cors_allowed_origins=[ + 'http://localhost:5000', + 'https://admin.socket.io', + ]) + sio.instrument(auth={ + 'username': 'admin', + 'password': os.environ['ADMIN_PASSWORD'], + }) + +This configures the server to accept connections from the hosted Admin UI +client. Administrators can then open https://admin.socket.io in their web +browsers and log in with username ``admin`` and the password given by the +``ADMIN_PASSWORD`` environment variable. To ensure the Admin UI front end is +allowed to connect, CORS is also configured. + +Consult the reference documentation to learn about additional configuration +options that are available. + Debugging and Troubleshooting ----------------------------- diff --git a/examples/server/asgi/app.py b/examples/server/asgi/app.py index 22180bb0..36af85f2 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 = False +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 3339826a..7b019fd0 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/examples/server/wsgi/templates/index.html b/examples/server/wsgi/templates/index.html index 7c9ae41f..bec1a628 100644 --- a/examples/server/wsgi/templates/index.html +++ b/examples/server/wsgi/templates/index.html @@ -6,7 +6,7 @@