Upgraded websocket-client module to fix Radarr SignalRCore disconnection issues.

pull/1414/head
morpheus65535 3 years ago
parent baf25b2a3e
commit 70a0f6835e

@ -40,7 +40,7 @@ subliminal=2.1.0dev
tzlocal=2.1b1 tzlocal=2.1b1
twine=3.4.1 twine=3.4.1
urllib3=1.23 urllib3=1.23
websocket-client=0.54.0 websocket-client=0.59.0 <-- Modified to work with SignalRCore: https://github.com/websocket-client/websocket-client/commit/3112b7d75b1e5d65cb8fdfca7801606649044ed1#commitcomment-50947250
## indirect dependencies ## indirect dependencies
auditok=0.1.5 # Required-by: ffsubsync auditok=0.1.5 # Required-by: ffsubsync

@ -15,8 +15,7 @@ Copyright (C) 2010 Hiroki Ohtani(liris)
You should have received a copy of the GNU Lesser General Public You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Boston, MA 02110-1335 USA
""" """
from ._abnf import * from ._abnf import *
@ -26,4 +25,4 @@ from ._exceptions import *
from ._logging import * from ._logging import *
from ._socket import * from ._socket import *
__version__ = "0.54.0" __version__ = "0.59.0"

@ -1,3 +1,7 @@
"""
"""
""" """
websocket - WebSocket client library for Python websocket - WebSocket client library for Python
@ -15,8 +19,7 @@ Copyright (C) 2010 Hiroki Ohtani(liris)
You should have received a copy of the GNU Lesser General Public You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Boston, MA 02110-1335 USA
""" """
import array import array
@ -105,7 +108,7 @@ VALID_CLOSE_STATUS = (
class ABNF(object): class ABNF(object):
""" """
ABNF frame class. ABNF frame class.
see http://tools.ietf.org/html/rfc5234 See http://tools.ietf.org/html/rfc5234
and http://tools.ietf.org/html/rfc6455#section-5.2 and http://tools.ietf.org/html/rfc6455#section-5.2
""" """
@ -139,8 +142,7 @@ class ABNF(object):
def __init__(self, fin=0, rsv1=0, rsv2=0, rsv3=0, def __init__(self, fin=0, rsv1=0, rsv2=0, rsv3=0,
opcode=OPCODE_TEXT, mask=1, data=""): opcode=OPCODE_TEXT, mask=1, data=""):
""" """
Constructor for ABNF. Constructor for ABNF. Please check RFC for arguments.
please check RFC for arguments.
""" """
self.fin = fin self.fin = fin
self.rsv1 = rsv1 self.rsv1 = rsv1
@ -155,7 +157,10 @@ class ABNF(object):
def validate(self, skip_utf8_validation=False): def validate(self, skip_utf8_validation=False):
""" """
validate the ABNF frame. Validate the ABNF frame.
Parameters
----------
skip_utf8_validation: skip utf8 validation. skip_utf8_validation: skip utf8 validation.
""" """
if self.rsv1 or self.rsv2 or self.rsv3: if self.rsv1 or self.rsv2 or self.rsv3:
@ -193,15 +198,18 @@ class ABNF(object):
@staticmethod @staticmethod
def create_frame(data, opcode, fin=1): def create_frame(data, opcode, fin=1):
""" """
create frame to send text, binary and other data. Create frame to send text, binary and other data.
data: data to send. This is string value(byte array). Parameters
if opcode is OPCODE_TEXT and this value is unicode, ----------
data: <type>
data to send. This is string value(byte array).
If opcode is OPCODE_TEXT and this value is unicode,
data value is converted into unicode string, automatically. data value is converted into unicode string, automatically.
opcode: <type>
opcode: operation code. please see OPCODE_XXX. operation code. please see OPCODE_XXX.
fin: <type>
fin: fin flag. if set to 0, create continue fragmentation. fin flag. if set to 0, create continue fragmentation.
""" """
if opcode == ABNF.OPCODE_TEXT and isinstance(data, six.text_type): if opcode == ABNF.OPCODE_TEXT and isinstance(data, six.text_type):
data = data.encode("utf-8") data = data.encode("utf-8")
@ -210,7 +218,7 @@ class ABNF(object):
def format(self): def format(self):
""" """
format this object to string(byte array) to send data to server. Format this object to string(byte array) to send data to server.
""" """
if any(x not in (0, 1) for x in [self.fin, self.rsv1, self.rsv2, self.rsv3]): if any(x not in (0, 1) for x in [self.fin, self.rsv1, self.rsv2, self.rsv3]):
raise ValueError("not 0 or 1") raise ValueError("not 0 or 1")
@ -220,9 +228,9 @@ class ABNF(object):
if length >= ABNF.LENGTH_63: if length >= ABNF.LENGTH_63:
raise ValueError("data is too long") raise ValueError("data is too long")
frame_header = chr(self.fin << 7 frame_header = chr(self.fin << 7 |
| self.rsv1 << 6 | self.rsv2 << 5 | self.rsv3 << 4 self.rsv1 << 6 | self.rsv2 << 5 | self.rsv3 << 4 |
| self.opcode) self.opcode)
if length < ABNF.LENGTH_7: if length < ABNF.LENGTH_7:
frame_header += chr(self.mask << 7 | length) frame_header += chr(self.mask << 7 | length)
frame_header = six.b(frame_header) frame_header = six.b(frame_header)
@ -252,11 +260,14 @@ class ABNF(object):
@staticmethod @staticmethod
def mask(mask_key, data): def mask(mask_key, data):
""" """
mask or unmask data. Just do xor for each byte Mask or unmask data. Just do xor for each byte
mask_key: 4 byte string(byte). Parameters
----------
data: data to mask/unmask. mask_key: <type>
4 byte string(byte).
data: <type>
data to mask/unmask.
""" """
if data is None: if data is None:
data = "" data = ""
@ -276,7 +287,7 @@ class ABNF(object):
a = numpy.frombuffer(data, dtype="uint32") a = numpy.frombuffer(data, dtype="uint32")
masked = numpy.bitwise_xor(a, [_mask_key]).astype("uint32") masked = numpy.bitwise_xor(a, [_mask_key]).astype("uint32")
if len(data) > origlen: if len(data) > origlen:
return masked.tobytes()[:origlen] return masked.tobytes()[:origlen]
return masked.tobytes() return masked.tobytes()
else: else:
_m = array.array("B", mask_key) _m = array.array("B", mask_key)

@ -1,3 +1,7 @@
"""
"""
""" """
websocket - WebSocket client library for Python websocket - WebSocket client library for Python
@ -15,13 +19,8 @@ Copyright (C) 2010 Hiroki Ohtani(liris)
You should have received a copy of the GNU Lesser General Public You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Boston, MA 02110-1335 USA
"""
"""
WebSocketApp provides higher level APIs.
""" """
import inspect import inspect
import select import select
@ -40,27 +39,35 @@ from . import _logging
__all__ = ["WebSocketApp"] __all__ = ["WebSocketApp"]
class Dispatcher: class Dispatcher:
"""
Dispatcher
"""
def __init__(self, app, ping_timeout): def __init__(self, app, ping_timeout):
self.app = app self.app = app
self.ping_timeout = ping_timeout self.ping_timeout = ping_timeout
def read(self, sock, read_callback, check_callback): def read(self, sock, read_callback, check_callback):
while self.app.sock.connected: while self.app.keep_running:
r, w, e = select.select( r, w, e = select.select(
(self.app.sock.sock, ), (), (), self.ping_timeout) (self.app.sock.sock, ), (), (), self.ping_timeout)
if r: if r:
if not read_callback(): if not read_callback():
break break
check_callback() check_callback()
class SSLDispacther:
class SSLDispatcher:
"""
SSLDispatcher
"""
def __init__(self, app, ping_timeout): def __init__(self, app, ping_timeout):
self.app = app self.app = app
self.ping_timeout = ping_timeout self.ping_timeout = ping_timeout
def read(self, sock, read_callback, check_callback): def read(self, sock, read_callback, check_callback):
while self.app.sock.connected: while self.app.keep_running:
r = self.select() r = self.select()
if r: if r:
if not read_callback(): if not read_callback():
@ -75,10 +82,10 @@ class SSLDispacther:
r, w, e = select.select((sock, ), (), (), self.ping_timeout) r, w, e = select.select((sock, ), (), (), self.ping_timeout)
return r return r
class WebSocketApp(object): class WebSocketApp(object):
""" """
Higher level of APIs are provided. Higher level of APIs are provided. The interface is like JavaScript WebSocket object.
The interface is like JavaScript WebSocket object.
""" """
def __init__(self, url, header=None, def __init__(self, url, header=None,
@ -89,39 +96,56 @@ class WebSocketApp(object):
subprotocols=None, subprotocols=None,
on_data=None): on_data=None):
""" """
url: websocket url. WebSocketApp initialization
header: custom header for websocket handshake.
on_open: callable object which is called at opening websocket. Parameters
this function has one argument. The argument is this class object. ----------
on_message: callable object which is called when received data. url: <type>
on_message has 2 arguments. websocket url.
The 1st argument is this class object. header: list or dict
The 2nd argument is utf-8 string which we get from the server. custom header for websocket handshake.
on_error: callable object which is called when we get error. on_open: <type>
on_error has 2 arguments. callable object which is called at opening websocket.
The 1st argument is this class object. this function has one argument. The argument is this class object.
The 2nd argument is exception object. on_message: <type>
on_close: callable object which is called when closed the connection. callable object which is called when received data.
this function has one argument. The argument is this class object. on_message has 2 arguments.
on_cont_message: callback object which is called when receive continued The 1st argument is this class object.
frame data. The 2nd argument is utf-8 string which we get from the server.
on_cont_message has 3 arguments. on_error: <type>
The 1st argument is this class object. callable object which is called when we get error.
The 2nd argument is utf-8 string which we get from the server. on_error has 2 arguments.
The 3rd argument is continue flag. if 0, the data continue The 1st argument is this class object.
to next frame data The 2nd argument is exception object.
on_data: callback object which is called when a message received. on_close: <type>
This is called before on_message or on_cont_message, callable object which is called when closed the connection.
and then on_message or on_cont_message is called. this function has one argument. The argument is this class object.
on_data has 4 argument. on_cont_message: <type>
The 1st argument is this class object. callback object which is called when receive continued
The 2nd argument is utf-8 string which we get from the server. frame data.
The 3rd argument is data type. ABNF.OPCODE_TEXT or ABNF.OPCODE_BINARY will be came. on_cont_message has 3 arguments.
The 4th argument is continue flag. if 0, the data continue The 1st argument is this class object.
keep_running: this parameter is obsolete and ignored. The 2nd argument is utf-8 string which we get from the server.
get_mask_key: a callable to produce new mask keys, The 3rd argument is continue flag. if 0, the data continue
see the WebSocket.set_mask_key's docstring for more information to next frame data
subprotocols: array of available sub protocols. default is None. on_data: <type>
callback object which is called when a message received.
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 argument is this class object.
The 2nd argument 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 4th argument is continue flag. if 0, the data continue
keep_running: <type>
this parameter is obsolete and ignored.
get_mask_key: func
a callable to produce new mask keys,
see the WebSocket.set_mask_key's docstring for more information
cookie: str
cookie value.
subprotocols: <type>
array of available sub protocols. default is None.
""" """
self.url = url self.url = url
self.header = header if header is not None else [] self.header = header if header is not None else []
@ -144,10 +168,15 @@ class WebSocketApp(object):
def send(self, data, opcode=ABNF.OPCODE_TEXT): def send(self, data, opcode=ABNF.OPCODE_TEXT):
""" """
send message. send message
data: message to send. If you set opcode to OPCODE_TEXT,
data must be utf-8 string or unicode. Parameters
opcode: operation code of data. default is OPCODE_TEXT. ----------
data: <type>
Message to send. If you set opcode to OPCODE_TEXT,
data must be utf-8 string or unicode.
opcode: <type>
Operation code of data. default is OPCODE_TEXT.
""" """
if not self.sock or self.sock.send(data, opcode) == 0: if not self.sock or self.sock.send(data, opcode) == 0:
@ -156,54 +185,73 @@ class WebSocketApp(object):
def close(self, **kwargs): def close(self, **kwargs):
""" """
close websocket connection. Close websocket connection.
""" """
self.keep_running = False self.keep_running = False
if self.sock: if self.sock:
self.sock.close(**kwargs) self.sock.close(**kwargs)
self.sock = None self.sock = None
def _send_ping(self, interval, event): def _send_ping(self, interval, event, payload):
while not event.wait(interval): while not event.wait(interval):
self.last_ping_tm = time.time() self.last_ping_tm = time.time()
if self.sock: if self.sock:
try: try:
self.sock.ping() self.sock.ping(payload)
except Exception as ex: except Exception as ex:
_logging.warning("send_ping routine terminated: {}".format(ex)) _logging.warning("send_ping routine terminated: {}".format(ex))
break break
def run_forever(self, sockopt=None, sslopt=None, def run_forever(self, sockopt=None, sslopt=None,
ping_interval=0, ping_timeout=None, ping_interval=0, ping_timeout=None,
ping_payload="",
http_proxy_host=None, http_proxy_port=None, http_proxy_host=None, http_proxy_port=None,
http_no_proxy=None, http_proxy_auth=None, http_no_proxy=None, http_proxy_auth=None,
skip_utf8_validation=False, skip_utf8_validation=False,
host=None, origin=None, dispatcher=None, host=None, origin=None, dispatcher=None,
suppress_origin = False, proxy_type=None): suppress_origin=False, proxy_type=None):
""" """
run event loop for WebSocket framework. Run event loop for WebSocket framework.
This loop is infinite loop and is alive during websocket is available.
sockopt: values for socket.setsockopt. This loop is an infinite loop and is alive while websocket is available.
Parameters
----------
sockopt: tuple
values for socket.setsockopt.
sockopt must be tuple sockopt must be tuple
and each element is argument of sock.setsockopt. and each element is argument of sock.setsockopt.
sslopt: ssl socket optional dict. sslopt: dict
ping_interval: automatically send "ping" command optional dict object for ssl socket option.
every specified period(second) ping_interval: int or float
automatically send "ping" command
every specified period (in seconds)
if set to 0, not send automatically. if set to 0, not send automatically.
ping_timeout: timeout(second) if the pong message is not received. ping_timeout: int or float
http_proxy_host: http proxy host name. timeout (in seconds) if the pong message is not received.
http_proxy_port: http proxy port. If not set, set to 80. ping_payload: str
http_no_proxy: host names, which doesn't use proxy. payload message to send with each ping.
skip_utf8_validation: skip utf8 validation. http_proxy_host: <type>
host: update host header. http proxy host name.
origin: update origin header. http_proxy_port: <type>
dispatcher: customize reading data from socket. http proxy port. If not set, set to 80.
suppress_origin: suppress outputting origin header. http_no_proxy: <type>
host names, which doesn't use proxy.
skip_utf8_validation: bool
skip utf8 validation.
host: str
update host header.
origin: str
update origin header.
dispatcher: <type>
customize reading data from socket.
suppress_origin: bool
suppress outputting origin header.
Returns Returns
------- -------
False if caught KeyboardInterrupt teardown: bool
True if other exception was raised during a loop False if caught KeyboardInterrupt, True if other exception was raised during a loop
""" """
if ping_timeout is not None and ping_timeout <= 0: if ping_timeout is not None and ping_timeout <= 0:
@ -224,10 +272,11 @@ class WebSocketApp(object):
def teardown(close_frame=None): def teardown(close_frame=None):
""" """
Tears down the connection. Tears down the connection.
If close_frame is set, we will invoke the on_close handler with the If close_frame is set, we will invoke the on_close handler with the
statusCode and reason from there. statusCode and reason from there.
""" """
if thread and thread.isAlive(): if thread and thread.is_alive():
event.set() event.set()
thread.join() thread.join()
self.keep_running = False self.keep_running = False
@ -260,8 +309,8 @@ class WebSocketApp(object):
if ping_interval: if ping_interval:
event = threading.Event() event = threading.Event()
thread = threading.Thread( thread = threading.Thread(
target=self._send_ping, args=(ping_interval, event)) target=self._send_ping, args=(ping_interval, event, ping_payload))
thread.setDaemon(True) thread.daemon = True
thread.start() thread.start()
def read(): def read():
@ -296,9 +345,9 @@ class WebSocketApp(object):
has_pong_not_arrived_after_last_ping = self.last_pong_tm - self.last_ping_tm < 0 has_pong_not_arrived_after_last_ping = self.last_pong_tm - self.last_ping_tm < 0
has_pong_arrived_too_late = self.last_pong_tm - self.last_ping_tm > ping_timeout has_pong_arrived_too_late = self.last_pong_tm - self.last_ping_tm > ping_timeout
if (self.last_ping_tm if (self.last_ping_tm and
and has_timeout_expired has_timeout_expired and
and (has_pong_not_arrived_after_last_ping or has_pong_arrived_too_late)): (has_pong_not_arrived_after_last_ping or has_pong_arrived_too_late)):
raise WebSocketTimeoutException("ping/pong timed out") raise WebSocketTimeoutException("ping/pong timed out")
return True return True
@ -314,13 +363,15 @@ class WebSocketApp(object):
def create_dispatcher(self, ping_timeout): def create_dispatcher(self, ping_timeout):
timeout = ping_timeout or 10 timeout = ping_timeout or 10
if self.sock.is_ssl(): if self.sock.is_ssl():
return SSLDispacther(self, timeout) return SSLDispatcher(self, timeout)
return Dispatcher(self, timeout) return Dispatcher(self, timeout)
def _get_close_args(self, data): def _get_close_args(self, data):
""" this functions extracts the code, reason from the close body """
if they exists, and if the self.on_close except three arguments """ _get_close_args extracts the code, reason from the close body
if they exists, and if the self.on_close except three arguments
"""
# if the on_close callback is "old", just return empty list # if the on_close callback is "old", just return empty list
if sys.version_info < (3, 0): if sys.version_info < (3, 0):
if not self.on_close or len(inspect.getargspec(self.on_close).args) != 3: if not self.on_close or len(inspect.getargspec(self.on_close).args) != 3:

@ -1,3 +1,27 @@
"""
"""
"""
websocket - WebSocket client library for Python
Copyright (C) 2010 Hiroki Ohtani(liris)
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
"""
try: try:
import Cookie import Cookie
except: except:
@ -48,5 +72,7 @@ class SimpleCookieJar(object):
if host.endswith(domain) or host == domain[1:]: if host.endswith(domain) or host == domain[1:]:
cookies.append(self.jar.get(domain)) cookies.append(self.jar.get(domain))
return "; ".join(filter(None, ["%s=%s" % (k, v.value) for cookie in filter(None, sorted(cookies)) for k, v in return "; ".join(filter(
sorted(cookie.items())])) None, sorted(
["%s=%s" % (k, v.value) for cookie in filter(None, cookies) for k, v in cookie.items()]
)))

@ -1,3 +1,10 @@
from __future__ import print_function
"""
_core.py
====================================
WebSocket Python client
"""
""" """
websocket - WebSocket client library for Python websocket - WebSocket client library for Python
@ -15,12 +22,9 @@ Copyright (C) 2010 Hiroki Ohtani(liris)
You should have received a copy of the GNU Lesser General Public You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Boston, MA 02110-1335 USA
""" """
from __future__ import print_function
import socket import socket
import struct import struct
import threading import threading
@ -40,21 +44,12 @@ from ._utils import *
__all__ = ['WebSocket', 'create_connection'] __all__ = ['WebSocket', 'create_connection']
"""
websocket python client.
=========================
This version support only hybi-13.
Please see http://tools.ietf.org/html/rfc6455 for protocol.
"""
class WebSocket(object): class WebSocket(object):
""" """
Low level WebSocket interface. Low level WebSocket interface.
This class is based on
The WebSocket protocol draft-hixie-thewebsocketprotocol-76 This class is based on the WebSocket protocol `draft-hixie-thewebsocketprotocol-76 <http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76>`_
http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76
We can connect to the websocket server and send/receive data. We can connect to the websocket server and send/receive data.
The following example is an echo client. The following example is an echo client.
@ -67,14 +62,22 @@ class WebSocket(object):
'Hello, Server' 'Hello, Server'
>>> ws.close() >>> ws.close()
get_mask_key: a callable to produce new mask keys, see the set_mask_key Parameters
function's docstring for more details ----------
sockopt: values for socket.setsockopt. get_mask_key: func
a callable to produce new mask keys, see the set_mask_key
function's docstring for more details
sockopt: tuple
values for socket.setsockopt.
sockopt must be tuple and each element is argument of sock.setsockopt. sockopt must be tuple and each element is argument of sock.setsockopt.
sslopt: dict object for ssl socket option. sslopt: dict
fire_cont_frame: fire recv event for each cont frame. default is False optional dict object for ssl socket option.
enable_multithread: if set to True, lock send method. fire_cont_frame: bool
skip_utf8_validation: skip utf8 validation. fire recv event for each cont frame. default is False
enable_multithread: bool
if set to True, lock send method.
skip_utf8_validation: bool
skip utf8 validation.
""" """
def __init__(self, get_mask_key=None, sockopt=None, sslopt=None, def __init__(self, get_mask_key=None, sockopt=None, sslopt=None,
@ -82,6 +85,10 @@ class WebSocket(object):
skip_utf8_validation=False, **_): skip_utf8_validation=False, **_):
""" """
Initialize WebSocket object. Initialize WebSocket object.
Parameters
----------
sslopt: specify ssl certification verification options
""" """
self.sock_opt = sock_opt(sockopt, sslopt) self.sock_opt = sock_opt(sockopt, sslopt)
self.handshake_response = None self.handshake_response = None
@ -119,19 +126,27 @@ class WebSocket(object):
def set_mask_key(self, func): def set_mask_key(self, func):
""" """
set function to create musk key. You can customize mask key generator. Set function to create mask key. You can customize mask key generator.
Mainly, this is for testing purpose. Mainly, this is for testing purpose.
func: callable object. the func takes 1 argument as integer. Parameters
The argument means length of mask key. ----------
This func must return string(byte array), func: func
which length is argument specified. callable object. the func takes 1 argument as integer.
The argument means length of mask key.
This func must return string(byte array),
which length is argument specified.
""" """
self.get_mask_key = func self.get_mask_key = func
def gettimeout(self): def gettimeout(self):
""" """
Get the websocket timeout(second). Get the websocket timeout (in seconds) as an int or float
Returns
----------
timeout: int or float
returns timeout value (in seconds). This value could be either float/integer.
""" """
return self.sock_opt.timeout return self.sock_opt.timeout
@ -139,7 +154,10 @@ class WebSocket(object):
""" """
Set the timeout to the websocket. Set the timeout to the websocket.
timeout: timeout time(second). Parameters
----------
timeout: int or float
timeout time (in seconds). This value could be either float/integer.
""" """
self.sock_opt.timeout = timeout self.sock_opt.timeout = timeout
if self.sock: if self.sock:
@ -149,7 +167,7 @@ class WebSocket(object):
def getsubprotocol(self): def getsubprotocol(self):
""" """
get subprotocol Get subprotocol
""" """
if self.handshake_response: if self.handshake_response:
return self.handshake_response.subprotocol return self.handshake_response.subprotocol
@ -160,7 +178,7 @@ class WebSocket(object):
def getstatus(self): def getstatus(self):
""" """
get handshake status Get handshake status
""" """
if self.handshake_response: if self.handshake_response:
return self.handshake_response.status return self.handshake_response.status
@ -171,7 +189,7 @@ class WebSocket(object):
def getheaders(self): def getheaders(self):
""" """
get handshake response header Get handshake response header
""" """
if self.handshake_response: if self.handshake_response:
return self.handshake_response.headers return self.handshake_response.headers
@ -195,27 +213,39 @@ class WebSocket(object):
... header=["User-Agent: MyProgram", ... header=["User-Agent: MyProgram",
... "x-custom: header"]) ... "x-custom: header"])
timeout: socket timeout time. This value is integer. timeout: <type>
if you set None for this value, socket timeout time. This value is an integer or float.
it means "use default_timeout value" if you set None for this value, it means "use default_timeout value"
options: "header" -> custom http header list or dict. Parameters
"cookie" -> cookie value. ----------
"origin" -> custom origin url. options:
"suppress_origin" -> suppress outputting origin header. - header: list or dict
"host" -> custom host header string. custom http header list or dict.
"http_proxy_host" - http proxy host name. - cookie: str
"http_proxy_port" - http proxy port. If not set, set to 80. cookie value.
"http_no_proxy" - host names, which doesn't use proxy. - origin: str
"http_proxy_auth" - http proxy auth information. custom origin url.
tuple of username and password. - suppress_origin: bool
default is None suppress outputting origin header.
"redirect_limit" -> number of redirects to follow. - host: str
"subprotocols" - array of available sub protocols. custom host header string.
default is None. - http_proxy_host: <type>
"socket" - pre-initialized stream socket. http proxy host name.
- http_proxy_port: <type>
""" http proxy port. If not set, set to 80.
- http_no_proxy: <type>
host names, which doesn't use proxy.
- http_proxy_auth: <type>
http proxy auth information. tuple of username and password. default is None
- redirect_limit: <type>
number of redirects to follow.
- subprotocols: <type>
array of available sub protocols. default is None.
- socket: <type>
pre-initialized stream socket.
"""
self.sock_opt.timeout = options.get('timeout', self.sock_opt.timeout)
self.sock, addrs = connect(url, self.sock_opt, proxy_info(**options), self.sock, addrs = connect(url, self.sock_opt, proxy_info(**options),
options.pop('socket', None)) options.pop('socket', None))
@ -225,8 +255,8 @@ class WebSocket(object):
if self.handshake_response.status in SUPPORTED_REDIRECT_STATUSES: if self.handshake_response.status in SUPPORTED_REDIRECT_STATUSES:
url = self.handshake_response.headers['location'] url = self.handshake_response.headers['location']
self.sock.close() self.sock.close()
self.sock, addrs = connect(url, self.sock_opt, proxy_info(**options), self.sock, addrs = connect(url, self.sock_opt, proxy_info(**options),
options.pop('socket', None)) options.pop('socket', None))
self.handshake_response = handshake(self.sock, *addrs, **options) self.handshake_response = handshake(self.sock, *addrs, **options)
self.connected = True self.connected = True
except: except:
@ -239,11 +269,14 @@ class WebSocket(object):
""" """
Send the data as string. Send the data as string.
payload: Payload must be utf-8 string or unicode, Parameters
----------
payload: <type>
Payload must be utf-8 string or unicode,
if the opcode is OPCODE_TEXT. if the opcode is OPCODE_TEXT.
Otherwise, it must be string(byte array) Otherwise, it must be string(byte array)
opcode: <type>
opcode: operation code to send. Please see OPCODE_XXX. operation code to send. Please see OPCODE_XXX.
""" """
frame = ABNF.create_frame(payload, opcode) frame = ABNF.create_frame(payload, opcode)
@ -253,8 +286,6 @@ class WebSocket(object):
""" """
Send the data frame. Send the data frame.
frame: frame data created by ABNF.create_frame
>>> ws = create_connection("ws://echo.websocket.org/") >>> ws = create_connection("ws://echo.websocket.org/")
>>> frame = ABNF.create_frame("Hello", ABNF.OPCODE_TEXT) >>> frame = ABNF.create_frame("Hello", ABNF.OPCODE_TEXT)
>>> ws.send_frame(frame) >>> ws.send_frame(frame)
@ -263,12 +294,17 @@ class WebSocket(object):
>>> cont_frame = ABNF.create_frame("Foo Bar", ABNF.OPCODE_CONT, 1) >>> cont_frame = ABNF.create_frame("Foo Bar", ABNF.OPCODE_CONT, 1)
>>> ws.send_frame(frame) >>> ws.send_frame(frame)
Parameters
----------
frame: <type>
frame data created by ABNF.create_frame
""" """
if self.get_mask_key: if self.get_mask_key:
frame.get_mask_key = self.get_mask_key frame.get_mask_key = self.get_mask_key
data = frame.format() data = frame.format()
length = len(data) length = len(data)
trace("send: " + repr(data)) if (isEnabledForTrace()):
trace("send: " + repr(data))
with self.lock: with self.lock:
while data: while data:
@ -282,19 +318,25 @@ class WebSocket(object):
def ping(self, payload=""): def ping(self, payload=""):
""" """
send ping data. Send ping data.
payload: data payload to send server. Parameters
----------
payload: <type>
data payload to send server.
""" """
if isinstance(payload, six.text_type): if isinstance(payload, six.text_type):
payload = payload.encode("utf-8") payload = payload.encode("utf-8")
self.send(payload, ABNF.OPCODE_PING) self.send(payload, ABNF.OPCODE_PING)
def pong(self, payload): def pong(self, payload=""):
""" """
send pong data. Send pong data.
payload: data payload to send server. Parameters
----------
payload: <type>
data payload to send server.
""" """
if isinstance(payload, six.text_type): if isinstance(payload, six.text_type):
payload = payload.encode("utf-8") payload = payload.encode("utf-8")
@ -304,7 +346,9 @@ class WebSocket(object):
""" """
Receive string data(byte array) from the server. Receive string data(byte array) from the server.
return value: string(byte array) value. Returns
----------
data: string (byte array) value.
""" """
with self.readlock: with self.readlock:
opcode, data = self.recv_data() opcode, data = self.recv_data()
@ -319,10 +363,16 @@ class WebSocket(object):
""" """
Receive data with operation code. Receive data with operation code.
control_frame: a boolean flag indicating whether to return control frame Parameters
data, defaults to False ----------
control_frame: bool
a boolean flag indicating whether to return control frame
data, defaults to False
return value: tuple of operation code and string(byte array) value. Returns
-------
opcode, frame.data: tuple
tuple of operation code and string(byte array) value.
""" """
opcode, frame = self.recv_data_frame(control_frame) opcode, frame = self.recv_data_frame(control_frame)
return opcode, frame.data return opcode, frame.data
@ -331,10 +381,16 @@ class WebSocket(object):
""" """
Receive data with operation code. Receive data with operation code.
control_frame: a boolean flag indicating whether to return control frame Parameters
data, defaults to False ----------
control_frame: bool
a boolean flag indicating whether to return control frame
data, defaults to False
return value: tuple of operation code and string(byte array) value. Returns
-------
frame.opcode, frame: tuple
tuple of operation code and string(byte array) value.
""" """
while True: while True:
frame = self.recv_frame() frame = self.recv_frame()
@ -367,19 +423,24 @@ class WebSocket(object):
def recv_frame(self): def recv_frame(self):
""" """
receive data as frame from server. Receive data as frame from server.
return value: ABNF frame object. Returns
-------
self.frame_buffer.recv_frame(): ABNF frame object
""" """
return self.frame_buffer.recv_frame() return self.frame_buffer.recv_frame()
def send_close(self, status=STATUS_NORMAL, reason=six.b("")): def send_close(self, status=STATUS_NORMAL, reason=six.b("")):
""" """
send close data to the server. Send close data to the server.
status: status code to send. see STATUS_XXX.
reason: the reason to close. This must be string or bytes. Parameters
----------
status: <type>
status code to send. see STATUS_XXX.
reason: str or bytes
the reason to close. This must be string or bytes.
""" """
if status < 0 or status >= ABNF.LENGTH_16: if status < 0 or status >= ABNF.LENGTH_16:
raise ValueError("code is invalid range") raise ValueError("code is invalid range")
@ -390,11 +451,14 @@ class WebSocket(object):
""" """
Close Websocket object Close Websocket object
status: status code to send. see STATUS_XXX. Parameters
----------
reason: the reason to close. This must be string. status: <type>
status code to send. see STATUS_XXX.
timeout: timeout until receive a close frame. reason: <type>
the reason to close. This must be string.
timeout: int or float
timeout until receive a close frame.
If None, it will wait forever until receive a close frame. If None, it will wait forever until receive a close frame.
""" """
if self.connected: if self.connected:
@ -403,8 +467,7 @@ class WebSocket(object):
try: try:
self.connected = False self.connected = False
self.send(struct.pack('!H', status) + self.send(struct.pack('!H', status) + reason, ABNF.OPCODE_CLOSE)
reason, ABNF.OPCODE_CLOSE)
sock_timeout = self.sock.gettimeout() sock_timeout = self.sock.gettimeout()
self.sock.settimeout(timeout) self.sock.settimeout(timeout)
start_time = time.time() start_time = time.time()
@ -415,7 +478,9 @@ class WebSocket(object):
continue continue
if isEnabledForError(): if isEnabledForError():
recv_status = struct.unpack("!H", frame.data[0:2])[0] recv_status = struct.unpack("!H", frame.data[0:2])[0]
if recv_status != STATUS_NORMAL: if recv_status >= 3000 and recv_status <= 4999:
debug("close status: " + repr(recv_status))
elif recv_status != STATUS_NORMAL:
error("close status: " + repr(recv_status)) error("close status: " + repr(recv_status))
break break
except: except:
@ -425,7 +490,7 @@ class WebSocket(object):
except: except:
pass pass
self.shutdown() self.shutdown()
def abort(self): def abort(self):
""" """
@ -435,7 +500,9 @@ class WebSocket(object):
self.sock.shutdown(socket.SHUT_RDWR) self.sock.shutdown(socket.SHUT_RDWR)
def shutdown(self): def shutdown(self):
"""close socket, immediately.""" """
close socket, immediately.
"""
if self.sock: if self.sock:
self.sock.close() self.sock.close()
self.sock = None self.sock = None
@ -457,12 +524,12 @@ class WebSocket(object):
def create_connection(url, timeout=None, class_=WebSocket, **options): def create_connection(url, timeout=None, class_=WebSocket, **options):
""" """
connect to url and return websocket object. Connect to url and return websocket object.
Connect to url and return the WebSocket object. Connect to url and return the WebSocket object.
Passing optional timeout parameter will set the timeout on the socket. Passing optional timeout parameter will set the timeout on the socket.
If no timeout is supplied, If no timeout is supplied,
the global default timeout setting returned by getdefauttimeout() is used. the global default timeout setting returned by getdefaulttimeout() is used.
You can customize using 'options'. You can customize using 'options'.
If you set "header" list object, you can set your own custom header. If you set "header" list object, you can set your own custom header.
@ -470,33 +537,49 @@ def create_connection(url, timeout=None, class_=WebSocket, **options):
... header=["User-Agent: MyProgram", ... header=["User-Agent: MyProgram",
... "x-custom: header"]) ... "x-custom: header"])
Parameters
timeout: socket timeout time. This value is integer. ----------
timeout: int or float
socket timeout time. This value could be either float/integer.
if you set None for this value, if you set None for this value,
it means "use default_timeout value" it means "use default_timeout value"
class_: <type>
class_: class to instantiate when creating the connection. It has to implement class to instantiate when creating the connection. It has to implement
settimeout and connect. It's __init__ should be compatible with settimeout and connect. It's __init__ should be compatible with
WebSocket.__init__, i.e. accept all of it's kwargs. WebSocket.__init__, i.e. accept all of it's kwargs.
options: "header" -> custom http header list or dict. options: <type>
"cookie" -> cookie value. - header: list or dict
"origin" -> custom origin url. custom http header list or dict.
"suppress_origin" -> suppress outputting origin header. - cookie: str
"host" -> custom host header string. cookie value.
"http_proxy_host" - http proxy host name. - origin: str
"http_proxy_port" - http proxy port. If not set, set to 80. custom origin url.
"http_no_proxy" - host names, which doesn't use proxy. - suppress_origin: bool
"http_proxy_auth" - http proxy auth information. suppress outputting origin header.
tuple of username and password. - host: <type>
default is None custom host header string.
"enable_multithread" -> enable lock for multithread. - http_proxy_host: <type>
"redirect_limit" -> number of redirects to follow. http proxy host name.
"sockopt" -> socket options - http_proxy_port: <type>
"sslopt" -> ssl option http proxy port. If not set, set to 80.
"subprotocols" - array of available sub protocols. - http_no_proxy: <type>
default is None. host names, which doesn't use proxy.
"skip_utf8_validation" - skip utf8 validation. - http_proxy_auth: <type>
"socket" - pre-initialized stream socket. http proxy auth information. tuple of username and password. default is None
- enable_multithread: bool
enable lock for multithread.
- redirect_limit: <type>
number of redirects to follow.
- sockopt: <type>
socket options
- sslopt: <type>
ssl option
- subprotocols: <type>
array of available sub protocols. default is None.
- skip_utf8_validation: bool
skip utf8 validation.
- socket: <type>
pre-initialized stream socket.
""" """
sockopt = options.pop("sockopt", []) sockopt = options.pop("sockopt", [])
sslopt = options.pop("sslopt", {}) sslopt = options.pop("sslopt", {})

@ -1,3 +1,7 @@
"""
Define WebSocket exceptions
"""
""" """
websocket - WebSocket client library for Python websocket - WebSocket client library for Python
@ -15,34 +19,28 @@ Copyright (C) 2010 Hiroki Ohtani(liris)
You should have received a copy of the GNU Lesser General Public You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Boston, MA 02110-1335 USA
"""
"""
define websocket exceptions
""" """
class WebSocketException(Exception): class WebSocketException(Exception):
""" """
websocket exception class. WebSocket exception class.
""" """
pass pass
class WebSocketProtocolException(WebSocketException): class WebSocketProtocolException(WebSocketException):
""" """
If the websocket protocol is invalid, this exception will be raised. If the WebSocket protocol is invalid, this exception will be raised.
""" """
pass pass
class WebSocketPayloadException(WebSocketException): class WebSocketPayloadException(WebSocketException):
""" """
If the websocket payload is invalid, this exception will be raised. If the WebSocket payload is invalid, this exception will be raised.
""" """
pass pass
@ -74,10 +72,12 @@ class WebSocketBadStatusException(WebSocketException):
WebSocketBadStatusException will be raised when we get bad handshake status code. WebSocketBadStatusException will be raised when we get bad handshake status code.
""" """
def __init__(self, message, status_code, status_message=None): def __init__(self, message, status_code, status_message=None, resp_headers=None):
msg = message % (status_code, status_message) msg = message % (status_code, status_message)
super(WebSocketBadStatusException, self).__init__(msg) super(WebSocketBadStatusException, self).__init__(msg)
self.status_code = status_code self.status_code = status_code
self.resp_headers = resp_headers
class WebSocketAddressException(WebSocketException): class WebSocketAddressException(WebSocketException):
""" """

@ -15,8 +15,7 @@ Copyright (C) 2010 Hiroki Ohtani(liris)
You should have received a copy of the GNU Lesser General Public You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Boston, MA 02110-1335 USA
""" """
import hashlib import hashlib
@ -31,13 +30,13 @@ from ._http import *
from ._logging import * from ._logging import *
from ._socket import * from ._socket import *
if six.PY3: if hasattr(six, 'PY3') and six.PY3:
from base64 import encodebytes as base64encode from base64 import encodebytes as base64encode
else: else:
from base64 import encodestring as base64encode from base64 import encodestring as base64encode
if six.PY3: if hasattr(six, 'PY3') and six.PY3:
if six.PY34: if hasattr(six, 'PY34') and six.PY34:
from http import client as HTTPStatus from http import client as HTTPStatus
else: else:
from http import HTTPStatus from http import HTTPStatus
@ -55,7 +54,8 @@ else:
# websocket supported version. # websocket supported version.
VERSION = 13 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() CookieJar = SimpleCookieJar()
@ -85,6 +85,7 @@ def handshake(sock, hostname, port, resource, **options):
return handshake_response(status, resp, subproto) return handshake_response(status, resp, subproto)
def _pack_hostname(hostname): def _pack_hostname(hostname):
# IPv6 address # IPv6 address
if ':' in hostname: if ':' in hostname:
@ -92,17 +93,16 @@ def _pack_hostname(hostname):
return hostname return hostname
def _get_handshake_headers(resource, host, port, options): def _get_handshake_headers(resource, host, port, options):
headers = [ headers = [
"GET %s HTTP/1.1" % resource, "GET %s HTTP/1.1" % resource,
"Upgrade: websocket", "Upgrade: websocket"
"Connection: Upgrade"
] ]
if port == 80 or port == 443: if port == 80 or port == 443:
hostport = _pack_hostname(host) hostport = _pack_hostname(host)
else: else:
hostport = "%s:%d" % (_pack_hostname(host), port) hostport = "%s:%d" % (_pack_hostname(host), port)
if "host" in options and options["host"] is not None: if "host" in options and options["host"] is not None:
headers.append("Host: %s" % options["host"]) headers.append("Host: %s" % options["host"])
else: else:
@ -115,8 +115,21 @@ def _get_handshake_headers(resource, host, port, options):
headers.append("Origin: http://%s" % hostport) headers.append("Origin: http://%s" % hostport)
key = _create_sec_websocket_key() key = _create_sec_websocket_key()
headers.append("Sec-WebSocket-Key: %s" % key)
headers.append("Sec-WebSocket-Version: %s" % VERSION) # Append Sec-WebSocket-Key & Sec-WebSocket-Version if not manually specified
if 'header' not in options or 'Sec-WebSocket-Key' not in options['header']:
key = _create_sec_websocket_key()
headers.append("Sec-WebSocket-Key: %s" % key)
else:
key = options['header']['Sec-WebSocket-Key']
if 'header' not in options or 'Sec-WebSocket-Version' not in options['header']:
headers.append("Sec-WebSocket-Version: %s" % VERSION)
if 'connection' not in options or options['connection'] is None:
headers.append('Connection: Upgrade')
else:
headers.append(options['connection'])
subprotocols = options.get("subprotocols") subprotocols = options.get("subprotocols")
if subprotocols: if subprotocols:
@ -146,12 +159,13 @@ def _get_handshake_headers(resource, host, port, options):
return headers, key 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) status, resp_headers, status_message = read_headers(sock)
if status not in success_statuses: if status not in success_statuses:
raise WebSocketBadStatusException("Handshake status %d %s", status, status_message) raise WebSocketBadStatusException("Handshake status %d %s", status, status_message, resp_headers)
return status, resp_headers return status, resp_headers
_HEADERS_TO_CHECK = { _HEADERS_TO_CHECK = {
"upgrade": "websocket", "upgrade": "websocket",
"connection": "upgrade", "connection": "upgrade",
@ -164,15 +178,16 @@ def _validate(headers, key, subprotocols):
r = headers.get(k, None) r = headers.get(k, None)
if not r: if not r:
return False, None return False, None
r = r.lower() r = [x.strip().lower() for x in r.split(',')]
if v != r: if v not in r:
return False, None return False, None
if subprotocols: if subprotocols:
subproto = headers.get("sec-websocket-protocol", None).lower() subproto = headers.get("sec-websocket-protocol", None)
if not subproto or subproto not in [s.lower() for s in subprotocols]: if not subproto or subproto.lower() not in [s.lower() for s in subprotocols]:
error("Invalid subprotocol: " + str(subprotocols)) error("Invalid subprotocol: " + str(subprotocols))
return False, None return False, None
subproto = subproto.lower()
result = headers.get("sec-websocket-accept", None) result = headers.get("sec-websocket-accept", None)
if not result: if not result:

@ -15,8 +15,7 @@ Copyright (C) 2010 Hiroki Ohtani(liris)
You should have received a copy of the GNU Lesser General Public You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Boston, MA 02110-1335 USA
""" """
import errno import errno
@ -48,6 +47,7 @@ except:
pass pass
HAS_PYSOCKS = False HAS_PYSOCKS = False
class proxy_info(object): class proxy_info(object):
def __init__(self, **options): def __init__(self, **options):
@ -64,6 +64,7 @@ class proxy_info(object):
self.auth = None self.auth = None
self.no_proxy = None self.no_proxy = None
def _open_proxied_socket(url, options, proxy): def _open_proxied_socket(url, options, proxy):
hostname, port, resource, is_secure = parse_url(url) hostname, port, resource, is_secure = parse_url(url)
@ -80,15 +81,15 @@ def _open_proxied_socket(url, options, proxy):
rdns = True rdns = True
sock = socks.create_connection( sock = socks.create_connection(
(hostname, port), (hostname, port),
proxy_type = ptype, proxy_type=ptype,
proxy_addr = proxy.host, proxy_addr=proxy.host,
proxy_port = proxy.port, proxy_port=proxy.port,
proxy_rdns = rdns, proxy_rdns=rdns,
proxy_username = proxy.auth[0] if proxy.auth else None, proxy_username=proxy.auth[0] if proxy.auth else None,
proxy_password = proxy.auth[1] if proxy.auth else None, proxy_password=proxy.auth[1] if proxy.auth else None,
timeout = options.timeout, timeout=options.timeout,
socket_options = DEFAULT_SOCKET_OPTION + options.sockopt socket_options=DEFAULT_SOCKET_OPTION + options.sockopt
) )
if is_secure: if is_secure:
@ -138,15 +139,18 @@ def _get_addrinfo_list(hostname, port, is_secure, proxy):
phost, pport, pauth = get_proxy_info( phost, pport, pauth = get_proxy_info(
hostname, is_secure, proxy.host, proxy.port, proxy.auth, proxy.no_proxy) hostname, is_secure, proxy.host, proxy.port, proxy.auth, proxy.no_proxy)
try: 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: if not phost:
addrinfo_list = socket.getaddrinfo( 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 return addrinfo_list, False, None
else: else:
pport = pport and pport or 80 pport = pport and pport or 80
# when running on windows 10, the getaddrinfo used above # when running on windows 10, the getaddrinfo used above
# returns a socktype 0. This generates an error exception: # 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 # Force the socket type to SOCK_STREAM
addrinfo_list = socket.getaddrinfo(phost, pport, 0, socket.SOCK_STREAM, socket.SOL_TCP) addrinfo_list = socket.getaddrinfo(phost, pport, 0, socket.SOCK_STREAM, socket.SOL_TCP)
return addrinfo_list, True, pauth return addrinfo_list, True, pauth
@ -166,28 +170,35 @@ def _open_socket(addrinfo_list, sockopt, timeout):
sock.setsockopt(*opts) sock.setsockopt(*opts)
address = addrinfo[4] address = addrinfo[4]
try: err = None
sock.connect(address) while not err:
err = None
except ProxyConnectionError as error:
err = WebSocketProxyException(str(error))
err.remote_ip = str(address[0])
continue
except socket.error as error:
error.remote_ip = str(address[0])
try: try:
eConnRefused = (errno.ECONNREFUSED, errno.WSAECONNREFUSED) sock.connect(address)
except: except ProxyConnectionError as error:
eConnRefused = (errno.ECONNREFUSED, ) err = WebSocketProxyException(str(error))
if error.errno in eConnRefused: err.remote_ip = str(address[0])
err = error
continue continue
except socket.error as error:
error.remote_ip = str(address[0])
try:
eConnRefused = (errno.ECONNREFUSED, errno.WSAECONNREFUSED)
except:
eConnRefused = (errno.ECONNREFUSED, )
if error.errno == errno.EINTR:
continue
elif error.errno in eConnRefused:
err = error
continue
else:
raise error
else: else:
raise error break
else: else:
break continue
break
else: else:
raise err if err:
raise err
return sock return sock
@ -263,13 +274,15 @@ def _ssl_socket(sock, user_sslopt, hostname):
def _tunnel(sock, host, port, auth): def _tunnel(sock, host, port, auth):
debug("Connecting proxy...") debug("Connecting proxy...")
connect_header = "CONNECT %s:%d HTTP/1.0\r\n" % (host, port) connect_header = "CONNECT %s:%d HTTP/1.1\r\n" % (host, port)
connect_header += "Host: %s:%d\r\n" % (host, port)
# TODO: support digest auth. # TODO: support digest auth.
if auth and auth[0]: if auth and auth[0]:
auth_str = auth[0] auth_str = auth[0]
if auth[1]: if auth[1]:
auth_str += ":" + 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 += "Proxy-Authorization: Basic %s\r\n" % encoded_str
connect_header += "\r\n" connect_header += "\r\n"
dump("request header", connect_header) dump("request header", connect_header)
@ -310,7 +323,10 @@ def read_headers(sock):
kv = line.split(":", 1) kv = line.split(":", 1)
if len(kv) == 2: if len(kv) == 2:
key, value = kv key, value = kv
headers[key.lower()] = value.strip() if key.lower() == "set-cookie" and headers.get("set-cookie"):
headers["set-cookie"] = headers.get("set-cookie") + "; " + value.strip()
else:
headers[key.lower()] = value.strip()
else: else:
raise WebSocketException("Invalid header") raise WebSocketException("Invalid header")

@ -1,3 +1,7 @@
"""
"""
""" """
websocket - WebSocket client library for Python websocket - WebSocket client library for Python
@ -15,8 +19,7 @@ Copyright (C) 2010 Hiroki Ohtani(liris)
You should have received a copy of the GNU Lesser General Public You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Boston, MA 02110-1335 USA
""" """
import logging import logging
@ -34,14 +37,17 @@ _logger.addHandler(NullHandler())
_traceEnabled = False _traceEnabled = False
__all__ = ["enableTrace", "dump", "error", "warning", "debug", "trace", __all__ = ["enableTrace", "dump", "error", "warning", "debug", "trace",
"isEnabledForError", "isEnabledForDebug"] "isEnabledForError", "isEnabledForDebug", "isEnabledForTrace"]
def enableTrace(traceable, handler = logging.StreamHandler()): def enableTrace(traceable, handler=logging.StreamHandler()):
""" """
turn on/off the traceability. Turn on/off the traceability.
traceable: boolean value. if set True, traceability is enabled. Parameters
----------
traceable: bool
If set to True, traceability is enabled.
""" """
global _traceEnabled global _traceEnabled
_traceEnabled = traceable _traceEnabled = traceable
@ -80,3 +86,7 @@ def isEnabledForError():
def isEnabledForDebug(): def isEnabledForDebug():
return _logger.isEnabledFor(logging.DEBUG) return _logger.isEnabledFor(logging.DEBUG)
def isEnabledForTrace():
return _traceEnabled

@ -1,3 +1,7 @@
"""
"""
""" """
websocket - WebSocket client library for Python websocket - WebSocket client library for Python
@ -15,14 +19,14 @@ Copyright (C) 2010 Hiroki Ohtani(liris)
You should have received a copy of the GNU Lesser General Public You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Boston, MA 02110-1335 USA
""" """
import errno
import select
import socket import socket
import six import six
import sys
from ._exceptions import * from ._exceptions import *
from ._ssl_compat import * from ._ssl_compat import *
@ -60,7 +64,10 @@ def setdefaulttimeout(timeout):
""" """
Set the global timeout setting to connect. Set the global timeout setting to connect.
timeout: default socket timeout time. This value is second. Parameters
----------
timeout: int or float
default socket timeout time (in seconds)
""" """
global _default_timeout global _default_timeout
_default_timeout = timeout _default_timeout = timeout
@ -68,7 +75,12 @@ def setdefaulttimeout(timeout):
def getdefaulttimeout(): def getdefaulttimeout():
""" """
Return the global timeout setting(second) to connect. Get default timeout
Returns
----------
_default_timeout: int or float
Return the global timeout setting (in seconds) to connect.
""" """
return _default_timeout return _default_timeout
@ -77,8 +89,27 @@ def recv(sock, bufsize):
if not sock: if not sock:
raise WebSocketConnectionClosedException("socket is already closed.") raise WebSocketConnectionClosedException("socket is already closed.")
def _recv():
try:
return sock.recv(bufsize)
except SSLWantReadError:
pass
except socket.error as exc:
error_code = extract_error_code(exc)
if error_code is None:
raise
if error_code != errno.EAGAIN or error_code != errno.EWOULDBLOCK:
raise
r, w, e = select.select((sock, ), (), (), sock.gettimeout())
if r:
return sock.recv(bufsize)
try: try:
bytes_ = sock.recv(bufsize) if sock.gettimeout() == 0:
bytes_ = sock.recv(bufsize)
else:
bytes_ = _recv()
except socket.timeout as e: except socket.timeout as e:
message = extract_err_message(e) message = extract_err_message(e)
raise WebSocketTimeoutException(message) raise WebSocketTimeoutException(message)
@ -113,8 +144,27 @@ def send(sock, data):
if not sock: if not sock:
raise WebSocketConnectionClosedException("socket is already closed.") raise WebSocketConnectionClosedException("socket is already closed.")
def _send():
try:
return sock.send(data)
except SSLWantWriteError:
pass
except socket.error as exc:
error_code = extract_error_code(exc)
if error_code is None:
raise
if error_code != errno.EAGAIN or error_code != errno.EWOULDBLOCK:
raise
r, w, e = select.select((), (sock, ), (), sock.gettimeout())
if w:
return sock.send(data)
try: try:
return sock.send(data) if sock.gettimeout() == 0:
return sock.send(data)
else:
return _send()
except socket.timeout as e: except socket.timeout as e:
message = extract_err_message(e) message = extract_err_message(e)
raise WebSocketTimeoutException(message) raise WebSocketTimeoutException(message)

@ -15,15 +15,16 @@ Copyright (C) 2010 Hiroki Ohtani(liris)
You should have received a copy of the GNU Lesser General Public You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Boston, MA 02110-1335 USA
""" """
__all__ = ["HAVE_SSL", "ssl", "SSLError"] __all__ = ["HAVE_SSL", "ssl", "SSLError", "SSLWantReadError", "SSLWantWriteError"]
try: try:
import ssl import ssl
from ssl import SSLError from ssl import SSLError
from ssl import SSLWantReadError
from ssl import SSLWantWriteError
if hasattr(ssl, 'SSLContext') and hasattr(ssl.SSLContext, 'check_hostname'): if hasattr(ssl, 'SSLContext') and hasattr(ssl.SSLContext, 'check_hostname'):
HAVE_CONTEXT_CHECK_HOSTNAME = True HAVE_CONTEXT_CHECK_HOSTNAME = True
else: else:
@ -41,4 +42,12 @@ except ImportError:
class SSLError(Exception): class SSLError(Exception):
pass pass
class SSLWantReadError(Exception):
pass
class SSLWantWriteError(Exception):
pass
ssl = None
HAVE_SSL = False HAVE_SSL = False

@ -1,4 +1,7 @@
""" """
"""
"""
websocket - WebSocket client library for Python websocket - WebSocket client library for Python
Copyright (C) 2010 Hiroki Ohtani(liris) Copyright (C) 2010 Hiroki Ohtani(liris)
@ -15,8 +18,7 @@ Copyright (C) 2010 Hiroki Ohtani(liris)
You should have received a copy of the GNU Lesser General Public You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Boston, MA 02110-1335 USA
""" """
@ -35,14 +37,17 @@ def parse_url(url):
parse url and the result is tuple of parse url and the result is tuple of
(hostname, port, resource path and the flag of secure mode) (hostname, port, resource path and the flag of secure mode)
url: url string. Parameters
----------
url: str
url string.
""" """
if ":" not in url: if ":" not in url:
raise ValueError("url is invalid") raise ValueError("url is invalid")
scheme, url = url.split(":", 1) scheme, url = url.split(":", 1)
parsed = urlparse(url, scheme="ws") parsed = urlparse(url, scheme="http")
if parsed.hostname: if parsed.hostname:
hostname = parsed.hostname hostname = parsed.hostname
else: else:
@ -94,24 +99,31 @@ def _is_subnet_address(hostname):
def _is_address_in_network(ip, net): def _is_address_in_network(ip, net):
ipaddr = struct.unpack('I', socket.inet_aton(ip))[0] ipaddr = struct.unpack('!I', socket.inet_aton(ip))[0]
netaddr, bits = net.split('/') netaddr, netmask = net.split('/')
netmask = struct.unpack('I', socket.inet_aton(netaddr))[0] & ((2 << int(bits) - 1) - 1) netaddr = struct.unpack('!I', socket.inet_aton(netaddr))[0]
return ipaddr & netmask == netmask
netmask = (0xFFFFFFFF << (32 - int(netmask))) & 0xFFFFFFFF
return ipaddr & netmask == netaddr
def _is_no_proxy_host(hostname, no_proxy): def _is_no_proxy_host(hostname, no_proxy):
if not no_proxy: if not no_proxy:
v = os.environ.get("no_proxy", "").replace(" ", "") v = os.environ.get("no_proxy", "").replace(" ", "")
no_proxy = v.split(",") if v:
no_proxy = v.split(",")
if not no_proxy: if not no_proxy:
no_proxy = DEFAULT_NO_PROXY_HOST no_proxy = DEFAULT_NO_PROXY_HOST
if '*' in no_proxy:
return True
if hostname in no_proxy: if hostname in no_proxy:
return True return True
elif _is_ip_address(hostname): if _is_ip_address(hostname):
return any([_is_address_in_network(hostname, subnet) for subnet in no_proxy if _is_subnet_address(subnet)]) return any([_is_address_in_network(hostname, subnet) for subnet in no_proxy if _is_subnet_address(subnet)])
for domain in [domain for domain in no_proxy if domain.startswith('.')]:
if hostname.endswith(domain):
return True
return False return False
@ -119,27 +131,30 @@ def get_proxy_info(
hostname, is_secure, proxy_host=None, proxy_port=0, proxy_auth=None, hostname, is_secure, proxy_host=None, proxy_port=0, proxy_auth=None,
no_proxy=None, proxy_type='http'): no_proxy=None, proxy_type='http'):
""" """
try to retrieve proxy host and port from environment Try to retrieve proxy host and port from environment
if not provided in options. if not provided in options.
result is (proxy_host, proxy_port, proxy_auth). Result is (proxy_host, proxy_port, proxy_auth).
proxy_auth is tuple of username and password proxy_auth is tuple of username and password
of proxy authentication information. of proxy authentication information.
hostname: websocket server name. Parameters
----------
is_secure: is the connection secure? (wss) hostname: <type>
looks for "https_proxy" in env websocket server name.
before falling back to "http_proxy" is_secure: <type>
is the connection secure? (wss) looks for "https_proxy" in env
options: "http_proxy_host" - http proxy host name. before falling back to "http_proxy"
"http_proxy_port" - http proxy port. options: <type>
"http_no_proxy" - host names, which doesn't use proxy. - http_proxy_host: <type>
"http_proxy_auth" - http proxy auth information. http proxy host name.
tuple of username and password. - http_proxy_port: <type>
default is None http proxy port.
"proxy_type" - if set to "socks5" PySocks wrapper - http_no_proxy: <type>
will be used in place of a http proxy. host names, which doesn't use proxy.
default is "http" - http_proxy_auth: <type>
http proxy auth information. tuple of username and password. default is None
- proxy_type: <type>
if set to "socks5" PySocks wrapper will be used in place of a http proxy. default is "http"
""" """
if _is_no_proxy_host(hostname, no_proxy): if _is_no_proxy_host(hostname, no_proxy):
return None, 0, None return None, 0, None

@ -15,13 +15,12 @@ Copyright (C) 2010 Hiroki Ohtani(liris)
You should have received a copy of the GNU Lesser General Public You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Boston, MA 02110-1335 USA
""" """
import six import six
__all__ = ["NoLock", "validate_utf8", "extract_err_message"] __all__ = ["NoLock", "validate_utf8", "extract_err_message", "extract_error_code"]
class NoLock(object): class NoLock(object):
@ -32,6 +31,7 @@ class NoLock(object):
def __exit__(self, exc_type, exc_value, traceback): def __exit__(self, exc_type, exc_value, traceback):
pass pass
try: try:
# If wsaccel is available we use compiled routines to validate UTF-8 # If wsaccel is available we use compiled routines to validate UTF-8
# strings. # strings.
@ -103,3 +103,8 @@ def extract_err_message(exception):
return exception.args[0] return exception.args[0]
else: else:
return None return None
def extract_error_code(exception):
if exception.args and len(exception.args) > 1:
return exception.args[0] if isinstance(exception.args[0], int) else None

@ -0,0 +1,6 @@
HTTP/1.1 101 WebSocket Protocol Handshake
Connection: Upgrade, Keep-Alive
Upgrade: WebSocket
Sec-WebSocket-Accept: Kxep+hNu9n51529fGidYu7a3wO0=
some_header: something

@ -0,0 +1,77 @@
# -*- coding: utf-8 -*-
#
"""
websocket - WebSocket client library for Python
Copyright (C) 2010 Hiroki Ohtani(liris)
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
"""
import os
import websocket as ws
from websocket._abnf import *
import sys
sys.path[0:0] = [""]
if sys.version_info[0] == 2 and sys.version_info[1] < 7:
import unittest2 as unittest
else:
import unittest
class ABNFTest(unittest.TestCase):
def testInit(self):
a = ABNF(0,0,0,0, opcode=ABNF.OPCODE_PING)
self.assertEqual(a.fin, 0)
self.assertEqual(a.rsv1, 0)
self.assertEqual(a.rsv2, 0)
self.assertEqual(a.rsv3, 0)
self.assertEqual(a.opcode, 9)
self.assertEqual(a.data, '')
a_bad = ABNF(0,1,0,0, opcode=77)
self.assertEqual(a_bad.rsv1, 1)
self.assertEqual(a_bad.opcode, 77)
def testValidate(self):
a = ABNF(0,0,0,0, opcode=ABNF.OPCODE_PING)
self.assertRaises(ws.WebSocketProtocolException, a.validate)
a_bad = ABNF(0,1,0,0, opcode=77)
self.assertRaises(ws.WebSocketProtocolException, a_bad.validate)
a_close = ABNF(0,1,0,0, opcode=ABNF.OPCODE_CLOSE, data="abcdefgh1234567890abcdefgh1234567890abcdefgh1234567890abcdefgh1234567890")
self.assertRaises(ws.WebSocketProtocolException, a_close.validate)
# This caused an error in the Python 2.7 Github Actions build
# Uncomment test case when Python 2 support no longer wanted
# def testMask(self):
# ab = ABNF(0,0,0,0, opcode=ABNF.OPCODE_PING)
# bytes_val = bytes("aaaa", 'utf-8')
# self.assertEqual(ab._get_masked(bytes_val), bytes_val)
def testFrameBuffer(self):
fb = frame_buffer(0, True)
self.assertEqual(fb.recv, 0)
self.assertEqual(fb.skip_utf8_validation, True)
fb.clear
self.assertEqual(fb.header, None)
self.assertEqual(fb.length, None)
self.assertEqual(fb.mask, None)
self.assertEqual(fb.has_mask(), False)
if __name__ == "__main__":
unittest.main()

@ -0,0 +1,137 @@
# -*- coding: utf-8 -*-
#
"""
websocket - WebSocket client library for Python
Copyright (C) 2010 Hiroki Ohtani(liris)
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
"""
import os
import os.path
import websocket as ws
import sys
sys.path[0:0] = [""]
try:
import ssl
except ImportError:
HAVE_SSL = False
if sys.version_info[0] == 2 and sys.version_info[1] < 7:
import unittest2 as unittest
else:
import unittest
# Skip test to access the internet.
TEST_WITH_INTERNET = os.environ.get('TEST_WITH_INTERNET', '0') == '1'
TRACEABLE = True
class WebSocketAppTest(unittest.TestCase):
class NotSetYet(object):
""" A marker class for signalling that a value hasn't been set yet.
"""
def setUp(self):
ws.enableTrace(TRACEABLE)
WebSocketAppTest.keep_running_open = WebSocketAppTest.NotSetYet()
WebSocketAppTest.keep_running_close = WebSocketAppTest.NotSetYet()
WebSocketAppTest.get_mask_key_id = WebSocketAppTest.NotSetYet()
def tearDown(self):
WebSocketAppTest.keep_running_open = WebSocketAppTest.NotSetYet()
WebSocketAppTest.keep_running_close = WebSocketAppTest.NotSetYet()
WebSocketAppTest.get_mask_key_id = WebSocketAppTest.NotSetYet()
@unittest.skipUnless(TEST_WITH_INTERNET, "Internet-requiring tests are disabled")
def testKeepRunning(self):
""" A WebSocketApp should keep running as long as its self.keep_running
is not False (in the boolean context).
"""
def on_open(self, *args, **kwargs):
""" Set the keep_running flag for later inspection and immediately
close the connection.
"""
WebSocketAppTest.keep_running_open = self.keep_running
self.close()
def on_close(self, *args, **kwargs):
""" Set the keep_running flag for the test to use.
"""
WebSocketAppTest.keep_running_close = self.keep_running
app = ws.WebSocketApp('ws://echo.websocket.org/', on_open=on_open, on_close=on_close)
app.run_forever()
# if numpy is installed, this assertion fail
# self.assertFalse(isinstance(WebSocketAppTest.keep_running_open,
# WebSocketAppTest.NotSetYet))
# self.assertFalse(isinstance(WebSocketAppTest.keep_running_close,
# WebSocketAppTest.NotSetYet))
# self.assertEqual(True, WebSocketAppTest.keep_running_open)
# self.assertEqual(False, WebSocketAppTest.keep_running_close)
@unittest.skipUnless(TEST_WITH_INTERNET, "Internet-requiring tests are disabled")
def testSockMaskKey(self):
""" A WebSocketApp should forward the received mask_key function down
to the actual socket.
"""
def my_mask_key_func():
pass
def on_open(self, *args, **kwargs):
""" Set the value so the test can use it later on and immediately
close the connection.
"""
WebSocketAppTest.get_mask_key_id = id(self.get_mask_key)
self.close()
app = ws.WebSocketApp('ws://echo.websocket.org/', on_open=on_open, get_mask_key=my_mask_key_func)
app.run_forever()
# if numpy is installed, this assertion fail
# Note: We can't use 'is' for comparing the functions directly, need to use 'id'.
# self.assertEqual(WebSocketAppTest.get_mask_key_id, id(my_mask_key_func))
@unittest.skipUnless(TEST_WITH_INTERNET, "Internet-requiring tests are disabled")
def testPingInterval(self):
""" A WebSocketApp should ping regularly
"""
def on_ping(app, msg):
print("Got a ping!")
app.close()
def on_pong(app, msg):
print("Got a pong! No need to respond")
app.close()
app = ws.WebSocketApp('wss://api-pub.bitfinex.com/ws/1', on_ping=on_ping, on_pong=on_pong)
app.run_forever(ping_interval=2, ping_timeout=1) # , sslopt={"cert_reqs": ssl.CERT_NONE}
self.assertRaises(ws.WebSocketException, app.run_forever, ping_interval=2, ping_timeout=3, sslopt={"cert_reqs": ssl.CERT_NONE})
if __name__ == "__main__":
unittest.main()

@ -1,12 +1,31 @@
"""
"""
"""
websocket - WebSocket client library for Python
Copyright (C) 2010 Hiroki Ohtani(liris)
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
"""
import unittest import unittest
from websocket._cookiejar import SimpleCookieJar from websocket._cookiejar import SimpleCookieJar
try:
import Cookie
except:
import http.cookies as Cookie
class CookieJarTest(unittest.TestCase): class CookieJarTest(unittest.TestCase):
def testAdd(self): def testAdd(self):
@ -29,24 +48,24 @@ class CookieJarTest(unittest.TestCase):
cookie_jar = SimpleCookieJar() cookie_jar = SimpleCookieJar()
cookie_jar.add("a=b; c=d; domain=abc") cookie_jar.add("a=b; c=d; domain=abc")
self.assertEquals(cookie_jar.get("abc"), "a=b; c=d") self.assertEqual(cookie_jar.get("abc"), "a=b; c=d")
cookie_jar = SimpleCookieJar() cookie_jar = SimpleCookieJar()
cookie_jar.add("a=b; c=d; domain=abc") cookie_jar.add("a=b; c=d; domain=abc")
cookie_jar.add("e=f; domain=abc") cookie_jar.add("e=f; domain=abc")
self.assertEquals(cookie_jar.get("abc"), "a=b; c=d; e=f") self.assertEqual(cookie_jar.get("abc"), "a=b; c=d; e=f")
cookie_jar = SimpleCookieJar() cookie_jar = SimpleCookieJar()
cookie_jar.add("a=b; c=d; domain=abc") cookie_jar.add("a=b; c=d; domain=abc")
cookie_jar.add("e=f; domain=.abc") cookie_jar.add("e=f; domain=.abc")
self.assertEquals(cookie_jar.get("abc"), "a=b; c=d; e=f") self.assertEqual(cookie_jar.get("abc"), "a=b; c=d; e=f")
cookie_jar = SimpleCookieJar() cookie_jar = SimpleCookieJar()
cookie_jar.add("a=b; c=d; domain=abc") cookie_jar.add("a=b; c=d; domain=abc")
cookie_jar.add("e=f; domain=xyz") cookie_jar.add("e=f; domain=xyz")
self.assertEquals(cookie_jar.get("abc"), "a=b; c=d") self.assertEqual(cookie_jar.get("abc"), "a=b; c=d")
self.assertEquals(cookie_jar.get("xyz"), "e=f") self.assertEqual(cookie_jar.get("xyz"), "e=f")
self.assertEquals(cookie_jar.get("something"), "") self.assertEqual(cookie_jar.get("something"), "")
def testSet(self): def testSet(self):
cookie_jar = SimpleCookieJar() cookie_jar = SimpleCookieJar()
@ -64,35 +83,35 @@ class CookieJarTest(unittest.TestCase):
cookie_jar = SimpleCookieJar() cookie_jar = SimpleCookieJar()
cookie_jar.set("a=b; c=d; domain=abc") cookie_jar.set("a=b; c=d; domain=abc")
self.assertEquals(cookie_jar.get("abc"), "a=b; c=d") self.assertEqual(cookie_jar.get("abc"), "a=b; c=d")
cookie_jar = SimpleCookieJar() cookie_jar = SimpleCookieJar()
cookie_jar.set("a=b; c=d; domain=abc") cookie_jar.set("a=b; c=d; domain=abc")
cookie_jar.set("e=f; domain=abc") cookie_jar.set("e=f; domain=abc")
self.assertEquals(cookie_jar.get("abc"), "e=f") self.assertEqual(cookie_jar.get("abc"), "e=f")
cookie_jar = SimpleCookieJar() cookie_jar = SimpleCookieJar()
cookie_jar.set("a=b; c=d; domain=abc") cookie_jar.set("a=b; c=d; domain=abc")
cookie_jar.set("e=f; domain=.abc") cookie_jar.set("e=f; domain=.abc")
self.assertEquals(cookie_jar.get("abc"), "e=f") self.assertEqual(cookie_jar.get("abc"), "e=f")
cookie_jar = SimpleCookieJar() cookie_jar = SimpleCookieJar()
cookie_jar.set("a=b; c=d; domain=abc") cookie_jar.set("a=b; c=d; domain=abc")
cookie_jar.set("e=f; domain=xyz") cookie_jar.set("e=f; domain=xyz")
self.assertEquals(cookie_jar.get("abc"), "a=b; c=d") self.assertEqual(cookie_jar.get("abc"), "a=b; c=d")
self.assertEquals(cookie_jar.get("xyz"), "e=f") self.assertEqual(cookie_jar.get("xyz"), "e=f")
self.assertEquals(cookie_jar.get("something"), "") self.assertEqual(cookie_jar.get("something"), "")
def testGet(self): def testGet(self):
cookie_jar = SimpleCookieJar() cookie_jar = SimpleCookieJar()
cookie_jar.set("a=b; c=d; domain=abc.com") cookie_jar.set("a=b; c=d; domain=abc.com")
self.assertEquals(cookie_jar.get("abc.com"), "a=b; c=d") self.assertEqual(cookie_jar.get("abc.com"), "a=b; c=d")
self.assertEquals(cookie_jar.get("x.abc.com"), "a=b; c=d") self.assertEqual(cookie_jar.get("x.abc.com"), "a=b; c=d")
self.assertEquals(cookie_jar.get("abc.com.es"), "") self.assertEqual(cookie_jar.get("abc.com.es"), "")
self.assertEquals(cookie_jar.get("xabc.com"), "") self.assertEqual(cookie_jar.get("xabc.com"), "")
cookie_jar.set("a=b; c=d; domain=.abc.com") cookie_jar.set("a=b; c=d; domain=.abc.com")
self.assertEquals(cookie_jar.get("abc.com"), "a=b; c=d") self.assertEqual(cookie_jar.get("abc.com"), "a=b; c=d")
self.assertEquals(cookie_jar.get("x.abc.com"), "a=b; c=d") self.assertEqual(cookie_jar.get("x.abc.com"), "a=b; c=d")
self.assertEquals(cookie_jar.get("abc.com.es"), "") self.assertEqual(cookie_jar.get("abc.com.es"), "")
self.assertEquals(cookie_jar.get("xabc.com"), "") self.assertEqual(cookie_jar.get("xabc.com"), "")

@ -0,0 +1,109 @@
# -*- coding: utf-8 -*-
#
"""
websocket - WebSocket client library for Python
Copyright (C) 2010 Hiroki Ohtani(liris)
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
"""
import os
import os.path
import websocket as ws
from websocket._http import proxy_info, read_headers, _open_proxied_socket, _tunnel
import sys
sys.path[0:0] = [""]
if sys.version_info[0] == 2 and sys.version_info[1] < 7:
import unittest2 as unittest
else:
import unittest
class SockMock(object):
def __init__(self):
self.data = []
self.sent = []
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)
if isinstance(e, Exception):
raise e
if len(e) > bufsize:
self.data.insert(0, e[bufsize:])
return e[:bufsize]
def send(self, data):
self.sent.append(data)
return len(data)
def close(self):
pass
class HeaderSockMock(SockMock):
def __init__(self, fname):
SockMock.__init__(self)
path = os.path.join(os.path.dirname(__file__), fname)
with open(path, "rb") as f:
self.add_packet(f.read())
class OptsList():
def __init__(self):
self.timeout = 0
self.sockopt = []
class HttpTest(unittest.TestCase):
def testReadHeader(self):
status, header, status_message = read_headers(HeaderSockMock("data/header01.txt"))
self.assertEqual(status, 101)
self.assertEqual(header["connection"], "Upgrade")
# header02.txt is intentionally malformed
self.assertRaises(ws.WebSocketException, read_headers, HeaderSockMock("data/header02.txt"))
def testTunnel(self):
self.assertRaises(ws.WebSocketProxyException, _tunnel, HeaderSockMock("data/header01.txt"), "example.com", 80, ("username", "password"))
self.assertRaises(ws.WebSocketProxyException, _tunnel, HeaderSockMock("data/header02.txt"), "example.com", 80, ("username", "password"))
def testConnect(self):
# Not currently testing an actual proxy connection, so just check whether TypeError is raised
self.assertRaises(TypeError, _open_proxied_socket, "wss://example.com", OptsList(), proxy_info(http_proxy_host="example.com", http_proxy_port="8080", proxy_type="http"))
self.assertRaises(TypeError, _open_proxied_socket, "wss://example.com", OptsList(), proxy_info(http_proxy_host="example.com", http_proxy_port="8080", proxy_type="socks4"))
self.assertRaises(TypeError, _open_proxied_socket, "wss://example.com", OptsList(), proxy_info(http_proxy_host="example.com", http_proxy_port="8080", proxy_type="socks5h"))
def testProxyInfo(self):
self.assertEqual(proxy_info(http_proxy_host="127.0.0.1", http_proxy_port="8080", proxy_type="http").type, "http")
self.assertRaises(ValueError, proxy_info, http_proxy_host="127.0.0.1", http_proxy_port="8080", proxy_type="badval")
self.assertEqual(proxy_info(http_proxy_host="example.com", http_proxy_port="8080", proxy_type="http").host, "example.com")
self.assertEqual(proxy_info(http_proxy_host="127.0.0.1", http_proxy_port="8080", proxy_type="http").port, "8080")
self.assertEqual(proxy_info(http_proxy_host="127.0.0.1", http_proxy_port="8080", proxy_type="http").auth, None)
if __name__ == "__main__":
unittest.main()

@ -0,0 +1,309 @@
# -*- coding: utf-8 -*-
#
"""
websocket - WebSocket client library for Python
Copyright (C) 2010 Hiroki Ohtani(liris)
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
"""
import sys
import os
from websocket._url import get_proxy_info, parse_url, _is_address_in_network, _is_no_proxy_host
if sys.version_info[0] == 2 and sys.version_info[1] < 7:
import unittest2 as unittest
else:
import unittest
sys.path[0:0] = [""]
class UrlTest(unittest.TestCase):
def test_address_in_network(self):
self.assertTrue(_is_address_in_network('127.0.0.1', '127.0.0.0/8'))
self.assertTrue(_is_address_in_network('127.1.0.1', '127.0.0.0/8'))
self.assertFalse(_is_address_in_network('127.1.0.1', '127.0.0.0/24'))
def testParseUrl(self):
p = parse_url("ws://www.example.com/r")
self.assertEqual(p[0], "www.example.com")
self.assertEqual(p[1], 80)
self.assertEqual(p[2], "/r")
self.assertEqual(p[3], False)
p = parse_url("ws://www.example.com/r/")
self.assertEqual(p[0], "www.example.com")
self.assertEqual(p[1], 80)
self.assertEqual(p[2], "/r/")
self.assertEqual(p[3], False)
p = parse_url("ws://www.example.com/")
self.assertEqual(p[0], "www.example.com")
self.assertEqual(p[1], 80)
self.assertEqual(p[2], "/")
self.assertEqual(p[3], False)
p = parse_url("ws://www.example.com")
self.assertEqual(p[0], "www.example.com")
self.assertEqual(p[1], 80)
self.assertEqual(p[2], "/")
self.assertEqual(p[3], False)
p = parse_url("ws://www.example.com:8080/r")
self.assertEqual(p[0], "www.example.com")
self.assertEqual(p[1], 8080)
self.assertEqual(p[2], "/r")
self.assertEqual(p[3], False)
p = parse_url("ws://www.example.com:8080/")
self.assertEqual(p[0], "www.example.com")
self.assertEqual(p[1], 8080)
self.assertEqual(p[2], "/")
self.assertEqual(p[3], False)
p = parse_url("ws://www.example.com:8080")
self.assertEqual(p[0], "www.example.com")
self.assertEqual(p[1], 8080)
self.assertEqual(p[2], "/")
self.assertEqual(p[3], False)
p = parse_url("wss://www.example.com:8080/r")
self.assertEqual(p[0], "www.example.com")
self.assertEqual(p[1], 8080)
self.assertEqual(p[2], "/r")
self.assertEqual(p[3], True)
p = parse_url("wss://www.example.com:8080/r?key=value")
self.assertEqual(p[0], "www.example.com")
self.assertEqual(p[1], 8080)
self.assertEqual(p[2], "/r?key=value")
self.assertEqual(p[3], True)
self.assertRaises(ValueError, parse_url, "http://www.example.com/r")
if sys.version_info[0] == 2 and sys.version_info[1] < 7:
return
p = parse_url("ws://[2a03:4000:123:83::3]/r")
self.assertEqual(p[0], "2a03:4000:123:83::3")
self.assertEqual(p[1], 80)
self.assertEqual(p[2], "/r")
self.assertEqual(p[3], False)
p = parse_url("ws://[2a03:4000:123:83::3]:8080/r")
self.assertEqual(p[0], "2a03:4000:123:83::3")
self.assertEqual(p[1], 8080)
self.assertEqual(p[2], "/r")
self.assertEqual(p[3], False)
p = parse_url("wss://[2a03:4000:123:83::3]/r")
self.assertEqual(p[0], "2a03:4000:123:83::3")
self.assertEqual(p[1], 443)
self.assertEqual(p[2], "/r")
self.assertEqual(p[3], True)
p = parse_url("wss://[2a03:4000:123:83::3]:8080/r")
self.assertEqual(p[0], "2a03:4000:123:83::3")
self.assertEqual(p[1], 8080)
self.assertEqual(p[2], "/r")
self.assertEqual(p[3], True)
class IsNoProxyHostTest(unittest.TestCase):
def setUp(self):
self.no_proxy = os.environ.get("no_proxy", None)
if "no_proxy" in os.environ:
del os.environ["no_proxy"]
def tearDown(self):
if self.no_proxy:
os.environ["no_proxy"] = self.no_proxy
elif "no_proxy" in os.environ:
del os.environ["no_proxy"]
def testMatchAll(self):
self.assertTrue(_is_no_proxy_host("any.websocket.org", ['*']))
self.assertTrue(_is_no_proxy_host("192.168.0.1", ['*']))
self.assertTrue(_is_no_proxy_host("any.websocket.org", ['other.websocket.org', '*']))
os.environ['no_proxy'] = '*'
self.assertTrue(_is_no_proxy_host("any.websocket.org", None))
self.assertTrue(_is_no_proxy_host("192.168.0.1", None))
os.environ['no_proxy'] = 'other.websocket.org, *'
self.assertTrue(_is_no_proxy_host("any.websocket.org", None))
def testIpAddress(self):
self.assertTrue(_is_no_proxy_host("127.0.0.1", ['127.0.0.1']))
self.assertFalse(_is_no_proxy_host("127.0.0.2", ['127.0.0.1']))
self.assertTrue(_is_no_proxy_host("127.0.0.1", ['other.websocket.org', '127.0.0.1']))
self.assertFalse(_is_no_proxy_host("127.0.0.2", ['other.websocket.org', '127.0.0.1']))
os.environ['no_proxy'] = '127.0.0.1'
self.assertTrue(_is_no_proxy_host("127.0.0.1", None))
self.assertFalse(_is_no_proxy_host("127.0.0.2", None))
os.environ['no_proxy'] = 'other.websocket.org, 127.0.0.1'
self.assertTrue(_is_no_proxy_host("127.0.0.1", None))
self.assertFalse(_is_no_proxy_host("127.0.0.2", None))
def testIpAddressInRange(self):
self.assertTrue(_is_no_proxy_host("127.0.0.1", ['127.0.0.0/8']))
self.assertTrue(_is_no_proxy_host("127.0.0.2", ['127.0.0.0/8']))
self.assertFalse(_is_no_proxy_host("127.1.0.1", ['127.0.0.0/24']))
os.environ['no_proxy'] = '127.0.0.0/8'
self.assertTrue(_is_no_proxy_host("127.0.0.1", None))
self.assertTrue(_is_no_proxy_host("127.0.0.2", None))
os.environ['no_proxy'] = '127.0.0.0/24'
self.assertFalse(_is_no_proxy_host("127.1.0.1", None))
def testHostnameMatch(self):
self.assertTrue(_is_no_proxy_host("my.websocket.org", ['my.websocket.org']))
self.assertTrue(_is_no_proxy_host("my.websocket.org", ['other.websocket.org', 'my.websocket.org']))
self.assertFalse(_is_no_proxy_host("my.websocket.org", ['other.websocket.org']))
os.environ['no_proxy'] = 'my.websocket.org'
self.assertTrue(_is_no_proxy_host("my.websocket.org", None))
self.assertFalse(_is_no_proxy_host("other.websocket.org", None))
os.environ['no_proxy'] = 'other.websocket.org, my.websocket.org'
self.assertTrue(_is_no_proxy_host("my.websocket.org", None))
def testHostnameMatchDomain(self):
self.assertTrue(_is_no_proxy_host("any.websocket.org", ['.websocket.org']))
self.assertTrue(_is_no_proxy_host("my.other.websocket.org", ['.websocket.org']))
self.assertTrue(_is_no_proxy_host("any.websocket.org", ['my.websocket.org', '.websocket.org']))
self.assertFalse(_is_no_proxy_host("any.websocket.com", ['.websocket.org']))
os.environ['no_proxy'] = '.websocket.org'
self.assertTrue(_is_no_proxy_host("any.websocket.org", None))
self.assertTrue(_is_no_proxy_host("my.other.websocket.org", None))
self.assertFalse(_is_no_proxy_host("any.websocket.com", None))
os.environ['no_proxy'] = 'my.websocket.org, .websocket.org'
self.assertTrue(_is_no_proxy_host("any.websocket.org", None))
class ProxyInfoTest(unittest.TestCase):
def setUp(self):
self.http_proxy = os.environ.get("http_proxy", None)
self.https_proxy = os.environ.get("https_proxy", None)
self.no_proxy = os.environ.get("no_proxy", None)
if "http_proxy" in os.environ:
del os.environ["http_proxy"]
if "https_proxy" in os.environ:
del os.environ["https_proxy"]
if "no_proxy" in os.environ:
del os.environ["no_proxy"]
def tearDown(self):
if self.http_proxy:
os.environ["http_proxy"] = self.http_proxy
elif "http_proxy" in os.environ:
del os.environ["http_proxy"]
if self.https_proxy:
os.environ["https_proxy"] = self.https_proxy
elif "https_proxy" in os.environ:
del os.environ["https_proxy"]
if self.no_proxy:
os.environ["no_proxy"] = self.no_proxy
elif "no_proxy" in os.environ:
del os.environ["no_proxy"]
def testProxyFromArgs(self):
self.assertEqual(get_proxy_info("echo.websocket.org", False, proxy_host="localhost"), ("localhost", 0, None))
self.assertEqual(get_proxy_info("echo.websocket.org", False, proxy_host="localhost", proxy_port=3128),
("localhost", 3128, None))
self.assertEqual(get_proxy_info("echo.websocket.org", True, proxy_host="localhost"), ("localhost", 0, None))
self.assertEqual(get_proxy_info("echo.websocket.org", True, proxy_host="localhost", proxy_port=3128),
("localhost", 3128, None))
self.assertEqual(get_proxy_info("echo.websocket.org", False, proxy_host="localhost", proxy_auth=("a", "b")),
("localhost", 0, ("a", "b")))
self.assertEqual(
get_proxy_info("echo.websocket.org", False, proxy_host="localhost", proxy_port=3128, proxy_auth=("a", "b")),
("localhost", 3128, ("a", "b")))
self.assertEqual(get_proxy_info("echo.websocket.org", True, proxy_host="localhost", proxy_auth=("a", "b")),
("localhost", 0, ("a", "b")))
self.assertEqual(
get_proxy_info("echo.websocket.org", True, proxy_host="localhost", proxy_port=3128, proxy_auth=("a", "b")),
("localhost", 3128, ("a", "b")))
self.assertEqual(get_proxy_info("echo.websocket.org", True, proxy_host="localhost", proxy_port=3128,
no_proxy=["example.com"], proxy_auth=("a", "b")),
("localhost", 3128, ("a", "b")))
self.assertEqual(get_proxy_info("echo.websocket.org", True, proxy_host="localhost", proxy_port=3128,
no_proxy=["echo.websocket.org"], proxy_auth=("a", "b")),
(None, 0, None))
def testProxyFromEnv(self):
os.environ["http_proxy"] = "http://localhost/"
self.assertEqual(get_proxy_info("echo.websocket.org", False), ("localhost", None, None))
os.environ["http_proxy"] = "http://localhost:3128/"
self.assertEqual(get_proxy_info("echo.websocket.org", False), ("localhost", 3128, None))
os.environ["http_proxy"] = "http://localhost/"
os.environ["https_proxy"] = "http://localhost2/"
self.assertEqual(get_proxy_info("echo.websocket.org", False), ("localhost", None, None))
os.environ["http_proxy"] = "http://localhost:3128/"
os.environ["https_proxy"] = "http://localhost2:3128/"
self.assertEqual(get_proxy_info("echo.websocket.org", False), ("localhost", 3128, None))
os.environ["http_proxy"] = "http://localhost/"
os.environ["https_proxy"] = "http://localhost2/"
self.assertEqual(get_proxy_info("echo.websocket.org", True), ("localhost2", None, None))
os.environ["http_proxy"] = "http://localhost:3128/"
os.environ["https_proxy"] = "http://localhost2:3128/"
self.assertEqual(get_proxy_info("echo.websocket.org", True), ("localhost2", 3128, None))
os.environ["http_proxy"] = "http://a:b@localhost/"
self.assertEqual(get_proxy_info("echo.websocket.org", False), ("localhost", None, ("a", "b")))
os.environ["http_proxy"] = "http://a:b@localhost:3128/"
self.assertEqual(get_proxy_info("echo.websocket.org", False), ("localhost", 3128, ("a", "b")))
os.environ["http_proxy"] = "http://a:b@localhost/"
os.environ["https_proxy"] = "http://a:b@localhost2/"
self.assertEqual(get_proxy_info("echo.websocket.org", False), ("localhost", None, ("a", "b")))
os.environ["http_proxy"] = "http://a:b@localhost:3128/"
os.environ["https_proxy"] = "http://a:b@localhost2:3128/"
self.assertEqual(get_proxy_info("echo.websocket.org", False), ("localhost", 3128, ("a", "b")))
os.environ["http_proxy"] = "http://a:b@localhost/"
os.environ["https_proxy"] = "http://a:b@localhost2/"
self.assertEqual(get_proxy_info("echo.websocket.org", True), ("localhost2", None, ("a", "b")))
os.environ["http_proxy"] = "http://a:b@localhost:3128/"
os.environ["https_proxy"] = "http://a:b@localhost2:3128/"
self.assertEqual(get_proxy_info("echo.websocket.org", True), ("localhost2", 3128, ("a", "b")))
os.environ["http_proxy"] = "http://a:b@localhost/"
os.environ["https_proxy"] = "http://a:b@localhost2/"
os.environ["no_proxy"] = "example1.com,example2.com"
self.assertEqual(get_proxy_info("example.1.com", True), ("localhost2", None, ("a", "b")))
os.environ["http_proxy"] = "http://a:b@localhost:3128/"
os.environ["https_proxy"] = "http://a:b@localhost2:3128/"
os.environ["no_proxy"] = "example1.com,example2.com, echo.websocket.org"
self.assertEqual(get_proxy_info("echo.websocket.org", True), (None, 0, None))
os.environ["http_proxy"] = "http://a:b@localhost:3128/"
os.environ["https_proxy"] = "http://a:b@localhost2:3128/"
os.environ["no_proxy"] = "example1.com,example2.com, .websocket.org"
self.assertEqual(get_proxy_info("echo.websocket.org", True), (None, 0, None))
os.environ["http_proxy"] = "http://a:b@localhost:3128/"
os.environ["https_proxy"] = "http://a:b@localhost2:3128/"
os.environ["no_proxy"] = "127.0.0.0/8, 192.168.0.0/16"
self.assertEqual(get_proxy_info("127.0.0.1", False), (None, 0, None))
self.assertEqual(get_proxy_info("192.168.1.1", False), (None, 0, None))
if __name__ == "__main__":
unittest.main()

@ -1,5 +1,29 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
"""
"""
"""
websocket - WebSocket client library for Python
Copyright (C) 2010 Hiroki Ohtani(liris)
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
"""
import sys import sys
sys.path[0:0] = [""] sys.path[0:0] = [""]
@ -15,7 +39,6 @@ import websocket as ws
from websocket._handshake import _create_sec_websocket_key, \ from websocket._handshake import _create_sec_websocket_key, \
_validate as _validate_header _validate as _validate_header
from websocket._http import read_headers from websocket._http import read_headers
from websocket._url import get_proxy_info, parse_url
from websocket._utils import validate_utf8 from websocket._utils import validate_utf8
if six.PY3: if six.PY3:
@ -37,9 +60,6 @@ except ImportError:
# Skip test to access the internet. # Skip test to access the internet.
TEST_WITH_INTERNET = os.environ.get('TEST_WITH_INTERNET', '0') == '1' TEST_WITH_INTERNET = os.environ.get('TEST_WITH_INTERNET', '0') == '1'
# Skip Secure WebSocket test.
TEST_SECURE_WS = True
TRACEABLE = True TRACEABLE = True
@ -55,6 +75,9 @@ class SockMock(object):
def add_packet(self, data): def add_packet(self, data):
self.data.append(data) self.data.append(data)
def gettimeout(self):
return None
def recv(self, bufsize): def recv(self, bufsize):
if self.data: if self.data:
e = self.data.pop(0) e = self.data.pop(0)
@ -94,102 +117,24 @@ class WebSocketTest(unittest.TestCase):
self.assertEqual(ws.getdefaulttimeout(), 10) self.assertEqual(ws.getdefaulttimeout(), 10)
ws.setdefaulttimeout(None) ws.setdefaulttimeout(None)
def testParseUrl(self):
p = parse_url("ws://www.example.com/r")
self.assertEqual(p[0], "www.example.com")
self.assertEqual(p[1], 80)
self.assertEqual(p[2], "/r")
self.assertEqual(p[3], False)
p = parse_url("ws://www.example.com/r/")
self.assertEqual(p[0], "www.example.com")
self.assertEqual(p[1], 80)
self.assertEqual(p[2], "/r/")
self.assertEqual(p[3], False)
p = parse_url("ws://www.example.com/")
self.assertEqual(p[0], "www.example.com")
self.assertEqual(p[1], 80)
self.assertEqual(p[2], "/")
self.assertEqual(p[3], False)
p = parse_url("ws://www.example.com")
self.assertEqual(p[0], "www.example.com")
self.assertEqual(p[1], 80)
self.assertEqual(p[2], "/")
self.assertEqual(p[3], False)
p = parse_url("ws://www.example.com:8080/r")
self.assertEqual(p[0], "www.example.com")
self.assertEqual(p[1], 8080)
self.assertEqual(p[2], "/r")
self.assertEqual(p[3], False)
p = parse_url("ws://www.example.com:8080/")
self.assertEqual(p[0], "www.example.com")
self.assertEqual(p[1], 8080)
self.assertEqual(p[2], "/")
self.assertEqual(p[3], False)
p = parse_url("ws://www.example.com:8080")
self.assertEqual(p[0], "www.example.com")
self.assertEqual(p[1], 8080)
self.assertEqual(p[2], "/")
self.assertEqual(p[3], False)
p = parse_url("wss://www.example.com:8080/r")
self.assertEqual(p[0], "www.example.com")
self.assertEqual(p[1], 8080)
self.assertEqual(p[2], "/r")
self.assertEqual(p[3], True)
p = parse_url("wss://www.example.com:8080/r?key=value")
self.assertEqual(p[0], "www.example.com")
self.assertEqual(p[1], 8080)
self.assertEqual(p[2], "/r?key=value")
self.assertEqual(p[3], True)
self.assertRaises(ValueError, parse_url, "http://www.example.com/r")
if sys.version_info[0] == 2 and sys.version_info[1] < 7:
return
p = parse_url("ws://[2a03:4000:123:83::3]/r")
self.assertEqual(p[0], "2a03:4000:123:83::3")
self.assertEqual(p[1], 80)
self.assertEqual(p[2], "/r")
self.assertEqual(p[3], False)
p = parse_url("ws://[2a03:4000:123:83::3]:8080/r")
self.assertEqual(p[0], "2a03:4000:123:83::3")
self.assertEqual(p[1], 8080)
self.assertEqual(p[2], "/r")
self.assertEqual(p[3], False)
p = parse_url("wss://[2a03:4000:123:83::3]/r")
self.assertEqual(p[0], "2a03:4000:123:83::3")
self.assertEqual(p[1], 443)
self.assertEqual(p[2], "/r")
self.assertEqual(p[3], True)
p = parse_url("wss://[2a03:4000:123:83::3]:8080/r")
self.assertEqual(p[0], "2a03:4000:123:83::3")
self.assertEqual(p[1], 8080)
self.assertEqual(p[2], "/r")
self.assertEqual(p[3], True)
def testWSKey(self): def testWSKey(self):
key = _create_sec_websocket_key() key = _create_sec_websocket_key()
self.assertTrue(key != 24) self.assertTrue(key != 24)
self.assertTrue(six.u("¥n") not in key) self.assertTrue(six.u("¥n") not in key)
def testNonce(self):
""" WebSocket key should be a random 16-byte nonce.
"""
key = _create_sec_websocket_key()
nonce = base64decode(key.encode("utf-8"))
self.assertEqual(16, len(nonce))
def testWsUtils(self): def testWsUtils(self):
key = "c6b8hTg4EeGb2gQMztV1/g==" key = "c6b8hTg4EeGb2gQMztV1/g=="
required_header = { required_header = {
"upgrade": "websocket", "upgrade": "websocket",
"connection": "upgrade", "connection": "upgrade",
"sec-websocket-accept": "Kxep+hNu9n51529fGidYu7a3wO0=", "sec-websocket-accept": "Kxep+hNu9n51529fGidYu7a3wO0="}
}
self.assertEqual(_validate_header(required_header, key, None), (True, None)) self.assertEqual(_validate_header(required_header, key, None), (True, None))
header = required_header.copy() header = required_header.copy()
@ -219,12 +164,18 @@ class WebSocketTest(unittest.TestCase):
header["sec-websocket-protocol"] = "sUb1" header["sec-websocket-protocol"] = "sUb1"
self.assertEqual(_validate_header(header, key, ["Sub1", "suB2"]), (True, "sub1")) self.assertEqual(_validate_header(header, key, ["Sub1", "suB2"]), (True, "sub1"))
header = required_header.copy()
self.assertEqual(_validate_header(header, key, ["Sub1", "suB2"]), (False, None))
def testReadHeader(self): def testReadHeader(self):
status, header, status_message = read_headers(HeaderSockMock("data/header01.txt")) status, header, status_message = read_headers(HeaderSockMock("data/header01.txt"))
self.assertEqual(status, 101) self.assertEqual(status, 101)
self.assertEqual(header["connection"], "Upgrade") self.assertEqual(header["connection"], "Upgrade")
status, header, status_message = read_headers(HeaderSockMock("data/header03.txt"))
self.assertEqual(status, 101)
self.assertEqual(header["connection"], "Upgrade, Keep-Alive")
HeaderSockMock("data/header02.txt") HeaderSockMock("data/header02.txt")
self.assertRaises(ws.WebSocketException, read_headers, HeaderSockMock("data/header02.txt")) self.assertRaises(ws.WebSocketException, read_headers, HeaderSockMock("data/header02.txt"))
@ -242,7 +193,10 @@ class WebSocketTest(unittest.TestCase):
sock.send(u"こんにちは") sock.send(u"こんにちは")
self.assertEqual(s.sent[1], six.b("\x81\x8fabcd\x82\xe3\xf0\x87\xe3\xf1\x80\xe5\xca\x81\xe2\xc5\x82\xe3\xcc")) self.assertEqual(s.sent[1], six.b("\x81\x8fabcd\x82\xe3\xf0\x87\xe3\xf1\x80\xe5\xca\x81\xe2\xc5\x82\xe3\xcc"))
sock.send("x" * 127) # sock.send("x" * 5000)
# self.assertEqual(s.sent[1], six.b("\x81\x8fabcd\x82\xe3\xf0\x87\xe3\xf1\x80\xe5\xca\x81\xe2\xc5\x82\xe3\xcc"))
self.assertEqual(sock.send_binary(b'1111111111101'), 19)
def testRecv(self): def testRecv(self):
# TODO: add longer frame data # TODO: add longer frame data
@ -260,14 +214,14 @@ class WebSocketTest(unittest.TestCase):
@unittest.skipUnless(TEST_WITH_INTERNET, "Internet-requiring tests are disabled") @unittest.skipUnless(TEST_WITH_INTERNET, "Internet-requiring tests are disabled")
def testIter(self): def testIter(self):
count = 2 count = 2
for _ in ws.create_connection('ws://stream.meetup.com/2/rsvps'): for _ in ws.create_connection('wss://stream.meetup.com/2/rsvps'):
count -= 1 count -= 1
if count == 0: if count == 0:
break break
@unittest.skipUnless(TEST_WITH_INTERNET, "Internet-requiring tests are disabled") @unittest.skipUnless(TEST_WITH_INTERNET, "Internet-requiring tests are disabled")
def testNext(self): def testNext(self):
sock = ws.create_connection('ws://stream.meetup.com/2/rsvps') sock = ws.create_connection('wss://stream.meetup.com/2/rsvps')
self.assertEqual(str, type(next(sock))) self.assertEqual(str, type(next(sock)))
def testInternalRecvStrict(self): def testInternalRecvStrict(self):
@ -415,6 +369,7 @@ class WebSocketTest(unittest.TestCase):
s.send(u"こにゃにゃちは、世界") s.send(u"こにゃにゃちは、世界")
result = s.recv() result = s.recv()
self.assertEqual(result, "こにゃにゃちは、世界") self.assertEqual(result, "こにゃにゃちは、世界")
self.assertRaises(ValueError, s.send_close, -1, "")
s.close() s.close()
@unittest.skipUnless(TEST_WITH_INTERNET, "Internet-requiring tests are disabled") @unittest.skipUnless(TEST_WITH_INTERNET, "Internet-requiring tests are disabled")
@ -426,31 +381,24 @@ class WebSocketTest(unittest.TestCase):
s.close() s.close()
@unittest.skipUnless(TEST_WITH_INTERNET, "Internet-requiring tests are disabled") @unittest.skipUnless(TEST_WITH_INTERNET, "Internet-requiring tests are disabled")
@unittest.skipUnless(TEST_SECURE_WS, "wss://echo.websocket.org doesn't work well.")
def testSecureWebSocket(self): def testSecureWebSocket(self):
if 1: import ssl
import ssl s = ws.create_connection("wss://api.bitfinex.com/ws/2")
s = ws.create_connection("wss://echo.websocket.org/") self.assertNotEqual(s, None)
self.assertNotEqual(s, None) self.assertTrue(isinstance(s.sock, ssl.SSLSocket))
self.assertTrue(isinstance(s.sock, ssl.SSLSocket)) self.assertEqual(s.getstatus(), 101)
s.send("Hello, World") self.assertNotEqual(s.getheaders(), None)
result = s.recv() s.close()
self.assertEqual(result, "Hello, World")
s.send(u"こにゃにゃちは、世界")
result = s.recv()
self.assertEqual(result, "こにゃにゃちは、世界")
s.close()
#except:
# pass
@unittest.skipUnless(TEST_WITH_INTERNET, "Internet-requiring tests are disabled") @unittest.skipUnless(TEST_WITH_INTERNET, "Internet-requiring tests are disabled")
def testWebSocketWihtCustomHeader(self): def testWebSocketWithCustomHeader(self):
s = ws.create_connection("ws://echo.websocket.org/", s = ws.create_connection("ws://echo.websocket.org/",
headers={"User-Agent": "PythonWebsocketClient"}) headers={"User-Agent": "PythonWebsocketClient"})
self.assertNotEqual(s, None) self.assertNotEqual(s, None)
s.send("Hello, World") s.send("Hello, World")
result = s.recv() result = s.recv()
self.assertEqual(result, "Hello, World") self.assertEqual(result, "Hello, World")
self.assertRaises(ValueError, s.close, -1, "")
s.close() s.close()
@unittest.skipUnless(TEST_WITH_INTERNET, "Internet-requiring tests are disabled") @unittest.skipUnless(TEST_WITH_INTERNET, "Internet-requiring tests are disabled")
@ -461,87 +409,6 @@ class WebSocketTest(unittest.TestCase):
self.assertRaises(ws.WebSocketConnectionClosedException, s.send, "Hello") self.assertRaises(ws.WebSocketConnectionClosedException, s.send, "Hello")
self.assertRaises(ws.WebSocketConnectionClosedException, s.recv) self.assertRaises(ws.WebSocketConnectionClosedException, s.recv)
def testNonce(self):
""" WebSocket key should be a random 16-byte nonce.
"""
key = _create_sec_websocket_key()
nonce = base64decode(key.encode("utf-8"))
self.assertEqual(16, len(nonce))
class WebSocketAppTest(unittest.TestCase):
class NotSetYet(object):
""" A marker class for signalling that a value hasn't been set yet.
"""
def setUp(self):
ws.enableTrace(TRACEABLE)
WebSocketAppTest.keep_running_open = WebSocketAppTest.NotSetYet()
WebSocketAppTest.keep_running_close = WebSocketAppTest.NotSetYet()
WebSocketAppTest.get_mask_key_id = WebSocketAppTest.NotSetYet()
def tearDown(self):
WebSocketAppTest.keep_running_open = WebSocketAppTest.NotSetYet()
WebSocketAppTest.keep_running_close = WebSocketAppTest.NotSetYet()
WebSocketAppTest.get_mask_key_id = WebSocketAppTest.NotSetYet()
@unittest.skipUnless(TEST_WITH_INTERNET, "Internet-requiring tests are disabled")
def testKeepRunning(self):
""" A WebSocketApp should keep running as long as its self.keep_running
is not False (in the boolean context).
"""
def on_open(self, *args, **kwargs):
""" Set the keep_running flag for later inspection and immediately
close the connection.
"""
WebSocketAppTest.keep_running_open = self.keep_running
self.close()
def on_close(self, *args, **kwargs):
""" Set the keep_running flag for the test to use.
"""
WebSocketAppTest.keep_running_close = self.keep_running
app = ws.WebSocketApp('ws://echo.websocket.org/', on_open=on_open, on_close=on_close)
app.run_forever()
# if numpy is installed, this assertion fail
# self.assertFalse(isinstance(WebSocketAppTest.keep_running_open,
# WebSocketAppTest.NotSetYet))
# self.assertFalse(isinstance(WebSocketAppTest.keep_running_close,
# WebSocketAppTest.NotSetYet))
# self.assertEqual(True, WebSocketAppTest.keep_running_open)
# self.assertEqual(False, WebSocketAppTest.keep_running_close)
@unittest.skipUnless(TEST_WITH_INTERNET, "Internet-requiring tests are disabled")
def testSockMaskKey(self):
""" A WebSocketApp should forward the received mask_key function down
to the actual socket.
"""
def my_mask_key_func():
pass
def on_open(self, *args, **kwargs):
""" Set the value so the test can use it later on and immediately
close the connection.
"""
WebSocketAppTest.get_mask_key_id = id(self.get_mask_key)
self.close()
app = ws.WebSocketApp('ws://echo.websocket.org/', on_open=on_open, get_mask_key=my_mask_key_func)
app.run_forever()
# if numpu is installed, this assertion fail
# Note: We can't use 'is' for comparing the functions directly, need to use 'id'.
# self.assertEqual(WebSocketAppTest.get_mask_key_id, id(my_mask_key_func))
class SockOptTest(unittest.TestCase): class SockOptTest(unittest.TestCase):
@unittest.skipUnless(TEST_WITH_INTERNET, "Internet-requiring tests are disabled") @unittest.skipUnless(TEST_WITH_INTERNET, "Internet-requiring tests are disabled")
@ -562,101 +429,5 @@ class UtilsTest(unittest.TestCase):
self.assertEqual(state, True) self.assertEqual(state, True)
class ProxyInfoTest(unittest.TestCase):
def setUp(self):
self.http_proxy = os.environ.get("http_proxy", None)
self.https_proxy = os.environ.get("https_proxy", None)
if "http_proxy" in os.environ:
del os.environ["http_proxy"]
if "https_proxy" in os.environ:
del os.environ["https_proxy"]
def tearDown(self):
if self.http_proxy:
os.environ["http_proxy"] = self.http_proxy
elif "http_proxy" in os.environ:
del os.environ["http_proxy"]
if self.https_proxy:
os.environ["https_proxy"] = self.https_proxy
elif "https_proxy" in os.environ:
del os.environ["https_proxy"]
def testProxyFromArgs(self):
self.assertEqual(get_proxy_info("echo.websocket.org", False, proxy_host="localhost"), ("localhost", 0, None))
self.assertEqual(get_proxy_info("echo.websocket.org", False, proxy_host="localhost", proxy_port=3128), ("localhost", 3128, None))
self.assertEqual(get_proxy_info("echo.websocket.org", True, proxy_host="localhost"), ("localhost", 0, None))
self.assertEqual(get_proxy_info("echo.websocket.org", True, proxy_host="localhost", proxy_port=3128), ("localhost", 3128, None))
self.assertEqual(get_proxy_info("echo.websocket.org", False, proxy_host="localhost", proxy_auth=("a", "b")),
("localhost", 0, ("a", "b")))
self.assertEqual(get_proxy_info("echo.websocket.org", False, proxy_host="localhost", proxy_port=3128, proxy_auth=("a", "b")),
("localhost", 3128, ("a", "b")))
self.assertEqual(get_proxy_info("echo.websocket.org", True, proxy_host="localhost", proxy_auth=("a", "b")),
("localhost", 0, ("a", "b")))
self.assertEqual(get_proxy_info("echo.websocket.org", True, proxy_host="localhost", proxy_port=3128, proxy_auth=("a", "b")),
("localhost", 3128, ("a", "b")))
self.assertEqual(get_proxy_info("echo.websocket.org", True, proxy_host="localhost", proxy_port=3128, no_proxy=["example.com"], proxy_auth=("a", "b")),
("localhost", 3128, ("a", "b")))
self.assertEqual(get_proxy_info("echo.websocket.org", True, proxy_host="localhost", proxy_port=3128, no_proxy=["echo.websocket.org"], proxy_auth=("a", "b")),
(None, 0, None))
def testProxyFromEnv(self):
os.environ["http_proxy"] = "http://localhost/"
self.assertEqual(get_proxy_info("echo.websocket.org", False), ("localhost", None, None))
os.environ["http_proxy"] = "http://localhost:3128/"
self.assertEqual(get_proxy_info("echo.websocket.org", False), ("localhost", 3128, None))
os.environ["http_proxy"] = "http://localhost/"
os.environ["https_proxy"] = "http://localhost2/"
self.assertEqual(get_proxy_info("echo.websocket.org", False), ("localhost", None, None))
os.environ["http_proxy"] = "http://localhost:3128/"
os.environ["https_proxy"] = "http://localhost2:3128/"
self.assertEqual(get_proxy_info("echo.websocket.org", False), ("localhost", 3128, None))
os.environ["http_proxy"] = "http://localhost/"
os.environ["https_proxy"] = "http://localhost2/"
self.assertEqual(get_proxy_info("echo.websocket.org", True), ("localhost2", None, None))
os.environ["http_proxy"] = "http://localhost:3128/"
os.environ["https_proxy"] = "http://localhost2:3128/"
self.assertEqual(get_proxy_info("echo.websocket.org", True), ("localhost2", 3128, None))
os.environ["http_proxy"] = "http://a:b@localhost/"
self.assertEqual(get_proxy_info("echo.websocket.org", False), ("localhost", None, ("a", "b")))
os.environ["http_proxy"] = "http://a:b@localhost:3128/"
self.assertEqual(get_proxy_info("echo.websocket.org", False), ("localhost", 3128, ("a", "b")))
os.environ["http_proxy"] = "http://a:b@localhost/"
os.environ["https_proxy"] = "http://a:b@localhost2/"
self.assertEqual(get_proxy_info("echo.websocket.org", False), ("localhost", None, ("a", "b")))
os.environ["http_proxy"] = "http://a:b@localhost:3128/"
os.environ["https_proxy"] = "http://a:b@localhost2:3128/"
self.assertEqual(get_proxy_info("echo.websocket.org", False), ("localhost", 3128, ("a", "b")))
os.environ["http_proxy"] = "http://a:b@localhost/"
os.environ["https_proxy"] = "http://a:b@localhost2/"
self.assertEqual(get_proxy_info("echo.websocket.org", True), ("localhost2", None, ("a", "b")))
os.environ["http_proxy"] = "http://a:b@localhost:3128/"
os.environ["https_proxy"] = "http://a:b@localhost2:3128/"
self.assertEqual(get_proxy_info("echo.websocket.org", True), ("localhost2", 3128, ("a", "b")))
os.environ["http_proxy"] = "http://a:b@localhost/"
os.environ["https_proxy"] = "http://a:b@localhost2/"
os.environ["no_proxy"] = "example1.com,example2.com"
self.assertEqual(get_proxy_info("example.1.com", True), ("localhost2", None, ("a", "b")))
os.environ["http_proxy"] = "http://a:b@localhost:3128/"
os.environ["https_proxy"] = "http://a:b@localhost2:3128/"
os.environ["no_proxy"] = "example1.com,example2.com, echo.websocket.org"
self.assertEqual(get_proxy_info("echo.websocket.org", True), (None, 0, None))
os.environ["http_proxy"] = "http://a:b@localhost:3128/"
os.environ["https_proxy"] = "http://a:b@localhost2:3128/"
os.environ["no_proxy"] = "127.0.0.0/8, 192.168.0.0/16"
self.assertEqual(get_proxy_info("127.0.0.1", False), (None, 0, None))
self.assertEqual(get_proxy_info("192.168.1.1", False), (None, 0, None))
if __name__ == "__main__": if __name__ == "__main__":
unittest.main() unittest.main()

Loading…
Cancel
Save