]> git.proxmox.com Git - mirror_novnc.git/blobdiff - utils/websocket.py
Merge pull request #444 from samhed/extdesktop
[mirror_novnc.git] / utils / websocket.py
index 37b69c868a59e50ba95ed28c86dbcf05efea68c0..67f5aef63667b321f629548658d69d51cec568a5 100644 (file)
@@ -6,9 +6,9 @@ Copyright 2011 Joel Martin
 Licensed under LGPL version 3 (see docs/LICENSE.LGPL-3)
 
 Supports following protocol versions:
-    - http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-75
-    - http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76
+    - http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-07
     - http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-10
+    - http://tools.ietf.org/html/rfc6455
 
 You can make a cert/key with openssl using:
 openssl req -new -x509 -days 365 -nodes -out self.pem -keyout self.pem
@@ -16,233 +16,129 @@ as taken from http://docs.python.org/dev/library/ssl.html#certificates
 
 '''
 
-import os, sys, time, errno, signal, socket, traceback, select
-import struct, array
-from cgi import parse_qsl
+import os, sys, time, errno, signal, socket, select, logging
+import array, struct
 from base64 import b64encode, b64decode
 
 # Imports that vary by python version
+
+# python 3.0 differences
 if sys.hexversion > 0x3000000:
-    # python >= 3.0
-    from io import StringIO
-    from http.server import SimpleHTTPRequestHandler
-    from urllib.parse import urlsplit
     b2s = lambda buf: buf.decode('latin_1')
     s2b = lambda s: s.encode('latin_1')
     s2a = lambda s: s
 else:
-    # python 2.X
-    from cStringIO import StringIO
-    from SimpleHTTPServer import SimpleHTTPRequestHandler
-    from urlparse import urlsplit
-    # No-ops
-    b2s = lambda buf: buf
-    s2b = lambda s: s
+    b2s = lambda buf: buf  # No-op
+    s2b = lambda s: s      # No-op
     s2a = lambda s: [ord(c) for c in s]
-
-if sys.hexversion >= 0x2060000:
-    # python >= 2.6
-    from multiprocessing import Process
-    from hashlib import md5, sha1
-else:
-    # python < 2.6
-    Process = None
-    from md5 import md5
-    from sha import sha as sha1
+try:    from io import StringIO
+except: from cStringIO import StringIO
+try:    from http.server import SimpleHTTPRequestHandler
+except: from SimpleHTTPServer import SimpleHTTPRequestHandler
+
+# python 2.6 differences
+try:    from hashlib import sha1
+except: from sha import sha as sha1
+
+# python 2.5 differences
+try:
+    from struct import pack, unpack_from
+except:
+    from struct import pack
+    def unpack_from(fmt, buf, offset=0):
+        slice = buffer(buf, offset, struct.calcsize(fmt))
+        return struct.unpack(fmt, slice)
 
 # Degraded functionality if these imports are missing
-for mod, sup in [('numpy', 'HyBi protocol'),
-        ('ssl', 'TLS/SSL/wss'), ('resource', 'daemonizing')]:
+for mod, msg in [('numpy', 'HyBi protocol will be slower'),
+                 ('ssl', 'TLS/SSL/wss is disabled'),
+                 ('multiprocessing', 'Multi-Processing is disabled'),
+                 ('resource', 'daemonizing is disabled')]:
     try:
         globals()[mod] = __import__(mod)
     except ImportError:
         globals()[mod] = None
-        print("WARNING: no '%s' module, %s decode may be slower" % (
-            mod, sup))
+        print("WARNING: no '%s' module, %s" % (mod, msg))
 
+if multiprocessing and sys.platform == 'win32':
+    # make sockets pickle-able/inheritable
+    import multiprocessing.reduction
 
-class WebSocketServer(object):
+
+# HTTP handler with WebSocket upgrade support
+class WebSocketRequestHandler(SimpleHTTPRequestHandler):
     """
-    WebSockets server class.
-    Must be sub-classed with new_client method definition.
+    WebSocket Request Handler Class, derived from SimpleHTTPRequestHandler.
+    Must be sub-classed with new_websocket_client method definition.
+    The request handler can be configured by setting optional
+    attributes on the server object:
+
+    * only_upgrade: If true, SimpleHTTPRequestHandler will not be enabled,
+      only websocket is allowed.
+    * verbose: If true, verbose logging is activated.
+    * daemon: Running as daemon, do not write to console etc
+    * record: Record raw frame data as JavaScript array into specified filename
+    * run_once: Handle a single request
+    * handler_id: A sequence number for this connection, appended to record filename
     """
-
     buffer_size = 65536
 
-    server_handshake_hixie = """HTTP/1.1 101 Web Socket Protocol Handshake\r
-Upgrade: WebSocket\r
-Connection: Upgrade\r
-%sWebSocket-Origin: %s\r
-%sWebSocket-Location: %s://%s%s\r
-"""
-
-    server_handshake_hybi = """HTTP/1.1 101 Switching Protocols\r
-Upgrade: websocket\r
-Connection: Upgrade\r
-Sec-WebSocket-Accept: %s\r
-"""
-
     GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
 
-    policy_response = """<cross-domain-policy><allow-access-from domain="*" to-ports="*" /></cross-domain-policy>\n"""
+    server_version = "WebSockify"
 
-    class EClose(Exception):
-        pass
-
-    def __init__(self, listen_host='', listen_port=None, source_is_ipv6=False,
-            verbose=False, cert='', key='', ssl_only=None,
-            daemon=False, record='', web='',
-            run_once=False, timeout=0):
-
-        # settings
-        self.verbose        = verbose
-        self.listen_host    = listen_host
-        self.listen_port    = listen_port
-        self.ssl_only       = ssl_only
-        self.daemon         = daemon
-        self.run_once       = run_once
-        self.timeout        = timeout
+    protocol_version = "HTTP/1.1"
 
-        self.launch_time    = time.time()
-        self.ws_connection  = False
-        self.handler_id     = 1
-
-        # Make paths settings absolute
-        self.cert = os.path.abspath(cert)
-        self.key = self.web = self.record = ''
-        if key:
-            self.key = os.path.abspath(key)
-        if web:
-            self.web = os.path.abspath(web)
-        if record:
-            self.record = os.path.abspath(record)
-
-        if self.web:
-            os.chdir(self.web)
-
-        # Sanity checks
-        if not ssl and self.ssl_only:
-            raise Exception("No 'ssl' module and SSL-only specified")
-        if self.daemon and not resource:
-            raise Exception("Module 'resource' required to daemonize")
+    # An exception while the WebSocket client was connected
+    class CClose(Exception):
+        pass
 
-        # Show configuration
-        print("WebSocket server settings:")
-        print("  - Listen on %s:%s" % (
-                self.listen_host, self.listen_port))
-        print("  - Flash security policy server")
-        if self.web:
-            print("  - Web server. Web root: %s" % self.web)
-        if ssl:
-            if os.path.exists(self.cert):
-                print("  - SSL/TLS support")
-                if self.ssl_only:
-                    print("  - Deny non-SSL/TLS connections")
-            else:
-                print("  - No SSL/TLS support (no cert file)")
-        else:
-            print("  - No SSL/TLS support (no 'ssl' module)")
-        if self.daemon:
-            print("  - Backgrounding (daemon)")
-        if self.record:
-            print("  - Recording to '%s.*'" % self.record)
+    def __init__(self, req, addr, server):
+        # Retrieve a few configuration variables from the server
+        self.only_upgrade = getattr(server, "only_upgrade", False)
+        self.verbose = getattr(server, "verbose", False)
+        self.daemon = getattr(server, "daemon", False)
+        self.record = getattr(server, "record", False)
+        self.run_once = getattr(server, "run_once", False)
+        self.rec        = None
+        self.handler_id = getattr(server, "handler_id", False)
+        self.file_only = getattr(server, "file_only", False)
+        self.traffic = getattr(server, "traffic", False)
 
-    #
-    # WebSocketServer static methods
-    #
+        self.logger = getattr(server, "logger", None)
+        if self.logger is None:
+            self.logger = WebSocketServer.get_logger()
 
-    @staticmethod
-    def socket(host, port=None, connect=False, prefer_ipv6=False):
-        """ Resolve a host (and optional port) to an IPv4 or IPv6
-        address. Create a socket. Bind to it if listen is set,
-        otherwise connect to it. Return the socket.
-        """
-        flags = 0
-        if host == '':
-            host = None
-        if connect and not port:
-            raise Exception("Connect mode requires a port")
-        if not connect:
-            flags = flags | socket.AI_PASSIVE
-        addrs = socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM,
-                socket.IPPROTO_TCP, flags)
-        if not addrs:
-            raise Exception("Could resolve host '%s'" % host)
-        addrs.sort(key=lambda x: x[0])
-        if prefer_ipv6:
-            addrs.reverse()
-        sock = socket.socket(addrs[0][0], addrs[0][1])
-        if connect:
-            sock.connect(addrs[0][4])
-        else:
-            sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
-            sock.bind(addrs[0][4])
-            sock.listen(100)
-        return sock
+        SimpleHTTPRequestHandler.__init__(self, req, addr, server)
 
     @staticmethod
-    def daemonize(keepfd=None, chdir='/'):
-        os.umask(0)
-        if chdir:
-            os.chdir(chdir)
-        else:
-            os.chdir('/')
-        os.setgid(os.getgid())  # relinquish elevations
-        os.setuid(os.getuid())  # relinquish elevations
-
-        # Double fork to daemonize
-        if os.fork() > 0: os._exit(0)  # Parent exits
-        os.setsid()                    # Obtain new process group
-        if os.fork() > 0: os._exit(0)  # Parent exits
-
-        # Signal handling
-        def terminate(a,b): os._exit(0)
-        signal.signal(signal.SIGTERM, terminate)
-        signal.signal(signal.SIGINT, signal.SIG_IGN)
-
-        # Close open files
-        maxfd = resource.getrlimit(resource.RLIMIT_NOFILE)[1]
-        if maxfd == resource.RLIM_INFINITY: maxfd = 256
-        for fd in reversed(range(maxfd)):
-            try:
-                if fd != keepfd:
-                    os.close(fd)
-            except OSError:
-                _, exc, _ = sys.exc_info()
-                if exc.errno != errno.EBADF: raise
-
-        # Redirect I/O to /dev/null
-        os.dup2(os.open(os.devnull, os.O_RDWR), sys.stdin.fileno())
-        os.dup2(os.open(os.devnull, os.O_RDWR), sys.stdout.fileno())
-        os.dup2(os.open(os.devnull, os.O_RDWR), sys.stderr.fileno())
-
-    @staticmethod
-    def unmask(buf, f):
-        pstart = f['hlen'] + 4
-        pend = pstart + f['length']
+    def unmask(buf, hlen, plen):
+        pstart = hlen + 4
+        pend = pstart + plen
         if numpy:
             b = c = s2b('')
-            if f['length'] >= 4:
+            if plen >= 4:
                 mask = numpy.frombuffer(buf, dtype=numpy.dtype('<u4'),
-                        offset=f['hlen'], count=1)
+                        offset=hlen, count=1)
                 data = numpy.frombuffer(buf, dtype=numpy.dtype('<u4'),
-                        offset=pstart, count=int(f['length'] / 4))
+                        offset=pstart, count=int(plen / 4))
                 #b = numpy.bitwise_xor(data, mask).data
                 b = numpy.bitwise_xor(data, mask).tostring()
 
-            if f['length'] % 4:
-                #print("Partial unmask")
+            if plen % 4:
+                #self.msg("Partial unmask")
                 mask = numpy.frombuffer(buf, dtype=numpy.dtype('B'),
-                        offset=f['hlen'], count=(f['length'] % 4))
+                        offset=hlen, count=(plen % 4))
                 data = numpy.frombuffer(buf, dtype=numpy.dtype('B'),
-                        offset=pend - (f['length'] % 4),
-                        count=(f['length'] % 4))
+                        offset=pend - (plen % 4),
+                        count=(plen % 4))
                 c = numpy.bitwise_xor(data, mask).tostring()
             return b + c
         else:
             # Slower fallback
+            mask = buf[hlen:hlen+4]
             data = array.array('B')
-            mask = s2a(f['mask'])
+            mask = s2a(mask)
             data.fromstring(buf[pstart:pend])
             for i in range(len(data)):
                 data[i] ^= mask[i % 4]
@@ -265,23 +161,23 @@ Sec-WebSocket-Accept: %s\r
         b1 = 0x80 | (opcode & 0x0f) # FIN + opcode
         payload_len = len(buf)
         if payload_len <= 125:
-            header = struct.pack('>BB', b1, payload_len)
+            header = pack('>BB', b1, payload_len)
         elif payload_len > 125 and payload_len < 65536:
-            header = struct.pack('>BBH', b1, 126, payload_len)
+            header = pack('>BBH', b1, 126, payload_len)
         elif payload_len >= 65536:
-            header = struct.pack('>BBQ', b1, 127, payload_len)
+            header = pack('>BBQ', b1, 127, payload_len)
 
-        #print("Encoded: %s" % repr(header + buf))
+        #self.msg("Encoded: %s", repr(header + buf))
 
         return header + buf, len(header), 0
 
     @staticmethod
-    def decode_hybi(buf, base64=False):
+    def decode_hybi(buf, base64=False, logger=None):
         """ Decode HyBi style WebSocket packets.
         Returns:
             {'fin'          : 0_or_1,
              'opcode'       : number,
-             'mask'         : 32_bit_number,
+             'masked'       : boolean,
              'hlen'         : header_bytes_number,
              'length'       : payload_bytes_number,
              'payload'      : decoded_buffer,
@@ -292,13 +188,16 @@ Sec-WebSocket-Accept: %s\r
 
         f = {'fin'          : 0,
              'opcode'       : 0,
-             'mask'         : 0,
+             'masked'       : False,
              'hlen'         : 2,
              'length'       : 0,
              'payload'      : None,
              'left'         : 0,
-             'close_code'   : None,
-             'close_reason' : None}
+             'close_code'   : 1000,
+             'close_reason' : ''}
+
+        if logger is None:
+            logger = WebSocketServer.get_logger()
 
         blen = len(buf)
         f['left'] = blen
@@ -306,10 +205,10 @@ Sec-WebSocket-Accept: %s\r
         if blen < f['hlen']:
             return f # Incomplete frame header
 
-        b1, b2 = struct.unpack_from(">BB", buf)
+        b1, b2 = unpack_from(">BB", buf)
         f['opcode'] = b1 & 0x0f
         f['fin'] = (b1 & 0x80) >> 7
-        has_mask = (b2 & 0x80) >> 7
+        f['masked'] = (b2 & 0x80) >> 7
 
         f['length'] = b2 & 0x7f
 
@@ -317,14 +216,14 @@ Sec-WebSocket-Accept: %s\r
             f['hlen'] = 4
             if blen < f['hlen']:
                 return f # Incomplete frame header
-            (f['length'],) = struct.unpack_from('>xxH', buf)
+            (f['length'],) = unpack_from('>xxH', buf)
         elif f['length'] == 127:
             f['hlen'] = 10
             if blen < f['hlen']:
                 return f # Incomplete frame header
-            (f['length'],) = struct.unpack_from('>xxQ', buf)
+            (f['length'],) = unpack_from('>xxQ', buf)
 
-        full_len = f['hlen'] + has_mask * 4 + f['length']
+        full_len = f['hlen'] + f['masked'] * 4 + f['length']
 
         if blen < full_len: # Incomplete frame
             return f # Incomplete frame header
@@ -333,79 +232,58 @@ Sec-WebSocket-Accept: %s\r
         f['left'] = blen - full_len
 
         # Process 1 frame
-        if has_mask:
+        if f['masked']:
             # unmask payload
-            f['mask'] = buf[f['hlen']:f['hlen']+4]
-            f['payload'] = WebSocketServer.unmask(buf, f)
+            f['payload'] = WebSocketRequestHandler.unmask(buf, f['hlen'],
+                                                  f['length'])
         else:
-            print("Unmasked frame: %s" % repr(buf))
-            f['payload'] = buf[(f['hlen'] + has_mask * 4):full_len]
+            logger.debug("Unmasked frame: %s" % repr(buf))
+            f['payload'] = buf[(f['hlen'] + f['masked'] * 4):full_len]
 
         if base64 and f['opcode'] in [1, 2]:
             try:
                 f['payload'] = b64decode(f['payload'])
             except:
-                print("Exception while b64decoding buffer: %s" %
-                        repr(buf))
+                logger.exception("Exception while b64decoding buffer: %s" %
+                                 (repr(buf)))
                 raise
 
         if f['opcode'] == 0x08:
             if f['length'] >= 2:
-                f['close_code'] = struct.unpack_from(">H", f['payload'])
+                f['close_code'] = unpack_from(">H", f['payload'])[0]
             if f['length'] > 3:
                 f['close_reason'] = f['payload'][2:]
 
         return f
 
-    @staticmethod
-    def encode_hixie(buf):
-        return s2b("\x00" + b2s(b64encode(buf)) + "\xff"), 1, 1
-
-    @staticmethod
-    def decode_hixie(buf):
-        end = buf.find(s2b('\xff'))
-        return {'payload': b64decode(buf[1:end]),
-                'hlen': 1,
-                'length': end - 1,
-                'left': len(buf) - (end + 1)}
-
-
-    @staticmethod
-    def gen_md5(keys):
-        """ Generate hash value for WebSockets hixie-76. """
-        key1 = keys['Sec-WebSocket-Key1']
-        key2 = keys['Sec-WebSocket-Key2']
-        key3 = keys['key3']
-        spaces1 = key1.count(" ")
-        spaces2 = key2.count(" ")
-        num1 = int("".join([c for c in key1 if c.isdigit()])) / spaces1
-        num2 = int("".join([c for c in key2 if c.isdigit()])) / spaces2
-
-        return b2s(md5(struct.pack('>II8s',
-            int(num1), int(num2), key3)).digest())
 
     #
-    # WebSocketServer logging/output functions
+    # WebSocketRequestHandler logging/output functions
     #
 
-    def traffic(self, token="."):
-        """ Show traffic flow in verbose mode. """
-        if self.verbose and not self.daemon:
+    def print_traffic(self, token="."):
+        """ Show traffic flow mode. """
+        if self.traffic:
             sys.stdout.write(token)
             sys.stdout.flush()
 
-    def msg(self, msg):
+    def msg(self, msg, *args, **kwargs):
         """ Output message with handler_id prefix. """
-        if not self.daemon:
-            print("% 3d: %s" % (self.handler_id, msg))
+        prefix = "% 3d: " % self.handler_id
+        self.logger.log(logging.INFO, "%s%s" % (prefix, msg), *args, **kwargs)
 
-    def vmsg(self, msg):
-        """ Same as msg() but only if verbose. """
-        if self.verbose:
-            self.msg(msg)
+    def vmsg(self, msg, *args, **kwargs):
+        """ Same as msg() but as debug. """
+        prefix = "% 3d: " % self.handler_id
+        self.logger.log(logging.DEBUG, "%s%s" % (prefix, msg), *args, **kwargs)
+
+    def warn(self, msg, *args, **kwargs):
+        """ Same as msg() but as warning. """
+        prefix = "% 3d: " % self.handler_id
+        self.logger.log(logging.WARN, "%s%s" % (prefix, msg), *args, **kwargs)
 
     #
-    # Main WebSocketServer methods
+    # Main WebSocketRequestHandler methods
     #
     def send_frames(self, bufs=None):
         """ Encode and send WebSocket frames. Any frames already
@@ -419,33 +297,27 @@ Sec-WebSocket-Accept: %s\r
 
         if bufs:
             for buf in bufs:
-                if self.version.startswith("hybi"):
-                    if self.base64:
-                        encbuf, lenhead, lentail = self.encode_hybi(
-                                buf, opcode=1, base64=True)
-                    else:
-                        encbuf, lenhead, lentail = self.encode_hybi(
-                                buf, opcode=2, base64=False)
-
+                if self.base64:
+                    encbuf, lenhead, lentail = self.encode_hybi(buf, opcode=1, base64=True)
                 else:
-                    encbuf, lenhead, lentail = self.encode_hixie(buf)
+                    encbuf, lenhead, lentail = self.encode_hybi(buf, opcode=2, base64=False)
 
                 if self.rec:
                     self.rec.write("%s,\n" %
                             repr("{%s{" % tdelta
-                                + encbuf[lenhead:-lentail]))
+                                + encbuf[lenhead:len(encbuf)-lentail]))
 
                 self.send_parts.append(encbuf)
 
         while self.send_parts:
             # Send pending frames
             buf = self.send_parts.pop(0)
-            sent = self.client.send(buf)
+            sent = self.request.send(buf)
 
             if sent == len(buf):
-                self.traffic("<")
+                self.print_traffic("<")
             else:
-                self.traffic("<.")
+                self.print_traffic("<.")
                 self.send_parts.insert(0, buf[sent:])
                 break
 
@@ -462,9 +334,9 @@ Sec-WebSocket-Accept: %s\r
         bufs = []
         tdelta = int(time.time()*1000) - self.start_time
 
-        buf = self.client.recv(self.buffer_size)
+        buf = self.request.recv(self.buffer_size)
         if len(buf) == 0:
-            closed = "Client closed abruptly"
+            closed = {'code': 1000, 'reason': "Client closed abruptly"}
             return bufs, closed
 
         if self.recv_part:
@@ -473,48 +345,35 @@ Sec-WebSocket-Accept: %s\r
             self.recv_part = None
 
         while buf:
-            if self.version.startswith("hybi"):
-
-                frame = self.decode_hybi(buf, base64=self.base64)
-                #print("Received buf: %s, frame: %s" % (repr(buf), frame))
-
-                if frame['payload'] == None:
-                    # Incomplete/partial frame
-                    self.traffic("}.")
-                    if frame['left'] > 0:
-                        self.recv_part = buf[-frame['left']:]
-                    break
-                else:
-                    if frame['opcode'] == 0x8: # connection close
-                        closed = "Client closed, reason: %s - %s" % (
-                                frame['close_code'],
-                                frame['close_reason'])
-                        break
-
+            frame = self.decode_hybi(buf, base64=self.base64,
+                                     logger=self.logger)
+            #self.msg("Received buf: %s, frame: %s", repr(buf), frame)
+
+            if frame['payload'] == None:
+                # Incomplete/partial frame
+                self.print_traffic("}.")
+                if frame['left'] > 0:
+                    self.recv_part = buf[-frame['left']:]
+                break
             else:
-                if buf[0:2] == s2b('\xff\x00'):
-                    closed = "Client sent orderly close frame"
+                if frame['opcode'] == 0x8: # connection close
+                    closed = {'code': frame['close_code'],
+                              'reason': frame['close_reason']}
                     break
 
-                elif buf[0:2] == s2b('\x00\xff'):
-                    buf = buf[2:]
-                    continue # No-op
-
-                elif buf.count(s2b('\xff')) == 0:
-                    # Partial frame
-                    self.traffic("}.")
-                    self.recv_part = buf
-                    break
-
-                frame = self.decode_hixie(buf)
-
-            self.traffic("}")
+            self.print_traffic("}")
 
             if self.rec:
                 start = frame['hlen']
                 end = frame['hlen'] + frame['length']
+                if frame['masked']:
+                    recbuf = WebSocketRequestHandler.unmask(buf, frame['hlen'],
+                                                   frame['length'])
+                else:
+                    recbuf = buf[frame['hlen']:frame['hlen'] +
+                                               frame['length']]
                 self.rec.write("%s,\n" %
-                        repr("}%s}" % tdelta + buf[start:end]))
+                        repr("}%s}" % tdelta + recbuf))
 
 
             bufs.append(frame['payload'])
@@ -526,22 +385,354 @@ Sec-WebSocket-Accept: %s\r
 
         return bufs, closed
 
-    def send_close(self, code=None, reason=''):
+    def send_close(self, code=1000, reason=''):
         """ Send a WebSocket orderly close frame. """
 
-        if self.version.startswith("hybi"):
-            msg = s2b('')
-            if code != None:
-                msg = struct.pack(">H%ds" % (len(reason)), code)
+        msg = pack(">H%ds" % len(reason), code, reason)
+        buf, h, t = self.encode_hybi(msg, opcode=0x08, base64=False)
+        self.request.send(buf)
+
+    def do_websocket_handshake(self):
+        h = self.headers
+
+        prot = 'WebSocket-Protocol'
+        protocols = h.get('Sec-'+prot, h.get(prot, '')).split(',')
+
+        ver = h.get('Sec-WebSocket-Version')
+        if ver:
+            # HyBi/IETF version of the protocol
+
+            # HyBi-07 report version 7
+            # HyBi-08 - HyBi-12 report version 8
+            # HyBi-13 reports version 13
+            if ver in ['7', '8', '13']:
+                self.version = "hybi-%02d" % int(ver)
+            else:
+                self.send_error(400, "Unsupported protocol version %s" % ver)
+                return False
+
+            key = h['Sec-WebSocket-Key']
+
+            # Choose binary if client supports it
+            if 'binary' in protocols:
+                self.base64 = False
+            elif 'base64' in protocols:
+                self.base64 = True
+            else:
+                self.send_error(400, "Client must support 'binary' or 'base64' protocol")
+                return False
+
+            # Generate the hash value for the accept header
+            accept = b64encode(sha1(s2b(key + self.GUID)).digest())
+
+            self.send_response(101, "Switching Protocols")
+            self.send_header("Upgrade", "websocket")
+            self.send_header("Connection", "Upgrade")
+            self.send_header("Sec-WebSocket-Accept", b2s(accept))
+            if self.base64:
+                self.send_header("Sec-WebSocket-Protocol", "base64")
+            else:
+                self.send_header("Sec-WebSocket-Protocol", "binary")
+            self.end_headers()
+            return True
+        else:
+            self.send_error(400, "Missing Sec-WebSocket-Version header. Hixie protocols not supported.")
+
+        return False
+
+    def handle_websocket(self):
+        """Upgrade a connection to Websocket, if requested. If this succeeds,
+        new_websocket_client() will be called. Otherwise, False is returned.
+        """
+        if (self.headers.get('upgrade') and
+            self.headers.get('upgrade').lower() == 'websocket'):
+
+            if not self.do_websocket_handshake():
+                return False
+
+            # Indicate to server that a Websocket upgrade was done
+            self.server.ws_connection = True
+            # Initialize per client settings
+            self.send_parts = []
+            self.recv_part  = None
+            self.start_time = int(time.time()*1000)
+
+            # client_address is empty with, say, UNIX domain sockets
+            client_addr = ""
+            is_ssl = False
+            try:
+                client_addr = self.client_address[0]
+                is_ssl = self.client_address[2]
+            except IndexError:
+                pass
+
+            if is_ssl:
+                self.stype = "SSL/TLS (wss://)"
+            else:
+                self.stype = "Plain non-SSL (ws://)"
+
+            self.log_message("%s: %s WebSocket connection", client_addr,
+                             self.stype)
+            self.log_message("%s: Version %s, base64: '%s'", client_addr,
+                             self.version, self.base64)
+            if self.path != '/':
+                self.log_message("%s: Path: '%s'", client_addr, self.path)
+
+            if self.record:
+                # Record raw frame data as JavaScript array
+                fname = "%s.%s" % (self.record,
+                                   self.handler_id)
+                self.log_message("opening record file: %s", fname)
+                self.rec = open(fname, 'w+')
+                encoding = "binary"
+                if self.base64: encoding = "base64"
+                self.rec.write("var VNC_frame_encoding = '%s';\n"
+                               % encoding)
+                self.rec.write("var VNC_frame_data = [\n")
+
+            try:
+                self.new_websocket_client()
+            except self.CClose:
+                # Close the client
+                _, exc, _ = sys.exc_info()
+                self.send_close(exc.args[0], exc.args[1])
+            return True
+        else:
+            return False
+
+    def do_GET(self):
+        """Handle GET request. Calls handle_websocket(). If unsuccessful,
+        and web server is enabled, SimpleHTTPRequestHandler.do_GET will be called."""
+        if not self.handle_websocket():
+            if self.only_upgrade:
+                self.send_error(405, "Method Not Allowed")
+            else:
+                SimpleHTTPRequestHandler.do_GET(self)
+
+    def list_directory(self, path):
+        if self.file_only:
+            self.send_error(404, "No such file")
+        else:
+            return SimpleHTTPRequestHandler.list_directory(self, path)
+
+    def new_websocket_client(self):
+        """ Do something with a WebSockets client connection. """
+        raise Exception("WebSocketRequestHandler.new_websocket_client() must be overloaded")
+
+    def do_HEAD(self):
+        if self.only_upgrade:
+            self.send_error(405, "Method Not Allowed")
+        else:
+            SimpleHTTPRequestHandler.do_HEAD(self)
+
+    def finish(self):
+        if self.rec:
+            self.rec.write("'EOF'];\n")
+            self.rec.close()
+
+    def handle(self):
+        # When using run_once, we have a single process, so
+        # we cannot loop in BaseHTTPRequestHandler.handle; we
+        # must return and handle new connections
+        if self.run_once:
+            self.handle_one_request()
+        else:
+            SimpleHTTPRequestHandler.handle(self)
+
+    def log_request(self, code='-', size='-'):
+        if self.verbose:
+            SimpleHTTPRequestHandler.log_request(self, code, size)
+
+
+class WebSocketServer(object):
+    """
+    WebSockets server class.
+    As an alternative, the standard library SocketServer can be used
+    """
+
+    policy_response = """<cross-domain-policy><allow-access-from domain="*" to-ports="*" /></cross-domain-policy>\n"""
+    log_prefix = "websocket"
 
-            buf, h, t = self.encode_hybi(msg, opcode=0x08, base64=False)
-            self.client.send(buf)
+    # An exception before the WebSocket connection was established
+    class EClose(Exception):
+        pass
 
-        elif self.version == "hixie-76":
-            buf = s2b('\xff\x00')
-            self.client.send(buf)
+    class Terminate(Exception):
+        pass
 
-        # No orderly close for 75
+    def __init__(self, RequestHandlerClass, listen_host='',
+                 listen_port=None, source_is_ipv6=False,
+            verbose=False, cert='', key='', ssl_only=None,
+            daemon=False, record='', web='',
+            file_only=False,
+            run_once=False, timeout=0, idle_timeout=0, traffic=False,
+            tcp_keepalive=True, tcp_keepcnt=None, tcp_keepidle=None,
+            tcp_keepintvl=None):
+
+        # settings
+        self.RequestHandlerClass = RequestHandlerClass
+        self.verbose        = verbose
+        self.listen_host    = listen_host
+        self.listen_port    = listen_port
+        self.prefer_ipv6    = source_is_ipv6
+        self.ssl_only       = ssl_only
+        self.daemon         = daemon
+        self.run_once       = run_once
+        self.timeout        = timeout
+        self.idle_timeout   = idle_timeout
+        self.traffic        = traffic
+
+        self.launch_time    = time.time()
+        self.ws_connection  = False
+        self.handler_id     = 1
+
+        self.logger         = self.get_logger()
+        self.tcp_keepalive  = tcp_keepalive
+        self.tcp_keepcnt    = tcp_keepcnt
+        self.tcp_keepidle   = tcp_keepidle
+        self.tcp_keepintvl  = tcp_keepintvl
+
+        # Make paths settings absolute
+        self.cert = os.path.abspath(cert)
+        self.key = self.web = self.record = ''
+        if key:
+            self.key = os.path.abspath(key)
+        if web:
+            self.web = os.path.abspath(web)
+        if record:
+            self.record = os.path.abspath(record)
+
+        if self.web:
+            os.chdir(self.web)
+        self.only_upgrade = not self.web
+
+        # Sanity checks
+        if not ssl and self.ssl_only:
+            raise Exception("No 'ssl' module and SSL-only specified")
+        if self.daemon and not resource:
+            raise Exception("Module 'resource' required to daemonize")
+
+        # Show configuration
+        self.msg("WebSocket server settings:")
+        self.msg("  - Listen on %s:%s",
+                self.listen_host, self.listen_port)
+        self.msg("  - Flash security policy server")
+        if self.web:
+            self.msg("  - Web server. Web root: %s", self.web)
+        if ssl:
+            if os.path.exists(self.cert):
+                self.msg("  - SSL/TLS support")
+                if self.ssl_only:
+                    self.msg("  - Deny non-SSL/TLS connections")
+            else:
+                self.msg("  - No SSL/TLS support (no cert file)")
+        else:
+            self.msg("  - No SSL/TLS support (no 'ssl' module)")
+        if self.daemon:
+            self.msg("  - Backgrounding (daemon)")
+        if self.record:
+            self.msg("  - Recording to '%s.*'", self.record)
+
+    #
+    # WebSocketServer static methods
+    #
+
+    @staticmethod
+    def get_logger():
+        return logging.getLogger("%s.%s" % (
+            WebSocketServer.log_prefix,
+            WebSocketServer.__class__.__name__))
+
+    @staticmethod
+    def socket(host, port=None, connect=False, prefer_ipv6=False,
+               unix_socket=None, use_ssl=False, tcp_keepalive=True,
+               tcp_keepcnt=None, tcp_keepidle=None, tcp_keepintvl=None):
+        """ Resolve a host (and optional port) to an IPv4 or IPv6
+        address. Create a socket. Bind to it if listen is set,
+        otherwise connect to it. Return the socket.
+        """
+        flags = 0
+        if host == '':
+            host = None
+        if connect and not (port or unix_socket):
+            raise Exception("Connect mode requires a port")
+        if use_ssl and not ssl:
+            raise Exception("SSL socket requested but Python SSL module not loaded.");
+        if not connect and use_ssl:
+            raise Exception("SSL only supported in connect mode (for now)")
+        if not connect:
+            flags = flags | socket.AI_PASSIVE
+
+        if not unix_socket:
+            addrs = socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM,
+                    socket.IPPROTO_TCP, flags)
+            if not addrs:
+                raise Exception("Could not resolve host '%s'" % host)
+            addrs.sort(key=lambda x: x[0])
+            if prefer_ipv6:
+                addrs.reverse()
+            sock = socket.socket(addrs[0][0], addrs[0][1])
+
+            if  tcp_keepalive:
+                sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
+                if tcp_keepcnt:
+                    sock.setsockopt(socket.SOL_TCP, socket.TCP_KEEPCNT,
+                                    tcp_keepcnt)
+                if tcp_keepidle:
+                    sock.setsockopt(socket.SOL_TCP, socket.TCP_KEEPIDLE,
+                                    tcp_keepidle)
+                if tcp_keepintvl:
+                    sock.setsockopt(socket.SOL_TCP, socket.TCP_KEEPINTVL,
+                                    tcp_keepintvl)
+
+            if connect:
+                sock.connect(addrs[0][4])
+                if use_ssl:
+                    sock = ssl.wrap_socket(sock)
+            else:
+                sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+                sock.bind(addrs[0][4])
+                sock.listen(100)
+        else:
+            sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
+            sock.connect(unix_socket)
+
+        return sock
+
+    @staticmethod
+    def daemonize(keepfd=None, chdir='/'):
+        os.umask(0)
+        if chdir:
+            os.chdir(chdir)
+        else:
+            os.chdir('/')
+        os.setgid(os.getgid())  # relinquish elevations
+        os.setuid(os.getuid())  # relinquish elevations
+
+        # Double fork to daemonize
+        if os.fork() > 0: os._exit(0)  # Parent exits
+        os.setsid()                    # Obtain new process group
+        if os.fork() > 0: os._exit(0)  # Parent exits
+
+        # Signal handling
+        signal.signal(signal.SIGTERM, signal.SIG_IGN)
+        signal.signal(signal.SIGINT, signal.SIG_IGN)
+
+        # Close open files
+        maxfd = resource.getrlimit(resource.RLIMIT_NOFILE)[1]
+        if maxfd == resource.RLIM_INFINITY: maxfd = 256
+        for fd in reversed(range(maxfd)):
+            try:
+                if fd != keepfd:
+                    os.close(fd)
+            except OSError:
+                _, exc, _ = sys.exc_info()
+                if exc.errno != errno.EBADF: raise
+
+        # Redirect I/O to /dev/null
+        os.dup2(os.open(os.devnull, os.O_RDWR), sys.stdin.fileno())
+        os.dup2(os.open(os.devnull, os.O_RDWR), sys.stdout.fileno())
+        os.dup2(os.open(os.devnull, os.O_RDWR), sys.stderr.fileno())
 
     def do_handshake(self, sock, address):
         """
@@ -560,10 +751,9 @@ Sec-WebSocket-Accept: %s\r
         - Send a WebSockets handshake server response.
         - Return the socket for this WebSocket client.
         """
-
-        stype = ""
-
         ready = select.select([sock], [], [], 3)[0]
+
+        
         if not ready:
             raise self.EClose("ignoring socket not ready")
         # Peek, but do not read the data so that we have a opportunity
@@ -580,7 +770,7 @@ Sec-WebSocket-Accept: %s\r
             sock.send(s2b(self.policy_response))
             raise self.EClose("Sending flash policy response")
 
-        elif handshake[0] in ("\x16", "\x80"):
+        elif handshake[0] in ("\x16", "\x80", 22, 128):
             # SSL wrap the connection
             if not ssl:
                 raise self.EClose("SSL connection but no 'ssl' module")
@@ -597,109 +787,45 @@ Sec-WebSocket-Accept: %s\r
             except ssl.SSLError:
                 _, x, _ = sys.exc_info()
                 if x.args[0] == ssl.SSL_ERROR_EOF:
-                    raise self.EClose("")
+                    if len(x.args) > 1:
+                        raise self.EClose(x.args[1])
+                    else:
+                        raise self.EClose("Got SSL_ERROR_EOF")
                 else:
                     raise
 
-            scheme = "wss"
-            stype = "SSL/TLS (wss://)"
-
         elif self.ssl_only:
             raise self.EClose("non-SSL connection received but disallowed")
 
         else:
             retsock = sock
-            scheme = "ws"
-            stype = "Plain non-SSL (ws://)"
-
-        wsh = WSRequestHandler(retsock, address, not self.web)
-        if wsh.last_code == 101:
-            # Continue on to handle WebSocket upgrade
-            pass
-        elif wsh.last_code == 405:
-            raise self.EClose("Normal web request received but disallowed")
-        elif wsh.last_code < 200 or wsh.last_code >= 300:
-            raise self.EClose(wsh.last_message)
-        elif self.verbose:
-            raise self.EClose(wsh.last_message)
-        else:
-            raise self.EClose("")
-
-        h = self.headers = wsh.headers
-        path = self.path = wsh.path
-
-        prot = 'WebSocket-Protocol'
-        protocols = h.get('Sec-'+prot, h.get(prot, '')).split(',')
-
-        ver = h.get('Sec-WebSocket-Version')
-        if ver:
-            # HyBi/IETF version of the protocol
-
-            # HyBi-07 report version 7
-            # HyBi-08 - HyBi-12 report version 8
-            # HyBi-13 reports version 13
-            if ver in ['7', '8', '13']:
-                self.version = "hybi-%02d" % int(ver)
-            else:
-                raise self.EClose('Unsupported protocol version %s' % ver)
-
-            key = h['Sec-WebSocket-Key']
-
-            # Choose binary if client supports it
-            if 'binary' in protocols:
-                self.base64 = False
-            elif 'base64' in protocols:
-                self.base64 = True
-            else:
-                raise self.EClose("Client must support 'binary' or 'base64' protocol")
-
-            # Generate the hash value for the accept header
-            accept = b64encode(sha1(s2b(key + self.GUID)).digest())
-
-            response = self.server_handshake_hybi % b2s(accept)
-            if self.base64:
-                response += "Sec-WebSocket-Protocol: base64\r\n"
-            else:
-                response += "Sec-WebSocket-Protocol: binary\r\n"
-            response += "\r\n"
-
-        else:
-            # Hixie version of the protocol (75 or 76)
-
-            if h.get('key3'):
-                trailer = self.gen_md5(h)
-                pre = "Sec-"
-                self.version = "hixie-76"
-            else:
-                trailer = ""
-                pre = ""
-                self.version = "hixie-75"
 
-            # We only support base64 in Hixie era
-            self.base64 = True
+        # If the address is like (host, port), we are extending it
+        # with a flag indicating SSL. Not many other options
+        # available...
+        if len(address) == 2:
+            address = (address[0], address[1], (retsock != sock))
 
-            response = self.server_handshake_hixie % (pre,
-                    h['Origin'], pre, scheme, h['Host'], path)
+        self.RequestHandlerClass(retsock, address, self)
 
-            if 'base64' in protocols:
-                response += "%sWebSocket-Protocol: base64\r\n" % pre
-            else:
-                self.msg("Warning: client does not report 'base64' protocol support")
-            response += "\r\n" + trailer
+        # Return the WebSockets socket which may be SSL wrapped
+        return retsock
 
-        self.msg("%s: %s WebSocket connection" % (address[0], stype))
-        self.msg("%s: Version %s, base64: '%s'" % (address[0],
-            self.version, self.base64))
-        if self.path != '/':
-            self.msg("%s: Path: '%s'" % (address[0], self.path))
+    #
+    # WebSocketServer logging/output functions
+    #
 
+    def msg(self, *args, **kwargs):
+        """ Output message as info """
+        self.logger.log(logging.INFO, *args, **kwargs)
 
-        # Send server WebSockets handshake response
-        #self.msg("sending response [%s]" % response)
-        retsock.send(s2b(response))
+    def vmsg(self, *args, **kwargs):
+        """ Same as msg() but as debug. """
+        self.logger.log(logging.DEBUG, *args, **kwargs)
 
-        # Return the WebSockets socket which may be SSL wrapped
-        return retsock
+    def warn(self, *args, **kwargs):
+        """ Same as msg() but as warning. """
+        self.logger.log(logging.WARN, *args, **kwargs)
 
 
     #
@@ -714,6 +840,12 @@ Sec-WebSocket-Accept: %s\r
         #self.vmsg("Running poll()")
         pass
 
+    def terminate(self):
+        raise self.Terminate()
+
+    def multiprocessing_SIGCHLD(self, sig, stack):
+        self.vmsg('Reaing zombies, active child count is %s', len(multiprocessing.active_children()))
+
     def fallback_SIGCHLD(self, sig, stack):
         # Reap zombies when using os.fork() (python 2.4)
         self.vmsg("Got SIGCHLD, reaping zombies")
@@ -727,184 +859,172 @@ Sec-WebSocket-Accept: %s\r
 
     def do_SIGINT(self, sig, stack):
         self.msg("Got SIGINT, exiting")
-        sys.exit(0)
+        self.terminate()
+
+    def do_SIGTERM(self, sig, stack):
+        self.msg("Got SIGTERM, exiting")
+        self.terminate()
 
     def top_new_client(self, startsock, address):
         """ Do something with a WebSockets client connection. """
-        # Initialize per client settings
-        self.send_parts = []
-        self.recv_part  = None
-        self.base64     = False
-        self.rec        = None
-        self.start_time = int(time.time()*1000)
-
-        # handler process
+        # handler process        
+        client = None
         try:
             try:
-                self.client = self.do_handshake(startsock, address)
-
-                if self.record:
-                    # Record raw frame data as JavaScript array
-                    fname = "%s.%s" % (self.record,
-                                        self.handler_id)
-                    self.msg("opening record file: %s" % fname)
-                    self.rec = open(fname, 'w+')
-                    self.rec.write("var VNC_frame_data = [\n")
-
-                self.ws_connection = True
-                self.new_client()
+                client = self.do_handshake(startsock, address)
             except self.EClose:
                 _, exc, _ = sys.exc_info()
                 # Connection was not a WebSockets connection
                 if exc.args[0]:
                     self.msg("%s: %s" % (address[0], exc.args[0]))
+            except WebSocketServer.Terminate:
+                raise
             except Exception:
                 _, exc, _ = sys.exc_info()
                 self.msg("handler exception: %s" % str(exc))
-                if self.verbose:
-                    self.msg(traceback.format_exc())
+                self.vmsg("exception", exc_info=True)
         finally:
-            if self.rec:
-                self.rec.write("'EOF']\n")
-                self.rec.close()
 
-            if self.client and self.client != startsock:
-                self.client.close()
-
-    def new_client(self):
-        """ Do something with a WebSockets client connection. """
-        raise("WebSocketServer.new_client() must be overloaded")
+            if client and client != startsock:
+                # Close the SSL wrapped socket
+                # Original socket closed by caller
+                client.close()
 
     def start_server(self):
         """
         Daemonize if requested. Listen for for connections. Run
         do_handshake() method for each connection. If the connection
-        is a WebSockets client then call new_client() method (which must
+        is a WebSockets client then call new_websocket_client() method (which must
         be overridden) for each new client connection.
         """
-        lsock = self.socket(self.listen_host, self.listen_port)
+        lsock = self.socket(self.listen_host, self.listen_port, False,
+                            self.prefer_ipv6,
+                            tcp_keepalive=self.tcp_keepalive,
+                            tcp_keepcnt=self.tcp_keepcnt,
+                            tcp_keepidle=self.tcp_keepidle,
+                            tcp_keepintvl=self.tcp_keepintvl)
 
         if self.daemon:
             self.daemonize(keepfd=lsock.fileno(), chdir=self.web)
 
         self.started()  # Some things need to happen after daemonizing
 
-        # Allow override of SIGINT
+        # Allow override of signals
+        original_signals = {
+            signal.SIGINT: signal.getsignal(signal.SIGINT),
+            signal.SIGTERM: signal.getsignal(signal.SIGTERM),
+            signal.SIGCHLD: signal.getsignal(signal.SIGCHLD),
+        }
         signal.signal(signal.SIGINT, self.do_SIGINT)
-        if not Process:
+        signal.signal(signal.SIGTERM, self.do_SIGTERM)
+        if not multiprocessing:
             # os.fork() (python 2.4) child reaper
             signal.signal(signal.SIGCHLD, self.fallback_SIGCHLD)
+        else:
+            # make sure that _cleanup is called when children die
+            # by calling active_children on SIGCHLD
+            signal.signal(signal.SIGCHLD, self.multiprocessing_SIGCHLD)
 
-        while True:
-            try:
+        last_active_time = self.launch_time
+        try:
+            while True:
                 try:
-                    self.client = None
-                    startsock = None
-                    pid = err = 0
-
-                    time_elapsed = time.time() - self.launch_time
-                    if self.timeout and time_elapsed > self.timeout:
-                        self.msg('listener exit due to --timeout %s'
-                                % self.timeout)
-                        break
-
                     try:
-                        self.poll()
+                        startsock = None
+                        pid = err = 0
+                        child_count = 0
+
+                        if multiprocessing:
+                            # Collect zombie child processes
+                            child_count = len(multiprocessing.active_children())
+
+                        time_elapsed = time.time() - self.launch_time
+                        if self.timeout and time_elapsed > self.timeout:
+                            self.msg('listener exit due to --timeout %s'
+                                    % self.timeout)
+                            break
 
-                        ready = select.select([lsock], [], [], 1)[0]
-                        if lsock in ready:
-                            startsock, address = lsock.accept()
-                        else:
-                            continue
-                    except Exception:
-                        _, exc, _ = sys.exc_info()
-                        if hasattr(exc, 'errno'):
-                            err = exc.errno
-                        elif hasattr(exc, 'args'):
-                            err = exc.args[0]
-                        else:
-                            err = exc[0]
-                        if err == errno.EINTR:
-                            self.vmsg("Ignoring interrupted syscall")
-                            continue
-                        else:
+                        if self.idle_timeout:
+                            idle_time = 0
+                            if child_count == 0:
+                                idle_time = time.time() - last_active_time
+                            else:
+                                idle_time = 0
+                                last_active_time = time.time()
+
+                            if idle_time > self.idle_timeout and child_count == 0:
+                                self.msg('listener exit due to --idle-timeout %s'
+                                            % self.idle_timeout)
+                                break
+
+                        try:
+                            self.poll()
+
+                            ready = select.select([lsock], [], [], 1)[0]
+                            if lsock in ready:
+                                startsock, address = lsock.accept()
+                            else:
+                                continue
+                        except self.Terminate:
                             raise
-
-                    if self.run_once:
-                        # Run in same process if run_once
-                        self.top_new_client(startsock, address)
-                        if self.ws_connection :
-                            self.msg('%s: exiting due to --run-once'
-                                    % address[0])
-                            break
-                    elif Process:
-                        self.vmsg('%s: new handler Process' % address[0])
-                        p = Process(target=self.top_new_client,
-                                args=(startsock, address))
-                        p.start()
-                        # child will not return
-                    else:
-                        # python 2.4
-                        self.vmsg('%s: forking handler' % address[0])
-                        pid = os.fork()
-                        if pid == 0:
-                            # child handler process
+                        except Exception:
+                            _, exc, _ = sys.exc_info()
+                            if hasattr(exc, 'errno'):
+                                err = exc.errno
+                            elif hasattr(exc, 'args'):
+                                err = exc.args[0]
+                            else:
+                                err = exc[0]
+                            if err == errno.EINTR:
+                                self.vmsg("Ignoring interrupted syscall")
+                                continue
+                            else:
+                                raise
+
+                        if self.run_once:
+                            # Run in same process if run_once
                             self.top_new_client(startsock, address)
-                            break  # child process exits
-
-                    # parent process
-                    self.handler_id += 1
-
-                except KeyboardInterrupt:
-                    _, exc, _ = sys.exc_info()
-                    print("In KeyboardInterrupt")
-                    pass
-                except SystemExit:
-                    _, exc, _ = sys.exc_info()
-                    print("In SystemExit")
-                    break
-                except Exception:
-                    _, exc, _ = sys.exc_info()
-                    self.msg("handler exception: %s" % str(exc))
-                    if self.verbose:
-                        self.msg(traceback.format_exc())
-
-            finally:
-                if startsock:
-                    startsock.close()
-
-
-# HTTP handler with WebSocket upgrade support
-class WSRequestHandler(SimpleHTTPRequestHandler):
-    def __init__(self, req, addr, only_upgrade=False):
-        self.only_upgrade = only_upgrade # only allow upgrades
-        SimpleHTTPRequestHandler.__init__(self, req, addr, object())
+                            if self.ws_connection :
+                                self.msg('%s: exiting due to --run-once'
+                                        % address[0])
+                                break
+                        elif multiprocessing:
+                            self.vmsg('%s: new handler Process' % address[0])
+                            p = multiprocessing.Process(
+                                    target=self.top_new_client,
+                                    args=(startsock, address))
+                            p.start()
+                            # child will not return
+                        else:
+                            # python 2.4
+                            self.vmsg('%s: forking handler' % address[0])
+                            pid = os.fork()
+                            if pid == 0:
+                                # child handler process
+                                self.top_new_client(startsock, address)
+                                break  # child process exits
+
+                        # parent process
+                        self.handler_id += 1
+
+                    except (self.Terminate, SystemExit, KeyboardInterrupt):
+                        self.msg("In exit")
+                        break
+                    except Exception:
+                        self.msg("handler exception: %s", str(exc))
+                        self.vmsg("exception", exc_info=True)
 
-    def do_GET(self):
-        if (self.headers.get('upgrade') and
-                self.headers.get('upgrade').lower() == 'websocket'):
-
-            if (self.headers.get('sec-websocket-key1') or
-                    self.headers.get('websocket-key1')):
-                # For Hixie-76 read out the key hash
-                self.headers.__setitem__('key3', self.rfile.read(8))
-
-            # Just indicate that an WebSocket upgrade is needed
-            self.last_code = 101
-            self.last_message = "101 Switching Protocols"
-        elif self.only_upgrade:
-            # Normal web request responses are disabled
-            self.last_code = 405
-            self.last_message = "405 Method Not Allowed"
-        else:
-            SimpleHTTPRequestHandler.do_GET(self)
+                finally:
+                    if startsock:
+                        startsock.close()
+        finally:
+            # Close listen port
+            self.vmsg("Closing socket listening at %s:%s",
+                      self.listen_host, self.listen_port)
+            lsock.close()
 
-    def send_response(self, code, message=None):
-        # Save the status code
-        self.last_code = code
-        SimpleHTTPRequestHandler.send_response(self, code, message)
+            # Restore signals
+            for sig, func in original_signals.items():
+                signal.signal(sig, func)
 
-    def log_message(self, f, *args):
-        # Save instead of printing
-        self.last_message = f % args