Сервис автоматизированной отправки факсов с помощью Asterisk

от автора

Имеем некий 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.

ссылка на оригинал статьи http://habrahabr.ru/post/183584/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *