diff options
author | liris <liris.pp@gmail.com> | 2011-01-06 18:02:11 +0900 |
---|---|---|
committer | liris <liris.pp@gmail.com> | 2011-01-06 18:02:11 +0900 |
commit | 2abb1c5f90281eb85e28a491f236f7a27b2278ac (patch) | |
tree | cd04d251ea7c66ec1358422e51743b3e264daece | |
parent | cc630793950753ccb99a1d45ff676a3103bc0232 (diff) | |
download | websocket-client-2abb1c5f90281eb85e28a491f236f7a27b2278ac.tar.gz |
- use logger, not print statement
- more document.
- add JavaScript WebSocket-like API
- add examples
-rw-r--r-- | README | 5 | ||||
-rw-r--r-- | examples/echo_client.py | 12 | ||||
-rw-r--r-- | examples/echoapp_client.py | 33 | ||||
-rw-r--r-- | test_websocket.py | 2 | ||||
-rw-r--r-- | websocket.py | 160 |
5 files changed, 190 insertions, 22 deletions
@@ -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() diff --git a/examples/echo_client.py b/examples/echo_client.py new file mode 100644 index 0000000..f8f6bf3 --- /dev/null +++ b/examples/echo_client.py @@ -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() diff --git a/examples/echoapp_client.py b/examples/echoapp_client.py new file mode 100644 index 0000000..15552a3 --- /dev/null +++ b/examples/echoapp_client.py @@ -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() diff --git a/test_websocket.py b/test_websocket.py index 15598ed..be0f5e9 100644 --- a/test_websocket.py +++ b/test_websocket.py @@ -33,7 +33,7 @@ class HeaderSockMock(StringSockMock): class WebSocketTest(unittest.TestCase): def setUp(self): - pass + ws.enableTrace(True) def tearDown(self): pass diff --git a/websocket.py b/websocket.py index 1603e77..bb65761 100644 --- a/websocket.py +++ b/websocket.py @@ -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() |