websocket.py 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182
  1. import re
  2. import socket
  3. from hashlib import sha1
  4. from frame import receive_frame
  5. from errors import HandshakeError
  6. WS_GUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'
  7. WS_VERSION = '13'
  8. def split_stripped(value, delim=','):
  9. return map(str.strip, str(value).split(delim))
  10. class websocket(object):
  11. """
  12. Implementation of web socket, upgrades a regular TCP socket to a websocket
  13. using the HTTP handshakes and frame (un)packing, as specified by RFC 6455.
  14. Server example:
  15. >>> sock = websocket()
  16. >>> sock.bind(('', 8000))
  17. >>> sock.listen()
  18. >>> client = sock.accept()
  19. >>> client.send(Frame(...))
  20. >>> frame = client.recv()
  21. Client example:
  22. >>> sock = websocket()
  23. >>> sock.connect(('', 8000))
  24. """
  25. def __init__(self, sock=None, protocols=[], extensions=[], sfamily=socket.AF_INET,
  26. sproto=0):
  27. """
  28. Create a regular TCP socket of family `family` and protocol
  29. `sock` is an optional regular TCP socket to be used for sending binary
  30. data. If not specified, a new socket is created.
  31. `protocols` is a list of supported protocol names.
  32. `extensions` is a list of supported extensions.
  33. `sfamily` and `sproto` are used for the regular socket constructor.
  34. """
  35. self.protocols = protocols
  36. self.extensions = extensions
  37. self.sock = sock or socket.socket(sfamily, socket.SOCK_STREAM, sproto)
  38. def bind(self, address):
  39. self.sock.bind(address)
  40. def listen(self, backlog):
  41. self.sock.listen(backlog)
  42. def accept(self):
  43. """
  44. Equivalent to socket.accept(), but transforms the socket into a
  45. websocket instance and sends a server handshake (after receiving a
  46. client handshake). Note that the handshake may raise a HandshakeError
  47. exception.
  48. """
  49. sock, address = self.sock.accept()
  50. wsock = websocket(sock)
  51. wsock.server_handshake()
  52. return wsock, address
  53. def connect(self, address):
  54. """
  55. Equivalent to socket.connect(), but sends an client handshake request
  56. after connecting.
  57. """
  58. self.sock.sonnect(address)
  59. self.client_handshake()
  60. def send(self, *args):
  61. """
  62. Send a number of frames.
  63. """
  64. for frame in args:
  65. self.sock.sendall(frame.pack())
  66. def recv(self):
  67. """
  68. Receive a single frames. This can be either a data frame or a control
  69. frame.
  70. """
  71. frame = receive_frame(self.sock)
  72. return frame
  73. def recvn(self, n):
  74. """
  75. Receive exactly `n` frames. These can be either data frames or control
  76. frames, or a combination of both.
  77. """
  78. return [self.recv() for i in xrange(n)]
  79. def getpeername(self):
  80. return self.sock.getpeername()
  81. def getsockname(self):
  82. return self.sock.getsockname()
  83. def setsockopt(self, level, optname, value):
  84. self.sock.setsockopt(level, optname, value)
  85. def getsockopt(self, level, optname):
  86. return self.sock.getsockopt(level, optname)
  87. def close(self):
  88. self.sock.close()
  89. def server_handshake(self):
  90. """
  91. Execute a handshake as the server end point of the socket. If the HTTP
  92. request headers sent by the client are invalid, a HandshakeError
  93. is raised.
  94. """
  95. raw_headers = self.sock.recv(512).decode('utf-8', 'ignore')
  96. # request must be HTTP (at least 1.1) GET request, find the location
  97. location = re.search(r'^GET (.*) HTTP/1.1\r\n', raw_headers).group(1)
  98. headers = re.findall(r'(.*?): (.*?)\r\n', raw_headers)
  99. header_names = [name for name, value in headers]
  100. def header(name):
  101. return ', '.join([v for n, v in headers if n == name])
  102. # Check if headers that MUST be present are actually present
  103. for name in ('Host', 'Upgrade', 'Connection', 'Sec-WebSocket-Key',
  104. 'Origin', 'Sec-WebSocket-Version'):
  105. if name not in header_names:
  106. raise HandshakeError('missing "%s" header' % name)
  107. # Check WebSocket version used by client
  108. version = header('Sec-WebSocket-Version')
  109. if version != WS_VERSION:
  110. raise HandshakeError('WebSocket version %s requested (only %s '
  111. 'is supported)' % (version, WS_VERSION))
  112. # Only supported protocols are returned
  113. proto = header('Sec-WebSocket-Extensions')
  114. protocols = split_stripped(proto) if proto else []
  115. protocols = [p for p in protocols if p in self.protocols]
  116. # Only supported extensions are returned
  117. ext = header('Sec-WebSocket-Extensions')
  118. extensions = split_stripped(ext) if ext else []
  119. extensions = [e for e in extensions if e in self.extensions]
  120. # Encode acceptation key using the WebSocket GUID
  121. key = header('Sec-WebSocket-Key')
  122. accept = sha1(key + WS_GUID).digest().encode('base64')
  123. # Construct HTTP response header
  124. shake = 'HTTP/1.1 101 Web Socket Protocol Handshake\r\n'
  125. shake += 'Upgrade: WebSocket\r\n'
  126. shake += 'Connection: Upgrade\r\n'
  127. shake += 'WebSocket-Origin: %s\r\n' % header('Origin')
  128. shake += 'WebSocket-Location: ws://%s%s\r\n' \
  129. % (header('Host'), location)
  130. shake += 'Sec-WebSocket-Accept: %s\r\n' % accept
  131. if protocols:
  132. shake += 'Sec-WebSocket-Protocol: %s\r\n' % ', '.join(protocols)
  133. if extensions:
  134. shake += 'Sec-WebSocket-Extensions: %s\r\n' % ', '.join(extensions)
  135. self.sock.send(shake + '\r\n')
  136. def client_handshake(self):
  137. """
  138. Execute a handshake as the client end point of the socket. May raise a
  139. HandshakeError if the server response is invalid.
  140. """
  141. # TODO: implement HTTP request headers for client handshake
  142. raise NotImplementedError()