YATE. О внешних модулях

в 21:55, , рубрики: ip-телефония, python, twisted, yate, телефония, метки: , , , ,

В данном посте мы с Вами настроим YATE на работу с внешними модулями, разберем протокол общения модуля с сервером, а также напишем небольшой модуль на python.

YATE. О внешних модулях

Если Вам интересно, прошу под кат.

Настройка

Для того что бы научить 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:globaln")

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)
extmodule.py

#!/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:])
extmodule.py

#!/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
extmodule.py

#!/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, "", {})
extmodule.py

#!/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

Автор: Kolyanu4

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js