You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
184 lines
6.6 KiB
184 lines
6.6 KiB
# Copyright (c) Alibaba, Inc. and its affiliates.
|
|
|
|
import logging
|
|
import threading
|
|
|
|
from enum import Enum, unique
|
|
from queue import Queue
|
|
|
|
from . import logging, token, websocket
|
|
from .exception import InvalidParameter, ConnectionTimeout, ConnectionUnavailable
|
|
|
|
__URL__ = 'wss://nls-gateway.cn-shanghai.aliyuncs.com/ws/v1'
|
|
__HEADER__ = [
|
|
'Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==',
|
|
'Sec-WebSocket-Version: 13',
|
|
]
|
|
|
|
__FORMAT__ = '%(asctime)s - %(levelname)s - %(message)s'
|
|
#__all__ = ['NlsCore']
|
|
|
|
def core_on_msg(ws, message, args):
|
|
logging.debug('core_on_msg:{}'.format(message))
|
|
if not args:
|
|
logging.error('callback core_on_msg with null args')
|
|
return
|
|
nls = args[0]
|
|
nls._NlsCore__issue_callback('on_message', [message])
|
|
|
|
def core_on_error(ws, message, args):
|
|
logging.debug('core_on_error:{}'.format(message))
|
|
if not args:
|
|
logging.error('callback core_on_error with null args')
|
|
return
|
|
nls = args[0]
|
|
nls._NlsCore__issue_callback('on_error', [message])
|
|
|
|
def core_on_close(ws, close_status_code, close_msg, args):
|
|
logging.debug('core_on_close')
|
|
if not args:
|
|
logging.error('callback core_on_close with null args')
|
|
return
|
|
nls = args[0]
|
|
nls._NlsCore__issue_callback('on_close')
|
|
|
|
def core_on_open(ws, args):
|
|
logging.debug('core_on_open:{}'.format(args))
|
|
if not args:
|
|
logging.debug('callback with null args')
|
|
ws.close()
|
|
elif len(args) != 2:
|
|
logging.debug('callback args not 2')
|
|
ws.close()
|
|
nls = args[0]
|
|
nls._NlsCore__notify_on_open()
|
|
nls.start(args[1], nls._NlsCore__ping_interval, nls._NlsCore__ping_timeout)
|
|
nls._NlsCore__issue_callback('on_open')
|
|
|
|
def core_on_data(ws, data, opcode, flag, args):
|
|
logging.debug('core_on_data opcode={}'.format(opcode))
|
|
if not args:
|
|
logging.error('callback core_on_data with null args')
|
|
return
|
|
nls = args[0]
|
|
nls._NlsCore__issue_callback('on_data', [data, opcode, flag])
|
|
|
|
@unique
|
|
class NlsConnectionStatus(Enum):
|
|
Disconnected = 0
|
|
Connected = 1
|
|
|
|
|
|
class NlsCore:
|
|
"""
|
|
NlsCore
|
|
"""
|
|
def __init__(self,
|
|
url=__URL__,
|
|
token=None,
|
|
on_open=None, on_message=None, on_close=None,
|
|
on_error=None, on_data=None, asynch=False, callback_args=[]):
|
|
self.__url = url
|
|
self.__async = asynch
|
|
if not token:
|
|
raise InvalidParameter('Must provide a valid token!')
|
|
else:
|
|
self.__token = token
|
|
self.__callbacks = {}
|
|
if on_open:
|
|
self.__callbacks['on_open'] = on_open
|
|
if on_message:
|
|
self.__callbacks['on_message'] = on_message
|
|
if on_close:
|
|
self.__callbacks['on_close'] = on_close
|
|
if on_error:
|
|
self.__callbacks['on_error'] = on_error
|
|
if on_data:
|
|
self.__callbacks['on_data'] = on_data
|
|
if not on_open and not on_message and not on_close and not on_error:
|
|
raise InvalidParameter('Must provide at least one callback')
|
|
logging.debug('callback args:{}'.format(callback_args))
|
|
self.__callback_args = callback_args
|
|
self.__header = __HEADER__ + ['X-NLS-Token: {}'.format(self.__token)]
|
|
websocket.enableTrace(True)
|
|
self.__ws = websocket.WebSocketApp(self.__url,
|
|
self.__header,
|
|
on_message=core_on_msg,
|
|
on_data=core_on_data,
|
|
on_error=core_on_error,
|
|
on_close=core_on_close,
|
|
callback_args=[self])
|
|
self.__ws.on_open = core_on_open
|
|
self.__lock = threading.Lock()
|
|
self.__cond = threading.Condition()
|
|
self.__connection_status = NlsConnectionStatus.Disconnected
|
|
|
|
def start(self, msg, ping_interval, ping_timeout):
|
|
self.__lock.acquire()
|
|
self.__ping_interval = ping_interval
|
|
self.__ping_timeout = ping_timeout
|
|
if self.__connection_status == NlsConnectionStatus.Disconnected:
|
|
self.__ws.update_args(self, msg)
|
|
self.__lock.release()
|
|
self.__connect_before_start(ping_interval, ping_timeout)
|
|
else:
|
|
self.__lock.release()
|
|
self.__ws.send(msg)
|
|
|
|
def __notify_on_open(self):
|
|
logging.debug('notify on open')
|
|
with self.__cond:
|
|
self.__connection_status = NlsConnectionStatus.Connected
|
|
self.__cond.notify()
|
|
|
|
def __issue_callback(self, which, exargs=[]):
|
|
if which not in self.__callbacks:
|
|
logging.error('no such callback:{}'.format(which))
|
|
return
|
|
if which is 'on_close':
|
|
with self.__cond:
|
|
self.__connection_status = NlsConnectionStatus.Disconnected
|
|
self.__cond.notify()
|
|
args = exargs+self.__callback_args
|
|
self.__callbacks[which](*args)
|
|
|
|
def send(self, msg, binary):
|
|
self.__lock.acquire()
|
|
if self.__connection_status == NlsConnectionStatus.Disconnected:
|
|
self.__lock.release()
|
|
logging.error('start before send')
|
|
raise ConnectionUnavailable('Must call start before send!')
|
|
else:
|
|
self.__lock.release()
|
|
if binary:
|
|
self.__ws.send(msg, opcode=websocket.ABNF.OPCODE_BINARY)
|
|
else:
|
|
logging.debug('send {}'.format(msg))
|
|
self.__ws.send(msg)
|
|
|
|
def shutdown(self):
|
|
self.__ws.close()
|
|
|
|
def __run(self, ping_interval, ping_timeout):
|
|
logging.debug('ws run...')
|
|
self.__ws.run_forever(ping_interval=ping_interval,
|
|
ping_timeout=ping_timeout)
|
|
with self.__lock:
|
|
self.__connection_status = NlsConnectionStatus.Disconnected
|
|
logging.debug('ws exit...')
|
|
|
|
def __connect_before_start(self, ping_interval, ping_timeout):
|
|
with self.__cond:
|
|
self.__th = threading.Thread(target=self.__run,
|
|
args=[ping_interval, ping_timeout])
|
|
self.__th.start()
|
|
if self.__connection_status == NlsConnectionStatus.Disconnected:
|
|
logging.debug('wait cond wakeup')
|
|
if not self.__async:
|
|
if self.__cond.wait(timeout=10):
|
|
logging.debug('wakeup without timeout')
|
|
return self.__connection_status == NlsConnectionStatus.Connected
|
|
else:
|
|
logging.debug('wakeup with timeout')
|
|
raise ConnectionTimeout('Wait response timeout! Please check local network!')
|