def pingpong(self): try: if self._ws_client is None: self._ws_client = WSClient(self.url, self._recv_msg_cbk, self._close_cbk, protocols=['janus-protocol']) ping_start_ts = get_monotonic_time() self.send_request(self._ws_client, create_janus_msg('ping')) ping_end_ts = get_monotonic_time() ping_latency = ping_end_ts - ping_start_ts if self._hwm_threshold and ping_latency > self._hwm_threshold: self.set_status(JANUS_SERVER_STATUS_HWM) else: self.set_status(JANUS_SERVER_STATUS_NORMAL) except Exception as e: if self._has_destroy: return log.warning('Poll janus server({}) failed: {}'.format(self.url, e)) self.set_status(JANUS_SERVER_STATUS_ABNORMAL) if self._ws_client: try: self._ws_client.close() except Exception: pass self._ws_client = None
def query_stat(self): try: if self._admin_ws_client is None: self._admin_ws_client = WSClient(self.admin_url, self._recv_msg_cbk, None, protocols=['janus-admin-protocol']) common_args = {} if self._admin_secret: common_args['admin_secret'] = self._admin_secret response = self.send_request(self._admin_ws_client, create_janus_msg('list_sessions', **common_args)) sessions = response.get('sessions', []) handles = [] for session_id in sessions: response = self.send_request(self._admin_ws_client, create_janus_msg('list_handles', session_id=session_id, **common_args)) handles.extend(response.get('handles', [])) self.set_stat(session_num=len(sessions), handle_num=len(handles)) except Exception as e: if self._has_destroy: return log.warning('Calculate stat of janus server({}) failed: {}'.format(self.admin_url, e)) self.set_stat(session_num=-1, handle_num=-1) # stop post statistic if self._admin_ws_client: try: self._admin_ws_client.close() except Exception: pass self._admin_ws_client = None
def init(self): try: self._ws_client = WSClient(self.url, self._recv_msg_cbk, self._close_cbk, protocols=['janus-protocol']) session_timeout = self._get_session_timeout() if session_timeout: self._keepalive_interval = int(session_timeout / 3) self.session_id = self._create_janus_session() self.state = BACKEND_SESSION_STATE_ACTIVE self._keepalive_greenlet = gevent.spawn(self._keepalive_routine) except Exception: if self._ws_client: self._ws_client.close() self._ws_client = None self.session_id = 0 self._keepalive_greenlet = None self.state = BACKEND_SESSION_STATE_CREATING raise
class Client(object): def __init__(self, url): self._conn = WSClient(url, self._on_recv_msg, self._on_closed, protocols=('janus-protocol', )) gevent.spawn(self.send_loop) def _on_recv_msg(self, msg): log.info('response received: {0}'.format(msg)) def _on_closed(self): log.info('Client closed') def send_loop(self): for x in range(5): time.sleep(1 + 1 / random.randint(1, 10)) self._conn.send_message({'janus': 'abc', 'transaction': str(x)}) log.info('Msg sent') time.sleep(2) self._conn.close()
class BackendSession(object): """ This backend session represents a session of the backend Janus server """ def __init__(self, url, auto_destroy=False, api_secret=''): self.url = url self._ws_client = None self._transactions = {} self.session_id = 0 self.state = BACKEND_SESSION_STATE_CREATING self._handles = {} self._auto_destroy = int(auto_destroy) self._auto_destroy_greenlet = None self._keepalive_interval = 10 self._keepalive_greenlet = None self._api_secret = api_secret _sessions[url] = self def init(self): try: self._ws_client = WSClient(self.url, self._recv_msg_cbk, self._close_cbk, protocols=['janus-protocol']) session_timeout = self._get_session_timeout() if session_timeout: self._keepalive_interval = int(session_timeout / 3) self.session_id = self._create_janus_session() self.state = BACKEND_SESSION_STATE_ACTIVE self._keepalive_greenlet = gevent.spawn(self._keepalive_routine) except Exception: if self._ws_client: self._ws_client.close() self._ws_client = None self.session_id = 0 self._keepalive_greenlet = None self.state = BACKEND_SESSION_STATE_CREATING raise def attach_handle(self, plugin_package_name, opaque_id=None, handle_listener=None): """ :param plugin_pacakge_name: str plugin package name :param opaque_id: str opaque id :param handle_listener: handle related callback listener which cannot block :return: BackendHandle object """ if self.state == BACKEND_SESSION_STATE_DESTROYED: raise JanusCloudError( 'Session has destroy for Janus server: {}'.format(self.url), JANUS_ERROR_SERVICE_UNAVAILABLE) attach_request_msg = create_janus_msg('attach', plugin=plugin_package_name) if opaque_id: attach_request_msg['opaque_id'] = opaque_id response = self.send_request(attach_request_msg) # would block for IO if response['janus'] == 'success': handle_id = response['data']['id'] elif response['janus'] == 'error': raise JanusCloudError( 'attach error for Janus server {} with reason {}'.format( self.url, response['error']['reason']), response['error']['code']) else: raise JanusCloudError( 'attach error for Janus server: {} with invalid response {}'. format(self.url, response), JANUS_ERROR_BAD_GATEWAY) # check again when wake up from block IO if self.state == BACKEND_SESSION_STATE_DESTROYED: raise JanusCloudError( 'Session has destroy for Janus server: {}'.format(self.url), JANUS_ERROR_SERVICE_UNAVAILABLE) handle = BackendHandle(handle_id, plugin_package_name, self, opaque_id=opaque_id, handle_listener=handle_listener) self._handles[handle_id] = handle if self._auto_destroy_greenlet: gevent.kill(self._auto_destroy_greenlet) self._auto_destroy_greenlet = None return handle def get_handle(self, handle_id, default=None): return self._handles.get(handle_id, default) def on_handle_detached(self, handle_id): self._handles.pop(handle_id, None) def send_request(self, msg, ignore_ack=True, timeout=30): if self.state == BACKEND_SESSION_STATE_DESTROYED: raise JanusCloudError( 'Session has destroy for Janus server: {}'.format(self.url), JANUS_ERROR_SERVICE_UNAVAILABLE) transaction_id = self._genrate_new_tid() send_msg = dict.copy(msg) send_msg['session_id'] = self.session_id send_msg['transaction'] = transaction_id if self._api_secret: send_msg['apisecret'] = self._api_secret transaction = BackendTransaction(transaction_id, send_msg, url=self.url, ignore_ack=ignore_ack) try: self._transactions[transaction_id] = transaction log.debug('Send Request {} to Janus server: {}'.format( send_msg, self.url)) self._ws_client.send_message(send_msg) response = transaction.wait_response(timeout=timeout) log.debug('Receive Response {} from Janus server: {}'.format( response, self.url)) return response finally: self._transactions.pop(transaction_id, None) def destroy(self): if self.state == BACKEND_SESSION_STATE_DESTROYED: return self.state = BACKEND_SESSION_STATE_DESTROYED if _sessions.get(self.url) == self: _sessions.pop(self.url) if self._auto_destroy_greenlet: gevent.kill(self._auto_destroy_greenlet) self._auto_destroy_greenlet = None for handle in self._handles.values(): handle.on_close() self._handles.clear() if self._ws_client: try: self._ws_client.close() except Exception: pass self._ws_client = None def _close_cbk(self): if self.state == BACKEND_SESSION_STATE_DESTROYED: return log.info('Backend session {} is closed by under network'.format( self.session_id)) self._ws_client = None self.destroy() def _auto_destroy_routine(self): log.info('Backend session {} is auto destroyed'.format( self.session_id)) self._auto_destroy_greenlet = None self.destroy() def _recv_msg_cbk(self, msg): try: if 'transaction' in msg: transaction = self._transactions.get(msg['transaction'], None) if transaction: transaction.response = msg elif msg['janus'] == 'timeout': log.debug( 'Receive session timeout from Janus server: {}'.format( self.url)) self.destroy() elif msg['janus'] == 'detached': log.debug( 'Receive async event {} from Janus server: {}'.format( msg, self.url)) handle = self._handles.pop(msg['sender'], None) if handle: handle.on_close() elif 'sender' in msg: log.debug( 'Receive async event {} from Janus server: {}'.format( msg, self.url)) handle = self._handles.get(msg['sender'], None) if handle: handle.on_async_event(msg) else: log.warn( 'Receive a invalid message {} on session {} for server {}'. format(msg, self.session_id, self.url)) except Exception: log.exception('Received a malformat msg {}'.format(msg)) def _genrate_new_tid(self): tid = str(random_uint64()) while tid in self._transactions: tid = str(random_uint64()) return tid def _get_session_timeout(self): response = self.send_request(create_janus_msg('info')) if response['janus'] == 'server_info': return response.get('session-timeout', 30) elif response['janus'] == 'error': raise JanusCloudError( 'Create session error for Janus server {} with reason {}'. format(self.url, response['error']['reason']), response['error']['code']) else: raise JanusCloudError( 'Create session error for Janus server: {} with invalid response {}' .format(self.url, response), JANUS_ERROR_BAD_GATEWAY) def _create_janus_session(self): response = self.send_request(create_janus_msg('create')) if response['janus'] == 'success': return response['data']['id'] elif response['janus'] == 'error': raise JanusCloudError( 'Create session error for Janus server {} with reason {}'. format(self.url, response['error']['reason']), response['error']['code']) else: raise JanusCloudError( 'Create session error for Janus server: {} with invalid response {}' .format(self.url, response), JANUS_ERROR_BAD_GATEWAY) def _keepalive_routine(self): gevent.sleep(self._keepalive_interval) keepalive_msg = create_janus_msg('keepalive') while self.state == BACKEND_SESSION_STATE_ACTIVE: try: # if there is no handle existed and auto destroy is enabled, just schedule the destroy route if not self._handles: if self._auto_destroy and self._auto_destroy_greenlet is None: self._auto_destroy_greenlet = gevent.spawn_later( self._auto_destroy, self._auto_destroy_routine) self.send_request(keepalive_msg, ignore_ack=False) except Exception as e: log.exception('Keepalive failed for backend session {}'.format( self.url)) self.destroy() else: gevent.sleep(self._keepalive_interval)
class JanusServer(object): def __init__(self, server_name, server_ip='127.0.0.1', public_ip='', ws_port=8188, admin_ws_port=0, pingpong_interval=5, statistic_interval=10, request_timeout=10, hwm_threshold=0, admin_secret='', location='', isp=''): self.server_name = server_name if self.server_name is None or self.server_name == '': self.server_name = str( uuid.uuid1()) # for empty, use uuid as server name self.server_local_ip = server_ip if server_ip == '': self.server_local_ip = '127.0.0.1' self.server_public_ip = public_ip if public_ip == '127.0.0.1' or public_ip == '': self.server_public_ip = get_host_ip() self.ws_port = ws_port self.session_num = -1 # unknown initially self.handle_num = -1 # unknown initially self.start_time = 0 self.status = JANUS_SERVER_STATUS_ABNORMAL self.location = location self.isp = isp self._in_maintenance = False self._admin_ws_port = admin_ws_port self._hwm_threshold = hwm_threshold self._admin_secret = admin_secret self._ws_client = None self._admin_ws_client = None self._transactions = {} self._has_destroy = False self._poll_greenlet = gevent.spawn(self._poll_routine) self._poll_interval = pingpong_interval self._statistic_greenlet = None if self._admin_ws_port: self._statistic_greenlet = gevent.spawn(self._statistic_routine) self._statistic_interval = statistic_interval self._request_timeout = request_timeout self._state_change_cbs = [] self._listeners = [] def destroy(self): if self._has_destroy: return self._has_destroy = True if self._ws_client: try: self._ws_client.close() except Exception: pass self._ws_client = None if self._admin_ws_client: try: self._admin_ws_client.close() except Exception: pass self._admin_ws_client = None self._poll_greenlet = None self._statistic_greenlet = None self.session_num = -1 self.handle_num = -1 self.status = JANUS_SERVER_STATUS_ABNORMAL self._listeners.clear() @property def url(self): return 'ws://{}:{}'.format(self.server_local_ip, self.ws_port) @property def admin_url(self): return 'ws://{}:{}'.format(self.server_local_ip, self._admin_ws_port) @property def public_url(self): return 'ws://{}:{}'.format(self.server_public_ip, self.ws_port) def set_status(self, new_status): if self._has_destroy: return if self._in_maintenance: return # ignore state change when maintaining old_status = self.status self.status = new_status if old_status != new_status: log.info('janus server({}) status changed to {}'.format( self.url, new_status)) for listener in self._listeners: if hasattr(listener, 'on_status_changed') and callable( listener.on_status_changed): listener.on_status_changed(new_status) def set_stat(self, session_num, handle_num): if self._has_destroy: return stat_updated = False if self.session_num != session_num: stat_updated = True self.session_num = session_num if self.handle_num != handle_num: stat_updated = True self.handle_num = handle_num if stat_updated: log.info( 'janus server({}) stat updated: session_num {}, handle_num {}'. format(self.url, self.session_num, self.handle_num)) if session_num >= 0 or handle_num >= 0: for listener in self._listeners: if hasattr(listener, 'on_stat_updated') and callable( listener.on_stat_updated): listener.on_stat_updated() def register_listener(self, listener): self._listeners.append(listener) def start_maintenance(self): if self._in_maintenance: return # already under maintenance self.set_status(JANUS_SERVER_STATUS_MAINTENANCE) self._in_maintenance = True def stop_maintenance(self): if not self._in_maintenance: return self._in_maintenance = False self.set_status(JANUS_SERVER_STATUS_ABNORMAL) def pingpong(self): try: if self._ws_client is None: self._ws_client = WSClient(self.url, self._recv_msg_cbk, self._close_cbk, protocols=['janus-protocol']) ping_start_ts = get_monotonic_time() self.send_request(self._ws_client, create_janus_msg('ping')) ping_end_ts = get_monotonic_time() ping_latency = ping_end_ts - ping_start_ts if self._hwm_threshold and ping_latency > self._hwm_threshold: self.set_status(JANUS_SERVER_STATUS_HWM) else: self.set_status(JANUS_SERVER_STATUS_NORMAL) except Exception as e: if self._has_destroy: return log.warning('Poll janus server({}) failed: {}'.format(self.url, e)) self.set_status(JANUS_SERVER_STATUS_ABNORMAL) if self._ws_client: try: self._ws_client.close() except Exception: pass self._ws_client = None def _get_self_session_ids(self): self_session_ids = set() self_sessions = get_cur_sessions() for session in self_sessions: if session.session_id: self_session_ids.add(session.session_id) return self_session_ids def query_stat(self): try: if self._admin_ws_client is None: self._admin_ws_client = WSClient( self.admin_url, self._recv_msg_cbk, None, protocols=['janus-admin-protocol']) common_args = {} if self._admin_secret: common_args['admin_secret'] = self._admin_secret self_session_ids = self._get_self_session_ids() response = self.send_request( self._admin_ws_client, create_janus_msg('list_sessions', **common_args)) sessions = response.get('sessions', []) handles = [] for session_id in sessions: if session_id in self_session_ids: # for self session, not add to stat continue response = self.send_request( self._admin_ws_client, create_janus_msg('list_handles', session_id=session_id, **common_args)) handles.extend(response.get('handles', [])) self.set_stat(session_num=len(sessions), handle_num=len(handles)) except Exception as e: if self._has_destroy: return log.warning('Calculate stat of janus server({}) failed: {}'.format( self.admin_url, e)) self.set_stat(session_num=-1, handle_num=-1) # stop post statistic if self._admin_ws_client: try: self._admin_ws_client.close() except Exception: pass self._admin_ws_client = None def send_request(self, client, msg, ignore_ack=True): if self._has_destroy: raise JanusCloudError('Janus server already destory', JANUS_ERROR_SERVICE_UNAVAILABLE) if client is None: raise JanusCloudError('websocket client not ready', JANUS_ERROR_SERVICE_UNAVAILABLE) transaction_id = self._generate_new_tid() send_msg = dict.copy(msg) send_msg['transaction'] = transaction_id transaction = BackendTransaction(transaction_id, send_msg, url=client.url, ignore_ack=ignore_ack) try: self._transactions[transaction_id] = transaction log.debug('Send Request {} to Janus server: {}'.format( send_msg, client.url)) client.send_message(send_msg) response = transaction.wait_response(timeout=self._request_timeout) log.debug('Receive Response {} from Janus server: {}'.format( response, client.url)) if response['janus'] == 'error': raise JanusCloudError(response['error']['reason'], response['error']['code']) return response finally: self._transactions.pop(transaction_id, None) def _close_cbk(self): if self._has_destroy: return log.info('WebSocket closed for Janus server {} '.format(self.url)) self.set_status(JANUS_SERVER_STATUS_ABNORMAL) def _recv_msg_cbk(self, msg): try: if 'transaction' in msg: transaction = self._transactions.get(msg['transaction'], None) if transaction: transaction.response = msg else: log.warning( 'Receive a invalid message {} for server {}'.format( msg, self.url)) except Exception: log.exception('Received a malformat msg {}'.format(msg)) def _generate_new_tid(self): tid = str(random_uint64()) while tid in self._transactions: tid = str(random_uint64()) return tid def _poll_routine(self): while not self._has_destroy: gevent.sleep(self._poll_interval) if self._has_destroy: break self.pingpong() def _statistic_routine(self): while not self._has_destroy: gevent.sleep(self._statistic_interval) if self._has_destroy: break self.query_stat() def on_process_status_change(self, watcher): log.debug( 'on_process_status_change is called, new status: {}({})'.format( watcher.process_status, PROC_STATUS_TEXT[watcher.process_status])) if watcher.process_status == PROC_RUNNING: self.start_time = time.time() else: self.start_time = 0
def __init__(self, url): self._conn = WSClient(url, self._on_recv_msg, self._on_closed, protocols=('janus-protocol', )) gevent.spawn(self.send_loop)
""" gevent.spawn(WSServer('127.0.0.1:9999', EchoServer(), keyfile='../../certs/mycert.key', certfile='../../certs/mycert.pem').server_forever) time.sleep(1) c = Client('wss://127.0.0.1:9999') time.sleep(60) """ # example to connect to Janus def incoming_msg(msg): print('received message:') print(msg) def on_closed(): print('Client closed') client = WSClient("ws://127.0.0.1:8288", incoming_msg, on_closed, protocols=('janus-protocol', )) client.send_message({'janus': 'info', 'transaction': 'abcdef'}) print('after send_message') time.sleep(5) print('before close') client.close() print('after close, client terminated:{}'.format(client.terminated)) time.sleep(1) print('after sleep, client terminated:{}'.format(client.terminated)) time.sleep(5)