Если Вам интересно, прошу под кат.
Настройка
Для того что бы научить Yate работать с нашим будущим модулем, нам нужно отредактировать файл exmodule.conf и дописать в него следующие строки:
[listener test]
type=tcp
addr=127.0.0.1
port=5678
В данном случае сервер начнет слушать подключения на порту 5678 по tcp протоколу. Давайте проверим!
telnet 0 5678
Trying 0.0.0.0...
Connected to 0.
Escape character is '^]'.
Отлично! Теперь мы можем перейти к протоколу.
Протокол
Формат сообщений
<параметры> — обязательные параметры
[параметры] — необязательные параметры
После каждой команды должен следовать перенос строки (\n, ^J, decimal 10). Символы, ASCII-коды которых меньше 32, а также символ "%" и ":" должны быть экранированы и превращены в %, где — символ с числовым значением, равным 64 + ASCII-код символа.
Символ "%" должен быть превращен в "%%", если он находится в ключевом слове. Символ ":" не экранируется если это разделитель между параметрами.
Сообщения
По причине того, что описание всех сообщений сделает статью огромной, рассмотрю только те, которые нам понадобятся.
Ключевое слово: %%>connect
Формат: %%>connect:<role>[:<id>][:<type>]
Направление: от внешнего модуля к серверу
Описание: Используется для того, что бы передать серверу информацию информацию о роли подключения. Первое сообщение, которое отсылается серверу после установки соединения. Команда может быть использована лишь один раз после соединения.
Сервер не присылает ответа на данный запрос. В случае ошибочного запроса, сервер закрывает соединение. Соединения с ролями play, record или playrec должны быть прикреплены за определенным каналом управления.
Параметры:
<role>
— роль подключения: global, channel, play, record, playrec
<id>
— id канала к которому подключен сокет
<type>
— тип канала
Ключевое слово: %%>install
Формат: %%>install:[<priority>]:<name>[:<filter-name>[:<filter-value>]]
Направление: от внешнего модуля к серверу
Описание: Запрос на установку обработчика сообщений (сигналов). Ответ на запрос приходит асинхронно.
Параметры:
<priority>
— приоритет. по умолчанию 100
<name>
— название сообщение (сигнала), обработчик на которое должен быть установлен
<filter-name>
— название переменой для фильтра
<filter-value>
— значение переменой для фильтра
Ключевое слово: %%<install
Формат: %%<install:<priority>:<name>:<success>
Направление: от сервера к внешнему модулю
Описание: Ответ на запрос для установки обработчика
Параметры:
<priority>
— приоритет с которым установлен обработчик
<name>
— название сообщение, обработчик на которое должен быть установлен
<success>
— boolean («true» or «false»)
Ключевое слово: %%>uninstall
Формат: %%>uninstall:<name>
Направление: от внешнего модуля к серверу
Описание: Запрос на удаление обработчика сообщений.
Параметры:
<name>
— название сообщение, обработчик на которое должен быть удален
Ключевое слово: %%<uninstall
Формат: %%<uninstall:<priority>:<name>:<success>
Направление: от сервера к внешнему модулю
Описание: Подтверждение на то, что обработчик сообщения был удален
Параметры:
<priority>
— приоритет с которым был установлен обработчик
<name>
— название сообщение, обработчик на которое должен быть удален
<success>
— boolean («true» or «false»)
Ключевое слово: %%>message
Формат: %%>message:<id>:<time>:<name>:<retvalue>[:<key>=<value>...]
Направление: от сервера к внешнему модулю
Описание: Запрос на обработку сообщения. Процесс Yate, который вызывает данное сообщение будет заблокирован, пока не получит ответ.
Параметры:
<id>
— уникальный ID сообщения, сгенерированный Yate-сервером
<time>
— timestamp, когда было сгенерировано сообщение
<name>
— имя сообщения (сигнала)
<retvalue>
— значение, которое будет возвращено по умолчанию
<key>=<value>
— данные, переданные для обработки, представленные в формате ключ-значение.
Ключевое слово: %%<message
Формат: %%<message:<id>:<processed>:[<name>]:<retvalue>[:<key>=<value>...]
Направление: от внешнего модуля к серверу
Описание: Сообщение ответ на %%>message. Когда сервер получает ответ, он изменяет указанные в сообщении значения и заканчивает обработку сообщения, либо продолжает ее.
Параметры:
<id>
— уникальный ID сообщения, который был получен при получении запроса на обработку
<processed>
— boolean. «true» — закончить обработку сообщения, «false» — передать на обработку в следующий модуль
<name>
— имя сообщения
<retvalue>
— возвращаемое значение
<key>=<value>
— новые данные в формате ключ-значение; что бы удалить значение укажите только ключ без знака равенства и значения ()
Ключевое слово: %%>message
Формат: %%>message:<id>:<time>:<name>:<retvalue>[:<key>=<value>...]
Направление: от внешнего модуля к серверу
Описание: Запрос от внешнего модуля на обработку сообщения. Ответ приходит асинхронно.
Параметры:
<id>
— уникальный ID сообщения, сгенерированный внешним модулем
<time>
— timestamp, когда было сгенерировано сообщение
<name>
— название сообщения (сигнала)
<retvalue>
— значение, которое будет возвращено по умолчанию
<key>=<value>
— данные, переданные для обработки, представленные в формате ключ-значение.
Ключевое слово: %%<message
Формат: %%<message:<id>:<processed>:[<name>]:<retvalue>[:<key>=<value>...]
Направление: от сервера к внешнему модулю
Описание: Сообщение ответ на %%>message. Содержит возвращаемое значение и параметры
Параметры:
<id>
— уникальный ID сообщения, который был получен при получении запроса на обработку
<processed>
— boolean. «true» — закончить обработку сообщения, «false» — передать на обработку в следующий модуль
<name>
— имя сообщения
<retvalue>
— возвращаемое значение
<key>=<value>
— данные в формате ключ-значение
Пишем свой модуль
Как видно из описанных сообщений, для общения нам нужен асинхронный клиент. Во время поисков в интернете в глаза кинулся Twisted. Решил я его немного пощупать. Итак создадим файл extmodule.py и добавим в него следующие строки:
#!/usr/bin/python import time from twisted.internet import reactor from twisted.internet.protocol import Protocol, ClientFactory class ExtModuleProtocol(Protocol): def connectionMade(self): #%%>connect:<role> self.transport.write("%%>connect:global\n") class ExtModuleFactory(ClientFactory): def buildProtocol(self, addr): return ExtModuleProtocol() def clientConnectionLost(self, connector, reason): print 'Lost connection. Reason:', reason time.sleep(2) connector.connect() def clientConnectionFailed(self, connector, reason): print 'Connection failed. Reason:', reason time.sleep(2) connector.connect() def main(): reactor.connectTCP("localhost", 5678, ExtModuleFactory()) reactor.run() if __name__ == '__main__': main()
Данным кодом мы осуществляем соединения с Yate-сервером и говорим Yate, что наш обработчик глобальный. После этого сервер ждет от нас сообщений. Т.к. сообщения должны быть определенного формата, напишем еще две функции: первая будет экранировать символы с кодом меньше 32, "%" и ":", а вторая возвращает их в нормальный вид.
def escape(str): esc_str = '' for c in str: if c == "%" or c == ":" or ord(c) < 32: esc_str += '%' + chr(ord(c) + 64) else: esc_str += c return esc_str def unescape(str): unesc_str = '' i = 0 while i < len(str): c = str[i] if c == "%": i += 1 c = chr(ord(str[i]) - 64) i += 1 unesc_str += c return unesc_str
Теперь нам нужно установить свои обработчики. Напишем функцию install_handler и добавим в connectionMade вызова данной функции.
def install_handler(self, message, priority=100, filter_name=None, filter_value=None): #%%>install:[<priority>]:<name>[:<filter-name>[:<filter-value>]] install_msg = "%%%%>install:%s:%s" % (priority, escape(message)) if filter_name: install_msg = "%s:%s" % (install_msg, escape(filter_name)) if filter_value: install_msg = "%s:%s" % (install_msg, escape(filter_value)) self.sendMessage(install_msg)
#!/usr/bin/python import time from twisted.internet import reactor from twisted.internet.protocol import Protocol, ClientFactory def escape(str): esc_str = '' for c in str: if c == "%" or c == ":" or ord(c) < 32: esc_str += '%' + chr(ord(c) + 64) else: esc_str += c return esc_str def unescape(str): unesc_str = '' i = 0 while i < len(str): c = str[i] if c == "%": i += 1 c = chr(ord(str[i]) - 64) i += 1 unesc_str += c return unesc_str class ExtModuleProtocol(Protocol): def sendMessage(self, msg): print 'sending message %s' % msg self.transport.write(msg) self.transport.write("\n") def connectionMade(self): #%%>connect:<role> self.sendMessage("%%>connect:global") self.install_handler('call.route', 100) self.install_handler('call.execute', 200) def install_handler(self, message, priority=100, filter_name=None, filter_value=None): #%%>install:[<priority>]:<name>[:<filter-name>[:<filter-value>]] install_msg = "%%%%>install:%s:%s" % (priority, escape(message)) if filter_name: install_msg = "%s:%s" % (install_msg, escape(filter_name)) if filter_value: install_msg = "%s:%s" % (install_msg, escape(filter_value)) self.sendMessage(install_msg) def dataReceived(self, data): print data class ExtModuleFactory(ClientFactory): def startedConnecting(self, connector): print 'Started to connect.' def buildProtocol(self, addr): print 'Connected.' return ExtModuleProtocol() def clientConnectionLost(self, connector, reason): print 'Lost connection. Reason:', reason time.sleep(2) connector.connect() def clientConnectionFailed(self, connector, reason): print 'Connection failed. Reason:', reason time.sleep(2) connector.connect() def main(): reactor.connectTCP("localhost", 5678, ExtModuleFactory()) reactor.run() if __name__ == '__main__': main()
Но нам нужно еще знать, что наш запрос был обработан. Для этого мы изменим функцию dataReceived. После запуска было выявлено, что dataReceived иногда получает сразу несколько сообщений. По данной причине класс ExtModuleProtocol немного изменился и теперь наследуется от LineReceiver, который разделяет сообщения по указанному dilemeter и передает их в метод lineReceived.
class ExtModuleProtocol(LineReceiver): delimiter = '\n' def sendMessage(self, msg): print 'sending message %s' % msg self.transport.write(msg) self.transport.write("\n") def connectionMade(self): #%%>connect:<role> self.sendMessage("%%>connect:global") self.install_handler('call.route', 100) self.install_handler('call.execute', 200) def install_handler(self, message, priority=100, filter_name=None, filter_value=None): #%%>install:[<priority>]:<name>[:<filter-name>[:<filter-value>]] install_msg = "%%%%>install:%s:%s" % (priority, escape(message)) if filter_name: install_msg = "%s:%s" % (install_msg, escape(filter_name)) if filter_value: install_msg = "%s:%s" % (install_msg, escape(filter_value)) self.sendMessage(install_msg) def process_request(self, request): pass def process_response(self, response): print "Response received: %s" % response data = response.split(":") data = [unescape(d) for d in data] msg_name = data.pop(0) # getting message name print 'Message name: %s. Data: %s' % (msg_name, data) # process received response according the message name def lineReceived(self, data): if not data.startswith("%%"): print "unknown message received. passing it" if data[2] == ">": self.process_request(data[3:]) else: self.process_response(data[3:])
#!/usr/bin/python # -*- coding: utf-8 -*- import time from twisted.internet import reactor from twisted.internet.protocol import ClientFactory from twisted.protocols.basic import LineReceiver def escape(str): esc_str = '' for c in str: if c == "%" or c == ":" or ord(c) < 32: esc_str += '%' + chr(ord(c) + 64) else: esc_str += c return esc_str def unescape(str): unesc_str = '' i = 0 while i < len(str): c = str[i] if c == "%": i += 1 c = chr(ord(str[i]) - 64) i += 1 unesc_str += c return unesc_str class ExtModuleProtocol(LineReceiver): delimiter = '\n' def sendMessage(self, msg): print 'sending message %s' % msg self.transport.write(msg) self.transport.write("\n") def connectionMade(self): #%%>connect:<role> self.sendMessage("%%>connect:global") self.install_handler('call.route', 100) self.install_handler('call.execute', 200) def install_handler(self, message, priority=100, filter_name=None, filter_value=None): #%%>install:[<priority>]:<name>[:<filter-name>[:<filter-value>]] install_msg = "%%%%>install:%s:%s" % (priority, escape(message)) if filter_name: install_msg = "%s:%s" % (install_msg, escape(filter_name)) if filter_value: install_msg = "%s:%s" % (install_msg, escape(filter_value)) self.sendMessage(install_msg) def process_request(self, request): pass def process_response(self, response): print "Response received: %s" % response data = response.split(":") data = [unescape(d) for d in data] msg_name = data.pop(0) # getting message key name print 'Message name: %s. Data: %s' % (msg_name, data) # process received response according to message name def lineReceived(self, data): if not data.startswith("%%"): print "unknown message received. passing it" if data[2] == ">": self.process_request(data[3:]) else: self.process_response(data[3:]) class ExtModuleFactory(ClientFactory): def startedConnecting(self, connector): print 'Started to connect.' def buildProtocol(self, addr): print 'Connected.' return ExtModuleProtocol() def clientConnectionLost(self, connector, reason): print 'Lost connection. Reason:', reason time.sleep(2) connector.connect() def clientConnectionFailed(self, connector, reason): print 'Connection failed. Reason:', reason time.sleep(2) connector.connect() def main(): reactor.connectTCP("localhost", 5678, ExtModuleFactory()) reactor.run() if __name__ == '__main__': main()
Осталось научить наш внешний модуль генерировать свои запросы и обрабатывать запросы от сервера. Для первого напишем метод enqueue, который будет генерировать указанное сообщение и отдавать его yate.
def enqueue(self, message_name, retvalue, **kwargs): # %%>message:<id>:<time>:<name>:<retvalue>[:<key>=<value>...] print "Enqueueing message '%s' with params: %s" % (message_name, kwargs) params = [] for key, value in kwargs.items() : if value: params.append(escape("%s=%s" % (key, value))) else: params.append(escape("%s" % key)) msg_id = uuid.uuid4().hex msg = "%%%%>message:%(id)s:%(time)s:%(name)s:%(retvalue)s:%(params)s" % { "id": msg_id, "time": int(time.time()), "name": escape(message_name), "retvalue": escape(retvalue), "params": ":".join(params) } self.sendMessage(msg) return msg_id
Данный код генерирует сообщение и возвращает его ID. Теперь получив ответ от сервера, мы можем ориентироваться на этот ID. Пример:
Enqueueing message ‘call.route’ with params: {‘target’: ‘sip:test@127.0.0.1’}
sending message %%>message:3f5e754aaca145b0b257b070e6566cb2:1383161706:call.route:None:target=sip%ztest@127.0.0.1
…
Message name: message. Data: [‘3f5e754aaca145b0b257b070e6566cb2‘, ‘false’, ‘call.route’, ‘None’, ‘target=sip’, ‘test@127.0.0.1’, ‘handlers=javascript’, ’15,cdrbuild’, ’50,fileinfo’, ’90,subscription’, ‘100,sip’, ‘100,iax’, ‘100,regfile’, ‘100,jingle’, ‘100,regexroute’, ‘100,analog’, ‘100,register’, ‘100,sig’, ‘100’]
Что бы обрабатывать запросы от сервера допишем метод process_request.
def process_request(self, request): print "Request received: %s" % request data = request.split(":") data = [unescape(d) for d in data] print "Data: %s" % data # process received request according the message name
#!/usr/bin/python # -*- coding: utf-8 -*- import time import uuid from twisted.internet import reactor from twisted.internet.protocol import ClientFactory from twisted.protocols.basic import LineReceiver def escape(str): esc_str = '' for c in str: if c == "%" or c == ":" or ord(c) < 32: esc_str += '%' + chr(ord(c) + 64) else: esc_str += c return esc_str def unescape(str): unesc_str = '' i = 0 while i < len(str): c = str[i] if c == "%": i += 1 c = chr(ord(str[i]) - 64) i += 1 unesc_str += c return unesc_str class ExtModuleProtocol(LineReceiver): delimiter = '\n' def sendMessage(self, msg): print 'sending message %s' % msg self.transport.write(msg) self.transport.write("\n") def enqueue(self, message_name, retvalue, **kwargs): # %%>message:<id>:<time>:<name>:<retvalue>[:<key>=<value>...] print "Enqueueing message '%s' with params: %s" % (message_name, kwargs) params = [] for key, value in kwargs.items() : if value: params.append(escape("%s=%s" % (key, value))) else: params.append(escape("%s" % key)) msg_id = uuid.uuid4().hex msg = "%%%%>message:%(id)s:%(time)s:%(name)s:%(retvalue)s:%(params)s" % { "id": msg_id, "time": int(time.time()), "name": escape(message_name), "retvalue": escape(retvalue), "params": ":".join(params) } self.sendMessage(msg) return msg_id def connectionMade(self): # %%>connect:<role> self.sendMessage("%%>connect:global") self.install_handler('call.route', 10) self.install_handler('call.execute', 200) self.call_route_msg_id = self.enqueue("call.route", "None", target="sip:test@127.0.0.1") def install_handler(self, message, priority=100, filter_name=None, filter_value=None): # %%>install:[<priority>]:<name>[:<filter-name>[:<filter-value>]] install_msg = "%%%%>install:%s:%s" % (priority, escape(message)) if filter_name: install_msg = "%s:%s" % (install_msg, escape(filter_name)) if filter_value: install_msg = "%s:%s" % (install_msg, escape(filter_value)) self.sendMessage(install_msg) def process_request(self, request): print "Request received: %s" % request data = request.split(":") data = [unescape(d) for d in data] print "Data: %s" % data # process received request according the message name def process_response(self, response): print "Response received: %s" % response data = response.split(":") data = [unescape(d) for d in data] msg_name = data.pop(0) # getting message key name print 'Message name: %s. Data: %s' % (msg_name, data) # process received response according the message name def lineReceived(self, data): if not data.startswith("%%"): print "unknown message received: %s. passing it" % data if data[2] == ">": self.process_request(data[3:)) else: self.process_response(data[3:]) class ExtModuleFactory(ClientFactory): def startedConnecting(self, connector): print 'Started to connect.' def buildProtocol(self, addr): print 'Connected.' return ExtModuleProtocol() def clientConnectionLost(self, connector, reason): print 'Lost connection. Reason:', reason time.sleep(2) connector.connect() def clientConnectionFailed(self, connector, reason): print 'Connection failed. Reason:', reason time.sleep(2) connector.connect() def main(): reactor.connectTCP("localhost", 5678, ExtModuleFactory()) reactor.run() if __name__ == '__main__': main()
Теперь мы можем запустить сервер, внешний модуль и посмотреть что происходит. Осталось научить наш модуль отвечать на запросы сервера. Для этого напишем метод answer и изменим метод process_request, добавив небольшую логику.
def answer(self, msg_id, msg_name, processed, retvalue, payload): # %%<message:<id>:<processed>:[<name>]:<retvalue>[:<key>=<value>...] print "Answering the server request" params = [] for key, value in payload.items(): if value: params.append(escape("%s=%s" % (key, value))) else: params.append(escape("%s" % key)) msg = "%%%%<message:%(id)s:%(processed)s:%(name)s:%(retvalue)s:%(params)s" % { "id": escape(msg_id), "processed": processed and "true" or "false", "name": escape(msg_name), "retvalue": escape(retvalue), "params": ":".join(params) } self.sendMessage(msg) def process_request(self, request): print "Request received: %s" % request data = request.split(":") data = [unescape(d) for d in data] print "Data: %s. Processing message." % data if data[0] == "message": msg_id = data[1] msg_name = data[3] msg_data = dict(d.split("=") for d in data[5:]) print "MSG Data: %s" % msg_data print "ID: %s. Name: %s" % (msg_id, msg_name) if msg_name == "call.route" and msg_data.get('called', '') == "123": self.answer(msg_id, msg_name, True, "sip/sip:test@127.0.0.1", {"param1": "value1"}) self.answer(msg_id, msg_name, False, "", {})
#!/usr/bin/python # -*- coding: utf-8 -*- import time import uuid from twisted.internet import reactor from twisted.internet.protocol import ClientFactory from twisted.protocols.basic import LineReceiver def escape(str): esc_str = '' for c in str: if c == "%" or c == ":" or ord(c) < 32: esc_str += '%' + chr(ord(c) + 64) else: esc_str += c return esc_str def unescape(str): unesc_str = '' i = 0 while i < len(str): c = str[i] if c == "%": i += 1 c = chr(ord(str[i]) - 64) i += 1 unesc_str += c return unesc_str class ExtModuleProtocol(LineReceiver): delimiter = '\n' def sendMessage(self, msg): print 'sending message %s' % msg self.transport.write(msg) self.transport.write("\n") def enqueue(self, message_name, retvalue, **kwargs): # %%>message:<id>:<time>:<name>:<retvalue>[:<key>=<value>...] print "Enqueueing message '%s' with params: %s" % (message_name, kwargs) params = [] for key, value in kwargs.items(): if value: params.append(escape("%s=%s" % (key, value))) else: params.append(escape("%s" % key)) msg_id = uuid.uuid4().hex msg = "%%%%>message:%(id)s:%(time)s:%(name)s:%(retvalue)s:%(params)s" % { "id": msg_id, "time": int(time.time()), "name": escape(message_name), "retvalue": escape(retvalue), "params": ":".join(params) } self.sendMessage(msg) return msg_id def connectionMade(self): # %%>connect:<role> self.sendMessage("%%>connect:global") self.install_handler('call.route', 10) self.install_handler('call.execute', 200) self.call_route_msg_id = self.enqueue("call.route", "None", target="sip:test@127.0.0.1") def install_handler(self, message, priority=100, filter_name=None, filter_value=None): # %%>install:[<priority>]:<name>[:<filter-name>[:<filter-value>]] install_msg = "%%%%>install:%s:%s" % (priority, escape(message)) if filter_name: install_msg = "%s:%s" % (install_msg, escape(filter_name)) if filter_value: install_msg = "%s:%s" % (install_msg, escape(filter_value)) self.sendMessage(install_msg) def answer(self, msg_id, msg_name, processed, retvalue, payload): # %%<message:<id>:<processed>:[<name>]:<retvalue>[:<key>=<value>...] print "Answering the server request" params = [] for key, value in payload.items(): if value: params.append(escape("%s=%s" % (key, value))) else: params.append(escape("%s" % key)) msg = "%%%%<message:%(id)s:%(processed)s:%(name)s:%(retvalue)s:%(params)s" % { "id": escape(msg_id), "processed": processed and "true" or "false", "name": escape(msg_name), "retvalue": escape(retvalue), "params": ":".join(params) } self.sendMessage(msg) def process_request(self, request): print "Request received: %s" % request data = request.split(":") data = [unescape(d) for d in data] print "Data: %s. Processing message." % data if data[0] == "message": msg_id = data[1] msg_name = data[3] msg_data = dict(d.split("=") for d in data[5:]) print "MSG Data: %s" % msg_data print "ID: %s. Name: %s" % (msg_id, msg_name) if msg_name == "call.route" and msg_data.get('called', '') == "123": self.answer(msg_id, msg_name, True, "sip/sip:test@127.0.0.1", {"param1": "value1"}) self.answer(msg_id, msg_name, False, "", {}) def process_response(self, response): print "Response received: %s" % response data = response.split(":") data = [unescape(d) for d in data] msg_name = data.pop(0) # getting message key name print 'Message name: %s. Data: %s' % (msg_name, data) # process received response according the message name def lineReceived(self, data): if not data.startswith("%%"): print "unknown message received: %s. passing it" % data if data[2] == ">": self.process_request(data[3:]) else: self.process_response(data[3:]) class ExtModuleFactory(ClientFactory): def startedConnecting(self, connector): print 'Started to connect.' def buildProtocol(self, addr): print 'Connected.' return ExtModuleProtocol() def clientConnectionLost(self, connector, reason): print 'Lost connection. Reason:', reason time.sleep(2) connector.connect() def clientConnectionFailed(self, connector, reason): print 'Connection failed. Reason:', reason time.sleep(2) connector.connect() def main(): reactor.connectTCP("localhost", 5678, ExtModuleFactory()) reactor.run() if __name__ == '__main__': main()
Метод answer может вернуть True, чем даст знать Yate, что сообщение не надо больше обрабатывать или False, тогда сервер передаст сообщение в следующий обработчик. На этом я закончу. Всем спасибо за внимание.
P.S. Код предоставлен для ознакомительных целей, из-за чего не вылизывался и не стремился к совершенству. Всем спасибо за внимание.
Ссылки:
yate.null.ro/pmwiki/index.php?n=Main.Extmodule
yate.null.ro/docs/extmodule.html
ссылка на оригинал статьи http://habrahabr.ru/post/199082/
Добавить комментарий