[Erlang/OTP, Функциональное программирование, Elixir/Phoenix] Отправляем SMS из Erlang/Elixir. Короткая инструкция
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
Photo by Science in HD
Если вам когда-либо приходилось решать задачу отправки SMS из кода вашего приложения, скорее всего, вы использовали готовое REST API поставщика дополнительных услуг. Но что происходит после того, как поставщик получит ваш запрос? Какие протоколы используются и какой путь проходит текст сообщения, прежде чем оказаться на экране мобильного терминала пользователя?
В этой статье вы найдёте:
- Немного теории и терминологии SMPP-протокола: SMSC, ESME, PDU, MO/MT SM.
- Краткий обзор существующих библиотек для работы с SMPP в Erlang/Elixir.
- Пример реализации асинхронного клиента при помощи библиотеки SMPPEX. Возможно, он будет полезен тем, кто ещё не использовал Elixir-библиотеки в Erlang-проектах.
- Информацию по обработке deliver_sm, MO SM.
Чего тут точно нет, так это информации по отправке коротких сообщений через SIGTRAN.
Определимся с терминами и понятиями
Прежде чем погружаться в протоколы и код, предлагаю разобраться в терминологии. Если быть придирчивым к определениям, то отправить SMS невозможно. Вспоминается момент из «Джентльменов удачи»: «Кто ж его отправит — он же сервис!» SMS — акроним от short message service, что на русский переводится как «сервис/служба коротких сообщений». Если возвращаться к шутке, то отправляем мы SM, т. е. короткие сообщения, используя SMS — сервис коротких сообщений.
У каждого оператора мобильной связи есть компонент, отвечающий за работу службы коротких сообщений. Это так называемый SMS-центр, он же SMSC, он же SMS-SC. Его задачами являются хранение, передача, конвертация и доставка SM-сообщений. Наиболее распространенным внешним протоколом взаимодействия с SMSC является SMPP. SMPP — клиент-серверный протокол-комбайн, отвечающий за обмен короткими сообщениями в одноранговой сети. Источником SM могут быть устройства и приложения. В терминологии SMPP их называют ESME.
Давайте ответим на вопросы в начале статьи. Итак, ваше сообщение по REST API или SMPP попало к поставщику услуг, у которого заключён договор с одним или несколькими операторами связи или другими посредниками. Сервер поставщика подключается к SMSC и отправляет по SMPP ваше SM, затем получает отчёт о доставке или ответное SM. В процессе обработки SM могут проходить через маршрутизаторы — RE. SMSC, сходив в HLR, узнает местоположение абонента и доставит SM абоненту. Общая картина и понимание проблемы, надеюсь, у вас появились. Давайте погрузимся в протокольные тонкости.
SMPP
Выше я сказал, что SMPP — протокол-комбайн. Подобный эвфемизм я позволил себе из-за того, что SMPP применим не только для организации обмена SMS, с его помощью можно организовать различные сервисы: ESM, голосовой почты, уведомлений, сотового радиовещания, WAP, USSD и прочие. Весь обмен происходит с помощью пар запрос-ответ. Их называют PDU — блоками данных или пакетами.
Инициализация подключения
Перед началом обмена мы должны указать, в каком направлении будет работать наше подключение. За это отвечают соответствующие команды:
- bind_transmitter — клиент может только отправлять запросы на сервер;
- bind_receiver — клиент только получает ответы от сервера;
- bind_transceiver — клиент работает в обоих направлениях, этот режим появился в SMPPv3.4.
Безопасность
При выполнении команды привязки мы должны передать параметры безопасности для идентификации нашего ESME: system_id, system_type и password.
SMPP в экосистеме OTP
Недавно у хорошего друга возник вопрос по работе с SMPP в Erlang. Собственно, благодаря этому и родился этот текст.
Казалось бы, никаких проблем. Проверенный временем телеком-протокол с давно известными проблемами с одной стороны и телеком-язык с другой. Все должно быть просто и весело, как в песенке PPAP.
Но есть нюанс… Найти адекватную реализацию оказалось непросто.
Наверняка все, кто пытался серьёзно работать с Erlang, знают про его недостаток, связанный с ограниченным выбором библиотек. С SMPP такая же история — в OTP нет штатной поддержки этого протокола, а на первой странице выдачи Гугла творится что-то странное:
- esmpp — библиотека со странным API и отсутствующим сообществом;
- древний OSERL — проект стартовал 11 лет назад, последний коммит сделали более 5 лет назад;
- неподдерживаемый smpp34 — последний коммит был более 10 лет назад;
- куча вопросов вида «Какую библиотеку/клиента использовать для SMPP?» на тематических форумах.
Лично я бы загрустил от такого разнообразия существующих решений. Особенно, когда хочется асинхронного режима, адекватной поддержки SMPP 3.4 и возможности написать как клиент, так и сервер. Но на помощь приходит Elixir и библиотека SMPPEX.
SMPPEX
Про сам проект скажу только то, что он активный и готов для продакшен-применения. Это стабильная, всесторонняя библиотека для SMPP с продуманным API, хорошей поддержкой разработчиков и отсутствием проблем с лицензией.
От слов к делу
Сначала можно ознакомиться с примерами синхронного и асинхронного клиента в документации. Затем можно перейти к более сложным вещам в контексте использования SMPPEX в Erlang-проекте.
Для иллюстрации возможностей библиотеки возьмём простой сценарий:
- Поднять линк.
- Отправить сообщение.
- Дождаться отчёта о доставке либо обработать входящие сообщения.
Придумаем дополнительные требования. Допустим, мы хотим отправлять MT SM, получать отчёты о доставке и MO SM. При этом по каким-то причинам нам нужны кастомные PDU и полный контроль над линком, поэтому за формирование submit_sm PDU и обработку всех входящих PDU мы будем отвечать сами. При этом мы не должны забывать про требование асинхронности.
Работа с линком
Надеюсь, что сложностей с установкой зависимости из hex.pm у вас не возникло и мы можем приступить к написанию кода. Как говорилось выше, работать мы будем в асинхронном режиме, поэтому запускаем клиента с помощью модуля SMPPEX.ESME:
'Elixir.SMPPEX.ESME':start_link(SmscHost, SmscPort, {?MODULE, [Opts]})
Для синхронного режима существует SMPPEX.ESME.Sync.
Наш клиент готов, и мы можем сделать привязку к SMSC. Предположим, что SMSC поддерживает SMPPv3.4 и мы можем использовать transceiver режим:
'Elixir.SMPPEX.Pdu.Factory':bind_transceiver(SystemId, Pass)
Если всё прошло хорошо, нам должен прийти PDU с командой bind_transceiver_resp:
bind_transceiver_resp = 'Elixir.SMPPEX.Pdu':command_name(Pdu)
Формирование PDU для MT SM
Линк поднят, и мы можем отправить наше сообщение. В терминах SMPP, сообщения адресуемые абоненту называются Mobile Terminated (MT SM). Соберём PDU для него:
submit_sm_pdu(SourceMsisdn, DestMsisdn, Message, Ttl) ->
{ok, CommandId} = 'Elixir.SMPPEX.Protocol.CommandNames':id_by_name(submit_sm),
{D, {H, M, S}} = calendar:seconds_to_daystime(Ttl),
VP = lists:flatten(io_lib:format("0000~2..0w~2..0w~2..0w~2..0w000R", [D, H, M, S])),
'Elixir.SMPPEX.Pdu':new(
CommandId,
#{
source_addr => SourceMsisdn,
source_addr_ton => 1,
source_addr_npi => 1,
destination_addr => DestMsisdn,
dest_addr_ton => 1,
dest_addr_npi => 1,
short_message => Message,
data_coding => 246,
protocol_id => 127,
%% For concatenated messages
esm_class => 64,
registered_delivery => 1,
validity_period => list_to_binary(VP)
}
).
Обработка отчетов о доставке и MO SM
Mobile originated (MO SM) — сообщения от абонента.
После отправки сообщения в линк SMSC ответит нам submit_sm_resp, в котором указан уникальный ID нашего сообщения:
MsgId = 'Elixir.SMPPEX.Pdu':mandatory_field(Pdu, message_id)
Теперь нам необходимо дождаться deliver_sm с этим message_id.
Чтобы отличить отчёты о доставке от MO SM, проанализируем esm_class:
EsmClass = 'Elixir.SMPPEX.Pdu':mandatory_field(Pdu, esm_class),
case <<EsmClass>> of
<<_Head : 2, 0 : 1, 0 : 1, 0 : 1, 1 : 1, _Tail : 2>> -> handle_delivery_receipt(Pdu);
<<_Head : 2, 0 : 1, 0 : 1, 0 : 1, 0 : 1, _Tail : 2>> -> handle_standart_message(Pdu);
Some -> ?LOG_ERROR("unknown deliver_sm: ~p", [Some])
end
При этом для обработки отчётов о доставке нам достаточно узнать ID доставленного сообщения:
SmsId = 'Elixir.SMPPEX.Pdu':field(Pdu, receipted_message_id)
А для входящих сообщений узнать номер отправителя:
Msisdn = 'Elixir.SMPPEX.Pdu':field(Pdu, source_addr)
и полезное содержимое сообщения:
Payload = 'Elixir.SMPPEX.Pdu':field(Pdu, short_message)
Как известно, спецификация SMPP требует deliver_sm_resp в ответ на deliver_sm. Поэтому после обработки отчёта о доставке и входящего сообщения мы должны ответить deliver_sm_resp. Создадим PDU для него:
deliver_sm_resp_pdu(MessageId) ->
{ok, CommandId} = 'Elixir.SMPPEX.Protocol.CommandNames':id_by_name(deliver_sm_resp),
CommandStatus = 0,
SeqNumber = 0,
'Elixir.SMPPEX.Pdu':new({CommandId, CommandStatus, SeqNumber}, #{message_id => MessageId}, #{}).
Я специально не указываю номер команды, добавим его автоматически:
ReplyPdu = 'Elixir.SMPPEX.Pdu':as_reply_to(deliver_sm_resp(SmsId), Pdu)
Весь код демопроекта можно найти в репозитории: https://github.com/Elzor/smpp_in_erlang.
OTP-тренды
В 2020 году на тренды развития OTP и BEAM всё большее влияние оказывает сообщество Elixir. Чаще и чаще хорошие инструменты и полезные библиотеки можно найти на Elixir, а не на Erlang. Это не повод для тревоги за Erlang, просто Elixir смог заинтересовать и привлечь больше людей в своё сообщество, и это прекрасно. А благодаря OTP для использования той или иной библиотеки нам не важно, на чём она написана. Надеюсь, пример из статьи смог показать гибкость SMPPEX как инструмента и удобство применения библиотек, написанных на Elixir в Erlang-проектах.
===========
Источник:
habr.com
===========
Похожие новости:
- [Программирование, Функциональное программирование, TypeScript] Функциональное программирование на TypeScript: паттерн «класс типов»
- [Разработка веб-сайтов, Scala, API, ООП, Функциональное программирование] Изучаю Scala: Часть 5 — Http Requests
- [Информационная безопасность, Habr, Системное администрирование] Используем tcpdump для анализа и перехвата сетевого трафика
- [Программирование, Функциональное программирование] Векторные языки — параллельный мир
- [Разработка для интернета вещей, Производство и разработка электроники, Умный дом] Многофункциональный сетевой контроллер управления и мониторинга Laurent-5G
- [JavaScript, Функциональное программирование] Для чего на самом деле нужны стрелочные функции в JavaScript
- [Lisp, Функциональное программирование, Профессиональная литература, Учебный процесс в IT, Читальный зал] Итоги двух лет изучения «Structure and Interpretation of Computer Programs»
- [Open source, Erlang/OTP, Elixir/Phoenix] Типы в рантайме: глубже в крольчью нору
- [Open source, Erlang/OTP, Elixir/Phoenix] Типы, где их не ждали
- [Программирование микроконтроллеров, Разработка на Raspberry Pi, DIY или Сделай сам, Здоровье] Затерянные в тумане, или Увлекательные приключения в мире АПР *
Теги для поиска: #_erlang/otp, #_funktsionalnoe_programmirovanie (Функциональное программирование), #_elixir/phoenix, #_erlang/otp, #_smpp, #_esme, #_pdu, #_elixir, #_erlang_mi_fa_stare_bene, #_erlang/otp, #_funktsionalnoe_programmirovanie (
Функциональное программирование
), #_elixir/phoenix
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 06:08
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
Photo by Science in HD Если вам когда-либо приходилось решать задачу отправки SMS из кода вашего приложения, скорее всего, вы использовали готовое REST API поставщика дополнительных услуг. Но что происходит после того, как поставщик получит ваш запрос? Какие протоколы используются и какой путь проходит текст сообщения, прежде чем оказаться на экране мобильного терминала пользователя? В этой статье вы найдёте:
Чего тут точно нет, так это информации по отправке коротких сообщений через SIGTRAN. Определимся с терминами и понятиями Прежде чем погружаться в протоколы и код, предлагаю разобраться в терминологии. Если быть придирчивым к определениям, то отправить SMS невозможно. Вспоминается момент из «Джентльменов удачи»: «Кто ж его отправит — он же сервис!» SMS — акроним от short message service, что на русский переводится как «сервис/служба коротких сообщений». Если возвращаться к шутке, то отправляем мы SM, т. е. короткие сообщения, используя SMS — сервис коротких сообщений. У каждого оператора мобильной связи есть компонент, отвечающий за работу службы коротких сообщений. Это так называемый SMS-центр, он же SMSC, он же SMS-SC. Его задачами являются хранение, передача, конвертация и доставка SM-сообщений. Наиболее распространенным внешним протоколом взаимодействия с SMSC является SMPP. SMPP — клиент-серверный протокол-комбайн, отвечающий за обмен короткими сообщениями в одноранговой сети. Источником SM могут быть устройства и приложения. В терминологии SMPP их называют ESME. Давайте ответим на вопросы в начале статьи. Итак, ваше сообщение по REST API или SMPP попало к поставщику услуг, у которого заключён договор с одним или несколькими операторами связи или другими посредниками. Сервер поставщика подключается к SMSC и отправляет по SMPP ваше SM, затем получает отчёт о доставке или ответное SM. В процессе обработки SM могут проходить через маршрутизаторы — RE. SMSC, сходив в HLR, узнает местоположение абонента и доставит SM абоненту. Общая картина и понимание проблемы, надеюсь, у вас появились. Давайте погрузимся в протокольные тонкости. SMPP Выше я сказал, что SMPP — протокол-комбайн. Подобный эвфемизм я позволил себе из-за того, что SMPP применим не только для организации обмена SMS, с его помощью можно организовать различные сервисы: ESM, голосовой почты, уведомлений, сотового радиовещания, WAP, USSD и прочие. Весь обмен происходит с помощью пар запрос-ответ. Их называют PDU — блоками данных или пакетами. Инициализация подключения Перед началом обмена мы должны указать, в каком направлении будет работать наше подключение. За это отвечают соответствующие команды:
Безопасность При выполнении команды привязки мы должны передать параметры безопасности для идентификации нашего ESME: system_id, system_type и password. SMPP в экосистеме OTP Недавно у хорошего друга возник вопрос по работе с SMPP в Erlang. Собственно, благодаря этому и родился этот текст. Казалось бы, никаких проблем. Проверенный временем телеком-протокол с давно известными проблемами с одной стороны и телеком-язык с другой. Все должно быть просто и весело, как в песенке PPAP. Но есть нюанс… Найти адекватную реализацию оказалось непросто. Наверняка все, кто пытался серьёзно работать с Erlang, знают про его недостаток, связанный с ограниченным выбором библиотек. С SMPP такая же история — в OTP нет штатной поддержки этого протокола, а на первой странице выдачи Гугла творится что-то странное:
Лично я бы загрустил от такого разнообразия существующих решений. Особенно, когда хочется асинхронного режима, адекватной поддержки SMPP 3.4 и возможности написать как клиент, так и сервер. Но на помощь приходит Elixir и библиотека SMPPEX. SMPPEX Про сам проект скажу только то, что он активный и готов для продакшен-применения. Это стабильная, всесторонняя библиотека для SMPP с продуманным API, хорошей поддержкой разработчиков и отсутствием проблем с лицензией. От слов к делу Сначала можно ознакомиться с примерами синхронного и асинхронного клиента в документации. Затем можно перейти к более сложным вещам в контексте использования SMPPEX в Erlang-проекте. Для иллюстрации возможностей библиотеки возьмём простой сценарий:
Придумаем дополнительные требования. Допустим, мы хотим отправлять MT SM, получать отчёты о доставке и MO SM. При этом по каким-то причинам нам нужны кастомные PDU и полный контроль над линком, поэтому за формирование submit_sm PDU и обработку всех входящих PDU мы будем отвечать сами. При этом мы не должны забывать про требование асинхронности. Работа с линком Надеюсь, что сложностей с установкой зависимости из hex.pm у вас не возникло и мы можем приступить к написанию кода. Как говорилось выше, работать мы будем в асинхронном режиме, поэтому запускаем клиента с помощью модуля SMPPEX.ESME: 'Elixir.SMPPEX.ESME':start_link(SmscHost, SmscPort, {?MODULE, [Opts]})
Для синхронного режима существует SMPPEX.ESME.Sync. Наш клиент готов, и мы можем сделать привязку к SMSC. Предположим, что SMSC поддерживает SMPPv3.4 и мы можем использовать transceiver режим: 'Elixir.SMPPEX.Pdu.Factory':bind_transceiver(SystemId, Pass)
Если всё прошло хорошо, нам должен прийти PDU с командой bind_transceiver_resp: bind_transceiver_resp = 'Elixir.SMPPEX.Pdu':command_name(Pdu)
Формирование PDU для MT SM Линк поднят, и мы можем отправить наше сообщение. В терминах SMPP, сообщения адресуемые абоненту называются Mobile Terminated (MT SM). Соберём PDU для него: submit_sm_pdu(SourceMsisdn, DestMsisdn, Message, Ttl) ->
{ok, CommandId} = 'Elixir.SMPPEX.Protocol.CommandNames':id_by_name(submit_sm), {D, {H, M, S}} = calendar:seconds_to_daystime(Ttl), VP = lists:flatten(io_lib:format("0000~2..0w~2..0w~2..0w~2..0w000R", [D, H, M, S])), 'Elixir.SMPPEX.Pdu':new( CommandId, #{ source_addr => SourceMsisdn, source_addr_ton => 1, source_addr_npi => 1, destination_addr => DestMsisdn, dest_addr_ton => 1, dest_addr_npi => 1, short_message => Message, data_coding => 246, protocol_id => 127, %% For concatenated messages esm_class => 64, registered_delivery => 1, validity_period => list_to_binary(VP) } ). Обработка отчетов о доставке и MO SM Mobile originated (MO SM) — сообщения от абонента. После отправки сообщения в линк SMSC ответит нам submit_sm_resp, в котором указан уникальный ID нашего сообщения: MsgId = 'Elixir.SMPPEX.Pdu':mandatory_field(Pdu, message_id)
Теперь нам необходимо дождаться deliver_sm с этим message_id. Чтобы отличить отчёты о доставке от MO SM, проанализируем esm_class: EsmClass = 'Elixir.SMPPEX.Pdu':mandatory_field(Pdu, esm_class),
case <<EsmClass>> of <<_Head : 2, 0 : 1, 0 : 1, 0 : 1, 1 : 1, _Tail : 2>> -> handle_delivery_receipt(Pdu); <<_Head : 2, 0 : 1, 0 : 1, 0 : 1, 0 : 1, _Tail : 2>> -> handle_standart_message(Pdu); Some -> ?LOG_ERROR("unknown deliver_sm: ~p", [Some]) end При этом для обработки отчётов о доставке нам достаточно узнать ID доставленного сообщения: SmsId = 'Elixir.SMPPEX.Pdu':field(Pdu, receipted_message_id)
А для входящих сообщений узнать номер отправителя: Msisdn = 'Elixir.SMPPEX.Pdu':field(Pdu, source_addr)
и полезное содержимое сообщения: Payload = 'Elixir.SMPPEX.Pdu':field(Pdu, short_message)
Как известно, спецификация SMPP требует deliver_sm_resp в ответ на deliver_sm. Поэтому после обработки отчёта о доставке и входящего сообщения мы должны ответить deliver_sm_resp. Создадим PDU для него: deliver_sm_resp_pdu(MessageId) ->
{ok, CommandId} = 'Elixir.SMPPEX.Protocol.CommandNames':id_by_name(deliver_sm_resp), CommandStatus = 0, SeqNumber = 0, 'Elixir.SMPPEX.Pdu':new({CommandId, CommandStatus, SeqNumber}, #{message_id => MessageId}, #{}). Я специально не указываю номер команды, добавим его автоматически: ReplyPdu = 'Elixir.SMPPEX.Pdu':as_reply_to(deliver_sm_resp(SmsId), Pdu)
Весь код демопроекта можно найти в репозитории: https://github.com/Elzor/smpp_in_erlang. OTP-тренды В 2020 году на тренды развития OTP и BEAM всё большее влияние оказывает сообщество Elixir. Чаще и чаще хорошие инструменты и полезные библиотеки можно найти на Elixir, а не на Erlang. Это не повод для тревоги за Erlang, просто Elixir смог заинтересовать и привлечь больше людей в своё сообщество, и это прекрасно. А благодаря OTP для использования той или иной библиотеки нам не важно, на чём она написана. Надеюсь, пример из статьи смог показать гибкость SMPPEX как инструмента и удобство применения библиотек, написанных на Elixir в Erlang-проектах. =========== Источник: habr.com =========== Похожие новости:
Функциональное программирование ), #_elixir/phoenix |
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 06:08
Часовой пояс: UTC + 5