- use logger, not print statement

- more document.
- add JavaScript WebSocket-like API
- add examples
This commit is contained in:
liris 2011-01-06 18:02:11 +09:00
parent cc63079395
commit 2abb1c5f90
5 changed files with 190 additions and 22 deletions

5
README

@ -8,7 +8,9 @@ Installation
=============
This module is tested on only Python 2.7.
Type "python setup.py install" to install.
Type "python setup.py install" or "pip install websocket-client" to install.
This module does not depend on any other module.
Example
========
@ -22,3 +24,4 @@ Example
print "Reeiving..."
result = ws.recv()
print "Received '%s'" % result
ws.close()

12
examples/echo_client.py Normal file

@ -0,0 +1,12 @@
import websocket
if __name__ == "__main__":
websocket.enableTrace(True)
ws = websocket.create_connection("ws://localhost:5000/chat")
print "Sending 'Hello, World'..."
ws.send("Hello, World")
print "Sent"
print "Receiving..."
result = ws.recv()
print "Received '%s'" % result
ws.close()

@ -0,0 +1,33 @@
import websocket
import thread
import time
def on_message(ws, message):
print message
def on_error(ws, error):
print error
def on_close(ws):
print "### closed ###"
def on_open(ws):
def run(*args):
for i in range(3):
time.sleep(1)
ws.send("Hello %d" % i)
time.sleep(1)
ws.close()
print "thread terminating..."
thread.start_new_thread(run, ())
if __name__ == "__main__":
websocket.enableTrace(True)
ws = websocket.WebSocketApp("ws://localhost:5000/chat",
on_message = on_message,
on_error = on_error,
on_close = on_close)
ws.on_open = on_open
ws.run_forever()

@ -33,7 +33,7 @@ class HeaderSockMock(StringSockMock):
class WebSocketTest(unittest.TestCase):
def setUp(self):
pass
ws.enableTrace(True)
def tearDown(self):
pass

@ -3,8 +3,11 @@ from urlparse import urlparse
import random
import struct
import md5
import logging
logger = logging.getLogger()
class WebSocketException(Exception):
pass
@ -15,8 +18,15 @@ default_timeout = None
traceEnabled = False
def enableTrace(tracable):
"""
turn on/off the tracability.
"""
global traceEnabled
traceEnabled = tracable
if tracable:
if not logger.handlers:
logger.addHandler(logging.StreamHandler())
logger.setLevel(logging.DEBUG)
def setdefaulttimeout(timeout):
"""
@ -119,7 +129,7 @@ HEADERS_TO_EXIST_FOR_HIXIE75 = [
"websocket-location",
]
class SSLSocketWrapper(object):
class _SSLSocketWrapper(object):
def __init__(self, sock):
self.ssl = socket.ssl(sock)
@ -130,6 +140,23 @@ class SSLSocketWrapper(object):
return self.ssl.write(payload)
class WebSocket(object):
"""
Low level WebSocket interface.
This class is based on
The WebSocket protocol draft-hixie-thewebsocketprotocol-76
http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76
We can connect to the websocket server and send/recieve data.
The following example is a echo client.
>>> import websocket
>>> ws = websocket.WebSocket()
>>> ws.Connect("ws://localhost:8080/echo")
>>> ws.send("Hello, Server")
>>> ws.recv()
'Hello, Server'
>>> ws.close()
"""
def __init__(self):
"""
Initalize WebSocket object.
@ -157,7 +184,7 @@ class WebSocket(object):
# TODO: we need to support proxy
self.sock.connect((hostname, port))
if is_secure:
self.io_sock = SSLSocketWrapper(self.sock)
self.io_sock = _SSLSocketWrapper(self.sock)
self._handshake(hostname, port, resource, **options)
def _handshake(self, host, port, resource, **options):
@ -187,9 +214,9 @@ class WebSocket(object):
header_str = "\r\n".join(headers)
sock.send(header_str)
if traceEnabled:
print "--- request header ---"
print header_str
print "-----------------------"
logger.debug( "--- request header ---")
logger.debug( header_str)
logger.debug("-----------------------")
status, resp_headers = self._read_headers()
if status != 101:
@ -209,8 +236,8 @@ class WebSocket(object):
self.connected = True
def _validate_resp(self, number_1, number_2, key3, resp):
challenge = struct.pack("!i", number_1)
challenge += struct.pack("!i", number_2)
challenge = struct.pack("!I", number_1)
challenge += struct.pack("!I", number_2)
challenge += key3
digest = md5.md5(challenge).digest()
@ -219,9 +246,9 @@ class WebSocket(object):
def _get_resp(self):
result = self._recv(16)
if traceEnabled:
print "--- challenge response result ---"
print repr(result)
print "---------------------------------"
logger.debug("--- challenge response result ---")
logger.debug(repr(result))
logger.debug("---------------------------------")
return result
@ -255,7 +282,7 @@ class WebSocket(object):
status = None
headers = {}
if traceEnabled:
print "--- response header ---"
logger.debug("--- response header ---")
while True:
line = self._recv_line()
@ -263,7 +290,7 @@ class WebSocket(object):
break
line = line.strip()
if traceEnabled:
print line
logger.debug(line)
if not status:
status_info = line.split(" ", 2)
status = int(status_info[1])
@ -276,7 +303,7 @@ class WebSocket(object):
raise WebSocketException("Invalid header")
if traceEnabled:
print "-----------------------"
logger.debug("-----------------------")
return status, headers
@ -286,13 +313,18 @@ class WebSocket(object):
"""
if isinstance(payload, unicode):
payload = payload.encode("utf-8")
self.io_sock.send("".join(["\x00", payload, "\xff"]))
data = "".join(["\x00", payload, "\xff"])
self.io_sock.send(data)
if traceEnabled:
logger.debug("send: " + repr(data))
def recv(self):
"""
Reeive utf-8 string data from the server.
"""
b = self._recv(1)
if enableTrace:
logger.debug("recv frame: " + repr(b))
frame_type = ord(b)
if frame_type == 0x00:
bytes = []
@ -303,11 +335,15 @@ class WebSocket(object):
else:
bytes.append(b)
return "".join(bytes)
elif frame_type > 0x80:
elif 0x80 < frame_type < 0xff:
# which frame type is valid?
length = self._read_length()
bytes = self._recv_strict(length)
return bytes
elif frame_type == 0xff:
n = self._recv(1)
self._closeInternal()
return None
else:
raise WebSocketException("Invalid frame type")
@ -328,11 +364,22 @@ class WebSocket(object):
if self.connected:
try:
self.io_sock.send("\xff\x00")
result = self._recv(2)
if result != "\xff\x00":
logger.error("bad closing Handshake")
timeout = self.sock.gettimeout()
self.sock.settimeout(1)
try:
result = self._recv(2)
if result != "\xff\x00":
logger.error("bad closing Handshake")
except:
pass
self.sock.settimeout(timeout)
self.sock.shutdown(socket.SHUT_RDWR)
except:
pass
self._closeInternal()
def _closeInternal(self):
self.connected = False
self.sock.close()
self.io_sock = self.sock
@ -360,11 +407,83 @@ class WebSocket(object):
break
return "".join(line)
class WebSocketApp(object):
"""
Higher level of APIs are provided.
The interface is like JavaScript WebSocket object.
"""
def __init__(self, url,
on_open = None, on_message = None, on_error = None,
on_close = None):
"""
url: websocket url.
on_open: callable object which is called at opening websocket.
this function has one argument. The arugment is this class object.
on_message: callbale object which is called when recieved data.
on_message has 2 arguments.
The 1st arugment is this class object.
The passing 2nd arugment is utf-8 string which we get from the server.
on_error: callable object which is called when we get error.
on_error has 2 arguments.
The 1st arugment is this class object.
The passing 2nd arugment is exception object.
on_close: callable object which is called when closed the connection.
this function has one argument. The arugment is this class object.
"""
self.url = url
self.on_open = on_open
self.on_message = on_message
self.on_error = on_error
self.on_close = on_close
self.sock = None
def send(self, data):
"""
send message. data must be utf-8 string or unicode.
"""
self.sock.send(data)
def close(self):
"""
close websocket connection.
"""
self.sock.close()
def run_forever(self):
"""
run event loop for WebSocket framework.
This loop is infinite loop and is alive during websocket is available.
"""
if self.sock:
raise WebSocketException("socket is already opened")
try:
self.sock = WebSocket()
self.sock.connect(self.url)
self._run_with_no_err(self.on_open)
while True:
data = self.sock.recv()
if data is None:
break
self._run_with_no_err(self.on_message, data)
except Exception, e:
self._run_with_no_err(self.on_error, e)
finally:
self.sock.close()
self._run_with_no_err(self.on_close)
self.sock = None
def _run_with_no_err(self, callback, *args):
if callback:
try:
callback(self, *args)
except Exception, e:
if logger.isEnabledFor(logging.DEBUG):
logger.error(e)
if __name__ == "__main__":
enableTrace(True)
# ws = create_connection("ws://localhost:8080/echo")
#ws = create_connection("ws://localhost:8080/echo")
ws = create_connection("ws://localhost:5000/chat")
print "Sending 'Hello, World'..."
ws.send("Hello, World")
@ -372,6 +491,7 @@ if __name__ == "__main__":
print "Receiving..."
result = ws.recv()
print "Received '%s'" % result
ws.close()