websocket.py 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867
  1. #!/usr/bin/env python
  2. '''
  3. Python WebSocket library with support for "wss://" encryption.
  4. Copyright 2011 Joel Martin
  5. Licensed under LGPL version 3 (see docs/LICENSE.LGPL-3)
  6. Supports following protocol versions:
  7. - http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-75
  8. - http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76
  9. - http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-07
  10. You can make a cert/key with openssl using:
  11. openssl req -new -x509 -days 365 -nodes -out self.pem -keyout self.pem
  12. as taken from http://docs.python.org/dev/library/ssl.html#certificates
  13. '''
  14. import os, sys, time, errno, signal, socket, struct, traceback, select
  15. from cgi import parse_qsl
  16. from base64 import b64encode, b64decode
  17. # Imports that vary by python version
  18. if sys.hexversion > 0x3000000:
  19. # python >= 3.0
  20. from io import StringIO
  21. from http.server import SimpleHTTPRequestHandler
  22. from urllib.parse import urlsplit
  23. b2s = lambda buf: buf.decode('latin_1')
  24. s2b = lambda s: s.encode('latin_1')
  25. else:
  26. # python 2.X
  27. from cStringIO import StringIO
  28. from SimpleHTTPServer import SimpleHTTPRequestHandler
  29. from urlparse import urlsplit
  30. # No-ops
  31. b2s = lambda buf: buf
  32. s2b = lambda s: s
  33. if sys.hexversion >= 0x2060000:
  34. # python >= 2.6
  35. from multiprocessing import Process
  36. from hashlib import md5, sha1
  37. else:
  38. # python < 2.6
  39. Process = None
  40. from md5 import md5
  41. from sha import sha as sha1
  42. # Degraded functionality if these imports are missing
  43. for mod, sup in [('numpy', 'HyBi protocol'),
  44. ('ctypes', 'HyBi protocol'), ('ssl', 'TLS/SSL/wss'),
  45. ('resource', 'daemonizing')]:
  46. try:
  47. globals()[mod] = __import__(mod)
  48. except ImportError:
  49. globals()[mod] = None
  50. print("WARNING: no '%s' module, %s support disabled" % (
  51. mod, sup))
  52. class WebSocketServer(object):
  53. """
  54. WebSockets server class.
  55. Must be sub-classed with new_client method definition.
  56. """
  57. buffer_size = 65536
  58. server_handshake_hixie = """HTTP/1.1 101 Web Socket Protocol Handshake\r
  59. Upgrade: WebSocket\r
  60. Connection: Upgrade\r
  61. %sWebSocket-Origin: %s\r
  62. %sWebSocket-Location: %s://%s%s\r
  63. """
  64. server_handshake_hybi = """HTTP/1.1 101 Switching Protocols\r
  65. Upgrade: websocket\r
  66. Connection: Upgrade\r
  67. Sec-WebSocket-Accept: %s\r
  68. """
  69. GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
  70. policy_response = """<cross-domain-policy><allow-access-from domain="*" to-ports="*" /></cross-domain-policy>\n"""
  71. class EClose(Exception):
  72. pass
  73. def __init__(self, listen_host='', listen_port=None, source_is_ipv6=False,
  74. verbose=False, cert='', key='', ssl_only=None,
  75. daemon=False, record='', web=''):
  76. # settings
  77. self.verbose = verbose
  78. self.listen_host = listen_host
  79. self.listen_port = listen_port
  80. self.ssl_only = ssl_only
  81. self.daemon = daemon
  82. self.handler_id = 1
  83. # Make paths settings absolute
  84. self.cert = os.path.abspath(cert)
  85. self.key = self.web = self.record = ''
  86. if key:
  87. self.key = os.path.abspath(key)
  88. if web:
  89. self.web = os.path.abspath(web)
  90. if record:
  91. self.record = os.path.abspath(record)
  92. if self.web:
  93. os.chdir(self.web)
  94. # Sanity checks
  95. if not ssl and self.ssl_only:
  96. raise Exception("No 'ssl' module and SSL-only specified")
  97. if self.daemon and not resource:
  98. raise Exception("Module 'resource' required to daemonize")
  99. # Show configuration
  100. print("WebSocket server settings:")
  101. print(" - Listen on %s:%s" % (
  102. self.listen_host, self.listen_port))
  103. print(" - Flash security policy server")
  104. if self.web:
  105. print(" - Web server")
  106. if ssl:
  107. if os.path.exists(self.cert):
  108. print(" - SSL/TLS support")
  109. if self.ssl_only:
  110. print(" - Deny non-SSL/TLS connections")
  111. else:
  112. print(" - No SSL/TLS support (no cert file)")
  113. else:
  114. print(" - No SSL/TLS support (no 'ssl' module)")
  115. if self.daemon:
  116. print(" - Backgrounding (daemon)")
  117. if self.record:
  118. print(" - Recording to '%s.*'" % self.record)
  119. #
  120. # WebSocketServer static methods
  121. #
  122. @staticmethod
  123. def socket(host, port=None, connect=False, prefer_ipv6=False):
  124. """ Resolve a host (and optional port) to an IPv4 or IPv6
  125. address. Create a socket. Bind to it if listen is set. Return
  126. a socket that is ready for listen or connect.
  127. """
  128. flags = 0
  129. if host == '': host = None
  130. if not connect:
  131. flags = flags | socket.AI_PASSIVE
  132. addrs = socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM,
  133. socket.IPPROTO_TCP, flags)
  134. if not addrs:
  135. raise Exception("Could resolve host '%s'" % host)
  136. addrs.sort(key=lambda x: x[0])
  137. if prefer_ipv6:
  138. addrs.reverse()
  139. sock = socket.socket(addrs[0][0], addrs[0][1])
  140. if connect:
  141. sock.connect(addrs[0][4])
  142. else:
  143. sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
  144. sock.bind(addrs[0][4])
  145. sock.listen(100)
  146. return sock
  147. @staticmethod
  148. def daemonize(keepfd=None, chdir='/'):
  149. os.umask(0)
  150. if chdir:
  151. os.chdir(chdir)
  152. else:
  153. os.chdir('/')
  154. os.setgid(os.getgid()) # relinquish elevations
  155. os.setuid(os.getuid()) # relinquish elevations
  156. # Double fork to daemonize
  157. if os.fork() > 0: os._exit(0) # Parent exits
  158. os.setsid() # Obtain new process group
  159. if os.fork() > 0: os._exit(0) # Parent exits
  160. # Signal handling
  161. def terminate(a,b): os._exit(0)
  162. signal.signal(signal.SIGTERM, terminate)
  163. signal.signal(signal.SIGINT, signal.SIG_IGN)
  164. # Close open files
  165. maxfd = resource.getrlimit(resource.RLIMIT_NOFILE)[1]
  166. if maxfd == resource.RLIM_INFINITY: maxfd = 256
  167. for fd in reversed(range(maxfd)):
  168. try:
  169. if fd != keepfd:
  170. os.close(fd)
  171. except OSError:
  172. _, exc, _ = sys.exc_info()
  173. if exc.errno != errno.EBADF: raise
  174. # Redirect I/O to /dev/null
  175. os.dup2(os.open(os.devnull, os.O_RDWR), sys.stdin.fileno())
  176. os.dup2(os.open(os.devnull, os.O_RDWR), sys.stdout.fileno())
  177. os.dup2(os.open(os.devnull, os.O_RDWR), sys.stderr.fileno())
  178. @staticmethod
  179. def encode_hybi(buf, opcode, base64=False):
  180. """ Encode a HyBi style WebSocket frame.
  181. Optional opcode:
  182. 0x0 - continuation
  183. 0x1 - text frame (base64 encode buf)
  184. 0x2 - binary frame (use raw buf)
  185. 0x8 - connection close
  186. 0x9 - ping
  187. 0xA - pong
  188. """
  189. if base64:
  190. buf = b64encode(buf)
  191. b1 = 0x80 | (opcode & 0x0f) # FIN + opcode
  192. payload_len = len(buf)
  193. if payload_len <= 125:
  194. header = struct.pack('>BB', b1, payload_len)
  195. elif payload_len > 125 and payload_len <= 65536:
  196. header = struct.pack('>BBH', b1, 126, payload_len)
  197. elif payload_len >= 65536:
  198. header = struct.pack('>BBQ', b1, 127, payload_len)
  199. #print("Encoded: %s" % repr(header + buf))
  200. return header + buf, len(header), 0
  201. @staticmethod
  202. def decode_hybi(buf, base64=False):
  203. """ Decode HyBi style WebSocket packets.
  204. Returns:
  205. {'fin' : 0_or_1,
  206. 'opcode' : number,
  207. 'mask' : 32_bit_number,
  208. 'hlen' : header_bytes_number,
  209. 'length' : payload_bytes_number,
  210. 'payload' : decoded_buffer,
  211. 'left' : bytes_left_number,
  212. 'close_code' : number,
  213. 'close_reason' : string}
  214. """
  215. f = {'fin' : 0,
  216. 'opcode' : 0,
  217. 'mask' : 0,
  218. 'hlen' : 2,
  219. 'length' : 0,
  220. 'payload' : None,
  221. 'left' : 0,
  222. 'close_code' : None,
  223. 'close_reason' : None}
  224. blen = len(buf)
  225. f['left'] = blen
  226. if blen < f['hlen']:
  227. return f # Incomplete frame header
  228. b1, b2 = struct.unpack_from(">BB", buf)
  229. f['opcode'] = b1 & 0x0f
  230. f['fin'] = (b1 & 0x80) >> 7
  231. has_mask = (b2 & 0x80) >> 7
  232. f['length'] = b2 & 0x7f
  233. if f['length'] == 126:
  234. f['hlen'] = 4
  235. if blen < f['hlen']:
  236. return f # Incomplete frame header
  237. (f['length'],) = struct.unpack_from('>xxH', buf)
  238. elif f['length'] == 127:
  239. f['hlen'] = 10
  240. if blen < f['hlen']:
  241. return f # Incomplete frame header
  242. (f['length'],) = struct.unpack_from('>xxQ', buf)
  243. full_len = f['hlen'] + has_mask * 4 + f['length']
  244. if blen < full_len: # Incomplete frame
  245. return f # Incomplete frame header
  246. # Number of bytes that are part of the next frame(s)
  247. f['left'] = blen - full_len
  248. # Process 1 frame
  249. if has_mask:
  250. # unmask payload
  251. f['mask'] = buf[f['hlen']:f['hlen']+4]
  252. b = c = ''
  253. if f['length'] >= 4:
  254. mask = numpy.frombuffer(buf, dtype=numpy.dtype('<L4'),
  255. offset=f['hlen'], count=1)
  256. data = numpy.frombuffer(buf, dtype=numpy.dtype('<L4'),
  257. offset=f['hlen'] + 4, count=int(f['length'] / 4))
  258. #b = numpy.bitwise_xor(data, mask).data
  259. b = numpy.bitwise_xor(data, mask).tostring()
  260. if f['length'] % 4:
  261. print("Partial unmask")
  262. mask = numpy.frombuffer(buf, dtype=numpy.dtype('B'),
  263. offset=f['hlen'], count=(f['length'] % 4))
  264. data = numpy.frombuffer(buf, dtype=numpy.dtype('B'),
  265. offset=full_len - (f['length'] % 4),
  266. count=(f['length'] % 4))
  267. c = numpy.bitwise_xor(data, mask).tostring()
  268. f['payload'] = b + c
  269. else:
  270. print("Unmasked frame: %s" % repr(buf))
  271. f['payload'] = buf[(f['hlen'] + has_mask * 4):full_len]
  272. if base64 and f['opcode'] in [1, 2]:
  273. try:
  274. f['payload'] = b64decode(f['payload'])
  275. except:
  276. print("Exception while b64decoding buffer: %s" %
  277. repr(buf))
  278. raise
  279. if f['opcode'] == 0x08:
  280. if f['length'] >= 2:
  281. f['close_code'] = struct.unpack_from(">H", f['payload'])
  282. if f['length'] > 3:
  283. f['close_reason'] = f['payload'][2:]
  284. return f
  285. @staticmethod
  286. def encode_hixie(buf):
  287. return s2b("\x00" + b2s(b64encode(buf)) + "\xff"), 1, 1
  288. @staticmethod
  289. def decode_hixie(buf):
  290. end = buf.find(s2b('\xff'))
  291. return {'payload': b64decode(buf[1:end]),
  292. 'hlen': 1,
  293. 'length': end - 1,
  294. 'left': len(buf) - (end + 1)}
  295. @staticmethod
  296. def gen_md5(keys):
  297. """ Generate hash value for WebSockets hixie-76. """
  298. key1 = keys['Sec-WebSocket-Key1']
  299. key2 = keys['Sec-WebSocket-Key2']
  300. key3 = keys['key3']
  301. spaces1 = key1.count(" ")
  302. spaces2 = key2.count(" ")
  303. num1 = int("".join([c for c in key1 if c.isdigit()])) / spaces1
  304. num2 = int("".join([c for c in key2 if c.isdigit()])) / spaces2
  305. return b2s(md5(struct.pack('>II8s',
  306. int(num1), int(num2), key3)).digest())
  307. #
  308. # WebSocketServer logging/output functions
  309. #
  310. def traffic(self, token="."):
  311. """ Show traffic flow in verbose mode. """
  312. if self.verbose and not self.daemon:
  313. sys.stdout.write(token)
  314. sys.stdout.flush()
  315. def msg(self, msg):
  316. """ Output message with handler_id prefix. """
  317. if not self.daemon:
  318. print("% 3d: %s" % (self.handler_id, msg))
  319. def vmsg(self, msg):
  320. """ Same as msg() but only if verbose. """
  321. if self.verbose:
  322. self.msg(msg)
  323. #
  324. # Main WebSocketServer methods
  325. #
  326. def send_frames(self, bufs=None):
  327. """ Encode and send WebSocket frames. Any frames already
  328. queued will be sent first. If buf is not set then only queued
  329. frames will be sent. Returns the number of pending frames that
  330. could not be fully sent. If returned pending frames is greater
  331. than 0, then the caller should call again when the socket is
  332. ready. """
  333. tdelta = int(time.time()*1000) - self.start_time
  334. if bufs:
  335. for buf in bufs:
  336. if self.version.startswith("hybi"):
  337. if self.base64:
  338. encbuf, lenhead, lentail = self.encode_hybi(
  339. buf, opcode=1, base64=True)
  340. else:
  341. encbuf, lenhead, lentail = self.encode_hybi(
  342. buf, opcode=2, base64=False)
  343. else:
  344. encbuf, lenhead, lentail = self.encode_hixie(buf)
  345. if self.rec:
  346. self.rec.write("%s,\n" %
  347. repr("{%s{" % tdelta
  348. + encbuf[lenhead:-lentail]))
  349. self.send_parts.append(encbuf)
  350. while self.send_parts:
  351. # Send pending frames
  352. buf = self.send_parts.pop(0)
  353. sent = self.client.send(buf)
  354. if sent == len(buf):
  355. self.traffic("<")
  356. else:
  357. self.traffic("<.")
  358. self.send_parts.insert(0, buf[sent:])
  359. break
  360. return len(self.send_parts)
  361. def recv_frames(self):
  362. """ Receive and decode WebSocket frames.
  363. Returns:
  364. (bufs_list, closed_string)
  365. """
  366. closed = False
  367. bufs = []
  368. tdelta = int(time.time()*1000) - self.start_time
  369. buf = self.client.recv(self.buffer_size)
  370. if len(buf) == 0:
  371. closed = "Client closed abruptly"
  372. return bufs, closed
  373. if self.recv_part:
  374. # Add partially received frames to current read buffer
  375. buf = self.recv_part + buf
  376. self.recv_part = None
  377. while buf:
  378. if self.version.startswith("hybi"):
  379. frame = self.decode_hybi(buf, base64=self.base64)
  380. #print("Received buf: %s, frame: %s" % (repr(buf), frame))
  381. if frame['payload'] == None:
  382. # Incomplete/partial frame
  383. self.traffic("}.")
  384. if frame['left'] > 0:
  385. self.recv_part = buf[-frame['left']:]
  386. break
  387. else:
  388. if frame['opcode'] == 0x8: # connection close
  389. closed = "Client closed, reason: %s - %s" % (
  390. frame['close_code'],
  391. frame['close_reason'])
  392. break
  393. else:
  394. if buf[0:2] == '\xff\x00':
  395. closed = "Client sent orderly close frame"
  396. break
  397. elif buf[0:2] == '\x00\xff':
  398. buf = buf[2:]
  399. continue # No-op
  400. elif buf.count(s2b('\xff')) == 0:
  401. # Partial frame
  402. self.traffic("}.")
  403. self.recv_part = buf
  404. break
  405. frame = self.decode_hixie(buf)
  406. self.traffic("}")
  407. if self.rec:
  408. start = frame['hlen']
  409. end = frame['hlen'] + frame['length']
  410. self.rec.write("%s,\n" %
  411. repr("}%s}" % tdelta + buf[start:end]))
  412. bufs.append(frame['payload'])
  413. if frame['left']:
  414. buf = buf[-frame['left']:]
  415. else:
  416. buf = ''
  417. return bufs, closed
  418. def send_close(self, code=None, reason=''):
  419. """ Send a WebSocket orderly close frame. """
  420. if self.version.startswith("hybi"):
  421. msg = s2b('')
  422. if code != None:
  423. msg = struct.pack(">H%ds" % (len(reason)), code)
  424. buf, h, t = self.encode_hybi(msg, opcode=0x08, base64=False)
  425. self.client.send(buf)
  426. elif self.version == "hixie-76":
  427. buf = s2b('\xff\x00')
  428. self.client.send(buf)
  429. # No orderly close for 75
  430. def do_handshake(self, sock, address):
  431. """
  432. do_handshake does the following:
  433. - Peek at the first few bytes from the socket.
  434. - If the connection is Flash policy request then answer it,
  435. close the socket and return.
  436. - If the connection is an HTTPS/SSL/TLS connection then SSL
  437. wrap the socket.
  438. - Read from the (possibly wrapped) socket.
  439. - If we have received a HTTP GET request and the webserver
  440. functionality is enabled, answer it, close the socket and
  441. return.
  442. - Assume we have a WebSockets connection, parse the client
  443. handshake data.
  444. - Send a WebSockets handshake server response.
  445. - Return the socket for this WebSocket client.
  446. """
  447. stype = ""
  448. ready = select.select([sock], [], [], 3)[0]
  449. if not ready:
  450. raise self.EClose("ignoring socket not ready")
  451. # Peek, but do not read the data so that we have a opportunity
  452. # to SSL wrap the socket first
  453. handshake = sock.recv(1024, socket.MSG_PEEK)
  454. #self.msg("Handshake [%s]" % handshake)
  455. if handshake == "":
  456. raise self.EClose("ignoring empty handshake")
  457. elif handshake.startswith(s2b("<policy-file-request/>")):
  458. # Answer Flash policy request
  459. handshake = sock.recv(1024)
  460. sock.send(s2b(self.policy_response))
  461. raise self.EClose("Sending flash policy response")
  462. elif handshake[0] in ("\x16", "\x80"):
  463. # SSL wrap the connection
  464. if not ssl:
  465. raise self.EClose("SSL connection but no 'ssl' module")
  466. if not os.path.exists(self.cert):
  467. raise self.EClose("SSL connection but '%s' not found"
  468. % self.cert)
  469. retsock = None
  470. try:
  471. retsock = ssl.wrap_socket(
  472. sock,
  473. server_side=True,
  474. certfile=self.cert,
  475. keyfile=self.key)
  476. except ssl.SSLError:
  477. _, x, _ = sys.exc_info()
  478. if x.args[0] == ssl.SSL_ERROR_EOF:
  479. raise self.EClose("")
  480. else:
  481. raise
  482. scheme = "wss"
  483. stype = "SSL/TLS (wss://)"
  484. elif self.ssl_only:
  485. raise self.EClose("non-SSL connection received but disallowed")
  486. else:
  487. retsock = sock
  488. scheme = "ws"
  489. stype = "Plain non-SSL (ws://)"
  490. wsh = WSRequestHandler(retsock, address, not self.web)
  491. if wsh.last_code == 101:
  492. # Continue on to handle WebSocket upgrade
  493. pass
  494. elif wsh.last_code == 405:
  495. raise self.EClose("Normal web request received but disallowed")
  496. elif wsh.last_code < 200 or wsh.last_code >= 300:
  497. raise self.EClose(wsh.last_message)
  498. elif self.verbose:
  499. raise self.EClose(wsh.last_message)
  500. else:
  501. raise self.EClose("")
  502. h = self.headers = wsh.headers
  503. path = self.path = wsh.path
  504. prot = 'WebSocket-Protocol'
  505. protocols = h.get('Sec-'+prot, h.get(prot, '')).split(',')
  506. ver = h.get('Sec-WebSocket-Version')
  507. if ver:
  508. # HyBi/IETF version of the protocol
  509. if sys.hexversion < 0x2060000 or not numpy:
  510. raise self.EClose("Python >= 2.6 and numpy module is required for HyBi-07 or greater")
  511. if ver in ['7', '8', '9']:
  512. self.version = "hybi-0" + ver
  513. else:
  514. raise self.EClose('Unsupported protocol version %s' % ver)
  515. key = h['Sec-WebSocket-Key']
  516. # Choose binary if client supports it
  517. if 'binary' in protocols:
  518. self.base64 = False
  519. elif 'base64' in protocols:
  520. self.base64 = True
  521. else:
  522. raise self.EClose("Client must support 'binary' or 'base64' protocol")
  523. # Generate the hash value for the accept header
  524. accept = b64encode(sha1(s2b(key + self.GUID)).digest())
  525. response = self.server_handshake_hybi % accept
  526. if self.base64:
  527. response += "Sec-WebSocket-Protocol: base64\r\n"
  528. else:
  529. response += "Sec-WebSocket-Protocol: binary\r\n"
  530. response += "\r\n"
  531. else:
  532. # Hixie version of the protocol (75 or 76)
  533. if h.get('key3'):
  534. trailer = self.gen_md5(h)
  535. pre = "Sec-"
  536. self.version = "hixie-76"
  537. else:
  538. trailer = ""
  539. pre = ""
  540. self.version = "hixie-75"
  541. # We only support base64 in Hixie era
  542. self.base64 = True
  543. response = self.server_handshake_hixie % (pre,
  544. h['Origin'], pre, scheme, h['Host'], path)
  545. if 'base64' in protocols:
  546. response += "%sWebSocket-Protocol: base64\r\n" % pre
  547. else:
  548. self.msg("Warning: client does not report 'base64' protocol support")
  549. response += "\r\n" + trailer
  550. self.msg("%s: %s WebSocket connection" % (address[0], stype))
  551. self.msg("%s: Version %s, base64: '%s'" % (address[0],
  552. self.version, self.base64))
  553. # Send server WebSockets handshake response
  554. #self.msg("sending response [%s]" % response)
  555. retsock.send(s2b(response))
  556. # Return the WebSockets socket which may be SSL wrapped
  557. return retsock
  558. #
  559. # Events that can/should be overridden in sub-classes
  560. #
  561. def started(self):
  562. """ Called after WebSockets startup """
  563. self.vmsg("WebSockets server started")
  564. def poll(self):
  565. """ Run periodically while waiting for connections. """
  566. #self.vmsg("Running poll()")
  567. pass
  568. def fallback_SIGCHLD(self, sig, stack):
  569. # Reap zombies when using os.fork() (python 2.4)
  570. self.vmsg("Got SIGCHLD, reaping zombies")
  571. try:
  572. result = os.waitpid(-1, os.WNOHANG)
  573. while result[0]:
  574. self.vmsg("Reaped child process %s" % result[0])
  575. result = os.waitpid(-1, os.WNOHANG)
  576. except (OSError):
  577. pass
  578. def do_SIGINT(self, sig, stack):
  579. self.msg("Got SIGINT, exiting")
  580. sys.exit(0)
  581. def top_new_client(self, startsock, address):
  582. """ Do something with a WebSockets client connection. """
  583. # Initialize per client settings
  584. self.send_parts = []
  585. self.recv_part = None
  586. self.base64 = False
  587. self.rec = None
  588. self.start_time = int(time.time()*1000)
  589. # handler process
  590. try:
  591. try:
  592. self.client = self.do_handshake(startsock, address)
  593. if self.record:
  594. # Record raw frame data as JavaScript array
  595. fname = "%s.%s" % (self.record,
  596. self.handler_id)
  597. self.msg("opening record file: %s" % fname)
  598. self.rec = open(fname, 'w+')
  599. self.rec.write("var VNC_frame_data = [\n")
  600. self.new_client()
  601. except self.EClose:
  602. _, exc, _ = sys.exc_info()
  603. # Connection was not a WebSockets connection
  604. if exc.args[0]:
  605. self.msg("%s: %s" % (address[0], exc.args[0]))
  606. except Exception:
  607. _, exc, _ = sys.exc_info()
  608. self.msg("handler exception: %s" % str(exc))
  609. if self.verbose:
  610. self.msg(traceback.format_exc())
  611. finally:
  612. if self.rec:
  613. self.rec.write("'EOF']\n")
  614. self.rec.close()
  615. if self.client and self.client != startsock:
  616. self.client.close()
  617. def new_client(self):
  618. """ Do something with a WebSockets client connection. """
  619. raise("WebSocketServer.new_client() must be overloaded")
  620. def start_server(self):
  621. """
  622. Daemonize if requested. Listen for for connections. Run
  623. do_handshake() method for each connection. If the connection
  624. is a WebSockets client then call new_client() method (which must
  625. be overridden) for each new client connection.
  626. """
  627. lsock = self.socket(self.listen_host, self.listen_port)
  628. if self.daemon:
  629. self.daemonize(keepfd=lsock.fileno(), chdir=self.web)
  630. self.started() # Some things need to happen after daemonizing
  631. # Allow override of SIGINT
  632. signal.signal(signal.SIGINT, self.do_SIGINT)
  633. if not Process:
  634. # os.fork() (python 2.4) child reaper
  635. signal.signal(signal.SIGCHLD, self.fallback_SIGCHLD)
  636. while True:
  637. try:
  638. try:
  639. self.client = None
  640. startsock = None
  641. pid = err = 0
  642. try:
  643. self.poll()
  644. ready = select.select([lsock], [], [], 1)[0]
  645. if lsock in ready:
  646. startsock, address = lsock.accept()
  647. else:
  648. continue
  649. except Exception:
  650. _, exc, _ = sys.exc_info()
  651. if hasattr(exc, 'errno'):
  652. err = exc.errno
  653. elif hasattr(exc, 'args'):
  654. err = exc.args[0]
  655. else:
  656. err = exc[0]
  657. if err == errno.EINTR:
  658. self.vmsg("Ignoring interrupted syscall")
  659. continue
  660. else:
  661. raise
  662. if Process:
  663. self.vmsg('%s: new handler Process' % address[0])
  664. p = Process(target=self.top_new_client,
  665. args=(startsock, address))
  666. p.start()
  667. # child will not return
  668. else:
  669. # python 2.4
  670. self.vmsg('%s: forking handler' % address[0])
  671. pid = os.fork()
  672. if pid == 0:
  673. # child handler process
  674. self.top_new_client(startsock, address)
  675. break # child process exits
  676. # parent process
  677. self.handler_id += 1
  678. except KeyboardInterrupt:
  679. _, exc, _ = sys.exc_info()
  680. print("In KeyboardInterrupt")
  681. pass
  682. except SystemExit:
  683. _, exc, _ = sys.exc_info()
  684. print("In SystemExit")
  685. break
  686. except Exception:
  687. _, exc, _ = sys.exc_info()
  688. self.msg("handler exception: %s" % str(exc))
  689. if self.verbose:
  690. self.msg(traceback.format_exc())
  691. finally:
  692. if startsock:
  693. startsock.close()
  694. # HTTP handler with WebSocket upgrade support
  695. class WSRequestHandler(SimpleHTTPRequestHandler):
  696. def __init__(self, req, addr, only_upgrade=False):
  697. self.only_upgrade = only_upgrade # only allow upgrades
  698. SimpleHTTPRequestHandler.__init__(self, req, addr, object())
  699. def do_GET(self):
  700. if (self.headers.get('upgrade') and
  701. self.headers.get('upgrade').lower() == 'websocket'):
  702. if (self.headers.get('sec-websocket-key1') or
  703. self.headers.get('websocket-key1')):
  704. # For Hixie-76 read out the key hash
  705. self.headers.__setitem__('key3', self.rfile.read(8))
  706. # Just indicate that an WebSocket upgrade is needed
  707. self.last_code = 101
  708. self.last_message = "101 Switching Protocols"
  709. elif self.only_upgrade:
  710. # Normal web request responses are disabled
  711. self.last_code = 405
  712. self.last_message = "405 Method Not Allowed"
  713. else:
  714. SimpleHTTPRequestHandler.do_GET(self)
  715. def send_response(self, code, message=None):
  716. # Save the status code
  717. self.last_code = code
  718. SimpleHTTPRequestHandler.send_response(self, code, message)
  719. def log_message(self, f, *args):
  720. # Save instead of printing
  721. self.last_message = f % args