При развертывании мобильных приложений 1С разработчики часто сталкиваются со следующей проблемой: внутри офиса мобильные устройства сотрудников подключаются к локальной Wi-Fi сети, однако опубликованный веб-сервер 1С доступен изнутри только по локальному IP-адресу (например, http://192.168.3.73/), а снаружи — только по внешнему статическому IP или доменному имени (например, https://MyApp.MyCompany.ru:8888/). Попытка подключиться по внешнему адресу, находясь внутри офисного Wi-Fi, приводит к ошибке соединения. Это происходит из-за особенностей маршрутизации, когда локальный роутер не умеет перенаправлять внутренний трафик обратно на себя по внешнему IP-адресу.
В этой статье мы подробно разберем, как решить эту проблему раз и навсегда. Мы рассмотрим как правильные системные настройки сетевого оборудования, так и программные обходные пути ("костыли") непосредственно в коде мобильного приложения на платформе 1С, если договориться с системными администраторами не удается.
Это самый надежный, профессиональный и безопасный подход. Если настроить сеть правильно, то мобильное приложение 1С сможет работать по одному и тому же внешнему адресу как из мобильного интернета (или домашнего Wi-Fi), так и находясь в офисной локальной сети.
Рассмотрим два основных способа реализации этой схемы на сетевом оборудовании:
Эта функция настраивается системным администратором на офисном маршрутизаторе (например, MikroTik, Cisco, Keenetic). Суть механизма заключается в том, что когда устройство из локальной сети обращается к внешнему IP-адресу своего же роутера, роутер распознает этот трафик и перенаправляет его обратно на внутренний IP-адрес сервера 1С. Для мобильного клиента этот процесс происходит абсолютно бесшовно.
Если ваши администраторы ссылаются на то, что это невозможно сделать из-за разных портов (например, внешний порт 8888 перенаправляется на внутренний 80), то правильным решением будет убрать подмену портов. Сделайте так, чтобы веб-сервер 1С внутри сети также слушал тот же порт (или настройте редирект внутри сети через обратный прокси-сервер, например, Nginx).
Этот метод основан на разграничении зон видимости DNS-имен. Суть в том, что доменное имя вашей публикации MyApp.MyCompany.ru на внешних DNS-серверах (в глобальном интернете) указывает на ваш внешний статический IP-адрес. В то же время на локальном DNS-сервере офиса (например, поднятом на контроллере домена Active Directory) настраивается зона, в которой это же имя MyApp.MyCompany.ru указывает на внутренний IP-адрес сервера 1С — 192.168.3.73.
Когда сотрудник находится дома или в дороге, его устройство запрашивает IP у публичного DNS и получает внешний адрес. Когда сотрудник подключается к офисному Wi-Fi, локальный DNS-сервер выдает устройству локальный IP-адрес. В итоге в настройках мобильного приложения всегда прописан один красивый адрес https://MyApp.MyCompany.ru.
Если системные администраторы наотрез отказываются идти навстречу, нам придется решать задачу на стороне мобильного приложения 1С. Один из самых простых и надежных способов — быстрая проверка доступности локального адреса перед началом синхронизации.
Мы можем реализовать функцию, которая отправляет легкий запрос к веб-серверу с очень маленьким временем ожидания (таймаутом). Если локальный сервер ответил быстро — работаем по локальному адресу. Если произошел таймаут или вернулась ошибка — переключаемся на глобальный адрес.
Разберем пример кода на встроенном языке 1С, реализующий этот алгоритм:
Функция ОпределитьРабочийАдресПубликации() Экспорт
ЛокальныйАдрес = "192.168.3.73";
ГлобальныйАдрес = "MyApp.MyCompany.ru";
ЛокальныйПорт = 80;
ГлобальныйПорт = 8888;
// Пытаемся быстро подключиться к локальному серверу.
// Устанавливаем таймаут в 1-2 секунды, чтобы пользователь не ждал долго.
Если ПроверитьДоступностьХоста(ЛокальныйАдрес, ЛокальныйПорт, 2) Тогда
Возврат "http://" + ЛокальныйАдрес + "/MyApp/ws/MobileAppExchange";
КонецЕсли;
// Если локальный адрес недоступен, предполагаем, что мы во внешней сети
Если ПроверитьДоступностьХоста(ГлобальныйАдрес, ГлобальныйПорт, 5) Тогда
Возврат "https://" + ГлобальныйАдрес + ":" + Формат(ГлобальныйПорт, "ЧГ=0") + "/MyApp/ws/MobileAppExchange";
КонецЕсли;
// Если оба адреса недоступны
ВызватьИсключение "Не удалось установить соединение с сервером 1С. Проверьте подключение к сети.";
КонецФункции
Функция ПроверитьДоступностьХоста(Знач Хост, Знач Порт, Знач Таймаут)
// Используем HTTPСоединение для быстрой проверки
// Нам не важно получить данные, главное - проверить сам факт соединения
Попытка
Соединение = Новый HTTPСоединение(Хост, Порт, , , , Таймаут);
Запрос = Новый HTTPЗапрос("/MyApp/ws/MobileAppExchange?wsdl");
Ответ = Соединение.Получить(Запрос);
// Коды 200 (ОК) или 401 (Требуется авторизация) подтверждают, что сервер ответил
Если Ответ.КодСостояния = 200 Или Ответ.КодСостояния = 401 Тогда
Возврат Истина;
КонецЕсли;
Исключение
// Исключение сработает по истечении таймаута или при отсутствии маршрута
Возврат Ложь;
КонецПопытки;
Возврат Ложь;
КонецФункции
Важный нюанс: метод HTTPСоединение с таймаутом отлично работает на мобильной платформе 1С. Использование короткого таймаута в 2 секунды гарантирует, что при нахождении вне офиса приложение не будет "зависать" надолго при попытке простучать локальный IP.
Еще один оригинальный программный способ заключается в сопоставлении текущего внешнего IP-адреса мобильного устройства со статическим IP-адресом офисного шлюза.
Когда устройство подключено к офисному Wi-Fi, весь его трафик идет в интернет через один общий офисный роутер, который имеет вполне конкретный статический внешний IP-адрес (например, 193.242.149.149). Мы можем выполнить быстрый GET-запрос к любому публичному API, возвращающему IP-адрес клиента, и сравнить результат.
Проанализируем пример кода для реализации этой схемы:
Функция УстройствоВЛокальнойСетиОфиса() Экспорт
СтатическийОфисныйIP = "193.242.149.149"; // Внешний IP вашей организации
// Используем бесплатный и быстрый сервис для определения внешнего IP
ХостСервиса = "api.ipify.org";
Попытка
Соединение = Новый HTTPСоединение(ХостСервиса, , , , , 3);
Запрос = Новый HTTPЗапрос("/?format=json");
Ответ = Соединение.Получить(Запрос);
Если Ответ.КодСостояния = 200 Тогда
// Читаем полученный JSON ответ
ЧтениеJSON = Новый ЧтениеJSON;
ЧтениеJSON.УстановитьСтроку(Ответ.ПолучитьТелоКакСтроку());
Данные = ПрочитатьJSON(ЧтениеJSON);
ЧтениеJSON.Закрыть();
ТекущийВнешнийIP = Данные.ip;
// Если внешний IP устройства совпадает с офисным, значит мы подключены к офисному Wi-Fi
Если ТекущийВнешнийIP = СтатическийОфисныйIP Тогда
Возврат Истина;
КонецЕсли;
КонецЕсли;
Исключение
// Если сервис определения IP недоступен, возвращаем Ложь и пробуем глобальный адрес по умолчанию
Возврат Ложь;
КонецПопытки;
Возврат Ложь;
КонецФункции
Если функция УстройствоВЛокальнойСетиОфиса() возвращает Истина, мобильное приложение 1С сразу использует для подключения локальный адрес публикации http://192.168.3.73/.... Если возвращается Ложь (например, пользователь сидит на домашнем Wi-Fi со своим внешним IP или использует мобильные сотовые данные) — приложение подключается по внешнему адресу.
Стандартный метод мобильной платформы ИнформацияОбИнтернетСоединении.ПолучитьТипСоединения() возвращает системное перечисление ТипИнтернетСоединения.WiFi, но не дает детальной информации о том, к какой именно беспроводной точке подключено устройство.
Чтобы программно вытащить имя сети (SSID), можно использовать технологию создания внешних компонент (Native API) для Android и iOS. Написанная на Java или Kotlin компонента для Android может легко обратиться к системным службам операционной системы:
WifiManager wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
WifiInfo info = wifiManager.getConnectionInfo();
String ssid = info.getSSID();
Однако при использовании данного метода следует учитывать жесткие ограничения современных версий мобильных ОС (Android 9+ и iOS 13+): для получения SSID приложение обязано запросить у пользователя явное разрешение на доступ к точной геолокации (ACCESS_FINE_LOCATION), а на самом устройстве в момент проверки должен быть включен GPS-приемник. В противном случае операционная система в целях безопасности вернет пустую строку или значение <unknown ssid>. Из-за этих ограничений данный способ является наименее предпочтительным и сложным в поддержке.
Подводя итоги, расположим решения в порядке убывания их правильности и удобства для бизнеса: