Update websocket-client-1.2.1

This commit is contained in:
JonnyWong16 2021-10-14 22:36:24 -07:00
parent c67f18d65c
commit aa127ecbda
No known key found for this signature in database
GPG key ID: B1F1F9807184697A
21 changed files with 1756 additions and 1045 deletions

View file

@ -1,23 +1,20 @@
""" """
__init__.py
websocket - WebSocket client library for Python websocket - WebSocket client library for Python
Copyright (C) 2010 Hiroki Ohtani(liris) Copyright 2021 engn33r
This library is free software; you can redistribute it and/or Licensed under the Apache License, Version 2.0 (the "License");
modify it under the terms of the GNU Lesser General Public you may not use this file except in compliance with the License.
License as published by the Free Software Foundation; either You may obtain a copy of the License at
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, http://www.apache.org/licenses/LICENSE-2.0
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-1335 USA
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
""" """
from ._abnf import * from ._abnf import *
from ._app import WebSocketApp from ._app import WebSocketApp
@ -26,4 +23,4 @@ from ._exceptions import *
from ._logging import * from ._logging import *
from ._socket import * from ._socket import *
__version__ = "0.57.0" __version__ = "1.2.1"

View file

@ -1,59 +1,53 @@
""" """
"""
"""
_abnf.py
websocket - WebSocket client library for Python websocket - WebSocket client library for Python
Copyright (C) 2010 Hiroki Ohtani(liris) Copyright 2021 engn33r
This library is free software; you can redistribute it and/or Licensed under the Apache License, Version 2.0 (the "License");
modify it under the terms of the GNU Lesser General Public you may not use this file except in compliance with the License.
License as published by the Free Software Foundation; either You may obtain a copy of the License at
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, http://www.apache.org/licenses/LICENSE-2.0
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-1335 USA
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
""" """
import array import array
import os import os
import struct import struct
import sys
import six
from ._exceptions import * from ._exceptions import *
from ._utils import validate_utf8 from ._utils import validate_utf8
from threading import Lock from threading import Lock
try: try:
if six.PY3: # If wsaccel is available, use compiled routines to mask data.
import numpy # wsaccel only provides around a 10% speed boost compared
else: # to the websocket-client _mask() implementation.
numpy = None # Note that wsaccel is unmaintained.
except ImportError:
numpy = None
try:
# If wsaccel is available we use compiled routines to mask data.
if not numpy:
from wsaccel.xormask import XorMaskerSimple from wsaccel.xormask import XorMaskerSimple
def _mask(_m, _d): def _mask(_m, _d):
return XorMaskerSimple(_m).process(_d) return XorMaskerSimple(_m).process(_d)
except ImportError:
# wsaccel is not available, we rely on python implementations.
def _mask(_m, _d):
for i in range(len(_d)):
_d[i] ^= _m[i % 4]
if six.PY3: except ImportError:
return _d.tobytes() # wsaccel is not available, use websocket-client _mask()
else: native_byteorder = sys.byteorder
return _d.tostring()
def _mask(mask_value, data_value):
datalen = len(data_value)
data_value = int.from_bytes(data_value, native_byteorder)
mask_value = int.from_bytes(mask_value * (datalen // 4) + mask_value[: datalen % 4], native_byteorder)
return (data_value ^ mask_value).to_bytes(datalen, native_byteorder)
__all__ = [ __all__ = [
@ -105,7 +99,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 +133,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 +148,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:
@ -176,8 +172,7 @@ class ABNF(object):
if l > 2 and not skip_utf8_validation and not validate_utf8(self.data[2:]): if l > 2 and not skip_utf8_validation and not validate_utf8(self.data[2:]):
raise WebSocketProtocolException("Invalid close frame.") raise WebSocketProtocolException("Invalid close frame.")
code = 256 * \ code = 256 * self.data[0] + self.data[1]
six.byte2int(self.data[0:1]) + six.byte2int(self.data[1:2])
if not self._is_valid_close_status(code): if not self._is_valid_close_status(code):
raise WebSocketProtocolException("Invalid close opcode.") raise WebSocketProtocolException("Invalid close opcode.")
@ -193,24 +188,27 @@ 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, str):
data = data.encode("utf-8") data = data.encode("utf-8")
# mask must be set if send data from client # mask must be set if send data from client
return ABNF(fin, 0, 0, 0, opcode, 1, data) return ABNF(fin, 0, 0, 0, opcode, 1, data)
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,19 +218,16 @@ 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).encode('latin-1')
if length < ABNF.LENGTH_7: if length < ABNF.LENGTH_7:
frame_header += chr(self.mask << 7 | length) frame_header += chr(self.mask << 7 | length).encode('latin-1')
frame_header = six.b(frame_header)
elif length < ABNF.LENGTH_16: elif length < ABNF.LENGTH_16:
frame_header += chr(self.mask << 7 | 0x7e) frame_header += chr(self.mask << 7 | 0x7e).encode('latin-1')
frame_header = six.b(frame_header)
frame_header += struct.pack("!H", length) frame_header += struct.pack("!H", length)
else: else:
frame_header += chr(self.mask << 7 | 0x7f) frame_header += chr(self.mask << 7 | 0x7f).encode('latin-1')
frame_header = six.b(frame_header)
frame_header += struct.pack("!Q", length) frame_header += struct.pack("!Q", length)
if not self.mask: if not self.mask:
@ -244,7 +239,7 @@ class ABNF(object):
def _get_masked(self, mask_key): def _get_masked(self, mask_key):
s = ABNF.mask(mask_key, self.data) s = ABNF.mask(mask_key, self.data)
if isinstance(mask_key, six.text_type): if isinstance(mask_key, str):
mask_key = mask_key.encode('utf-8') mask_key = mask_key.encode('utf-8')
return mask_key + s return mask_key + s
@ -252,36 +247,25 @@ 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.
data: <type>
data to mask/unmask.
""" """
if data is None: if data is None:
data = "" data = ""
if isinstance(mask_key, six.text_type): if isinstance(mask_key, str):
mask_key = six.b(mask_key) mask_key = mask_key.encode('latin-1')
if isinstance(data, six.text_type): if isinstance(data, str):
data = six.b(data) data = data.encode('latin-1')
if numpy: return _mask(array.array("B", mask_key), array.array("B", data))
origlen = len(data)
_mask_key = mask_key[3] << 24 | mask_key[2] << 16 | mask_key[1] << 8 | mask_key[0]
# We need data to be a multiple of four...
data += bytes(" " * (4 - (len(data) % 4)), "us-ascii")
a = numpy.frombuffer(data, dtype="uint32")
masked = numpy.bitwise_xor(a, [_mask_key]).astype("uint32")
if len(data) > origlen:
return masked.tobytes()[:origlen]
return masked.tobytes()
else:
_m = array.array("B", mask_key)
_d = array.array("B", data)
return _mask(_m, _d)
class frame_buffer(object): class frame_buffer(object):
@ -308,20 +292,12 @@ class frame_buffer(object):
def recv_header(self): def recv_header(self):
header = self.recv_strict(2) header = self.recv_strict(2)
b1 = header[0] b1 = header[0]
if six.PY2:
b1 = ord(b1)
fin = b1 >> 7 & 1 fin = b1 >> 7 & 1
rsv1 = b1 >> 6 & 1 rsv1 = b1 >> 6 & 1
rsv2 = b1 >> 5 & 1 rsv2 = b1 >> 5 & 1
rsv3 = b1 >> 4 & 1 rsv3 = b1 >> 4 & 1
opcode = b1 & 0xf opcode = b1 & 0xf
b2 = header[1] b2 = header[1]
if six.PY2:
b2 = ord(b2)
has_mask = b2 >> 7 & 1 has_mask = b2 >> 7 & 1
length_bits = b2 & 0x7f length_bits = b2 & 0x7f
@ -385,7 +361,7 @@ class frame_buffer(object):
return frame return frame
def recv_strict(self, bufsize): def recv_strict(self, bufsize):
shortage = bufsize - sum(len(x) for x in self.recv_buffer) shortage = bufsize - sum(map(len, self.recv_buffer))
while shortage > 0: while shortage > 0:
# Limit buffer size that we pass to socket.recv() to avoid # Limit buffer size that we pass to socket.recv() to avoid
# fragmenting the heap -- the number of bytes recv() actually # fragmenting the heap -- the number of bytes recv() actually
@ -397,7 +373,7 @@ class frame_buffer(object):
self.recv_buffer.append(bytes_) self.recv_buffer.append(bytes_)
shortage -= len(bytes_) shortage -= len(bytes_)
unified = six.b("").join(self.recv_buffer) unified = bytes("", 'utf-8').join(self.recv_buffer)
if shortage == 0: if shortage == 0:
self.recv_buffer = [] self.recv_buffer = []

View file

@ -1,37 +1,30 @@
""" """
"""
"""
_app.py
websocket - WebSocket client library for Python websocket - WebSocket client library for Python
Copyright (C) 2010 Hiroki Ohtani(liris) Copyright 2021 engn33r
This library is free software; you can redistribute it and/or Licensed under the Apache License, Version 2.0 (the "License");
modify it under the terms of the GNU Lesser General Public you may not use this file except in compliance with the License.
License as published by the Free Software Foundation; either You may obtain a copy of the License at
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, http://www.apache.org/licenses/LICENSE-2.0
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-1335 USA
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
""" """
import selectors
"""
WebSocketApp provides higher level APIs.
"""
import inspect
import select
import sys import sys
import threading import threading
import time import time
import traceback import traceback
import six
from ._abnf import ABNF from ._abnf import ABNF
from ._core import WebSocket, getdefaulttimeout from ._core import WebSocket, getdefaulttimeout
from ._exceptions import * from ._exceptions import *
@ -40,21 +33,32 @@ 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.keep_running: while self.app.keep_running:
r, w, e = select.select( sel = selectors.DefaultSelector()
(self.app.sock.sock, ), (), (), self.ping_timeout) sel.register(self.app.sock.sock, selectors.EVENT_READ)
r = sel.select(self.ping_timeout)
if r: if r:
if not read_callback(): if not read_callback():
break break
check_callback() check_callback()
sel.close()
class SSLDispatcher: 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
@ -72,14 +76,19 @@ class SSLDispatcher:
if sock.pending(): if sock.pending():
return [sock,] return [sock,]
r, w, e = select.select((sock, ), (), (), self.ping_timeout) sel = selectors.DefaultSelector()
return r sel.register(sock, selectors.EVENT_READ)
r = sel.select(self.ping_timeout)
sel.close()
if len(r) > 0:
return r[0][0]
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,
@ -90,39 +99,60 @@ 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: str
Websocket url.
header: list or dict
Custom header for websocket handshake.
on_open: function
Callback object which is called at opening websocket.
on_open has one argument.
The 1st argument is this class object.
on_message: function
Callback object which is called when received data.
on_message has 2 arguments. on_message has 2 arguments.
The 1st argument is this class object. The 1st argument is this class object.
The 2nd argument is utf-8 string which we get from the server. The 2nd argument is utf-8 data received from the server.
on_error: callable object which is called when we get error. on_error: function
Callback object which is called when we get error.
on_error has 2 arguments. on_error has 2 arguments.
The 1st argument is this class object. The 1st argument is this class object.
The 2nd argument is exception object. The 2nd argument is exception object.
on_close: callable object which is called when closed the connection. on_close: function
this function has one argument. The argument is this class object. Callback object which is called when connection is closed.
on_cont_message: callback object which is called when receive continued on_close has 3 arguments.
frame data. The 1st argument is this class object.
The 2nd argument is close_status_code.
The 3rd argument is close_msg.
on_cont_message: function
Callback object which is called when a continuation
frame is received.
on_cont_message has 3 arguments. on_cont_message has 3 arguments.
The 1st argument is this class object. The 1st argument is this class object.
The 2nd argument is utf-8 string which we get from the server. The 2nd argument is utf-8 string which we get from the server.
The 3rd argument is continue flag. if 0, the data continue The 3rd argument is continue flag. if 0, the data continue
to next frame data to next frame data
on_data: callback object which is called when a message received. on_data: function
Callback object which is called when a message received.
This is called before on_message or on_cont_message, This is called before on_message or on_cont_message,
and then on_message or on_cont_message is called. and then on_message or on_cont_message is called.
on_data has 4 argument. on_data has 4 argument.
The 1st argument is this class object. The 1st argument is this class object.
The 2nd argument is utf-8 string which we get from the server. 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 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 The 4th argument is continue flag. If 0, the data continue
keep_running: this parameter is obsolete and ignored. keep_running: bool
get_mask_key: a callable to produce new mask keys, This parameter is obsolete and ignored.
see the WebSocket.set_mask_key's docstring for more information get_mask_key: function
subprotocols: array of available sub protocols. default is None. A callable function to get new mask keys, see the
WebSocket.set_mask_key's docstring for more information.
cookie: str
Cookie value.
subprotocols: list
List 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 []
@ -145,10 +175,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,
Parameters
----------
data: str
Message to send. If you set opcode to OPCODE_TEXT,
data must be utf-8 string or unicode. data must be utf-8 string or unicode.
opcode: operation code of data. default is OPCODE_TEXT. opcode: int
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:
@ -157,58 +192,79 @@ 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
if set to 0, not send automatically. Automatically send "ping" command
ping_timeout: timeout(second) if the pong message is not received. every specified period (in seconds).
http_proxy_host: http proxy host name. If set to 0, no ping is sent periodically.
http_proxy_port: http proxy port. If not set, set to 80. ping_timeout: int or float
http_no_proxy: host names, which doesn't use proxy. Timeout (in seconds) if the pong message is not received.
skip_utf8_validation: skip utf8 validation. ping_payload: str
host: update host header. Payload message to send with each ping.
origin: update origin header. http_proxy_host: str
dispatcher: customize reading data from socket. HTTP proxy host name.
suppress_origin: suppress outputting origin header. http_proxy_port: int or str
HTTP proxy port. If not set, set to 80.
http_no_proxy: list
Whitelisted host names that don't use the proxy.
skip_utf8_validation: bool
skip utf8 validation.
host: str
update host header.
origin: str
update origin header.
dispatcher: Dispatcher object
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:
ping_timeout = None raise WebSocketException("Ensure ping_timeout > 0")
if ping_interval is not None and ping_interval < 0:
raise WebSocketException("Ensure ping_interval >= 0")
if ping_timeout and ping_interval and ping_interval <= ping_timeout: if ping_timeout and ping_interval and ping_interval <= ping_timeout:
raise WebSocketException("Ensure ping_interval > ping_timeout") raise WebSocketException("Ensure ping_interval > ping_timeout")
if not sockopt: if not sockopt:
@ -225,26 +281,33 @@ 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
statusCode and reason from there. Parameters
----------
close_frame: ABNF frame
If close_frame is set, the on_close handler is invoked
with the statusCode and reason from the provided frame.
""" """
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
if self.sock: if self.sock:
self.sock.close() self.sock.close()
close_args = self._get_close_args( close_status_code, close_reason = self._get_close_args(
close_frame.data if close_frame else None) close_frame if close_frame else None)
self._callback(self.on_close, *close_args)
self.sock = None self.sock = None
# Finally call the callback AFTER all teardown is complete
self._callback(self.on_close, close_status_code, close_reason)
try: try:
self.sock = WebSocket( self.sock = WebSocket(
self.get_mask_key, sockopt=sockopt, sslopt=sslopt, self.get_mask_key, sockopt=sockopt, sslopt=sslopt,
fire_cont_frame=self.on_cont_message is not None, fire_cont_frame=self.on_cont_message is not None,
skip_utf8_validation=skip_utf8_validation, skip_utf8_validation=skip_utf8_validation,
enable_multithread=True if ping_interval else False) enable_multithread=True)
self.sock.settimeout(getdefaulttimeout()) self.sock.settimeout(getdefaulttimeout())
self.sock.connect( self.sock.connect(
self.url, header=self.header, cookie=self.cookie, self.url, header=self.header, cookie=self.cookie,
@ -261,8 +324,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():
@ -284,7 +347,7 @@ class WebSocketApp(object):
frame.data, frame.fin) frame.data, frame.fin)
else: else:
data = frame.data data = frame.data
if six.PY3 and op_code == ABNF.OPCODE_TEXT: if op_code == ABNF.OPCODE_TEXT:
data = data.decode("utf-8") data = data.decode("utf-8")
self._callback(self.on_data, data, frame.opcode, True) self._callback(self.on_data, data, frame.opcode, True)
self._callback(self.on_message, data) self._callback(self.on_message, data)
@ -297,9 +360,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
@ -319,34 +382,31 @@ class WebSocketApp(object):
return Dispatcher(self, timeout) return Dispatcher(self, timeout)
def _get_close_args(self, data): def _get_close_args(self, close_frame):
""" 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 close code and reason from the close body
# if the on_close callback is "old", just return empty list if it exists (RFC6455 says WebSocket Connection Close Code is optional)
if sys.version_info < (3, 0): """
if not self.on_close or len(inspect.getargspec(self.on_close).args) != 3: # Need to catch the case where close_frame is None
return [] # Otherwise the following if statement causes an error
if not self.on_close or not close_frame:
return [None, None]
# Extract close frame status code
if close_frame.data and len(close_frame.data) >= 2:
close_status_code = 256 * close_frame.data[0] + close_frame.data[1]
reason = close_frame.data[2:].decode('utf-8')
return [close_status_code, reason]
else: else:
if not self.on_close or len(inspect.getfullargspec(self.on_close).args) != 3: # Most likely reached this because len(close_frame_data.data) < 2
return []
if data and len(data) >= 2:
code = 256 * six.byte2int(data[0:1]) + six.byte2int(data[1:2])
reason = data[2:].decode('utf-8')
return [code, reason]
return [None, None] return [None, None]
def _callback(self, callback, *args): def _callback(self, callback, *args):
if callback: if callback:
try: try:
if inspect.ismethod(callback):
callback(*args)
else:
callback(self, *args) callback(self, *args)
except Exception as e: except Exception as e:
_logging.error("error from callback {}: {}".format(callback, e)) _logging.error("error from callback {}: {}".format(callback, e))
if _logging.isEnabledForDebug(): if self.on_error:
_, _, tb = sys.exc_info() self.on_error(self, e)
traceback.print_tb(tb)

View file

@ -1,7 +1,26 @@
try: """
import Cookie
except: """
import http.cookies as Cookie
"""
_cookiejar.py
websocket - WebSocket client library for Python
Copyright 2021 engn33r
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""
import http.cookies
class SimpleCookieJar(object): class SimpleCookieJar(object):
@ -10,26 +29,20 @@ class SimpleCookieJar(object):
def add(self, set_cookie): def add(self, set_cookie):
if set_cookie: if set_cookie:
try: simpleCookie = http.cookies.SimpleCookie(set_cookie)
simpleCookie = Cookie.SimpleCookie(set_cookie)
except:
simpleCookie = Cookie.SimpleCookie(set_cookie.encode('ascii', 'ignore'))
for k, v in simpleCookie.items(): for k, v in simpleCookie.items():
domain = v.get("domain") domain = v.get("domain")
if domain: if domain:
if not domain.startswith("."): if not domain.startswith("."):
domain = "." + domain domain = "." + domain
cookie = self.jar.get(domain) if self.jar.get(domain) else Cookie.SimpleCookie() cookie = self.jar.get(domain) if self.jar.get(domain) else http.cookies.SimpleCookie()
cookie.update(simpleCookie) cookie.update(simpleCookie)
self.jar[domain.lower()] = cookie self.jar[domain.lower()] = cookie
def set(self, set_cookie): def set(self, set_cookie):
if set_cookie: if set_cookie:
try: simpleCookie = http.cookies.SimpleCookie(set_cookie)
simpleCookie = Cookie.SimpleCookie(set_cookie)
except:
simpleCookie = Cookie.SimpleCookie(set_cookie.encode('ascii', 'ignore'))
for k, v in simpleCookie.items(): for k, v in simpleCookie.items():
domain = v.get("domain") domain = v.get("domain")
@ -48,5 +61,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()]
)))

View file

@ -1,33 +1,32 @@
""" """
_core.py
====================================
WebSocket Python client
"""
"""
_core.py
websocket - WebSocket client library for Python websocket - WebSocket client library for Python
Copyright (C) 2010 Hiroki Ohtani(liris) Copyright 2021 engn33r
This library is free software; you can redistribute it and/or Licensed under the Apache License, Version 2.0 (the "License");
modify it under the terms of the GNU Lesser General Public you may not use this file except in compliance with the License.
License as published by the Free Software Foundation; either You may obtain a copy of the License at
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, http://www.apache.org/licenses/LICENSE-2.0
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-1335 USA
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
""" """
from __future__ import print_function
import socket import socket
import struct import struct
import threading import threading
import time import time
import six
# websocket modules # websocket modules
from ._abnf import * from ._abnf import *
from ._exceptions import * from ._exceptions import *
@ -40,21 +39,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,21 +57,34 @@ 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 function to get new mask keys, see the
WebSocket.set_mask_key's docstring for more information.
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 options.
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,
fire_cont_frame=False, enable_multithread=False, fire_cont_frame=False, enable_multithread=True,
skip_utf8_validation=False, **_): skip_utf8_validation=False, **_):
""" """
Initialize WebSocket object. Initialize WebSocket object.
Parameters
----------
sslopt: dict
Optional dict object for ssl socket options.
""" """
self.sock_opt = sock_opt(sockopt, sslopt) self.sock_opt = sock_opt(sockopt, sslopt)
self.handshake_response = None self.handshake_response = None
@ -119,10 +122,13 @@ 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
----------
func: func
callable object. the func takes 1 argument as integer.
The argument means length of mask key. The argument means length of mask key.
This func must return string(byte array), This func must return string(byte array),
which length is argument specified. which length is argument specified.
@ -131,7 +137,12 @@ class WebSocket(object):
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 +150,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 +163,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 +174,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 +185,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
@ -179,7 +193,10 @@ class WebSocket(object):
return None return None
def is_ssl(self): def is_ssl(self):
try:
return isinstance(self.sock, ssl.SSLSocket) return isinstance(self.sock, ssl.SSLSocket)
except:
return False
headers = property(getheaders) headers = property(getheaders)
@ -195,29 +212,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. Parameters
if you set None for this value, ----------
it means "use default_timeout value" header: list or dict
Custom http header list or dict.
options: "header" -> custom http header list or dict. cookie: str
"cookie" -> cookie value. Cookie value.
"origin" -> custom origin url. origin: str
"suppress_origin" -> suppress outputting origin header. Custom origin url.
"host" -> custom host header string. connection: str
"http_proxy_host" - http proxy host name. Custom connection header value.
"http_proxy_port" - http proxy port. If not set, set to 80. Default value "Upgrade" set in _handshake.py
"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: str
default is None Custom host header string.
"redirect_limit" -> number of redirects to follow. timeout: int or float
"subprotocols" - array of available sub protocols. Socket timeout time. This value is an integer or float.
default is None. If you set None for this value, it means "use default_timeout value"
"socket" - pre-initialized stream socket. http_proxy_host: str
HTTP proxy host name.
http_proxy_port: str or int
HTTP proxy port. Default is 80.
http_no_proxy: list
Whitelisted host names that don't use the proxy.
http_proxy_auth: tuple
HTTP proxy auth information. Tuple of username and password. Default is None.
redirect_limit: int
Number of redirects to follow.
subprotocols: list
List of available subprotocols. Default is None.
socket: socket
Pre-initialized stream socket.
""" """
# FIXME: "subprotocols" are getting lost, not passed down
# FIXME: "header", "cookie", "origin" and "host" too
self.sock_opt.timeout = options.get('timeout', self.sock_opt.timeout) 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))
@ -242,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
if the opcode is OPCODE_TEXT. ----------
Otherwise, it must be string(byte array) payload: str
Payload must be utf-8 string or unicode,
opcode: operation code to send. Please see OPCODE_XXX. If the opcode is OPCODE_TEXT.
Otherwise, it must be string(byte array).
opcode: int
Operation code (opcode) to send.
""" """
frame = ABNF.create_frame(payload, opcode) frame = ABNF.create_frame(payload, opcode)
@ -256,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)
@ -266,14 +294,18 @@ 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: ABNF frame
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)
if (isEnabledForTrace()): if (isEnabledForTrace()):
trace("send: " + repr(data)) trace("++Sent raw: " + repr(data))
trace("++Sent decoded: " + frame.__str__())
with self.lock: with self.lock:
while data: while data:
l = self._send(data) l = self._send(data)
@ -286,21 +318,27 @@ 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: str
data payload to send server.
""" """
if isinstance(payload, six.text_type): if isinstance(payload, str):
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: str
data payload to send server.
""" """
if isinstance(payload, six.text_type): if isinstance(payload, str):
payload = payload.encode("utf-8") payload = payload.encode("utf-8")
self.send(payload, ABNF.OPCODE_PONG) self.send(payload, ABNF.OPCODE_PONG)
@ -308,11 +346,13 @@ 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()
if six.PY3 and opcode == ABNF.OPCODE_TEXT: if opcode == ABNF.OPCODE_TEXT:
return data.decode("utf-8") return data.decode("utf-8")
elif opcode == ABNF.OPCODE_TEXT or opcode == ABNF.OPCODE_BINARY: elif opcode == ABNF.OPCODE_TEXT or opcode == ABNF.OPCODE_BINARY:
return data return data
@ -323,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
----------
control_frame: bool
a boolean flag indicating whether to return control frame
data, defaults to False 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
@ -335,13 +381,22 @@ 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
----------
control_frame: bool
a boolean flag indicating whether to return control frame
data, defaults to False 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()
if (isEnabledForTrace()):
trace("++Rcv raw: " + repr(frame.format()))
trace("++Rcv decoded: " + frame.__str__())
if not frame: if not frame:
# handle error: # handle error:
# 'NoneType' object has no attribute 'opcode' # 'NoneType' object has no attribute 'opcode'
@ -371,34 +426,42 @@ 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=bytes('', encoding='utf-8')):
""" """
send close data to the server. Send close data to the server.
status: status code to send. see STATUS_XXX. Parameters
----------
reason: the reason to close. This must be string or bytes. status: int
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")
self.connected = False self.connected = False
self.send(struct.pack('!H', status) + reason, ABNF.OPCODE_CLOSE) self.send(struct.pack('!H', status) + reason, ABNF.OPCODE_CLOSE)
def close(self, status=STATUS_NORMAL, reason=six.b(""), timeout=3): def close(self, status=STATUS_NORMAL, reason=bytes('', encoding='utf-8'), timeout=3):
""" """
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: int
Status code to send. See STATUS_XXX.
timeout: timeout until receive a close frame. reason: bytes
The reason to close.
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:
@ -407,8 +470,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()
@ -419,7 +481,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:
@ -439,7 +503,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
@ -461,12 +527,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.
@ -474,38 +540,53 @@ 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. ----------
if you set None for this value, class_: class
it means "use default_timeout value" class to instantiate when creating the connection. It has to implement
class_: 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. header: list or dict
"cookie" -> cookie value. custom http header list or dict.
"origin" -> custom origin url. cookie: str
"suppress_origin" -> suppress outputting origin header. Cookie value.
"host" -> custom host header string. origin: str
"http_proxy_host" - http proxy host name. custom origin url.
"http_proxy_port" - http proxy port. If not set, set to 80. suppress_origin: bool
"http_no_proxy" - host names, which doesn't use proxy. suppress outputting origin header.
"http_proxy_auth" - http proxy auth information. host: str
tuple of username and password. custom host header string.
default is None timeout: int or float
"enable_multithread" -> enable lock for multithread. socket timeout time. This value could be either float/integer.
"redirect_limit" -> number of redirects to follow. If set to None, it uses the default_timeout value.
"sockopt" -> socket options http_proxy_host: str
"sslopt" -> ssl option HTTP proxy host name.
"subprotocols" - array of available sub protocols. http_proxy_port: str or int
default is None. HTTP proxy port. If not set, set to 80.
"skip_utf8_validation" - skip utf8 validation. http_no_proxy: list
"socket" - pre-initialized stream socket. Whitelisted host names that don't use the proxy.
http_proxy_auth: tuple
HTTP proxy auth information. tuple of username and password. Default is None.
enable_multithread: bool
Enable lock for multithread.
redirect_limit: int
Number of redirects to follow.
sockopt: tuple
Values for socket.setsockopt.
sockopt must be a tuple and each element is an argument of sock.setsockopt.
sslopt: dict
Optional dict object for ssl socket options.
subprotocols: list
List of available subprotocols. Default is None.
skip_utf8_validation: bool
Skip utf8 validation.
socket: socket
Pre-initialized stream socket.
""" """
sockopt = options.pop("sockopt", []) sockopt = options.pop("sockopt", [])
sslopt = options.pop("sslopt", {}) sslopt = options.pop("sslopt", {})
fire_cont_frame = options.pop("fire_cont_frame", False) fire_cont_frame = options.pop("fire_cont_frame", False)
enable_multithread = options.pop("enable_multithread", False) enable_multithread = options.pop("enable_multithread", True)
skip_utf8_validation = options.pop("skip_utf8_validation", False) skip_utf8_validation = options.pop("skip_utf8_validation", False)
websock = class_(sockopt=sockopt, sslopt=sslopt, websock = class_(sockopt=sockopt, sslopt=sslopt,
fire_cont_frame=fire_cont_frame, fire_cont_frame=fire_cont_frame,

View file

@ -1,48 +1,44 @@
""" """
Define WebSocket exceptions
"""
"""
_exceptions.py
websocket - WebSocket client library for Python websocket - WebSocket client library for Python
Copyright (C) 2010 Hiroki Ohtani(liris) Copyright 2021 engn33r
This library is free software; you can redistribute it and/or Licensed under the Apache License, Version 2.0 (the "License");
modify it under the terms of the GNU Lesser General Public you may not use this file except in compliance with the License.
License as published by the Free Software Foundation; either You may obtain a copy of the License at
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, http://www.apache.org/licenses/LICENSE-2.0
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 Unless required by applicable law or agreed to in writing, software
License along with this library; if not, write to the Free Software distributed under the License is distributed on an "AS IS" BASIS,
Foundation, Inc., 51 Franklin Street, Fifth Floor, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
Boston, MA 02110-1335 USA See the License for the specific language governing permissions and
limitations under the License.
"""
"""
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

View file

@ -1,57 +1,34 @@
""" """
_handshake.py
websocket - WebSocket client library for Python websocket - WebSocket client library for Python
Copyright (C) 2010 Hiroki Ohtani(liris) Copyright 2021 engn33r
This library is free software; you can redistribute it and/or Licensed under the Apache License, Version 2.0 (the "License");
modify it under the terms of the GNU Lesser General Public you may not use this file except in compliance with the License.
License as published by the Free Software Foundation; either You may obtain a copy of the License at
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, http://www.apache.org/licenses/LICENSE-2.0
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-1335 USA
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
""" """
import hashlib import hashlib
import hmac import hmac
import os import os
from base64 import encodebytes as base64encode
import six from http import client as HTTPStatus
from ._cookiejar import SimpleCookieJar from ._cookiejar import SimpleCookieJar
from ._exceptions import * from ._exceptions import *
from ._http import * from ._http import *
from ._logging import * from ._logging import *
from ._socket import * from ._socket import *
if hasattr(six, 'PY3') and six.PY3:
from base64 import encodebytes as base64encode
else:
from base64 import encodestring as base64encode
if hasattr(six, 'PY3') and six.PY3:
if hasattr(six, 'PY34') and six.PY34:
from http import client as HTTPStatus
else:
from http import HTTPStatus
else:
import httplib as HTTPStatus
__all__ = ["handshake_response", "handshake", "SUPPORTED_REDIRECT_STATUSES"] __all__ = ["handshake_response", "handshake", "SUPPORTED_REDIRECT_STATUSES"]
if hasattr(hmac, "compare_digest"):
compare_digest = hmac.compare_digest
else:
def compare_digest(s1, s2):
return s1 == s2
# websocket supported version. # websocket supported version.
VERSION = 13 VERSION = 13
@ -94,6 +71,7 @@ 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,
@ -117,17 +95,17 @@ def _get_handshake_headers(resource, host, port, options):
key = _create_sec_websocket_key() key = _create_sec_websocket_key()
# Append Sec-WebSocket-Key & Sec-WebSocket-Version if not manually specified # Append Sec-WebSocket-Key & Sec-WebSocket-Version if not manually specified
if not 'header' in options or 'Sec-WebSocket-Key' not in options['header']: if 'header' not in options or 'Sec-WebSocket-Key' not in options['header']:
key = _create_sec_websocket_key() key = _create_sec_websocket_key()
headers.append("Sec-WebSocket-Key: %s" % key) headers.append("Sec-WebSocket-Key: %s" % key)
else: else:
key = options['header']['Sec-WebSocket-Key'] key = options['header']['Sec-WebSocket-Key']
if not 'header' in options or 'Sec-WebSocket-Version' not in options['header']: if 'header' not in options or 'Sec-WebSocket-Version' not in options['header']:
headers.append("Sec-WebSocket-Version: %s" % VERSION) headers.append("Sec-WebSocket-Version: %s" % VERSION)
if not 'connection' in options or options['connection'] is None: if 'connection' not in options or options['connection'] is None:
headers.append('Connection: upgrade') headers.append('Connection: Upgrade')
else: else:
headers.append(options['connection']) headers.append(options['connection'])
@ -178,27 +156,28 @@ 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:
return False, None return False, None
result = result.lower() result = result.lower()
if isinstance(result, six.text_type): if isinstance(result, str):
result = result.encode('utf-8') result = result.encode('utf-8')
value = (key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11").encode('utf-8') value = (key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11").encode('utf-8')
hashed = base64encode(hashlib.sha1(value).digest()).strip().lower() hashed = base64encode(hashlib.sha1(value).digest()).strip().lower()
success = compare_digest(hashed, result) success = hmac.compare_digest(hashed, result)
if success: if success:
return True, subproto return True, subproto

View file

@ -1,109 +1,118 @@
""" """
_http.py
websocket - WebSocket client library for Python websocket - WebSocket client library for Python
Copyright (C) 2010 Hiroki Ohtani(liris) Copyright 2021 engn33r
This library is free software; you can redistribute it and/or Licensed under the Apache License, Version 2.0 (the "License");
modify it under the terms of the GNU Lesser General Public you may not use this file except in compliance with the License.
License as published by the Free Software Foundation; either You may obtain a copy of the License at
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, http://www.apache.org/licenses/LICENSE-2.0
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-1335 USA
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
""" """
import errno import errno
import os import os
import socket import socket
import sys import sys
import six
from ._exceptions import * from ._exceptions import *
from ._logging import * from ._logging import *
from ._socket import* from ._socket import*
from ._ssl_compat import * from ._ssl_compat import *
from ._url import * from ._url import *
if six.PY3:
from base64 import encodebytes as base64encode from base64 import encodebytes as base64encode
else:
from base64 import encodestring as base64encode
__all__ = ["proxy_info", "connect", "read_headers"] __all__ = ["proxy_info", "connect", "read_headers"]
try: try:
import socks from python_socks.sync import Proxy
ProxyConnectionError = socks.ProxyConnectionError from python_socks._errors import *
HAS_PYSOCKS = True from python_socks._types import ProxyType
HAVE_PYTHON_SOCKS = True
except: except:
class ProxyConnectionError(BaseException): HAVE_PYTHON_SOCKS = False
class ProxyError(Exception):
pass pass
HAS_PYSOCKS = False
class ProxyTimeoutError(Exception):
pass
class ProxyConnectionError(Exception):
pass
class proxy_info(object): class proxy_info(object):
def __init__(self, **options): def __init__(self, **options):
self.type = options.get("proxy_type") or "http" self.proxy_host = options.get("http_proxy_host", None)
if not(self.type in ['http', 'socks4', 'socks5', 'socks5h']): if self.proxy_host:
raise ValueError("proxy_type must be 'http', 'socks4', 'socks5' or 'socks5h'") self.proxy_port = options.get("http_proxy_port", 0)
self.host = options.get("http_proxy_host", None)
if self.host:
self.port = options.get("http_proxy_port", 0)
self.auth = options.get("http_proxy_auth", None) self.auth = options.get("http_proxy_auth", None)
self.no_proxy = options.get("http_no_proxy", None) self.no_proxy = options.get("http_no_proxy", None)
self.proxy_protocol = options.get("proxy_type", "http")
# Note: If timeout not specified, default python-socks timeout is 60 seconds
self.proxy_timeout = options.get("timeout", None)
if self.proxy_protocol not in ['http', 'socks4', 'socks4a', 'socks5', 'socks5h']:
raise ProxyError("Only http, socks4, socks5 proxy protocols are supported")
else: else:
self.port = 0 self.proxy_port = 0
self.auth = None self.auth = None
self.no_proxy = None self.no_proxy = None
self.proxy_protocol = "http"
def _open_proxied_socket(url, options, proxy): def _start_proxied_socket(url, options, proxy):
if not HAVE_PYTHON_SOCKS:
raise WebSocketException("Python Socks is needed for SOCKS proxying but is not available")
hostname, port, resource, is_secure = parse_url(url) hostname, port, resource, is_secure = parse_url(url)
if not HAS_PYSOCKS: if proxy.proxy_protocol == "socks5":
raise WebSocketException("PySocks module not found.")
ptype = socks.SOCKS5
rdns = False rdns = False
if proxy.type == "socks4": proxy_type = ProxyType.SOCKS5
ptype = socks.SOCKS4 if proxy.proxy_protocol == "socks4":
if proxy.type == "http": rdns = False
ptype = socks.HTTP proxy_type = ProxyType.SOCKS4
if proxy.type[-1] == "h": # socks5h and socks4a send DNS through proxy
if proxy.proxy_protocol == "socks5h":
rdns = True rdns = True
proxy_type = ProxyType.SOCKS5
if proxy.proxy_protocol == "socks4a":
rdns = True
proxy_type = ProxyType.SOCKS4
sock = socks.create_connection( ws_proxy = Proxy.create(
(hostname, port), proxy_type=proxy_type,
proxy_type = ptype, host=proxy.proxy_host,
proxy_addr = proxy.host, port=int(proxy.proxy_port),
proxy_port = proxy.port, username=proxy.auth[0] if proxy.auth else None,
proxy_rdns = rdns, password=proxy.auth[1] if proxy.auth else None,
proxy_username = proxy.auth[0] if proxy.auth else None, rdns=rdns)
proxy_password = proxy.auth[1] if proxy.auth else None,
timeout = options.timeout,
socket_options = DEFAULT_SOCKET_OPTION + options.sockopt
)
if is_secure: sock = ws_proxy.connect(hostname, port, timeout=proxy.proxy_timeout)
if HAVE_SSL:
if is_secure and HAVE_SSL:
sock = _ssl_socket(sock, options.sslopt, hostname) sock = _ssl_socket(sock, options.sslopt, hostname)
else: elif is_secure:
raise WebSocketException("SSL not available.") raise WebSocketException("SSL not available.")
return sock, (hostname, port, resource) return sock, (hostname, port, resource)
def connect(url, options, proxy, socket): def connect(url, options, proxy, socket):
if proxy.host and not socket and not (proxy.type == 'http'): # Use _start_proxied_socket() only for socks4 or socks5 proxy
return _open_proxied_socket(url, options, proxy) # Use _tunnel() for http proxy
# TODO: Use python-socks for http protocol also, to standardize flow
if proxy.proxy_host and not socket and not (proxy.proxy_protocol == "http"):
return _start_proxied_socket(url, options, proxy)
hostname, port, resource, is_secure = parse_url(url) hostname, port, resource, is_secure = parse_url(url)
@ -137,7 +146,7 @@ def connect(url, options, proxy, socket):
def _get_addrinfo_list(hostname, port, is_secure, proxy): 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.proxy_host, proxy.proxy_port, proxy.auth, proxy.no_proxy)
try: try:
# when running on windows 10, getaddrinfo without socktype returns a socktype 0. # 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` # This generates an error exception: `_on_error: exception Socket type must be stream or datagram, not 0`
@ -174,10 +183,6 @@ def _open_socket(addrinfo_list, sockopt, timeout):
while not err: while not err:
try: try:
sock.connect(address) sock.connect(address)
except ProxyConnectionError as error:
err = WebSocketProxyException(str(error))
err.remote_ip = str(address[0])
continue
except socket.error as error: except socket.error as error:
error.remote_ip = str(address[0]) error.remote_ip = str(address[0])
try: try:
@ -190,6 +195,8 @@ def _open_socket(addrinfo_list, sockopt, timeout):
err = error err = error
continue continue
else: else:
if sock:
sock.close()
raise error raise error
else: else:
break break
@ -203,12 +210,8 @@ def _open_socket(addrinfo_list, sockopt, timeout):
return sock return sock
def _can_use_sni():
return six.PY2 and sys.version_info >= (2, 7, 9) or sys.version_info >= (3, 2)
def _wrap_sni_socket(sock, sslopt, hostname, check_hostname): def _wrap_sni_socket(sock, sslopt, hostname, check_hostname):
context = ssl.SSLContext(sslopt.get('ssl_version', ssl.PROTOCOL_SSLv23)) context = ssl.SSLContext(sslopt.get('ssl_version', ssl.PROTOCOL_TLS))
if sslopt.get('cert_reqs', ssl.CERT_NONE) != ssl.CERT_NONE: if sslopt.get('cert_reqs', ssl.CERT_NONE) != ssl.CERT_NONE:
cafile = sslopt.get('ca_certs', None) cafile = sslopt.get('ca_certs', None)
@ -250,21 +253,18 @@ def _ssl_socket(sock, user_sslopt, hostname):
certPath = os.environ.get('WEBSOCKET_CLIENT_CA_BUNDLE') certPath = os.environ.get('WEBSOCKET_CLIENT_CA_BUNDLE')
if certPath and os.path.isfile(certPath) \ if certPath and os.path.isfile(certPath) \
and user_sslopt.get('ca_certs', None) is None \ and user_sslopt.get('ca_certs', None) is None:
and user_sslopt.get('ca_cert', None) is None:
sslopt['ca_certs'] = certPath sslopt['ca_certs'] = certPath
elif certPath and os.path.isdir(certPath) \ elif certPath and os.path.isdir(certPath) \
and user_sslopt.get('ca_cert_path', None) is None: and user_sslopt.get('ca_cert_path', None) is None:
sslopt['ca_cert_path'] = certPath sslopt['ca_cert_path'] = certPath
if sslopt.get('server_hostname', None):
hostname = sslopt['server_hostname']
check_hostname = sslopt["cert_reqs"] != ssl.CERT_NONE and sslopt.pop( check_hostname = sslopt["cert_reqs"] != ssl.CERT_NONE and sslopt.pop(
'check_hostname', True) 'check_hostname', True)
if _can_use_sni():
sock = _wrap_sni_socket(sock, sslopt, hostname, check_hostname) sock = _wrap_sni_socket(sock, sslopt, hostname, check_hostname)
else:
sslopt.pop('check_hostname', True)
sock = ssl.wrap_socket(sock, **sslopt)
if not HAVE_CONTEXT_CHECK_HOSTNAME and check_hostname: if not HAVE_CONTEXT_CHECK_HOSTNAME and check_hostname:
match_hostname(sock.getpeercert(), hostname) match_hostname(sock.getpeercert(), hostname)
@ -274,7 +274,9 @@ 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]
@ -321,6 +323,9 @@ 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
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() headers[key.lower()] = value.strip()
else: else:
raise WebSocketException("Invalid header") raise WebSocketException("Invalid header")

View file

@ -1,23 +1,24 @@
""" """
"""
"""
_logging.py
websocket - WebSocket client library for Python websocket - WebSocket client library for Python
Copyright (C) 2010 Hiroki Ohtani(liris) Copyright 2021 engn33r
This library is free software; you can redistribute it and/or Licensed under the Apache License, Version 2.0 (the "License");
modify it under the terms of the GNU Lesser General Public you may not use this file except in compliance with the License.
License as published by the Free Software Foundation; either You may obtain a copy of the License at
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, http://www.apache.org/licenses/LICENSE-2.0
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-1335 USA
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
""" """
import logging import logging
@ -39,9 +40,12 @@ __all__ = ["enableTrace", "dump", "error", "warning", "debug", "trace",
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
@ -49,6 +53,7 @@ def enableTrace(traceable, handler = logging.StreamHandler()):
_logger.addHandler(handler) _logger.addHandler(handler)
_logger.setLevel(logging.DEBUG) _logger.setLevel(logging.DEBUG)
def dump(title, message): def dump(title, message):
if _traceEnabled: if _traceEnabled:
_logger.debug("--- " + title + " ---") _logger.debug("--- " + title + " ---")
@ -80,5 +85,6 @@ def isEnabledForError():
def isEnabledForDebug(): def isEnabledForDebug():
return _logger.isEnabledFor(logging.DEBUG) return _logger.isEnabledFor(logging.DEBUG)
def isEnabledForTrace(): def isEnabledForTrace():
return _traceEnabled return _traceEnabled

View file

@ -1,31 +1,29 @@
""" """
"""
"""
_socket.py
websocket - WebSocket client library for Python websocket - WebSocket client library for Python
Copyright (C) 2010 Hiroki Ohtani(liris) Copyright 2021 engn33r
This library is free software; you can redistribute it and/or Licensed under the Apache License, Version 2.0 (the "License");
modify it under the terms of the GNU Lesser General Public you may not use this file except in compliance with the License.
License as published by the Free Software Foundation; either You may obtain a copy of the License at
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, http://www.apache.org/licenses/LICENSE-2.0
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-1335 USA
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
""" """
import errno import errno
import select import selectors
import socket import socket
import six
import sys
from ._exceptions import * from ._exceptions import *
from ._ssl_compat import * from ._ssl_compat import *
from ._utils import * from ._utils import *
@ -62,7 +60,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
@ -70,7 +71,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
@ -91,7 +97,12 @@ def recv(sock, bufsize):
if error_code != errno.EAGAIN or error_code != errno.EWOULDBLOCK: if error_code != errno.EAGAIN or error_code != errno.EWOULDBLOCK:
raise raise
r, w, e = select.select((sock, ), (), (), sock.gettimeout()) sel = selectors.DefaultSelector()
sel.register(sock, selectors.EVENT_READ)
r = sel.select(sock.gettimeout())
sel.close()
if r: if r:
return sock.recv(bufsize) return sock.recv(bufsize)
@ -112,7 +123,7 @@ def recv(sock, bufsize):
if not bytes_: if not bytes_:
raise WebSocketConnectionClosedException( raise WebSocketConnectionClosedException(
"Connection is already closed.") "Connection to remote host was lost.")
return bytes_ return bytes_
@ -122,13 +133,13 @@ def recv_line(sock):
while True: while True:
c = recv(sock, 1) c = recv(sock, 1)
line.append(c) line.append(c)
if c == six.b("\n"): if c == b'\n':
break break
return six.b("").join(line) return b''.join(line)
def send(sock, data): def send(sock, data):
if isinstance(data, six.text_type): if isinstance(data, str):
data = data.encode('utf-8') data = data.encode('utf-8')
if not sock: if not sock:
@ -146,7 +157,12 @@ def send(sock, data):
if error_code != errno.EAGAIN or error_code != errno.EWOULDBLOCK: if error_code != errno.EAGAIN or error_code != errno.EWOULDBLOCK:
raise raise
r, w, e = select.select((), (sock, ), (), sock.gettimeout()) sel = selectors.DefaultSelector()
sel.register(sock, selectors.EVENT_WRITE)
w = sel.select(sock.gettimeout())
sel.close()
if w: if w:
return sock.send(data) return sock.send(data)

View file

@ -1,23 +1,20 @@
""" """
_ssl_compat.py
websocket - WebSocket client library for Python websocket - WebSocket client library for Python
Copyright (C) 2010 Hiroki Ohtani(liris) Copyright 2021 engn33r
This library is free software; you can redistribute it and/or Licensed under the Apache License, Version 2.0 (the "License");
modify it under the terms of the GNU Lesser General Public you may not use this file except in compliance with the License.
License as published by the Free Software Foundation; either You may obtain a copy of the License at
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, http://www.apache.org/licenses/LICENSE-2.0
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-1335 USA
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
""" """
__all__ = ["HAVE_SSL", "ssl", "SSLError", "SSLWantReadError", "SSLWantWriteError"] __all__ = ["HAVE_SSL", "ssl", "SSLError", "SSLWantReadError", "SSLWantWriteError"]
@ -26,20 +23,14 @@ try:
from ssl import SSLError from ssl import SSLError
from ssl import SSLWantReadError from ssl import SSLWantReadError
from ssl import SSLWantWriteError from ssl import SSLWantWriteError
HAVE_CONTEXT_CHECK_HOSTNAME = False
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:
HAVE_CONTEXT_CHECK_HOSTNAME = False
if hasattr(ssl, "match_hostname"):
from ssl import match_hostname
else:
from backports.ssl_match_hostname import match_hostname
__all__.append("match_hostname")
__all__.append("HAVE_CONTEXT_CHECK_HOSTNAME")
__all__.append("HAVE_CONTEXT_CHECK_HOSTNAME")
HAVE_SSL = True HAVE_SSL = True
except ImportError: except ImportError:
# dummy class of SSLError for ssl none-support environment. # dummy class of SSLError for environment without ssl support
class SSLError(Exception): class SSLError(Exception):
pass pass
@ -49,6 +40,5 @@ except ImportError:
class SSLWantWriteError(Exception): class SSLWantWriteError(Exception):
pass pass
ssl = lambda: None ssl = None
HAVE_SSL = False HAVE_SSL = False

View file

@ -1,30 +1,30 @@
""" """
"""
"""
_url.py
websocket - WebSocket client library for Python websocket - WebSocket client library for Python
Copyright (C) 2010 Hiroki Ohtani(liris) Copyright 2021 engn33r
This library is free software; you can redistribute it and/or Licensed under the Apache License, Version 2.0 (the "License");
modify it under the terms of the GNU Lesser General Public you may not use this file except in compliance with the License.
License as published by the Free Software Foundation; either You may obtain a copy of the License at
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, http://www.apache.org/licenses/LICENSE-2.0
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-1335 USA
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
""" """
import os import os
import socket import socket
import struct import struct
from six.moves.urllib.parse import urlparse from urllib.parse import unquote, urlparse
__all__ = ["parse_url", "get_proxy_info"] __all__ = ["parse_url", "get_proxy_info"]
@ -35,14 +35,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,25 +97,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", os.environ.get("NO_PROXY", "")).replace(" ", "")
if v: if v:
no_proxy = v.split(",") 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
@ -120,27 +129,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: str
looks for "https_proxy" in env Websocket server name.
is_secure: bool
Is the connection secure? (wss) looks for "https_proxy" in env
before falling back to "http_proxy" before falling back to "http_proxy"
proxy_host: str
options: "http_proxy_host" - http proxy host name. http proxy host name.
"http_proxy_port" - http proxy port. http_proxy_port: str or int
"http_no_proxy" - host names, which doesn't use proxy. http proxy port.
"http_proxy_auth" - http proxy auth information. http_no_proxy: list
tuple of username and password. Whitelisted host names that don't use the proxy.
default is None http_proxy_auth: tuple
"proxy_type" - if set to "socks5" PySocks wrapper HTTP proxy auth information. Tuple of username and password. Default is None.
will be used in place of a http proxy. proxy_type: str
default is "http" Specify the proxy protocol (http, socks4, socks4a, socks5, socks5h). Default is "http".
Use socks4a or socks5h if you want to send DNS requests through the proxy.
""" """
if _is_no_proxy_host(hostname, no_proxy): if _is_no_proxy_host(hostname, no_proxy):
return None, 0, None return None, 0, None
@ -155,10 +167,10 @@ def get_proxy_info(
env_keys.insert(0, "https_proxy") env_keys.insert(0, "https_proxy")
for key in env_keys: for key in env_keys:
value = os.environ.get(key, None) value = os.environ.get(key, os.environ.get(key.upper(), "")).replace(" ", "")
if value: if value:
proxy = urlparse(value) proxy = urlparse(value)
auth = (proxy.username, proxy.password) if proxy.username else None auth = (unquote(proxy.username), unquote(proxy.password)) if proxy.username else None
return proxy.hostname, proxy.port, auth return proxy.hostname, proxy.port, auth
return None, 0, None return None, 0, None

View file

@ -1,26 +1,21 @@
""" """
_url.py
websocket - WebSocket client library for Python websocket - WebSocket client library for Python
Copyright (C) 2010 Hiroki Ohtani(liris) Copyright 2021 engn33r
This library is free software; you can redistribute it and/or Licensed under the Apache License, Version 2.0 (the "License");
modify it under the terms of the GNU Lesser General Public you may not use this file except in compliance with the License.
License as published by the Free Software Foundation; either You may obtain a copy of the License at
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, http://www.apache.org/licenses/LICENSE-2.0
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-1335 USA
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
""" """
import six
__all__ = ["NoLock", "validate_utf8", "extract_err_message", "extract_error_code"] __all__ = ["NoLock", "validate_utf8", "extract_err_message", "extract_error_code"]
@ -81,8 +76,6 @@ except ImportError:
state = _UTF8_ACCEPT state = _UTF8_ACCEPT
codep = 0 codep = 0
for i in utfbytes: for i in utfbytes:
if six.PY2:
i = ord(i)
state, codep = _decode(state, codep, i) state, codep = _decode(state, codep, i)
if state == _UTF8_REJECT: if state == _UTF8_REJECT:
return False return False

View file

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

View file

@ -0,0 +1,18 @@
#!/usr/bin/env python
# From https://github.com/aaugustin/websockets/blob/main/example/echo.py
import asyncio
import websockets
async def echo(websocket, path):
async for message in websocket:
await websocket.send(message)
async def main():
async with websockets.serve(echo, "localhost", 8765):
await asyncio.Future() # run forever
asyncio.run(main())

View file

@ -0,0 +1,92 @@
# -*- coding: utf-8 -*-
#
"""
test_abnf.py
websocket - WebSocket client library for Python
Copyright 2021 engn33r
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""
import os
import websocket as ws
from websocket._abnf import *
import sys
import unittest
sys.path[0:0] = [""]
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_invalid_ping = ABNF(0,0,0,0, opcode=ABNF.OPCODE_PING)
self.assertRaises(ws._exceptions.WebSocketProtocolException, a_invalid_ping.validate, skip_utf8_validation=False)
a_bad_rsv_value = ABNF(0,1,0,0, opcode=ABNF.OPCODE_TEXT)
self.assertRaises(ws._exceptions.WebSocketProtocolException, a_bad_rsv_value.validate, skip_utf8_validation=False)
a_bad_opcode = ABNF(0,0,0,0, opcode=77)
self.assertRaises(ws._exceptions.WebSocketProtocolException, a_bad_opcode.validate, skip_utf8_validation=False)
a_bad_close_frame = ABNF(0,0,0,0, opcode=ABNF.OPCODE_CLOSE, data=b'\x01')
self.assertRaises(ws._exceptions.WebSocketProtocolException, a_bad_close_frame.validate, skip_utf8_validation=False)
a_bad_close_frame_2 = ABNF(0,0,0,0, opcode=ABNF.OPCODE_CLOSE, data=b'\x01\x8a\xaa\xff\xdd')
self.assertRaises(ws._exceptions.WebSocketProtocolException, a_bad_close_frame_2.validate, skip_utf8_validation=False)
a_bad_close_frame_3 = ABNF(0,0,0,0, opcode=ABNF.OPCODE_CLOSE, data=b'\x03\xe7')
self.assertRaises(ws._exceptions.WebSocketProtocolException, a_bad_close_frame_3.validate, skip_utf8_validation=True)
def testMask(self):
abnf_none_data = ABNF(0,0,0,0, opcode=ABNF.OPCODE_PING, mask=1, data=None)
bytes_val = bytes("aaaa", 'utf-8')
self.assertEqual(abnf_none_data._get_masked(bytes_val), bytes_val)
abnf_str_data = ABNF(0,0,0,0, opcode=ABNF.OPCODE_PING, mask=1, data="a")
self.assertEqual(abnf_str_data._get_masked(bytes_val), b'aaaa\x00')
def testFormat(self):
abnf_bad_rsv_bits = ABNF(2,0,0,0, opcode=ABNF.OPCODE_TEXT)
self.assertRaises(ValueError, abnf_bad_rsv_bits.format)
abnf_bad_opcode = ABNF(0,0,0,0, opcode=5)
self.assertRaises(ValueError, abnf_bad_opcode.format)
abnf_length_10 = ABNF(0,0,0,0, opcode=ABNF.OPCODE_TEXT, data="abcdefghij")
self.assertEqual(b'\x01', abnf_length_10.format()[0].to_bytes(1, 'big'))
self.assertEqual(b'\x8a', abnf_length_10.format()[1].to_bytes(1, 'big'))
self.assertEqual("fin=0 opcode=1 data=abcdefghij", abnf_length_10.__str__())
abnf_length_20 = ABNF(0,0,0,0, opcode=ABNF.OPCODE_BINARY, data="abcdefghijabcdefghij")
self.assertEqual(b'\x02', abnf_length_20.format()[0].to_bytes(1, 'big'))
self.assertEqual(b'\x94', abnf_length_20.format()[1].to_bytes(1, 'big'))
abnf_no_mask = ABNF(0,0,0,0, opcode=ABNF.OPCODE_TEXT, mask=0, data=b'\x01\x8a\xcc')
self.assertEqual(b'\x01\x03\x01\x8a\xcc', abnf_no_mask.format())
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()

View file

@ -0,0 +1,178 @@
# -*- coding: utf-8 -*-
#
"""
test_app.py
websocket - WebSocket client library for Python
Copyright 2021 engn33r
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""
import os
import os.path
import websocket as ws
import sys
import ssl
import unittest
sys.path[0:0] = [""]
# 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.
"""
self.send("hello!")
WebSocketAppTest.keep_running_open = self.keep_running
self.keep_running = False
def on_message(wsapp, message):
print(message)
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://127.0.0.1:8765', on_open=on_open, on_close=on_close, on_message=on_message)
app.run_forever()
@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():
return "\x00\x00\x00\x00"
app = ws.WebSocketApp('wss://stream.meetup.com/2/rsvps', get_mask_key=my_mask_key_func)
# if numpy is installed, this assertion fail
# Note: We can't use 'is' for comparing the functions directly, need to use 'id'.
self.assertEqual(id(app.get_mask_key), id(my_mask_key_func))
@unittest.skipUnless(TEST_WITH_INTERNET, "Internet-requiring tests are disabled")
def testInvalidPingIntervalPingTimeout(self):
""" Test exception handling if ping_interval < ping_timeout
"""
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)
self.assertRaises(ws.WebSocketException, app.run_forever, ping_interval=1, ping_timeout=2, sslopt={"cert_reqs": ssl.CERT_NONE})
@unittest.skipUnless(TEST_WITH_INTERNET, "Internet-requiring tests are disabled")
def testPingInterval(self):
""" Test WebSocketApp proper ping functionality
"""
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})
@unittest.skipUnless(TEST_WITH_INTERNET, "Internet-requiring tests are disabled")
def testOpcodeClose(self):
""" Test WebSocketApp close opcode
"""
app = ws.WebSocketApp('wss://tsock.us1.twilio.com/v3/wsconnect')
app.run_forever(ping_interval=2, ping_timeout=1, ping_payload="Ping payload")
@unittest.skipUnless(TEST_WITH_INTERNET, "Internet-requiring tests are disabled")
def testOpcodeBinary(self):
""" Test WebSocketApp binary opcode
"""
app = ws.WebSocketApp('streaming.vn.teslamotors.com/streaming/')
app.run_forever(ping_interval=2, ping_timeout=1, ping_payload="Ping payload")
@unittest.skipUnless(TEST_WITH_INTERNET, "Internet-requiring tests are disabled")
def testBadPingInterval(self):
""" A WebSocketApp handling of negative ping_interval
"""
app = ws.WebSocketApp('wss://api-pub.bitfinex.com/ws/1')
self.assertRaises(ws.WebSocketException, app.run_forever, ping_interval=-5, sslopt={"cert_reqs": ssl.CERT_NONE})
@unittest.skipUnless(TEST_WITH_INTERNET, "Internet-requiring tests are disabled")
def testBadPingTimeout(self):
""" A WebSocketApp handling of negative ping_timeout
"""
app = ws.WebSocketApp('wss://api-pub.bitfinex.com/ws/1')
self.assertRaises(ws.WebSocketException, app.run_forever, ping_timeout=-3, sslopt={"cert_reqs": ssl.CERT_NONE})
@unittest.skipUnless(TEST_WITH_INTERNET, "Internet-requiring tests are disabled")
def testCloseStatusCode(self):
""" Test extraction of close frame status code and close reason in WebSocketApp
"""
def on_close(wsapp, close_status_code, close_msg):
print("on_close reached")
app = ws.WebSocketApp('wss://tsock.us1.twilio.com/v3/wsconnect', on_close=on_close)
closeframe = ws.ABNF(opcode=ws.ABNF.OPCODE_CLOSE, data=b'\x03\xe8no-init-from-client')
self.assertEqual([1000, 'no-init-from-client'], app._get_close_args(closeframe))
closeframe = ws.ABNF(opcode=ws.ABNF.OPCODE_CLOSE, data=b'')
self.assertEqual([None, None], app._get_close_args(closeframe))
app2 = ws.WebSocketApp('wss://tsock.us1.twilio.com/v3/wsconnect')
closeframe = ws.ABNF(opcode=ws.ABNF.OPCODE_CLOSE, data=b'')
self.assertEqual([None, None], app2._get_close_args(closeframe))
self.assertRaises(ws.WebSocketConnectionClosedException, app.send, data="test if connection is closed")
if __name__ == "__main__":
unittest.main()

View file

@ -1,12 +1,29 @@
"""
"""
"""
test_cookiejar.py
websocket - WebSocket client library for Python
Copyright 2021 engn33r
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""
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 +46,25 @@ 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")
self.assertEqual(cookie_jar.get(None), "")
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 +82,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"), "")

View file

@ -0,0 +1,177 @@
# -*- coding: utf-8 -*-
#
"""
test_http.py
websocket - WebSocket client library for Python
Copyright 2021 engn33r
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""
import os
import os.path
import websocket as ws
from websocket._http import proxy_info, read_headers, _start_proxied_socket, _tunnel, _get_addrinfo_list, connect
import sys
import unittest
import ssl
import websocket
import socket
try:
from python_socks.sync import Proxy
from python_socks._errors import *
except:
from websocket._http import ProxyError, ProxyTimeoutError, ProxyConnectionError
sys.path[0:0] = [""]
# Skip test to access the internet.
TEST_WITH_INTERNET = os.environ.get('TEST_WITH_INTERNET', '0') == '1'
TEST_WITH_PROXY = os.environ.get('TEST_WITH_PROXY', '0') == '1'
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 = 1
self.sockopt = []
self.sslopt = {"cert_reqs": ssl.CERT_NONE}
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"))
@unittest.skipUnless(TEST_WITH_INTERNET, "Internet-requiring tests are disabled")
def testConnect(self):
# Not currently testing an actual proxy connection, so just check whether proxy errors are raised. This requires internet for a DNS lookup
if ws._http.HAVE_PYTHON_SOCKS:
# Need this check, otherwise case where python_socks is not installed triggers
# websocket._exceptions.WebSocketException: Python Socks is needed for SOCKS proxying but is not available
self.assertRaises(ProxyTimeoutError, _start_proxied_socket, "wss://example.com", OptsList(), proxy_info(http_proxy_host="example.com", http_proxy_port="8080", proxy_type="socks4", timeout=1))
self.assertRaises(ProxyTimeoutError, _start_proxied_socket, "wss://example.com", OptsList(), proxy_info(http_proxy_host="example.com", http_proxy_port="8080", proxy_type="socks4a", timeout=1))
self.assertRaises(ProxyTimeoutError, _start_proxied_socket, "wss://example.com", OptsList(), proxy_info(http_proxy_host="example.com", http_proxy_port="8080", proxy_type="socks5", timeout=1))
self.assertRaises(ProxyTimeoutError, _start_proxied_socket, "wss://example.com", OptsList(), proxy_info(http_proxy_host="example.com", http_proxy_port="8080", proxy_type="socks5h", timeout=1))
self.assertRaises(ProxyConnectionError, connect, "wss://example.com", OptsList(), proxy_info(http_proxy_host="127.0.0.1", http_proxy_port=9999, proxy_type="socks4", timeout=1), None)
self.assertRaises(TypeError, _get_addrinfo_list, None, 80, True, proxy_info(http_proxy_host="127.0.0.1", http_proxy_port="9999", proxy_type="http"))
self.assertRaises(TypeError, _get_addrinfo_list, None, 80, True, proxy_info(http_proxy_host="127.0.0.1", http_proxy_port="9999", proxy_type="http"))
self.assertRaises(socket.timeout, connect, "wss://google.com", OptsList(), proxy_info(http_proxy_host="8.8.8.8", http_proxy_port=9999, proxy_type="http", timeout=1), None)
self.assertEqual(
connect("wss://google.com", OptsList(), proxy_info(http_proxy_host="8.8.8.8", http_proxy_port=8080, proxy_type="http"), True),
(True, ("google.com", 443, "/")))
# The following test fails on Mac OS with a gaierror, not an OverflowError
# self.assertRaises(OverflowError, connect, "wss://example.com", OptsList(), proxy_info(http_proxy_host="127.0.0.1", http_proxy_port=99999, proxy_type="socks4", timeout=2), False)
@unittest.skipUnless(TEST_WITH_INTERNET, "Internet-requiring tests are disabled")
@unittest.skipUnless(TEST_WITH_PROXY, "This test requires a HTTP proxy to be running on port 8899")
def testProxyConnect(self):
ws = websocket.WebSocket()
ws.connect("ws://127.0.0.1:8765", http_proxy_host="127.0.0.1", http_proxy_port="8899", proxy_type="http")
ws.send("Hello, Server")
server_response = ws.recv()
self.assertEqual(server_response, "Hello, Server")
# self.assertEqual(_start_proxied_socket("wss://api.bitfinex.com/ws/2", OptsList(), proxy_info(http_proxy_host="127.0.0.1", http_proxy_port="8899", proxy_type="http"))[1], ("api.bitfinex.com", 443, '/ws/2'))
self.assertEqual(_get_addrinfo_list("api.bitfinex.com", 443, True, proxy_info(http_proxy_host="127.0.0.1", http_proxy_port="8899", proxy_type="http")),
(socket.getaddrinfo("127.0.0.1", 8899, 0, socket.SOCK_STREAM, socket.SOL_TCP), True, None))
self.assertEqual(connect("wss://api.bitfinex.com/ws/2", OptsList(), proxy_info(http_proxy_host="127.0.0.1", http_proxy_port=8899, proxy_type="http"), None)[1], ("api.bitfinex.com", 443, '/ws/2'))
# TODO: Test SOCKS4 and SOCK5 proxies with unit tests
@unittest.skipUnless(TEST_WITH_INTERNET, "Internet-requiring tests are disabled")
def testSSLopt(self):
ssloptions = {
"cert_reqs": ssl.CERT_NONE,
"check_hostname": False,
"server_hostname": "ServerName",
"ssl_version": ssl.PROTOCOL_TLS,
"ciphers": "TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:\
TLS_AES_128_GCM_SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:\
ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384:\
ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:\
DHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:\
ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES128-GCM-SHA256:\
ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:\
DHE-RSA-AES256-SHA256:ECDHE-ECDSA-AES128-SHA256:\
ECDHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA256:\
ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA",
"ecdh_curve": "prime256v1"
}
ws_ssl1 = websocket.WebSocket(sslopt=ssloptions)
ws_ssl1.connect("wss://api.bitfinex.com/ws/2")
ws_ssl1.send("Hello")
ws_ssl1.close()
ws_ssl2 = websocket.WebSocket(sslopt={"check_hostname": True})
ws_ssl2.connect("wss://api.bitfinex.com/ws/2")
ws_ssl2.close
def testProxyInfo(self):
self.assertEqual(proxy_info(http_proxy_host="127.0.0.1", http_proxy_port="8080", proxy_type="http").proxy_protocol, "http")
self.assertRaises(ProxyError, 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").proxy_host, "example.com")
self.assertEqual(proxy_info(http_proxy_host="127.0.0.1", http_proxy_port="8080", proxy_type="http").proxy_port, "8080")
self.assertEqual(proxy_info(http_proxy_host="127.0.0.1", http_proxy_port="8080", proxy_type="http").auth, None)
self.assertEqual(proxy_info(http_proxy_host="127.0.0.1", http_proxy_port="8080", proxy_type="http", http_proxy_auth=("my_username123", "my_pass321")).auth[0], "my_username123")
self.assertEqual(proxy_info(http_proxy_host="127.0.0.1", http_proxy_port="8080", proxy_type="http", http_proxy_auth=("my_username123", "my_pass321")).auth[1], "my_pass321")
if __name__ == "__main__":
unittest.main()

View file

@ -0,0 +1,303 @@
# -*- coding: utf-8 -*-
#
"""
test_url.py
websocket - WebSocket client library for Python
Copyright 2021 engn33r
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""
import sys
import os
import unittest
sys.path[0:0] = [""]
from websocket._url import get_proxy_info, parse_url, _is_address_in_network, _is_no_proxy_host
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")
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://john%40example.com:P%40SSWORD@localhost:3128/"
os.environ["https_proxy"] = "http://john%40example.com:P%40SSWORD@localhost2:3128/"
self.assertEqual(get_proxy_info("echo.websocket.org", True), ("localhost2", 3128, ("john@example.com", "P@SSWORD")))
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()

View file

@ -1,34 +1,44 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
"""
"""
"""
test_websocket.py
websocket - WebSocket client library for Python
Copyright 2021 engn33r
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""
import sys import sys
sys.path[0:0] = [""] sys.path[0:0] = [""]
import os import os
import os.path import os.path
import socket import socket
import six
# websocket-client
import websocket as ws 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:
from base64 import decodebytes as base64decode from base64 import decodebytes as base64decode
else:
from base64 import decodestring as base64decode
if sys.version_info[0] == 2 and sys.version_info[1] < 7:
import unittest2 as unittest
else:
import unittest import unittest
try: try:
import ssl
from ssl import SSLError from ssl import SSLError
except ImportError: except ImportError:
# dummy class of SSLError for ssl none-support environment. # dummy class of SSLError for ssl none-support environment.
@ -37,9 +47,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
@ -97,102 +104,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(str("¥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()
@ -216,18 +145,26 @@ class WebSocketTest(unittest.TestCase):
header = required_header.copy() header = required_header.copy()
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"))
# This case will print out a logging error using the error() function, but that is expected
self.assertEqual(_validate_header(header, key, ["sub2", "sub3"]), (False, None)) self.assertEqual(_validate_header(header, key, ["sub2", "sub3"]), (False, None))
header = required_header.copy() header = required_header.copy()
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()
# This case will print out a logging error using the error() function, but that is expected
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"))
@ -237,71 +174,67 @@ class WebSocketTest(unittest.TestCase):
sock.set_mask_key(create_mask_key) sock.set_mask_key(create_mask_key)
s = sock.sock = HeaderSockMock("data/header01.txt") s = sock.sock = HeaderSockMock("data/header01.txt")
sock.send("Hello") sock.send("Hello")
self.assertEqual(s.sent[0], six.b("\x81\x85abcd)\x07\x0f\x08\x0e")) self.assertEqual(s.sent[0], b'\x81\x85abcd)\x07\x0f\x08\x0e')
sock.send("こんにちは") sock.send("こんにちは")
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], b'\x81\x8fabcd\x82\xe3\xf0\x87\xe3\xf1\x80\xe5\xca\x81\xe2\xc5\x82\xe3\xcc')
sock.send(u"こんにちは") # 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(s.sent[1], b'\x81\x8fabcd\x82\xe3\xf0\x87\xe3\xf1\x80\xe5\xca\x81\xe2\xc5\x82\xe3\xcc")
sock.send("x" * 127) self.assertEqual(sock.send_binary(b'1111111111101'), 19)
def testRecv(self): def testRecv(self):
# TODO: add longer frame data # TODO: add longer frame data
sock = ws.WebSocket() sock = ws.WebSocket()
s = sock.sock = SockMock() s = sock.sock = SockMock()
something = six.b("\x81\x8fabcd\x82\xe3\xf0\x87\xe3\xf1\x80\xe5\xca\x81\xe2\xc5\x82\xe3\xcc") something = b'\x81\x8fabcd\x82\xe3\xf0\x87\xe3\xf1\x80\xe5\xca\x81\xe2\xc5\x82\xe3\xcc'
s.add_packet(something) s.add_packet(something)
data = sock.recv() data = sock.recv()
self.assertEqual(data, "こんにちは") self.assertEqual(data, "こんにちは")
s.add_packet(six.b("\x81\x85abcd)\x07\x0f\x08\x0e")) s.add_packet(b'\x81\x85abcd)\x07\x0f\x08\x0e')
data = sock.recv() data = sock.recv()
self.assertEqual(data, "Hello") self.assertEqual(data, "Hello")
@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):
sock = ws.WebSocket() sock = ws.WebSocket()
s = sock.sock = SockMock() s = sock.sock = SockMock()
s.add_packet(six.b("foo")) s.add_packet(b'foo')
s.add_packet(socket.timeout()) s.add_packet(socket.timeout())
s.add_packet(six.b("bar")) s.add_packet(b'bar')
# s.add_packet(SSLError("The read operation timed out")) # s.add_packet(SSLError("The read operation timed out"))
s.add_packet(six.b("baz")) s.add_packet(b'baz')
with self.assertRaises(ws.WebSocketTimeoutException): with self.assertRaises(ws.WebSocketTimeoutException):
sock.frame_buffer.recv_strict(9) sock.frame_buffer.recv_strict(9)
# if six.PY2:
# with self.assertRaises(ws.WebSocketTimeoutException):
# data = sock._recv_strict(9)
# else:
# with self.assertRaises(SSLError): # with self.assertRaises(SSLError):
# data = sock._recv_strict(9) # data = sock._recv_strict(9)
data = sock.frame_buffer.recv_strict(9) data = sock.frame_buffer.recv_strict(9)
self.assertEqual(data, six.b("foobarbaz")) self.assertEqual(data, b'foobarbaz')
with self.assertRaises(ws.WebSocketConnectionClosedException): with self.assertRaises(ws.WebSocketConnectionClosedException):
sock.frame_buffer.recv_strict(1) sock.frame_buffer.recv_strict(1)
def testRecvTimeout(self): def testRecvTimeout(self):
sock = ws.WebSocket() sock = ws.WebSocket()
s = sock.sock = SockMock() s = sock.sock = SockMock()
s.add_packet(six.b("\x81")) s.add_packet(b'\x81')
s.add_packet(socket.timeout()) s.add_packet(socket.timeout())
s.add_packet(six.b("\x8dabcd\x29\x07\x0f\x08\x0e")) s.add_packet(b'\x8dabcd\x29\x07\x0f\x08\x0e')
s.add_packet(socket.timeout()) s.add_packet(socket.timeout())
s.add_packet(six.b("\x4e\x43\x33\x0e\x10\x0f\x00\x40")) s.add_packet(b'\x4e\x43\x33\x0e\x10\x0f\x00\x40')
with self.assertRaises(ws.WebSocketTimeoutException): with self.assertRaises(ws.WebSocketTimeoutException):
sock.recv() sock.recv()
with self.assertRaises(ws.WebSocketTimeoutException): with self.assertRaises(ws.WebSocketTimeoutException):
@ -315,9 +248,9 @@ class WebSocketTest(unittest.TestCase):
sock = ws.WebSocket() sock = ws.WebSocket()
s = sock.sock = SockMock() s = sock.sock = SockMock()
# OPCODE=TEXT, FIN=0, MSG="Brevity is " # OPCODE=TEXT, FIN=0, MSG="Brevity is "
s.add_packet(six.b("\x01\x8babcd#\x10\x06\x12\x08\x16\x1aD\x08\x11C")) s.add_packet(b'\x01\x8babcd#\x10\x06\x12\x08\x16\x1aD\x08\x11C')
# OPCODE=CONT, FIN=1, MSG="the soul of wit" # OPCODE=CONT, FIN=1, MSG="the soul of wit"
s.add_packet(six.b("\x80\x8fabcd\x15\n\x06D\x12\r\x16\x08A\r\x05D\x16\x0b\x17")) s.add_packet(b'\x80\x8fabcd\x15\n\x06D\x12\r\x16\x08A\r\x05D\x16\x0b\x17')
data = sock.recv() data = sock.recv()
self.assertEqual(data, "Brevity is the soul of wit") self.assertEqual(data, "Brevity is the soul of wit")
with self.assertRaises(ws.WebSocketConnectionClosedException): with self.assertRaises(ws.WebSocketConnectionClosedException):
@ -327,21 +260,21 @@ class WebSocketTest(unittest.TestCase):
sock = ws.WebSocket(fire_cont_frame=True) sock = ws.WebSocket(fire_cont_frame=True)
s = sock.sock = SockMock() s = sock.sock = SockMock()
# OPCODE=TEXT, FIN=0, MSG="Brevity is " # OPCODE=TEXT, FIN=0, MSG="Brevity is "
s.add_packet(six.b("\x01\x8babcd#\x10\x06\x12\x08\x16\x1aD\x08\x11C")) s.add_packet(b'\x01\x8babcd#\x10\x06\x12\x08\x16\x1aD\x08\x11C')
# OPCODE=CONT, FIN=0, MSG="Brevity is " # OPCODE=CONT, FIN=0, MSG="Brevity is "
s.add_packet(six.b("\x00\x8babcd#\x10\x06\x12\x08\x16\x1aD\x08\x11C")) s.add_packet(b'\x00\x8babcd#\x10\x06\x12\x08\x16\x1aD\x08\x11C')
# OPCODE=CONT, FIN=1, MSG="the soul of wit" # OPCODE=CONT, FIN=1, MSG="the soul of wit"
s.add_packet(six.b("\x80\x8fabcd\x15\n\x06D\x12\r\x16\x08A\r\x05D\x16\x0b\x17")) s.add_packet(b'\x80\x8fabcd\x15\n\x06D\x12\r\x16\x08A\r\x05D\x16\x0b\x17')
_, data = sock.recv_data() _, data = sock.recv_data()
self.assertEqual(data, six.b("Brevity is ")) self.assertEqual(data, b'Brevity is ')
_, data = sock.recv_data() _, data = sock.recv_data()
self.assertEqual(data, six.b("Brevity is ")) self.assertEqual(data, b'Brevity is ')
_, data = sock.recv_data() _, data = sock.recv_data()
self.assertEqual(data, six.b("the soul of wit")) self.assertEqual(data, b'the soul of wit')
# OPCODE=CONT, FIN=0, MSG="Brevity is " # OPCODE=CONT, FIN=0, MSG="Brevity is "
s.add_packet(six.b("\x80\x8babcd#\x10\x06\x12\x08\x16\x1aD\x08\x11C")) s.add_packet(b'\x80\x8babcd#\x10\x06\x12\x08\x16\x1aD\x08\x11C')
with self.assertRaises(ws.WebSocketException): with self.assertRaises(ws.WebSocketException):
sock.recv_data() sock.recv_data()
@ -351,15 +284,13 @@ class WebSocketTest(unittest.TestCase):
def testClose(self): def testClose(self):
sock = ws.WebSocket() sock = ws.WebSocket()
sock.sock = SockMock()
sock.connected = True sock.connected = True
sock.close() sock.close
self.assertEqual(sock.connected, False)
sock = ws.WebSocket() sock = ws.WebSocket()
s = sock.sock = SockMock() s = sock.sock = SockMock()
sock.connected = True sock.connected = True
s.add_packet(six.b('\x88\x80\x17\x98p\x84')) s.add_packet(b'\x88\x80\x17\x98p\x84')
sock.recv() sock.recv()
self.assertEqual(sock.connected, False) self.assertEqual(sock.connected, False)
@ -367,20 +298,18 @@ class WebSocketTest(unittest.TestCase):
sock = ws.WebSocket() sock = ws.WebSocket()
s = sock.sock = SockMock() s = sock.sock = SockMock()
# OPCODE=CONT, FIN=1, MSG="the soul of wit" # OPCODE=CONT, FIN=1, MSG="the soul of wit"
s.add_packet(six.b("\x80\x8fabcd\x15\n\x06D\x12\r\x16\x08A\r\x05D\x16\x0b\x17")) s.add_packet(b'\x80\x8fabcd\x15\n\x06D\x12\r\x16\x08A\r\x05D\x16\x0b\x17')
self.assertRaises(ws.WebSocketException, sock.recv) self.assertRaises(ws.WebSocketException, sock.recv)
def testRecvWithProlongedFragmentation(self): def testRecvWithProlongedFragmentation(self):
sock = ws.WebSocket() sock = ws.WebSocket()
s = sock.sock = SockMock() s = sock.sock = SockMock()
# OPCODE=TEXT, FIN=0, MSG="Once more unto the breach, " # OPCODE=TEXT, FIN=0, MSG="Once more unto the breach, "
s.add_packet(six.b("\x01\x9babcd.\x0c\x00\x01A\x0f\x0c\x16\x04B\x16\n\x15" s.add_packet(b'\x01\x9babcd.\x0c\x00\x01A\x0f\x0c\x16\x04B\x16\n\x15\rC\x10\t\x07C\x06\x13\x07\x02\x07\tNC')
"\rC\x10\t\x07C\x06\x13\x07\x02\x07\tNC"))
# OPCODE=CONT, FIN=0, MSG="dear friends, " # OPCODE=CONT, FIN=0, MSG="dear friends, "
s.add_packet(six.b("\x00\x8eabcd\x05\x07\x02\x16A\x04\x11\r\x04\x0c\x07" s.add_packet(b'\x00\x8eabcd\x05\x07\x02\x16A\x04\x11\r\x04\x0c\x07\x17MB')
"\x17MB"))
# OPCODE=CONT, FIN=1, MSG="once more" # OPCODE=CONT, FIN=1, MSG="once more"
s.add_packet(six.b("\x80\x89abcd\x0e\x0c\x00\x01A\x0f\x0c\x16\x04")) s.add_packet(b'\x80\x89abcd\x0e\x0c\x00\x01A\x0f\x0c\x16\x04')
data = sock.recv() data = sock.recv()
self.assertEqual( self.assertEqual(
data, data,
@ -393,272 +322,135 @@ class WebSocketTest(unittest.TestCase):
sock.set_mask_key(create_mask_key) sock.set_mask_key(create_mask_key)
s = sock.sock = SockMock() s = sock.sock = SockMock()
# OPCODE=TEXT, FIN=0, MSG="Too much " # OPCODE=TEXT, FIN=0, MSG="Too much "
s.add_packet(six.b("\x01\x89abcd5\r\x0cD\x0c\x17\x00\x0cA")) s.add_packet(b'\x01\x89abcd5\r\x0cD\x0c\x17\x00\x0cA')
# OPCODE=PING, FIN=1, MSG="Please PONG this" # OPCODE=PING, FIN=1, MSG="Please PONG this"
s.add_packet(six.b("\x89\x90abcd1\x0e\x06\x05\x12\x07C4.,$D\x15\n\n\x17")) s.add_packet(b'\x89\x90abcd1\x0e\x06\x05\x12\x07C4.,$D\x15\n\n\x17')
# OPCODE=CONT, FIN=1, MSG="of a good thing" # OPCODE=CONT, FIN=1, MSG="of a good thing"
s.add_packet(six.b("\x80\x8fabcd\x0e\x04C\x05A\x05\x0c\x0b\x05B\x17\x0c" s.add_packet(b'\x80\x8fabcd\x0e\x04C\x05A\x05\x0c\x0b\x05B\x17\x0c\x08\x0c\x04')
"\x08\x0c\x04"))
data = sock.recv() data = sock.recv()
self.assertEqual(data, "Too much of a good thing") self.assertEqual(data, "Too much of a good thing")
with self.assertRaises(ws.WebSocketConnectionClosedException): with self.assertRaises(ws.WebSocketConnectionClosedException):
sock.recv() sock.recv()
self.assertEqual( self.assertEqual(
s.sent[0], s.sent[0],
six.b("\x8a\x90abcd1\x0e\x06\x05\x12\x07C4.,$D\x15\n\n\x17")) b'\x8a\x90abcd1\x0e\x06\x05\x12\x07C4.,$D\x15\n\n\x17')
@unittest.skipUnless(TEST_WITH_INTERNET, "Internet-requiring tests are disabled") @unittest.skipUnless(TEST_WITH_INTERNET, "Internet-requiring tests are disabled")
def testWebSocket(self): def testWebSocket(self):
s = ws.create_connection("ws://echo.websocket.org/") s = ws.create_connection("ws://127.0.0.1:8765")
self.assertNotEqual(s, None) self.assertNotEqual(s, None)
s.send("Hello, World") s.send("Hello, World")
result = s.recv() result = s.next()
s.fileno()
self.assertEqual(result, "Hello, World") self.assertEqual(result, "Hello, World")
s.send(u"こにゃにゃちは、世界") s.send("こにゃにゃちは、世界")
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")
def testPingPong(self): def testPingPong(self):
s = ws.create_connection("ws://echo.websocket.org/") s = ws.create_connection("ws://127.0.0.1:8765")
self.assertNotEqual(s, None) self.assertNotEqual(s, None)
s.ping("Hello") s.ping("Hello")
s.pong("Hi") s.pong("Hi")
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 testSupportRedirect(self):
def testSecureWebSocket(self): s = ws.WebSocket()
if 1: self.assertRaises(ws._exceptions.WebSocketBadStatusException, s.connect, "ws://google.com/")
import ssl # Need to find a URL that has a redirect code leading to a websocket
s = ws.create_connection("wss://echo.websocket.org/")
self.assertNotEqual(s, None)
self.assertTrue(isinstance(s.sock, ssl.SSLSocket))
s.send("Hello, World")
result = s.recv()
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 testSecureWebSocket(self):
s = ws.create_connection("ws://echo.websocket.org/", import ssl
s = ws.create_connection("wss://api.bitfinex.com/ws/2")
self.assertNotEqual(s, None)
self.assertTrue(isinstance(s.sock, ssl.SSLSocket))
self.assertEqual(s.getstatus(), 101)
self.assertNotEqual(s.getheaders(), None)
s.settimeout(10)
self.assertEqual(s.gettimeout(), 10)
self.assertEqual(s.getsubprotocol(), None)
s.abort()
@unittest.skipUnless(TEST_WITH_INTERNET, "Internet-requiring tests are disabled")
def testWebSocketWithCustomHeader(self):
s = ws.create_connection("ws://127.0.0.1:8765",
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")
def testAfterClose(self): def testAfterClose(self):
s = ws.create_connection("ws://echo.websocket.org/") s = ws.create_connection("ws://127.0.0.1:8765")
self.assertNotEqual(s, None) self.assertNotEqual(s, None)
s.close() s.close()
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")
def testSockOpt(self): def testSockOpt(self):
sockopt = ((socket.IPPROTO_TCP, socket.TCP_NODELAY, 1),) sockopt = ((socket.IPPROTO_TCP, socket.TCP_NODELAY, 1),)
s = ws.create_connection("ws://echo.websocket.org", sockopt=sockopt) s = ws.create_connection("ws://127.0.0.1:8765", sockopt=sockopt)
self.assertNotEqual(s.sock.getsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY), 0) self.assertNotEqual(s.sock.getsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY), 0)
s.close() s.close()
class UtilsTest(unittest.TestCase): class UtilsTest(unittest.TestCase):
def testUtf8Validator(self): def testUtf8Validator(self):
state = validate_utf8(six.b('\xf0\x90\x80\x80')) state = validate_utf8(b'\xf0\x90\x80\x80')
self.assertEqual(state, True) self.assertEqual(state, True)
state = validate_utf8(six.b('\xce\xba\xe1\xbd\xb9\xcf\x83\xce\xbc\xce\xb5\xed\xa0\x80edited')) state = validate_utf8(b'\xce\xba\xe1\xbd\xb9\xcf\x83\xce\xbc\xce\xb5\xed\xa0\x80edited')
self.assertEqual(state, False) self.assertEqual(state, False)
state = validate_utf8(six.b('')) state = validate_utf8(b'')
self.assertEqual(state, True) self.assertEqual(state, True)
class ProxyInfoTest(unittest.TestCase): class HandshakeTest(unittest.TestCase):
def setUp(self): @unittest.skipUnless(TEST_WITH_INTERNET, "Internet-requiring tests are disabled")
self.http_proxy = os.environ.get("http_proxy", None) def test_http_SSL(self):
self.https_proxy = os.environ.get("https_proxy", None) websock1 = ws.WebSocket(sslopt={"cert_chain": ssl.get_default_verify_paths().capath}, enable_multithread=False)
if "http_proxy" in os.environ: self.assertRaises(ValueError,
del os.environ["http_proxy"] websock1.connect, "wss://api.bitfinex.com/ws/2")
if "https_proxy" in os.environ: websock2 = ws.WebSocket(sslopt={"certfile": "myNonexistentCertFile"})
del os.environ["https_proxy"] self.assertRaises(FileNotFoundError,
websock2.connect, "wss://api.bitfinex.com/ws/2")
def tearDown(self): @unittest.skipUnless(TEST_WITH_INTERNET, "Internet-requiring tests are disabled")
if self.http_proxy: def testManualHeaders(self):
os.environ["http_proxy"] = self.http_proxy websock3 = ws.WebSocket(sslopt={"cert_reqs": ssl.CERT_NONE,
elif "http_proxy" in os.environ: "ca_certs": ssl.get_default_verify_paths().capath,
del os.environ["http_proxy"] "ca_cert_path": ssl.get_default_verify_paths().openssl_cafile})
self.assertRaises(ws._exceptions.WebSocketBadStatusException,
websock3.connect, "wss://api.bitfinex.com/ws/2", cookie="chocolate",
origin="testing_websockets.com",
host="echo.websocket.org/websocket-client-test",
subprotocols=["testproto"],
connection="Upgrade",
header={"CustomHeader1":"123",
"Cookie":"TestValue",
"Sec-WebSocket-Key":"k9kFAUWNAMmf5OEMfTlOEA==",
"Sec-WebSocket-Protocol":"newprotocol"})
if self.https_proxy: def testIPv6(self):
os.environ["https_proxy"] = self.https_proxy websock2 = ws.WebSocket()
elif "https_proxy" in os.environ: self.assertRaises(ValueError, websock2.connect, "2001:4860:4860::8888")
del os.environ["https_proxy"]
def testProxyFromArgs(self): def testBadURLs(self):
self.assertEqual(get_proxy_info("echo.websocket.org", False, proxy_host="localhost"), ("localhost", 0, None)) websock3 = ws.WebSocket()
self.assertEqual(get_proxy_info("echo.websocket.org", False, proxy_host="localhost", proxy_port=3128), ("localhost", 3128, None)) self.assertRaises(ValueError, websock3.connect, "ws//example.com")
self.assertEqual(get_proxy_info("echo.websocket.org", True, proxy_host="localhost"), ("localhost", 0, None)) self.assertRaises(ws.WebSocketAddressException, websock3.connect, "ws://example")
self.assertEqual(get_proxy_info("echo.websocket.org", True, proxy_host="localhost", proxy_port=3128), ("localhost", 3128, None)) self.assertRaises(ValueError, websock3.connect, "example.com")
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__":