import logging
import engineio
from . import base_manager
from . import exceptions
from . import namespace
from . import packet
default_logger = logging . getLogger ( ' socketio.server ' )
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 ` ` . The default is
` ` False ` ` . Note that fatal errors are logged even when
` ` logger ` ` is ` ` False ` ` .
: param serializer : The serialization method to use when transmitting
packets . Valid values are ` ` ' default ' ` ` , ` ` ' pickle ' ` ` ,
` ` ' msgpack ' ` ` and ` ` ' cbor ' ` ` . Alternatively , a subclass
of the : class : ` Packet ` class with custom implementations
of the ` ` encode ( ) ` ` and ` ` decode ( ) ` ` methods can be
provided . Client and server must use compatible
serializers .
: 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 for a client are
executed in separate threads . To run handlers for a
client synchronously , set to ` ` False ` ` . The default
is ` ` True ` ` .
: param always_connect : When set to ` ` False ` ` , new connections are
provisory until the connect handler returns
something other than ` ` False ` ` , at which point they
are accepted . When set to ` ` True ` ` , connections are
immediately accepted , and then if the connect
handler returns ` ` False ` ` a disconnect is issued .
Set to ` ` True ` ` if you need to emit events from the
connect handler and your client is confused when it
receives events before the connection acceptance .
In any other case use the default of ` ` 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_interval : The interval in seconds at which the server pings
the client . The default is 25 seconds . For advanced
control , a two element tuple can be given , where
the first number is the ping interval and the second
is a grace period added by the server .
: param ping_timeout : The time in seconds that the client waits for the
server to respond before disconnecting . The default
is 5 seconds .
: param max_http_buffer_size : The maximum size of a message when using the
polling transport . The default is 1 , 000 , 000
bytes .
: param allow_upgrades : Whether to allow transport upgrades or not . The
default is ` ` True ` ` .
: param http_compression : Whether to compress packages when using the
polling transport . The default is ` ` True ` ` .
: param compression_threshold : Only compress messages when their byte size
is greater than this value . The default is
1024 bytes .
: param cookie : If set to a string , it is the name of the HTTP cookie the
server sends back tot he client containing the client
session id . If set to a dictionary , the ` ` ' name ' ` ` key
contains the cookie name and other keys define cookie
attributes , where the value of each attribute can be a
string , a callable with no arguments , or a boolean . If set
to ` ` None ` ` ( the default ) , a cookie is not sent to the
client .
: param cors_allowed_origins : Origin or list of origins that are allowed to
connect to this server . Only the same origin
is allowed by default . Set this argument to
` ` ' * ' ` ` to allow all origins , or to ` ` [ ] ` ` to
disable CORS handling .
: param cors_credentials : Whether credentials ( cookies , authentication ) are
allowed in requests to this server . The default is
` ` True ` ` .
: param monitor_clients : If set to ` ` True ` ` , a background task will ensure
inactive clients are closed . Set to ` ` False ` ` to
disable the monitoring task ( not recommended ) . The
default is ` ` True ` ` .
: param engineio_logger : To enable Engine . IO logging set to ` ` True ` ` or pass
a logger object to use . To disable logging set to
` ` False ` ` . The default is ` ` False ` ` . Note that
fatal errors are logged even when
` ` engineio_logger ` ` is ` ` False ` ` .
"""
reserved_events = [ ' connect ' , ' disconnect ' ]
def __init__ ( self , client_manager = None , logger = False , serializer = ' default ' ,
json = None , async_handlers = True , always_connect = 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 serializer == ' default ' :
self . packet_class = packet . Packet
elif serializer == ' msgpack ' :
from . import msgpack_packet
self . packet_class = msgpack_packet . MsgPackPacket
else :
self . packet_class = serializer
if json is not None :
self . packet_class . 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 . environ = { }
self . handlers = { }
self . namespace_handlers = { }
self . _binary_packet = { }
if not isinstance ( logger , bool ) :
self . logger = logger
else :
self . logger = default_logger
if 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 . always_connect = always_connect
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 ' , handler = 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 event ( self , * args , * * kwargs ) :
""" Decorator to register an event handler.
This is a simplified version of the ` ` on ( ) ` ` method that takes the
event name from the decorated function .
Example usage : :
@sio.event
def my_event ( data ) :
print ( ' Received data: ' , data )
The above example is equivalent to : :
@sio.on ( ' my_event ' )
def my_event ( data ) :
print ( ' Received data: ' , data )
A custom namespace can be given as an argument to the decorator : :
@sio.event ( namespace = ' /test ' )
def my_event ( data ) :
print ( ' Received data: ' , data )
"""
if len ( args ) == 1 and len ( kwargs ) == 0 and callable ( args [ 0 ] ) :
# the decorator was invoked without arguments
# args[0] is the decorated function
return self . on ( args [ 0 ] . __name__ ) ( args [ 0 ] )
else :
# the decorator was invoked with arguments
def set_handler ( handler ) :
return self . on ( handler . __name__ , * args , * * kwargs ) ( handler )
return set_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 , to = 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 ` ` . To send
multiple arguments , use a tuple where each element is of
one of the types indicated above .
: param to : The recipient of the message . This can be set to the
session ID of a client to address only that client , to any
any custom room created by the application to address all
the clients in that room , or to a list of custom room
names . If this argument is omitted the event is broadcasted
to all connected clients .
: param room : Alias for the ` ` to ` ` parameter .
: 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 . To
skip multiple sids , pass a list .
: 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 not thread safe . If multiple threads are emitting
at the same time to the same client , then messages composed of
multiple packets may end up being sent in an incorrect sequence . Use
standard concurrency solutions ( such as a Lock object ) to prevent this
situation .
"""
namespace = namespace or ' / '
room = to or room
self . logger . info ( ' emitting event " %s " to %s [ %s ] ' , event ,
room or ' all ' , namespace )
self . manager . emit ( event , data , namespace , room = room ,
skip_sid = skip_sid , callback = callback , * * kwargs )
def send ( self , data , to = None , 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 ` ` . To send
multiple arguments , use a tuple where each element is of
one of the types indicated above .
: param to : The recipient of the message . This can be set to the
session ID of a client to address only that client , to any
any custom room created by the application to address all
the clients in that room , or to a list of custom room
names . If this argument is omitted the event is broadcasted
to all connected clients .
: param room : Alias for the ` ` to ` ` parameter .
: 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 . To
skip multiple sids , pass a list .
: 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 = data , to = to , room = room , skip_sid = skip_sid ,
namespace = namespace , callback = callback , * * kwargs )
def call ( self , event , data = None , to = None , sid = None , namespace = None ,
timeout = 60 , * * kwargs ) :
""" Emit a custom event to a client and wait for the response.
This method issues an emit with a callback and waits for the callback
to be invoked before returning . If the callback isn ' t invoked before
the timeout , then a ` ` TimeoutError ` ` exception is raised . If the
Socket . IO connection drops during the wait , this method still waits
until the specified timeout .
: 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 ` ` . To send
multiple arguments , use a tuple where each element is of
one of the types indicated above .
: param to : The session ID of the recipient client .
: param sid : Alias for the ` ` to ` ` parameter .
: param namespace : The Socket . IO namespace for the event . If this
argument is omitted the event is emitted to the
default namespace .
: param timeout : The waiting timeout . If the timeout is reached before
the client acknowledges the event , then a
` ` TimeoutError ` ` exception is raised .
: param ignore_queue : Only used when a message queue is configured . If
set to ` ` True ` ` , the event is emitted to the
client 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 not thread safe . If multiple threads are emitting
at the same time to the same client , then messages composed of
multiple packets may end up being sent in an incorrect sequence . Use
standard concurrency solutions ( such as a Lock object ) to prevent this
situation .
"""
if to is None and sid is None :
raise ValueError ( ' Cannot use call() to broadcast. ' )
if not self . async_handlers :
raise RuntimeError (
' Cannot use call() when async_handlers is False. ' )
callback_event = self . eio . create_event ( )
callback_args = [ ]
def event_callback ( * args ) :
callback_args . append ( args )
callback_event . set ( )
self . emit ( event , data = data , room = to or sid , namespace = namespace ,
callback = event_callback , * * kwargs )
if not callback_event . wait ( timeout = timeout ) :
raise exceptions . TimeoutError ( )
return callback_args [ 0 ] if len ( callback_args [ 0 ] ) > 1 \
else callback_args [ 0 ] [ 0 ] if len ( callback_args [ 0 ] ) == 1 \
else None
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 get_session ( self , sid , namespace = None ) :
""" Return the user session for a client.
: param sid : The session id of the client .
: param namespace : The Socket . IO namespace . If this argument is omitted
the default namespace is used .
The return value is a dictionary . Modifications made to this
dictionary are not guaranteed to be preserved unless
` ` save_session ( ) ` ` is called , or when the ` ` session ` ` context manager
is used .
"""
namespace = namespace or ' / '
eio_sid = self . manager . eio_sid_from_sid ( sid , namespace )
eio_session = self . eio . get_session ( eio_sid )
return eio_session . setdefault ( namespace , { } )
def save_session ( self , sid , session , namespace = None ) :
""" Store the user session for a client.
: param sid : The session id of the client .
: param session : The session dictionary .
: param namespace : The Socket . IO namespace . If this argument is omitted
the default namespace is used .
"""
namespace = namespace or ' / '
eio_sid = self . manager . eio_sid_from_sid ( sid , namespace )
eio_session = self . eio . get_session ( eio_sid )
eio_session [ namespace ] = session
def session ( self , sid , namespace = None ) :
""" Return the user session for a client with context manager syntax.
: param sid : The session id of the client .
This is a context manager that returns the user session dictionary for
the client . Any changes that are made to this dictionary inside the
context manager block are saved back to the session . Example usage : :
@sio.on ( ' connect ' )
def on_connect ( sid , environ ) :
username = authenticate_user ( environ )
if not username :
return False
with sio . session ( sid ) as session :
session [ ' username ' ] = username
@sio.on ( ' message ' )
def on_message ( sid , msg ) :
with sio . session ( sid ) as session :
print ( ' received message from ' , session [ ' username ' ] )
"""
class _session_context_manager ( object ) :
def __init__ ( self , server , sid , namespace ) :
self . server = server
self . sid = sid
self . namespace = namespace
self . session = None
def __enter__ ( self ) :
self . session = self . server . get_session ( sid ,
namespace = namespace )
return self . session
def __exit__ ( self , * args ) :
self . server . save_session ( sid , self . session ,
namespace = namespace )
return _session_context_manager ( self , sid , namespace )
def disconnect ( self , sid , namespace = None , ignore_queue = False ) :
""" 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 .
: param ignore_queue : Only used when a message queue is configured . If
set to ` ` True ` ` , the disconnect is processed
locally , without broadcasting on the queue . It is
recommended to always leave this parameter with
its default value of ` ` False ` ` .
"""
namespace = namespace or ' / '
if ignore_queue :
delete_it = self . manager . is_connected ( sid , namespace )
else :
delete_it = self . manager . can_disconnect ( sid , namespace )
if delete_it :
self . logger . info ( ' Disconnecting %s [ %s ] ' , sid , namespace )
eio_sid = self . manager . pre_disconnect ( sid , namespace = namespace )
self . _send_packet ( eio_sid , self . packet_class (
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 get_environ ( self , sid , namespace = None ) :
""" Return the WSGI environ dictionary for a client.
: param sid : The session of the client .
: param namespace : The Socket . IO namespace . If this argument is omitted
the default namespace is used .
"""
eio_sid = self . manager . eio_sid_from_sid ( sid , namespace or ' / ' )
return self . environ . get ( eio_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 that represents the background task ,
on which the ` ` join ( ) ` ` methond can be invoked to wait for the task to
complete .
"""
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 , eio_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 )
elif data is not None :
data = [ data ]
else :
data = [ ]
self . _send_packet ( eio_sid , self . packet_class (
packet . EVENT , namespace = namespace , data = [ event ] + data , id = id ) )
def _send_packet ( self , eio_sid , pkt ) :
""" Send a Socket.IO packet to a client. """
encoded_packet = pkt . encode ( )
if isinstance ( encoded_packet , list ) :
for ep in encoded_packet :
self . eio . send ( eio_sid , ep )
else :
self . eio . send ( eio_sid , encoded_packet )
def _handle_connect ( self , eio_sid , namespace , data ) :
""" Handle a client connection request. """
namespace = namespace or ' / '
sid = self . manager . connect ( eio_sid , namespace )
if sid is None :
self . _send_packet ( eio_sid , self . packet_class (
packet . CONNECT_ERROR , data = ' Unable to connect ' ,
namespace = namespace ) )
return
if self . always_connect :
self . _send_packet ( eio_sid , self . packet_class (
packet . CONNECT , { ' sid ' : sid } , namespace = namespace ) )
fail_reason = exceptions . ConnectionRefusedError ( ) . error_args
try :
if data :
success = self . _trigger_event (
' connect ' , namespace , sid , self . environ [ eio_sid ] , data )
else :
try :
success = self . _trigger_event (
' connect ' , namespace , sid , self . environ [ eio_sid ] )
except TypeError :
success = self . _trigger_event (
' connect ' , namespace , sid , self . environ [ eio_sid ] , None )
except exceptions . ConnectionRefusedError as exc :
fail_reason = exc . error_args
success = False
if success is False :
if self . always_connect :
self . manager . pre_disconnect ( sid , namespace )
self . _send_packet ( eio_sid , self . packet_class (
packet . DISCONNECT , data = fail_reason , namespace = namespace ) )
else :
self . _send_packet ( eio_sid , self . packet_class (
packet . CONNECT_ERROR , data = fail_reason ,
namespace = namespace ) )
self . manager . disconnect ( sid , namespace )
elif not self . always_connect :
self . _send_packet ( eio_sid , self . packet_class (
packet . CONNECT , { ' sid ' : sid } , namespace = namespace ) )
def _handle_disconnect ( self , eio_sid , namespace ) :
""" Handle a client disconnect. """
namespace = namespace or ' / '
sid = self . manager . sid_from_eio_sid ( eio_sid , namespace )
if not self . manager . is_connected ( sid , namespace ) : # pragma: no cover
return
self . manager . pre_disconnect ( sid , namespace = namespace )
self . _trigger_event ( ' disconnect ' , namespace , sid )
self . manager . disconnect ( sid , namespace )
def _handle_event ( self , eio_sid , namespace , id , data ) :
""" Handle an incoming client event. """
namespace = namespace or ' / '
sid = self . manager . sid_from_eio_sid ( eio_sid , namespace )
self . logger . info ( ' received event " %s " from %s [ %s ] ' , data [ 0 ] , sid ,
namespace )
if not self . manager . is_connected ( sid , namespace ) :
self . logger . warning ( ' %s is not connected to namespace %s ' ,
sid , namespace )
return
if self . async_handlers :
self . start_background_task ( self . _handle_event_internal , self , sid ,
eio_sid , data , namespace , id )
else :
self . _handle_event_internal ( self , sid , eio_sid , data , namespace ,
id )
def _handle_event_internal ( self , server , sid , eio_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 ]
server . _send_packet ( eio_sid , self . packet_class (
packet . ACK , namespace = namespace , id = id , data = data ) )
def _handle_ack ( self , eio_sid , namespace , id , data ) :
""" Handle ACK packets from the client. """
namespace = namespace or ' / '
sid = self . manager . sid_from_eio_sid ( eio_sid , namespace )
self . logger . info ( ' received ack from %s [ %s ] ' , sid , namespace )
self . manager . trigger_callback ( sid , 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 :
if event in self . handlers [ namespace ] :
return self . handlers [ namespace ] [ event ] ( * args )
elif event not in self . reserved_events and \
' * ' in self . handlers [ namespace ] :
return self . handlers [ namespace ] [ ' * ' ] ( event , * args )
# or else, forward the event to a namespace handler if one exists
elif namespace in self . namespace_handlers :
return self . namespace_handlers [ namespace ] . trigger_event (
event , * args )
def _handle_eio_connect ( self , eio_sid , environ ) :
""" Handle the Engine.IO connection event. """
if not self . manager_initialized :
self . manager_initialized = True
self . manager . initialize ( )
self . environ [ eio_sid ] = environ
def _handle_eio_message ( self , eio_sid , data ) :
""" Dispatch Engine.IO messages. """
if eio_sid in self . _binary_packet :
pkt = self . _binary_packet [ eio_sid ]
if pkt . add_attachment ( data ) :
del self . _binary_packet [ eio_sid ]
if pkt . packet_type == packet . BINARY_EVENT :
self . _handle_event ( eio_sid , pkt . namespace , pkt . id ,
pkt . data )
else :
self . _handle_ack ( eio_sid , pkt . namespace , pkt . id , pkt . data )
else :
pkt = self . packet_class ( encoded_packet = data )
if pkt . packet_type == packet . CONNECT :
self . _handle_connect ( eio_sid , pkt . namespace , pkt . data )
elif pkt . packet_type == packet . DISCONNECT :
self . _handle_disconnect ( eio_sid , pkt . namespace )
elif pkt . packet_type == packet . EVENT :
self . _handle_event ( eio_sid , pkt . namespace , pkt . id , pkt . data )
elif pkt . packet_type == packet . ACK :
self . _handle_ack ( eio_sid , pkt . namespace , pkt . id , pkt . data )
elif pkt . packet_type == packet . BINARY_EVENT or \
pkt . packet_type == packet . BINARY_ACK :
self . _binary_packet [ eio_sid ] = pkt
elif pkt . packet_type == packet . CONNECT_ERROR :
raise ValueError ( ' Unexpected CONNECT_ERROR packet. ' )
else :
raise ValueError ( ' Unknown packet type. ' )
def _handle_eio_disconnect ( self , eio_sid ) :
""" Handle Engine.IO disconnect event. """
for n in list ( self . manager . get_namespaces ( ) ) . copy ( ) :
self . _handle_disconnect ( eio_sid , n )
if eio_sid in self . environ :
del self . environ [ eio_sid ]
def _engineio_server_class ( self ) :
return engineio . Server