Merge remote-tracking branch 'origin/master' into tweaks

* origin/master:
  fixed #192 introduced close timeout
  fixed #199 WebSocketBadStatusException for handshake error
  fixed #198 introduce on_data callback to pass data type.
  documentation
  add space.
  Typo fix in README.rst
  Fix string formatting in exception
  add support for ssl cert chains to support client certs
This commit is contained in:
Noah Levitt
2015-09-23 00:46:21 +00:00
7 changed files with 44 additions and 8 deletions

View File

@@ -7,6 +7,12 @@ ChangeLog
- fixed timeout+ssl error handling bug on python 2.7.10 (#190) - fixed timeout+ssl error handling bug on python 2.7.10 (#190)
- add proxy support to wsdump.py (#194) - add proxy support to wsdump.py (#194)
- use wsaccel if available (#193) - 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 - 0.32.0

View File

@@ -62,7 +62,7 @@ Low Level API example::
print "Sending 'Hello, World'..." print "Sending 'Hello, World'..."
ws.send("Hello, World") ws.send("Hello, World")
print "Sent" print "Sent"
print "Reeiving..." print "Receiving..."
result = ws.recv() result = ws.recv()
print "Received '%s'" % result print "Received '%s'" % result
ws.close() ws.close()

View File

@@ -48,7 +48,8 @@ class WebSocketApp(object):
on_close=None, on_ping=None, on_pong=None, on_close=None, on_ping=None, on_pong=None,
on_cont_message=None, on_cont_message=None,
keep_running=True, get_mask_key=None, cookie=None, keep_running=True, get_mask_key=None, cookie=None,
subprotocols=None): subprotocols=None,
on_data=None):
""" """
url: websocket url. url: websocket url.
header: custom header for websocket handshake. 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 passing 2nd arugment is utf-8 string which we get from the server.
The 3rd arugment is continue flag. if 0, the data continue The 3rd arugment is continue flag. if 0, the data continue
to next frame data 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 keep_running: a boolean flag indicating whether the app's main loop
should keep running, defaults to True should keep running, defaults to True
get_mask_key: a callable to produce new mask keys, get_mask_key: a callable to produce new mask keys,
@@ -82,6 +91,7 @@ class WebSocketApp(object):
self.cookie = cookie self.cookie = cookie
self.on_open = on_open self.on_open = on_open
self.on_message = on_message self.on_message = on_message
self.on_data = on_data
self.on_error = on_error self.on_error = on_error
self.on_close = on_close self.on_close = on_close
self.on_ping = on_ping self.on_ping = on_ping
@@ -192,11 +202,13 @@ class WebSocketApp(object):
elif op_code == ABNF.OPCODE_PONG: elif op_code == ABNF.OPCODE_PONG:
self._callback(self.on_pong, frame.data) self._callback(self.on_pong, frame.data)
elif op_code == ABNF.OPCODE_CONT and self.on_cont_message: 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) self._callback(self.on_cont_message, frame.data, frame.fin)
else: else:
data = frame.data data = frame.data
if six.PY3 and frame.opcode == ABNF.OPCODE_TEXT: if six.PY3 and frame.opcode == ABNF.OPCODE_TEXT:
data = data.decode("utf-8") data = data.decode("utf-8")
self._callback(self.on_data, data, frame.opcode, True)
self._callback(self.on_message, data) self._callback(self.on_message, data)
except Exception as e: except Exception as e:
self._callback(self.on_error, e) self._callback(self.on_error, e)

View File

@@ -422,13 +422,16 @@ class WebSocket(object):
self.connected = False self.connected = False
self.send(struct.pack('!H', status) + reason, ABNF.OPCODE_CLOSE) 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 Close Websocket object
status: status code to send. see STATUS_XXX. status: status code to send. see STATUS_XXX.
reason: the reason to close. This must be string. 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 self.connected:
if status < 0 or status >= ABNF.LENGTH_16: if status < 0 or status >= ABNF.LENGTH_16:
@@ -437,8 +440,8 @@ class WebSocket(object):
try: try:
self.connected = False self.connected = False
self.send(struct.pack('!H', status) + reason, ABNF.OPCODE_CLOSE) self.send(struct.pack('!H', status) + reason, ABNF.OPCODE_CLOSE)
timeout = self.sock.gettimeout() sock_timeout = self.sock.gettimeout()
self.sock.settimeout(3) self.sock.settimeout(timeout)
try: try:
frame = self.recv_frame() frame = self.recv_frame()
if isEnabledForError(): if isEnabledForError():
@@ -447,7 +450,7 @@ class WebSocket(object):
error("close status: " + repr(recv_status)) error("close status: " + repr(recv_status))
except: except:
pass pass
self.sock.settimeout(timeout) self.sock.settimeout(sock_timeout)
self.sock.shutdown(socket.SHUT_RDWR) self.sock.shutdown(socket.SHUT_RDWR)
except: except:
pass pass

View File

@@ -31,18 +31,21 @@ class WebSocketException(Exception):
""" """
pass pass
class WebSocketProtocolException(WebSocketException): class WebSocketProtocolException(WebSocketException):
""" """
If the webscoket protocol is invalid, this exception will be raised. If the webscoket protocol is invalid, this exception will be raised.
""" """
pass pass
class WebSocketPayloadException(WebSocketException): class WebSocketPayloadException(WebSocketException):
""" """
If the webscoket payload is invalid, this exception will be raised. If the webscoket payload is invalid, this exception will be raised.
""" """
pass pass
class WebSocketConnectionClosedException(WebSocketException): class WebSocketConnectionClosedException(WebSocketException):
""" """
If remote host closed the connection or some network error happened, If remote host closed the connection or some network error happened,
@@ -50,12 +53,14 @@ class WebSocketConnectionClosedException(WebSocketException):
""" """
pass pass
class WebSocketTimeoutException(WebSocketException): class WebSocketTimeoutException(WebSocketException):
""" """
WebSocketTimeoutException will be raised at socket timeout during read/write data. WebSocketTimeoutException will be raised at socket timeout during read/write data.
""" """
pass pass
class WebSocketProxyException(WebSocketException): class WebSocketProxyException(WebSocketException):
""" """
WebSocketProxyException will be raised when proxy error occured. WebSocketProxyException will be raised when proxy error occured.
@@ -63,3 +68,10 @@ class WebSocketProxyException(WebSocketException):
pass 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

View File

@@ -108,7 +108,7 @@ def _get_handshake_headers(resource, host, port, options):
def _get_resp_headers(sock, success_status=101): def _get_resp_headers(sock, success_status=101):
status, resp_headers = read_headers(sock) status, resp_headers = read_headers(sock)
if status != success_status: if status != success_status:
raise WebSocketException("Handshake status %d" % status) raise WebSocketBadStatusException("Handshake status %d", status)
return status, resp_headers return status, resp_headers
_HEADERS_TO_CHECK = { _HEADERS_TO_CHECK = {

View File

@@ -132,6 +132,9 @@ def _wrap_sni_socket(sock, sslopt, hostname, check_hostname):
context.check_hostname = check_hostname context.check_hostname = check_hostname
if 'ciphers' in sslopt: if 'ciphers' in sslopt:
context.set_ciphers(sslopt['ciphers']) 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( return context.wrap_socket(
sock, sock,
@@ -183,7 +186,7 @@ def _tunnel(sock, host, port, auth):
if status != 200: if status != 200:
raise WebSocketProxyException( raise WebSocketProxyException(
"failed CONNECT via proxy status: %r" + status) "failed CONNECT via proxy status: %r" % status)
return sock return sock