Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.

728 lines
33KB

  1. import logging
  2. import engineio
  3. import six
  4. from . import base_manager
  5. from . import exceptions
  6. from . import namespace
  7. from . import packet
  8. default_logger = logging.getLogger('socketio.server')
  9. class Server(object):
  10. """A Socket.IO server.
  11. This class implements a fully compliant Socket.IO web server with support
  12. for websocket and long-polling transports.
  13. :param client_manager: The client manager instance that will manage the
  14. client list. When this is omitted, the client list
  15. is stored in an in-memory structure, so the use of
  16. multiple connected servers is not possible.
  17. :param logger: To enable logging set to ``True`` or pass a logger object to
  18. use. To disable logging set to ``False``. The default is
  19. ``False``.
  20. :param binary: ``True`` to support binary payloads, ``False`` to treat all
  21. payloads as text. On Python 2, if this is set to ``True``,
  22. ``unicode`` values are treated as text, and ``str`` and
  23. ``bytes`` values are treated as binary. This option has no
  24. effect on Python 3, where text and binary payloads are
  25. always automatically discovered.
  26. :param json: An alternative json module to use for encoding and decoding
  27. packets. Custom json modules must have ``dumps`` and ``loads``
  28. functions that are compatible with the standard library
  29. versions.
  30. :param async_handlers: If set to ``True``, event handlers for a client are
  31. executed in separate threads. To run handlers for a
  32. client synchronously, set to ``False``. The default
  33. is ``True``.
  34. :param always_connect: When set to ``False``, new connections are
  35. provisory until the connect handler returns
  36. something other than ``False``, at which point they
  37. are accepted. When set to ``True``, connections are
  38. immediately accepted, and then if the connect
  39. handler returns ``False`` a disconnect is issued.
  40. Set to ``True`` if you need to emit events from the
  41. connect handler and your client is confused when it
  42. receives events before the connection acceptance.
  43. In any other case use the default of ``False``.
  44. :param kwargs: Connection parameters for the underlying Engine.IO server.
  45. The Engine.IO configuration supports the following settings:
  46. :param async_mode: The asynchronous model to use. See the Deployment
  47. section in the documentation for a description of the
  48. available options. Valid async modes are "threading",
  49. "eventlet", "gevent" and "gevent_uwsgi". If this
  50. argument is not given, "eventlet" is tried first, then
  51. "gevent_uwsgi", then "gevent", and finally "threading".
  52. The first async mode that has all its dependencies
  53. installed is then one that is chosen.
  54. :param ping_timeout: The time in seconds that the client waits for the
  55. server to respond before disconnecting. The default
  56. is 60 seconds.
  57. :param ping_interval: The interval in seconds at which the client pings
  58. the server. The default is 25 seconds.
  59. :param max_http_buffer_size: The maximum size of a message when using the
  60. polling transport. The default is 100,000,000
  61. bytes.
  62. :param allow_upgrades: Whether to allow transport upgrades or not. The
  63. default is ``True``.
  64. :param http_compression: Whether to compress packages when using the
  65. polling transport. The default is ``True``.
  66. :param compression_threshold: Only compress messages when their byte size
  67. is greater than this value. The default is
  68. 1024 bytes.
  69. :param cookie: Name of the HTTP cookie that contains the client session
  70. id. If set to ``None``, a cookie is not sent to the client.
  71. The default is ``'io'``.
  72. :param cors_allowed_origins: Origin or list of origins that are allowed to
  73. connect to this server. Only the same origin
  74. is allowed by default. Set this argument to
  75. ``'*'`` to allow all origins, or to ``[]`` to
  76. disable CORS handling.
  77. :param cors_credentials: Whether credentials (cookies, authentication) are
  78. allowed in requests to this server. The default is
  79. ``True``.
  80. :param monitor_clients: If set to ``True``, a background task will ensure
  81. inactive clients are closed. Set to ``False`` to
  82. disable the monitoring task (not recommended). The
  83. default is ``True``.
  84. :param engineio_logger: To enable Engine.IO logging set to ``True`` or pass
  85. a logger object to use. To disable logging set to
  86. ``False``. The default is ``False``.
  87. """
  88. def __init__(self, client_manager=None, logger=False, binary=False,
  89. json=None, async_handlers=True, always_connect=False,
  90. **kwargs):
  91. engineio_options = kwargs
  92. engineio_logger = engineio_options.pop('engineio_logger', None)
  93. if engineio_logger is not None:
  94. engineio_options['logger'] = engineio_logger
  95. if json is not None:
  96. packet.Packet.json = json
  97. engineio_options['json'] = json
  98. engineio_options['async_handlers'] = False
  99. self.eio = self._engineio_server_class()(**engineio_options)
  100. self.eio.on('connect', self._handle_eio_connect)
  101. self.eio.on('message', self._handle_eio_message)
  102. self.eio.on('disconnect', self._handle_eio_disconnect)
  103. self.binary = binary
  104. self.environ = {}
  105. self.handlers = {}
  106. self.namespace_handlers = {}
  107. self._binary_packet = {}
  108. if not isinstance(logger, bool):
  109. self.logger = logger
  110. else:
  111. self.logger = default_logger
  112. if not logging.root.handlers and \
  113. self.logger.level == logging.NOTSET:
  114. if logger:
  115. self.logger.setLevel(logging.INFO)
  116. else:
  117. self.logger.setLevel(logging.ERROR)
  118. self.logger.addHandler(logging.StreamHandler())
  119. if client_manager is None:
  120. client_manager = base_manager.BaseManager()
  121. self.manager = client_manager
  122. self.manager.set_server(self)
  123. self.manager_initialized = False
  124. self.async_handlers = async_handlers
  125. self.always_connect = always_connect
  126. self.async_mode = self.eio.async_mode
  127. def is_asyncio_based(self):
  128. return False
  129. def on(self, event, handler=None, namespace=None):
  130. """Register an event handler.
  131. :param event: The event name. It can be any string. The event names
  132. ``'connect'``, ``'message'`` and ``'disconnect'`` are
  133. reserved and should not be used.
  134. :param handler: The function that should be invoked to handle the
  135. event. When this parameter is not given, the method
  136. acts as a decorator for the handler function.
  137. :param namespace: The Socket.IO namespace for the event. If this
  138. argument is omitted the handler is associated with
  139. the default namespace.
  140. Example usage::
  141. # as a decorator:
  142. @socket_io.on('connect', namespace='/chat')
  143. def connect_handler(sid, environ):
  144. print('Connection request')
  145. if environ['REMOTE_ADDR'] in blacklisted:
  146. return False # reject
  147. # as a method:
  148. def message_handler(sid, msg):
  149. print('Received message: ', msg)
  150. eio.send(sid, 'response')
  151. socket_io.on('message', namespace='/chat', message_handler)
  152. The handler function receives the ``sid`` (session ID) for the
  153. client as first argument. The ``'connect'`` event handler receives the
  154. WSGI environment as a second argument, and can return ``False`` to
  155. reject the connection. The ``'message'`` handler and handlers for
  156. custom event names receive the message payload as a second argument.
  157. Any values returned from a message handler will be passed to the
  158. client's acknowledgement callback function if it exists. The
  159. ``'disconnect'`` handler does not take a second argument.
  160. """
  161. namespace = namespace or '/'
  162. def set_handler(handler):
  163. if namespace not in self.handlers:
  164. self.handlers[namespace] = {}
  165. self.handlers[namespace][event] = handler
  166. return handler
  167. if handler is None:
  168. return set_handler
  169. set_handler(handler)
  170. def event(self, *args, **kwargs):
  171. """Decorator to register an event handler.
  172. This is a simplified version of the ``on()`` method that takes the
  173. event name from the decorated function.
  174. Example usage::
  175. @sio.event
  176. def my_event(data):
  177. print('Received data: ', data)
  178. The above example is equivalent to::
  179. @sio.on('my_event')
  180. def my_event(data):
  181. print('Received data: ', data)
  182. A custom namespace can be given as an argument to the decorator::
  183. @sio.event(namespace='/test')
  184. def my_event(data):
  185. print('Received data: ', data)
  186. """
  187. if len(args) == 1 and len(kwargs) == 0 and callable(args[0]):
  188. # the decorator was invoked without arguments
  189. # args[0] is the decorated function
  190. return self.on(args[0].__name__)(args[0])
  191. else:
  192. # the decorator was invoked with arguments
  193. def set_handler(handler):
  194. return self.on(handler.__name__, *args, **kwargs)(handler)
  195. return set_handler
  196. def register_namespace(self, namespace_handler):
  197. """Register a namespace handler object.
  198. :param namespace_handler: An instance of a :class:`Namespace`
  199. subclass that handles all the event traffic
  200. for a namespace.
  201. """
  202. if not isinstance(namespace_handler, namespace.Namespace):
  203. raise ValueError('Not a namespace instance')
  204. if self.is_asyncio_based() != namespace_handler.is_asyncio_based():
  205. raise ValueError('Not a valid namespace class for this server')
  206. namespace_handler._set_server(self)
  207. self.namespace_handlers[namespace_handler.namespace] = \
  208. namespace_handler
  209. def emit(self, event, data=None, to=None, room=None, skip_sid=None,
  210. namespace=None, callback=None, **kwargs):
  211. """Emit a custom event to one or more connected clients.
  212. :param event: The event name. It can be any string. The event names
  213. ``'connect'``, ``'message'`` and ``'disconnect'`` are
  214. reserved and should not be used.
  215. :param data: The data to send to the client or clients. Data can be of
  216. type ``str``, ``bytes``, ``list`` or ``dict``. If a
  217. ``list`` or ``dict``, the data will be serialized as JSON.
  218. :param to: The recipient of the message. This can be set to the
  219. session ID of a client to address only that client, or to
  220. to any custom room created by the application to address all
  221. the clients in that room, If this argument is omitted the
  222. event is broadcasted to all connected clients.
  223. :param room: Alias for the ``to`` parameter.
  224. :param skip_sid: The session ID of a client to skip when broadcasting
  225. to a room or to all clients. This can be used to
  226. prevent a message from being sent to the sender. To
  227. skip multiple sids, pass a list.
  228. :param namespace: The Socket.IO namespace for the event. If this
  229. argument is omitted the event is emitted to the
  230. default namespace.
  231. :param callback: If given, this function will be called to acknowledge
  232. the the client has received the message. The arguments
  233. that will be passed to the function are those provided
  234. by the client. Callback functions can only be used
  235. when addressing an individual client.
  236. :param ignore_queue: Only used when a message queue is configured. If
  237. set to ``True``, the event is emitted to the
  238. clients directly, without going through the queue.
  239. This is more efficient, but only works when a
  240. single server process is used. It is recommended
  241. to always leave this parameter with its default
  242. value of ``False``.
  243. """
  244. namespace = namespace or '/'
  245. room = to or room
  246. self.logger.info('emitting event "%s" to %s [%s]', event,
  247. room or 'all', namespace)
  248. self.manager.emit(event, data, namespace, room=room,
  249. skip_sid=skip_sid, callback=callback, **kwargs)
  250. def send(self, data, to=None, room=None, skip_sid=None, namespace=None,
  251. callback=None, **kwargs):
  252. """Send a message to one or more connected clients.
  253. This function emits an event with the name ``'message'``. Use
  254. :func:`emit` to issue custom event names.
  255. :param data: The data to send to the client or clients. Data can be of
  256. type ``str``, ``bytes``, ``list`` or ``dict``. If a
  257. ``list`` or ``dict``, the data will be serialized as JSON.
  258. :param to: The recipient of the message. This can be set to the
  259. session ID of a client to address only that client, or to
  260. to any custom room created by the application to address all
  261. the clients in that room, If this argument is omitted the
  262. event is broadcasted to all connected clients.
  263. :param room: Alias for the ``to`` parameter.
  264. :param skip_sid: The session ID of a client to skip when broadcasting
  265. to a room or to all clients. This can be used to
  266. prevent a message from being sent to the sender. To
  267. skip multiple sids, pass a list.
  268. :param namespace: The Socket.IO namespace for the event. If this
  269. argument is omitted the event is emitted to the
  270. default namespace.
  271. :param callback: If given, this function will be called to acknowledge
  272. the the client has received the message. The arguments
  273. that will be passed to the function are those provided
  274. by the client. Callback functions can only be used
  275. when addressing an individual client.
  276. :param ignore_queue: Only used when a message queue is configured. If
  277. set to ``True``, the event is emitted to the
  278. clients directly, without going through the queue.
  279. This is more efficient, but only works when a
  280. single server process is used. It is recommended
  281. to always leave this parameter with its default
  282. value of ``False``.
  283. """
  284. self.emit('message', data=data, to=to, room=room, skip_sid=skip_sid,
  285. namespace=namespace, callback=callback, **kwargs)
  286. def call(self, event, data=None, to=None, sid=None, namespace=None,
  287. timeout=60, **kwargs):
  288. """Emit a custom event to a client and wait for the response.
  289. :param event: The event name. It can be any string. The event names
  290. ``'connect'``, ``'message'`` and ``'disconnect'`` are
  291. reserved and should not be used.
  292. :param data: The data to send to the client or clients. Data can be of
  293. type ``str``, ``bytes``, ``list`` or ``dict``. If a
  294. ``list`` or ``dict``, the data will be serialized as JSON.
  295. :param to: The session ID of the recipient client.
  296. :param sid: Alias for the ``to`` parameter.
  297. :param namespace: The Socket.IO namespace for the event. If this
  298. argument is omitted the event is emitted to the
  299. default namespace.
  300. :param timeout: The waiting timeout. If the timeout is reached before
  301. the client acknowledges the event, then a
  302. ``TimeoutError`` exception is raised.
  303. :param ignore_queue: Only used when a message queue is configured. If
  304. set to ``True``, the event is emitted to the
  305. client directly, without going through the queue.
  306. This is more efficient, but only works when a
  307. single server process is used. It is recommended
  308. to always leave this parameter with its default
  309. value of ``False``.
  310. """
  311. if not self.async_handlers:
  312. raise RuntimeError(
  313. 'Cannot use call() when async_handlers is False.')
  314. callback_event = self.eio.create_event()
  315. callback_args = []
  316. def event_callback(*args):
  317. callback_args.append(args)
  318. callback_event.set()
  319. self.emit(event, data=data, room=to or sid, namespace=namespace,
  320. callback=event_callback, **kwargs)
  321. if not callback_event.wait(timeout=timeout):
  322. raise exceptions.TimeoutError()
  323. return callback_args[0] if len(callback_args[0]) > 1 \
  324. else callback_args[0][0] if len(callback_args[0]) == 1 \
  325. else None
  326. def enter_room(self, sid, room, namespace=None):
  327. """Enter a room.
  328. This function adds the client to a room. The :func:`emit` and
  329. :func:`send` functions can optionally broadcast events to all the
  330. clients in a room.
  331. :param sid: Session ID of the client.
  332. :param room: Room name. If the room does not exist it is created.
  333. :param namespace: The Socket.IO namespace for the event. If this
  334. argument is omitted the default namespace is used.
  335. """
  336. namespace = namespace or '/'
  337. self.logger.info('%s is entering room %s [%s]', sid, room, namespace)
  338. self.manager.enter_room(sid, namespace, room)
  339. def leave_room(self, sid, room, namespace=None):
  340. """Leave a room.
  341. This function removes the client from a room.
  342. :param sid: Session ID of the client.
  343. :param room: Room name.
  344. :param namespace: The Socket.IO namespace for the event. If this
  345. argument is omitted the default namespace is used.
  346. """
  347. namespace = namespace or '/'
  348. self.logger.info('%s is leaving room %s [%s]', sid, room, namespace)
  349. self.manager.leave_room(sid, namespace, room)
  350. def close_room(self, room, namespace=None):
  351. """Close a room.
  352. This function removes all the clients from the given room.
  353. :param room: Room name.
  354. :param namespace: The Socket.IO namespace for the event. If this
  355. argument is omitted the default namespace is used.
  356. """
  357. namespace = namespace or '/'
  358. self.logger.info('room %s is closing [%s]', room, namespace)
  359. self.manager.close_room(room, namespace)
  360. def rooms(self, sid, namespace=None):
  361. """Return the rooms a client is in.
  362. :param sid: Session ID of the client.
  363. :param namespace: The Socket.IO namespace for the event. If this
  364. argument is omitted the default namespace is used.
  365. """
  366. namespace = namespace or '/'
  367. return self.manager.get_rooms(sid, namespace)
  368. def get_session(self, sid, namespace=None):
  369. """Return the user session for a client.
  370. :param sid: The session id of the client.
  371. :param namespace: The Socket.IO namespace. If this argument is omitted
  372. the default namespace is used.
  373. The return value is a dictionary. Modifications made to this
  374. dictionary are not guaranteed to be preserved unless
  375. ``save_session()`` is called, or when the ``session`` context manager
  376. is used.
  377. """
  378. namespace = namespace or '/'
  379. eio_session = self.eio.get_session(sid)
  380. return eio_session.setdefault(namespace, {})
  381. def save_session(self, sid, session, namespace=None):
  382. """Store the user session for a client.
  383. :param sid: The session id of the client.
  384. :param session: The session dictionary.
  385. :param namespace: The Socket.IO namespace. If this argument is omitted
  386. the default namespace is used.
  387. """
  388. namespace = namespace or '/'
  389. eio_session = self.eio.get_session(sid)
  390. eio_session[namespace] = session
  391. def session(self, sid, namespace=None):
  392. """Return the user session for a client with context manager syntax.
  393. :param sid: The session id of the client.
  394. This is a context manager that returns the user session dictionary for
  395. the client. Any changes that are made to this dictionary inside the
  396. context manager block are saved back to the session. Example usage::
  397. @sio.on('connect')
  398. def on_connect(sid, environ):
  399. username = authenticate_user(environ)
  400. if not username:
  401. return False
  402. with sio.session(sid) as session:
  403. session['username'] = username
  404. @sio.on('message')
  405. def on_message(sid, msg):
  406. with sio.session(sid) as session:
  407. print('received message from ', session['username'])
  408. """
  409. class _session_context_manager(object):
  410. def __init__(self, server, sid, namespace):
  411. self.server = server
  412. self.sid = sid
  413. self.namespace = namespace
  414. self.session = None
  415. def __enter__(self):
  416. self.session = self.server.get_session(sid,
  417. namespace=namespace)
  418. return self.session
  419. def __exit__(self, *args):
  420. self.server.save_session(sid, self.session,
  421. namespace=namespace)
  422. return _session_context_manager(self, sid, namespace)
  423. def disconnect(self, sid, namespace=None):
  424. """Disconnect a client.
  425. :param sid: Session ID of the client.
  426. :param namespace: The Socket.IO namespace to disconnect. If this
  427. argument is omitted the default namespace is used.
  428. """
  429. namespace = namespace or '/'
  430. if self.manager.is_connected(sid, namespace=namespace):
  431. self.logger.info('Disconnecting %s [%s]', sid, namespace)
  432. self.manager.pre_disconnect(sid, namespace=namespace)
  433. self._send_packet(sid, packet.Packet(packet.DISCONNECT,
  434. namespace=namespace))
  435. self._trigger_event('disconnect', namespace, sid)
  436. self.manager.disconnect(sid, namespace=namespace)
  437. if namespace == '/':
  438. self.eio.disconnect(sid)
  439. def transport(self, sid):
  440. """Return the name of the transport used by the client.
  441. The two possible values returned by this function are ``'polling'``
  442. and ``'websocket'``.
  443. :param sid: The session of the client.
  444. """
  445. return self.eio.transport(sid)
  446. def handle_request(self, environ, start_response):
  447. """Handle an HTTP request from the client.
  448. This is the entry point of the Socket.IO application, using the same
  449. interface as a WSGI application. For the typical usage, this function
  450. is invoked by the :class:`Middleware` instance, but it can be invoked
  451. directly when the middleware is not used.
  452. :param environ: The WSGI environment.
  453. :param start_response: The WSGI ``start_response`` function.
  454. This function returns the HTTP response body to deliver to the client
  455. as a byte sequence.
  456. """
  457. return self.eio.handle_request(environ, start_response)
  458. def start_background_task(self, target, *args, **kwargs):
  459. """Start a background task using the appropriate async model.
  460. This is a utility function that applications can use to start a
  461. background task using the method that is compatible with the
  462. selected async mode.
  463. :param target: the target function to execute.
  464. :param args: arguments to pass to the function.
  465. :param kwargs: keyword arguments to pass to the function.
  466. This function returns an object compatible with the `Thread` class in
  467. the Python standard library. The `start()` method on this object is
  468. already called by this function.
  469. """
  470. return self.eio.start_background_task(target, *args, **kwargs)
  471. def sleep(self, seconds=0):
  472. """Sleep for the requested amount of time using the appropriate async
  473. model.
  474. This is a utility function that applications can use to put a task to
  475. sleep without having to worry about using the correct call for the
  476. selected async mode.
  477. """
  478. return self.eio.sleep(seconds)
  479. def _emit_internal(self, sid, event, data, namespace=None, id=None):
  480. """Send a message to a client."""
  481. if six.PY2 and not self.binary:
  482. binary = False # pragma: nocover
  483. else:
  484. binary = None
  485. # tuples are expanded to multiple arguments, everything else is sent
  486. # as a single argument
  487. if isinstance(data, tuple):
  488. data = list(data)
  489. else:
  490. data = [data]
  491. self._send_packet(sid, packet.Packet(packet.EVENT, namespace=namespace,
  492. data=[event] + data, id=id,
  493. binary=binary))
  494. def _send_packet(self, sid, pkt):
  495. """Send a Socket.IO packet to a client."""
  496. encoded_packet = pkt.encode()
  497. if isinstance(encoded_packet, list):
  498. binary = False
  499. for ep in encoded_packet:
  500. self.eio.send(sid, ep, binary=binary)
  501. binary = True
  502. else:
  503. self.eio.send(sid, encoded_packet, binary=False)
  504. def _handle_connect(self, sid, namespace):
  505. """Handle a client connection request."""
  506. namespace = namespace or '/'
  507. self.manager.connect(sid, namespace)
  508. if self.always_connect:
  509. self._send_packet(sid, packet.Packet(packet.CONNECT,
  510. namespace=namespace))
  511. fail_reason = None
  512. try:
  513. success = self._trigger_event('connect', namespace, sid,
  514. self.environ[sid])
  515. except exceptions.ConnectionRefusedError as exc:
  516. fail_reason = exc.error_args
  517. success = False
  518. if success is False:
  519. if self.always_connect:
  520. self.manager.pre_disconnect(sid, namespace)
  521. self._send_packet(sid, packet.Packet(
  522. packet.DISCONNECT, data=fail_reason, namespace=namespace))
  523. self.manager.disconnect(sid, namespace)
  524. if not self.always_connect:
  525. self._send_packet(sid, packet.Packet(
  526. packet.ERROR, data=fail_reason, namespace=namespace))
  527. if sid in self.environ: # pragma: no cover
  528. del self.environ[sid]
  529. return False
  530. elif not self.always_connect:
  531. self._send_packet(sid, packet.Packet(packet.CONNECT,
  532. namespace=namespace))
  533. def _handle_disconnect(self, sid, namespace):
  534. """Handle a client disconnect."""
  535. namespace = namespace or '/'
  536. if namespace == '/':
  537. namespace_list = list(self.manager.get_namespaces())
  538. else:
  539. namespace_list = [namespace]
  540. for n in namespace_list:
  541. if n != '/' and self.manager.is_connected(sid, n):
  542. self._trigger_event('disconnect', n, sid)
  543. self.manager.disconnect(sid, n)
  544. if namespace == '/' and self.manager.is_connected(sid, namespace):
  545. self._trigger_event('disconnect', '/', sid)
  546. self.manager.disconnect(sid, '/')
  547. def _handle_event(self, sid, namespace, id, data):
  548. """Handle an incoming client event."""
  549. namespace = namespace or '/'
  550. self.logger.info('received event "%s" from %s [%s]', data[0], sid,
  551. namespace)
  552. if self.async_handlers:
  553. self.start_background_task(self._handle_event_internal, self, sid,
  554. data, namespace, id)
  555. else:
  556. self._handle_event_internal(self, sid, data, namespace, id)
  557. def _handle_event_internal(self, server, sid, data, namespace, id):
  558. r = server._trigger_event(data[0], namespace, sid, *data[1:])
  559. if id is not None:
  560. # send ACK packet with the response returned by the handler
  561. # tuples are expanded as multiple arguments
  562. if r is None:
  563. data = []
  564. elif isinstance(r, tuple):
  565. data = list(r)
  566. else:
  567. data = [r]
  568. if six.PY2 and not self.binary:
  569. binary = False # pragma: nocover
  570. else:
  571. binary = None
  572. server._send_packet(sid, packet.Packet(packet.ACK,
  573. namespace=namespace,
  574. id=id, data=data,
  575. binary=binary))
  576. def _handle_ack(self, sid, namespace, id, data):
  577. """Handle ACK packets from the client."""
  578. namespace = namespace or '/'
  579. self.logger.info('received ack from %s [%s]', sid, namespace)
  580. self.manager.trigger_callback(sid, namespace, id, data)
  581. def _trigger_event(self, event, namespace, *args):
  582. """Invoke an application event handler."""
  583. # first see if we have an explicit handler for the event
  584. if namespace in self.handlers and event in self.handlers[namespace]:
  585. return self.handlers[namespace][event](*args)
  586. # or else, forward the event to a namespace handler if one exists
  587. elif namespace in self.namespace_handlers:
  588. return self.namespace_handlers[namespace].trigger_event(
  589. event, *args)
  590. def _handle_eio_connect(self, sid, environ):
  591. """Handle the Engine.IO connection event."""
  592. if not self.manager_initialized:
  593. self.manager_initialized = True
  594. self.manager.initialize()
  595. self.environ[sid] = environ
  596. return self._handle_connect(sid, '/')
  597. def _handle_eio_message(self, sid, data):
  598. """Dispatch Engine.IO messages."""
  599. if sid in self._binary_packet:
  600. pkt = self._binary_packet[sid]
  601. if pkt.add_attachment(data):
  602. del self._binary_packet[sid]
  603. if pkt.packet_type == packet.BINARY_EVENT:
  604. self._handle_event(sid, pkt.namespace, pkt.id, pkt.data)
  605. else:
  606. self._handle_ack(sid, pkt.namespace, pkt.id, pkt.data)
  607. else:
  608. pkt = packet.Packet(encoded_packet=data)
  609. if pkt.packet_type == packet.CONNECT:
  610. self._handle_connect(sid, pkt.namespace)
  611. elif pkt.packet_type == packet.DISCONNECT:
  612. self._handle_disconnect(sid, pkt.namespace)
  613. elif pkt.packet_type == packet.EVENT:
  614. self._handle_event(sid, pkt.namespace, pkt.id, pkt.data)
  615. elif pkt.packet_type == packet.ACK:
  616. self._handle_ack(sid, pkt.namespace, pkt.id, pkt.data)
  617. elif pkt.packet_type == packet.BINARY_EVENT or \
  618. pkt.packet_type == packet.BINARY_ACK:
  619. self._binary_packet[sid] = pkt
  620. elif pkt.packet_type == packet.ERROR:
  621. raise ValueError('Unexpected ERROR packet.')
  622. else:
  623. raise ValueError('Unknown packet type.')
  624. def _handle_eio_disconnect(self, sid):
  625. """Handle Engine.IO disconnect event."""
  626. self._handle_disconnect(sid, '/')
  627. if sid in self.environ:
  628. del self.environ[sid]
  629. def _engineio_server_class(self):
  630. return engineio.Server