В первую очередь необходимо было настроить Asterisk так, чтобы он создавал события, отображающие продвижение факса в реально времени. Ниже приведен фрагмент плана набора Asterisk, который позволит нам делать это, а так же обрабатывать следующие ситуации:
- Не удалось дозвониться до абонента (например занята линия или трубка не была поднята до истечения таймаута).
- Абонент снял трубку и положил ее до того, как началась передача факса (т.е. в момент воспроизведения приветствия).
- Передача факса началась успешно, но не завершилась.
Кроме этого план набора позволяет ограничить число одновременных исходящих факс-вызовов, чтобы случайно не занять все свободных SIP каналы.
[OutgoingFaxInit] ; Этот экстеншен используется для совершения исходящего звонка exten => _X.,1,NoOp() same => n,Set(GROUP()=faxout) ; Запоминаем количество одновременных факс-соединений в БД Asterisk, ; чтобы failed экстеншен мог отличать превышение одновременных от ситуации, ; когда соединение просто не удалось. same => n,Set(DB(fax_group_count/${UUID})=${GROUP_COUNT(faxout)}) same => n,GotoIf($[${DB(fax_group_count/${UUID})}<=${MAX_PARALLELISM}]?call) same => n,UserEvent(Fax,uuid: ${UUID},Status: CALL SUSPENDED) same => n,HangUp() same => n(call),Dial(Local/${EXTEN}@OutgoingCalls) same => n,HangUp() ; Этот экстеншен будет выступать источником данных с нашей стороны exten => router,1,NoOp() same => n,Set(__UUID=${UUID}) same => n,Set(__DATA=${DATA}) same => n,Dial(Local/fax@OutgoingFax) same => n,HangUp() exten => failed,1,NoOp() ; В случае, если факс был прерван из-за превышения числа соединений UserEvent ; создаваться не будет same => n,GotoIf($[${DB_DELETE(fax_group_count/${UUID})}<=${MAX_PARALLELISM}]?:end) same => n,UserEvent(Fax,uuid: ${UUID},Status: CALL PICKUP FAILED) same => n(end),HangUp() [OutgoingFax] exten => fax,1,NoOp() same => n,UserEvent(Fax,uuid: ${UUID},Status: CALL PICKUP SUCCESS); ; Передача факса еще не началась. Запоминаем это. same => n,Set(DB(fax_sendstatus/${UUID})=0) same => n,Playback(autofax) same => n,Set(FAXOPT(headerinfo)=Company) same => n,Set(FAXOPT(localstationid)=XXX-XX-XX) ; Началась передача факса same => n,Set(DB(fax_sendstatus/${UUID})=1) same => n,SendFax(${DATA}) same => n,HangUp() exten => h,1,NoOp() ; Если передача факса не была начата, то генерируем событие ; неудачной отправки same => n,GotoIf($[${DB_DELETE(fax_sendstatus/${UUID})}]?sendstatus) same => n,UserEvent(Fax,uuid: ${UUID},Status: FAX SEND FAILED) same => n,Goto(end) ; Если мы начали передавать факс, то сообщаем данные из ${FAXOPT} same => n(sendstatus),UserEvent(Fax,uuid: ${UUID},Status: FAX SEND ${FAXOPT(status)}) same => n(end),NoOp()
Основная часть нашего факс-сервера, как было отмечено выше, будет работать на node.js. С Asterisk мы будем взаимодействовать по AMI. Для полноценной работы клиенту будет достаточно прав на создание вызовов и чтения UserEvent’ов. Таким образом manager.conf будет иметь следующий вид:
[general] enabled=yes [FAX] secret=password read=user write=originate
Для работы с AMI был выбран модуль nami. В отличие аналог он подкупает достаточно большим функционалом из коробки. Есть уже готовые методы для работы с большей частью событий и генерации Action’ов. Стоит отметить, что у автора данного модуля есть реализации AMI интерфейсов и для других языков, кроме JS.
Общий механизм работы факс-сервера следующий:
- Пользователь заходит на специальную веб страницу и в окне создания нового факса указывает файл для отправки (PDF) и номер получателя.
- Загруженный файл помещается на сервер и конвертируется в поддерживаемый Asterisk .tiff формат. Факсу присваивается UUID.
- Вся информация о факсе (время отправки, номер получателя, UUID, количестве попыток повтора) сохраняется в базе данных.
- При появлении нового факса в очереди выполняется его отправка и перемещение в очередь обрабатываемых факсов
- Если через AMI будет получено событие об ошибке отправки, то количество попыток отправки для данного факса будет увеличено, а сам факс будет перемещено в очередь отложенных.
- По истечении попыток отправки факс помечается как недоставленный.
Для реализации очередь и самой базы данных используется Redis. Структура хранения данных следующая:
- Key-value для каждой характеристики факса. Ключи имеют вид fax:uuid:field. Первоначально предполагалось хранить все данные по одному ключу в виде JSON, но потом я решил, что каждый раз парсить и снова сериализовать JSON для изменения какой-либо информации, будет достаточно глупо.
- Сама очередь факсов хранится в виде LIST с ключом fax:send. Подписка на новые факсы в очередь реализована через команду BLPOP.
- Все обрабатываемые факсы хранятся в Sorted Set с ключом fax:processing. В качестве веса выступает время поступления в очередь.
- Для реализации задачи перезвонов через заданный интервал используется еще один Sorted Set fax:delayed, так же с временным весом
- Успешные и недоставленные факсы сохраняются в fax:failed и fax:success.
Для создания приятной глазу веб-формы был выбран twitter bootstrap. Отображение информации пользователю сделано через jQuery datatables. Тут меня поджидала проблема с тем, что jQuery datatables не адаптирован к текущий версии bootstrap 3. К счастью на github был репозиторий исправленной версии.
В конечном итоге получилось следующее:


Все основные настройки расположены в config.json:
{ "logLevel": "info", "port": 80, // порт веб сервера "FAX": { "uploadDir": "/tmp/faxout", // каталог для загрузок "storageDir": "/tmp/faxout", // каталог для хранения TIFF файлов факсов "gsCommand": "gs", // команда вызова Ghostscript (им конвертируются входящие файлы) "maxParallelism": 3, // максимально количество одновременных вызовов "maxRetry": 5, // максимально количество повторных факсов "retryInterval": 420, // интервал повтора факсов "delayedProcessingInterval": 5 // интервал проверки очереди отложенных факсов }, "AMI": { // данные для подключения к AMI "host": "192.168.1.1", "port": 5038, "username": "FAX", "secret": "password" }
Получить исходный код можно на github. Для работы необходимо добавить в план набора Asterisk описанный выше фрагмент а так же иметь на сервере redis и node.js. Надеюсь мой «Hello world» (он же факс-сервер) на node.js окажется вам полезным.
ссылка на оригинал статьи http://habrahabr.ru/post/207080/
Добавить комментарий