websocket.py 9.0 KB

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