websocket.py 25 KB

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