PageRenderTime 26ms CodeModel.GetById 23ms RepoModel.GetById 0ms app.codeStats 0ms

/ENV/lib/python3.6/site-packages/engineio/asyncio_socket.py

https://bitbucket.org/codengine/auction
Python | 213 lines | 202 code | 7 blank | 4 comment | 17 complexity | bf699e3a83339997b725f115731a9414 MD5 | raw file
Possible License(s): BSD-3-Clause
  1. import asyncio
  2. import six
  3. import sys
  4. import time
  5. from . import exceptions
  6. from . import packet
  7. from . import payload
  8. from . import socket
  9. class AsyncSocket(socket.Socket):
  10. def create_queue(self):
  11. return asyncio.Queue()
  12. async def poll(self):
  13. """Wait for packets to send to the client."""
  14. try:
  15. packets = [await asyncio.wait_for(self.queue.get(),
  16. self.server.ping_timeout)]
  17. self.queue.task_done()
  18. except (asyncio.TimeoutError, asyncio.CancelledError):
  19. raise exceptions.QueueEmpty()
  20. if packets == [None]:
  21. return []
  22. try:
  23. packets.append(self.queue.get_nowait())
  24. self.queue.task_done()
  25. except asyncio.QueueEmpty:
  26. pass
  27. return packets
  28. async def receive(self, pkt):
  29. """Receive packet from the client."""
  30. self.server.logger.info('%s: Received packet %s data %s',
  31. self.sid, packet.packet_names[pkt.packet_type],
  32. pkt.data if not isinstance(pkt.data, bytes)
  33. else '<binary>')
  34. if pkt.packet_type == packet.PING:
  35. self.last_ping = time.time()
  36. await self.send(packet.Packet(packet.PONG, pkt.data))
  37. elif pkt.packet_type == packet.MESSAGE:
  38. await self.server._trigger_event(
  39. 'message', self.sid, pkt.data,
  40. run_async=self.server.async_handlers)
  41. elif pkt.packet_type == packet.UPGRADE:
  42. await self.send(packet.Packet(packet.NOOP))
  43. elif pkt.packet_type == packet.CLOSE:
  44. await self.close(wait=False, abort=True)
  45. else:
  46. raise exceptions.UnknownPacketError()
  47. async def send(self, pkt):
  48. """Send a packet to the client."""
  49. if self.closed:
  50. raise exceptions.SocketIsClosedError()
  51. if time.time() - self.last_ping > self.server.ping_timeout:
  52. self.server.logger.info('%s: Client is gone, closing socket',
  53. self.sid)
  54. return await self.close(wait=False, abort=True)
  55. self.server.logger.info('%s: Sending packet %s data %s',
  56. self.sid, packet.packet_names[pkt.packet_type],
  57. pkt.data if not isinstance(pkt.data, bytes)
  58. else '<binary>')
  59. await self.queue.put(pkt)
  60. async def handle_get_request(self, environ):
  61. """Handle a long-polling GET request from the client."""
  62. connections = [
  63. s.strip()
  64. for s in environ.get('HTTP_CONNECTION', '').lower().split(',')]
  65. transport = environ.get('HTTP_UPGRADE', '').lower()
  66. if 'upgrade' in connections and transport in self.upgrade_protocols:
  67. self.server.logger.info('%s: Received request to upgrade to %s',
  68. self.sid, transport)
  69. return await getattr(self, '_upgrade_' + transport)(environ)
  70. try:
  71. packets = await self.poll()
  72. except exceptions.QueueEmpty:
  73. exc = sys.exc_info()
  74. await self.close(wait=False)
  75. six.reraise(*exc)
  76. return packets
  77. async def handle_post_request(self, environ):
  78. """Handle a long-polling POST request from the client."""
  79. length = int(environ.get('CONTENT_LENGTH', '0'))
  80. if length > self.server.max_http_buffer_size:
  81. raise exceptions.ContentTooLongError()
  82. else:
  83. body = await environ['wsgi.input'].read(length)
  84. p = payload.Payload(encoded_payload=body)
  85. for pkt in p.packets:
  86. await self.receive(pkt)
  87. async def close(self, wait=True, abort=False):
  88. """Close the socket connection."""
  89. if not self.closed and not self.closing:
  90. self.closing = True
  91. await self.server._trigger_event('disconnect', self.sid)
  92. if not abort:
  93. await self.send(packet.Packet(packet.CLOSE))
  94. self.closed = True
  95. if wait:
  96. await self.queue.join()
  97. async def _upgrade_websocket(self, environ):
  98. """Upgrade the connection from polling to websocket."""
  99. if self.upgraded:
  100. raise IOError('Socket has been upgraded already')
  101. if self.server._async['websocket'] is None or \
  102. self.server._async['websocket_class'] is None:
  103. # the selected async mode does not support websocket
  104. return self.server._bad_request()
  105. websocket_class = getattr(self.server._async['websocket'],
  106. self.server._async['websocket_class'])
  107. ws = websocket_class(self._websocket_handler)
  108. return await ws(environ)
  109. async def _websocket_handler(self, ws):
  110. """Engine.IO handler for websocket transport."""
  111. if self.connected:
  112. # the socket was already connected, so this is an upgrade
  113. await self.queue.join() # flush the queue first
  114. pkt = await ws.wait()
  115. if pkt != packet.Packet(packet.PING,
  116. data=six.text_type('probe')).encode(
  117. always_bytes=False):
  118. self.server.logger.info(
  119. '%s: Failed websocket upgrade, no PING packet', self.sid)
  120. return
  121. await ws.send(packet.Packet(
  122. packet.PONG,
  123. data=six.text_type('probe')).encode(always_bytes=False))
  124. await self.send(packet.Packet(packet.NOOP))
  125. pkt = await ws.wait()
  126. decoded_pkt = packet.Packet(encoded_packet=pkt)
  127. if decoded_pkt.packet_type != packet.UPGRADE:
  128. self.upgraded = False
  129. self.server.logger.info(
  130. ('%s: Failed websocket upgrade, expected UPGRADE packet, '
  131. 'received %s instead.'),
  132. self.sid, pkt)
  133. return
  134. self.upgraded = True
  135. else:
  136. self.connected = True
  137. self.upgraded = True
  138. # start separate writer thread
  139. async def writer():
  140. while True:
  141. packets = None
  142. try:
  143. packets = await self.poll()
  144. except exceptions.QueueEmpty:
  145. break
  146. if not packets:
  147. # empty packet list returned -> connection closed
  148. break
  149. try:
  150. for pkt in packets:
  151. await ws.send(pkt.encode(always_bytes=False))
  152. except:
  153. break
  154. writer_task = asyncio.ensure_future(writer())
  155. self.server.logger.info(
  156. '%s: Upgrade to websocket successful', self.sid)
  157. while True:
  158. p = None
  159. wait_task = asyncio.ensure_future(ws.wait())
  160. try:
  161. p = await asyncio.wait_for(wait_task, self.server.ping_timeout)
  162. except asyncio.CancelledError: # pragma: no cover
  163. # there is a bug (https://bugs.python.org/issue30508) in
  164. # asyncio that causes a "Task exception never retrieved" error
  165. # to appear when wait_task raises an exception before it gets
  166. # cancelled. Calling wait_task.exception() prevents the error
  167. # from being issued in Python 3.6, but causes other errors in
  168. # other versions, so we run it with all errors suppressed and
  169. # hope for the best.
  170. try:
  171. wait_task.exception()
  172. except:
  173. pass
  174. break
  175. except:
  176. break
  177. if p is None:
  178. # connection closed by client
  179. break
  180. if isinstance(p, six.text_type): # pragma: no cover
  181. p = p.encode('utf-8')
  182. pkt = packet.Packet(encoded_packet=p)
  183. try:
  184. await self.receive(pkt)
  185. except exceptions.UnknownPacketError:
  186. pass
  187. except exceptions.SocketIsClosedError:
  188. self.server.logger.info('Receive error -- socket is closed')
  189. break
  190. except: # pragma: no cover
  191. # if we get an unexpected exception we log the error and exit
  192. # the connection properly
  193. self.server.logger.exception('Unknown receive error')
  194. await self.queue.put(None) # unlock the writer task so it can exit
  195. await asyncio.wait_for(writer_task, timeout=None)
  196. await self.close(wait=True, abort=True)