Wednesday, October 28, 2009

Using mod_xml_curl In FreeSWITCH - How Hard Can It Be? (Part 2)

In my previous post I talked about my desire to go through the exercise of setting up xml_curl to pull user directory information out of a database. I left off having just verified that I was receiving proper CGI parameters. I first tested a dialplan call using portaudio to call the MOH extension:

pa call 9999

This yielded good results: my Perl script dumped the CGI params to a text file and I got a good feel for what data looks like coming from an xml_curl request.

Next I needed a way to send information back to xml_curl/FreeSWITCH. I recalled some talk about sending a generic "no found" response. Sure enough, the wiki confirms it. I added a simple function to my perl script that would reply to xml_curl and would dump the "no found" message to my log file also:


sub return_not_found() {
print "Content-Type: text/xml\n
<?xml version=\"1.0\"?>
<document type=\"freeswitch/xml\">
<section name=\"result\">
<result status=\"not found\" />
</section>
</document>
";

## dump to file for checking
print FILEOUT "Content-Type: text/xml\n
<?xml version=\"1.0\"?>
<document type=\"freeswitch/xml\">
<section name=\"result\">
<result status=\"not found\" />
</section>
</document>
";
}


A simple call to &return_not_found() will gracefully handle the situation where my CGI script has no useful information to send back. At this point I did a quick test and had the CGI script send the no found info back on any xml_curl request just to make sure it was working. It did.

Next up: having a phone register so that I could see what a 'directory' request looks like. In this case you could use any SIP device that tries to REGISTER, even another FS box with a gateway defined. I found a Snom 300 that I have in production and added a second identity. (The first identity is being used in production so I didn't want to mess with it.) I added "Tony Romo" as the second identity on the phone, with the account and auth. user as "1100" and the appropriate password from my database file. I watched on the FS CLI to make sure that the phone tried to register, then I went to my script's log file to see what the request looked like. It was a bit shorter than the 'dialplan' request but it definitely had useful information:


bless({
".charset" => "ISO-8859-1",
".fieldnames" => {},
".parameters" => [
"hostname",
"section",
"tag_name",
"key_name",
"key_value",
"Event-Name",
"Core-UUID",
"FreeSWITCH-Hostname",
"FreeSWITCH-IPv4",
"FreeSWITCH-IPv6",
"Event-Date-Local",
"Event-Date-GMT",
"Event-Date-Timestamp",
"Event-Calling-File",
"Event-Calling-Function",
"Event-Calling-Line-Number",
"action",
"key",
"user",
"domain",
],
"Core-UUID" => ["9b4e5fe9-9bc9-4bed-915a-3c277cbd760b"],
"Event-Calling-File" => ["mod_voicemail.c"],
"Event-Calling-Function" => ["resolve_id"],
"Event-Calling-Line-Number" => [1278],
"Event-Date-GMT" => ["Thu, 29 Oct 2009 17:00:15 GMT"],
"Event-Date-Local" => ["2009-10-29 10:00:15"],
"Event-Date-Timestamp" => ["1256835615546212"],
"Event-Name" => ["GENERAL"],
"FreeSWITCH-Hostname" => ["michael-collinss-macbook-pro.local"],
"FreeSWITCH-IPv4" => ["10.15.0.136"],
"FreeSWITCH-IPv6" => ["::1"],
action => ["message-count"],
domain => ["10.15.0.136"],
escape => 1,
hostname => ["michael-collinss-macbook-pro.local"],
key => ["id"],
key_name => ["name"],
key_value => ["10.15.0.136"],
section => ["directory"],
tag_name => ["domain"],
user => [1100],
}, "CGI")


The values I was particularly interested in were 'section' and 'user' which had "directory" and "1100" as their respective values. I knew I was in business! I added a little bit of logic to my script so that I could differentiate between a 'dialplan' and 'directory' request. The relevant tidbits look like this:

my $q = CGI->new;
my $section = $q->{'section'}->[0];

if ( $section eq 'directory' ) {
SNIP
} elsif ( $section eq 'dialplan' ) {
SNIP
} else {
## We don't anything other than dialplan or directory, so if we're here then return a no found
open(FILEOUT,'>','/tftpboot/cgidat.txt');
print FILEOUT "\n\n--- UNKNOWN REQUEST ---\n";
print FILEOUT "\nSection: $section\n\n";
print FILEOUT dump($q);
print FILEOUT "\n = = = = = = = = = = = = = = = = = = = = = = =\n";
&return_not_found();
close(FILEOUT);
}

Okay, so at this point I have a basic script that knows the difference between a 'directory' and a 'dialplan' request and can return a no found message if need be. Now I needed to tackle the database stuff.

DBI: Perl's Handy Dandy DataBase Interface
When you have a Perl question there's always one place you can go: the CPAN. I had used DBI before, although I can't recall exactly when/where/how. But I did remember that it wasn't really all that difficult for simple stuff. After some digging around I found this code:


use DBI;
my $dbh;
$dbh = DBI->connect('dbi:Pg:dbname=xml_curl_test;host=127.0.0.1', 'xmlcurl', 'freeswitch',
{ RaiseError => 1, AutoCommit => 1});


In short, this creates a db handler object ($dbh) upon which you can do SQL operations. Let's take a quick look at part of the connection string:

DBI->connect('dbi:Pg:dbname=xml_curl;host=127.0.1','xmlcurl','freeswitch'

The connect uses the format of DSN, user, password. My DSN is for 'Pg' (PostgreSQL) with a dbname of xml_curl and the local host. My user is 'xmlcurl' with a password of 'freeswitch'. The other stuff - { RaiseError => 1, AutoCommit => 1} - is recommended in the DBI docs so I roll with it.

My SQL call looks like this:

my $sql = "SELECT lastname, firstname, mailbox, password, context FROM users WHERE username = '$user'";
my $row = $dbh->selectrow_hashref($sql);


This makes the SQL query and puts a single row of results into a hash reference $row. From here I can access the results like this:

$row->{'context'};
$row->{'password'};


I like this because I don't have to remember what column number each field is, instead I simply reference the name of the column. In my case I'm assuming that I have only a single row of results; you probably want to do some error checking on a production system.

The last order of business is to format some XML and give it back. Since I'm just doing a POC I decided to cheat and use a string with some placeholders and then swap them out. Here's my template definition:


## User template variable; replace the ALL CAPS VALUES with stuff pulled from database
use constant USER_TEMPLATE =>
' <user id="USER_ID">
<params>
<param name="password" value="USER_PASS"/>
<param name="vm-password" value="USER_PASS"/>
</params>
<variables>
<variable name="toll_allow" value="domestic,international,local"/>
<variable name="accountcode" value="USER_ID"/>
<variable name="user_context" value="USER_CONTEXT"/>
<variable name="effective_caller_id_name" value="USER_FIRSTNAME USER_LASTNAME"/>
<variable name="effective_caller_id_number" value="USER_ID"/>
<variable name="outbound_caller_id_name" value="$${outbound_caller_name}"/>
<variable name="outbound_caller_id_number" value="$${outbound_caller_id}"/>
<variable name="callgroup" value="techsupport"/>
</variables>
</user>
';


The items in all caps will get replaced with some simple regular expressions. Stay tuned for Part 3 where I show you the entire script. It's not pretty but it is functional and it took me all of an hour to get everything together. It's taken much longer to blog it all and figure out how to format XML code so that it looks right. ;)

1 comment:

  1. Hi Mike,
    I am doing a requirement analysis on a project where we are looking into options for handling the dialplan, either using mod_xml_curl or some custom C/C++. We are looking to hit at least 100 calls per second, do you think mod_xml_curl can handle that and we save time on custom implementation? Your reply on this is sincerely appreciated.

    D

    ReplyDelete