Mod erlang event

From FreeSWITCH Wiki
Jump to: navigation, search

Contents

Introduction

mod_erlang_event is a derivative of mod_event_socket that sends events and receives commands in erlang's binary term format. This allows FreeSWITCH™ to behave like just another erlang node (albeit a hidden one). It uses the ei library to handle encoding/decoding the erlang binary format and the ei_connect functions to handle the network connections. (Erlang is a programming language and runtime system. It supports hot swapping, so that code can be changed without stopping a system. )

The module operates in two modes, inbound and outbound.

Inbound

In inbound mode your erlang application communicates with FreeSWITCH™ by sending messages. These messages can be used for example to check status, execute applications, originate calls and register to receive events.

Outbound

Outbound mode is used by erlang dialplan tool to allow your erlang application to handle a particular call. When the dialplan application is executed, the call is first parked. Messages relating to the call are then sent from FreeSWITCH™ to the specified erlang process on the specified node. The process can either be a pre-defined one (a registered process), a dynamic one (a spawned process) or a dynamically returned process (send a message to a registered process, it returns a pid that can either be newly spawned or already existent).

Status

Maturing, I'll try to provide backwards compatibility for 2 freeswitch releases if I have to make an API change.

TODO

  • Ability to have multiple event handler pids per node, each with their own event subscriptions?
  • Figure out how to handle premature exits of spawned outbound processes
  • Allow log/event handler and handle call processes to be registered processes or pids
  • Check pids obtained via 'get_pid' to determine the node they're on in case they're not the node we requested a pid from (load balancing)
  • Investigate supporting starting a gen_server/gen_fsm using proc_lib:spawn/4 and enter_loop
  • Make datastructures more proplist friendly

Compiling

You need erlang (and its development headers) installed to compile this module. You must have Erlang development files installed *before* running the FreeSWITCH ./configure (or rerun ./configure after having Erlang installed). The FreeSWITCH™ configure script now checks for the erlang requirements and configures the Makefile appropriately.

Windows

As of SVN r13766, the module is known to work on windows (but it's not terribly well tested). Unfortunately the ei.lib that ships with windows releases of erlang isn't suitable for being used inside a DLL due to issues with thread local storage. The work around for this is to build it yourself following the README.win32 instructions included with the erlang source distribution and after you've run configure edit the eidefs.mk and remove -DUSE_DECLSPEC_THREAD from the THR_DEFS variable. Then you can just run `make release` in the libs/erl_interface directory and then edit mod_erlang_event's project options to point at the erl_interface/obj/win32/ei.lib for its linker flags. You'll probably also have to fix the include path in the compiler flags, too.

To add the erlang module to the freeswitch solution, right click on the solution and choose Add->Existing Project and browse to its .vcproj file. Once it's added edit the solution's properties, go to dependencies and for the module, make it depend on the freeswitch core lib.

ei binaries for compiling the module against R13B01 can be found here. The release libs are in obj/win32 and the debug ones are in obj.debug/win32.

OSX

If after compiling the module, loading it in FreeSWITCH gives you an error like

Error Loading module /usr/local/freeswitch/mod/mod_erlang_event.so
**dlopen(/usr/local/freeswitch/mod/mod_erlang_event.so, 6): Symbol not found: ___erl_errno_place

make sure you've built both Erlang AND FreeSWITCH with the same architecture. Erlang defaults to building 32 bit (even on a 64 bit kernel for some reason) and FreeSWITCH seems to default to 64 bit. The easiest solution is to rebuild erlang with the --enable-darwin-64bit argument to configure. Note, however, that 'make clean' doesn't work right for Erlang and its best to have a fresh source tree if recompiling to a new architecture.

Configuration

The configuration is very similar to the one for event_socket, but there are some different parameters. Here's an example config file:

<configuration name="erlang_event.conf" description="Erlang Socket Client">
  <settings>
    <param name="listen-ip" value="0.0.0.0"/>
    <param name="listen-port" value="8031"/>
    <param name="nodename" value="freeswitch"/>
    <param name="cookie" value="ClueCon"/>
    <param name="shortname" value="true"/>
    <param name="apply-inbound-acl" value="lan"/>
    <param name="encoding" value="string"/>
  </settings>
</configuration>

Note that it listens on a different port than the event socket, and instead of a password we set a cookie (used for inter-erlang node authentication). Nodename is just a name of the current node, it's used so that other erlang nodes know how to address a node.

In addition, the 'shortname' parameter determines the form of the node's name. If shortname is enabled, the FQDN of the IP the module is configured to listen on is truncated at the first dot. So, for a module listening on the IP that resolves to example.com and the nodename set to 'freeswitch', with shortname enabled, the full nodename is 'freeswitch@example' with shortname off, it instead is 'freeswitch@example.com'. Erlang nodes with a shortname can only talk to other nodes with a shortname, and the same thing applies for nodes with long names. See here for more information. To force the nodename, specify the whole name (eg 'freeswitch@example.com') in the 'nodename' tag; mod_erlang_event will not try to guess what to call the node.

The 'encoding' parameter indicates if you'd prefer events to be encoded as erlang binaries or as erlang strings. Binaries are faster and consume less memory, but strings can be easier to work with.

Example Inbound Connection

Here's a brief example of using the erlang shell 'erl' to communicate with FreeSWITCH™ using mod_erlang_event. We assume freeswitch running locally, with the cookie set to 'ClueCon', the nodename set to 'freeswitch' and shortnames enabled. The hostname is 'example.com', so the short nodename is freeswitch@example.

$erl -sname test -setcookie ClueCon
Erlang (BEAM) emulator version 5.6.4 [source] [async-threads:0] [hipe] [kernel-poll:false]
Eshell V5.6.4 (abort with ^G) (test@example)1>{foo, freeswitch@example} ! {api, status, ""}. {api,status,[]}

We've just sent the API status command to FreeSWITCH™. Note how the connection was negotiated automagically. FreeSWITCH™ will send the reply back to us asynchronously (all erlang messages are asynchronous), so we have to explicitly receive it:

(test@example)2> receive X -> X after 1000 -> timeout end.
{ok,"Content-Type: text/html\n\nUP 0 years, 0 days, 0 hours, 0 minutes, 35 seconds, 692 milliseconds, 193 microseconds\n0 session(s) since startup\n0 session(s) 0/30\n"}

We tried to receive anything in the current processes' mailbox with a 1000 millisecond timeout that will return the atom timeout as the result of the expression instead. However, there was indeed something waiting for us in the mailbox; the result of the API command we just made.

Now, lets send an invalid command and listen for the reply:

(test@example)3> {foo, freeswitch@example} ! {api, wtf, ""}, receive Y -> Y after 1000 -> timeout end.
{error,"wtf: Command not found!\n"}

So, as we can see, the command 'wtf' isn't valid. Note that in both cases, a 2 element tuple {} is returned. Depending on the result of the command, the first element is either the atom 'ok' or 'error'. Note that we had to use a different variable name to receive to this time, if we used X again, erlang would search the processes' mailbox for an event that matched what X was bound. You could also do f(X) to forget the value of X in the shell.

We'll do a bgapi command as a final example:

(test@example)4> {foo, freeswitch@example} ! {bgapi, status, ""}, receive Z -> Z after 1000 -> timeout end.
{ok,"191d2b07-58ac-dd11-829b-000f1f68e553"}

Note that all we got was job UUID of the background command. We have to receive one more time to get the actual command output:

(test@example)5> receive A -> A after 1000 -> timeout end.
{bgok,"191d2b07-58ac-dd11-829b-000f1f68e553",
     "Content-Type: text/html\n\nUP 0 years, 0 days, 1 hour, 11 minutes, 8 seconds, 654 milliseconds, 138
        microseconds\n0 session(s) since startup\n0 session(s) 0/30\n"}

Note that the status of the command is indicated by bgok/bgerror and the second element of the tuple is the job uuid we got as the initial reply. In addition to this directed reply, a normal BACKGROUND_JOB event is also fired, which you could alternately choose to receive.

Example Outbound Connection

Here's a simple example using outbound mode. First, add an entry in your dialplan to redirect an inbound call to the erlang dialplan application. You can do so by creating the file freeswitch/conf/dialplan/default/123456_erlang.xml with the following content:

<include>
  <extension name="to_erlang">
    <condition field="destination_number" expression="^123456$"> 
      <action application="erlang" data="myhandler mynode@myserver"/>
    </condition>
  </extension>
</include>

This will send calls for extension 123456 to a registered process called myhandler on the erlang node mynode@myserver. Replace myserver with your hostname, using either short or long form depending on your setting in the mod_erlang_event config file. Next, write some erlang code to handle the call. Save this in the file myhandler.erl:

-module(myhandler).

-export([start/0,run/0,launch/1]).

start()->
  %% start our handler process running
  Pid = spawn(?MODULE,run,[]),
  %% register it with the same name as the module - myhandler
  register(?MODULE,Pid).

run()->
  %% wait for messages from FreeSWITCH
  receive
    {call,Data}->
      %% a new call is starting, find the UUID
      %% _Rest is a list of all the channel variable in the form {"<name>","<value"}
      {event, [UUID | _Rest]} = Data,
      error_logger:info_msg("myhandler ~p: new call received, UUID is ~p~n",[self(), UUID]),
      run();
    {call_event,Data} ->
      %% we've got an event for a call we've been notified of already
      {event, [UUID | Rest]} = Data,
      %% find out the name of the event
      Name = proplists:get_value("Event-Name", Rest),
      error_logger:info_msg("myhandler ~p: UUID ~p, event ~p~n",[self(), UUID,Name]),
      run();
    {get_pid, UUID, Ref, Pid} ->
      %% request from FreeSWITCH for an outbound process to handle call at 'UUID'
      NewPid = spawn(?MODULE, run, []),
      error_logger:info_msg("myhandler ~p: request to spawn new handler process, returning ~p~n", [self(), NewPid]),
      Pid ! {Ref, NewPid},
      run()
  end.

launch(Ref) ->
  %% rpc call to a function to return a new outbound call pid
  NewPid = spawn(?MODULE, run, []),
  error_logger:info_msg("myhandler ~p: launch request, returning ~p~n", [self(), NewPid]),
  {Ref, NewPid}.

And now run it:

$ erlc myhandler.erl
$ erl -sname mynode -setcookie ClueCon
Erlang (BEAM) emulator version 5.5.5 [source] [async-threads:0] [hipe] [kernel-poll:false]

Eshell V5.5.5  (abort with ^G)
(mynode@myserver)1> myhandler:start().
true

Now dial extension 123456 and see what happens at the erlang console:

(mynode@myserver)2>
=INFO REPORT==== 23-Jan-2009::11:59:38 ===
myhandler: new call received, UUID is "4f77b818-e945-11dd-a442-9fe384e7e5a2"

=INFO REPORT==== 23-Jan-2009::11:59:39 ===
myhandler: UUID "4f77b818-e945-11dd-a442-9fe384e7e5a2", event "CHANNEL_PROGRESS"

=INFO REPORT==== 23-Jan-2009::11:59:39 ===
myhandler: UUID "4f77b818-e945-11dd-a442-9fe384e7e5a2", event "CHANNEL_PROGRESS_MEDIA"

=INFO REPORT==== 23-Jan-2009::11:59:39 ===
myhandler: UUID "4f77b818-e945-11dd-a442-9fe384e7e5a2", event "CHANNEL_PARK"

We've received all the events up to CHANNEL_PARK. Now hang up the call, since we didn't add any code to answer it or do anything more interesting:

=INFO REPORT==== 23-Jan-2009::11:59:43 ===
myhandler: UUID "4f77b818-e945-11dd-a442-9fe384e7e5a2", event "CHANNEL_HANGUP"

To answer the call, execute applications and hang up, just as you would in the dialplan, you can send messages to freeswitch just as with inbound mode.

If you wanted to send events to a dynamic process instead of a registered one, you can instead do:

<action application="erlang" data="myhandler:launch mynode@myserver"/>

This will make an RPC call to myhandler:launch with a single argument, a unique reference. The function is expected to return a tuple of the form {Ref, NewPid}. NewPid should be a spawned process or a newly launched gen_server or something, Ref is the original reference passed in.

<action application="erlang" data="myhandler:! mynode@server"/>

If the string after the : is a '!', then mod_erlang_event knows you want to send a new process request to the registered process 'myhandler' which will return a pid that all events for that call will be sent to. The message the registered process receives is of the form:

{get_pid, UUID, Ref, Pid}

Where Ref is a unique reference used to identify this request, UUID is the call's UUID and Pid is the process to send the response to. The expected response is of the form:

{Ref, NewPid}

Where Ref is the original reference passed with get_pid and NewPid is the pid you'd like FreeSWITCH™ to send the events to.14877

Warning

The old new_pid message, which didn't include the call's UUID is deprecated in favor of get_pid as of SVN revision 14877 (09/15/09)

 

The module also supports true spawn/4 behaviour, but it turned out not to be so useful, so the rpc:call functionality above replaced it.

The myhandler example above supports all 3 outbound methods.

API

The api is very similar to the event socket, just expressed in erlang terms. You can send any of these terms by using the ! operator as above.

api

Send an API command. Examples:

{api, strftime, "%Y"}
{api, status, ""}
{api, originate, "sofia/mydomain.com/ext@yourvsp.com 1000"}

Note that the 3rd element of the tuple is currently mandatory. I may make it optional.

The result of the API call is sent as a message to the process that made the api call. The format of the reply is a tuple of the form {ok|error, "some reply"} as seen above.

bgapi

Same as the api command, but is non blocking. The sending process gets 2 messages, the message indicating the event was accepted, and then sometime later the actual result of the api command. See the example above.

register_event_handler

Send the atom register_event_handler to register the process that sent this as the process to receive all subscribed events. You need to use the events command to indicate what events to receive, none are subscribed by default. Events are received in the form:

{event, [(UniqueID|'undefined'),
         {EventHeaderKey, EventHeaderValue},
         {EventHeaderKey2, EventHeaderValue2},
         ...]}

This being, a tuple with the first element being the atom event followed by a variable length list of {Key, Value} tuples, the first value of which is the call's unique-id, or if the event doesn't relate to a call, the atom 'undefined'. This first element of the list is to make it easy to determine which call, if any, an event relates to without iterating through the entire list.

If you send this command again, the new sending process becomes the one the events are sent to.

event

Subscribe to an event. Examples:

{foo, freeswitch@example} ! {event, 'ALL'}
{event, 'CUSTOM', 'conference::maintenance'}
{event, 'CHANNEL_CREATE', 'CHANNEL_DESTROY', 'CUSTOM', 'conference::maintenance', 'sofia::register', 'sofia::expire'}

Note that atoms beginning with uppercase letters or containing colons need to be quoted in single quotes.

nixevent

Same syntax as event. Does the inverse.

noevents

Just send the atom noevents to disable all events.

register_log_handler

Send the atom register_log_handler to make the current process the one to send log messages to. Logs are received in the format:

{log, [{level, LogLevel},
       {text_channel, TextChannel},
       {file, FileName},
       {func, FunctionName},
       {line, LineNumber},
       {data, "Log message"}]}

Logs at DEBUG level by default.

Sending this command again changes the process log messages are sent to to the sending process.

set_log_level

Change the log level. Valid levels are defined switch_types.h. Example:

{set_log_level, info}
{set_log_level, error}

nolog

Send nolog to disable logging.

exit

Send exit to close the connection.

sendevent

{sendevent, 'NOTIFY', [{"profile", "internal"}, {"event-string","check-sync;reboot=false"}, {"user", "false"},
{"host", "192.168.10.4"}, {"content-type", "application/simple-message-summary"}]}

sendmsg

{sendmsg, "d9189508-7caf-dd11-829b-000f1f68e553", [{"call-command", "hangup"}, {"hangup-cause", "16"}]}

UUID can be a binary or a string.

getpid

Send getpid to receive {ok,Pid} where Pid is the fake erlang process id on the FreeSWITCH™ side. This is helpful if you want to link to the process so that, for example, FreeSWITCH™ can notice that your log handler process exited.

handlecall

{handlecall, "129d1446-0063-122c-15aa-001a923f6a0f", mycallhandler}
{handlecall, "129d1446-0063-122c-15aa-001a923f6a0f"}

Send handlecall to attach an outbound call handler to the specified UUID where mycallhandler is a registered process name. FreeSWITCH™ will then send events related to that call to the registered process on the node that sent the handlecall message. The event messages sent are the same as using outbound mode. Use this for example if you have originated a call from FreeSWITCH™ using inbound mode but want to handle it specifically. UUID can be a binary or a string. If you omit the registered process name and just send {handlecall, UUID} the call's events will be sent to the process that send the message.

XML search bindings

This module also supports mod_xml_curl style bindings to allow FreeSWITCH™ to fetch configuration/directory/dialplan/etc from Erlang. Unlike mod_xml_curl, and the other modules with this functionality, however, the bindings are dynamic, not statically configured. To register the current process send a {bind, <BindType>} message to mod_erlang_event, where BindType is an atom of one of the binding types supported (see the mod_xml_curl documentation for these). After you do this, you will receive messages of the type:

{fetch, <Section>, <Tag>, <Key>, <Value>, <FetchID>, <Params>}

where Section is an atom describing the binding type, FetchID is a UUID associated with the fetch request and Params is a list of key/value tuples with the parameters for this request.

To tell the switch to take some action, send back a reply of the format:

{fetch_reply, <FetchID>, <XML>}

where FetchID is the ID you received in the request and XMLString is the XML reply you want to send. FetchID and XML can be binaries or strings.

The binding is automatically removed when the process or the entire node exits or disconnects. Multiple bindings for one section type are supported as of SVN r16697, the first one to respond wins.

Console Commands

There are only 2 right now:

erlang listeners - list all nodes connected and how many outbound sessions each has
erlang sessions <nodename> - lists all the outbound sessions for the specified node

Feel free to suggest any others that might be useful.

Debugging

If you wish to see all the erlang terms sent and received from the module, add #define EI_DEBUG to src/include/switch_am_config.h and do a make clean; make; make install in the mod_erlang_event directory. Now every erlang message will be printed at DEBUG level to the logfile and to the console if you enable debug messages at the console.

freeswitch.erl

In the mod_erlang_event source directory, there's an erlang file called freeswitch.erl. It's a a module to ease dealing with the above API. The module is fairly well documented and exposes most of the API documented here. It will do all the low level send/recv stuff for you, so you can do stuff like

(test@example)3> freeswitch:api(freeswitch@example,status).

And the return value of that function call will be the result of the api command. The module also makes it easy to do bgapi commands effectively, as well as set up XML search bindings, event listeners and log handlers.