Exemplo n.º 1
0
    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
Exemplo n.º 2
0
    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
Exemplo n.º 3
0
 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
Exemplo n.º 4
0
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()
Exemplo n.º 5
0
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)
Exemplo n.º 6
0
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
Exemplo n.º 7
0
 def __init__(self, url):
     self._conn = WSClient(url,
                           self._on_recv_msg,
                           self._on_closed,
                           protocols=('janus-protocol', ))
     gevent.spawn(self.send_loop)
Exemplo n.º 8
0
    """
    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)