diff options
-rw-r--r-- | .gitignore | 2 | ||||
-rw-r--r-- | ChangeLog | 8 | ||||
-rwxr-xr-x | bin/wsdump.py | 24 | ||||
-rw-r--r-- | setup.py | 2 | ||||
-rw-r--r-- | websocket/__init__.py | 2 | ||||
-rw-r--r-- | websocket/_app.py | 15 | ||||
-rw-r--r-- | websocket/_core.py | 3 | ||||
-rw-r--r-- | websocket/_exceptions.py | 1 | ||||
-rw-r--r-- | websocket/_handshake.py | 16 | ||||
-rw-r--r-- | websocket/_http.py | 10 | ||||
-rw-r--r-- | websocket/_logging.py | 6 | ||||
-rw-r--r-- | websocket/_socket.py | 10 | ||||
-rw-r--r-- | websocket/_ssl_compat.py | 2 | ||||
-rw-r--r-- | websocket/_url.py | 3 | ||||
-rw-r--r-- | websocket/_utils.py | 1 | ||||
-rw-r--r-- | websocket/tests/test_websocket.py | 3 |
16 files changed, 81 insertions, 27 deletions
@@ -9,3 +9,5 @@ dist .cache/ compliance/reports/ +.eggs +.vscode
\ No newline at end of file @@ -1,6 +1,14 @@ ChangeLog ============ +- 0.57.0 + - wsdump: Fix --headers option (#589) + - Fix getting 400 bad request with long proxy authorization string (#587) + +- 0.56.0 + + - Don't retry sockets with timeout set to 0(#536) + - 0.55.0 - Add response headers in WebSocketBadStatusException (#501) diff --git a/bin/wsdump.py b/bin/wsdump.py index bc07246..d16d361 100755 --- a/bin/wsdump.py +++ b/bin/wsdump.py @@ -6,6 +6,8 @@ import sys import threading import time import ssl +import gzip +import zlib import six from six.moves.urllib.parse import urlparse @@ -130,7 +132,7 @@ def main(): if args.nocert: opts = {"cert_reqs": ssl.CERT_NONE, "check_hostname": False} if args.headers: - options['header'] = map(str.strip, args.headers.split(',')) + options['header'] = list(map(str.strip, args.headers.split(','))) ws = websocket.create_connection(args.url, sslopt=opts, **options) if args.raw: console = NonInteractive() @@ -162,10 +164,24 @@ def main(): msg = None if six.PY3 and opcode == websocket.ABNF.OPCODE_TEXT and isinstance(data, bytes): data = str(data, "utf-8") - if not args.verbose and opcode in OPCODE_DATA: - msg = data - elif args.verbose: + if isinstance(data, bytes) and len(data)>2 and data[:2] == b'\037\213': # gzip magick + try: + data = "[gzip] " + str(gzip.decompress(data), "utf-8") + except: + pass + elif isinstance(data, bytes): + try: + data = "[zlib] " + str(zlib.decompress(data, -zlib.MAX_WBITS), "utf-8") + except: + pass + + if isinstance(data, bytes): + data = repr(data) + + if args.verbose: msg = "%s: %s" % (websocket.ABNF.OPCODE_MAP.get(opcode), data) + else: + msg = data if msg is not None: if args.timings: @@ -3,7 +3,7 @@ import sys from setuptools import setup import pkg_resources -VERSION = "0.55.0" +VERSION = "0.56.0" NAME = "websocket_client" install_requires = ["six"] diff --git a/websocket/__init__.py b/websocket/__init__.py index 7c3154d..605f76c 100644 --- a/websocket/__init__.py +++ b/websocket/__init__.py @@ -26,4 +26,4 @@ from ._exceptions import * from ._logging import * from ._socket import * -__version__ = "0.55.0" +__version__ = "0.57.0" diff --git a/websocket/_app.py b/websocket/_app.py index 81aa1fc..e4e9f99 100644 --- a/websocket/_app.py +++ b/websocket/_app.py @@ -42,11 +42,11 @@ __all__ = ["WebSocketApp"] class Dispatcher: def __init__(self, app, ping_timeout): - self.app = app + self.app = app self.ping_timeout = ping_timeout def read(self, sock, read_callback, check_callback): - while self.app.sock.connected: + while self.app.keep_running: r, w, e = select.select( (self.app.sock.sock, ), (), (), self.ping_timeout) if r: @@ -54,13 +54,13 @@ class Dispatcher: break check_callback() -class SSLDispacther: +class SSLDispatcher: def __init__(self, app, ping_timeout): - self.app = app + self.app = app self.ping_timeout = ping_timeout def read(self, sock, read_callback, check_callback): - while self.app.sock.connected: + while self.app.keep_running: r = self.select() if r: if not read_callback(): @@ -75,6 +75,7 @@ class SSLDispacther: r, w, e = select.select((sock, ), (), (), self.ping_timeout) return r + class WebSocketApp(object): """ Higher level of APIs are provided. @@ -179,7 +180,7 @@ class WebSocketApp(object): http_no_proxy=None, http_proxy_auth=None, skip_utf8_validation=False, host=None, origin=None, dispatcher=None, - suppress_origin = False, proxy_type=None): + suppress_origin=False, proxy_type=None): """ run event loop for WebSocket framework. This loop is infinite loop and is alive during websocket is available. @@ -314,7 +315,7 @@ class WebSocketApp(object): def create_dispatcher(self, ping_timeout): timeout = ping_timeout or 10 if self.sock.is_ssl(): - return SSLDispacther(self, timeout) + return SSLDispatcher(self, timeout) return Dispatcher(self, timeout) diff --git a/websocket/_core.py b/websocket/_core.py index 0f914c2..418aafc 100644 --- a/websocket/_core.py +++ b/websocket/_core.py @@ -271,7 +271,8 @@ class WebSocket(object): frame.get_mask_key = self.get_mask_key data = frame.format() length = len(data) - trace("send: " + repr(data)) + if (isEnabledForTrace()): + trace("send: " + repr(data)) with self.lock: while data: diff --git a/websocket/_exceptions.py b/websocket/_exceptions.py index b7a61d3..2070790 100644 --- a/websocket/_exceptions.py +++ b/websocket/_exceptions.py @@ -80,6 +80,7 @@ class WebSocketBadStatusException(WebSocketException): self.status_code = status_code self.resp_headers = resp_headers + class WebSocketAddressException(WebSocketException): """ If the websocket address info cannot be found, this exception will be raised. diff --git a/websocket/_handshake.py b/websocket/_handshake.py index c4bcb69..7476a07 100644 --- a/websocket/_handshake.py +++ b/websocket/_handshake.py @@ -55,7 +55,8 @@ else: # websocket supported version. VERSION = 13 -SUPPORTED_REDIRECT_STATUSES = [HTTPStatus.MOVED_PERMANENTLY, HTTPStatus.FOUND, HTTPStatus.SEE_OTHER] +SUPPORTED_REDIRECT_STATUSES = (HTTPStatus.MOVED_PERMANENTLY, HTTPStatus.FOUND, HTTPStatus.SEE_OTHER,) +SUCCESS_STATUSES = SUPPORTED_REDIRECT_STATUSES + (HTTPStatus.SWITCHING_PROTOCOLS,) CookieJar = SimpleCookieJar() @@ -85,6 +86,7 @@ def handshake(sock, hostname, port, resource, **options): return handshake_response(status, resp, subproto) + def _pack_hostname(hostname): # IPv6 address if ':' in hostname: @@ -95,14 +97,12 @@ def _pack_hostname(hostname): def _get_handshake_headers(resource, host, port, options): headers = [ "GET %s HTTP/1.1" % resource, - "Upgrade: websocket", - "Connection: Upgrade" + "Upgrade: websocket" ] if port == 80 or port == 443: hostport = _pack_hostname(host) else: hostport = "%s:%d" % (_pack_hostname(host), port) - if "host" in options and options["host"] is not None: headers.append("Host: %s" % options["host"]) else: @@ -126,6 +126,11 @@ def _get_handshake_headers(resource, host, port, options): if not 'header' in options or 'Sec-WebSocket-Version' not in options['header']: headers.append("Sec-WebSocket-Version: %s" % VERSION) + if not 'connection' in options or options['connection'] is None: + headers.append('Connection: upgrade') + else: + headers.append(options['connection']) + subprotocols = options.get("subprotocols") if subprotocols: headers.append("Sec-WebSocket-Protocol: %s" % ",".join(subprotocols)) @@ -154,12 +159,13 @@ def _get_handshake_headers(resource, host, port, options): return headers, key -def _get_resp_headers(sock, success_statuses=(101, 301, 302, 303)): +def _get_resp_headers(sock, success_statuses=SUCCESS_STATUSES): status, resp_headers, status_message = read_headers(sock) if status not in success_statuses: raise WebSocketBadStatusException("Handshake status %d %s", status, status_message, resp_headers) return status, resp_headers + _HEADERS_TO_CHECK = { "upgrade": "websocket", "connection": "upgrade", diff --git a/websocket/_http.py b/websocket/_http.py index 5b9a26d..a8777de 100644 --- a/websocket/_http.py +++ b/websocket/_http.py @@ -64,6 +64,7 @@ class proxy_info(object): self.auth = None self.no_proxy = None + def _open_proxied_socket(url, options, proxy): hostname, port, resource, is_secure = parse_url(url) @@ -138,15 +139,18 @@ def _get_addrinfo_list(hostname, port, is_secure, proxy): phost, pport, pauth = get_proxy_info( hostname, is_secure, proxy.host, proxy.port, proxy.auth, proxy.no_proxy) try: + # when running on windows 10, getaddrinfo without socktype returns a socktype 0. + # This generates an error exception: `_on_error: exception Socket type must be stream or datagram, not 0` + # or `OSError: [Errno 22] Invalid argument` when creating socket. Force the socket type to SOCK_STREAM. if not phost: addrinfo_list = socket.getaddrinfo( - hostname, port, 0, 0, socket.SOL_TCP) + hostname, port, 0, socket.SOCK_STREAM, socket.SOL_TCP) return addrinfo_list, False, None else: pport = pport and pport or 80 # when running on windows 10, the getaddrinfo used above # returns a socktype 0. This generates an error exception: - #_on_error: exception Socket type must be stream or datagram, not 0 + # _on_error: exception Socket type must be stream or datagram, not 0 # Force the socket type to SOCK_STREAM addrinfo_list = socket.getaddrinfo(phost, pport, 0, socket.SOCK_STREAM, socket.SOL_TCP) return addrinfo_list, True, pauth @@ -276,7 +280,7 @@ def _tunnel(sock, host, port, auth): auth_str = auth[0] if auth[1]: auth_str += ":" + auth[1] - encoded_str = base64encode(auth_str.encode()).strip().decode() + encoded_str = base64encode(auth_str.encode()).strip().decode().replace('\n', '') connect_header += "Proxy-Authorization: Basic %s\r\n" % encoded_str connect_header += "\r\n" dump("request header", connect_header) diff --git a/websocket/_logging.py b/websocket/_logging.py index 70a6271..c947778 100644 --- a/websocket/_logging.py +++ b/websocket/_logging.py @@ -34,7 +34,7 @@ _logger.addHandler(NullHandler()) _traceEnabled = False __all__ = ["enableTrace", "dump", "error", "warning", "debug", "trace", - "isEnabledForError", "isEnabledForDebug"] + "isEnabledForError", "isEnabledForDebug", "isEnabledForTrace"] def enableTrace(traceable, handler = logging.StreamHandler()): @@ -49,7 +49,6 @@ def enableTrace(traceable, handler = logging.StreamHandler()): _logger.addHandler(handler) _logger.setLevel(logging.DEBUG) - def dump(title, message): if _traceEnabled: _logger.debug("--- " + title + " ---") @@ -80,3 +79,6 @@ def isEnabledForError(): def isEnabledForDebug(): return _logger.isEnabledFor(logging.DEBUG) + +def isEnabledForTrace(): + return _traceEnabled diff --git a/websocket/_socket.py b/websocket/_socket.py index d811c21..7be3913 100644 --- a/websocket/_socket.py +++ b/websocket/_socket.py @@ -96,7 +96,10 @@ def recv(sock, bufsize): return sock.recv(bufsize) try: - bytes_ = _recv() + if sock.gettimeout() == 0: + bytes_ = sock.recv(bufsize) + else: + bytes_ = _recv() except socket.timeout as e: message = extract_err_message(e) raise WebSocketTimeoutException(message) @@ -148,7 +151,10 @@ def send(sock, data): return sock.send(data) try: - return _send() + if sock.gettimeout() == 0: + return sock.send(data) + else: + return _send() except socket.timeout as e: message = extract_err_message(e) raise WebSocketTimeoutException(message) diff --git a/websocket/_ssl_compat.py b/websocket/_ssl_compat.py index 5b3c413..96cd173 100644 --- a/websocket/_ssl_compat.py +++ b/websocket/_ssl_compat.py @@ -49,4 +49,6 @@ except ImportError: class SSLWantWriteError(Exception): pass + ssl = lambda: None + HAVE_SSL = False diff --git a/websocket/_url.py b/websocket/_url.py index ae46d6c..a394fc3 100644 --- a/websocket/_url.py +++ b/websocket/_url.py @@ -103,7 +103,8 @@ def _is_address_in_network(ip, net): def _is_no_proxy_host(hostname, no_proxy): if not no_proxy: v = os.environ.get("no_proxy", "").replace(" ", "") - no_proxy = v.split(",") + if v: + no_proxy = v.split(",") if not no_proxy: no_proxy = DEFAULT_NO_PROXY_HOST diff --git a/websocket/_utils.py b/websocket/_utils.py index 8eddabf..32ee12e 100644 --- a/websocket/_utils.py +++ b/websocket/_utils.py @@ -32,6 +32,7 @@ class NoLock(object): def __exit__(self, exc_type, exc_value, traceback): pass + try: # If wsaccel is available we use compiled routines to validate UTF-8 # strings. diff --git a/websocket/tests/test_websocket.py b/websocket/tests/test_websocket.py index f49a893..8b131bb 100644 --- a/websocket/tests/test_websocket.py +++ b/websocket/tests/test_websocket.py @@ -55,6 +55,9 @@ class SockMock(object): def add_packet(self, data): self.data.append(data) + def gettimeout(self): + return None + def recv(self, bufsize): if self.data: e = self.data.pop(0) |