Имеем некий web-сервис, которому необходимо отправлять очень много факсов.
Можно использовать сторонние сервисы, которые специализируются на этом.
Но если факсов много, то это выливается в нехилую такую копеечку. Поэтому мы будем создавать свой сервис.
Используя свой сервис, мы будем платить повременно за голосовой трафик.
Сервис будет получать запрос на отправку факса и рапортовать нам о результатах.
Использовать будем Asterisk, может он не самый производительный, но хорошо известный.
В нашей конфигурации мы будем использовать готовую сборку Elastix, т.к. она работает стабильнее, если верить Klistrod ( Битва титанов FreeSwitch vs. Asterisk — Тест производительности).
Так же плюсом является наличие Apache и php. Доставлять пакеты не придется.
В Asterisk есть несколько способов отправки факсов: голосом через G.711 и с использованием T.38.
Второй вариант предпочтительнее, т.к. вероятность доставки выше.
Так же есть 2 реализации отправки факсов:
1. Spandsp OpenSource проект
2. Digium Fax for Asterisk
Коммерческая реализация от авторов астериска, 1 конкурентная лицензия бесплатная.
Подробности установки( docs.digium.com/FAX/fax_for_asterisk_admin_manual.pdf )
Spandsp уже включена в поставку Elastix.
Логически систему можно разбить на 3 части.
1.Прием запросов
2.Ротация факсов
3.Отправка факса и рапорт о результатах.
Прием запросов на отправку.
Инициация отправки факса будет происходить посредством перемещения .call файла в папку выполения астериска.
В call файле находятся все необходимые Asteriskу параметры для отправки.
faxsend.call
Callerid:"FaxSender"<1111>
Maxretries:maxRetries
Waittime:300
Context:faxsend-t38
Extension:faxout
RetryTime:50
Priority:1
SetVar: T38CALL=1
Set:RETURNURL={returnUrl}
Set:TAGLINE=Fax from CompanyName
Set:RECEIVER=Number Of Receiver
Set:FAX_ID={faxId}
Set:TIFF_2_SEND={faxId}.tif';
Создаем скрипт который будет принимать параметры и файл для отправки. Файл для отправки будем получать в PDF.
Параметры на которые, думаю стоит обратить внимание:
maxRetries — количество попыток дозвона.
faxid – идентификатор факса для которого будет возвращаться статус.
returnUrl – адрес по которому будет возвращаться результат отправки.
index.php
<?php
define( "STATUS_SUCCESS", "success" );
define( "STATUS_ERROR", "fail" );
$pdfLocation = "//var//tmp//faxes//";
function sendStatus( $statusKind )
{
header('Content-type: application/json');
exit( "{"status": "" . $statusKind . ""}" );
}
$fax = $_REQUEST['fax'];
$faxId = $_REQUEST['faxId'];
$maxRetries = isset( $_REQUEST['maxRetries'] ) ? $_REQUEST['maxRetries']
: 0;
$returnUrl = $_REQUEST['returnUrl'];
$pdf = $faxId . '.pdf';
if ($fax == '' || $returnUrl == '' || $faxId == ''
)
{
sendStatus( STATUS_ERROR );
}
if(!move_uploaded_file($_FILES['file']['tmp_name'], $pdfLocation .
$pdf))
{
sendStatus( STATUS_ERROR );
}
$callFileBody = 'Channel:SIP/trunkname/{fax}
Callerid:"FaxSender"<1111>
Maxretries:{maxRetries}
Waittime:300
Context:faxsend-t38
Extension:faxout
RetryTime:50
Priority:1
SetVar: T38CALL=1
Set:RETURNURL={returnUrl}
Set:TAGLINE=Fax from Company
Set:RECEIVER={fax}
Set:FAX_ID={faxId}
Set:TIFF_2_SEND={faxId}.tif';
$callFileBody = str_replace("{fax}", $fax, $callFileBody);
$callFileBody = str_replace("{faxId}", $faxId, $callFileBody);
$callFileBody = str_replace("{maxRetries}", $maxRetries, $callFileBody);
$callFileBody = str_replace("{returnUrl}", $returnUrl, $callFileBody);
$callFilename = $faxId . ".call";
file_put_contents($pdfLocation . $callFilename, $callFileBody);
if (!file_exists($pdfLocation . $callFilename))
{
sendStatus( STATUS_ERROR );
}
sendStatus( STATUS_SUCCESS );
?>
Таким образом в указанной папке pdfLocation появляется 2 файла: .call и .pdf.
Данный скрипт можно поместить в какую-нибудь из подпапок в /var/www/html
Я создал папку faxservice.
Таким образом наш сервис доступен по адресу http://serveradress/faxservice
По-умолчанию Elastix перенаправляет все запросы http на https. Что бы не заморачиваться с сертефикатами я этот редирект отключил:
/etc/httpd/conf.d/elastix.conf
# Apache-level configuration for Elastix administration interface
Timeout 300
# Default apache configuration specifies greater limits than these
#MaxClients 150
#MaxRequestsPerChild 1000
# Default apache User and Group diretives MUST be commented out
# in order for these to take effect.
User asterisk
Group asterisk
<Directory "/var/www/html">
# Redirect administration interface to https
#RewriteEngine off
#RewriteCond %{HTTPS} off
#RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI}
</Directory>
Просто закомментировал реврайты.
Ротация факсов
Нам необходим мониторинг этой папки на предмет появления новых заданий, и после появления заданий, передачи в очередь на отправку в Asterisk. Так же необходимо конвертация нашего pdf в tif
Данный функционал мы реализуем с помощью bash скрипта:
faxrotate
#!/bin/bash
SOURCEDIR="/var/tmp/faxes/"
for file in `ls $SOURCEDIR*.pdf `;
do
callfile="${file/pdf/call}"
if [ ! -e "$callfile" ]
then
continue
fi
`gs -q -dNOPAUSE -dBATCH -sDEVICE=tiffg4 -sPAPERSIZE=letter -sOutputFile=${file/pdf/tif} $file`
mv ${file/pdf/call} /var/spool/asterisk/outgoing
rm $file
done
Подвешиваем выполнение данного скрипта каждую минуту от имени Asterisk.
«Отработанные» файлы tif будем удалять каждую ночь, хранить их будем 2 дня (на тот случай, если количество попыток будет большим, а промежутки между ними еще больше.):
/usr/bin/find /var/tmp/faxes -name "*tif" -mtime +2 –delete
Отправка факсов Asteriskом
Осталось теперь только научить Asterisk отправлять факсы через T.38 и рапортовать нам о результатах.
Для начала необходимо «научить» Elastix работать с T.38
Для этого в sip_general_custom.conf добавляем:
t38pt_udptl=yes
А в настройках транспорта не забываем добавить
transport=udp,udptl
Примерный вид настроек транка:
[trunkname]
username=username
type=friend
transport=udp,udptl
secret=password
qualify=yes
nat=yes
insecure=port,invite
host=voip.host.com
disalow=all
directmedia=yes
context=from-pstn
canreinvite=yes
allow=ulaw&alaw
Далее необходимо создать контекст, который будет вызываться из call файла
Его добавляем в
extensions_custom.conf
[faxsend-t38]
exten => faxout,1,Set(STARTTIME=${SHELL(date +%s)} )
exten => faxout,n,Wait(1)
exten => faxout,n,Playback(fax24,skip)
exten => faxout,n,Wait(1)
exten => faxout,n,NoOp(**** SENDING FAX ****)
; Set FAXOPTs
exten => faxout,n,NoOp(**** SETTING FAXOPT ****)
exten => faxout,n,Set(FAXFILE=${TIFF_2_SEND})
exten => faxout,n,Set(FAXOPT(ecm)=yes)
exten => faxout,n,Set(FAXOPT(headerinfo)=${TAGLINE})
exten => faxout,n,Set(FAXOPT(maxrate)=14400)
exten => faxout,n,Set(FAXOPT(minrate)=4800)
; Send the fax
exten => faxout,n,NoOp(**** SENDING FAX : ${FAXFILE} ****)
exten => faxout,n,SendFAX(/var/tmp/faxes/${FAXFILE},dfzs)
;Calculating Time of Sending
exten => faxout,n,Set(ENDTIME=${SHELL(date +%s)} )
exten => faxout,n,Set(TRANSFERTIME=${MATH(${ENDTIME}-${STARTTIME},int)})
;Actions after sending fax
exten => faxout,n,Set(NORMURL=${FAXOPT(error)})
exten => faxout,n,Set(STATUSMESSAGE=${REPLACE(NORMURL, ,+)})
exten => faxout,n,Set(FAXOPTRATE=${FAXOPT(rate)})
exten => faxout,n,Hangup
;Actions if no answer or busy
exten => failed,1,Set(FAXSTATUS=FAILED)
exten => failed,2,Set(STATUSMESSAGE=number+no+answer+or+busy)
exten => failed,3,Set(FAXOPTRATE=none)
exten => h,1,NoOP(------------------- FAX to ${EXTEN} with ${FAXSTATUS} -----------------)
exten => h,2,Set(CURLRESULT=${CURL(${RETURNURL}?fax=${RECEIVER}&faxId=${FAX_ID}&status=${FAXSTATUS}&message=${STATUSMESSAGE})})
exten => h,4,Set(LOGFAXOUT=${SHELL(echo "${STRFTIME(${EPOCH},,%d%m%Y-%H:%M:%S)} : ${FAX_ID} : ${RECEIVER} : ${FAXSTATUS} : ${STATUSMESSAGE} : ${TRANSFERTIME}s : ${FAXOPTRATE}" >> /var/log/asterisk/faxout.log)})
exten => h,3,NoOp(${RECEIVER}:${FAX_ID}:${FAXSTATUS}:${STATUSMESSAGE}:${FAXOPTRATE})
Данный контекст пытается дозвониться и отправить факс, при недозвоне он возвращает number+no+answer+or+busy.
При удачном дозвоне и попытке отправки возвращает статус и расшифровку статусного сообщения.
Эту информацию он возвращает по адресу RETURNURL из php скрипта.
Так же он пишет лог файл для дальнейших разборов полетов по каким-то спорным вопросам.
Для работы голосового приветствия необходимо записать файл в формате PCM Encoded, 16 Bits, at 8000Hz и поместить его в /var/lib/asterisk/sounds.
Автор: volhonsky