Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

180 lignes
6.6KB

  1. import functools
  2. import json as _json
  3. import six
  4. (CONNECT, DISCONNECT, EVENT, ACK, ERROR, BINARY_EVENT, BINARY_ACK) = \
  5. (0, 1, 2, 3, 4, 5, 6)
  6. packet_names = ['CONNECT', 'DISCONNECT', 'EVENT', 'ACK', 'ERROR',
  7. 'BINARY_EVENT', 'BINARY_ACK']
  8. class Packet(object):
  9. """Socket.IO packet."""
  10. # the format of the Socket.IO packet is as follows:
  11. #
  12. # packet type: 1 byte, values 0-6
  13. # num_attachments: ASCII encoded, only if num_attachments != 0
  14. # '-': only if num_attachments != 0
  15. # namespace: only if namespace != '/'
  16. # ',': only if namespace and one of id and data are defined in this packet
  17. # id: ASCII encoded, only if id is not None
  18. # data: JSON dump of data payload
  19. json = _json
  20. def __init__(self, packet_type=EVENT, data=None, namespace=None, id=None,
  21. binary=None, encoded_packet=None):
  22. self.packet_type = packet_type
  23. self.data = data
  24. self.namespace = namespace
  25. self.id = id
  26. if binary or (binary is None and self._data_is_binary(self.data)):
  27. if self.packet_type == EVENT:
  28. self.packet_type = BINARY_EVENT
  29. elif self.packet_type == ACK:
  30. self.packet_type = BINARY_ACK
  31. else:
  32. raise ValueError('Packet does not support binary payload.')
  33. self.attachment_count = 0
  34. self.attachments = []
  35. if encoded_packet:
  36. self.attachment_count = self.decode(encoded_packet)
  37. def encode(self):
  38. """Encode the packet for transmission.
  39. If the packet contains binary elements, this function returns a list
  40. of packets where the first is the original packet with placeholders for
  41. the binary components and the remaining ones the binary attachments.
  42. """
  43. encoded_packet = six.text_type(self.packet_type)
  44. if self.packet_type == BINARY_EVENT or self.packet_type == BINARY_ACK:
  45. data, attachments = self._deconstruct_binary(self.data)
  46. encoded_packet += six.text_type(len(attachments)) + '-'
  47. else:
  48. data = self.data
  49. attachments = None
  50. needs_comma = False
  51. if self.namespace is not None and self.namespace != '/':
  52. encoded_packet += self.namespace
  53. needs_comma = True
  54. if self.id is not None:
  55. if needs_comma:
  56. encoded_packet += ','
  57. needs_comma = False
  58. encoded_packet += six.text_type(self.id)
  59. if data is not None:
  60. if needs_comma:
  61. encoded_packet += ','
  62. encoded_packet += self.json.dumps(data, separators=(',', ':'))
  63. if attachments is not None:
  64. encoded_packet = [encoded_packet] + attachments
  65. return encoded_packet
  66. def decode(self, encoded_packet):
  67. """Decode a transmitted package.
  68. The return value indicates how many binary attachment packets are
  69. necessary to fully decode the packet.
  70. """
  71. ep = encoded_packet
  72. try:
  73. self.packet_type = int(ep[0:1])
  74. except TypeError:
  75. self.packet_type = ep
  76. ep = ''
  77. self.namespace = None
  78. self.data = None
  79. ep = ep[1:]
  80. dash = ep.find('-')
  81. attachment_count = 0
  82. if dash > 0 and ep[0:dash].isdigit():
  83. attachment_count = int(ep[0:dash])
  84. ep = ep[dash + 1:]
  85. if ep and ep[0:1] == '/':
  86. sep = ep.find(',')
  87. if sep == -1:
  88. self.namespace = ep
  89. ep = ''
  90. else:
  91. self.namespace = ep[0:sep]
  92. ep = ep[sep + 1:]
  93. q = self.namespace.find('?')
  94. if q != -1:
  95. self.namespace = self.namespace[0:q]
  96. if ep and ep[0].isdigit():
  97. self.id = 0
  98. while ep and ep[0].isdigit():
  99. self.id = self.id * 10 + int(ep[0])
  100. ep = ep[1:]
  101. if ep:
  102. self.data = self.json.loads(ep)
  103. return attachment_count
  104. def add_attachment(self, attachment):
  105. if self.attachment_count <= len(self.attachments):
  106. raise ValueError('Unexpected binary attachment')
  107. self.attachments.append(attachment)
  108. if self.attachment_count == len(self.attachments):
  109. self.reconstruct_binary(self.attachments)
  110. return True
  111. return False
  112. def reconstruct_binary(self, attachments):
  113. """Reconstruct a decoded packet using the given list of binary
  114. attachments.
  115. """
  116. self.data = self._reconstruct_binary_internal(self.data,
  117. self.attachments)
  118. def _reconstruct_binary_internal(self, data, attachments):
  119. if isinstance(data, list):
  120. return [self._reconstruct_binary_internal(item, attachments)
  121. for item in data]
  122. elif isinstance(data, dict):
  123. if data.get('_placeholder') and 'num' in data:
  124. return attachments[data['num']]
  125. else:
  126. return {key: self._reconstruct_binary_internal(value,
  127. attachments)
  128. for key, value in six.iteritems(data)}
  129. else:
  130. return data
  131. def _deconstruct_binary(self, data):
  132. """Extract binary components in the packet."""
  133. attachments = []
  134. data = self._deconstruct_binary_internal(data, attachments)
  135. return data, attachments
  136. def _deconstruct_binary_internal(self, data, attachments):
  137. if isinstance(data, six.binary_type):
  138. attachments.append(data)
  139. return {'_placeholder': True, 'num': len(attachments) - 1}
  140. elif isinstance(data, list):
  141. return [self._deconstruct_binary_internal(item, attachments)
  142. for item in data]
  143. elif isinstance(data, dict):
  144. return {key: self._deconstruct_binary_internal(value, attachments)
  145. for key, value in six.iteritems(data)}
  146. else:
  147. return data
  148. def _data_is_binary(self, data):
  149. """Check if the data contains binary components."""
  150. if isinstance(data, six.binary_type):
  151. return True
  152. elif isinstance(data, list):
  153. return functools.reduce(
  154. lambda a, b: a or b, [self._data_is_binary(item)
  155. for item in data], False)
  156. elif isinstance(data, dict):
  157. return functools.reduce(
  158. lambda a, b: a or b, [self._data_is_binary(item)
  159. for item in six.itervalues(data)],
  160. False)
  161. else:
  162. return False