This commit is contained in:
Croneter 2018-06-15 14:40:29 +02:00
parent 1a58967111
commit 51444111d2

View file

@ -27,8 +27,10 @@ try:
from ssl import SSLError from ssl import SSLError
HAVE_SSL = True HAVE_SSL = True
except ImportError: except ImportError:
# dummy class of SSLError for ssl none-support environment.
class SSLError(Exception): class SSLError(Exception):
"""
Dummy class of SSLError for ssl none-support environment.
"""
pass pass
HAVE_SSL = False HAVE_SSL = False
@ -50,7 +52,7 @@ import utils
############################################################################### ###############################################################################
log = logging.getLogger("PLEX."+__name__) LOG = logging.getLogger("PLEX." + __name__)
############################################################################### ###############################################################################
@ -95,28 +97,31 @@ class WebSocketConnectionClosedException(WebSocketException):
""" """
pass pass
class WebSocketTimeoutException(WebSocketException): class WebSocketTimeoutException(WebSocketException):
""" """
WebSocketTimeoutException will be raised at socket timeout during read/write data. WebSocketTimeoutException will be raised at socket timeout during read and
write data.
""" """
pass pass
default_timeout = None
traceEnabled = False DEFAULT_TIMEOUT = None
TRACE_ENABLED = False
def enableTrace(tracable): def enable_trace(tracable):
""" """
turn on/off the tracability. turn on/off the tracability.
tracable: boolean value. if set True, tracability is enabled. tracable: boolean value. if set True, tracability is enabled.
""" """
global traceEnabled global TRACE_ENABLED
traceEnabled = tracable TRACE_ENABLED = tracable
if tracable: if tracable:
if not log.handlers: if not LOG.handlers:
log.addHandler(logging.StreamHandler()) LOG.addHandler(logging.StreamHandler())
log.setLevel(logging.DEBUG) LOG.setLevel(logging.DEBUG)
def setdefaulttimeout(timeout): def setdefaulttimeout(timeout):
@ -125,15 +130,15 @@ def setdefaulttimeout(timeout):
timeout: default socket timeout time. This value is second. timeout: default socket timeout time. This value is second.
""" """
global default_timeout global DEFAULT_TIMEOUT
default_timeout = timeout DEFAULT_TIMEOUT = timeout
def getdefaulttimeout(): def getdefaulttimeout():
""" """
Return the global timeout setting(second) to connect. Return the global timeout setting(second) to connect.
""" """
return default_timeout return DEFAULT_TIMEOUT
def _parse_url(url): def _parse_url(url):
@ -185,7 +190,8 @@ def create_connection(url, timeout=None, **options):
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, the global default timeout setting returned by getdefauttimeout() is used. If no timeout is supplied, the global default timeout setting returned by
getdefauttimeout() 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.
@ -195,21 +201,23 @@ def create_connection(url, timeout=None, **options):
timeout: socket timeout time. This value is integer. timeout: socket timeout time. This value is integer.
if you set None for this value, it means "use default_timeout value" if you set None for this value, it means "use DEFAULT_TIMEOUT
value"
options: current support option is only "header". options: current support option is only "header".
if you set header as dict value, the custom HTTP headers are added. if you set header as dict value, the custom HTTP headers are added
""" """
sockopt = options.get("sockopt", []) sockopt = options.get("sockopt", [])
sslopt = options.get("sslopt", {}) sslopt = options.get("sslopt", {})
websock = WebSocket(sockopt=sockopt, sslopt=sslopt) websock = WebSocket(sockopt=sockopt, sslopt=sslopt)
websock.settimeout(timeout if timeout is not None else default_timeout) websock.settimeout(timeout if timeout is not None else DEFAULT_TIMEOUT)
websock.connect(url, **options) websock.connect(url, **options)
return websock return websock
_MAX_INTEGER = (1 << 32) -1
_MAX_INTEGER = (1 << 32) - 1
_AVAILABLE_KEY_CHARS = range(0x21, 0x2f + 1) + range(0x3a, 0x7e + 1) _AVAILABLE_KEY_CHARS = range(0x21, 0x2f + 1) + range(0x3a, 0x7e + 1)
_MAX_CHAR_BYTE = (1<<8) -1 _MAX_CHAR_BYTE = (1 << 8) - 1
# ref. Websocket gets an update, and it breaks stuff. # ref. Websocket gets an update, and it breaks stuff.
# http://axod.blogspot.com/2010/06/websocket-gets-update-and-it-breaks.html # http://axod.blogspot.com/2010/06/websocket-gets-update-and-it-breaks.html
@ -220,10 +228,7 @@ def _create_sec_websocket_key():
return base64.encodestring(uid.bytes).strip() return base64.encodestring(uid.bytes).strip()
_HEADERS_TO_CHECK = { _HEADERS_TO_CHECK = {"upgrade": "websocket", "connection": "upgrade"}
"upgrade": "websocket",
"connection": "upgrade",
}
class ABNF(object): class ABNF(object):
@ -234,16 +239,16 @@ class ABNF(object):
""" """
# operation code values. # operation code values.
OPCODE_CONT = 0x0 OPCODE_CONT = 0x0
OPCODE_TEXT = 0x1 OPCODE_TEXT = 0x1
OPCODE_BINARY = 0x2 OPCODE_BINARY = 0x2
OPCODE_CLOSE = 0x8 OPCODE_CLOSE = 0x8
OPCODE_PING = 0x9 OPCODE_PING = 0x9
OPCODE_PONG = 0xa OPCODE_PONG = 0xa
# available operation code value tuple # available operation code value tuple
OPCODES = (OPCODE_CONT, OPCODE_TEXT, OPCODE_BINARY, OPCODE_CLOSE, OPCODES = (OPCODE_CONT, OPCODE_TEXT, OPCODE_BINARY, OPCODE_CLOSE,
OPCODE_PING, OPCODE_PONG) OPCODE_PING, OPCODE_PONG)
# opcode human readable string # opcode human readable string
OPCODE_MAP = { OPCODE_MAP = {
@ -253,10 +258,10 @@ class ABNF(object):
OPCODE_CLOSE: "close", OPCODE_CLOSE: "close",
OPCODE_PING: "ping", OPCODE_PING: "ping",
OPCODE_PONG: "pong" OPCODE_PONG: "pong"
} }
# data length threashold. # data length threashold.
LENGTH_7 = 0x7d LENGTH_7 = 0x7d
LENGTH_16 = 1 << 16 LENGTH_16 = 1 << 16
LENGTH_63 = 1 << 63 LENGTH_63 = 1 << 63
@ -277,8 +282,8 @@ class ABNF(object):
def __str__(self): def __str__(self):
return "fin=" + str(self.fin) \ return "fin=" + str(self.fin) \
+ " opcode=" + str(self.opcode) \ + " opcode=" + str(self.opcode) \
+ " data=" + str(self.data) + " data=" + str(self.data)
@staticmethod @staticmethod
def create_frame(data, opcode): def create_frame(data, opcode):
@ -308,9 +313,9 @@ class ABNF(object):
if length >= ABNF.LENGTH_63: if length >= ABNF.LENGTH_63:
raise ValueError("data is too long") raise ValueError("data is too long")
frame_header = chr(self.fin << 7 frame_header = chr(self.fin << 7 |
| self.rsv1 << 6 | self.rsv2 << 5 | self.rsv3 << 4 self.rsv1 << 6 | self.rsv2 << 5 | self.rsv3 << 4 |
| self.opcode) self.opcode)
if length < ABNF.LENGTH_7: if length < ABNF.LENGTH_7:
frame_header += chr(self.mask << 7 | length) frame_header += chr(self.mask << 7 | length)
elif length < ABNF.LENGTH_16: elif length < ABNF.LENGTH_16:
@ -395,6 +400,9 @@ class WebSocket(object):
self._cont_data = None self._cont_data = None
def fileno(self): def fileno(self):
"""
Returns sock.fileno()
"""
return self.sock.fileno() return self.sock.fileno()
def set_mask_key(self, func): def set_mask_key(self, func):
@ -438,7 +446,7 @@ class WebSocket(object):
timeout: socket timeout time. This value is integer. timeout: socket timeout time. This value is integer.
if you set None for this value, if you set None for this value,
it means "use default_timeout value" it means "use DEFAULT_TIMEOUT value"
options: current support option is only "header". options: current support option is only "header".
if you set header as dict value, if you set header as dict value,
@ -487,10 +495,10 @@ class WebSocket(object):
header_str = "\r\n".join(headers) header_str = "\r\n".join(headers)
self._send(header_str) self._send(header_str)
if traceEnabled: if TRACE_ENABLED:
log.debug("--- request header ---") LOG.debug("--- request header ---")
log.debug(header_str) LOG.debug(header_str)
log.debug("-----------------------") LOG.debug("-----------------------")
status, resp_headers = self._read_headers() status, resp_headers = self._read_headers()
if status != 101: if status != 101:
@ -526,16 +534,16 @@ class WebSocket(object):
def _read_headers(self): def _read_headers(self):
status = None status = None
headers = {} headers = {}
if traceEnabled: if TRACE_ENABLED:
log.debug("--- response header ---") LOG.debug("--- response header ---")
while True: while True:
line = self._recv_line() line = self._recv_line()
if line == "\r\n": if line == "\r\n":
break break
line = line.strip() line = line.strip()
if traceEnabled: if TRACE_ENABLED:
log.debug(line) LOG.debug(line)
if not status: if not status:
status_info = line.split(" ", 2) status_info = line.split(" ", 2)
status = int(status_info[1]) status = int(status_info[1])
@ -547,8 +555,8 @@ class WebSocket(object):
else: else:
raise WebSocketException("Invalid header") raise WebSocketException("Invalid header")
if traceEnabled: if TRACE_ENABLED:
log.debug("-----------------------") LOG.debug("-----------------------")
return status, headers return status, headers
@ -567,14 +575,17 @@ class WebSocket(object):
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 traceEnabled: if TRACE_ENABLED:
log.debug("send: " + repr(data)) LOG.debug("send: %s", repr(data))
while data: while data:
l = self._send(data) l = self._send(data)
data = data[l:] data = data[l:]
return length return length
def send_binary(self, payload): def send_binary(self, payload):
"""
send the payload
"""
return self.send(payload, ABNF.OPCODE_BINARY) return self.send(payload, ABNF.OPCODE_BINARY)
def ping(self, payload=""): def ping(self, payload=""):
@ -693,34 +704,10 @@ class WebSocket(object):
reason: the reason to close. This must be string. reason: the reason to close. This must be string.
""" """
try: try:
self.sock.shutdown(socket.SHUT_RDWR) self.sock.shutdown(socket.SHUT_RDWR)
except: except:
pass pass
'''
if self.connected:
if status < 0 or status >= ABNF.LENGTH_16:
raise ValueError("code is invalid range")
try:
self.send(struct.pack('!H', status) + reason, ABNF.OPCODE_CLOSE)
timeout = self.sock.gettimeout()
self.sock.settimeout(3)
try:
frame = self.recv_frame()
if log.isEnabledFor(logging.ERROR):
recv_status = struct.unpack("!H", frame.data)[0]
if recv_status != STATUS_NORMAL:
log.error("close status: " + repr(recv_status))
except:
pass
self.sock.settimeout(timeout)
self.sock.shutdown(socket.SHUT_RDWR)
except:
pass
'''
self._closeInternal() self._closeInternal()
def _closeInternal(self): def _closeInternal(self):
@ -752,7 +739,6 @@ class WebSocket(object):
raise WebSocketConnectionClosedException() raise WebSocketConnectionClosedException()
return bytes_ return bytes_
def _recv_strict(self, bufsize): def _recv_strict(self, bufsize):
shortage = bufsize - sum(len(x) for x in self._recv_buffer) shortage = bufsize - sum(len(x) for x in self._recv_buffer)
while shortage > 0: while shortage > 0:
@ -767,7 +753,6 @@ class WebSocket(object):
self._recv_buffer = [unified[bufsize:]] self._recv_buffer = [unified[bufsize:]]
return unified[:bufsize] return unified[:bufsize]
def _recv_line(self): def _recv_line(self):
line = [] line = []
while True: while True:
@ -846,9 +831,11 @@ class WebSocketApp(object):
run event loop for WebSocket framework. run event loop for WebSocket framework.
This loop is infinite loop and is alive during websocket is available. This loop is infinite loop and is alive during websocket is available.
sockopt: values for socket.setsockopt. sockopt: values for socket.setsockopt.
sockopt must be tuple and each element is argument of sock.setscokopt. sockopt must be tuple and each element is argument of
sock.setscokopt.
sslopt: ssl socket optional dict. sslopt: ssl socket optional dict.
ping_interval: automatically send "ping" command every specified period(second) ping_interval: automatically send "ping" command every specified
period(second)
if set to 0, not send automatically. if set to 0, not send automatically.
""" """
if sockopt is None: if sockopt is None:
@ -861,26 +848,26 @@ class WebSocketApp(object):
self.keep_running = True self.keep_running = True
try: try:
self.sock = WebSocket(self.get_mask_key, sockopt=sockopt, sslopt=sslopt) self.sock = WebSocket(self.get_mask_key,
self.sock.settimeout(default_timeout) sockopt=sockopt,
sslopt=sslopt)
self.sock.settimeout(DEFAULT_TIMEOUT)
self.sock.connect(self.url, header=self.header) self.sock.connect(self.url, header=self.header)
self._callback(self.on_open) self._callback(self.on_open)
if ping_interval: if ping_interval:
thread = threading.Thread(target=self._send_ping, args=(ping_interval,)) thread = threading.Thread(target=self._send_ping,
args=(ping_interval,))
thread.setDaemon(True) thread.setDaemon(True)
thread.start() thread.start()
while self.keep_running: while self.keep_running:
try: try:
data = self.sock.recv() data = self.sock.recv()
if data is None or self.keep_running is False:
if data is None or self.keep_running == False:
break break
self._callback(self.on_message, data) self._callback(self.on_message, data)
except Exception, e: except Exception, e:
#print str(e.args[0])
if "timed out" not in e.args[0]: if "timed out" not in e.args[0]:
raise e raise e
@ -898,19 +885,18 @@ class WebSocketApp(object):
try: try:
callback(self, *args) callback(self, *args)
except Exception, e: except Exception, e:
log.error(e) LOG.error(e)
if True:#log.isEnabledFor(logging.DEBUG): _, _, tb = sys.exc_info()
_, _, tb = sys.exc_info() traceback.print_tb(tb)
traceback.print_tb(tb)
if __name__ == "__main__": if __name__ == "__main__":
enableTrace(True) enable_trace(True)
ws = create_connection("ws://echo.websocket.org/") WEBSOCKET = create_connection("ws://echo.websocket.org/")
print("Sending 'Hello, World'...") LOG.info("Sending 'Hello, World'...")
ws.send("Hello, World") WEBSOCKET.send("Hello, World")
print("Sent") LOG.info("Sent")
print("Receiving...") LOG.info("Receiving...")
result = ws.recv() RESULT = WEBSOCKET.recv()
print("Received '%s'" % result) LOG.info("Received '%s'", RESULT)
ws.close() WEBSOCKET.close()