Skip to content

Instantly share code, notes, and snippets.

@kanaka
Created May 17, 2011 17:53
Show Gist options
  • Save kanaka/976972 to your computer and use it in GitHub Desktop.
Save kanaka/976972 to your computer and use it in GitHub Desktop.
websockify python 3.X support
diff --git a/websocket.py b/websocket.py
index 09e7ee4..760070f 100755
--- a/websocket.py
+++ b/websocket.py
@@ -16,23 +16,34 @@ as taken from http://docs.python.org/dev/library/ssl.html#certificates
'''
-import sys, socket, ssl, struct, traceback, select
+import sys, socket, struct, traceback, select
+
+if sys.hexversion < 0x2060000: # python < 2.6
+ raise Exception("python 2.6 or greater required")
+elif 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')
+else:
+ from cStringIO import StringIO
+ from SimpleHTTPServer import SimpleHTTPRequestHandler
+ from urlparse import urlsplit
+ # No-ops
+ b2s = lambda buf: buf
+ s2b = lambda s: s
+
import os, resource, errno, signal # daemonizing
-from SimpleHTTPServer import SimpleHTTPRequestHandler
-from cStringIO import StringIO
+from cgi import parse_qsl
from base64 import b64encode, b64decode
-try:
- from hashlib import md5, sha1
-except:
- # Support python 2.4
- from md5 import md5
- from sha import sha as sha1
+from multiprocessing import Process
+from hashlib import md5, sha1
+
try:
import numpy, ctypes
-except:
+except ImportError:
numpy = ctypes = None
-from urlparse import urlsplit
-from cgi import parse_qsl
class WebSocketServer(object):
"""
@@ -88,20 +99,20 @@ Sec-WebSocket-Accept: %s\r
self.handler_id = 1
- print "WebSocket server settings:"
- print " - Listen on %s:%s" % (
- self.listen_host, self.listen_port)
- print " - Flash security policy server"
+ 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"
+ print(" - Web server")
if os.path.exists(self.cert):
- print " - SSL/TLS support"
+ print(" - SSL/TLS support")
if self.ssl_only:
- print " - Deny non-SSL/TLS connections"
+ print(" - Deny non-SSL/TLS connections")
else:
- print " - No SSL/TLS support (no cert file)"
+ print(" - No SSL/TLS support (no cert file)")
if self.daemon:
- print " - Backgrounding (daemon)"
+ print(" - Backgrounding (daemon)")
#
# WebSocketServer static methods
@@ -133,7 +144,8 @@ Sec-WebSocket-Accept: %s\r
try:
if fd != keepfd:
os.close(fd)
- except OSError, exc:
+ except OSError:
+ _, exc, _ = sys.exc_info()
if exc.errno != errno.EBADF: raise
# Redirect I/O to /dev/null
@@ -164,7 +176,7 @@ Sec-WebSocket-Accept: %s\r
elif payload_len >= 65536:
header = struct.pack('>BBQ', b1, 127, payload_len)
- #print "Encoded: %s" % repr(header + buf)
+ #print("Encoded: %s" % repr(header + buf))
return header + buf
@@ -238,7 +250,7 @@ Sec-WebSocket-Accept: %s\r
b = numpy.bitwise_xor(data, mask).tostring()
if ret['length'] % 4:
- print "Partial unmask"
+ print("Partial unmask")
mask = numpy.frombuffer(buf, dtype=numpy.dtype('B'),
offset=header_len, count=(ret['length'] % 4))
data = numpy.frombuffer(buf, dtype=numpy.dtype('B'),
@@ -247,14 +259,15 @@ Sec-WebSocket-Accept: %s\r
c = numpy.bitwise_xor(data, mask).tostring()
ret['payload'] = b + c
else:
- print "Unmasked frame:", repr(buf)
+ print("Unmasked frame: %s" % repr(buf))
ret['payload'] = buf[(header_len + has_mask * 4):full_len]
if base64 and ret['opcode'] in [1, 2]:
try:
ret['payload'] = b64decode(ret['payload'])
except:
- print "Exception while b64decoding buffer:", repr(buf)
+ print("Exception while b64decoding buffer: %s" %
+ repr(buf))
raise
if ret['opcode'] == 0x08:
@@ -268,11 +281,11 @@ Sec-WebSocket-Accept: %s\r
@staticmethod
def encode_hixie(buf):
- return "\x00" + b64encode(buf) + "\xff"
+ return s2b("\x00" + b2s(b64encode(buf)) + "\xff")
@staticmethod
def decode_hixie(buf):
- end = buf.find('\xff')
+ end = buf.find(s2b('\xff'))
return {'payload': b64decode(buf[1:end]),
'left': len(buf) - (end + 1)}
@@ -288,7 +301,8 @@ Sec-WebSocket-Accept: %s\r
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 md5(struct.pack('>II8s', num1, num2, key3)).digest()
+ return b2s(md5(struct.pack('>II8s',
+ int(num1), int(num2), key3)).digest())
#
# WebSocketServer logging/output functions
@@ -303,7 +317,7 @@ Sec-WebSocket-Accept: %s\r
def msg(self, msg):
""" Output message with handler_id prefix. """
if not self.daemon:
- print "% 3d: %s" % (self.handler_id, msg)
+ print("% 3d: %s" % (self.handler_id, msg))
def vmsg(self, msg):
""" Same as msg() but only if verbose. """
@@ -371,7 +385,7 @@ Sec-WebSocket-Accept: %s\r
if self.version.startswith("hybi"):
frame = self.decode_hybi(buf, base64=self.base64)
- #print "Received buf: %s, frame: %s" % (repr(buf), frame)
+ #print("Received buf: %s, frame: %s" % (repr(buf), frame))
if frame['payload'] == None:
# Incomplete/partial frame
@@ -395,7 +409,7 @@ Sec-WebSocket-Accept: %s\r
buf = buf[2:]
continue # No-op
- elif buf.count('\xff') == 0:
+ elif buf.count(s2b('\xff')) == 0:
# Partial frame
self.traffic("}.")
self.recv_part = buf
@@ -426,7 +440,7 @@ Sec-WebSocket-Accept: %s\r
self.client.send(buf)
elif self.version == "hixie-76":
- buf = self.encode_hixie('\xff\x00')
+ buf = s2b('\xff\x00')
self.client.send(buf)
# No orderly close for 75
@@ -462,7 +476,7 @@ Sec-WebSocket-Accept: %s\r
if handshake == "":
raise self.EClose("ignoring empty handshake")
- elif handshake.startswith("<policy-file-request/>"):
+ elif handshake.startswith(s2b("<policy-file-request/>")):
# Answer Flash policy request
handshake = sock.recv(1024)
sock.send(self.policy_response)
@@ -479,7 +493,8 @@ Sec-WebSocket-Accept: %s\r
server_side=True,
certfile=self.cert,
keyfile=self.key)
- except ssl.SSLError, x:
+ except ssl.SSLError:
+ _, x, _ = sys.exc_info()
if x.args[0] == ssl.SSL_ERROR_EOF:
raise self.EClose("")
else:
@@ -538,7 +553,7 @@ Sec-WebSocket-Accept: %s\r
raise self.EClose("Client must support 'binary' or 'base64' protocol")
# Generate the hash value for the accept header
- accept = b64encode(sha1(key + self.GUID).digest())
+ accept = b2s(b64encode(sha1(key + self.GUID).digest()))
response = self.server_handshake_hybi % accept
if self.base64:
@@ -577,7 +592,7 @@ Sec-WebSocket-Accept: %s\r
# Send server WebSockets handshake response
#self.msg("sending response [%s]" % response)
- retsock.send(response)
+ retsock.send(s2b(response))
# Return the WebSockets socket which may be SSL wrapped
return retsock
@@ -598,14 +613,15 @@ Sec-WebSocket-Accept: %s\r
def top_SIGCHLD(self, sig, stack):
# Reap zombies after calling child SIGCHLD handler
self.do_SIGCHLD(sig, stack)
- self.vmsg("Got SIGCHLD, reaping zombies")
- try:
- result = os.waitpid(-1, os.WNOHANG)
- while result[0]:
- self.vmsg("Reaped child process %s" % result[0])
- result = os.waitpid(-1, os.WNOHANG)
- except (OSError):
- pass
+ self.vmsg("Got SIGCHLD")
+# self.vmsg("Got SIGCHLD, reaping zombies")
+# try:
+# result = os.waitpid(-1, os.WNOHANG)
+# while result[0]:
+# self.vmsg("Reaped child process %s" % result[0])
+# result = os.waitpid(-1, os.WNOHANG)
+# except OSError:
+# pass
def do_SIGCHLD(self, sig, stack):
pass
@@ -614,7 +630,35 @@ Sec-WebSocket-Accept: %s\r
self.msg("Got SIGINT, exiting")
sys.exit(0)
- def new_client(self, client):
+ 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
+
+ # handler process
+ self.msg("here5")
+ try:
+ try:
+ self.client = self.do_handshake(startsock, address)
+ self.new_client()
+ 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 Exception:
+ _, exc, _ = sys.exc_info()
+ self.msg("handler exception: %s" % str(exc))
+ if self.verbose:
+ self.msg(traceback.format_exc())
+ finally:
+ if self.client and self.client != startsock:
+ self.client.close()
+ self.msg("here6")
+
+ def new_client(self):
""" Do something with a WebSockets client connection. """
raise("WebSocketServer.new_client() must be overloaded")
@@ -645,7 +689,7 @@ Sec-WebSocket-Accept: %s\r
try:
self.client = None
startsock = None
- pid = err = 0
+ err = 0
try:
self.poll()
@@ -655,9 +699,13 @@ Sec-WebSocket-Accept: %s\r
startsock, address = lsock.accept()
else:
continue
- except Exception, exc:
+ except Exception:
+ _, exc, _ = sys.exc_info()
+ print("here7")
if hasattr(exc, 'errno'):
err = exc.errno
+ elif hasattr(exc, 'args'):
+ err = exc.args[0]
else:
err = exc[0]
if err == errno.EINTR:
@@ -667,40 +715,26 @@ Sec-WebSocket-Accept: %s\r
raise
self.vmsg('%s: forking handler' % address[0])
- pid = os.fork()
-
- if pid == 0:
- # Initialize per client settings
- self.send_parts = []
- self.recv_part = None
- self.base64 = False
- # handler process
- self.client = self.do_handshake(
- startsock, address)
- self.new_client()
- else:
- # parent process
- self.handler_id += 1
-
- except self.EClose, exc:
- # Connection was not a WebSockets connection
- if exc.args[0]:
- self.msg("%s: %s" % (address[0], exc.args[0]))
- except KeyboardInterrupt, exc:
- pass
- except Exception, exc:
+ p = Process(target=self.top_new_client,
+ args=(startsock, address))
+ p.start()
+
+ # parent process
+ self.handler_id += 1
+
+ except Exception:
+ _, exc, _ = sys.exc_info()
self.msg("handler exception: %s" % str(exc))
if self.verbose:
self.msg(traceback.format_exc())
+ except KeyboardInterrupt:
+ _, exc, _ = sys.exc_info()
+ pass
finally:
- if self.client and self.client != startsock:
- self.client.close()
if startsock:
startsock.close()
- if pid == 0:
- break # Child process exits
# HTTP handler with WebSocket upgrade support
class WSRequestHandler(SimpleHTTPRequestHandler):
@@ -709,13 +743,13 @@ class WSRequestHandler(SimpleHTTPRequestHandler):
SimpleHTTPRequestHandler.__init__(self, req, addr, object())
def do_GET(self):
- if (self.headers.has_key('upgrade') and
+ 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.dict['key3'] = self.rfile.read(8)
+ self.headers.__setitem__('key3', self.rfile.read(8))
# Just indicate that an WebSocket upgrade is needed
self.last_code = 101
diff --git a/websockify b/websockify
index 6ec7c80..88d164b 100755
--- a/websockify
+++ b/websockify
@@ -74,7 +74,7 @@ Traffic Legend:
WebSocketServer.__init__(self, *args, **kwargs)
def run_wrap_cmd(self):
- print "Starting '%s'" % " ".join(self.wrap_cmd)
+ print("Starting '%s'" % " ".join(self.wrap_cmd))
self.wrap_times.append(time.time())
self.wrap_times.pop(0)
self.cmd = subprocess.Popen(
@@ -88,14 +88,14 @@ Traffic Legend:
# Need to call wrapped command after daemonization so we can
# know when the wrapped command exits
if self.wrap_cmd:
- print " - proxying from %s:%s to '%s' (port %s)\n" % (
+ print(" - proxying from %s:%s to '%s' (port %s)\n" % (
self.listen_host, self.listen_port,
- " ".join(self.wrap_cmd), self.target_port)
+ " ".join(self.wrap_cmd), self.target_port))
self.run_wrap_cmd()
else:
- print " - proxying from %s:%s to %s:%s\n" % (
+ print(" - proxying from %s:%s to %s:%s\n" % (
self.listen_host, self.listen_port,
- self.target_host, self.target_port)
+ self.target_host, self.target_port))
def poll(self):
# If we are wrapping a command, check it's status
@@ -118,7 +118,7 @@ Traffic Legend:
if (now - avg) < 10:
# 3 times in the last 10 seconds
if self.spawn_message:
- print "Command respawning too fast"
+ print("Command respawning too fast")
self.spawn_message = False
else:
self.run_wrap_cmd()
@@ -154,7 +154,7 @@ Traffic Legend:
tsock.connect((self.target_host, self.target_port))
if self.verbose and not self.daemon:
- print self.traffic_legend
+ print(self.traffic_legend)
# Start proxying
try:
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment