25'ten fazla konu seçemezsiniz Konular bir harf veya rakamla başlamalı, kısa çizgiler ('-') içerebilir ve en fazla 35 karakter uzunluğunda olabilir.

248 satır
9.5KB

  1. import six
  2. import sys
  3. import time
  4. from . import exceptions
  5. from . import packet
  6. from . import payload
  7. class Socket(object):
  8. """An Engine.IO socket."""
  9. upgrade_protocols = ['websocket']
  10. def __init__(self, server, sid):
  11. self.server = server
  12. self.sid = sid
  13. self.queue = self.server.create_queue()
  14. self.last_ping = time.time()
  15. self.connected = False
  16. self.upgrading = False
  17. self.upgraded = False
  18. self.packet_backlog = []
  19. self.closing = False
  20. self.closed = False
  21. self.session = {}
  22. def poll(self):
  23. """Wait for packets to send to the client."""
  24. queue_empty = self.server.get_queue_empty_exception()
  25. try:
  26. packets = [self.queue.get(timeout=self.server.ping_timeout)]
  27. self.queue.task_done()
  28. except queue_empty:
  29. raise exceptions.QueueEmpty()
  30. if packets == [None]:
  31. return []
  32. while True:
  33. try:
  34. packets.append(self.queue.get(block=False))
  35. self.queue.task_done()
  36. except queue_empty:
  37. break
  38. return packets
  39. def receive(self, pkt):
  40. """Receive packet from the client."""
  41. packet_name = packet.packet_names[pkt.packet_type] \
  42. if pkt.packet_type < len(packet.packet_names) else 'UNKNOWN'
  43. self.server.logger.info('%s: Received packet %s data %s',
  44. self.sid, packet_name,
  45. pkt.data if not isinstance(pkt.data, bytes)
  46. else '<binary>')
  47. if pkt.packet_type == packet.PING:
  48. self.last_ping = time.time()
  49. self.send(packet.Packet(packet.PONG, pkt.data))
  50. elif pkt.packet_type == packet.MESSAGE:
  51. self.server._trigger_event('message', self.sid, pkt.data,
  52. run_async=self.server.async_handlers)
  53. elif pkt.packet_type == packet.UPGRADE:
  54. self.send(packet.Packet(packet.NOOP))
  55. elif pkt.packet_type == packet.CLOSE:
  56. self.close(wait=False, abort=True)
  57. else:
  58. raise exceptions.UnknownPacketError()
  59. def check_ping_timeout(self):
  60. """Make sure the client is still sending pings.
  61. This helps detect disconnections for long-polling clients.
  62. """
  63. if self.closed:
  64. raise exceptions.SocketIsClosedError()
  65. if time.time() - self.last_ping > self.server.ping_interval + 5:
  66. self.server.logger.info('%s: Client is gone, closing socket',
  67. self.sid)
  68. # Passing abort=False here will cause close() to write a
  69. # CLOSE packet. This has the effect of updating half-open sockets
  70. # to their correct state of disconnected
  71. self.close(wait=False, abort=False)
  72. return False
  73. return True
  74. def send(self, pkt):
  75. """Send a packet to the client."""
  76. if not self.check_ping_timeout():
  77. return
  78. if self.upgrading:
  79. self.packet_backlog.append(pkt)
  80. else:
  81. self.queue.put(pkt)
  82. self.server.logger.info('%s: Sending packet %s data %s',
  83. self.sid, packet.packet_names[pkt.packet_type],
  84. pkt.data if not isinstance(pkt.data, bytes)
  85. else '<binary>')
  86. def handle_get_request(self, environ, start_response):
  87. """Handle a long-polling GET request from the client."""
  88. connections = [
  89. s.strip()
  90. for s in environ.get('HTTP_CONNECTION', '').lower().split(',')]
  91. transport = environ.get('HTTP_UPGRADE', '').lower()
  92. if 'upgrade' in connections and transport in self.upgrade_protocols:
  93. self.server.logger.info('%s: Received request to upgrade to %s',
  94. self.sid, transport)
  95. return getattr(self, '_upgrade_' + transport)(environ,
  96. start_response)
  97. try:
  98. packets = self.poll()
  99. except exceptions.QueueEmpty:
  100. exc = sys.exc_info()
  101. self.close(wait=False)
  102. six.reraise(*exc)
  103. return packets
  104. def handle_post_request(self, environ):
  105. """Handle a long-polling POST request from the client."""
  106. length = int(environ.get('CONTENT_LENGTH', '0'))
  107. if length > self.server.max_http_buffer_size:
  108. raise exceptions.ContentTooLongError()
  109. else:
  110. body = environ['wsgi.input'].read(length)
  111. p = payload.Payload(encoded_payload=body)
  112. for pkt in p.packets:
  113. self.receive(pkt)
  114. def close(self, wait=True, abort=False):
  115. """Close the socket connection."""
  116. if not self.closed and not self.closing:
  117. self.closing = True
  118. self.server._trigger_event('disconnect', self.sid, run_async=False)
  119. if not abort:
  120. self.send(packet.Packet(packet.CLOSE))
  121. self.closed = True
  122. self.queue.put(None)
  123. if wait:
  124. self.queue.join()
  125. def _upgrade_websocket(self, environ, start_response):
  126. """Upgrade the connection from polling to websocket."""
  127. if self.upgraded:
  128. raise IOError('Socket has been upgraded already')
  129. if self.server._async['websocket'] is None:
  130. # the selected async mode does not support websocket
  131. return self.server._bad_request()
  132. ws = self.server._async['websocket'](self._websocket_handler)
  133. return ws(environ, start_response)
  134. def _websocket_handler(self, ws):
  135. """Engine.IO handler for websocket transport."""
  136. # try to set a socket timeout matching the configured ping interval
  137. for attr in ['_sock', 'socket']: # pragma: no cover
  138. if hasattr(ws, attr) and hasattr(getattr(ws, attr), 'settimeout'):
  139. getattr(ws, attr).settimeout(self.server.ping_timeout)
  140. if self.connected:
  141. # the socket was already connected, so this is an upgrade
  142. self.upgrading = True # hold packet sends during the upgrade
  143. pkt = ws.wait()
  144. decoded_pkt = packet.Packet(encoded_packet=pkt)
  145. if decoded_pkt.packet_type != packet.PING or \
  146. decoded_pkt.data != 'probe':
  147. self.server.logger.info(
  148. '%s: Failed websocket upgrade, no PING packet', self.sid)
  149. return []
  150. ws.send(packet.Packet(
  151. packet.PONG,
  152. data=six.text_type('probe')).encode(always_bytes=False))
  153. self.queue.put(packet.Packet(packet.NOOP)) # end poll
  154. pkt = ws.wait()
  155. decoded_pkt = packet.Packet(encoded_packet=pkt)
  156. if decoded_pkt.packet_type != packet.UPGRADE:
  157. self.upgraded = False
  158. self.server.logger.info(
  159. ('%s: Failed websocket upgrade, expected UPGRADE packet, '
  160. 'received %s instead.'),
  161. self.sid, pkt)
  162. return []
  163. self.upgraded = True
  164. # flush any packets that were sent during the upgrade
  165. for pkt in self.packet_backlog:
  166. self.queue.put(pkt)
  167. self.packet_backlog = []
  168. self.upgrading = False
  169. else:
  170. self.connected = True
  171. self.upgraded = True
  172. # start separate writer thread
  173. def writer():
  174. while True:
  175. packets = None
  176. try:
  177. packets = self.poll()
  178. except exceptions.QueueEmpty:
  179. break
  180. if not packets:
  181. # empty packet list returned -> connection closed
  182. break
  183. try:
  184. for pkt in packets:
  185. ws.send(pkt.encode(always_bytes=False))
  186. except:
  187. break
  188. writer_task = self.server.start_background_task(writer)
  189. self.server.logger.info(
  190. '%s: Upgrade to websocket successful', self.sid)
  191. while True:
  192. p = None
  193. try:
  194. p = ws.wait()
  195. except Exception as e:
  196. # if the socket is already closed, we can assume this is a
  197. # downstream error of that
  198. if not self.closed: # pragma: no cover
  199. self.server.logger.info(
  200. '%s: Unexpected error "%s", closing connection',
  201. self.sid, str(e))
  202. break
  203. if p is None:
  204. # connection closed by client
  205. break
  206. if isinstance(p, six.text_type): # pragma: no cover
  207. p = p.encode('utf-8')
  208. pkt = packet.Packet(encoded_packet=p)
  209. try:
  210. self.receive(pkt)
  211. except exceptions.UnknownPacketError: # pragma: no cover
  212. pass
  213. except exceptions.SocketIsClosedError: # pragma: no cover
  214. self.server.logger.info('Receive error -- socket is closed')
  215. break
  216. except: # pragma: no cover
  217. # if we get an unexpected exception we log the error and exit
  218. # the connection properly
  219. self.server.logger.exception('Unknown receive error')
  220. break
  221. self.queue.put(None) # unlock the writer task so that it can exit
  222. writer_task.join()
  223. self.close(wait=False, abort=True)
  224. return []