No puede seleccionar más de 25 temas Los temas deben comenzar con una letra o número, pueden incluir guiones ('-') y pueden tener hasta 35 caracteres de largo.

155 líneas
6.2KB

  1. from functools import partial
  2. import uuid
  3. import json
  4. import pickle
  5. import six
  6. from .base_manager import BaseManager
  7. class PubSubManager(BaseManager):
  8. """Manage a client list attached to a pub/sub backend.
  9. This is a base class that enables multiple servers to share the list of
  10. clients, with the servers communicating events through a pub/sub backend.
  11. The use of a pub/sub backend also allows any client connected to the
  12. backend to emit events addressed to Socket.IO clients.
  13. The actual backends must be implemented by subclasses, this class only
  14. provides a pub/sub generic framework.
  15. :param channel: The channel name on which the server sends and receives
  16. notifications.
  17. """
  18. name = 'pubsub'
  19. def __init__(self, channel='socketio', write_only=False, logger=None):
  20. super(PubSubManager, self).__init__()
  21. self.channel = channel
  22. self.write_only = write_only
  23. self.host_id = uuid.uuid4().hex
  24. self.logger = logger
  25. def initialize(self):
  26. super(PubSubManager, self).initialize()
  27. if not self.write_only:
  28. self.thread = self.server.start_background_task(self._thread)
  29. self._get_logger().info(self.name + ' backend initialized.')
  30. def emit(self, event, data, namespace=None, room=None, skip_sid=None,
  31. callback=None, **kwargs):
  32. """Emit a message to a single client, a room, or all the clients
  33. connected to the namespace.
  34. This method takes care or propagating the message to all the servers
  35. that are connected through the message queue.
  36. The parameters are the same as in :meth:`.Server.emit`.
  37. """
  38. if kwargs.get('ignore_queue'):
  39. return super(PubSubManager, self).emit(
  40. event, data, namespace=namespace, room=room, skip_sid=skip_sid,
  41. callback=callback)
  42. namespace = namespace or '/'
  43. if callback is not None:
  44. if self.server is None:
  45. raise RuntimeError('Callbacks can only be issued from the '
  46. 'context of a server.')
  47. if room is None:
  48. raise ValueError('Cannot use callback without a room set.')
  49. id = self._generate_ack_id(room, namespace, callback)
  50. callback = (room, namespace, id)
  51. else:
  52. callback = None
  53. self._publish({'method': 'emit', 'event': event, 'data': data,
  54. 'namespace': namespace, 'room': room,
  55. 'skip_sid': skip_sid, 'callback': callback,
  56. 'host_id': self.host_id})
  57. def close_room(self, room, namespace=None):
  58. self._publish({'method': 'close_room', 'room': room,
  59. 'namespace': namespace or '/'})
  60. def _publish(self, data):
  61. """Publish a message on the Socket.IO channel.
  62. This method needs to be implemented by the different subclasses that
  63. support pub/sub backends.
  64. """
  65. raise NotImplementedError('This method must be implemented in a '
  66. 'subclass.') # pragma: no cover
  67. def _listen(self):
  68. """Return the next message published on the Socket.IO channel,
  69. blocking until a message is available.
  70. This method needs to be implemented by the different subclasses that
  71. support pub/sub backends.
  72. """
  73. raise NotImplementedError('This method must be implemented in a '
  74. 'subclass.') # pragma: no cover
  75. def _handle_emit(self, message):
  76. # Events with callbacks are very tricky to handle across hosts
  77. # Here in the receiving end we set up a local callback that preserves
  78. # the callback host and id from the sender
  79. remote_callback = message.get('callback')
  80. remote_host_id = message.get('host_id')
  81. if remote_callback is not None and len(remote_callback) == 3:
  82. callback = partial(self._return_callback, remote_host_id,
  83. *remote_callback)
  84. else:
  85. callback = None
  86. super(PubSubManager, self).emit(message['event'], message['data'],
  87. namespace=message.get('namespace'),
  88. room=message.get('room'),
  89. skip_sid=message.get('skip_sid'),
  90. callback=callback)
  91. def _handle_callback(self, message):
  92. if self.host_id == message.get('host_id'):
  93. try:
  94. sid = message['sid']
  95. namespace = message['namespace']
  96. id = message['id']
  97. args = message['args']
  98. except KeyError:
  99. return
  100. self.trigger_callback(sid, namespace, id, args)
  101. def _return_callback(self, host_id, sid, namespace, callback_id, *args):
  102. # When an event callback is received, the callback is returned back
  103. # the sender, which is identified by the host_id
  104. self._publish({'method': 'callback', 'host_id': host_id,
  105. 'sid': sid, 'namespace': namespace, 'id': callback_id,
  106. 'args': args})
  107. def _handle_close_room(self, message):
  108. super(PubSubManager, self).close_room(
  109. room=message.get('room'), namespace=message.get('namespace'))
  110. def _thread(self):
  111. for message in self._listen():
  112. data = None
  113. if isinstance(message, dict):
  114. data = message
  115. else:
  116. if isinstance(message, six.binary_type): # pragma: no cover
  117. try:
  118. data = pickle.loads(message)
  119. except:
  120. pass
  121. if data is None:
  122. try:
  123. data = json.loads(message)
  124. except:
  125. pass
  126. if data and 'method' in data:
  127. if data['method'] == 'emit':
  128. self._handle_emit(data)
  129. elif data['method'] == 'callback':
  130. self._handle_callback(data)
  131. elif data['method'] == 'close_room':
  132. self._handle_close_room(data)