Asterisk est une voiture de Formule 1, pas un bus ordinaire

Asterisk - fi, c'est de mauvaises manières



Bonjour chers lecteurs de cette merveilleuse ressource. Par tradition, je suis un lecteur habr de longue date, mais ce n'est que maintenant que j'ai décidé de publier un message. Qu'est-ce qui vous a poussé à écrire? Honnêtement, je ne me connais pas. Soit les articles tirés sur les performances de FreeSWITCH / Yate / 3CX / etc par rapport à Asterisk, soit les problèmes réels et réels de l'architecture de ce dernier, soit, peut-être, le désir de faire quelque chose d'unique.



Et étonnamment, dans le premier cas, en règle générale, ils comparent doux et chaleureux, pour ainsi dire, FreeSWITCH / Yate / etc et FreePBX. Oui, FreePBX. Ce n'est pas une faute de frappe. De plus, il est intéressant de noter que dans toutes les comparaisons, il y a souvent un astérisque dans la configuration par défaut. Eh bien, vous savez, cette configuration est chargée avec tous les modules disponibles, la courbe du plan de numérotation (en quelque sorte FreePBX contribue) et un tas d'autres biais. Quant aux plaies génériques d'Asterisk - oui, objectivement, leur chariot et leur petit chariot.



Que faire de tout ça? Brisez les stéréotypes et réparez les traumatismes à la naissance. C'est ce que nous allons faire.



Nous croisons un hérisson avec un serpent



Beaucoup de débutants sont mal à l'aise en regardant la syntaxe pour décrire le plan de numérotation dans Asterisk, et certains justifient sérieusement le choix d'un autre serveur de téléphonie précisément par la nécessité d'écrire le plan de numérotation sous la forme dans laquelle il se trouve par défaut. Par exemple, pelleter du XML multiligne est le summum du confort. Oui, il est possible d'utiliser LUA / AEL, ce qui est bien. Mais personnellement, je classerais cela comme un inconvénient, et en particulier en ce qui concerne 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- ;
  • la possibilité d'utiliser un langage de script assez simple, extensible et très flexible pour créer des applications VoIP;
  • élargi les possibilités d'intégration avec des systèmes externes à partir d'applications VoIP.


Comme n'importe qui, mais j'aime toujours tout.



gestionnaire entièrement
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



All Articles