Code import
This commit is contained in:
29
venv/lib/python2.7/site-packages/socketio/__init__.py
Normal file
29
venv/lib/python2.7/site-packages/socketio/__init__.py
Normal file
@@ -0,0 +1,29 @@
|
||||
import sys
|
||||
|
||||
from .middleware import Middleware
|
||||
from .base_manager import BaseManager
|
||||
from .pubsub_manager import PubSubManager
|
||||
from .kombu_manager import KombuManager
|
||||
from .redis_manager import RedisManager
|
||||
from .zmq_manager import ZmqManager
|
||||
from .server import Server
|
||||
from .namespace import Namespace
|
||||
if sys.version_info >= (3, 5): # pragma: no cover
|
||||
from .asyncio_server import AsyncServer
|
||||
from .asyncio_manager import AsyncManager
|
||||
from .asyncio_namespace import AsyncNamespace
|
||||
from .asyncio_redis_manager import AsyncRedisManager
|
||||
else: # pragma: no cover
|
||||
AsyncServer = None
|
||||
AsyncManager = None
|
||||
AsyncNamespace = None
|
||||
AsyncRedisManager = None
|
||||
|
||||
__version__ = '1.7.6'
|
||||
|
||||
__all__ = ['__version__', 'Middleware', 'Server', 'BaseManager',
|
||||
'PubSubManager', 'KombuManager', 'RedisManager', 'ZmqManager',
|
||||
'Namespace']
|
||||
if AsyncServer is not None: # pragma: no cover
|
||||
__all__ += ['AsyncServer', 'AsyncNamespace', 'AsyncManager',
|
||||
'AsyncRedisManager']
|
||||
BIN
venv/lib/python2.7/site-packages/socketio/__init__.pyc
Normal file
BIN
venv/lib/python2.7/site-packages/socketio/__init__.pyc
Normal file
Binary file not shown.
57
venv/lib/python2.7/site-packages/socketio/asyncio_manager.py
Normal file
57
venv/lib/python2.7/site-packages/socketio/asyncio_manager.py
Normal file
@@ -0,0 +1,57 @@
|
||||
import asyncio
|
||||
|
||||
from .base_manager import BaseManager
|
||||
|
||||
|
||||
class AsyncManager(BaseManager):
|
||||
"""Manage a client list for an asyncio server."""
|
||||
async def emit(self, event, data, namespace, room=None, skip_sid=None,
|
||||
callback=None, **kwargs):
|
||||
"""Emit a message to a single client, a room, or all the clients
|
||||
connected to the namespace.
|
||||
|
||||
Note: this method is a coroutine.
|
||||
"""
|
||||
if namespace not in self.rooms or room not in self.rooms[namespace]:
|
||||
return
|
||||
tasks = []
|
||||
for sid in self.get_participants(namespace, room):
|
||||
if sid != skip_sid:
|
||||
if callback is not None:
|
||||
id = self._generate_ack_id(sid, namespace, callback)
|
||||
else:
|
||||
id = None
|
||||
tasks.append(self.server._emit_internal(sid, event, data,
|
||||
namespace, id))
|
||||
if tasks == []: # pragma: no cover
|
||||
return
|
||||
await asyncio.wait(tasks)
|
||||
|
||||
async def close_room(self, room, namespace):
|
||||
"""Remove all participants from a room.
|
||||
|
||||
Note: this method is a coroutine.
|
||||
"""
|
||||
return super().close_room(room, namespace)
|
||||
|
||||
async def trigger_callback(self, sid, namespace, id, data):
|
||||
"""Invoke an application callback.
|
||||
|
||||
Note: this method is a coroutine.
|
||||
"""
|
||||
callback = None
|
||||
try:
|
||||
callback = self.callbacks[sid][namespace][id]
|
||||
except KeyError:
|
||||
# if we get an unknown callback we just ignore it
|
||||
self.server.logger.warning('Unknown callback received, ignoring.')
|
||||
else:
|
||||
del self.callbacks[sid][namespace][id]
|
||||
if callback is not None:
|
||||
if asyncio.iscoroutinefunction(callback) is True:
|
||||
try:
|
||||
await callback(*data)
|
||||
except asyncio.CancelledError: # pragma: no cover
|
||||
pass
|
||||
else:
|
||||
callback(*data)
|
||||
@@ -0,0 +1,95 @@
|
||||
import asyncio
|
||||
|
||||
from socketio import namespace
|
||||
|
||||
|
||||
class AsyncNamespace(namespace.Namespace):
|
||||
"""Base class for asyncio 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
|
||||
with the prefix ``on_``, such as ``on_connect``, ``on_disconnect``,
|
||||
``on_message``, ``on_json``, and so on. These can be regular functions or
|
||||
coroutines.
|
||||
|
||||
:param namespace: The Socket.IO namespace to be used with all the event
|
||||
handlers defined in this class. If this argument is
|
||||
omitted, the default namespace is used.
|
||||
"""
|
||||
def is_asyncio_based(self):
|
||||
return True
|
||||
|
||||
async def trigger_event(self, event, *args):
|
||||
"""Dispatch an event to the proper handler method.
|
||||
|
||||
In the most common usage, this method is not overloaded by subclasses,
|
||||
as it performs the routing of events to methods. However, this
|
||||
method can be overriden if special dispatching rules are needed, or if
|
||||
having a single method that catches all events is desired.
|
||||
|
||||
Note: this method is a coroutine.
|
||||
"""
|
||||
handler_name = 'on_' + event
|
||||
if hasattr(self, handler_name):
|
||||
handler = getattr(self, handler_name)
|
||||
if asyncio.iscoroutinefunction(handler) is True:
|
||||
try:
|
||||
ret = await handler(*args)
|
||||
except asyncio.CancelledError: # pragma: no cover
|
||||
pass
|
||||
else:
|
||||
ret = handler(*args)
|
||||
return ret
|
||||
|
||||
async def emit(self, event, data=None, room=None, skip_sid=None,
|
||||
namespace=None, callback=None):
|
||||
"""Emit a custom event to one or more connected clients.
|
||||
|
||||
The only difference with the :func:`socketio.Server.emit` method is
|
||||
that when the ``namespace`` argument is not given the namespace
|
||||
associated with the class is used.
|
||||
|
||||
Note: this method is a coroutine.
|
||||
"""
|
||||
return await self.server.emit(event, data=data, room=room,
|
||||
skip_sid=skip_sid,
|
||||
namespace=namespace or self.namespace,
|
||||
callback=callback)
|
||||
|
||||
async def send(self, data, room=None, skip_sid=None, namespace=None,
|
||||
callback=None):
|
||||
"""Send a message to one or more connected clients.
|
||||
|
||||
The only difference with the :func:`socketio.Server.send` method is
|
||||
that when the ``namespace`` argument is not given the namespace
|
||||
associated with the class is used.
|
||||
|
||||
Note: this method is a coroutine.
|
||||
"""
|
||||
return await self.server.send(data, room=room, skip_sid=skip_sid,
|
||||
namespace=namespace or self.namespace,
|
||||
callback=callback)
|
||||
|
||||
async def close_room(self, room, namespace=None):
|
||||
"""Close a room.
|
||||
|
||||
The only difference with the :func:`socketio.Server.close_room` method
|
||||
is that when the ``namespace`` argument is not given the namespace
|
||||
associated with the class is used.
|
||||
|
||||
Note: this method is a coroutine.
|
||||
"""
|
||||
return await self.server.close_room(
|
||||
room, namespace=namespace or self.namespace)
|
||||
|
||||
async def disconnect(self, sid, namespace=None):
|
||||
"""Disconnect a client.
|
||||
|
||||
The only difference with the :func:`socketio.Server.disconnect` method
|
||||
is that when the ``namespace`` argument is not given the namespace
|
||||
associated with the class is used.
|
||||
|
||||
Note: this method is a coroutine.
|
||||
"""
|
||||
return await self.server.disconnect(
|
||||
sid, namespace=namespace or self.namespace)
|
||||
@@ -0,0 +1,160 @@
|
||||
from functools import partial
|
||||
import uuid
|
||||
|
||||
import json
|
||||
import pickle
|
||||
import six
|
||||
|
||||
from .asyncio_manager import AsyncManager
|
||||
|
||||
|
||||
class AsyncPubSubManager(AsyncManager):
|
||||
"""Manage a client list attached to a pub/sub backend under asyncio.
|
||||
|
||||
This is a base class that enables multiple servers to share the list of
|
||||
clients, with the servers communicating events through a pub/sub backend.
|
||||
The use of a pub/sub backend also allows any client connected to the
|
||||
backend to emit events addressed to Socket.IO clients.
|
||||
|
||||
The actual backends must be implemented by subclasses, this class only
|
||||
provides a pub/sub generic framework for asyncio applications.
|
||||
|
||||
:param channel: The channel name on which the server sends and receives
|
||||
notifications.
|
||||
"""
|
||||
name = 'asyncpubsub'
|
||||
|
||||
def __init__(self, channel='socketio', write_only=False):
|
||||
super().__init__()
|
||||
self.channel = channel
|
||||
self.write_only = write_only
|
||||
self.host_id = uuid.uuid4().hex
|
||||
|
||||
def initialize(self):
|
||||
super().initialize()
|
||||
if not self.write_only:
|
||||
self.thread = self.server.start_background_task(self._thread)
|
||||
self.server.logger.info(self.name + ' backend initialized.')
|
||||
|
||||
async def emit(self, event, data, namespace=None, room=None, skip_sid=None,
|
||||
callback=None, **kwargs):
|
||||
"""Emit a message to a single client, a room, or all the clients
|
||||
connected to the namespace.
|
||||
|
||||
This method takes care or propagating the message to all the servers
|
||||
that are connected through the message queue.
|
||||
|
||||
The parameters are the same as in :meth:`.Server.emit`.
|
||||
|
||||
Note: this method is a coroutine.
|
||||
"""
|
||||
if kwargs.get('ignore_queue'):
|
||||
return await super().emit(
|
||||
event, data, namespace=namespace, room=room, skip_sid=skip_sid,
|
||||
callback=callback)
|
||||
namespace = namespace or '/'
|
||||
if callback is not None:
|
||||
if self.server is None:
|
||||
raise RuntimeError('Callbacks can only be issued from the '
|
||||
'context of a server.')
|
||||
if room is None:
|
||||
raise ValueError('Cannot use callback without a room set.')
|
||||
id = self._generate_ack_id(room, namespace, callback)
|
||||
callback = (room, namespace, id)
|
||||
else:
|
||||
callback = None
|
||||
await self._publish({'method': 'emit', 'event': event, 'data': data,
|
||||
'namespace': namespace, 'room': room,
|
||||
'skip_sid': skip_sid, 'callback': callback})
|
||||
|
||||
async def close_room(self, room, namespace=None):
|
||||
await self._publish({'method': 'close_room', 'room': room,
|
||||
'namespace': namespace or '/'})
|
||||
|
||||
async def _publish(self, data):
|
||||
"""Publish a message on the Socket.IO channel.
|
||||
|
||||
This method needs to be implemented by the different subclasses that
|
||||
support pub/sub backends.
|
||||
"""
|
||||
raise NotImplementedError('This method must be implemented in a '
|
||||
'subclass.') # pragma: no cover
|
||||
|
||||
async def _listen(self):
|
||||
"""Return the next message published on the Socket.IO channel,
|
||||
blocking until a message is available.
|
||||
|
||||
This method needs to be implemented by the different subclasses that
|
||||
support pub/sub backends.
|
||||
"""
|
||||
raise NotImplementedError('This method must be implemented in a '
|
||||
'subclass.') # pragma: no cover
|
||||
|
||||
async def _handle_emit(self, message):
|
||||
# Events with callbacks are very tricky to handle across hosts
|
||||
# Here in the receiving end we set up a local callback that preserves
|
||||
# the callback host and id from the sender
|
||||
remote_callback = message.get('callback')
|
||||
if remote_callback is not None and len(remote_callback) == 3:
|
||||
callback = partial(self._return_callback, self.host_id,
|
||||
*remote_callback)
|
||||
else:
|
||||
callback = None
|
||||
await super().emit(message['event'], message['data'],
|
||||
namespace=message.get('namespace'),
|
||||
room=message.get('room'),
|
||||
skip_sid=message.get('skip_sid'),
|
||||
callback=callback)
|
||||
|
||||
async def _handle_callback(self, message):
|
||||
if self.host_id == message.get('host_id'):
|
||||
try:
|
||||
sid = message['sid']
|
||||
namespace = message['namespace']
|
||||
id = message['id']
|
||||
args = message['args']
|
||||
except KeyError:
|
||||
return
|
||||
await self.trigger_callback(sid, namespace, id, args)
|
||||
|
||||
async def _return_callback(self, host_id, sid, namespace, callback_id,
|
||||
*args):
|
||||
# When an event callback is received, the callback is returned back
|
||||
# the sender, which is identified by the host_id
|
||||
await self._publish({'method': 'callback', 'host_id': host_id,
|
||||
'sid': sid, 'namespace': namespace,
|
||||
'id': callback_id, 'args': args})
|
||||
|
||||
async def _handle_close_room(self, message):
|
||||
await super().close_room(
|
||||
room=message.get('room'), namespace=message.get('namespace'))
|
||||
|
||||
async def _thread(self):
|
||||
while True:
|
||||
try:
|
||||
message = await self._listen()
|
||||
except:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
break
|
||||
data = None
|
||||
if isinstance(message, dict):
|
||||
data = message
|
||||
else:
|
||||
if isinstance(message, six.binary_type): # pragma: no cover
|
||||
try:
|
||||
data = pickle.loads(message)
|
||||
except:
|
||||
pass
|
||||
if data is None:
|
||||
try:
|
||||
data = json.loads(message)
|
||||
except:
|
||||
pass
|
||||
if data and 'method' in data:
|
||||
if data['method'] == 'emit':
|
||||
await self._handle_emit(data)
|
||||
elif data['method'] == 'callback':
|
||||
await self._handle_callback(data)
|
||||
elif data['method'] == 'close_room':
|
||||
await self._handle_close_room(data)
|
||||
@@ -0,0 +1,78 @@
|
||||
import pickle
|
||||
from urllib.parse import urlparse
|
||||
|
||||
try:
|
||||
import aioredis
|
||||
except ImportError:
|
||||
aioredis = None
|
||||
|
||||
from .asyncio_pubsub_manager import AsyncPubSubManager
|
||||
|
||||
|
||||
def _parse_redis_url(url):
|
||||
p = urlparse(url)
|
||||
if p.scheme != 'redis':
|
||||
raise ValueError('Invalid redis url')
|
||||
if ':' in p.netloc:
|
||||
host, port = p.netloc.split(':')
|
||||
port = int(port)
|
||||
else:
|
||||
host = p.netloc or 'localhost'
|
||||
port = 6379
|
||||
if p.path:
|
||||
db = int(p.path[1:])
|
||||
else:
|
||||
db = 0
|
||||
if not host:
|
||||
raise ValueError('Invalid redis hostname')
|
||||
return host, port, db
|
||||
|
||||
|
||||
class AsyncRedisManager(AsyncPubSubManager): # pragma: no cover
|
||||
"""Redis based client manager for asyncio servers.
|
||||
|
||||
This class implements a Redis backend for event sharing across multiple
|
||||
processes. Only kept here as one more example of how to build a custom
|
||||
backend, since the kombu backend is perfectly adequate to support a Redis
|
||||
message queue.
|
||||
|
||||
To use a Redis backend, initialize the :class:`Server` instance as
|
||||
follows::
|
||||
|
||||
server = socketio.Server(client_manager=socketio.AsyncRedisManager(
|
||||
'redis://hostname:port/0'))
|
||||
|
||||
:param url: The connection URL for the Redis server. For a default Redis
|
||||
store running on the same host, use ``redis://``.
|
||||
:param channel: The channel name on which the server sends and receives
|
||||
notifications. Must be the same in all the servers.
|
||||
:param write_only: If set ot ``True``, only initialize to emit events. The
|
||||
default of ``False`` initializes the class for emitting
|
||||
and receiving.
|
||||
"""
|
||||
name = 'aioredis'
|
||||
|
||||
def __init__(self, url='redis://localhost:6379/0', channel='socketio',
|
||||
write_only=False):
|
||||
if aioredis is None:
|
||||
raise RuntimeError('Redis package is not installed '
|
||||
'(Run "pip install aioredis" in your '
|
||||
'virtualenv).')
|
||||
self.host, self.port, self.db = _parse_redis_url(url)
|
||||
self.pub = None
|
||||
self.sub = None
|
||||
super().__init__(channel=channel, write_only=write_only)
|
||||
|
||||
async def _publish(self, data):
|
||||
if self.pub is None:
|
||||
self.pub = await aioredis.create_redis((self.host, self.port),
|
||||
db=self.db)
|
||||
return await self.pub.publish(self.channel, pickle.dumps(data))
|
||||
|
||||
async def _listen(self):
|
||||
if self.sub is None:
|
||||
self.sub = await aioredis.create_redis((self.host, self.port),
|
||||
db=self.db)
|
||||
self.ch = (await self.sub.subscribe(self.channel))[0]
|
||||
while True:
|
||||
return await self.ch.get()
|
||||
378
venv/lib/python2.7/site-packages/socketio/asyncio_server.py
Normal file
378
venv/lib/python2.7/site-packages/socketio/asyncio_server.py
Normal file
@@ -0,0 +1,378 @@
|
||||
import asyncio
|
||||
|
||||
import engineio
|
||||
|
||||
from . import asyncio_manager
|
||||
from . import packet
|
||||
from . import server
|
||||
|
||||
|
||||
class AsyncServer(server.Server):
|
||||
"""A Socket.IO server for asyncio.
|
||||
|
||||
This class implements a fully compliant Socket.IO web server with support
|
||||
for websocket and long-polling transports, compatible with the asyncio
|
||||
framework on Python 3.5 or newer.
|
||||
|
||||
:param client_manager: The client manager instance that will manage the
|
||||
client list. When this is omitted, the client list
|
||||
is stored in an in-memory structure, so the use of
|
||||
multiple connected servers is not possible.
|
||||
:param logger: To enable logging set to ``True`` or pass a logger object to
|
||||
use. To disable logging set to ``False``.
|
||||
:param json: An alternative json module to use for encoding and decoding
|
||||
packets. Custom json modules must have ``dumps`` and ``loads``
|
||||
functions that are compatible with the standard library
|
||||
versions.
|
||||
:param async_handlers: If set to ``True``, event handlers are executed in
|
||||
separate threads. To run handlers synchronously,
|
||||
set to ``False``. The default is ``False``.
|
||||
:param kwargs: Connection parameters for the underlying Engine.IO server.
|
||||
|
||||
The Engine.IO configuration supports the following settings:
|
||||
|
||||
:param async_mode: The asynchronous model to use. See the Deployment
|
||||
section in the documentation for a description of the
|
||||
available options. Valid async modes are "aiohttp". If
|
||||
this argument is not given, an async mode is chosen
|
||||
based on the installed packages.
|
||||
:param ping_timeout: The time in seconds that the client waits for the
|
||||
server to respond before disconnecting.
|
||||
:param ping_interval: The interval in seconds at which the client pings
|
||||
the server.
|
||||
:param max_http_buffer_size: The maximum size of a message when using the
|
||||
polling transport.
|
||||
:param allow_upgrades: Whether to allow transport upgrades or not.
|
||||
:param http_compression: Whether to compress packages when using the
|
||||
polling transport.
|
||||
:param compression_threshold: Only compress messages when their byte size
|
||||
is greater than this value.
|
||||
:param cookie: Name of the HTTP cookie that contains the client session
|
||||
id. If set to ``None``, a cookie is not sent to the client.
|
||||
:param cors_allowed_origins: List of origins that are allowed to connect
|
||||
to this server. All origins are allowed by
|
||||
default.
|
||||
:param cors_credentials: Whether credentials (cookies, authentication) are
|
||||
allowed in requests to this server.
|
||||
:param engineio_logger: To enable Engine.IO logging set to ``True`` or pass
|
||||
a logger object to use. To disable logging set to
|
||||
``False``.
|
||||
"""
|
||||
def __init__(self, client_manager=None, logger=False, json=None,
|
||||
async_handlers=False, **kwargs):
|
||||
if client_manager is None:
|
||||
client_manager = asyncio_manager.AsyncManager()
|
||||
super().__init__(client_manager=client_manager, logger=logger,
|
||||
binary=False, json=json,
|
||||
async_handlers=async_handlers, **kwargs)
|
||||
|
||||
def is_asyncio_based(self):
|
||||
return True
|
||||
|
||||
def attach(self, app, socketio_path='socket.io'):
|
||||
"""Attach the Socket.IO server to an application."""
|
||||
self.eio.attach(app, socketio_path)
|
||||
|
||||
async def emit(self, event, data=None, room=None, skip_sid=None,
|
||||
namespace=None, callback=None, **kwargs):
|
||||
"""Emit a custom event to one or more connected clients.
|
||||
|
||||
:param event: The event name. It can be any string. The event names
|
||||
``'connect'``, ``'message'`` and ``'disconnect'`` are
|
||||
reserved and should not be used.
|
||||
:param data: The data to send to the client or clients. Data can be of
|
||||
type ``str``, ``bytes``, ``list`` or ``dict``. If a
|
||||
``list`` or ``dict``, the data will be serialized as JSON.
|
||||
:param room: The recipient of the message. This can be set to the
|
||||
session ID of a client to address that client's room, or
|
||||
to any custom room created by the application, If this
|
||||
argument is omitted the event is broadcasted to all
|
||||
connected clients.
|
||||
:param skip_sid: The session ID of a client to skip when broadcasting
|
||||
to a room or to all clients. This can be used to
|
||||
prevent a message from being sent to the sender.
|
||||
:param namespace: The Socket.IO namespace for the event. If this
|
||||
argument is omitted the event is emitted to the
|
||||
default namespace.
|
||||
:param callback: If given, this function will be called to acknowledge
|
||||
the the client has received the message. The arguments
|
||||
that will be passed to the function are those provided
|
||||
by the client. Callback functions can only be used
|
||||
when addressing an individual client.
|
||||
:param ignore_queue: Only used when a message queue is configured. If
|
||||
set to ``True``, the event is emitted to the
|
||||
clients directly, without going through the queue.
|
||||
This is more efficient, but only works when a
|
||||
single server process is used. It is recommended
|
||||
to always leave this parameter with its default
|
||||
value of ``False``.
|
||||
|
||||
Note: this method is a coroutine.
|
||||
"""
|
||||
namespace = namespace or '/'
|
||||
self.logger.info('emitting event "%s" to %s [%s]', event,
|
||||
room or 'all', namespace)
|
||||
await self.manager.emit(event, data, namespace, room, skip_sid,
|
||||
callback, **kwargs)
|
||||
|
||||
async def send(self, data, room=None, skip_sid=None, namespace=None,
|
||||
callback=None, **kwargs):
|
||||
"""Send a message to one or more connected clients.
|
||||
|
||||
This function emits an event with the name ``'message'``. Use
|
||||
:func:`emit` to issue custom event names.
|
||||
|
||||
:param data: The data to send to the client or clients. Data can be of
|
||||
type ``str``, ``bytes``, ``list`` or ``dict``. If a
|
||||
``list`` or ``dict``, the data will be serialized as JSON.
|
||||
:param room: The recipient of the message. This can be set to the
|
||||
session ID of a client to address that client's room, or
|
||||
to any custom room created by the application, If this
|
||||
argument is omitted the event is broadcasted to all
|
||||
connected clients.
|
||||
:param skip_sid: The session ID of a client to skip when broadcasting
|
||||
to a room or to all clients. This can be used to
|
||||
prevent a message from being sent to the sender.
|
||||
:param namespace: The Socket.IO namespace for the event. If this
|
||||
argument is omitted the event is emitted to the
|
||||
default namespace.
|
||||
:param callback: If given, this function will be called to acknowledge
|
||||
the the client has received the message. The arguments
|
||||
that will be passed to the function are those provided
|
||||
by the client. Callback functions can only be used
|
||||
when addressing an individual client.
|
||||
:param ignore_queue: Only used when a message queue is configured. If
|
||||
set to ``True``, the event is emitted to the
|
||||
clients directly, without going through the queue.
|
||||
This is more efficient, but only works when a
|
||||
single server process is used. It is recommended
|
||||
to always leave this parameter with its default
|
||||
value of ``False``.
|
||||
|
||||
Note: this method is a coroutine.
|
||||
"""
|
||||
await self.emit('message', data, room, skip_sid, namespace, callback,
|
||||
**kwargs)
|
||||
|
||||
async def close_room(self, room, namespace=None):
|
||||
"""Close a room.
|
||||
|
||||
This function removes all the clients from the given room.
|
||||
|
||||
:param room: Room name.
|
||||
:param namespace: The Socket.IO namespace for the event. If this
|
||||
argument is omitted the default namespace is used.
|
||||
|
||||
Note: this method is a coroutine.
|
||||
"""
|
||||
namespace = namespace or '/'
|
||||
self.logger.info('room %s is closing [%s]', room, namespace)
|
||||
await self.manager.close_room(room, namespace)
|
||||
|
||||
async def disconnect(self, sid, namespace=None):
|
||||
"""Disconnect a client.
|
||||
|
||||
:param sid: Session ID of the client.
|
||||
:param namespace: The Socket.IO namespace to disconnect. If this
|
||||
argument is omitted the default namespace is used.
|
||||
|
||||
Note: this method is a coroutine.
|
||||
"""
|
||||
namespace = namespace or '/'
|
||||
if self.manager.is_connected(sid, namespace=namespace):
|
||||
self.logger.info('Disconnecting %s [%s]', sid, namespace)
|
||||
self.manager.pre_disconnect(sid, namespace=namespace)
|
||||
await self._send_packet(sid, packet.Packet(packet.DISCONNECT,
|
||||
namespace=namespace))
|
||||
await self._trigger_event('disconnect', namespace, sid)
|
||||
self.manager.disconnect(sid, namespace=namespace)
|
||||
|
||||
async def handle_request(self, *args, **kwargs):
|
||||
"""Handle an HTTP request from the client.
|
||||
|
||||
This is the entry point of the Socket.IO application. This function
|
||||
returns the HTTP response body to deliver to the client.
|
||||
|
||||
Note: this method is a coroutine.
|
||||
"""
|
||||
return await self.eio.handle_request(*args, **kwargs)
|
||||
|
||||
def start_background_task(self, target, *args, **kwargs):
|
||||
"""Start a background task using the appropriate async model.
|
||||
|
||||
This is a utility function that applications can use to start a
|
||||
background task using the method that is compatible with the
|
||||
selected async mode.
|
||||
|
||||
:param target: the target function to execute. Must be a coroutine.
|
||||
:param args: arguments to pass to the function.
|
||||
:param kwargs: keyword arguments to pass to the function.
|
||||
|
||||
The return value is a ``asyncio.Task`` object.
|
||||
|
||||
Note: this method is a coroutine.
|
||||
"""
|
||||
return self.eio.start_background_task(target, *args, **kwargs)
|
||||
|
||||
async def sleep(self, seconds=0):
|
||||
"""Sleep for the requested amount of time using the appropriate async
|
||||
model.
|
||||
|
||||
This is a utility function that applications can use to put a task to
|
||||
sleep without having to worry about using the correct call for the
|
||||
selected async mode.
|
||||
|
||||
Note: this method is a coroutine.
|
||||
"""
|
||||
return await self.eio.sleep(seconds)
|
||||
|
||||
async def _emit_internal(self, sid, event, data, namespace=None, id=None):
|
||||
"""Send a message to a client."""
|
||||
# tuples are expanded to multiple arguments, everything else is sent
|
||||
# as a single argument
|
||||
if isinstance(data, tuple):
|
||||
data = list(data)
|
||||
else:
|
||||
data = [data]
|
||||
await self._send_packet(sid, packet.Packet(
|
||||
packet.EVENT, namespace=namespace, data=[event] + data, id=id,
|
||||
binary=None))
|
||||
|
||||
async def _send_packet(self, sid, pkt):
|
||||
"""Send a Socket.IO packet to a client."""
|
||||
encoded_packet = pkt.encode()
|
||||
if isinstance(encoded_packet, list):
|
||||
binary = False
|
||||
for ep in encoded_packet:
|
||||
await self.eio.send(sid, ep, binary=binary)
|
||||
binary = True
|
||||
else:
|
||||
await self.eio.send(sid, encoded_packet, binary=False)
|
||||
|
||||
async def _handle_connect(self, sid, namespace):
|
||||
"""Handle a client connection request."""
|
||||
namespace = namespace or '/'
|
||||
self.manager.connect(sid, namespace)
|
||||
if await self._trigger_event('connect', namespace, sid,
|
||||
self.environ[sid]) is False:
|
||||
self.manager.disconnect(sid, namespace)
|
||||
await self._send_packet(sid, packet.Packet(packet.ERROR,
|
||||
namespace=namespace))
|
||||
return False
|
||||
else:
|
||||
await self._send_packet(sid, packet.Packet(packet.CONNECT,
|
||||
namespace=namespace))
|
||||
|
||||
async def _handle_disconnect(self, sid, namespace):
|
||||
"""Handle a client disconnect."""
|
||||
namespace = namespace or '/'
|
||||
if namespace == '/':
|
||||
namespace_list = list(self.manager.get_namespaces())
|
||||
else:
|
||||
namespace_list = [namespace]
|
||||
for n in namespace_list:
|
||||
if n != '/' and self.manager.is_connected(sid, n):
|
||||
await self._trigger_event('disconnect', n, sid)
|
||||
self.manager.disconnect(sid, n)
|
||||
if namespace == '/' and self.manager.is_connected(sid, namespace):
|
||||
await self._trigger_event('disconnect', '/', sid)
|
||||
self.manager.disconnect(sid, '/')
|
||||
if sid in self.environ:
|
||||
del self.environ[sid]
|
||||
|
||||
async def _handle_event(self, sid, namespace, id, data):
|
||||
"""Handle an incoming client event."""
|
||||
namespace = namespace or '/'
|
||||
self.logger.info('received event "%s" from %s [%s]', data[0], sid,
|
||||
namespace)
|
||||
if self.async_handlers:
|
||||
self.start_background_task(self._handle_event_internal, self, sid,
|
||||
data, namespace, id)
|
||||
else:
|
||||
await self._handle_event_internal(self, sid, data, namespace, id)
|
||||
|
||||
async def _handle_event_internal(self, server, sid, data, namespace, id):
|
||||
r = await server._trigger_event(data[0], namespace, sid, *data[1:])
|
||||
if id is not None:
|
||||
# send ACK packet with the response returned by the handler
|
||||
# tuples are expanded as multiple arguments
|
||||
if r is None:
|
||||
data = []
|
||||
elif isinstance(r, tuple):
|
||||
data = list(r)
|
||||
else:
|
||||
data = [r]
|
||||
await server._send_packet(sid, packet.Packet(packet.ACK,
|
||||
namespace=namespace,
|
||||
id=id, data=data,
|
||||
binary=None))
|
||||
|
||||
async def _handle_ack(self, sid, namespace, id, data):
|
||||
"""Handle ACK packets from the client."""
|
||||
namespace = namespace or '/'
|
||||
self.logger.info('received ack from %s [%s]', sid, namespace)
|
||||
await self.manager.trigger_callback(sid, namespace, id, data)
|
||||
|
||||
async def _trigger_event(self, event, namespace, *args):
|
||||
"""Invoke an application event handler."""
|
||||
# first see if we have an explicit handler for the event
|
||||
if namespace in self.handlers and event in self.handlers[namespace]:
|
||||
if asyncio.iscoroutinefunction(self.handlers[namespace][event]) \
|
||||
is True:
|
||||
try:
|
||||
ret = await self.handlers[namespace][event](*args)
|
||||
except asyncio.CancelledError: # pragma: no cover
|
||||
ret = None
|
||||
else:
|
||||
ret = self.handlers[namespace][event](*args)
|
||||
return ret
|
||||
|
||||
# or else, forward the event to a namepsace handler if one exists
|
||||
elif namespace in self.namespace_handlers:
|
||||
return await self.namespace_handlers[namespace].trigger_event(
|
||||
event, *args)
|
||||
|
||||
async def _handle_eio_connect(self, sid, environ):
|
||||
"""Handle the Engine.IO connection event."""
|
||||
if not self.manager_initialized:
|
||||
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."""
|
||||
if sid in self._binary_packet:
|
||||
pkt = self._binary_packet[sid]
|
||||
if pkt.add_attachment(data):
|
||||
del self._binary_packet[sid]
|
||||
if pkt.packet_type == packet.BINARY_EVENT:
|
||||
await self._handle_event(sid, pkt.namespace, pkt.id,
|
||||
pkt.data)
|
||||
else:
|
||||
await self._handle_ack(sid, pkt.namespace, pkt.id,
|
||||
pkt.data)
|
||||
else:
|
||||
pkt = packet.Packet(encoded_packet=data)
|
||||
if pkt.packet_type == packet.CONNECT:
|
||||
await self._handle_connect(sid, pkt.namespace)
|
||||
elif pkt.packet_type == packet.DISCONNECT:
|
||||
await self._handle_disconnect(sid, pkt.namespace)
|
||||
elif pkt.packet_type == packet.EVENT:
|
||||
await self._handle_event(sid, pkt.namespace, pkt.id, pkt.data)
|
||||
elif pkt.packet_type == packet.ACK:
|
||||
await self._handle_ack(sid, pkt.namespace, pkt.id, pkt.data)
|
||||
elif pkt.packet_type == packet.BINARY_EVENT or \
|
||||
pkt.packet_type == packet.BINARY_ACK:
|
||||
self._binary_packet[sid] = pkt
|
||||
elif pkt.packet_type == packet.ERROR:
|
||||
raise ValueError('Unexpected ERROR packet.')
|
||||
else:
|
||||
raise ValueError('Unknown packet type.')
|
||||
|
||||
async def _handle_eio_disconnect(self, sid):
|
||||
"""Handle Engine.IO disconnect event."""
|
||||
await self._handle_disconnect(sid, '/')
|
||||
|
||||
def _engineio_server_class(self):
|
||||
return engineio.AsyncServer
|
||||
159
venv/lib/python2.7/site-packages/socketio/base_manager.py
Normal file
159
venv/lib/python2.7/site-packages/socketio/base_manager.py
Normal file
@@ -0,0 +1,159 @@
|
||||
import itertools
|
||||
|
||||
import six
|
||||
|
||||
|
||||
class BaseManager(object):
|
||||
"""Manage client connections.
|
||||
|
||||
This class keeps track of all the clients and the rooms they are in, to
|
||||
support the broadcasting of messages. The data used by this class is
|
||||
stored in a memory structure, making it appropriate only for single process
|
||||
services. More sophisticated storage backends can be implemented by
|
||||
subclasses.
|
||||
"""
|
||||
def __init__(self):
|
||||
self.server = None
|
||||
self.rooms = {}
|
||||
self.callbacks = {}
|
||||
self.pending_disconnect = {}
|
||||
|
||||
def set_server(self, server):
|
||||
self.server = server
|
||||
|
||||
def initialize(self):
|
||||
"""Invoked before the first request is received. Subclasses can add
|
||||
their initialization code here.
|
||||
"""
|
||||
pass
|
||||
|
||||
def get_namespaces(self):
|
||||
"""Return an iterable with the active namespace names."""
|
||||
return six.iterkeys(self.rooms)
|
||||
|
||||
def get_participants(self, namespace, room):
|
||||
"""Return an iterable with the active participants in a room."""
|
||||
for sid, active in six.iteritems(self.rooms[namespace][room].copy()):
|
||||
yield sid
|
||||
|
||||
def connect(self, sid, namespace):
|
||||
"""Register a client connection to a namespace."""
|
||||
self.enter_room(sid, namespace, None)
|
||||
self.enter_room(sid, namespace, sid)
|
||||
|
||||
def is_connected(self, sid, namespace):
|
||||
if namespace in self.pending_disconnect and \
|
||||
sid in self.pending_disconnect[namespace]:
|
||||
# the client is in the process of being disconnected
|
||||
return False
|
||||
try:
|
||||
return self.rooms[namespace][None][sid]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
def pre_disconnect(self, sid, namespace):
|
||||
"""Put the client in the to-be-disconnected list.
|
||||
|
||||
This allows the client data structures to be present while the
|
||||
disconnect handler is invoked, but still recognize the fact that the
|
||||
client is soon going away.
|
||||
"""
|
||||
if namespace not in self.pending_disconnect:
|
||||
self.pending_disconnect[namespace] = []
|
||||
self.pending_disconnect[namespace].append(sid)
|
||||
|
||||
def disconnect(self, sid, namespace):
|
||||
"""Register a client disconnect from a namespace."""
|
||||
if namespace not in self.rooms:
|
||||
return
|
||||
rooms = []
|
||||
for room_name, room in six.iteritems(self.rooms[namespace]):
|
||||
if sid in room:
|
||||
rooms.append(room_name)
|
||||
for room in rooms:
|
||||
self.leave_room(sid, namespace, room)
|
||||
if sid in self.callbacks and namespace in self.callbacks[sid]:
|
||||
del self.callbacks[sid][namespace]
|
||||
if len(self.callbacks[sid]) == 0:
|
||||
del self.callbacks[sid]
|
||||
if namespace in self.pending_disconnect and \
|
||||
sid in self.pending_disconnect[namespace]:
|
||||
self.pending_disconnect[namespace].remove(sid)
|
||||
if len(self.pending_disconnect[namespace]) == 0:
|
||||
del self.pending_disconnect[namespace]
|
||||
|
||||
def enter_room(self, sid, namespace, room):
|
||||
"""Add a client to a room."""
|
||||
if namespace not in self.rooms:
|
||||
self.rooms[namespace] = {}
|
||||
if room not in self.rooms[namespace]:
|
||||
self.rooms[namespace][room] = {}
|
||||
self.rooms[namespace][room][sid] = True
|
||||
|
||||
def leave_room(self, sid, namespace, room):
|
||||
"""Remove a client from a room."""
|
||||
try:
|
||||
del self.rooms[namespace][room][sid]
|
||||
if len(self.rooms[namespace][room]) == 0:
|
||||
del self.rooms[namespace][room]
|
||||
if len(self.rooms[namespace]) == 0:
|
||||
del self.rooms[namespace]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
def close_room(self, room, namespace):
|
||||
"""Remove all participants from a room."""
|
||||
try:
|
||||
for sid in self.get_participants(namespace, room):
|
||||
self.leave_room(sid, namespace, room)
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
def get_rooms(self, sid, namespace):
|
||||
"""Return the rooms a client is in."""
|
||||
r = []
|
||||
try:
|
||||
for room_name, room in six.iteritems(self.rooms[namespace]):
|
||||
if room_name is not None and sid in room and room[sid]:
|
||||
r.append(room_name)
|
||||
except KeyError:
|
||||
pass
|
||||
return r
|
||||
|
||||
def emit(self, event, data, namespace, room=None, skip_sid=None,
|
||||
callback=None, **kwargs):
|
||||
"""Emit a message to a single client, a room, or all the clients
|
||||
connected to the namespace."""
|
||||
if namespace not in self.rooms or room not in self.rooms[namespace]:
|
||||
return
|
||||
for sid in self.get_participants(namespace, room):
|
||||
if sid != skip_sid:
|
||||
if callback is not None:
|
||||
id = self._generate_ack_id(sid, namespace, callback)
|
||||
else:
|
||||
id = None
|
||||
self.server._emit_internal(sid, event, data, namespace, id)
|
||||
|
||||
def trigger_callback(self, sid, namespace, id, data):
|
||||
"""Invoke an application callback."""
|
||||
callback = None
|
||||
try:
|
||||
callback = self.callbacks[sid][namespace][id]
|
||||
except KeyError:
|
||||
# if we get an unknown callback we just ignore it
|
||||
self.server.logger.warning('Unknown callback received, ignoring.')
|
||||
else:
|
||||
del self.callbacks[sid][namespace][id]
|
||||
if callback is not None:
|
||||
callback(*data)
|
||||
|
||||
def _generate_ack_id(self, sid, namespace, callback):
|
||||
"""Generate a unique identifier for an ACK packet."""
|
||||
namespace = namespace or '/'
|
||||
if sid not in self.callbacks:
|
||||
self.callbacks[sid] = {}
|
||||
if namespace not in self.callbacks[sid]:
|
||||
self.callbacks[sid][namespace] = {0: itertools.count(1)}
|
||||
id = six.next(self.callbacks[sid][namespace][0])
|
||||
self.callbacks[sid][namespace][id] = callback
|
||||
return id
|
||||
BIN
venv/lib/python2.7/site-packages/socketio/base_manager.pyc
Normal file
BIN
venv/lib/python2.7/site-packages/socketio/base_manager.pyc
Normal file
Binary file not shown.
88
venv/lib/python2.7/site-packages/socketio/kombu_manager.py
Normal file
88
venv/lib/python2.7/site-packages/socketio/kombu_manager.py
Normal file
@@ -0,0 +1,88 @@
|
||||
import pickle
|
||||
import uuid
|
||||
|
||||
try:
|
||||
import kombu
|
||||
except ImportError:
|
||||
kombu = None
|
||||
|
||||
from .pubsub_manager import PubSubManager
|
||||
|
||||
|
||||
class KombuManager(PubSubManager): # pragma: no cover
|
||||
"""Client manager that uses kombu for inter-process messaging.
|
||||
|
||||
This class implements a client manager backend for event sharing across
|
||||
multiple processes, using RabbitMQ, Redis or any other messaging mechanism
|
||||
supported by `kombu <http://kombu.readthedocs.org/en/latest/>`_.
|
||||
|
||||
To use a kombu backend, initialize the :class:`Server` instance as
|
||||
follows::
|
||||
|
||||
url = 'amqp://user:password@hostname:port//'
|
||||
server = socketio.Server(client_manager=socketio.KombuManager(url))
|
||||
|
||||
:param url: The connection URL for the backend messaging queue. Example
|
||||
connection URLs are ``'amqp://guest:guest@localhost:5672//'``
|
||||
and ``'redis://localhost:6379/'`` for RabbitMQ and Redis
|
||||
respectively. Consult the `kombu documentation
|
||||
<http://kombu.readthedocs.org/en/latest/userguide\
|
||||
/connections.html#urls>`_ for more on how to construct
|
||||
connection URLs.
|
||||
:param channel: The channel name on which the server sends and receives
|
||||
notifications. Must be the same in all the servers.
|
||||
:param write_only: If set ot ``True``, only initialize to emit events. The
|
||||
default of ``False`` initializes the class for emitting
|
||||
and receiving.
|
||||
"""
|
||||
name = 'kombu'
|
||||
|
||||
def __init__(self, url='amqp://guest:guest@localhost:5672//',
|
||||
channel='socketio', write_only=False):
|
||||
if kombu is None:
|
||||
raise RuntimeError('Kombu package is not installed '
|
||||
'(Run "pip install kombu" in your '
|
||||
'virtualenv).')
|
||||
super(KombuManager, self).__init__(channel=channel)
|
||||
self.url = url
|
||||
self.producer = self._producer()
|
||||
|
||||
def initialize(self):
|
||||
super(KombuManager, self).initialize()
|
||||
|
||||
monkey_patched = True
|
||||
if self.server.async_mode == 'eventlet':
|
||||
from eventlet.patcher import is_monkey_patched
|
||||
monkey_patched = is_monkey_patched('socket')
|
||||
elif 'gevent' in self.server.async_mode:
|
||||
from gevent.monkey import is_module_patched
|
||||
monkey_patched = is_module_patched('socket')
|
||||
if not monkey_patched:
|
||||
raise RuntimeError(
|
||||
'Kombu requires a monkey patched socket library to work '
|
||||
'with ' + self.server.async_mode)
|
||||
|
||||
def _connection(self):
|
||||
return kombu.Connection(self.url)
|
||||
|
||||
def _exchange(self):
|
||||
return kombu.Exchange(self.channel, type='fanout', durable=False)
|
||||
|
||||
def _queue(self):
|
||||
queue_name = 'flask-socketio.' + str(uuid.uuid4())
|
||||
return kombu.Queue(queue_name, self._exchange(),
|
||||
queue_arguments={'x-expires': 300000})
|
||||
|
||||
def _producer(self):
|
||||
return self._connection().Producer(exchange=self._exchange())
|
||||
|
||||
def _publish(self, data):
|
||||
self.producer.publish(pickle.dumps(data))
|
||||
|
||||
def _listen(self):
|
||||
reader_queue = self._queue()
|
||||
with self._connection().SimpleQueue(reader_queue) as queue:
|
||||
while True:
|
||||
message = queue.get(block=True)
|
||||
message.ack()
|
||||
yield message.payload
|
||||
BIN
venv/lib/python2.7/site-packages/socketio/kombu_manager.pyc
Normal file
BIN
venv/lib/python2.7/site-packages/socketio/kombu_manager.pyc
Normal file
Binary file not shown.
27
venv/lib/python2.7/site-packages/socketio/middleware.py
Normal file
27
venv/lib/python2.7/site-packages/socketio/middleware.py
Normal file
@@ -0,0 +1,27 @@
|
||||
import engineio
|
||||
|
||||
|
||||
class Middleware(engineio.Middleware):
|
||||
"""WSGI middleware for Socket.IO.
|
||||
|
||||
This middleware dispatches traffic to a Socket.IO application, and
|
||||
optionally forwards regular HTTP traffic to a WSGI application.
|
||||
|
||||
:param socketio_app: The Socket.IO server.
|
||||
:param wsgi_app: The WSGI app that receives all other traffic.
|
||||
:param socketio_path: The endpoint where the Socket.IO application should
|
||||
be installed. The default value is appropriate for
|
||||
most cases.
|
||||
|
||||
Example usage::
|
||||
|
||||
import socketio
|
||||
import eventlet
|
||||
from . import wsgi_app
|
||||
|
||||
sio = socketio.Server()
|
||||
app = socketio.Middleware(sio, wsgi_app)
|
||||
eventlet.wsgi.server(eventlet.listen(('', 8000)), app)
|
||||
"""
|
||||
def __init__(self, socketio_app, wsgi_app=None, socketio_path='socket.io'):
|
||||
super(Middleware, self).__init__(socketio_app, wsgi_app, socketio_path)
|
||||
BIN
venv/lib/python2.7/site-packages/socketio/middleware.pyc
Normal file
BIN
venv/lib/python2.7/site-packages/socketio/middleware.pyc
Normal file
Binary file not shown.
106
venv/lib/python2.7/site-packages/socketio/namespace.py
Normal file
106
venv/lib/python2.7/site-packages/socketio/namespace.py
Normal file
@@ -0,0 +1,106 @@
|
||||
class Namespace(object):
|
||||
"""Base class for 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
|
||||
with the prefix ``on_``, such as ``on_connect``, ``on_disconnect``,
|
||||
``on_message``, ``on_json``, and so on.
|
||||
|
||||
:param namespace: The Socket.IO namespace to be used with all the event
|
||||
handlers defined in this class. If this argument is
|
||||
omitted, the default namespace is used.
|
||||
"""
|
||||
def __init__(self, namespace=None):
|
||||
self.namespace = namespace or '/'
|
||||
self.server = None
|
||||
|
||||
def _set_server(self, server):
|
||||
self.server = server
|
||||
|
||||
def is_asyncio_based(self):
|
||||
return False
|
||||
|
||||
def trigger_event(self, event, *args):
|
||||
"""Dispatch an event to the proper handler method.
|
||||
|
||||
In the most common usage, this method is not overloaded by subclasses,
|
||||
as it performs the routing of events to methods. However, this
|
||||
method can be overriden if special dispatching rules are needed, or if
|
||||
having a single method that catches all events is desired.
|
||||
"""
|
||||
handler_name = 'on_' + event
|
||||
if hasattr(self, handler_name):
|
||||
return getattr(self, handler_name)(*args)
|
||||
|
||||
def emit(self, event, data=None, room=None, skip_sid=None, namespace=None,
|
||||
callback=None):
|
||||
"""Emit a custom event to one or more connected clients.
|
||||
|
||||
The only difference with the :func:`socketio.Server.emit` method is
|
||||
that when the ``namespace`` argument is not given the namespace
|
||||
associated with the class is used.
|
||||
"""
|
||||
return self.server.emit(event, data=data, room=room, skip_sid=skip_sid,
|
||||
namespace=namespace or self.namespace,
|
||||
callback=callback)
|
||||
|
||||
def send(self, data, room=None, skip_sid=None, namespace=None,
|
||||
callback=None):
|
||||
"""Send a message to one or more connected clients.
|
||||
|
||||
The only difference with the :func:`socketio.Server.send` method is
|
||||
that when the ``namespace`` argument is not given the namespace
|
||||
associated with the class is used.
|
||||
"""
|
||||
return self.server.send(data, room=room, skip_sid=skip_sid,
|
||||
namespace=namespace or self.namespace,
|
||||
callback=callback)
|
||||
|
||||
def enter_room(self, sid, room, namespace=None):
|
||||
"""Enter a room.
|
||||
|
||||
The only difference with the :func:`socketio.Server.enter_room` method
|
||||
is that when the ``namespace`` argument is not given the namespace
|
||||
associated with the class is used.
|
||||
"""
|
||||
return self.server.enter_room(sid, room,
|
||||
namespace=namespace or self.namespace)
|
||||
|
||||
def leave_room(self, sid, room, namespace=None):
|
||||
"""Leave a room.
|
||||
|
||||
The only difference with the :func:`socketio.Server.leave_room` method
|
||||
is that when the ``namespace`` argument is not given the namespace
|
||||
associated with the class is used.
|
||||
"""
|
||||
return self.server.leave_room(sid, room,
|
||||
namespace=namespace or self.namespace)
|
||||
|
||||
def close_room(self, room, namespace=None):
|
||||
"""Close a room.
|
||||
|
||||
The only difference with the :func:`socketio.Server.close_room` method
|
||||
is that when the ``namespace`` argument is not given the namespace
|
||||
associated with the class is used.
|
||||
"""
|
||||
return self.server.close_room(room,
|
||||
namespace=namespace or self.namespace)
|
||||
|
||||
def rooms(self, sid, namespace=None):
|
||||
"""Return the rooms a client is in.
|
||||
|
||||
The only difference with the :func:`socketio.Server.rooms` method is
|
||||
that when the ``namespace`` argument is not given the namespace
|
||||
associated with the class is used.
|
||||
"""
|
||||
return self.server.rooms(sid, namespace=namespace or self.namespace)
|
||||
|
||||
def disconnect(self, sid, namespace=None):
|
||||
"""Disconnect a client.
|
||||
|
||||
The only difference with the :func:`socketio.Server.disconnect` method
|
||||
is that when the ``namespace`` argument is not given the namespace
|
||||
associated with the class is used.
|
||||
"""
|
||||
return self.server.disconnect(sid,
|
||||
namespace=namespace or self.namespace)
|
||||
BIN
venv/lib/python2.7/site-packages/socketio/namespace.pyc
Normal file
BIN
venv/lib/python2.7/site-packages/socketio/namespace.pyc
Normal file
Binary file not shown.
176
venv/lib/python2.7/site-packages/socketio/packet.py
Normal file
176
venv/lib/python2.7/site-packages/socketio/packet.py
Normal file
@@ -0,0 +1,176 @@
|
||||
import functools
|
||||
import json as _json
|
||||
|
||||
import six
|
||||
|
||||
(CONNECT, DISCONNECT, EVENT, ACK, ERROR, BINARY_EVENT, BINARY_ACK) = \
|
||||
(0, 1, 2, 3, 4, 5, 6)
|
||||
packet_names = ['CONNECT', 'DISCONNECT', 'EVENT', 'ACK', 'ERROR',
|
||||
'BINARY_EVENT', 'BINARY_ACK']
|
||||
|
||||
|
||||
class Packet(object):
|
||||
"""Socket.IO packet."""
|
||||
|
||||
# the format of the Socket.IO packet is as follows:
|
||||
#
|
||||
# type: 1 byte, values 0-6
|
||||
# num_attachments: ASCII encoded, only if num_attachments != 0
|
||||
# '-': only if num_attachments != 0
|
||||
# namespace: only if namespace != '/'
|
||||
# ',': only if namespace and one of id and data are defined in this packet
|
||||
# id: ASCII encoded, only if id is not None
|
||||
# data: JSON dump of data payload
|
||||
|
||||
json = _json
|
||||
|
||||
def __init__(self, packet_type=EVENT, data=None, namespace=None, id=None,
|
||||
binary=None, encoded_packet=None):
|
||||
self.packet_type = packet_type
|
||||
self.data = data
|
||||
self.namespace = namespace
|
||||
self.id = id
|
||||
if binary or (binary is None and self._data_is_binary(self.data)):
|
||||
if self.packet_type == EVENT:
|
||||
self.packet_type = BINARY_EVENT
|
||||
elif self.packet_type == ACK:
|
||||
self.packet_type = BINARY_ACK
|
||||
else:
|
||||
raise ValueError('Packet does not support binary payload.')
|
||||
self.attachment_count = 0
|
||||
self.attachments = []
|
||||
if encoded_packet:
|
||||
self.attachment_count = self.decode(encoded_packet)
|
||||
|
||||
def encode(self):
|
||||
"""Encode the packet for transmission.
|
||||
|
||||
If the packet contains binary elements, this function returns a list
|
||||
of packets where the first is the original packet with placeholders for
|
||||
the binary components and the remaining ones the binary attachments.
|
||||
"""
|
||||
encoded_packet = six.text_type(self.packet_type)
|
||||
if self.packet_type == BINARY_EVENT or self.packet_type == BINARY_ACK:
|
||||
data, attachments = self._deconstruct_binary(self.data)
|
||||
encoded_packet += six.text_type(len(attachments)) + '-'
|
||||
else:
|
||||
data = self.data
|
||||
attachments = None
|
||||
needs_comma = False
|
||||
if self.namespace is not None and self.namespace != '/':
|
||||
encoded_packet += self.namespace
|
||||
needs_comma = True
|
||||
if self.id is not None:
|
||||
if needs_comma:
|
||||
encoded_packet += ','
|
||||
needs_comma = False
|
||||
encoded_packet += six.text_type(self.id)
|
||||
if data is not None:
|
||||
if needs_comma:
|
||||
encoded_packet += ','
|
||||
encoded_packet += self.json.dumps(data, separators=(',', ':'))
|
||||
if attachments is not None:
|
||||
encoded_packet = [encoded_packet] + attachments
|
||||
return encoded_packet
|
||||
|
||||
def decode(self, encoded_packet):
|
||||
"""Decode a transmitted package.
|
||||
|
||||
The return value indicates how many binary attachment packets are
|
||||
necessary to fully decode the packet.
|
||||
"""
|
||||
ep = encoded_packet
|
||||
try:
|
||||
self.packet_type = int(ep[0:1])
|
||||
except TypeError:
|
||||
self.packet_type = ep
|
||||
ep = ''
|
||||
self.namespace = None
|
||||
self.data = None
|
||||
ep = ep[1:]
|
||||
dash = (ep + '-').find('-')
|
||||
attachment_count = 0
|
||||
if ep[0:dash].isdigit():
|
||||
attachment_count = int(ep[0:dash])
|
||||
ep = ep[dash + 1:]
|
||||
if ep and ep[0:1] == '/':
|
||||
sep = ep.find(',')
|
||||
if sep == -1:
|
||||
self.namespace = ep
|
||||
ep = ''
|
||||
else:
|
||||
self.namespace = ep[0:sep]
|
||||
ep = ep[sep + 1:]
|
||||
if ep and ep[0].isdigit():
|
||||
self.id = 0
|
||||
while ep[0].isdigit():
|
||||
self.id = self.id * 10 + int(ep[0])
|
||||
ep = ep[1:]
|
||||
if ep:
|
||||
self.data = self.json.loads(ep)
|
||||
return attachment_count
|
||||
|
||||
def add_attachment(self, attachment):
|
||||
if self.attachment_count <= len(self.attachments):
|
||||
raise ValueError('Unexpected binary attachment')
|
||||
self.attachments.append(attachment)
|
||||
if self.attachment_count == len(self.attachments):
|
||||
self.reconstruct_binary(self.attachments)
|
||||
return True
|
||||
return False
|
||||
|
||||
def reconstruct_binary(self, attachments):
|
||||
"""Reconstruct a decoded packet using the given list of binary
|
||||
attachments.
|
||||
"""
|
||||
self.data = self._reconstruct_binary_internal(self.data,
|
||||
self.attachments)
|
||||
|
||||
def _reconstruct_binary_internal(self, data, attachments):
|
||||
if isinstance(data, list):
|
||||
return [self._reconstruct_binary_internal(item, attachments)
|
||||
for item in data]
|
||||
elif isinstance(data, dict):
|
||||
if data.get('_placeholder') and 'num' in data:
|
||||
return attachments[data['num']]
|
||||
else:
|
||||
return {key: self._reconstruct_binary_internal(value,
|
||||
attachments)
|
||||
for key, value in six.iteritems(data)}
|
||||
else:
|
||||
return data
|
||||
|
||||
def _deconstruct_binary(self, data):
|
||||
"""Extract binary components in the packet."""
|
||||
attachments = []
|
||||
data = self._deconstruct_binary_internal(data, attachments)
|
||||
return data, attachments
|
||||
|
||||
def _deconstruct_binary_internal(self, data, attachments):
|
||||
if isinstance(data, six.binary_type):
|
||||
attachments.append(data)
|
||||
return {'_placeholder': True, 'num': len(attachments) - 1}
|
||||
elif isinstance(data, list):
|
||||
return [self._deconstruct_binary_internal(item, attachments)
|
||||
for item in data]
|
||||
elif isinstance(data, dict):
|
||||
return {key: self._deconstruct_binary_internal(value, attachments)
|
||||
for key, value in six.iteritems(data)}
|
||||
else:
|
||||
return data
|
||||
|
||||
def _data_is_binary(self, data):
|
||||
"""Check if the data contains binary components."""
|
||||
if isinstance(data, six.binary_type):
|
||||
return True
|
||||
elif isinstance(data, list):
|
||||
return functools.reduce(
|
||||
lambda a, b: a or b, [self._data_is_binary(item)
|
||||
for item in data], False)
|
||||
elif isinstance(data, dict):
|
||||
return functools.reduce(
|
||||
lambda a, b: a or b, [self._data_is_binary(item)
|
||||
for item in six.itervalues(data)],
|
||||
False)
|
||||
else:
|
||||
return False
|
||||
BIN
venv/lib/python2.7/site-packages/socketio/packet.pyc
Normal file
BIN
venv/lib/python2.7/site-packages/socketio/packet.pyc
Normal file
Binary file not shown.
151
venv/lib/python2.7/site-packages/socketio/pubsub_manager.py
Normal file
151
venv/lib/python2.7/site-packages/socketio/pubsub_manager.py
Normal file
@@ -0,0 +1,151 @@
|
||||
from functools import partial
|
||||
import uuid
|
||||
|
||||
import json
|
||||
import pickle
|
||||
import six
|
||||
|
||||
from .base_manager import BaseManager
|
||||
|
||||
|
||||
class PubSubManager(BaseManager):
|
||||
"""Manage a client list attached to a pub/sub backend.
|
||||
|
||||
This is a base class that enables multiple servers to share the list of
|
||||
clients, with the servers communicating events through a pub/sub backend.
|
||||
The use of a pub/sub backend also allows any client connected to the
|
||||
backend to emit events addressed to Socket.IO clients.
|
||||
|
||||
The actual backends must be implemented by subclasses, this class only
|
||||
provides a pub/sub generic framework.
|
||||
|
||||
:param channel: The channel name on which the server sends and receives
|
||||
notifications.
|
||||
"""
|
||||
name = 'pubsub'
|
||||
|
||||
def __init__(self, channel='socketio', write_only=False):
|
||||
super(PubSubManager, self).__init__()
|
||||
self.channel = channel
|
||||
self.write_only = write_only
|
||||
self.host_id = uuid.uuid4().hex
|
||||
|
||||
def initialize(self):
|
||||
super(PubSubManager, self).initialize()
|
||||
if not self.write_only:
|
||||
self.thread = self.server.start_background_task(self._thread)
|
||||
self.server.logger.info(self.name + ' backend initialized.')
|
||||
|
||||
def emit(self, event, data, namespace=None, room=None, skip_sid=None,
|
||||
callback=None, **kwargs):
|
||||
"""Emit a message to a single client, a room, or all the clients
|
||||
connected to the namespace.
|
||||
|
||||
This method takes care or propagating the message to all the servers
|
||||
that are connected through the message queue.
|
||||
|
||||
The parameters are the same as in :meth:`.Server.emit`.
|
||||
"""
|
||||
if kwargs.get('ignore_queue'):
|
||||
return super(PubSubManager, self).emit(
|
||||
event, data, namespace=namespace, room=room, skip_sid=skip_sid,
|
||||
callback=callback)
|
||||
namespace = namespace or '/'
|
||||
if callback is not None:
|
||||
if self.server is None:
|
||||
raise RuntimeError('Callbacks can only be issued from the '
|
||||
'context of a server.')
|
||||
if room is None:
|
||||
raise ValueError('Cannot use callback without a room set.')
|
||||
id = self._generate_ack_id(room, namespace, callback)
|
||||
callback = (room, namespace, id)
|
||||
else:
|
||||
callback = None
|
||||
self._publish({'method': 'emit', 'event': event, 'data': data,
|
||||
'namespace': namespace, 'room': room,
|
||||
'skip_sid': skip_sid, 'callback': callback})
|
||||
|
||||
def close_room(self, room, namespace=None):
|
||||
self._publish({'method': 'close_room', 'room': room,
|
||||
'namespace': namespace or '/'})
|
||||
|
||||
def _publish(self, data):
|
||||
"""Publish a message on the Socket.IO channel.
|
||||
|
||||
This method needs to be implemented by the different subclasses that
|
||||
support pub/sub backends.
|
||||
"""
|
||||
raise NotImplementedError('This method must be implemented in a '
|
||||
'subclass.') # pragma: no cover
|
||||
|
||||
def _listen(self):
|
||||
"""Return the next message published on the Socket.IO channel,
|
||||
blocking until a message is available.
|
||||
|
||||
This method needs to be implemented by the different subclasses that
|
||||
support pub/sub backends.
|
||||
"""
|
||||
raise NotImplementedError('This method must be implemented in a '
|
||||
'subclass.') # pragma: no cover
|
||||
|
||||
def _handle_emit(self, message):
|
||||
# Events with callbacks are very tricky to handle across hosts
|
||||
# Here in the receiving end we set up a local callback that preserves
|
||||
# the callback host and id from the sender
|
||||
remote_callback = message.get('callback')
|
||||
if remote_callback is not None and len(remote_callback) == 3:
|
||||
callback = partial(self._return_callback, self.host_id,
|
||||
*remote_callback)
|
||||
else:
|
||||
callback = None
|
||||
super(PubSubManager, self).emit(message['event'], message['data'],
|
||||
namespace=message.get('namespace'),
|
||||
room=message.get('room'),
|
||||
skip_sid=message.get('skip_sid'),
|
||||
callback=callback)
|
||||
|
||||
def _handle_callback(self, message):
|
||||
if self.host_id == message.get('host_id'):
|
||||
try:
|
||||
sid = message['sid']
|
||||
namespace = message['namespace']
|
||||
id = message['id']
|
||||
args = message['args']
|
||||
except KeyError:
|
||||
return
|
||||
self.trigger_callback(sid, namespace, id, args)
|
||||
|
||||
def _return_callback(self, host_id, sid, namespace, callback_id, *args):
|
||||
# When an event callback is received, the callback is returned back
|
||||
# the sender, which is identified by the host_id
|
||||
self._publish({'method': 'callback', 'host_id': host_id,
|
||||
'sid': sid, 'namespace': namespace, 'id': callback_id,
|
||||
'args': args})
|
||||
|
||||
def _handle_close_room(self, message):
|
||||
super(PubSubManager, self).close_room(
|
||||
room=message.get('room'), namespace=message.get('namespace'))
|
||||
|
||||
def _thread(self):
|
||||
for message in self._listen():
|
||||
data = None
|
||||
if isinstance(message, dict):
|
||||
data = message
|
||||
else:
|
||||
if isinstance(message, six.binary_type): # pragma: no cover
|
||||
try:
|
||||
data = pickle.loads(message)
|
||||
except:
|
||||
pass
|
||||
if data is None:
|
||||
try:
|
||||
data = json.loads(message)
|
||||
except:
|
||||
pass
|
||||
if data and 'method' in data:
|
||||
if data['method'] == 'emit':
|
||||
self._handle_emit(data)
|
||||
elif data['method'] == 'callback':
|
||||
self._handle_callback(data)
|
||||
elif data['method'] == 'close_room':
|
||||
self._handle_close_room(data)
|
||||
BIN
venv/lib/python2.7/site-packages/socketio/pubsub_manager.pyc
Normal file
BIN
venv/lib/python2.7/site-packages/socketio/pubsub_manager.pyc
Normal file
Binary file not shown.
71
venv/lib/python2.7/site-packages/socketio/redis_manager.py
Normal file
71
venv/lib/python2.7/site-packages/socketio/redis_manager.py
Normal file
@@ -0,0 +1,71 @@
|
||||
import pickle
|
||||
|
||||
try:
|
||||
import redis
|
||||
except ImportError:
|
||||
redis = None
|
||||
|
||||
from .pubsub_manager import PubSubManager
|
||||
|
||||
|
||||
class RedisManager(PubSubManager): # pragma: no cover
|
||||
"""Redis based client manager.
|
||||
|
||||
This class implements a Redis backend for event sharing across multiple
|
||||
processes. Only kept here as one more example of how to build a custom
|
||||
backend, since the kombu backend is perfectly adequate to support a Redis
|
||||
message queue.
|
||||
|
||||
To use a Redis backend, initialize the :class:`Server` instance as
|
||||
follows::
|
||||
|
||||
url = 'redis://hostname:port/0'
|
||||
server = socketio.Server(client_manager=socketio.RedisManager(url))
|
||||
|
||||
:param url: The connection URL for the Redis server. For a default Redis
|
||||
store running on the same host, use ``redis://``.
|
||||
:param channel: The channel name on which the server sends and receives
|
||||
notifications. Must be the same in all the servers.
|
||||
:param write_only: If set ot ``True``, only initialize to emit events. The
|
||||
default of ``False`` initializes the class for emitting
|
||||
and receiving.
|
||||
"""
|
||||
name = 'redis'
|
||||
|
||||
def __init__(self, url='redis://localhost:6379/0', channel='socketio',
|
||||
write_only=False):
|
||||
if redis is None:
|
||||
raise RuntimeError('Redis package is not installed '
|
||||
'(Run "pip install redis" in your '
|
||||
'virtualenv).')
|
||||
self.redis = redis.Redis.from_url(url)
|
||||
self.pubsub = self.redis.pubsub()
|
||||
super(RedisManager, self).__init__(channel=channel,
|
||||
write_only=write_only)
|
||||
|
||||
def initialize(self):
|
||||
super(RedisManager, self).initialize()
|
||||
|
||||
monkey_patched = True
|
||||
if self.server.async_mode == 'eventlet':
|
||||
from eventlet.patcher import is_monkey_patched
|
||||
monkey_patched = is_monkey_patched('socket')
|
||||
elif 'gevent' in self.server.async_mode:
|
||||
from gevent.monkey import is_module_patched
|
||||
monkey_patched = is_module_patched('socket')
|
||||
if not monkey_patched:
|
||||
raise RuntimeError(
|
||||
'Redis requires a monkey patched socket library to work '
|
||||
'with ' + self.server.async_mode)
|
||||
|
||||
def _publish(self, data):
|
||||
return self.redis.publish(self.channel, pickle.dumps(data))
|
||||
|
||||
def _listen(self):
|
||||
channel = self.channel.encode('utf-8')
|
||||
self.pubsub.subscribe(self.channel)
|
||||
for message in self.pubsub.listen():
|
||||
if message['channel'] == channel and \
|
||||
message['type'] == 'message' and 'data' in message:
|
||||
yield message['data']
|
||||
self.pubsub.unsubscribe(self.channel)
|
||||
BIN
venv/lib/python2.7/site-packages/socketio/redis_manager.pyc
Normal file
BIN
venv/lib/python2.7/site-packages/socketio/redis_manager.pyc
Normal file
Binary file not shown.
529
venv/lib/python2.7/site-packages/socketio/server.py
Normal file
529
venv/lib/python2.7/site-packages/socketio/server.py
Normal file
@@ -0,0 +1,529 @@
|
||||
import logging
|
||||
|
||||
import engineio
|
||||
import six
|
||||
|
||||
from . import base_manager
|
||||
from . import packet
|
||||
from . import namespace
|
||||
|
||||
default_logger = logging.getLogger('socketio')
|
||||
|
||||
|
||||
class Server(object):
|
||||
"""A Socket.IO server.
|
||||
|
||||
This class implements a fully compliant Socket.IO web server with support
|
||||
for websocket and long-polling transports.
|
||||
|
||||
:param client_manager: The client manager instance that will manage the
|
||||
client list. When this is omitted, the client list
|
||||
is stored in an in-memory structure, so the use of
|
||||
multiple connected servers is not possible.
|
||||
:param logger: To enable logging set to ``True`` or pass a logger object to
|
||||
use. To disable logging set to ``False``.
|
||||
:param binary: ``True`` to support binary payloads, ``False`` to treat all
|
||||
payloads as text. On Python 2, if this is set to ``True``,
|
||||
``unicode`` values are treated as text, and ``str`` and
|
||||
``bytes`` values are treated as binary. This option has no
|
||||
effect on Python 3, where text and binary payloads are
|
||||
always automatically discovered.
|
||||
:param json: An alternative json module to use for encoding and decoding
|
||||
packets. Custom json modules must have ``dumps`` and ``loads``
|
||||
functions that are compatible with the standard library
|
||||
versions.
|
||||
:param async_handlers: If set to ``True``, event handlers are executed in
|
||||
separate threads. To run handlers synchronously,
|
||||
set to ``False``. The default is ``False``.
|
||||
:param kwargs: Connection parameters for the underlying Engine.IO server.
|
||||
|
||||
The Engine.IO configuration supports the following settings:
|
||||
|
||||
:param async_mode: The asynchronous model to use. See the Deployment
|
||||
section in the documentation for a description of the
|
||||
available options. Valid async modes are "threading",
|
||||
"eventlet", "gevent" and "gevent_uwsgi". If this
|
||||
argument is not given, "eventlet" is tried first, then
|
||||
"gevent_uwsgi", then "gevent", and finally "threading".
|
||||
The first async mode that has all its dependencies
|
||||
installed is then one that is chosen.
|
||||
:param ping_timeout: The time in seconds that the client waits for the
|
||||
server to respond before disconnecting.
|
||||
:param ping_interval: The interval in seconds at which the client pings
|
||||
the server.
|
||||
:param max_http_buffer_size: The maximum size of a message when using the
|
||||
polling transport.
|
||||
:param allow_upgrades: Whether to allow transport upgrades or not.
|
||||
:param http_compression: Whether to compress packages when using the
|
||||
polling transport.
|
||||
:param compression_threshold: Only compress messages when their byte size
|
||||
is greater than this value.
|
||||
:param cookie: Name of the HTTP cookie that contains the client session
|
||||
id. If set to ``None``, a cookie is not sent to the client.
|
||||
:param cors_allowed_origins: List of origins that are allowed to connect
|
||||
to this server. All origins are allowed by
|
||||
default.
|
||||
:param cors_credentials: Whether credentials (cookies, authentication) are
|
||||
allowed in requests to this server.
|
||||
:param engineio_logger: To enable Engine.IO logging set to ``True`` or pass
|
||||
a logger object to use. To disable logging set to
|
||||
``False``.
|
||||
"""
|
||||
def __init__(self, client_manager=None, logger=False, binary=False,
|
||||
json=None, async_handlers=False, **kwargs):
|
||||
engineio_options = kwargs
|
||||
engineio_logger = engineio_options.pop('engineio_logger', None)
|
||||
if engineio_logger is not None:
|
||||
engineio_options['logger'] = engineio_logger
|
||||
if json is not None:
|
||||
packet.Packet.json = json
|
||||
engineio_options['json'] = json
|
||||
engineio_options['async_handlers'] = False
|
||||
self.eio = self._engineio_server_class()(**engineio_options)
|
||||
self.eio.on('connect', self._handle_eio_connect)
|
||||
self.eio.on('message', self._handle_eio_message)
|
||||
self.eio.on('disconnect', self._handle_eio_disconnect)
|
||||
self.binary = binary
|
||||
|
||||
self.environ = {}
|
||||
self.handlers = {}
|
||||
self.namespace_handlers = {}
|
||||
|
||||
self._binary_packet = {}
|
||||
|
||||
if not isinstance(logger, bool):
|
||||
self.logger = logger
|
||||
else:
|
||||
self.logger = default_logger
|
||||
if not logging.root.handlers and \
|
||||
self.logger.level == logging.NOTSET:
|
||||
if logger:
|
||||
self.logger.setLevel(logging.INFO)
|
||||
else:
|
||||
self.logger.setLevel(logging.ERROR)
|
||||
self.logger.addHandler(logging.StreamHandler())
|
||||
|
||||
if client_manager is None:
|
||||
client_manager = base_manager.BaseManager()
|
||||
self.manager = client_manager
|
||||
self.manager.set_server(self)
|
||||
self.manager_initialized = False
|
||||
|
||||
self.async_handlers = async_handlers
|
||||
|
||||
self.async_mode = self.eio.async_mode
|
||||
|
||||
def is_asyncio_based(self):
|
||||
return False
|
||||
|
||||
def on(self, event, handler=None, namespace=None):
|
||||
"""Register an event handler.
|
||||
|
||||
:param event: The event name. It can be any string. The event names
|
||||
``'connect'``, ``'message'`` and ``'disconnect'`` are
|
||||
reserved and should not be used.
|
||||
:param handler: The function that should be invoked to handle the
|
||||
event. When this parameter is not given, the method
|
||||
acts as a decorator for the handler function.
|
||||
:param namespace: The Socket.IO namespace for the event. If this
|
||||
argument is omitted the handler is associated with
|
||||
the default namespace.
|
||||
|
||||
Example usage::
|
||||
|
||||
# as a decorator:
|
||||
@socket_io.on('connect', namespace='/chat')
|
||||
def connect_handler(sid, environ):
|
||||
print('Connection request')
|
||||
if environ['REMOTE_ADDR'] in blacklisted:
|
||||
return False # reject
|
||||
|
||||
# as a method:
|
||||
def message_handler(sid, msg):
|
||||
print('Received message: ', msg)
|
||||
eio.send(sid, 'response')
|
||||
socket_io.on('message', namespace='/chat', message_handler)
|
||||
|
||||
The handler function receives the ``sid`` (session ID) for the
|
||||
client as first argument. The ``'connect'`` event handler receives the
|
||||
WSGI environment as a second argument, and can return ``False`` to
|
||||
reject the connection. The ``'message'`` handler and handlers for
|
||||
custom event names receive the message payload as a second argument.
|
||||
Any values returned from a message handler will be passed to the
|
||||
client's acknowledgement callback function if it exists. The
|
||||
``'disconnect'`` handler does not take a second argument.
|
||||
"""
|
||||
namespace = namespace or '/'
|
||||
|
||||
def set_handler(handler):
|
||||
if namespace not in self.handlers:
|
||||
self.handlers[namespace] = {}
|
||||
self.handlers[namespace][event] = handler
|
||||
return handler
|
||||
|
||||
if handler is None:
|
||||
return set_handler
|
||||
set_handler(handler)
|
||||
|
||||
def register_namespace(self, namespace_handler):
|
||||
"""Register a namespace handler object.
|
||||
|
||||
:param namespace_handler: An instance of a :class:`Namespace`
|
||||
subclass that handles all the event traffic
|
||||
for a namespace.
|
||||
"""
|
||||
if not isinstance(namespace_handler, namespace.Namespace):
|
||||
raise ValueError('Not a namespace instance')
|
||||
if self.is_asyncio_based() != namespace_handler.is_asyncio_based():
|
||||
raise ValueError('Not a valid namespace class for this server')
|
||||
namespace_handler._set_server(self)
|
||||
self.namespace_handlers[namespace_handler.namespace] = \
|
||||
namespace_handler
|
||||
|
||||
def emit(self, event, data=None, room=None, skip_sid=None, namespace=None,
|
||||
callback=None, **kwargs):
|
||||
"""Emit a custom event to one or more connected clients.
|
||||
|
||||
:param event: The event name. It can be any string. The event names
|
||||
``'connect'``, ``'message'`` and ``'disconnect'`` are
|
||||
reserved and should not be used.
|
||||
:param data: The data to send to the client or clients. Data can be of
|
||||
type ``str``, ``bytes``, ``list`` or ``dict``. If a
|
||||
``list`` or ``dict``, the data will be serialized as JSON.
|
||||
:param room: The recipient of the message. This can be set to the
|
||||
session ID of a client to address that client's room, or
|
||||
to any custom room created by the application, If this
|
||||
argument is omitted the event is broadcasted to all
|
||||
connected clients.
|
||||
:param skip_sid: The session ID of a client to skip when broadcasting
|
||||
to a room or to all clients. This can be used to
|
||||
prevent a message from being sent to the sender.
|
||||
:param namespace: The Socket.IO namespace for the event. If this
|
||||
argument is omitted the event is emitted to the
|
||||
default namespace.
|
||||
:param callback: If given, this function will be called to acknowledge
|
||||
the the client has received the message. The arguments
|
||||
that will be passed to the function are those provided
|
||||
by the client. Callback functions can only be used
|
||||
when addressing an individual client.
|
||||
:param ignore_queue: Only used when a message queue is configured. If
|
||||
set to ``True``, the event is emitted to the
|
||||
clients directly, without going through the queue.
|
||||
This is more efficient, but only works when a
|
||||
single server process is used. It is recommended
|
||||
to always leave this parameter with its default
|
||||
value of ``False``.
|
||||
"""
|
||||
namespace = namespace or '/'
|
||||
self.logger.info('emitting event "%s" to %s [%s]', event,
|
||||
room or 'all', namespace)
|
||||
self.manager.emit(event, data, namespace, room, skip_sid, callback,
|
||||
**kwargs)
|
||||
|
||||
def send(self, data, room=None, skip_sid=None, namespace=None,
|
||||
callback=None, **kwargs):
|
||||
"""Send a message to one or more connected clients.
|
||||
|
||||
This function emits an event with the name ``'message'``. Use
|
||||
:func:`emit` to issue custom event names.
|
||||
|
||||
:param data: The data to send to the client or clients. Data can be of
|
||||
type ``str``, ``bytes``, ``list`` or ``dict``. If a
|
||||
``list`` or ``dict``, the data will be serialized as JSON.
|
||||
:param room: The recipient of the message. This can be set to the
|
||||
session ID of a client to address that client's room, or
|
||||
to any custom room created by the application, If this
|
||||
argument is omitted the event is broadcasted to all
|
||||
connected clients.
|
||||
:param skip_sid: The session ID of a client to skip when broadcasting
|
||||
to a room or to all clients. This can be used to
|
||||
prevent a message from being sent to the sender.
|
||||
:param namespace: The Socket.IO namespace for the event. If this
|
||||
argument is omitted the event is emitted to the
|
||||
default namespace.
|
||||
:param callback: If given, this function will be called to acknowledge
|
||||
the the client has received the message. The arguments
|
||||
that will be passed to the function are those provided
|
||||
by the client. Callback functions can only be used
|
||||
when addressing an individual client.
|
||||
:param ignore_queue: Only used when a message queue is configured. If
|
||||
set to ``True``, the event is emitted to the
|
||||
clients directly, without going through the queue.
|
||||
This is more efficient, but only works when a
|
||||
single server process is used. It is recommended
|
||||
to always leave this parameter with its default
|
||||
value of ``False``.
|
||||
"""
|
||||
self.emit('message', data, room, skip_sid, namespace, callback,
|
||||
**kwargs)
|
||||
|
||||
def enter_room(self, sid, room, namespace=None):
|
||||
"""Enter a room.
|
||||
|
||||
This function adds the client to a room. The :func:`emit` and
|
||||
:func:`send` functions can optionally broadcast events to all the
|
||||
clients in a room.
|
||||
|
||||
:param sid: Session ID of the client.
|
||||
:param room: Room name. If the room does not exist it is created.
|
||||
:param namespace: The Socket.IO namespace for the event. If this
|
||||
argument is omitted the default namespace is used.
|
||||
"""
|
||||
namespace = namespace or '/'
|
||||
self.logger.info('%s is entering room %s [%s]', sid, room, namespace)
|
||||
self.manager.enter_room(sid, namespace, room)
|
||||
|
||||
def leave_room(self, sid, room, namespace=None):
|
||||
"""Leave a room.
|
||||
|
||||
This function removes the client from a room.
|
||||
|
||||
:param sid: Session ID of the client.
|
||||
:param room: Room name.
|
||||
:param namespace: The Socket.IO namespace for the event. If this
|
||||
argument is omitted the default namespace is used.
|
||||
"""
|
||||
namespace = namespace or '/'
|
||||
self.logger.info('%s is leaving room %s [%s]', sid, room, namespace)
|
||||
self.manager.leave_room(sid, namespace, room)
|
||||
|
||||
def close_room(self, room, namespace=None):
|
||||
"""Close a room.
|
||||
|
||||
This function removes all the clients from the given room.
|
||||
|
||||
:param room: Room name.
|
||||
:param namespace: The Socket.IO namespace for the event. If this
|
||||
argument is omitted the default namespace is used.
|
||||
"""
|
||||
namespace = namespace or '/'
|
||||
self.logger.info('room %s is closing [%s]', room, namespace)
|
||||
self.manager.close_room(room, namespace)
|
||||
|
||||
def rooms(self, sid, namespace=None):
|
||||
"""Return the rooms a client is in.
|
||||
|
||||
:param sid: Session ID of the client.
|
||||
:param namespace: The Socket.IO namespace for the event. If this
|
||||
argument is omitted the default namespace is used.
|
||||
"""
|
||||
namespace = namespace or '/'
|
||||
return self.manager.get_rooms(sid, namespace)
|
||||
|
||||
def disconnect(self, sid, namespace=None):
|
||||
"""Disconnect a client.
|
||||
|
||||
:param sid: Session ID of the client.
|
||||
:param namespace: The Socket.IO namespace to disconnect. If this
|
||||
argument is omitted the default namespace is used.
|
||||
"""
|
||||
namespace = namespace or '/'
|
||||
if self.manager.is_connected(sid, namespace=namespace):
|
||||
self.logger.info('Disconnecting %s [%s]', sid, namespace)
|
||||
self.manager.pre_disconnect(sid, namespace=namespace)
|
||||
self._send_packet(sid, packet.Packet(packet.DISCONNECT,
|
||||
namespace=namespace))
|
||||
self._trigger_event('disconnect', namespace, sid)
|
||||
self.manager.disconnect(sid, namespace=namespace)
|
||||
|
||||
def transport(self, sid):
|
||||
"""Return the name of the transport used by the client.
|
||||
|
||||
The two possible values returned by this function are ``'polling'``
|
||||
and ``'websocket'``.
|
||||
|
||||
:param sid: The session of the client.
|
||||
"""
|
||||
return self.eio.transport(sid)
|
||||
|
||||
def handle_request(self, environ, start_response):
|
||||
"""Handle an HTTP request from the client.
|
||||
|
||||
This is the entry point of the Socket.IO application, using the same
|
||||
interface as a WSGI application. For the typical usage, this function
|
||||
is invoked by the :class:`Middleware` instance, but it can be invoked
|
||||
directly when the middleware is not used.
|
||||
|
||||
:param environ: The WSGI environment.
|
||||
:param start_response: The WSGI ``start_response`` function.
|
||||
|
||||
This function returns the HTTP response body to deliver to the client
|
||||
as a byte sequence.
|
||||
"""
|
||||
return self.eio.handle_request(environ, start_response)
|
||||
|
||||
def start_background_task(self, target, *args, **kwargs):
|
||||
"""Start a background task using the appropriate async model.
|
||||
|
||||
This is a utility function that applications can use to start a
|
||||
background task using the method that is compatible with the
|
||||
selected async mode.
|
||||
|
||||
:param target: the target function to execute.
|
||||
:param args: arguments to pass to the function.
|
||||
:param kwargs: keyword arguments to pass to the function.
|
||||
|
||||
This function returns an object compatible with the `Thread` class in
|
||||
the Python standard library. The `start()` method on this object is
|
||||
already called by this function.
|
||||
"""
|
||||
return self.eio.start_background_task(target, *args, **kwargs)
|
||||
|
||||
def sleep(self, seconds=0):
|
||||
"""Sleep for the requested amount of time using the appropriate async
|
||||
model.
|
||||
|
||||
This is a utility function that applications can use to put a task to
|
||||
sleep without having to worry about using the correct call for the
|
||||
selected async mode.
|
||||
"""
|
||||
return self.eio.sleep(seconds)
|
||||
|
||||
def _emit_internal(self, sid, event, data, namespace=None, id=None):
|
||||
"""Send a message to a client."""
|
||||
if six.PY2 and not self.binary:
|
||||
binary = False # pragma: nocover
|
||||
else:
|
||||
binary = None
|
||||
# tuples are expanded to multiple arguments, everything else is sent
|
||||
# as a single argument
|
||||
if isinstance(data, tuple):
|
||||
data = list(data)
|
||||
else:
|
||||
data = [data]
|
||||
self._send_packet(sid, packet.Packet(packet.EVENT, namespace=namespace,
|
||||
data=[event] + data, id=id,
|
||||
binary=binary))
|
||||
|
||||
def _send_packet(self, sid, pkt):
|
||||
"""Send a Socket.IO packet to a client."""
|
||||
encoded_packet = pkt.encode()
|
||||
if isinstance(encoded_packet, list):
|
||||
binary = False
|
||||
for ep in encoded_packet:
|
||||
self.eio.send(sid, ep, binary=binary)
|
||||
binary = True
|
||||
else:
|
||||
self.eio.send(sid, encoded_packet, binary=False)
|
||||
|
||||
def _handle_connect(self, sid, namespace):
|
||||
"""Handle a client connection request."""
|
||||
namespace = namespace or '/'
|
||||
self.manager.connect(sid, namespace)
|
||||
if self._trigger_event('connect', namespace, sid,
|
||||
self.environ[sid]) is False:
|
||||
self.manager.disconnect(sid, namespace)
|
||||
self._send_packet(sid, packet.Packet(packet.ERROR,
|
||||
namespace=namespace))
|
||||
return False
|
||||
else:
|
||||
self._send_packet(sid, packet.Packet(packet.CONNECT,
|
||||
namespace=namespace))
|
||||
|
||||
def _handle_disconnect(self, sid, namespace):
|
||||
"""Handle a client disconnect."""
|
||||
namespace = namespace or '/'
|
||||
if namespace == '/':
|
||||
namespace_list = list(self.manager.get_namespaces())
|
||||
else:
|
||||
namespace_list = [namespace]
|
||||
for n in namespace_list:
|
||||
if n != '/' and self.manager.is_connected(sid, n):
|
||||
self._trigger_event('disconnect', n, sid)
|
||||
self.manager.disconnect(sid, n)
|
||||
if namespace == '/' and self.manager.is_connected(sid, namespace):
|
||||
self._trigger_event('disconnect', '/', sid)
|
||||
self.manager.disconnect(sid, '/')
|
||||
if sid in self.environ:
|
||||
del self.environ[sid]
|
||||
|
||||
def _handle_event(self, sid, namespace, id, data):
|
||||
"""Handle an incoming client event."""
|
||||
namespace = namespace or '/'
|
||||
self.logger.info('received event "%s" from %s [%s]', data[0], sid,
|
||||
namespace)
|
||||
if self.async_handlers:
|
||||
self.start_background_task(self._handle_event_internal, self, sid,
|
||||
data, namespace, id)
|
||||
else:
|
||||
self._handle_event_internal(self, sid, data, namespace, id)
|
||||
|
||||
def _handle_event_internal(self, server, sid, data, namespace, id):
|
||||
r = server._trigger_event(data[0], namespace, sid, *data[1:])
|
||||
if id is not None:
|
||||
# send ACK packet with the response returned by the handler
|
||||
# tuples are expanded as multiple arguments
|
||||
if r is None:
|
||||
data = []
|
||||
elif isinstance(r, tuple):
|
||||
data = list(r)
|
||||
else:
|
||||
data = [r]
|
||||
if six.PY2 and not self.binary:
|
||||
binary = False # pragma: nocover
|
||||
else:
|
||||
binary = None
|
||||
server._send_packet(sid, packet.Packet(packet.ACK,
|
||||
namespace=namespace,
|
||||
id=id, data=data,
|
||||
binary=binary))
|
||||
|
||||
def _handle_ack(self, sid, namespace, id, data):
|
||||
"""Handle ACK packets from the client."""
|
||||
namespace = namespace or '/'
|
||||
self.logger.info('received ack from %s [%s]', sid, namespace)
|
||||
self.manager.trigger_callback(sid, namespace, id, data)
|
||||
|
||||
def _trigger_event(self, event, namespace, *args):
|
||||
"""Invoke an application event handler."""
|
||||
# first see if we have an explicit handler for the event
|
||||
if namespace in self.handlers and event in self.handlers[namespace]:
|
||||
return self.handlers[namespace][event](*args)
|
||||
|
||||
# or else, forward the event to a namepsace handler if one exists
|
||||
elif namespace in self.namespace_handlers:
|
||||
return self.namespace_handlers[namespace].trigger_event(
|
||||
event, *args)
|
||||
|
||||
def _handle_eio_connect(self, sid, environ):
|
||||
"""Handle the Engine.IO connection event."""
|
||||
if not self.manager_initialized:
|
||||
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."""
|
||||
if sid in self._binary_packet:
|
||||
pkt = self._binary_packet[sid]
|
||||
if pkt.add_attachment(data):
|
||||
del self._binary_packet[sid]
|
||||
if pkt.packet_type == packet.BINARY_EVENT:
|
||||
self._handle_event(sid, pkt.namespace, pkt.id, pkt.data)
|
||||
else:
|
||||
self._handle_ack(sid, pkt.namespace, pkt.id, pkt.data)
|
||||
else:
|
||||
pkt = packet.Packet(encoded_packet=data)
|
||||
if pkt.packet_type == packet.CONNECT:
|
||||
self._handle_connect(sid, pkt.namespace)
|
||||
elif pkt.packet_type == packet.DISCONNECT:
|
||||
self._handle_disconnect(sid, pkt.namespace)
|
||||
elif pkt.packet_type == packet.EVENT:
|
||||
self._handle_event(sid, pkt.namespace, pkt.id, pkt.data)
|
||||
elif pkt.packet_type == packet.ACK:
|
||||
self._handle_ack(sid, pkt.namespace, pkt.id, pkt.data)
|
||||
elif pkt.packet_type == packet.BINARY_EVENT or \
|
||||
pkt.packet_type == packet.BINARY_ACK:
|
||||
self._binary_packet[sid] = pkt
|
||||
elif pkt.packet_type == packet.ERROR:
|
||||
raise ValueError('Unexpected ERROR packet.')
|
||||
else:
|
||||
raise ValueError('Unknown packet type.')
|
||||
|
||||
def _handle_eio_disconnect(self, sid):
|
||||
"""Handle Engine.IO disconnect event."""
|
||||
self._handle_disconnect(sid, '/')
|
||||
|
||||
def _engineio_server_class(self):
|
||||
return engineio.Server
|
||||
BIN
venv/lib/python2.7/site-packages/socketio/server.pyc
Normal file
BIN
venv/lib/python2.7/site-packages/socketio/server.pyc
Normal file
Binary file not shown.
109
venv/lib/python2.7/site-packages/socketio/zmq_manager.py
Normal file
109
venv/lib/python2.7/site-packages/socketio/zmq_manager.py
Normal file
@@ -0,0 +1,109 @@
|
||||
import pickle
|
||||
import re
|
||||
|
||||
try:
|
||||
import eventlet.green.zmq as zmq
|
||||
except ImportError:
|
||||
zmq = None
|
||||
import six
|
||||
|
||||
from .pubsub_manager import PubSubManager
|
||||
|
||||
|
||||
class ZmqManager(PubSubManager): # pragma: no cover
|
||||
"""zmq based client manager.
|
||||
|
||||
NOTE: this zmq implementation should be considered experimental at this
|
||||
time. At this time, eventlet is required to use zmq.
|
||||
|
||||
This class implements a zmq backend for event sharing across multiple
|
||||
processes. To use a zmq backend, initialize the :class:`Server` instance as
|
||||
follows::
|
||||
|
||||
url = 'zmq+tcp://hostname:port1+port2'
|
||||
server = socketio.Server(client_manager=socketio.ZmqManager(url))
|
||||
|
||||
:param url: The connection URL for the zmq message broker,
|
||||
which will need to be provided and running.
|
||||
:param channel: The channel name on which the server sends and receives
|
||||
notifications. Must be the same in all the servers.
|
||||
:param write_only: If set to ``True``, only initialize to emit events. The
|
||||
default of ``False`` initializes the class for emitting
|
||||
and receiving.
|
||||
|
||||
A zmq message broker must be running for the zmq_manager to work.
|
||||
you can write your own or adapt one from the following simple broker
|
||||
below::
|
||||
|
||||
import zmq
|
||||
|
||||
receiver = zmq.Context().socket(zmq.PULL)
|
||||
receiver.bind("tcp://*:5555")
|
||||
|
||||
publisher = zmq.Context().socket(zmq.PUB)
|
||||
publisher.bind("tcp://*:5556")
|
||||
|
||||
while True:
|
||||
publisher.send(receiver.recv())
|
||||
"""
|
||||
name = 'zmq'
|
||||
|
||||
def __init__(self, url='zmq+tcp://localhost:5555+5556',
|
||||
channel='socketio',
|
||||
write_only=False):
|
||||
if zmq is None:
|
||||
raise RuntimeError('zmq package is not installed '
|
||||
'(Run "pip install pyzmq" in your '
|
||||
'virtualenv).')
|
||||
|
||||
r = re.compile(':\d+\+\d+$')
|
||||
if not (url.startswith('zmq+tcp://') and r.search(url)):
|
||||
raise RuntimeError('unexpected connection string: ' + url)
|
||||
|
||||
url = url.replace('zmq+', '')
|
||||
(sink_url, sub_port) = url.split('+')
|
||||
sink_port = sink_url.split(':')[-1]
|
||||
sub_url = sink_url.replace(sink_port, sub_port)
|
||||
|
||||
sink = zmq.Context().socket(zmq.PUSH)
|
||||
sink.connect(sink_url)
|
||||
|
||||
sub = zmq.Context().socket(zmq.SUB)
|
||||
sub.setsockopt_string(zmq.SUBSCRIBE, u'')
|
||||
sub.connect(sub_url)
|
||||
|
||||
self.sink = sink
|
||||
self.sub = sub
|
||||
self.channel = channel
|
||||
super(ZmqManager, self).__init__(channel=channel,
|
||||
write_only=write_only)
|
||||
|
||||
def _publish(self, data):
|
||||
pickled_data = pickle.dumps(
|
||||
{
|
||||
'type': 'message',
|
||||
'channel': self.channel,
|
||||
'data': data
|
||||
}
|
||||
)
|
||||
return self.sink.send(pickled_data)
|
||||
|
||||
def zmq_listen(self):
|
||||
while True:
|
||||
response = self.sub.recv()
|
||||
if response is not None:
|
||||
yield response
|
||||
|
||||
def _listen(self):
|
||||
for message in self.zmq_listen():
|
||||
if isinstance(message, six.binary_type):
|
||||
try:
|
||||
message = pickle.loads(message)
|
||||
except Exception:
|
||||
pass
|
||||
if isinstance(message, dict) and \
|
||||
message['type'] == 'message' and \
|
||||
message['channel'] == self.channel and \
|
||||
'data' in message:
|
||||
yield message['data']
|
||||
return
|
||||
BIN
venv/lib/python2.7/site-packages/socketio/zmq_manager.pyc
Normal file
BIN
venv/lib/python2.7/site-packages/socketio/zmq_manager.pyc
Normal file
Binary file not shown.
Reference in New Issue
Block a user