Asterisk - fi, es sind schlechte Manieren
Hallo liebe Leser dieser wunderbaren Ressource. Aus Tradition bin ich ein langjähriger Habr-Leser, aber erst jetzt habe ich beschlossen, einen Beitrag zu schreiben. Was hat Sie tatsächlich zum Schreiben veranlasst? Ehrlich gesagt kenne ich mich nicht. Entweder die gezeichneten Artikel über die Leistung von FreeSWITCH / Yate / 3CX / etc im Vergleich zu Asterisk oder die realen, realen Probleme der Architektur des letzteren oder vielleicht der Wunsch, etwas Einzigartiges zu tun.
Und überraschenderweise vergleichen sie im ersten Fall in der Regel sozusagen weich und warm mit FreeSWITCH / Yate / etc und FreePBX. Ja, FreePBX. Dies ist kein Tippfehler. Und es ist interessant, dass in allen Vergleichen häufig ein Sternchen in der Standardkonfiguration vorhanden ist. Nun, Sie wissen, diese Konfiguration ist mit allen verfügbaren Modulen, einer Wählplankurve (FreePBX-Art trägt dazu bei) und einer Reihe anderer Verzerrungen geladen. Was die generischen Asterisk-Wunden betrifft - ja, objektiv ihre Kutsche und ihren kleinen Karren.
Was tun mit all dem? Brechen Sie Stereotypen und reparieren Sie das Geburtstrauma. Das werden wir tun.
Wir überqueren einen Igel mit einer Schlange
Vielen Neulingen ist es unangenehm, die Syntax zur Beschreibung des Wählplans in Asterisk zu betrachten, und einige rechtfertigen die Wahl eines anderen Telefonieservers ernsthaft damit, dass der Wählplan in der Form geschrieben werden muss, in der er standardmäßig ist. Wie das Schaufeln von mehrzeiligem XML ist die Höhe des Komforts. Ja, es ist möglich, LUA / AEL zu verwenden, was gut ist. Ich persönlich würde dies jedoch als Nachteil einstufen, insbesondere in Bezug auf pbx_lua.
, — . , . , , , , , .., .. , , . – , .
, Asterisk' pbx_lua, Yate , FreeSWITCH , "overhead" . , , , . :
- Asterisk, . ARI , , 12- . , - 1.8/1.6, 1.4, .
- Lua — , . , , .
- Lunapark — github', voip-.
Lunapark . , AMI- FastAGI, . , ARI AGI AMI .
: ? Asterisk REST Interface, ! . , ARI : , , , "" , WebSockets , , , XML/JSON — . , , , . — . — - , .
? FastAGI-, , pbx_lua . Asterisk’ , FastAGI- AMI-. , FastAGI-, , , NewChannel. ARI, , stasis' ARI .
Lunapark , . "shared data". , . — , , - .
?
— ? , , . , . .
:
[test]
exten => _XXX/102,1,Hangup()
exten => _XXX,1,Dial(SIP/${EXTEN})
, 102. , , extended CallerID . , , CallerIDName , , regexp, . , , , :
[test]
exten => _XXX/102,1,Hangup()
; CallerIDName
exten => _XXX,1,ExecIf($[ "${CALLERID(name)}" == "Vasya" ]?Hangup())
;
exten => _XXX,n,ExecIf($[ "${CHANNEL(state)}" != "Ring" ]?Hangup())
;
exten => _XXX,n,ExecIf($[ "${CUT(CUT(CHANNEL,-,1),/,2)}" == "333" ]?Hangup())
exten => _XXX,n,Dial(SIP/${EXTEN})
, , Hangup', extensions.conf Goto, GoSub, Macro , , Local.
— .
:
${Exten}:match('%d%d%d')
and
(
${CallerIDNum}:match('201') or
${CallerIDName}:match('Vasya') or
${State}:lower() ~= 'ring' or
${Channel}:match('^[^/]+/([^%-]+)') == '333'
) => Hangup();
${Exten}:match('%d%d%d') => Dial {callee = ('SIP/%s'):format(${Exten})};
, , . , regexp' , , , .
, .
Lunapark pbx_lua. . ${...} , ('...'). .
, :
-- Exten = 123
-- Sate = Ring
-- CallerIDNum = 100
-- CallerIDName = Test
-- Channel = SIP/100-00000012c
if ('123'):match('%d%d%d') and
(
('100'):match('201') or
('Test'):match('Vasya') or
('Ring'):lower() ~= 'ring' or
('SIP/100-00000012c'):match('^[^/]+/([^%-]+)') == '333'
) then
Hangup()
end
if ('123'):match('%d%d%d') then
Dial {callee = ('SIP/%s'):format(('123'))}
end
fmt syntax :
local fmt = function(str, tab)
return (str:gsub('(%${[^}{]+})', function(w)
local mark = w:sub(3, -2)
return (mark:gsub('(.+)',function(v)
local out = tab[v] or v
return ("('%s')"):format(out)
end))
end))
end
local syntax = function(str)
return (str:gsub('([^;]+)=>([^;]+)',function(p,r)
return ([[
if %s then
%s
end
]]):format(p,r)
end))
end
, . — , . routes.
local routes = function(...)
local conf, content = ...
local f, err = io.open(conf, "r")
if io.type(f) ~= 'file' then
log.warn(err) -- LOG Lunapark'
return ""
else
content = f:read('*all')
end
f:close() return content
end
: Lunapark . — Lunapark handler'. , FastAGI- AMI .
, AMI — , AMI-, AMI . , extensions.conf.
[default]
exten => _[hit],1,NoOp()
exten => _.,n,Wait(5)
exten => _.,1,AGI(agi://127.0.0.1/${EXTEN}${IF($[ "X${PRMS}" != "X" ]?"?${PRMS}")})
Wait(5) FastAGI-, , Redirect default ${EXTEN}.
, Lunapark', FastAGI-.
-- rules
local rules = routes('routes.conf')
-- ,
-- HUP/QUIT
ami.removeEvents('*')
--
ami.addEvents {
['newchannel'] = function(e)
-- , users
if (e['Context'] and e['Context']:match('users')) and e['Exten'] then
-- , , FastAGI
local step
-- FatsAGI
local count = 0
--
local code, err = loadstring(syntax(fmt(rules,e)))
-- ,
if type(code) == 'function' then
-- FastAGI
setfenv(code,setmetatable({indexes = {}},{__index = function(t,k)
--
return coroutine.wrap(
function(...)
local prms = {} -- FastAGI
local owner = t --
local event = e -- event
local thread = coroutine.running() -- ID
-- URI
for p,v in pairs({...}) do
if type(v) == 'table' then
for key, val in pairs(v) do
table.insert(prms,("%s=%s"):format(key,val))
end
else
table.insert(prms,("%s=%s"):format(p,v))
end
end
-- FastAGI
if step then
--
local last = ("%s"):format(step)
-- UserEvent .
-- indexes( )
--
table.insert(owner['indexes'],ami.addEvent('UserEvent',function(evt)
-- AGIStatus
-- ,
if (evt['Channel'] and evt['Channel'] == event['Channel'])
and
(evt['UserEvent'] and evt['UserEvent']:match('AGIStatus'))
and
(evt['Script'] and evt['Script'] == last)
then
--
--
--
if owner['indexes'][count] == thread then
if coroutine.status(thread) ~= 'dead' then
coroutine.resume(thread)
end
end
end
end,thread))
-- FastAGI
step = k
--
coroutine.yield()
else -- FastAGI
local index -- Hangup
-- FastAGI
step = k
-- Hangup
--
index = ami.addEvent('Hangup',function(evt)
if evt['Channel'] and evt['Channel'] == event['Channel'] then
-- Hangup
ami.removeEvent('Hangup',index)
--
for _,v in pairs(owner['indexes']) do
ami.removeEvent('UserEvent',v)
end
--
owner = nil
end
end,thread)
end
-- AMI
ami.setvar{
Value = table.concat(prms,'&'),
Channel = event['Channel'],
Variable = 'PRMS'
}
-- AGI- default
ami.redirect{
Exten = k,
Priority = 1,
Channel = event['Channel'],
Context = 'default'
}
--
count = count + 1
end)
end}))()
else
-- -
log.warn(err)
end
end
end
}
, , , . , . , . , , , ..
, . — , redirect . , , FastAGI-. Lunapark UserEvent FastAGI- — . default , , PRMS.
, redirect' handler, AGI . Hangup() Dial(). .
function Hangup(...)
local app, channel = ... -- pbx_lua
app.verbose(('The Channel %s does not match by routing rules'):format(channel.get('CHANNEL')))
app.hangup()
end
function Dial(...)
local app, channel = ...
local leg = app.agi.params['callee'] or ''
app.verbose(('Trying to make a call from %s to %s'):format(
channel.get('CALLERID(num)'),
leg:match('^[^/]+/([^%-]+)'))
)
app.dial(leg)
end
, —
, . ?
- , ;
- VoIP-. Queue, , asterisk';
- , VoIP-, asterisk' Mediahub, VoIP- ;
- die Fähigkeit, eine ziemlich einfache, erweiterbare und sehr flexible Skriptsprache zum Erstellen von VoIP-Anwendungen zu verwenden;
- erweiterte die Möglichkeiten der Integration mit externen Systemen aus VoIP-Anwendungen.
Wie jeder andere auch, aber ich mag immer noch alles.
local fmt = function(str, tab)
return (str:gsub('(%${[^}{]+})', function(w)
local mark = w:sub(3, -2)
return (mark:gsub('(.+)',function(v)
local out = tab[v] or v
return ("('%s')"):format(out)
end))
end))
end
local syntax = function(str)
return (str:gsub('([^;]+)=>([^;]+)',function(p,r)
return ([[
if %s then
%s
end
]]):format(p,r)
end))
end
local routes = function(...)
local conf, content = ...
local f, err = io.open(conf, "r")
if io.type(f) ~= 'file' then
log.warn(err) -- LOG Lunapark'
return ""
else
content = f:read('*all')
end
f:close() return content
end
-- rules
local rules = routes('routes.conf')
-- ,
-- HUP/QUIT
ami.removeEvents('*')
--
ami.addEvents {
['newchannel'] = function(e)
-- , users
if (e['Context'] and e['Context']:match('users')) and e['Exten'] then
local step -- , , FastAGI
local count = 0 -- FatsAGI
--
local code, err = loadstring(syntax(fmt(rules,e)))
-- ,
if type(code) == 'function' then
-- FastAGI
setfenv(code,setmetatable({indexes = {}},{__index = function(t,k)
--
return coroutine.wrap(
function(...)
local prms = {} -- FastAGI
local owner = t --
local event = e -- event
local thread = coroutine.running() -- ID
-- URI
for p,v in pairs({...}) do
if type(v) == 'table' then
for key, val in pairs(v) do
table.insert(prms,("%s=%s"):format(key,val))
end
else
table.insert(prms,("%s=%s"):format(p,v))
end
end
-- FastAGI
if step then
--
local last = ("%s"):format(step)
-- UserEvent .
-- indexes( )
--
table.insert(owner['indexes'],ami.addEvent('UserEvent',function(evt)
-- AGIStatus
-- ,
if (evt['Channel'] and evt['Channel'] == event['Channel'])
and
(evt['UserEvent'] and evt['UserEvent']:match('AGIStatus'))
and
(evt['Script'] and evt['Script'] == last)
then
--
--
--
if owner['indexes'][count] == thread then
if coroutine.status(thread) ~= 'dead' then
coroutine.resume(thread)
end
end
end
end,thread))
-- FastAGI
step = k
--
coroutine.yield()
else -- FastAGI
local index -- Hangup
-- FastAGI
step = k
-- Hangup
--
index = ami.addEvent('Hangup',function(evt)
if evt['Channel'] and evt['Channel'] == event['Channel'] then
-- Hangup
ami.removeEvent('Hangup',index)
--
for _,v in pairs(owner['indexes']) do
ami.removeEvent('UserEvent',v)
end
--
owner = nil
end
end,thread)
end
-- AMI
ami.setvar{
Value = table.concat(prms,'&'),
Channel = event['Channel'],
Variable = 'PRMS'
}
-- AGI- default
ami.redirect{
Exten = k,
Priority = 1,
Channel = event['Channel'],
Context = 'default'
}
--
count = count + 1
end)
end}))()
else
-- -
log.warn(err)
end
end
end
}
function Hangup(...)
local app, channel = ... -- pbx_lua
app.verbose(('The Channel %s does not match by routing rules'):format(channel.get('CHANNEL')))
app.hangup()
end
function Dial(...)
local app, channel = ...
local leg = app.agi.params['callee'] or ''
app.verbose(('Trying to make a call from %s to %s'):format(
channel.get('CALLERID(num)'),
leg:match('^[^/]+/([^%-]+)'))
)
app.dial(leg)
end