Джим Меггелен - Asterisk™: будущее телефонии Второе издание
В конце сценария AGI результаты тестирования записываются в STDERR, который должен быть выведен в консоли Asterisk.
Итак, при написании AGI-программ на Perl необходимо помнить следующее:
• Должен быть активирован строгий контроль выполнения правил языка программирования с помощью команды use strict[102].
• Должна быть отключена буферизация вывода через задание $|=1.
• Данные, поступающие от Asterisk, принимаются с помощью цикла
while(<STDIN>).
• Значения записываются командой print.
• Для записи отладочной информации в консоль Asterisk используется команда print STDERR.
Библиотека AGI для Perl
Тем, кто собирается создавать собственные сценарии AGI на языке Perl, вероятно, будет интересен модуль на Perl - Asterisk::AGI, написанный Джеймсом Головичем (James Golovich), который можно найти по адресу http://asterisk.gnuinter.net. Модуль Asterisk::AGI еще больше упрощает написание сценариев AGI на Perl.
Создание сценариев AGI на PHP
Мы обещали обсудить несколько языков программирования, поэтому пойдем дальше и рассмотрим, как выглядит сценарий AGI на PHP. Основные правила программирования AGI те же, изменился только язык программирования. В данном примере мы напишем сценарий AGI для загрузки из Интернета сводки погоды и предоставления вызывающему абоненту информации о температуре, направлении и скорости ветра.
#!/usr/bin/php -q
<?php
Первая строка указывает системе использовать для выполнения сценария интерпретатор PHP. Опция -q отключает HTML-сообщения об ошибках. Необходимо убедиться в отсутствии дополнительных строк между первой строкой и открывающим тегом PHP, поскольку это собьет Asterisk с толку.
# внесите соответствующие изменения для получения
# данных по интересующему вас городу
# полный список городов США можно найти по адресу
# http://www.nws.noaa.gov/data/current_obs/
$weatherURL="http://www.nws.noaa.gov/data/current_obs/KMDQ.xml"; Тем самым вы указываете сценарию AGI, где можно получить информацию о текущих погодных условиях. В данном примере предоставляются данные для Хантсвилла, штат Алабама. Вы можете свободно посетить указанный сайт, на котором найдете полный список станций по всем Соединенным Штатам Америки1.
# не допускайте, чтобы этот сценарий выполнялся дольше 60 с set_time_limit(60);
Здесь мы указываем PHP, что данная программа не должна выполняться более 60 с. Таким образом, сценарий будет гарантированно завершен, если по какой-то причине время его выполнения превысит 60 с.
# отключить буферизацию вывода ob_implicit_flush(false);
Эта команда отключает буферизацию вывода, то есть все данные будут отправляться в интерфейс AGI немедленно и не станут накапливаться в буфере.
# отключите сообщения об ошибках, поскольку, скорее всего,
# они будут пересекаться с сообщениями интерфейса AGI error_reporting(0);
Эта команда отключает все сообщения об ошибках, поскольку они могут пересекаться с сообщениями интерфейса AGI. (Вероятно, полезно будет закомментировать эту строку при тестировании.)
# создать описатели файла в случае необходимости if (!defined('STDIN'))
define('STDIN', fopen('php://stdin', 'r'));
if (!defined('STDOUT'))
define('STDOUT', fopen('php://stdout', 'w'));
if (!defined('STDERR'))
define('STDERR', fopen('php://stderr', 'w'));
Этот фрагмент кода гарантирует открытие описателей файла для потоков STDIN, STDOUT и STDERR, которые будут обрабатывать все взаимодействия между Asterisk и нашим сценарием.
# извлекаем все переменные AGI из Asterisk
while (!feof(STDIN)) {
$temp = trim(fgets(STDIN,4096));
if (($temp == "") || ($temp == "n")) {
break;
i
$s = split(":",$temp);
$name = str_replace("agi_","",$s[0]);
$agi[$name] = trim($s[1]);
}
Далее считываем все AGI-переменные, передаваемые нам Asterisk. Использование в PHP команды fgets для чтения данных из STDIN обеспечит сохранение каждой переменной в хеше $agi. Эти переменные могли бы использоваться в логике сценария AGI, но в данном примере мы не будем этого делать.
# вывести все переменные AGI в целях отладки
foreach($agi as $key=>$value) {
fwrite(STDERR,"-- $key = $valuen"); fflush(STDERR);
}
Здесь переменные возвращаются в STDERR для целей отладки.
# извлечь эту веб-страницу $weatherPage=file_get_contents($weatherURL);
Эта строка кода обеспечивает извлечение XML-файла с сайта National Weather Service (Национальная метеорологическая служба) и помещение его содержимого в переменную $weatherPage. Эта переменная будет использована позже для получения необходимых частей сводки погоды.
# получить температуру в градусах по Фаренгейту
if (preg_match("/<temp_f>([0-9]+)</temp_f>/i",$weatherPage,$matches)) {
$currentTemp=$matches[1];
}
Данный фрагмент кода извлекает данные о температуре (в градусах по Фаренгейту) из сводки погоды с помощью команды preg_match. Для получения необходимых данных эта команда использует совместимые с Perl регулярные выражения[103].
# получить направление ветра
if (preg_match("/<wind_dir>North</wind_dir>/i",$weatherPage)) {
$currentWindDirection='northerly';
elseif (preg_match("/<wind_dir>South</wind_dir>/i",$weatherPage))
$currentWindDirection='southerly'; elseif (preg_match("/<wind_dir>East</wind_dir>/i",$weatherPage))
$currentWindDirection='easterly'; elseif (preg_match("/<wind_dir>West</wind_dir>/i",$weatherPage))
$currentWindDirection='westerly'; elseif (preg_match("/<wind_dir>Northwest</wind_dir>/i",$weatherPage))
$currentWindDirection='northwesterly'; elseif (preg_match("/<wind_dir>Northeast</wind_dir>/i",$weatherPage))
$currentWindDirection='northeasterly'; elseif (preg_match("/<wind_dir>Southwest</wind_dir>/i",$weatherPage))
$currentWindDirection='southwesterly'; elseif (preg_match("/<wind_dir>Southeast</wind_dir>/i",$weatherPage)) $currentWindDirection='southeasterly';
Направление ветра извлекаем посредством команды preg_match, а полученное значение (заключенное в теги wind_dir) присваиваем переменной $currentWindDirection.
# получаем скорость ветра
if (preg_match("/<wind_mph>([0-9.]+)</wind_mph>/i",$weatherPage,$matches)) {
$currentWindSpeed = $matches[1];
}
Наконец получаем текущую скорость ветра и присваиваем ее значение переменной $currentWindSpeed.
# сообщить вызывающему абоненту текущие погодные условия
if ($currentTemp) {
fwrite(STDOUT,"STREAM FILE temperature ""n"); fflush(STDOUT);
$result = trim(fgets(STDIN,4096)); checkresult($result); fwrite(STDOUT,"STREAM FILE is ""n"); fflush(STDOUT);
$result = trim(fgets(STDIN,4096));
checkresult($result);
fwrite(STDOUT,"SAY NUMBER $currentTemp ""n"); fflush(STDOUT);
$result = trim(fgets(STDIN,4096)); checkresult($result);
fwrite(STDOUT,"STREAM FILE degrees ""n"); fflush(STDOUT);
$result = trim(fgets(STDIN,4096)); checkresult($result);
fwrite(STDOUT,"STREAM FILE fahrenheit ""n"); fflush(STDOUT);
$result = trim(fgets(STDIN,4096)); checkresult($result);
}
if ($currentWindDirection && $currentWindSpeed) {
fwrite(STDOUT,"STREAM FILE with ""n"); fflush(STDOUT);
$result = trim(fgets(STDIN,4096)); checkresult($result);
fwrite(STDOUT,"STREAM FILE $currentWindDirection ""n"); fflush(STDOUT);
$result = trim(fgets(STDIN,4096)); checkresult($result);
fwrite(STDOUT,"STREAM FILE wx/winds ""n"); fflush(STDOUT);
$result = trim(fgets(STDIN,4096)); checkresult($result); fwrite(STDOUT,"STREAM FILE at ""n";) fflush(STDOUT);
$result = trim(fgets(STDIN,4096)); checkresult($result);
fwrite(STDOUT,"SAY NUMBER $currentWindSpeed ""n"); fflush(STDOUT);
$result = trim(fgets(STDIN,4096)); checkresult($result);
fwrite($STDOUT,"STREAM FILE miles-per-hour ""n"); fflush(STDOUT);
$result = trim(fgets(STDIN,4096)); checkresult($result);
}
Теперь, собрав все необходимые данные, можно отправить AGI-коман- ды в Asterisk (проверяя результаты по ходу), которые доставят информацию о текущих погодных условиях вызывающему абоненту. Это будет реализовано с помощью AGI-команд STREAM FILE и SAY NUMBER. Мы говорили об этом раньше, повторим еще раз: при вызове команд AGI в них должны передаваться все необходимые аргументы. В данном случае обе команды, STREAM FILE и SAY NUMBER, требуют второго аргумента. Передадим пустые кавычки, экранированные символом обратного слэша.
Также следует обратить внимание, что при каждой записи в STDOUT вызывается команда fflush. Вероятно, это лишнее, но не будет вреда в том, чтобы гарантировать немедленную отправку AGI-команды в Asterisk, без буферизации.
function checkresult($res) {
trim($res);
if (preg_match('/"200/',$res)) {
if (! preg_match('/result=(-?d+)/',$res,$matches)) {
fwrite(STDERR,"FAIL ($res)n");
fflush(STDERR);
return 0;
}
else {
fwrite(STDERR,"PASS (".$matches[1].")n");
fflush(STDERR);
return $matches[1];
}
}
else {
fwrite(STDERR,"FAIL (unexpected result '$res')n");
fflush(STDERR);
return -1;
}
}
Назначение функции checkresult аналогично подпрограмме checkresult из нашего примера на Perl. Как следует из ее имени, она проводит проверку
результатов, возвращаемых Asterisk, при каждом вызове команды AGI.
?>
В конце файла располагается закрывающий тег PHP. После закрывающего тега PHP не должно быть никаких пробелов, поскольку это может сбить с толку интерфейс AGI.
Теперь мы уже рассмотрели два разных языка программирования с целью продемонстрировать, что общего в написании сценария AGI на PHP и Perl и чем они отличаются. При создании сценария AGI на PHP помните, что необходимо:
• Запускать PHP с ключом -q; это отключает HTML в сообщениях об ошибках.
• Отключить ограничение по времени или задать для него приемлемое значение (более новые версии PHP автоматически отключают ограничение по времени при запуске PHP из командной строки).
• Отключить буферизацию вывода с помощью команды ob_implicit_ flush(false).
• Открыть описатели файла для STDIN, STDOUT и STDERR (в более новых версиях PHP один или более этих описателей файла уже могут быть открыты; в предыдущем фрагменте кода показано, как сделать это красиво для большинства версий PHP).
• Прочитать переменные из STDIN, используя функцию fgets.
• Использовать функцию fwrite для записи данных в STDOUT и STDERR.
• Всегда вызывать функцию fflush после записи в STDOUT или STDERR.
Библиотека AGI для PHP
Для более продвинутого программирования AGI на PHP, вероятно, пригодится проект PHPAGI, который можно найти по адресу http:// phpagi.sourceforge.net. Изначально он был написан Мэттью Ашамом (Matthew Asham) и дорабатывался несколькими членами сообщества разработчиков Asterisk.