diff --git a/docs/api.rst b/docs/api.rst
index 55e8214..7139e2d 100644
--- a/docs/api.rst
+++ b/docs/api.rst
@@ -6,6 +6,19 @@ API Reference
.. module:: socketio
+``Client`` class
+----------------
+
+.. autoclass:: Client
+ :members:
+
+``AsyncClient`` class
+---------------------
+
+.. autoclass:: AsyncClient
+ :members:
+ :inherited-members:
+
``Server`` class
----------------
@@ -37,6 +50,13 @@ API Reference
.. autoclass:: Middleware
:members:
+``ClientNamespace`` class
+-------------------------
+
+.. autoclass:: ClientNamespace
+ :members:
+ :inherited-members:
+
``Namespace`` class
-------------------
@@ -44,6 +64,13 @@ API Reference
:members:
:inherited-members:
+``AsyncClientNamespace`` class
+------------------------------
+
+.. autoclass:: AsyncClientNamespace
+ :members:
+ :inherited-members:
+
``AsyncNamespace`` class
------------------------
diff --git a/docs/client.rst b/docs/client.rst
new file mode 100644
index 0000000..d929d75
--- /dev/null
+++ b/docs/client.rst
@@ -0,0 +1,290 @@
+The Socket.IO Client
+====================
+
+This package contains two Socket.IO clients:
+
+- The :func:`socketio.Client` class creates a client compatible with the
+ standard Python library.
+- The :func:`socketio.AsyncClient` class creates a client compatible with
+ the ``asyncio`` package.
+
+The methods in the two clients are the same, with the only difference that in
+the ``asyncio`` client most methods are implemented as coroutines.
+
+Installation
+------------
+
+To install the standard Python client along with its dependencies, use the
+following command::
+
+ pip install "python-socketio[client]"
+
+If instead you plan on using the ``asyncio`` client, then use this::
+
+ pip install "python-socketio[asyncio_client]"
+
+Creating a Client Instance
+--------------------------
+
+To instantiate an Socket.IO client, simply create an instance of the
+appropriate client class::
+
+ import socketio
+
+ # standard Python
+ sio = socketio.Client()
+
+ # asyncio
+ sio = socketio.AsyncClient()
+
+Defining Event Handlers
+-----------------------
+
+To responds to events triggered by the connection or the server, event Handler
+functions must be defined using the ``on`` decorator::
+
+ @sio.on('connect')
+ def on_connect():
+ print('I'm connected!')
+
+ @sio.on('message')
+ def on_message(data):
+ print('I received a message!')
+
+ @sio.on('my message')
+ def on_message(data):
+ print('I received a custom message!')
+
+ @sio.on('disconnect')
+ def on_disconnect():
+ print('I'm disconnected!')
+
+For the ``asyncio`` server, event handlers can be regular functions as above,
+or can also be coroutines::
+
+ @sio.on('message')
+ async def on_message(data):
+ print('I received a message!')
+
+The argument given to the ``on`` decorator is the event name. The predefined
+events that are supported are ``connect``, ``message`` and ``disconnect``. The
+application can define any other desired event names.
+
+Note that the ``disconnect`` handler is invoked for application initiated
+disconnects, server initiated disconnects, or accidental disconnects, for
+example due to networking failures. In the case of an accidental disconnection,
+the client is going to attempt to reconnect immediately after invoking the
+disconnect handler. As soon as the connection is re-established the connect
+handler will be invoked once again.
+
+The ``data`` argument passed to the ``'message'`` and custom event Handlers
+contains application-specific data provided by the server.
+
+Connecting to a Server
+----------------------
+
+The connection to a server is established by calling the ``connect()``
+method::
+
+ sio.connect('http://localhost:5000')
+
+In the case of the ``asyncio`` client, the method is a coroutine::
+
+ await sio.connect('http://localhost:5000')
+
+Emitting Events
+---------------
+
+The client can emit an event to the server using the ``emit()`` method::
+
+ sio.emit('my message', {'foo': 'bar'})
+
+Or in the case of ``asyncio``, as a coroutine::
+
+ await sio.emit('my message', {'foo': 'bar'})
+
+The single argument provided to the method is the data that is passed on
+to the server. The data can be of type ``str``, ``bytes``, ``dict`` or
+``list``. The data included inside dictionaries and lists is also
+constrained to these types.
+
+The ``emit()`` method can be invoked inside an event handler as a response
+to a server event, or in any other part of the application, including in
+background tasks.
+
+For convenience, a ``send()`` method is also provided. This method accepts
+a data element as its only argument, and emits the standard ``message``
+event with it::
+
+ sio.send('some data')
+
+In the case of ``asyncio``, ``send()`` is a coroutine::
+
+ await sio.send('some data')
+
+Event Callbacks
+---------------
+
+When a server emits an event to a client, it can optionally provide a
+callback function, to be invoked as a way of acknowledgment that the server
+has processed the event. While this is entirely managed by the server, the
+client can provide a list of return values that are to be passed on to the
+callback function set up by the server. This is achieves simply by returning
+the desired values from the handler function::
+
+ @sio.on('my event', namespace='/chat')
+ def my_event_handler(sid, data):
+ # handle the message
+ return "OK", 123
+
+Likewise, the client can request a callback function to be invoked after the
+server has processed an event. The :func:`socketio.Server.emit` method has an
+optional ``callback`` argument that can be set to a callable. If this
+argument is given, the callable will be invoked after the server has processed
+the event, and any values returned by the server handler will be passed as
+arguments to this function.
+
+Namespaces
+----------
+
+The Socket.IO protocol supports multiple logical connections, all multiplexed
+on the same physical connection. Clients can open multiple connections by
+specifying a different *namespace* on each. Namespaces use a path syntax
+starting with a forward slash. A list of namespaces can be given by the client
+in the ``connect()`` call. For example, this example creates two logical
+connections, the default one plus a second connection under the ``/chat``
+namespace::
+
+ sio.connect('http://localhost:5000', namespaces=['/chat'])
+
+To define event handlers on a namespace, the ``namespace`` argument must be
+added to the ``on`` decorator::
+
+ @sio.on('connect', namespace='/chat')
+ def on_connect():
+ print('I'm connected to the /chat namespace!')
+
+Likewise, the client can emit an event to the server on a namespace by
+providing its in the ``emit()`` call::
+
+ sio.emit('my message', {'foo': 'bar'}, namespace='/chat')
+
+If the ``namespaces`` argument of the ``connect()`` call isn't given, any
+namespaces used in event handlers are automatically connected.
+
+Class-Based Namespaces
+----------------------
+
+As an alternative to the decorator-based event handlers, the event handlers
+that belong to a namespace can be created as methods of a subclass of
+:class:`socketio.ClientNamespace`::
+
+ class MyCustomNamespace(socketio.ClientNamespace):
+ def on_connect(self):
+ pass
+
+ def on_disconnect(self):
+ pass
+
+ def on_my_event(self, data):
+ self.emit('my_response', data)
+
+ sio.register_namespace(MyCustomNamespace('/chat'))
+
+For asyncio based severs, namespaces must inherit from
+:class:`socketio.AsyncClientNamespace`, and can define event handlers as
+coroutines if desired::
+
+ class MyCustomNamespace(socketio.AsyncClientNamespace):
+ def on_connect(self):
+ pass
+
+ def on_disconnect(self):
+ pass
+
+ async def on_my_event(self, data):
+ await self.emit('my_response', data)
+
+ sio.register_namespace(MyCustomNamespace('/chat'))
+
+When class-based namespaces are used, any events received by the client are
+dispatched to a method named as the event name with the ``on_`` prefix. For
+example, event ``my_event`` will be handled by a method named ``on_my_event``.
+If an event is received for which there is no corresponding method defined in
+the namespace class, then the event is ignored. All event names used in
+class-based namespaces must use characters that are legal in method names.
+
+As a convenience to methods defined in a class-based namespace, the namespace
+instance includes versions of several of the methods in the
+:class:`socketio.Client` and :class:`socketio.AsyncClient` classes that
+default to the proper namespace when the ``namespace`` argument is not given.
+
+In the case that an event has a handler in a class-based namespace, and also a
+decorator-based function handler, only the standalone function handler is
+invoked.
+
+Disconnecting from the Server
+-----------------------------
+
+At any time the client can request to be disconnected from the server by
+invoking the ``disconnect()`` method::
+
+ sio.disconnect()
+
+For the ``asyncio`` client this is a coroutine::
+
+ await sio.disconnect()
+
+Managing Background Tasks
+-------------------------
+
+When a client connection to the server is established, a few background
+tasks will be spawned to keep the connection alive and handle incoming
+events. The application running on the main thread is free to do any
+work, as this is not going to prevent the functioning of the Socket.IO
+client.
+
+If the application does not have anything to do in the main thread and
+just wants to wait until the connection with the server ends, it can call
+the ``wait()`` method::
+
+ sio.wait()
+
+Or in the ``asyncio`` version::
+
+ await sio.wait()
+
+For the convenience of the application, a helper function is provided to
+start a custom background task::
+
+ def my_background_task(my_argument)
+ # do some background work here!
+ pass
+
+ sio.start_background_task(my_background_task, 123)
+
+The arguments passed to this method are the background function and any
+positional or keyword arguments to invoke the function with.
+
+Here is the ``asyncio`` version::
+
+ async def my_background_task(my_argument)
+ # do some background work here!
+ pass
+
+ sio.start_background_task(my_background_task, 123)
+
+Note that this function is not a coroutine, since it does not wait for the
+background function to end. The background function must be a coroutine.
+
+The ``sleep()`` method is a second convenince function that is provided for
+the benefit of applications working with background tasks of their own::
+
+ sio.sleep(2)
+
+Or for ``asyncio``::
+
+ await sio.sleep(2)
+
+The single argument passed to the method is the number of seconds to sleep
+for.
diff --git a/docs/deployment.rst b/docs/deployment.rst
deleted file mode 100644
index 7de1644..0000000
--- a/docs/deployment.rst
+++ /dev/null
@@ -1,277 +0,0 @@
-Deployment
-==========
-
-The following sections describe a variety of deployment strategies for
-Socket.IO servers.
-
-aiohttp
--------
-
-`Aiohttp `_ is a framework with support for HTTP
-and WebSocket, based on asyncio. Support for this framework is limited to Python
-3.5 and newer.
-
-Instances of class ``socketio.AsyncServer`` will automatically use aiohttp
-for asynchronous operations if the library is installed. To request its use
-explicitly, the ``async_mode`` option can be given in the constructor::
-
- sio = socketio.AsyncServer(async_mode='aiohttp')
-
-A server configured for aiohttp must be attached to an existing application::
-
- app = web.Application()
- sio.attach(app)
-
-The aiohttp application can define regular routes that will coexist with the
-Socket.IO server. A typical pattern is to add routes that serve a client
-application and any associated static files.
-
-The aiohttp application is then executed in the usual manner::
-
- if __name__ == '__main__':
- web.run_app(app)
-
-Tornado
--------
-
-`Tornado `_ is a web framework with support
-for HTTP and WebSocket. Support for this framework requires Python 3.5 and
-newer. Only Tornado version 5 and newer are supported, thanks to its tight
-integration with asyncio.
-
-Instances of class ``socketio.AsyncServer`` will automatically use tornado
-for asynchronous operations if the library is installed. To request its use
-explicitly, the ``async_mode`` option can be given in the constructor::
-
- sio = socketio.AsyncServer(async_mode='tornado')
-
-A server configured for tornado must include a request handler for
-Engine.IO::
-
- app = tornado.web.Application(
- [
- (r"/socketio.io/", socketio.get_tornado_handler(sio)),
- ],
- # ... other application options
- )
-
-The tornado application can define other routes that will coexist with the
-Socket.IO server. A typical pattern is to add routes that serve a client
-application and any associated static files.
-
-The tornado application is then executed in the usual manner::
-
- app.listen(port)
- tornado.ioloop.IOLoop.current().start()
-
-Sanic
------
-
-`Sanic `_ is a very efficient asynchronous web
-server for Python 3.5 and newer.
-
-Instances of class ``socketio.AsyncServer`` will automatically use Sanic for
-asynchronous operations if the framework is installed. To request its use
-explicitly, the ``async_mode`` option can be given in the constructor::
-
- sio = socketio.AsyncServer(async_mode='sanic')
-
-A server configured for aiohttp must be attached to an existing application::
-
- app = web.Application()
- sio.attach(app)
-
-The Sanic application can define regular routes that will coexist with the
-Socket.IO server. A typical pattern is to add routes that serve a client
-application and any associated static files.
-
-The Sanic application is then executed in the usual manner::
-
- if __name__ == '__main__':
- app.run()
-
-Uvicorn, Daphne, and other ASGI servers
----------------------------------------
-
-The ``socketio.ASGIApp`` class is an ASGI compatible application that can
-forward Socket.IO traffic to an ``socketio.AsyncServer`` instance::
-
- sio = socketio.AsyncServer(async_mode='asgi')
- app = socketio.ASGIApp(sio)
-
-The application can then be deployed with any ASGI compatible web server.
-
-Eventlet
---------
-
-`Eventlet `_ is a high performance concurrent networking
-library for Python 2 and 3 that uses coroutines, enabling code to be written in
-the same style used with the blocking standard library functions. An Socket.IO
-server deployed with eventlet has access to the long-polling and WebSocket
-transports.
-
-Instances of class ``socketio.Server`` will automatically use eventlet for
-asynchronous operations if the library is installed. To request its use
-explicitly, the ``async_mode`` option can be given in the constructor::
-
- sio = socketio.Server(async_mode='eventlet')
-
-A server configured for eventlet is deployed as a regular WSGI application,
-using the provided ``socketio.Middleware``::
-
- app = socketio.Middleware(sio)
- import eventlet
- eventlet.wsgi.server(eventlet.listen(('', 8000)), app)
-
-Using Gunicorn with Eventlet
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-An alternative to running the eventlet WSGI server as above is to use
-`gunicorn `_, a fully featured pure Python web server. The
-command to launch the application under gunicorn is shown below::
-
- $ gunicorn -k eventlet -w 1 module:app
-
-Due to limitations in its load balancing algorithm, gunicorn can only be used
-with one worker process, so the ``-w`` option cannot be set to a value higher
-than 1. A single eventlet worker can handle a large number of concurrent
-clients, each handled by a greenlet.
-
-Eventlet provides a ``monkey_patch()`` function that replaces all the blocking
-functions in the standard library with equivalent asynchronous versions. While
-python-socketio does not require monkey patching, other libraries such as
-database drivers are likely to require it.
-
-Gevent
-------
-
-`Gevent `_ is another asynchronous framework based on
-coroutines, very similar to eventlet. An Socket.IO server deployed with
-gevent has access to the long-polling transport. If project
-`gevent-websocket `_ is
-installed, the WebSocket transport is also available.
-
-Instances of class ``socketio.Server`` will automatically use gevent for
-asynchronous operations if the library is installed and eventlet is not
-installed. To request gevent to be selected explicitly, the ``async_mode``
-option can be given in the constructor::
-
- sio = socketio.Server(async_mode='gevent')
-
-A server configured for gevent is deployed as a regular WSGI application,
-using the provided ``socketio.Middleware``::
-
- app = socketio.Middleware(sio)
- from gevent import pywsgi
- pywsgi.WSGIServer(('', 8000), app).serve_forever()
-
-If the WebSocket transport is installed, then the server must be started as
-follows::
-
- from gevent import pywsgi
- from geventwebsocket.handler import WebSocketHandler
- app = socketio.Middleware(sio)
- pywsgi.WSGIServer(('', 8000), app,
- handler_class=WebSocketHandler).serve_forever()
-
-Using Gunicorn with Gevent
-~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-An alternative to running the gevent WSGI server as above is to use
-`gunicorn `_, a fully featured pure Python web server. The
-command to launch the application under gunicorn is shown below::
-
- $ gunicorn -k gevent -w 1 module:app
-
-Or to include WebSocket::
-
- $ gunicorn -k geventwebsocket.gunicorn.workers.GeventWebSocketWorker -w 1 module: app
-
-Same as with eventlet, due to limitations in its load balancing algorithm,
-gunicorn can only be used with one worker process, so the ``-w`` option cannot
-be higher than 1. A single gevent worker can handle a large number of
-concurrent clients through the use of greenlets.
-
-Gevent provides a ``monkey_patch()`` function that replaces all the blocking
-functions in the standard library with equivalent asynchronous versions. While
-python-socketio does not require monkey patching, other libraries such as
-database drivers are likely to require it.
-
-uWSGI
------
-
-When using the uWSGI server in combination with gevent, the Socket.IO server
-can take advantage of uWSGI's native WebSocket support.
-
-Instances of class ``socketio.Server`` will automatically use this option for
-asynchronous operations if both gevent and uWSGI are installed and eventlet is
-not installed. To request this asynchronous mode explicitly, the
-``async_mode`` option can be given in the constructor::
-
- # gevent with uWSGI
- sio = socketio.Server(async_mode='gevent_uwsgi')
-
-A complete explanation of the configuration and usage of the uWSGI server is
-beyond the scope of this documentation. The uWSGI server is a fairly complex
-package that provides a large and comprehensive set of options. It must be
-compiled with WebSocket and SSL support for the WebSocket transport to be
-available. As way of an introduction, the following command starts a uWSGI
-server for the ``latency.py`` example on port 5000::
-
- $ uwsgi --http :5000 --gevent 1000 --http-websockets --master --wsgi-file latency.py --callable app
-
-Standard Threads
-----------------
-
-While not comparable to eventlet and gevent in terms of performance,
-the Socket.IO server can also be configured to work with multi-threaded web
-servers that use standard Python threads. This is an ideal setup to use with
-development servers such as `Werkzeug `_. Only the
-long-polling transport is currently available when using standard threads.
-
-Instances of class ``socketio.Server`` will automatically use the threading
-mode if neither eventlet nor gevent are not installed. To request the
-threading mode explicitly, the ``async_mode`` option can be given in the
-constructor::
-
- sio = socketio.Server(async_mode='threading')
-
-A server configured for threading is deployed as a regular web application,
-using any WSGI complaint multi-threaded server. The example below deploys an
-Socket.IO application combined with a Flask web application, using Flask's
-development web server based on Werkzeug::
-
- sio = socketio.Server(async_mode='threading')
- app = Flask(__name__)
- app.wsgi_app = socketio.Middleware(sio, app.wsgi_app)
-
- # ... Socket.IO and Flask handler functions ...
-
- if __name__ == '__main__':
- app.run(threaded=True)
-
-When using the threading mode, it is important to ensure that the WSGI server
-can handle multiple concurrent requests using threads, since a client can have
-up to two outstanding requests at any given time. The Werkzeug server is
-single-threaded by default, so the ``threaded=True`` option is required.
-
-Note that servers that use worker processes instead of threads, such as
-gunicorn, do not support a Socket.IO server configured in threading mode.
-
-Scalability Notes
------------------
-
-Socket.IO is a stateful protocol, which makes horizontal scaling more
-difficult. To deploy a cluster of Socket.IO processes hosted on one or
-multiple servers, the following conditions must be met:
-
-- Each Socket.IO process must be able to handle multiple requests
- concurrently. This is required because long-polling clients send two
- requests in parallel. Worker processes that can only handle one request at a
- time are not supported.
-- The load balancer must be configured to always forward requests from a
- client to the same worker process. Load balancers call this *sticky
- sessions*, or *session affinity*.
-- The worker processes need to communicate with each other to coordinate
- complex operations such as broadcasts. This is done through a configured
- message queue. See the section on using message queues for details.
diff --git a/docs/index.rst b/docs/index.rst
index b267c5c..a475d57 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -6,20 +6,17 @@
python-socketio
===============
-This projects implements a Socket.IO server that can run standalone or
-integrated with a variety of Python web frameworks.
+This projects implements Socket.IO clients and servers that can run standalone
+or integrated with a variety of Python web frameworks.
.. toctree::
- :maxdepth: 2
+ :maxdepth: 3
intro
- guide
- deployment
+ client
+ server
api
-Indices and tables
-------------------
-
* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`
diff --git a/docs/intro.rst b/docs/intro.rst
index 844f2a6..e05b626 100644
--- a/docs/intro.rst
+++ b/docs/intro.rst
@@ -10,57 +10,84 @@ What is Socket.IO?
------------------
Socket.IO is a transport protocol that enables real-time bidirectional
-event-based communication between clients (typically web browsers or
-smartphones) and a server. There are Socket.IO clients and servers implemented
-in a variety of languages, including JavaScript, Python, C++, Swift, C# and
-PHP.
-
-Features
---------
-
-- Fully compatible with the
- `Javascript `_,
- `Swift `_,
- `C++ `_ and
- `Java `_ official
- Socket.IO clients, plus any third party clients that comply with the
- Socket.IO specification.
-- Compatible with Python 2.7 and Python 3.3+.
-- Supports large number of clients even on modest hardware due to being
- asynchronous, even when asyncio is not used.
-- Compatible with `aiohttp `_,
- `sanic `_,
- `tornado `_,
- `eventlet `_,
- `gevent `_,
- or any `WSGI `_ or
- `ASGI `_ compatible server.
-- Includes WSGI and ASGI middlewares that integrate Socket.IO traffic with
- other web applications.
-- Broadcasting of messages to all connected clients, or to subsets of them
- assigned to "rooms".
-- Optional support for multiple servers, connected through a messaging queue
- such as Redis or RabbitMQ.
-- Send messages to clients from external processes, such as Celery workers or
- auxiliary scripts.
-- Event-based architecture implemented with decorators that hides the details
- of the protocol.
-- Support for HTTP long-polling and WebSocket transports.
-- Support for XHR2 and XHR browsers.
-- Support for text and binary messages.
-- Support for gzip and deflate HTTP compression.
-- Configurable CORS responses, to avoid cross-origin problems with browsers.
+event-based communication between clients (typically, though not always,
+web browsers) and a server. The official implementations of the client
+and server components are written in JavaScript. This package provides
+Python implementations of both, each with standard and asyncio variants.
+
+Client Examples
+---------------
+
+The example that follows shows a simple Python client:
+
+.. code:: python
+
+ import socketio
+
+ sio = socketio.Client()
+
+ @sio.on('connect')
+ def on_connect():
+ print('connection established')
+
+ @sio.on('my message')
+ def on_message(data):
+ print('message received with ', data)
+ sio.emit('my response', {'response': 'my response'})
+
+ @sio.on('disconnect')
+ def on_disconnect():
+ print('disconnected from server')
+
+ sio.connect('http://localhost:5000')
+ sio.wait()
+
+Client Features
+---------------
+
+- Can connect to other Socket.IO complaint servers besides the one in
+ this package.
+- Compatible with Python 2.7 and 3.5+.
+- Two versions of the client, one for standard Python and another for
+ asyncio.
+- Uses an event-based architecture implemented with decorators that
+ hides the details of the protocol.
+- Implements HTTP long-polling and WebSocket transports.
+- Automatically reconnects to the server if the connection is dropped.
+
+Server Examples
+---------------
+
+The following application is a basic server example that uses the Eventlet
+asynchronous server:
+
+.. code:: python
+
+ import engineio
+ import eventlet
-Examples
---------
+ sio = socketio.Server()
+ app = socketio.WSGIApp(eio, static_files={
+ '/': {'content_type': 'text/html', 'filename': 'index.html'}
+ })
+
+ @sio.on('connect')
+ def connect(sid, environ):
+ print('connect ', sid)
+
+ @sio.on('my message')
+ def message(sid, data):
+ print('message ', data)
-The Socket.IO server can be installed with pip::
+ @sio.on('disconnect')
+ def disconnect(sid):
+ print('disconnect ', sid)
- pip install python-socketio
+ if __name__ == '__main__':
+ eventlet.wsgi.server(eventlet.listen(('', 5000)), app)
-The following is a basic example of a Socket.IO server that uses the
-`aiohttp `_ framework for asyncio (Python 3.5+
-only):
+Below is a similar application, coded for ``asyncio`` (Python 3.5+ only) and the
+Uvicorn web server:
.. code:: python
@@ -95,54 +122,35 @@ only):
if __name__ == '__main__':
web.run_app(app)
-And below is a similar example, but using Flask and Eventlet. This example is
-compatible with Python 2.7 and 3.3+::
-
- import socketio
- import eventlet
- from flask import Flask, render_template
-
- sio = socketio.Server()
- app = Flask(__name__)
-
- @app.route('/')
- def index():
- """Serve the client-side application."""
- return render_template('index.html')
-
- @sio.on('connect')
- def connect(sid, environ):
- print('connect ', sid)
-
- @sio.on('my message')
- def message(sid, data):
- print('message ', data)
-
- @sio.on('disconnect')
- def disconnect(sid):
- print('disconnect ', sid)
-
- if __name__ == '__main__':
- # wrap Flask application with socketio's middleware
- app = socketio.WSGIApp(sio, app)
-
- # deploy as an eventlet WSGI server
- eventlet.wsgi.server(eventlet.listen(('', 8000)), app)
-
-The client-side application must include the
-`socket.io-client `_ library
-(versions 1.3.5 or newer recommended).
+Server Features
+---------------
-Each time a client connects to the server the ``connect`` event handler is
-invoked with the ``sid`` (session ID) assigned to the connection and the WSGI
-environment dictionary. The server can inspect authentication or other headers
-to decide if the client is allowed to connect. To reject a client the handler
-must return ``False``.
-
-When the client sends an event to the server, the appropriate event handler is
-invoked with the ``sid`` and the message, which can be a single or multiple
-arguments. The application can define as many events as needed and associate
-them with event handlers. An event is defined simply by a name.
-
-When a connection with a client is broken, the ``disconnect`` event is called,
-allowing the application to perform cleanup.
+- Can connect to servers running other complaint Socket.IO clients besides
+ the one in this package.
+- Compatible with Python 2.7 and Python 3.5+.
+- Two versions of the server, one for standard Python and another for
+ asyncio.
+- Supports large number of clients even on modest hardware due to being
+ asynchronous.
+- Can be hosted on any `WSGI `_ and
+ `ASGI `_ web servers includind
+ `Gunicorn `_, `Uvicorn `_,
+ `eventlet `_ and `gevent `_.
+- Can be integrated with WSGI applications written in frameworks such as Flask, Django,
+ etc.
+- Can be integrated with `aiohttp `_,
+ `sanic `_ and `tornado `_
+ ``asyncio`` applications.
+- Broadcasting of messages to all connected clients, or to subsets of them
+ assigned to "rooms".
+- Optional support for multiple servers, connected through a messaging queue
+ such as Redis or RabbitMQ.
+- Send messages to clients from external processes, such as Celery workers or
+ auxiliary scripts.
+- Event-based architecture implemented with decorators that hides the details
+ of the protocol.
+- Support for HTTP long-polling and WebSocket transports.
+- Support for XHR2 and XHR browsers.
+- Support for text and binary messages.
+- Support for gzip and deflate HTTP compression.
+- Configurable CORS responses, to avoid cross-origin problems with browsers.
diff --git a/docs/guide.rst b/docs/server.rst
similarity index 52%
rename from docs/guide.rst
rename to docs/server.rst
index 27f5d4e..4b35184 100644
--- a/docs/guide.rst
+++ b/docs/server.rst
@@ -1,8 +1,30 @@
-User's Guide
-============
+The Socket.IO Server
+====================
-The ``Server`` and ``AsyncServer`` classes
-------------------------------------------
+This package contains two Socket.IO clients:
+
+- The :func:`socketio.Server` class creates a server compatible with the
+ Python standard library.
+- The :func:`socketio.AsyncServer` class creates a server compatible with
+ the ``asyncio`` package.
+
+The methods in the two clients are the same, with the only difference that in
+the ``asyncio`` client most methods are implemented as coroutines.
+
+Installation
+------------
+
+To install the Socket.IO server along with its dependencies, use the following
+command::
+
+ pip install python-socketio
+
+In addition to the server, you will need to select an asynchronous framework
+or server to use along with it. The list of supported packages is covered
+in the :ref:`deployment-strategies` section.
+
+Creating a Server Instance
+--------------------------
A Socket.IO server is an instance of class :class:`socketio.Server`. This
instance can be transformed into a standard WSGI application by wrapping it
@@ -49,8 +71,8 @@ servers to integrate easily into existing WSGI or ASGI applications::
app = socketio.WSGIApp(sio, app)
-Receiving Events
-----------------
+Defining Event Handlers
+-----------------------
The Socket.IO protocol is event based. When a client wants to communicate with
the server it *emits* an event. Each event has a name, and a list of
@@ -89,8 +111,8 @@ standard WSGI format containing the request information, including HTTP
headers. After inspecting the request, the connect event handler can return
``False`` to reject the connection with the client.
-Sending Events
---------------
+Emitting Events
+---------------
Socket.IO is a bidirectional protocol, so at any time the server can send an
event to its connected clients. The :func:`socketio.Server.emit` method is
@@ -112,44 +134,6 @@ used to identify the client that should receive the event, and is set to the
``sid`` value assigned to that client's connection with the server. When
omitted, the event is broadcasted to all connected clients.
-Rooms
------
-
-To make it easy for the server to emit events to groups of related clients,
-the application can put its clients into "rooms", and then address messages to
-these rooms.
-
-In the previous section the ``room`` argument of the
-:func:`socketio.SocketIO.emit` method was used to designate a specific
-client as the recipient of the event. This is because upon connection, a
-personal room for each client is created and named with the ``sid`` assigned
-to the connection. The application is then free to create additional rooms and
-manage which clients are in them using the :func:`socketio.Server.enter_room`
-and :func:`socketio.Server.leave_room` methods. Clients can be in as many
-rooms as needed and can be moved between rooms as often as necessary.
-
-::
-
- @sio.on('chat')
- def begin_chat(sid):
- sio.enter_room(sid, 'chat_users')
-
- @sio.on('exit_chat')
- def exit_chat(sid):
- sio.leave_room(sid, 'chat_users')
-
-In chat applications it is often desired that an event is broadcasted to all
-the members of the room except one, which is the originator of the event such
-as a chat message. The :func:`socketio.Server.emit` method provides an
-optional ``skip_sid`` argument to indicate a client that should be skipped
-during the broadcast.
-
-::
-
- @sio.on('my message')
- def message(sid, data):
- sio.emit('my reply', data, room='chat_users', skip_sid=sid)
-
Event Callbacks
---------------
@@ -253,6 +237,44 @@ that a single instance of a namespace class is used for all clients, and
consequently, a namespace instance cannot be used to store client specific
information.
+Rooms
+-----
+
+To make it easy for the server to emit events to groups of related clients,
+the application can put its clients into "rooms", and then address messages to
+these rooms.
+
+In the previous section the ``room`` argument of the
+:func:`socketio.SocketIO.emit` method was used to designate a specific
+client as the recipient of the event. This is because upon connection, a
+personal room for each client is created and named with the ``sid`` assigned
+to the connection. The application is then free to create additional rooms and
+manage which clients are in them using the :func:`socketio.Server.enter_room`
+and :func:`socketio.Server.leave_room` methods. Clients can be in as many
+rooms as needed and can be moved between rooms as often as necessary.
+
+::
+
+ @sio.on('chat')
+ def begin_chat(sid):
+ sio.enter_room(sid, 'chat_users')
+
+ @sio.on('exit_chat')
+ def exit_chat(sid):
+ sio.leave_room(sid, 'chat_users')
+
+In chat applications it is often desired that an event is broadcasted to all
+the members of the room except one, which is the originator of the event such
+as a chat message. The :func:`socketio.Server.emit` method provides an
+optional ``skip_sid`` argument to indicate a client that should be skipped
+during the broadcast.
+
+::
+
+ @sio.on('my message')
+ def message(sid, data):
+ sio.emit('my reply', data, room='chat_users', skip_sid=sid)
+
Using a Message Queue
---------------------
@@ -344,3 +366,283 @@ example::
# emit an event
external_sio.emit('my event', data={'foo': 'bar'}, room='my room')
+
+.. _deployment-strategies:
+
+Deployment Strategies
+---------------------
+
+The following sections describe a variety of deployment strategies for
+Socket.IO servers.
+
+Aiohttp
+~~~~~~~
+
+`Aiohttp `_ is a framework with support for HTTP
+and WebSocket, based on asyncio. Support for this framework is limited to Python
+3.5 and newer.
+
+Instances of class ``socketio.AsyncServer`` will automatically use aiohttp
+for asynchronous operations if the library is installed. To request its use
+explicitly, the ``async_mode`` option can be given in the constructor::
+
+ sio = socketio.AsyncServer(async_mode='aiohttp')
+
+A server configured for aiohttp must be attached to an existing application::
+
+ app = web.Application()
+ sio.attach(app)
+
+The aiohttp application can define regular routes that will coexist with the
+Socket.IO server. A typical pattern is to add routes that serve a client
+application and any associated static files.
+
+The aiohttp application is then executed in the usual manner::
+
+ if __name__ == '__main__':
+ web.run_app(app)
+
+Tornado
+~~~~~~~
+
+`Tornado `_ is a web framework with support
+for HTTP and WebSocket. Support for this framework requires Python 3.5 and
+newer. Only Tornado version 5 and newer are supported, thanks to its tight
+integration with asyncio.
+
+Instances of class ``socketio.AsyncServer`` will automatically use tornado
+for asynchronous operations if the library is installed. To request its use
+explicitly, the ``async_mode`` option can be given in the constructor::
+
+ sio = socketio.AsyncServer(async_mode='tornado')
+
+A server configured for tornado must include a request handler for
+Engine.IO::
+
+ app = tornado.web.Application(
+ [
+ (r"/socketio.io/", socketio.get_tornado_handler(sio)),
+ ],
+ # ... other application options
+ )
+
+The tornado application can define other routes that will coexist with the
+Socket.IO server. A typical pattern is to add routes that serve a client
+application and any associated static files.
+
+The tornado application is then executed in the usual manner::
+
+ app.listen(port)
+ tornado.ioloop.IOLoop.current().start()
+
+Sanic
+~~~~~
+
+`Sanic `_ is a very efficient asynchronous web
+server for Python 3.5 and newer.
+
+Instances of class ``socketio.AsyncServer`` will automatically use Sanic for
+asynchronous operations if the framework is installed. To request its use
+explicitly, the ``async_mode`` option can be given in the constructor::
+
+ sio = socketio.AsyncServer(async_mode='sanic')
+
+A server configured for aiohttp must be attached to an existing application::
+
+ app = web.Application()
+ sio.attach(app)
+
+The Sanic application can define regular routes that will coexist with the
+Socket.IO server. A typical pattern is to add routes that serve a client
+application and any associated static files.
+
+The Sanic application is then executed in the usual manner::
+
+ if __name__ == '__main__':
+ app.run()
+
+Uvicorn, Daphne, and other ASGI servers
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The ``socketio.ASGIApp`` class is an ASGI compatible application that can
+forward Socket.IO traffic to an ``socketio.AsyncServer`` instance::
+
+ sio = socketio.AsyncServer(async_mode='asgi')
+ app = socketio.ASGIApp(sio)
+
+The application can then be deployed with any ASGI compatible web server.
+
+Eventlet
+~~~~~~~~
+
+`Eventlet `_ is a high performance concurrent networking
+library for Python 2 and 3 that uses coroutines, enabling code to be written in
+the same style used with the blocking standard library functions. An Socket.IO
+server deployed with eventlet has access to the long-polling and WebSocket
+transports.
+
+Instances of class ``socketio.Server`` will automatically use eventlet for
+asynchronous operations if the library is installed. To request its use
+explicitly, the ``async_mode`` option can be given in the constructor::
+
+ sio = socketio.Server(async_mode='eventlet')
+
+A server configured for eventlet is deployed as a regular WSGI application,
+using the provided ``socketio.Middleware``::
+
+ app = socketio.Middleware(sio)
+ import eventlet
+ eventlet.wsgi.server(eventlet.listen(('', 8000)), app)
+
+Eventlet with Gunicorn
+~~~~~~~~~~~~~~~~~~~~~~
+
+An alternative to running the eventlet WSGI server as above is to use
+`gunicorn `_, a fully featured pure Python web server. The
+command to launch the application under gunicorn is shown below::
+
+ $ gunicorn -k eventlet -w 1 module:app
+
+Due to limitations in its load balancing algorithm, gunicorn can only be used
+with one worker process, so the ``-w`` option cannot be set to a value higher
+than 1. A single eventlet worker can handle a large number of concurrent
+clients, each handled by a greenlet.
+
+Eventlet provides a ``monkey_patch()`` function that replaces all the blocking
+functions in the standard library with equivalent asynchronous versions. While
+python-socketio does not require monkey patching, other libraries such as
+database drivers are likely to require it.
+
+Gevent
+~~~~~~
+
+`Gevent `_ is another asynchronous framework based on
+coroutines, very similar to eventlet. An Socket.IO server deployed with
+gevent has access to the long-polling transport. If project
+`gevent-websocket `_ is
+installed, the WebSocket transport is also available.
+
+Instances of class ``socketio.Server`` will automatically use gevent for
+asynchronous operations if the library is installed and eventlet is not
+installed. To request gevent to be selected explicitly, the ``async_mode``
+option can be given in the constructor::
+
+ sio = socketio.Server(async_mode='gevent')
+
+A server configured for gevent is deployed as a regular WSGI application,
+using the provided ``socketio.Middleware``::
+
+ app = socketio.Middleware(sio)
+ from gevent import pywsgi
+ pywsgi.WSGIServer(('', 8000), app).serve_forever()
+
+If the WebSocket transport is installed, then the server must be started as
+follows::
+
+ from gevent import pywsgi
+ from geventwebsocket.handler import WebSocketHandler
+ app = socketio.Middleware(sio)
+ pywsgi.WSGIServer(('', 8000), app,
+ handler_class=WebSocketHandler).serve_forever()
+
+Gevent with Gunicorn
+~~~~~~~~~~~~~~~~~~~~
+
+An alternative to running the gevent WSGI server as above is to use
+`gunicorn `_, a fully featured pure Python web server. The
+command to launch the application under gunicorn is shown below::
+
+ $ gunicorn -k gevent -w 1 module:app
+
+Or to include WebSocket::
+
+ $ gunicorn -k geventwebsocket.gunicorn.workers.GeventWebSocketWorker -w 1 module: app
+
+Same as with eventlet, due to limitations in its load balancing algorithm,
+gunicorn can only be used with one worker process, so the ``-w`` option cannot
+be higher than 1. A single gevent worker can handle a large number of
+concurrent clients through the use of greenlets.
+
+Gevent provides a ``monkey_patch()`` function that replaces all the blocking
+functions in the standard library with equivalent asynchronous versions. While
+python-socketio does not require monkey patching, other libraries such as
+database drivers are likely to require it.
+
+uWSGI
+~~~~~
+
+When using the uWSGI server in combination with gevent, the Socket.IO server
+can take advantage of uWSGI's native WebSocket support.
+
+Instances of class ``socketio.Server`` will automatically use this option for
+asynchronous operations if both gevent and uWSGI are installed and eventlet is
+not installed. To request this asynchronous mode explicitly, the
+``async_mode`` option can be given in the constructor::
+
+ # gevent with uWSGI
+ sio = socketio.Server(async_mode='gevent_uwsgi')
+
+A complete explanation of the configuration and usage of the uWSGI server is
+beyond the scope of this documentation. The uWSGI server is a fairly complex
+package that provides a large and comprehensive set of options. It must be
+compiled with WebSocket and SSL support for the WebSocket transport to be
+available. As way of an introduction, the following command starts a uWSGI
+server for the ``latency.py`` example on port 5000::
+
+ $ uwsgi --http :5000 --gevent 1000 --http-websockets --master --wsgi-file latency.py --callable app
+
+Standard Threads
+~~~~~~~~~~~~~~~~
+
+While not comparable to eventlet and gevent in terms of performance,
+the Socket.IO server can also be configured to work with multi-threaded web
+servers that use standard Python threads. This is an ideal setup to use with
+development servers such as `Werkzeug `_. Only the
+long-polling transport is currently available when using standard threads.
+
+Instances of class ``socketio.Server`` will automatically use the threading
+mode if neither eventlet nor gevent are not installed. To request the
+threading mode explicitly, the ``async_mode`` option can be given in the
+constructor::
+
+ sio = socketio.Server(async_mode='threading')
+
+A server configured for threading is deployed as a regular web application,
+using any WSGI complaint multi-threaded server. The example below deploys an
+Socket.IO application combined with a Flask web application, using Flask's
+development web server based on Werkzeug::
+
+ sio = socketio.Server(async_mode='threading')
+ app = Flask(__name__)
+ app.wsgi_app = socketio.Middleware(sio, app.wsgi_app)
+
+ # ... Socket.IO and Flask handler functions ...
+
+ if __name__ == '__main__':
+ app.run(threaded=True)
+
+When using the threading mode, it is important to ensure that the WSGI server
+can handle multiple concurrent requests using threads, since a client can have
+up to two outstanding requests at any given time. The Werkzeug server is
+single-threaded by default, so the ``threaded=True`` option is required.
+
+Note that servers that use worker processes instead of threads, such as
+gunicorn, do not support a Socket.IO server configured in threading mode.
+
+Scalability Notes
+~~~~~~~~~~~~~~~~~
+
+Socket.IO is a stateful protocol, which makes horizontal scaling more
+difficult. To deploy a cluster of Socket.IO processes hosted on one or
+multiple servers, the following conditions must be met:
+
+- Each Socket.IO process must be able to handle multiple requests
+ concurrently. This is required because long-polling clients send two
+ requests in parallel. Worker processes that can only handle one request at a
+ time are not supported.
+- The load balancer must be configured to always forward requests from a
+ client to the same worker process. Load balancers call this *sticky
+ sessions*, or *session affinity*.
+- The worker processes need to communicate with each other to coordinate
+ complex operations such as broadcasts. This is done through a configured
+ message queue. See the section on using message queues for details.
diff --git a/setup.py b/setup.py
index 2456ad6..82ef3f3 100755
--- a/setup.py
+++ b/setup.py
@@ -31,6 +31,14 @@ setup(
'six>=1.9.0',
'python-engineio>=3.0.0'
],
+ extras_require={
+ 'client': [
+ 'python-engineio[client]>=3.0.0'
+ ],
+ 'asyncio_client': [
+ 'python-engineio[asyncio_client]>=3.0.0'
+ ]
+ },
tests_require=[
'mock',
],
diff --git a/socketio/asyncio_client.py b/socketio/asyncio_client.py
index de789ec..f5fae64 100644
--- a/socketio/asyncio_client.py
+++ b/socketio/asyncio_client.py
@@ -92,6 +92,9 @@ class AsyncClient(client.Client):
if namespaces is None:
namespaces = set(self.handlers.keys()).union(
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 != '/']
try:
await self.eio.connect(url, headers=headers,
diff --git a/socketio/asyncio_namespace.py b/socketio/asyncio_namespace.py
index aafe017..38bad98 100644
--- a/socketio/asyncio_namespace.py
+++ b/socketio/asyncio_namespace.py
@@ -4,7 +4,7 @@ from socketio import namespace
class AsyncNamespace(namespace.Namespace):
- """Base class for asyncio class-based namespaces.
+ """Base class for asyncio server-side class-based namespaces.
A class-based namespace is a class that contains all the event handlers
for a Socket.IO namespace. The event handlers are methods of the class
@@ -96,7 +96,7 @@ class AsyncNamespace(namespace.Namespace):
class AsyncClientNamespace(namespace.ClientNamespace):
- """Base class for asyncio class-based namespaces.
+ """Base class for asyncio client-side class-based namespaces.
A class-based namespace is a class that contains all the event handlers
for a Socket.IO namespace. The event handlers are methods of the class
diff --git a/socketio/client.py b/socketio/client.py
index c252a17..484cb12 100644
--- a/socketio/client.py
+++ b/socketio/client.py
@@ -199,6 +199,9 @@ class Client(object):
if namespaces is None:
namespaces = set(self.handlers.keys()).union(
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 != '/']
try:
self.eio.connect(url, headers=headers, transports=transports,
diff --git a/tests/test_asyncio_client.py b/tests/test_asyncio_client.py
index b76a606..a09b838 100644
--- a/tests/test_asyncio_client.py
+++ b/tests/test_asyncio_client.py
@@ -60,6 +60,22 @@ class TestAsyncClient(unittest.TestCase):
'url', headers='headers', transports='transports',
engineio_path='path')
+ def test_connect_one_namespace(self):
+ c = asyncio_client.AsyncClient()
+ c.eio.connect = AsyncMock()
+ _run(c.connect('url', headers='headers', transports='transports',
+ namespaces='/foo',
+ socketio_path='path'))
+ self.assertEqual(c.connection_url, 'url')
+ self.assertEqual(c.connection_headers, 'headers')
+ self.assertEqual(c.connection_transports, 'transports')
+ self.assertEqual(c.connection_namespaces, ['/foo'])
+ self.assertEqual(c.socketio_path, 'path')
+ self.assertEqual(c.namespaces, ['/foo'])
+ c.eio.connect.mock.assert_called_once_with(
+ 'url', headers='headers', transports='transports',
+ engineio_path='path')
+
def test_connect_default_namespaces(self):
c = asyncio_client.AsyncClient()
c.eio.connect = AsyncMock()
diff --git a/tests/test_client.py b/tests/test_client.py
index a724577..7458b5c 100644
--- a/tests/test_client.py
+++ b/tests/test_client.py
@@ -137,6 +137,21 @@ class TestClient(unittest.TestCase):
'url', headers='headers', transports='transports',
engineio_path='path')
+ def test_connect_one_namespace(self):
+ c = client.Client()
+ c.eio.connect = mock.MagicMock()
+ c.connect('url', headers='headers', transports='transports',
+ namespaces='/foo', socketio_path='path')
+ self.assertEqual(c.connection_url, 'url')
+ self.assertEqual(c.connection_headers, 'headers')
+ self.assertEqual(c.connection_transports, 'transports')
+ self.assertEqual(c.connection_namespaces, ['/foo'])
+ self.assertEqual(c.socketio_path, 'path')
+ self.assertEqual(c.namespaces, ['/foo'])
+ c.eio.connect.assert_called_once_with(
+ 'url', headers='headers', transports='transports',
+ engineio_path='path')
+
def test_connect_default_namespaces(self):
c = client.Client()
c.eio.connect = mock.MagicMock()