利用mod_lua實現動態配置FreeSwtich

        FreeSwtich默認的配置體系是XML文件,修改配置後需要reloadxml生效。這對於大型線上系統,是不可接受的。FreeSwitch本身提供了幾種不同的動態配置接口,比如mod_xml_curl,本文介紹利用mod_lua動態配置FS的方法。

        通過mod_lua模塊,你可以利用lua實現動態配置。綁定一個腳本處理XML請求,就像mod_xml_curl那樣,動態地把所需要的XML片段餵給FS,指示FS的執行。實現應用與數據的分離。FreeSWITCH需要某種XML信息時,它查詢註冊的鉤子,調用指定的腳本。在腳本中,你可以爲所欲爲,只是不要忘記返回XML就行了。

配置mod_lua

        FreeSwitch啓動時會加載conf/autoload_configs/lua.conf.xml配置。

        以下是一個極簡配置實例:

<configuration name="lua.conf" description="LUA Configuration">
  <settings>
    <param name="xml-handler-script" value="dp.lua"/>
    <param name="xml-handler-bindings" value="dialplan"/>
  </settings>
</configuration>

  編輯lua.conf.xml之後,FreeSWITCH控制檯命令reloadxml不能識別 "xml-handler-script" 參數,你必須重啓FS纔會生效 FreeSWITCH.


可配置標籤

        對於"xml-handler-bindings"參數,可配置值有:"dialplan" "directory" "configuration" "dialplan|directory|configuration"

  • FS會向腳本傳遞一個XML_REQUEST對象,它包含以下成員:section、tag_name、key_name和key_value。它可用於查詢模塊配置、目錄信息、撥號方案,還可能傳遞一個'params'屬性,攜帶一個 event對象
  • 腳本執行完畢之後,你所設置的XML_STRING對象將返回給FS,無論你在裏面設置了什麼內容

        在Lua腳本中,可以訪問XML_REQUEST對象獲取必要的信息。比如:

freeswitch.consoleLog("notice", "SECTION " .. XML_REQUEST["section"] .. "\n")

模塊配置

        如果mod_lua加載時有如下配置:

<configuration name="lua.conf" description="LUA Configuration">
  <settings>
    <param name="xml-handler-script" value="configuration.lua"/>
    <param name="xml-handler-bindings" value="configuration"/>
  </settings>
</configuration>

那麼之後加載的模塊,可以把mod_lua當成配置服務器,需要配置信息時,調用綁定的lua實時查詢。

        查詢模塊配置的lua腳本中,隱含一個XML_REQUEST對象,其典型的屬性值:

  • key_value = 'iax.conf'|'event_socket.conf'|sofia.conf'|...
  • key_name = 'name'
  • section = 'configuration'
  • tag_name = 'configuration'

 

'params' event object
acl.conf no N/A
event_socket.conf no N/A
post_load_switch.conf no N/A
sofia.conf yes
Event-Name: REQUEST_PARAMS
Core-UUID: 0f8afb73-2183-a1e2-2316-71053c746130
FreeSWITCH-Hostname: hostname
FreeSWITCH-IPv4: 192.168.1.12
FreeSWITCH-IPv6: %3A%3A1
Event-Date-Local: 2010-08-06%2014%3A04%3A38
Event-Date-GMT: Fri,%2006%20Aug%202010%2018%3A04%3A38%20GMT
Event-Date-Timestamp: 1281117878629975
Event-Calling-File: sofia.c
Event-Calling-Function: config_sofia
Event-Calling-Line-Number: 2637
switch.conf no N/A
syslog.conf no N/A

目錄(directory)

啓動初始化時

 

        在啓動初始化過程中,會讀取profile配置,爲網關和別名域讀取目錄信息。

        在模塊加載時,Lua腳本中的XML_REQUEST對象的屬性值:

  • key_value = ''
  • key_name = ''
  • section = 'directory'
  • tag_name = ''

此外,"params" 攜帶一個event 對象。

        對sofia中的每個profile,directory都會讀取一次。The directory is read once for each profile in the sofia configuration


    注意上面的變量值有“空字符串”,不是nil


Params event事件

對於External profile:

Event-Name: REQUEST_PARAMS
Core-UUID: <uuid>
FreeSWITCH-Hostname: hostname
FreeSWITCH-IPv4: 192.168.1.11
FreeSWITCH-IPv6: %3A%3A1
Event-Date-Local: 2010-08-06%2014%3A04%3A40
Event-Date-GMT: Fri,%2006%20Aug%202010%2018%3A04%3A40%20GMT
Event-Date-Timestamp: 1281117880813532
Event-Calling-File: sofia.c
Event-Calling-Function: config_sofia
Event-Calling-Line-Number: 3481
purpose: gateways
profile: external

重要字段:

purpose: gateways

profile: external

...

FreeSWITCH-Hostname: hostname

FreeSWITCH-IPv4: 192.168.1.11

對於 Internal profile 

Event-Name: REQUEST_PARAMS
Core-UUID: <uuid>
FreeSWITCH-Hostname: hostname
FreeSWITCH-IPv4: 192.168.1.11
FreeSWITCH-IPv6: %3A%3A1
Event-Date-Local: 2010-08-06%2014%3A04%3A41
Event-Date-GMT: Fri,%2006%20Aug%202010%2018%3A04%3A41%20GMT
Event-Date-Timestamp: 1281117881174514
Event-Calling-File: sofia.c
Event-Calling-Function: config_sofia
Event-Calling-Line-Number: 3481
purpose: gateways
profile: internal

 

重要字段:

purpose: gateways

profile: internal

...

FreeSWITCH-Hostname: hostname

FreeSWITCH-IPv4: 192.168.1.11

 

網絡列表

        啓動初始化過程中,還會從directory中讀取網絡列表,但這時的XML_REQUEST屬性值有些差異:

  • key_value = <name-of-domain> (比如 192.168.1.11)
  • key_name = 'name'
  • section = 'directory'
  • tag_name = 'domain'

Event 報文

Event-Name: REQUEST_PARAMS
Core-UUID: <uuid>
FreeSWITCH-Hostname: hostname
FreeSWITCH-IPv4: 192.168.1.11
FreeSWITCH-IPv6: %3A%3A1
Event-Date-Local: 2010-08-06%2014%3A04%3A43
Event-Date-GMT: Fri,%2006%20Aug%202010%2018%3A04%3A43%20GMT
Event-Date-Timestamp: 1281117883173795
Event-Calling-File: sofia_reg.c
Event-Calling-Function: sofia_reg_parse_auth
Event-Calling-Line-Number: 1797
action: sip_auth
sip_profile: internal
sip_user_agent: IP-Phone-V3.2.49T5.13%20-%20G729
sip_auth_username: 1000
sip_auth_realm: 192.168.1.11
sip_auth_nonce: <auth_nonce_uuid>
sip_auth_uri: sip%3A192.168.1.11
sip_contact_user: 1000
sip_contact_host: 192.168.88.202
sip_to_user: 1000
sip_to_host: 192.168.1.11
sip_to_port: 5060
sip_from_user: 1000
sip_from_host: 192.168.1.11
sip_from_port: 5060
sip_request_host: 192.168.1.11
sip_auth_qop: auth
sip_auth_cnonce: 829326
sip_auth_nc: 00000001
sip_auth_response: <auth_response - md5sum?>
sip_auth_method: REGISTER
key: id
user: 1000
domain: 192.168.1.11
ip: 192.168.88.202

重要字段:

domain: 192.168.1.11

purpose: network-list

...

FreeSWITCH-Hostname: hostname

FreeSWITCH-IPv4: 192.168.1.11

 

查找用戶

        FreeSWITCH以特定域標籤查找用戶,這時XML_REQUEST對象的屬性值:

  • key_value = '<name-of-domain>'
  • key_name = 'name'
  • section = 'directory'
  • tag_name = 'domain'

and 'params' will have an event object

註冊時(REGISTER)

Event 報文:

Event-Name: REQUEST_PARAMS
Core-UUID: <uuid>
FreeSWITCH-Hostname: hostname
FreeSWITCH-IPv4: 192.168.1.11
FreeSWITCH-IPv6: %3A%3A1
Event-Date-Local: 2010-08-06%2014%3A04%3A43
Event-Date-GMT: Fri,%2006%20Aug%202010%2018%3A04%3A43%20GMT
Event-Date-Timestamp: 1281117883173795
Event-Calling-File: sofia_reg.c
Event-Calling-Function: sofia_reg_parse_auth
Event-Calling-Line-Number: 1797
action: sip_auth
sip_profile: internal
sip_user_agent: IP-Phone-V3.2.49T5.13%20-%20G729
sip_auth_username: 1000
sip_auth_realm: 192.168.1.11
sip_auth_nonce: <auth_nonce_uuid>
sip_auth_uri: sip%3A192.168.1.11
sip_contact_user: 1000
sip_contact_host: 192.168.88.202
sip_to_user: 1000
sip_to_host: 192.168.1.11
sip_to_port: 5060
sip_from_user: 1000
sip_from_host: 192.168.1.11
sip_from_port: 5060
sip_request_host: 192.168.1.11
sip_auth_qop: auth
sip_auth_cnonce: 829326
sip_auth_nc: 00000001
sip_auth_response: <auth_response - md5sum?>
sip_auth_method: REGISTER
key: id
user: 1000
domain: 192.168.1.11
ip: 192.168.88.202

重要字段:

key: id

user: 1000

domain: 192.168.1.11

...

action: sip_auth

sip_profile: internal

FreeSWITCH-Hostname: hostname

FreeSWITCH-IPv4: 192.168.1.11

ip: 192.168.88.202

呼出 (INVITE)

Event 報文

Event-Name: REQUEST_PARAMS
Core-UUID: <uuid>
FreeSWITCH-Hostname: hostname
FreeSWITCH-IPv4: 192.168.1.11
FreeSWITCH-IPv6: %3A%3A1
Event-Date-Local: 2010-08-06%2016%3A28%3A08
Event-Date-GMT: Fri,%2006%20Aug%202010%2020%3A28%3A08%20GMT
Event-Date-Timestamp: 1281126488274011
Event-Calling-File: sofia_reg.c
Event-Calling-Function: sofia_reg_parse_auth
Event-Calling-Line-Number: 1797
action: sip_auth
sip_profile: internal
sip_user_agent: IP-Phone-V3.2.49T5.13%20-%20G729
sip_auth_username: 1001
sip_auth_realm: 192.168.1.11
sip_auth_nonce: 1fe3d1fa-a199-11df-b392-b105e374638e
sip_auth_uri: sip%3A1000%40192.168.1.11
sip_contact_user: 1001
sip_contact_host: 192.168.88.99
sip_to_user: 1000
sip_to_host: 192.168.1.11
sip_to_port: 5060
sip_from_user: 1001
sip_from_host: 192.168.1.11
sip_from_port: 5060
sip_request_user: 1000
sip_request_host: 192.168.1.11
sip_auth_qop: auth
sip_auth_cnonce: 10560d0
sip_auth_nc: 00000001
sip_auth_response: b99d1213022480a2b6c4e14432661821
sip_auth_method: INVITE
key: id
user: 1001
domain: 192.168.1.11
ip: 192.168.88.99

重要字段:

key: id

user: 1001

domain: 192.168.1.11

...

ip: 192.168.88.99

FreeSWITCH-Hostname: hostname

FreeSWITCH-IPv4: 192.168.1.11

action: sip_auth

sip_profile: internal

 

呼入

Event 報文

Event-Name: REQUEST_PARAMS
Core-UUID: <uuid>
FreeSWITCH-Hostname: hostname
FreeSWITCH-IPv4: 192.168.1.11
FreeSWITCH-IPv6: %3A%3A1
Event-Date-Local: 2010-08-06%2016%3A28%3A09
Event-Date-GMT: Fri,%2006%20Aug%202010%2020%3A28%3A09%20GMT
Event-Date-Timestamp: 1281126489462522
Event-Calling-File: mod_dptools.c
Event-Calling-Function: user_outgoing_channel
Event-Calling-Line-Number: 2662
as_channel: true
action: user_call
key: id
user: 1000
domain: 192.168.1.11

重要字段:

key: id

user: 1000

domain: 192.168.1.11

...

FreeSWITCH-Hostname: hostname

FreeSWITCH-IPv4: 192.168.1.11

as_channel: true

Event-Calling-Function: user_outgoing_channel

 

撥號方案

配置

<configuration name="lua.conf" description="LUA Configuration">
  <settings>
    <param name="xml-handler-script" value="dp.lua"/>
    <param name="xml-handler-bindings" value="dialplan"/>
  </settings>
</configuration>

Lua 腳本 dp.lua示例

-- params is the event passed into us we can use params:getHeader to grab things we want.
io.write("TEST\n" .. params:serialize("xml") .. "\n");

mydialplan = [[
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="freeswitch/xml">
    <section name="dialplan" description="LUA Dial Plan For FreeSwitch">
        <context name="default">
              <extension name="lua_dp_ext">
                        <condition field="destination_number" expression="^10086$">
                        <action application="sleep" data="1000 "/>
                        <action application="answer"/>
                        <action application="playback" data="ivr/8000/ivr-welcome_to_freeswitch.wav"/>
                    </condition>
               </extension>
         </context>
     </section>
</document>
]]

XML_STRING = mydialplan
-- comment the following line for production:
freeswitch.consoleLog("notice", "Debug XML:\n" .. XML_STRING .. "\n")

同一功能的另一個版本:

-- LUA dialplan
  
DIALPLAN = {}
freeswitch.consoleLog("notice", "SECTION " .. XML_REQUEST["section"] .. "\n")

table.insert(DIALPLAN, [[<?xml version="1.0" encoding="UTF-8" standalone="no"?>]])
table.insert(DIALPLAN, [[<document type="freeswitch/xml">]])
table.insert(DIALPLAN, [[    <section name="dialplan" description="LUA Dial Plan For FreeSwitch">]])
table.insert(DIALPLAN, [[        <context name="default">]])
table.insert(DIALPLAN, [[              <extension name="lua_dp_ext">o]])
table.insert(DIALPLAN, [[                        <condition field="destination_number" expression="^10086$">]])
table.insert(DIALPLAN, [[                        <action application="sleep" data="1000 "/>]])
table.insert(DIALPLAN, [[                        <action application="answer"/>]])
table.insert(DIALPLAN, [[                        <action application="playback" data="ivr/8000/ivr-welcome_to_freeswitch.wav"/>]])
table.insert(DIALPLAN, [[                    </condition>]])
table.insert(DIALPLAN, [[               </extension>]])
table.insert(DIALPLAN, [[         </context>]])
table.insert(DIALPLAN, [[     </section>]])
table.insert(DIALPLAN, [[</document>]])

XML_STRING =  table.concat(DIALPLAN, "\n")

mydialplan = [[

<document type="freeswitch/xml">
    <section name="dialplan" description="LUA Dial Plan For FreeSwitch">
        <context name="default">
              <extension name="lua_dp_ext">
                        <condition field="destination_number" expression="^10086$">
                        <action application="sleep" data="1000 "/>
                        <action application="answer"/>
                        <action application="playback" data="ivr/8000/ivr-welcome_to_freeswitch.wav"/>
                    </condition>
               </extension>
         </context>
     </section>
</document>
]]


-- comment the following line for production:
freeswitch.consoleLog("notice", "Debug XML:\n" .. XML_STRING .. "\n")

 

通過profile設置跳轉

        上述實例,展示了lua是怎樣生成XML片段並餵給FS執行的。但是拼接XML的過程顯得有些繁瑣。有沒有辦法簡單一點呢?Dialplan的描述本質上無非就是一組的Action加上參數而已。

         lua_dialplan_hunt的實現中,允許使用ACTIONS表來傳遞這些信息,換句話說,在腳本里,構建一個ACTIONS table並傳回給FS就可以了。那麼我們需要怎樣做呢?

profile設置

        編輯sip profile配置文件,比如說freeswitch/sip_profiles/internal.xml,修改以下兩個屬性:

    <param name="context" value="luadp.lua"/>
    <param name="dialplan" value="LUA"/>

用戶目錄配置

        修改用戶的context屬性爲“luadp.lua”,比如說freeswitch/directory/default/1000.xml:

     <variable name="user_context" value="luadp.lua"/>

添加lua腳本

        在腳本目錄下,添加一個名爲luadp.lua的腳本,內容如下:

-- LUA dialplan
  
ACTIONS = {}
freeswitch.consoleLog("notice", "from your script")

table.insert(ACTIONS, {"sleep", "1000"})
table.insert(ACTIONS, "answer")
table.insert(ACTIONS, {"playback", "ivr/8000/ivr-welcome_to_freeswitch.wav"})
table.insert(ACTIONS, {"log", "NOTICE after your script"})

        這個實例是前面dialplan的第三個版本,腳本邏輯是不是簡單得多了。


Lua調用XML dialplan:

 

table.insert(ACTIONS, {"transfer", "123 XML some-context"})

 XML 調用Lua dialplan:

<action application="transfer" data="123 LUA some-dialplan.lua"/>

Not found

        如果LUA 收到一個dialplan查詢請求,但沒有相應處理邏輯,那麼應當返回 "not found" 結果(如下所示),不能返回空串:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="freeswitch/xml">
  <section name="result">
    <result status="not found" />
  </section>
</document>

如果返回一個空串,FS將處理報錯:

[ERR] switch_xml.c:1534 switch_xml_locate() Error[[error near line 1]: root tag missing]

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章