- 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
View File

@@ -8,7 +8,9 @@ Installation
============= =============
This module is tested on only Python 2.7. 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 Example
======== ========
@@ -22,3 +24,4 @@ Example
print "Reeiving..." print "Reeiving..."
result = ws.recv() result = ws.recv()
print "Received '%s'" % result print "Received '%s'" % result
ws.close()

12
examples/echo_client.py Normal file
View 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()

View File

@@ -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()

View File

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

View File

@@ -3,8 +3,11 @@ from urlparse import urlparse
import random import random
import struct import struct
import md5 import md5
import logging
logger = logging.getLogger()
class WebSocketException(Exception): class WebSocketException(Exception):
pass pass
@@ -15,8 +18,15 @@ default_timeout = None
traceEnabled = False traceEnabled = False
def enableTrace(tracable): def enableTrace(tracable):
"""
turn on/off the tracability.
"""
global traceEnabled global traceEnabled
traceEnabled = tracable traceEnabled = tracable
if tracable:
if not logger.handlers:
logger.addHandler(logging.StreamHandler())
logger.setLevel(logging.DEBUG)
def setdefaulttimeout(timeout): def setdefaulttimeout(timeout):
""" """
@@ -119,7 +129,7 @@ HEADERS_TO_EXIST_FOR_HIXIE75 = [
"websocket-location", "websocket-location",
] ]
class SSLSocketWrapper(object): class _SSLSocketWrapper(object):
def __init__(self, sock): def __init__(self, sock):
self.ssl = socket.ssl(sock) self.ssl = socket.ssl(sock)
@@ -130,6 +140,23 @@ class SSLSocketWrapper(object):
return self.ssl.write(payload) return self.ssl.write(payload)
class WebSocket(object): 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): def __init__(self):
""" """
Initalize WebSocket object. Initalize WebSocket object.
@@ -157,7 +184,7 @@ class WebSocket(object):
# TODO: we need to support proxy # TODO: we need to support proxy
self.sock.connect((hostname, port)) self.sock.connect((hostname, port))
if is_secure: if is_secure:
self.io_sock = SSLSocketWrapper(self.sock) self.io_sock = _SSLSocketWrapper(self.sock)
self._handshake(hostname, port, resource, **options) self._handshake(hostname, port, resource, **options)
def _handshake(self, host, port, resource, **options): def _handshake(self, host, port, resource, **options):
@@ -187,9 +214,9 @@ class WebSocket(object):
header_str = "\r\n".join(headers) header_str = "\r\n".join(headers)
sock.send(header_str) sock.send(header_str)
if traceEnabled: if traceEnabled:
print "--- request header ---" logger.debug( "--- request header ---")
print header_str logger.debug( header_str)
print "-----------------------" logger.debug("-----------------------")
status, resp_headers = self._read_headers() status, resp_headers = self._read_headers()
if status != 101: if status != 101:
@@ -209,8 +236,8 @@ class WebSocket(object):
self.connected = True self.connected = True
def _validate_resp(self, number_1, number_2, key3, resp): def _validate_resp(self, number_1, number_2, key3, resp):
challenge = struct.pack("!i", number_1) challenge = struct.pack("!I", number_1)
challenge += struct.pack("!i", number_2) challenge += struct.pack("!I", number_2)
challenge += key3 challenge += key3
digest = md5.md5(challenge).digest() digest = md5.md5(challenge).digest()
@@ -219,9 +246,9 @@ class WebSocket(object):
def _get_resp(self): def _get_resp(self):
result = self._recv(16) result = self._recv(16)
if traceEnabled: if traceEnabled:
print "--- challenge response result ---" logger.debug("--- challenge response result ---")
print repr(result) logger.debug(repr(result))
print "---------------------------------" logger.debug("---------------------------------")
return result return result
@@ -255,7 +282,7 @@ class WebSocket(object):
status = None status = None
headers = {} headers = {}
if traceEnabled: if traceEnabled:
print "--- response header ---" logger.debug("--- response header ---")
while True: while True:
line = self._recv_line() line = self._recv_line()
@@ -263,7 +290,7 @@ class WebSocket(object):
break break
line = line.strip() line = line.strip()
if traceEnabled: if traceEnabled:
print line logger.debug(line)
if not status: if not status:
status_info = line.split(" ", 2) status_info = line.split(" ", 2)
status = int(status_info[1]) status = int(status_info[1])
@@ -276,7 +303,7 @@ class WebSocket(object):
raise WebSocketException("Invalid header") raise WebSocketException("Invalid header")
if traceEnabled: if traceEnabled:
print "-----------------------" logger.debug("-----------------------")
return status, headers return status, headers
@@ -286,13 +313,18 @@ class WebSocket(object):
""" """
if isinstance(payload, unicode): if isinstance(payload, unicode):
payload = payload.encode("utf-8") 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): def recv(self):
""" """
Reeive utf-8 string data from the server. Reeive utf-8 string data from the server.
""" """
b = self._recv(1) b = self._recv(1)
if enableTrace:
logger.debug("recv frame: " + repr(b))
frame_type = ord(b) frame_type = ord(b)
if frame_type == 0x00: if frame_type == 0x00:
bytes = [] bytes = []
@@ -303,11 +335,15 @@ class WebSocket(object):
else: else:
bytes.append(b) bytes.append(b)
return "".join(bytes) return "".join(bytes)
elif frame_type > 0x80: elif 0x80 < frame_type < 0xff:
# which frame type is valid? # which frame type is valid?
length = self._read_length() length = self._read_length()
bytes = self._recv_strict(length) bytes = self._recv_strict(length)
return bytes return bytes
elif frame_type == 0xff:
n = self._recv(1)
self._closeInternal()
return None
else: else:
raise WebSocketException("Invalid frame type") raise WebSocketException("Invalid frame type")
@@ -328,11 +364,22 @@ class WebSocket(object):
if self.connected: if self.connected:
try: try:
self.io_sock.send("\xff\x00") self.io_sock.send("\xff\x00")
result = self._recv(2) timeout = self.sock.gettimeout()
if result != "\xff\x00": self.sock.settimeout(1)
logger.error("bad closing Handshake") 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: except:
pass pass
self._closeInternal()
def _closeInternal(self):
self.connected = False
self.sock.close() self.sock.close()
self.io_sock = self.sock self.io_sock = self.sock
@@ -360,11 +407,83 @@ class WebSocket(object):
break break
return "".join(line) 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__": if __name__ == "__main__":
enableTrace(True) enableTrace(True)
# ws = create_connection("ws://localhost:8080/echo") #ws = create_connection("ws://localhost:8080/echo")
ws = create_connection("ws://localhost:5000/chat") ws = create_connection("ws://localhost:5000/chat")
print "Sending 'Hello, World'..." print "Sending 'Hello, World'..."
ws.send("Hello, World") ws.send("Hello, World")
@@ -372,6 +491,7 @@ if __name__ == "__main__":
print "Receiving..." print "Receiving..."
result = ws.recv() result = ws.recv()
print "Received '%s'" % result print "Received '%s'" % result
ws.close()