websocket.py 7.7 KB

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