___websocket.py 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236
  1. """
  2. REF:: https://github.com/mtah/python-websocket (+ /.setup/websocket.py.patch)
  3. This program is free software: you can redistribute it and/or modify
  4. it under the terms of the GNU General Public License as published by
  5. the Free Software Foundation, either version 3 of the License, or
  6. (at your option) any later version.
  7. This program is distributed in the hope that it will be useful,
  8. but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  10. GNU General Public License for more details.
  11. You should have received a copy of the GNU General Public License
  12. along with this program. If not, see <http://www.gnu.org/licenses/>
  13. """
  14. import sys, re, urlparse, socket, asyncore
  15. class WebSocket(object):
  16. def __init__(self, url, **kwargs):
  17. self.host, self.port, self.resource, self.secure = WebSocket._parse_url(url)
  18. self.protocol = kwargs.pop('protocol', None)
  19. self.cookie_jar = kwargs.pop('cookie_jar', None)
  20. self.onopen = kwargs.pop('onopen', None)
  21. self.onmessage = kwargs.pop('onmessage', None)
  22. self.onerror = kwargs.pop('onerror', None)
  23. self.onclose = kwargs.pop('onclose', None)
  24. if kwargs: raise ValueError('Unexpected argument(s): %s' % ', '.join(kwargs.values()))
  25. self._dispatcher = _Dispatcher(self)
  26. def send(self, data):
  27. self._dispatcher.write('\x00' + _utf8(data) + '\xff')
  28. def close(self):
  29. self._dispatcher.handle_close()
  30. @classmethod
  31. def _parse_url(cls, url):
  32. p = urlparse.urlparse(url)
  33. if p.hostname:
  34. host = p.hostname
  35. else:
  36. raise ValueError('URL must be absolute')
  37. if p.fragment:
  38. raise ValueError('URL must not contain a fragment component')
  39. if p.scheme == 'ws':
  40. secure = False
  41. port = p.port or 80
  42. elif p.scheme == 'wss':
  43. raise NotImplementedError('Secure WebSocket not yet supported')
  44. # secure = True
  45. # port = p.port or 443
  46. else:
  47. raise ValueError('Invalid URL scheme')
  48. resource = p.path or u'/'
  49. if p.query: resource += u'?' + p.query
  50. return (host, port, resource, secure)
  51. #@classmethod
  52. #def _generate_key(cls):
  53. # spaces = random.randint(1, 12)
  54. # number = random.randint(0, 0xffffffff/spaces)
  55. # key = list(str(number*spaces))
  56. # chars = map(unichr, range(0x21, 0x2f) + range(0x3a, 0x7e))
  57. # random_inserts = random.sample(xrange(len(key)), random.randint(1,12))
  58. # for (i, c) in [(r, random.choice(chars)) for r in random_inserts]:
  59. # key.insert(i, c)
  60. # print key
  61. # return ''.join(key)
  62. class WebSocketError(Exception):
  63. def _init_(self, value):
  64. self.value = value
  65. def _str_(self):
  66. return str(self.value)
  67. class _Dispatcher(asyncore.dispatcher):
  68. def __init__(self, ws):
  69. asyncore.dispatcher.__init__(self)
  70. self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
  71. self.connect((ws.host, ws.port))
  72. self.ws = ws
  73. self._read_buffer = ''
  74. self._write_buffer = ''
  75. self._handshake_complete = False
  76. if self.ws.port != 80:
  77. hostport = '%s:%d' % (self.ws.host, self.ws.port)
  78. else:
  79. hostport = self.ws.host
  80. fields = [
  81. 'Upgrade: WebSocket',
  82. 'Connection: Upgrade',
  83. 'Host: ' + hostport,
  84. 'Origin: http://' + hostport,
  85. #'Sec-WebSocket-Key1: %s' % WebSocket.generate_key(),
  86. #'Sec-WebSocket-Key2: %s' % WebSocket.generate_key()
  87. ]
  88. if self.ws.protocol: fields['Sec-WebSocket-Protocol'] = self.ws.protocol
  89. if self.ws.cookie_jar:
  90. cookies = filter(lambda c: _cookie_for_domain(c, _eff_host(self.ws.host)) and \
  91. _cookie_for_path(c, self.ws.resource) and \
  92. not c.is_expired(), self.ws.cookie_jar)
  93. for cookie in cookies:
  94. fields.append('Cookie: %s=%s' % (cookie.name, cookie.value))
  95. # key3 = ''.join(map(unichr, (random.randrange(256) for i in xrange(8))))
  96. self.write(_utf8('GET %s HTTP/1.1\r\n' \
  97. '%s\r\n\r\n' % (self.ws.resource,
  98. '\r\n'.join(fields))))
  99. # key3)))
  100. def handl_expt(self):
  101. self.handle_error()
  102. def handle_error(self):
  103. self.close()
  104. t, e, trace = sys.exc_info()
  105. if self.ws.onerror:
  106. self.ws.onerror(e)
  107. else:
  108. asyncore.dispatcher.handle_error(self)
  109. def handle_close(self):
  110. self.close()
  111. if self.ws.onclose:
  112. self.ws.onclose()
  113. def handle_read(self):
  114. if self._handshake_complete:
  115. self._read_until('\xff', self._handle_frame)
  116. else:
  117. self._read_until('\r\n\r\n', self._handle_header)
  118. def handle_write(self):
  119. sent = self.send(self._write_buffer)
  120. self._write_buffer = self._write_buffer[sent:]
  121. def writable(self):
  122. return len(self._write_buffer) > 0
  123. def write(self, data):
  124. self._write_buffer += data # TODO: separate buffer for handshake from data to
  125. # prevent mix-up when send() is called before
  126. # handshake is complete?
  127. def _read_until(self, delimiter, callback):
  128. def lookForAndHandleCompletedFrame():
  129. pos = self._read_buffer.find(delimiter)
  130. if pos >= 0:
  131. pos += len(delimiter)
  132. data = self._read_buffer[:pos]
  133. self._read_buffer = self._read_buffer[pos:]
  134. if data:
  135. callback(data)
  136. lookForAndHandleCompletedFrame()
  137. self._read_buffer += self.recv(4096)
  138. lookForAndHandleCompletedFrame()
  139. def _handle_frame(self, frame):
  140. assert frame[-1] == '\xff'
  141. if frame[0] != '\x00':
  142. raise WebSocketError('WebSocket stream error')
  143. if self.ws.onmessage:
  144. self.ws.onmessage(frame[1:-1])
  145. # TODO: else raise WebSocketError('No message handler defined')
  146. def _handle_header(self, header):
  147. assert header[-4:] == '\r\n\r\n'
  148. start_line, fields = _parse_http_header(header)
  149. if start_line != 'HTTP/1.1 101 Web Socket Protocol Handshake' or \
  150. fields.get('Connection', None) != 'Upgrade' or \
  151. fields.get('Upgrade', None) != 'WebSocket':
  152. raise WebSocketError('Invalid server handshake')
  153. self._handshake_complete = True
  154. if self.ws.onopen:
  155. self.ws.onopen()
  156. _IPV4_RE = re.compile(r'\.\d+$')
  157. _PATH_SEP = re.compile(r'/+')
  158. def _parse_http_header(header):
  159. def split_field(field):
  160. k, v = field.split(':', 1)
  161. return (k, v.strip())
  162. lines = header.strip().split('\r\n')
  163. if len(lines) > 0:
  164. start_line = lines[0]
  165. else:
  166. start_line = None
  167. return (start_line, dict(map(split_field, lines[1:])))
  168. def _eff_host(host):
  169. if host.find('.') == -1 and not _IPV4_RE.search(host):
  170. return host + '.local'
  171. return host
  172. def _cookie_for_path(cookie, path):
  173. if not cookie.path or path == '' or path == '/':
  174. return True
  175. path = _PATH_SEP.split(path)[1:]
  176. cookie_path = _PATH_SEP.split(cookie.path)[1:]
  177. for p1, p2 in map(lambda *ps: ps, path, cookie_path):
  178. if p1 == None:
  179. return True
  180. elif p1 != p2:
  181. return False
  182. return True
  183. def _cookie_for_domain(cookie, domain):
  184. if not cookie.domain:
  185. return True
  186. elif cookie.domain[0] == '.':
  187. return domain.endswith(cookie.domain)
  188. else:
  189. return cookie.domain == domain
  190. def _utf8(s):
  191. return s.encode('utf-8')