websocket.py 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  1. import re
  2. import struct
  3. from hashlib import sha1
  4. from threading import Thread
  5. from frame import ControlFrame, receive_fragments, receive_frame, \
  6. OPCODE_CLOSE, OPCODE_PING, OPCODE_PONG
  7. from message import create_message
  8. from exceptions import InvalidRequest, SocketClosed, PingError
  9. WS_GUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'
  10. WS_VERSION = '13'
  11. class WebSocket(object):
  12. def __init__(self, sock):
  13. self.sock = sock
  14. self.received_close_params = None
  15. self.close_frame_sent = False
  16. self.ping_sent = False
  17. self.ping_payload = None
  18. def send_message(self, message, fragment_size=None):
  19. if fragment_size is None:
  20. self.send_frame(message.frame())
  21. else:
  22. map(self.send_frame, message.fragment(fragment_size))
  23. def send_frame(self, frame):
  24. self.sock.sendall(frame.pack())
  25. def handle_control_frame(self, frame):
  26. if frame.opcode == OPCODE_CLOSE:
  27. self.received_close_params = frame.unpack_close()
  28. elif frame.opcode == OPCODE_PING:
  29. # Respond with a pong message with identical payload
  30. self.send_frame(ControlFrame(OPCODE_PONG, frame.payload))
  31. elif frame.opcode == OPCODE_PONG:
  32. # Assert that the PONG payload is identical to that of the PING
  33. if not self.ping_sent:
  34. raise PingError('received PONG while no PING was sent')
  35. self.ping_sent = False
  36. if frame.payload != self.ping_payload:
  37. raise PingError('received PONG with invalid payload')
  38. self.ping_payload = None
  39. self.onpong(frame.payload)
  40. def receive_message(self):
  41. frames = receive_fragments(self.sock, self.handle_control_frame)
  42. payload = ''.join([f.payload for f in frames])
  43. return create_message(frames[0].opcode, payload)
  44. def handshake(self):
  45. """
  46. Execute a handshake with the other end point of the socket. If the HTTP
  47. request headers read from the socket are invalid, an InvalidRequest
  48. exception will be raised.
  49. """
  50. raw_headers = self.sock.recv(512).decode('utf-8', 'ignore')
  51. # request must be HTTP (at least 1.1) GET request, find the location
  52. location = re.search(r'^GET (.*) HTTP/1.1\r\n', raw_headers).group(1)
  53. headers = dict(re.findall(r'(.*?): (.*?)\r\n', raw_headers))
  54. # Check if headers that MUST be present are actually present
  55. for name in ('Host', 'Upgrade', 'Connection', 'Sec-WebSocket-Key',
  56. 'Origin', 'Sec-WebSocket-Version'):
  57. if name not in headers:
  58. raise InvalidRequest('missing "%s" header' % name)
  59. # Check WebSocket version used by client
  60. version = headers['Sec-WebSocket-Version']
  61. if version != WS_VERSION:
  62. raise InvalidRequest('WebSocket version %s requested (only %s '
  63. 'is supported)' % (version, WS_VERSION))
  64. # Make sure the requested protocols are supported by this server
  65. if 'Sec-WebSocket-Protocol' in headers:
  66. parts = headers['Sec-WebSocket-Protocol'].split(',')
  67. protocols = map(str.strip, parts)
  68. for p in protocols:
  69. if p not in self.protocols:
  70. raise InvalidRequest('unsupported protocol "%s"' % p)
  71. else:
  72. protocols = []
  73. # Encode acceptation key using the WebSocket GUID
  74. key = headers['Sec-WebSocket-Key']
  75. accept = sha1(key + WS_GUID).digest().encode('base64')
  76. # Construct HTTP response header
  77. shake = 'HTTP/1.1 101 Web Socket Protocol Handshake\r\n'
  78. shake += 'Upgrade: WebSocket\r\n'
  79. shake += 'Connection: Upgrade\r\n'
  80. shake += 'WebSocket-Origin: %s\r\n' % headers['Origin']
  81. shake += 'WebSocket-Location: ws://%s%s\r\n' \
  82. % (headers['Host'], location)
  83. shake += 'Sec-WebSocket-Accept: %s\r\n' % accept
  84. if self.protocols:
  85. shake += 'Sec-WebSocket-Protocol: %s\r\n' \
  86. % ', '.join(self.protocols)
  87. self.sock.send(shake + '\r\n')
  88. self.onopen()
  89. def receive_forever(self):
  90. """
  91. Receive and handle messages in an endless loop. A message may consist
  92. of multiple data frames, but this is not visible for onmessage().
  93. Control messages (or control frames) are handled automatically.
  94. """
  95. try:
  96. while True:
  97. self.onmessage(self, self.receive_message())
  98. if self.received_close_params is not None:
  99. self.handle_close(*self.received_close_params)
  100. break
  101. except SocketClosed:
  102. self.onclose(None, '')
  103. def run_threaded(self, daemon=True):
  104. """
  105. Spawn a new thread that receives messages in an endless loop.
  106. """
  107. thread = Thread(target=self.receive_forever)
  108. thread.daemon = daemon
  109. thread.start()
  110. return thread
  111. def send_close(self, code, reason):
  112. payload = '' if code is None else struct.pack('!H', code)
  113. self.send_frame(ControlFrame(OPCODE_CLOSE, payload))
  114. self.close_frame_sent = True
  115. def send_ping(self, payload=''):
  116. """
  117. Send a ping control frame with an optional payload.
  118. """
  119. self.send_frame(ControlFrame(OPCODE_PING, payload))
  120. self.ping_payload = payload
  121. self.ping_sent = True
  122. self.onping(payload)
  123. def handle_close(self, code=None, reason=''):
  124. """
  125. Handle a close message by sending a response close message if no close
  126. message was sent before, and closing the connection. The onclose()
  127. handler is called afterwards.
  128. """
  129. if not self.close_frame_sent:
  130. payload = '' if code is None else struct.pack('!H', code)
  131. self.send_frame(ControlFrame(OPCODE_CLOSE, payload))
  132. self.sock.close()
  133. self.onclose(code, reason)
  134. def close(self, code=None, reason=''):
  135. """
  136. Close the socket by sending a close message and waiting for a response
  137. close message. The onclose() handler is called after the close message
  138. has been sent, but before the response has been received.
  139. """
  140. self.send_close(code, reason)
  141. # FIXME: swap the two lines below?
  142. self.onclose(code, reason)
  143. frame = receive_frame(self.sock)
  144. self.sock.close()
  145. if frame.opcode != OPCODE_CLOSE:
  146. raise ValueError('Expected close frame, got %s instead' % frame)
  147. def onopen(self):
  148. """
  149. Called after the handshake has completed.
  150. """
  151. pass
  152. def onmessage(self, message):
  153. """
  154. Called when a message is received. `message' is a Message object, which
  155. can be constructed from a single frame or multiple fragmented frames.
  156. """
  157. return NotImplemented
  158. def onping(self, payload):
  159. """
  160. Called after a ping control frame has been sent. This handler could be
  161. used to start a timeout handler for a pong message that is not received
  162. in time.
  163. """
  164. pass
  165. def onpong(self, payload):
  166. """
  167. Called when a pong control frame is received.
  168. """
  169. pass
  170. def onclose(self, code, reason):
  171. """
  172. Called when the socket is closed by either end point.
  173. """
  174. pass