Все мы знаем и любим мессенджер Телеграм. Несмотря на то, что данный мессенджер заблокирован в России, многие пользуются им. Этот факт очевиден хотя бы потому, что на Инфостарте регулярно выходят статьи, освещающие какие-либо аспекты интеграции решений на платформе 1С и Телеграма. В данной статье хочу поделиться небольшим и, может быть, очевидным лайфхаком по использованию Телеграма в условиях блокировки.
ИнтернетПрокси = Новый ИнтернетПрокси(Ложь);
ИнтернетПрокси.Установить(Прокси.Протокол, Прокси.Сервер, Прокси.Порт, Прокси.Пользователь, Прокси.Пароль, Ложь);
Соединение = Новый HTTPСоединение("api.telegram.org",443,,,ИнтернетПрокси,,Новый ЗащищенноеСоединениеOpenSSL());
ПолучениеЗапрос = "bot"+ТокенБота+"/sendMessage?chat_id="+Адресат+"&text="+ Сообщение;
Запрос = Новый HTTPЗапрос(ПолучениеЗапрос);
Ответ = Соединение.Получить(Запрос);
После блокировки Телеграма и последовавших за этим событий возник резонный вопрос о стабильности работы мессенджера. Иными словами, в условиях, когда подключение к Телеграму осуществляется с использованием различных прокси, возникают дополнительные риски, связанные с тем, что прокси-сервер может быть заблокирован или перегружен. Здесь замечу, что мы решали задачу по отправке уведомлений о различных событиях системы в Телеграм. Ну например, кто-то из пользователей изменил какой-то документ, и об этом изменении информируются все остальные пользователи путем отправки сообщения в общий чат. Функциональность незатейливая, но позволяющая поддерживать распространение информации в условиях совместной работы.
В благодатные времена, когда Телеграм не блокировался, описанная выше функциональность реализовывалась буквально в несколько строк кода:
Соединение = Новый HTTPСоединение("api.telegram.org",443,,,,,Новый ЗащищенноеСоединениеOpenSSL());
ПолучениеЗапрос = "bot"+ТокенБота+"/sendMessage?chat_id="+Адресат+"&text="+ Сообщение;
Запрос = Новый HTTPЗапрос(ПолучениеЗапрос);
Ответ = Соединение.Получить(Запрос);
После блокировки приходится добавить прокси:
Казалось бы, код практически не усложнился. Но это на первый взгляд. А где взять прокси? Логично, что в Интернете есть множество сайтов, на которых приведены актуальные списки прокси-серверов. И в самом начале мы воспользовались одним из таких сайтов, где и взяли параметры для подключения к прокси-серверу. Естественно, что сами параметры подключения к прокси оставлять в исходном коде просто не спортивно, ведь может возникнуть и возникнет необходимость изменить их. Поэтому вполне логично завести справочник прокси-серверов. Сам справочник приведен на рисунке ниже:
Нетрудно догадаться, что если прокси хранятся в справочнике, то на поверхности лежит идея о том, чтобы в случае недоступности прокси-сервера, через который производится отправка, использовать другой прокси-сервер из справочника
ВыборкаПрокси = Справочники.Прокси.Выбрать();
Пока ВыборкаПрокси.Следующий() Цикл
Попытка
ИнтернетПрокси = Новый ИнтернетПрокси(Ложь);
ИнтернетПрокси.Установить(ВыборкаПрокси.Протокол, ВыборкаПрокси.Сервер, ВыборкаПрокси.Порт, ВыборкаПрокси.Пользователь, ВыборкаПрокси.Пароль, Ложь);
Соединение = Новый HTTPСоединение("api.telegram.org",443,,,ИнтернетПрокси,,Новый ЗащищенноеСоединениеOpenSSL());
ПолучениеЗапрос = "bot"+ТокенБота+"/sendMessage?chat_id="+Адресат+"&text="+ Сообщение;
Запрос = Новый HTTPЗапрос(ПолучениеЗапрос);
Ответ = Соединение.Получить(Запрос);
Прервать;
Исключение
КонецПопытки;
КонецЦикла;
Примерно такая версия отправки сообщений через прокси начала работать у нас в апреле через несколько дней после известных всем событий. Пару недель этот способ работал хорошо, но потом возникли некоторые проблемы:
- Часть прокси-серверов оказалась заблокированной
- Другая часть прокси периодически становилась недоступной
Для решения этих проблем в справочнике были введены реквизиты "ИспользоватьПоУмолчанию" и "Порядок", которые были призваны обеспечить попытку отправки сообщений через наиболее надежные прокси-сервера. Очевидно, что надежность определялась на глазок. Кроме того изменять эти реквизиты приходилось вручную, что не особо радовало.
На этом велосипеде мы ехали около месяца. Но в начале июня стало понятно, что кактус уже слишком колючий, чтобы его есть, поскольку время отправки сообщений варьировалось от 5 до 30 минут. Иными словами, перебор прокси шел в соответствии с вручную установленными параметрами и, если везло, то отправка происходила с первого-второго раза, а если нет, то перебор прокси продолжался, при этом, как было замечено выше, перебор этот не был основан на сколь-нибудь объективной оценке. Здесь замечу, что при всех описанных неудобствах сообщения все-таки приходили, однако ценность пришедшего через пол часа сообщения, мягко говоря, не всегда была велика, а иногда возникала путаница: поскольку отправка сообщений осуществлялась через фоновые задания, то иногда отправленное позже сообщений приходило даже раньше более раннего сообщения. Ничего хорошего в такой ситуации нет.
Что же делать? С одной стороны можно было забросить Телеграм и переехать на Slack или Viber или даже вернуться к использованию собственного XMPP-сервера. С другой стороны не хотелось отказываться от всех наработок, да и трудозатраты на переезд представлялись вполне ощутимыми. В связи с этим в голову пришла простая и логичная мысль - вести историю отправки и уже на основании этой истории строить своеобразный рейтинг прокси-серверов. Такой подход позволил бы автоматически определять прокси, который в настоящее время наиболее надежен (то есть стабильно отправляет сообщения).
Для ведения истории был создан простой регистр:
В этот регистр записывался факт отправки или неотправки сообщения через прокси-сервер, если сообщение было отправлено, то записывалась 1, иначе -1.
Таким образом, код отправки стал выглядеть примерно так:
Функция ПолучитьHTTPСоединениеСПрокси(Прокси) Экспорт
Если Прокси = Неопределено Тогда
Соединение = Новый HTTPСоединение("api.telegram.org",443,,,,,Новый ЗащищенноеСоединениеOpenSSL());
Иначе
ИнтернетПрокси = Новый ИнтернетПрокси(Ложь);
ИнтернетПрокси.Установить(Прокси.Протокол, Прокси.Сервер, Прокси.Порт, Прокси.Пользователь, Прокси.Пароль, Ложь);
Соединение = Новый HTTPСоединение("api.telegram.org",443,,,ИнтернетПрокси,,Новый ЗащищенноеСоединениеOpenSSL());
КонецЕсли;
Возврат Соединение;
КонецФункции
Процедура ДобавитьВНаборСтатистикиОтправкиСообщенийВМессенджер(Набор, Прокси, Отправлено)
НСтр = Набор.Добавить();
НСтр.Период = ТекущаяДата();
НСтр.Прокси = Прокси;
НСтр.Отправлено = Отправлено;
КонецПроцедуры
Функция ОтправитьСообщение(Сообщение, Адресат, ТокенБота) Экспорт
ВыборкаПрокси = ПолучитьАктивныеПрокси();
Ответ = Неопределено;
Ок = "";
Набор = РегистрыСведений.СтатистикаОтправкиСообщенийВМессенджер.СоздатьНаборЗаписей();
Пока ВыборкаПрокси.Следующий() Цикл
Попытка
Соединение = ПолучитьHTTPСоединениеСПрокси(ВыборкаПрокси.Ссылка);
ПолучениеЗапрос = "bot"+ТокенБота+"/sendMessage?chat_id="+Адресат+"&text="+ Сообщение;
Запрос = Новый HTTPЗапрос(ПолучениеЗапрос);
Ответ = Соединение.Получить(Запрос);
ДобавитьВНаборСтатистикиОтправкиСообщенийВМессенджер(Набор, ВыборкаПрокси.Ссылка, 1);
Прервать;
Исключение
ДобавитьВНаборСтатистикиОтправкиСообщенийВМессенджер(Набор, ВыборкаПрокси.Ссылка, -1);
КонецПопытки;
КонецЦикла;
Набор.Записать(Ложь);
Если ЗначениеЗаполнено(Ответ) = Истина Тогда
Чтение = Новый ЧтениеJSON;
Чтение.УстановитьСтроку(Ответ.ПолучитьТелоКакСтроку());
ОтветСоотв = ПрочитатьJSON(Чтение,Истина);
Ок = ОтветСоотв["ok"] ;
КонецЕсли;
Возврат Ок
КонецФункции
В приведенном фрагменте кода присутствует вызов функции ПолучитьАктивныеПрокси(), однако нет ее объявления. Поэтому хочется остановиться на данной функции подробнее. Само по себе хранение истории отправки сообщений дает совсем не много. Ну что интересного в, например, таких данных:
Видно, что через какие-то прокси сообщения не уходят, через какие-то уходят, а иногда отправка работает через раз. Очевидно, что необходим какой-то критерий для составления того самого рейтинга прокси, о котором говорилось выше. Долго думать над таким критерием не пришлось - за аналог был взят метод оценки по трем точкам. То есть прокси ранжировались исходя из суммы трех показателей:
- сумма успешных и неуспешных отправлений за месяц;
- успешность последней отправки;
- сумма успешных и неуспешных отправлений за текущий день
Каждый из указанных показателей имеет свой коэффициент - 7/12, 3/12 и 2/12 соответственно, коэффициенты подобраны эвристически. При этом неуспешное отправление при суммировании за день засчитывается не за -1 а за -0,8. Такую оценку словами можно описать примерно так: "если прокси хорошо отправлял в течении предыдущего месяца (читай 30 предшествующих дней, то скорее всего будет выбран он, однако если в течении дня прокси работал нестабильно и последняя отправка была неудачной, то возможно его рейтинг снизится) ". Теперь приведу код ранжирования:
Функция ПолучитьАктивныеПрокси() Экспорт
Запрос = Новый Запрос;
Запрос.Текст = "ВЫБРАТЬ
| СтатистикаОтправкиСообщенийВМессенджерСрезПоследних.Прокси,
| СтатистикаОтправкиСообщенийВМессенджерСрезПоследних.Отправлено
|ПОМЕСТИТЬ ВтПоследние
|ИЗ
| РегистрСведений.СтатистикаОтправкиСообщенийВМессенджер.СрезПоследних КАК СтатистикаОтправкиСообщенийВМессенджерСрезПоследних
|ГДЕ
| СтатистикаОтправкиСообщенийВМессенджерСрезПоследних.Отправлено = 1
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| СтатистикаОтправкиСообщенийВМессенджер.Прокси,
| СУММА(ВЫБОР
| КОГДА СтатистикаОтправкиСообщенийВМессенджер.Отправлено < 0
| ТОГДА -0.8
| ИНАЧЕ СтатистикаОтправкиСообщенийВМессенджер.Отправлено
| КОНЕЦ) КАК Отправлено
|ПОМЕСТИТЬ ВтДень
|ИЗ
| РегистрСведений.СтатистикаОтправкиСообщенийВМессенджер КАК СтатистикаОтправкиСообщенийВМессенджер
|ГДЕ
| СтатистикаОтправкиСообщенийВМессенджер.Период МЕЖДУ &ДатаНачала И &ДатаКонец
|
|СГРУППИРОВАТЬ ПО
| СтатистикаОтправкиСообщенийВМессенджер.Прокси
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| СтатистикаОтправкиСообщенийВМессенджер.Прокси,
| СУММА(СтатистикаОтправкиСообщенийВМессенджер.Отправлено) КАК Отправлено
|ПОМЕСТИТЬ ВтМесяц
|ИЗ
| РегистрСведений.СтатистикаОтправкиСообщенийВМессенджер КАК СтатистикаОтправкиСообщенийВМессенджер
|ГДЕ
| СтатистикаОтправкиСообщенийВМессенджер.Период МЕЖДУ &ДатаНачала1 И &ДатаКонец1
|
|СГРУППИРОВАТЬ ПО
| СтатистикаОтправкиСообщенийВМессенджер.Прокси
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| ВтМесяц.Прокси,
| ВтМесяц.Отправлено КАК ОтправленоМесяц,
| ВтПоследние.Отправлено КАК ОтправленоПоследнее,
| ВтДень.Отправлено КАК ОтправленоДень,
| ЕСТЬNULL(ВтМесяц.Отправлено, 0) * (7 / 12) + ЕСТЬNULL(ВтПоследние.Отправлено, 0) * (3 / 12) + ЕСТЬNULL(ВтДень.Отправлено, 0) * (2 / 12) КАК ОтправленоУсред
|ПОМЕСТИТЬ ВтСтат
|ИЗ
| ВтМесяц КАК ВтМесяц
| ЛЕВОЕ СОЕДИНЕНИЕ ВтПоследние КАК ВтПоследние
| ПО ВтМесяц.Прокси = ВтПоследние.Прокси
| ЛЕВОЕ СОЕДИНЕНИЕ ВтДень КАК ВтДень
| ПО ВтМесяц.Прокси = ВтДень.Прокси
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| ЕСТЬNULL(ВтСтат.ОтправленоУсред, 0) КАК ОтправленоУсред,
| ВложенныйЗапрос.Ссылка КАК Ссылка
|ИЗ
| (ВЫБРАТЬ
| Прокси.Ссылка КАК Ссылка,
| Прокси.Порядок КАК Порядок
| ИЗ
| Справочник.Прокси КАК Прокси
| ГДЕ
| Прокси.Активно = ИСТИНА) КАК ВложенныйЗапрос
| ЛЕВОЕ СОЕДИНЕНИЕ ВтСтат КАК ВтСтат
| ПО ВложенныйЗапрос.Ссылка = ВтСтат.Прокси
|
|УПОРЯДОЧИТЬ ПО
| ОтправленоУсред УБЫВ";
Запрос.УстановитьПараметр("ДатаНачала", НачалоДня(ТекущаяДата()) );
Запрос.УстановитьПараметр("ДатаКонец", ТекущаяДата());
Запрос.УстановитьПараметр("ДатаНачала1", ДобавитьМесяц(ТекущаяДата(), -1));
Запрос.УстановитьПараметр("ДатаКонец1", ТекущаяДата());
Результат = Запрос.Выполнить();
Выборка = Результат.Выбрать();
Возврат Выборка;
КонецФункции
Функция оказалась совсем незатейливой. Однако результаты выбранного подхода порадовали. В течении недели собиралась статистика и время отправки сообщений так и оставалось на уровне 5-30 минут, перебор прокси все еще был почти случайным. Но через несколько дней время отправки стабилизировалось и в среднем перестало превышать 1 минуту с редкими задержками до 5 минут. Казалось бы, просто на основании истории были определены 1-2 наиболее стабильных прокси сервера, которые и обеспечивали стабильную отправку сообщений. Однако на деле происходит так, что если эти стабильные прокси вдруг почему-то перестают работать, то их заменяют другие прокси-сервера, которые может быть, не так стабильны, но в текущий момент работают. При этом, например, на следующий день отправка может пойти снова через тот прокси, который вчера почему-то "захандрил".
Для подтверждения сказанного приведу простенькую статистику.
Из рисунка видно, что в период с 16 по 22 июня количество ошибок (когда прокси не отвечал) при отправке сообщений было крайне велико. При этом каждая ошибка увеличивала время отправки сообщений. Однако позднее количество ошибок при отправке снижается и выходит на достаточно стабильный уровень, что обусловлено, как уже говорилось выше, с одной стороны отсутствием необоснованного перебора прокси-серверов и с другой стороны возможностью автоматически производить отправку через прокси с "рейтингом чуть ниже", если "самый хороший сервер" почему-то не отвечает.
Например, заметно, что в первый день использования истории и ранжирования перебор прокси производился практически случайно и большое количество ошибок при отправке обуславливалось тем, что происходило подключение к недоступным прокси. Замечу только, что многие из этих недоступных прокси серверов проверялись буквально за день до этого и были вполне работоспособны.
Если же посмотреть на свежие данные, то очевидно, что:
- При небольшом числе сообщений за день отправка происходит с использованием одного-двух прокси-"лидеров рейтинга" (например 31.07.2018, 01.08.2018);
- При чуть большем количестве сообщений иногда используются прокси с чуть меньшим рейтингом (например 30.07.2018);
- При еще большем количестве сообщений и наличии проблем у прокси-"лидеров" используется еще большее количество серверов (например 27.07.2018), но при этом в отличии от ситуации на предыдущем рисунке ( 16.06.2018) доля обращений к серверам, которые не отвечают, совсем мала; если говорить по-другому то аутсайдеру давался шанс, в условиях, когда лидеры рейтинга сплоховали, но если шанс использован не был, то второго шанса точно не будет.
В завершении хочу сказать, что приведенное решение в общем-то лежит на поверхности. Однако надеюсь, что кому-то этот опыт будет полезен, поскольку он наглядно демонстрирует то, что даже в условиях блокировки и не всегда стабильной работы прокси не стоит спешить отказываться от уже реализованной интеграции с Телеграмом.
Этой мой первый пост, буду признателен за конструктивную обратную связь!