diff --git a/ChangeLog b/ChangeLog index bbd94e1..44572f3 100644 --- a/ChangeLog +++ b/ChangeLog @@ -7,6 +7,12 @@ ChangeLog - fixed timeout+ssl error handling bug on python 2.7.10 (#190) - add proxy support to wsdump.py (#194) - use wsaccel if available (#193) + - add support for ssl cert chains to support client certs (#195) + - fix string formatting in exception (#196) + - fix typo in README.rst (#197) + - introduce on_data callback to pass data type. (#198) + - WebSocketBadStatusException for Handshake error (#199) + - set close timeout (#192) - 0.32.0 diff --git a/README.rst b/README.rst index 495a043..8ef2b1b 100644 --- a/README.rst +++ b/README.rst @@ -62,7 +62,7 @@ Low Level API example:: print "Sending 'Hello, World'..." ws.send("Hello, World") print "Sent" - print "Reeiving..." + print "Receiving..." result = ws.recv() print "Received '%s'" % result ws.close() diff --git a/websocket/_app.py b/websocket/_app.py index d334d97..2ebef0e 100644 --- a/websocket/_app.py +++ b/websocket/_app.py @@ -48,7 +48,8 @@ class WebSocketApp(object): on_close=None, on_ping=None, on_pong=None, on_cont_message=None, keep_running=True, get_mask_key=None, cookie=None, - subprotocols=None): + subprotocols=None, + on_data=None): """ url: websocket url. header: custom header for websocket handshake. @@ -71,6 +72,14 @@ class WebSocketApp(object): The passing 2nd arugment is utf-8 string which we get from the server. The 3rd arugment is continue flag. if 0, the data continue to next frame data + on_data: callback object which is called when a message recieved. + This is called before on_message or on_cont_message, + and then on_message or on_cont_message is called. + on_data has 4 argument. + The 1st arugment is this class object. + The passing 2nd arugment is utf-8 string which we get from the server. + The 3rd argument is data type. ABNF.OPCODE_TEXT or ABNF.OPCODE_BINARY will be came. + The 4rd arugment is continue flag. if 0, the data continue keep_running: a boolean flag indicating whether the app's main loop should keep running, defaults to True get_mask_key: a callable to produce new mask keys, @@ -82,6 +91,7 @@ class WebSocketApp(object): self.cookie = cookie self.on_open = on_open self.on_message = on_message + self.on_data = on_data self.on_error = on_error self.on_close = on_close self.on_ping = on_ping @@ -192,11 +202,13 @@ class WebSocketApp(object): elif op_code == ABNF.OPCODE_PONG: self._callback(self.on_pong, frame.data) elif op_code == ABNF.OPCODE_CONT and self.on_cont_message: + self._callback(self.on_data, data, frame.opcode, frame.fin) self._callback(self.on_cont_message, frame.data, frame.fin) else: data = frame.data if six.PY3 and frame.opcode == ABNF.OPCODE_TEXT: data = data.decode("utf-8") + self._callback(self.on_data, data, frame.opcode, True) self._callback(self.on_message, data) except Exception as e: self._callback(self.on_error, e) diff --git a/websocket/_core.py b/websocket/_core.py index 4d7ddec..7524f94 100644 --- a/websocket/_core.py +++ b/websocket/_core.py @@ -422,13 +422,16 @@ class WebSocket(object): self.connected = False self.send(struct.pack('!H', status) + reason, ABNF.OPCODE_CLOSE) - def close(self, status=STATUS_NORMAL, reason=six.b("")): + def close(self, status=STATUS_NORMAL, reason=six.b(""), timeout=3): """ Close Websocket object status: status code to send. see STATUS_XXX. reason: the reason to close. This must be string. + + timeout: timeout until recieve a close frame. + If None, it will wait forever until recieve a close frame. """ if self.connected: if status < 0 or status >= ABNF.LENGTH_16: @@ -437,8 +440,8 @@ class WebSocket(object): try: self.connected = False self.send(struct.pack('!H', status) + reason, ABNF.OPCODE_CLOSE) - timeout = self.sock.gettimeout() - self.sock.settimeout(3) + sock_timeout = self.sock.gettimeout() + self.sock.settimeout(timeout) try: frame = self.recv_frame() if isEnabledForError(): @@ -447,7 +450,7 @@ class WebSocket(object): error("close status: " + repr(recv_status)) except: pass - self.sock.settimeout(timeout) + self.sock.settimeout(sock_timeout) self.sock.shutdown(socket.SHUT_RDWR) except: pass diff --git a/websocket/_exceptions.py b/websocket/_exceptions.py index 9b1791d..ffc41ab 100644 --- a/websocket/_exceptions.py +++ b/websocket/_exceptions.py @@ -31,18 +31,21 @@ class WebSocketException(Exception): """ pass + class WebSocketProtocolException(WebSocketException): """ If the webscoket protocol is invalid, this exception will be raised. """ pass + class WebSocketPayloadException(WebSocketException): """ If the webscoket payload is invalid, this exception will be raised. """ pass + class WebSocketConnectionClosedException(WebSocketException): """ If remote host closed the connection or some network error happened, @@ -50,12 +53,14 @@ class WebSocketConnectionClosedException(WebSocketException): """ pass + class WebSocketTimeoutException(WebSocketException): """ WebSocketTimeoutException will be raised at socket timeout during read/write data. """ pass + class WebSocketProxyException(WebSocketException): """ WebSocketProxyException will be raised when proxy error occured. @@ -63,3 +68,10 @@ class WebSocketProxyException(WebSocketException): pass +class WebSocketBadStatusException(WebSocketException): + """ + WebSocketBadStatusException will be raised when we get bad handshake status code. + """ + def __init__(self, message, status_code): + super(WebSocketBadStatusException, self).__init__(message % status_code) + self.status_code = status_code diff --git a/websocket/_handshake.py b/websocket/_handshake.py index d8876e7..4d20fed 100644 --- a/websocket/_handshake.py +++ b/websocket/_handshake.py @@ -108,7 +108,7 @@ def _get_handshake_headers(resource, host, port, options): def _get_resp_headers(sock, success_status=101): status, resp_headers = read_headers(sock) if status != success_status: - raise WebSocketException("Handshake status %d" % status) + raise WebSocketBadStatusException("Handshake status %d", status) return status, resp_headers _HEADERS_TO_CHECK = { diff --git a/websocket/_http.py b/websocket/_http.py index 5c4a638..7da328e 100644 --- a/websocket/_http.py +++ b/websocket/_http.py @@ -132,6 +132,9 @@ def _wrap_sni_socket(sock, sslopt, hostname, check_hostname): context.check_hostname = check_hostname if 'ciphers' in sslopt: context.set_ciphers(sslopt['ciphers']) + if 'cert_chain' in sslopt : + certfile,keyfile,password = sslopt['cert_chain'] + context.load_cert_chain(certfile, keyfile, password) return context.wrap_socket( sock, @@ -183,7 +186,7 @@ def _tunnel(sock, host, port, auth): if status != 200: raise WebSocketProxyException( - "failed CONNECT via proxy status: %r" + status) + "failed CONNECT via proxy status: %r" % status) return sock