При разработке мобильных приложений на платформе 1С программисты часто сталкиваются с ситуацией, когда небольшие пакеты данных передаются через Web-сервисы без проблем, но при увеличении объема передаваемой информации (например, при передаче файлов или больших структур ValueStorage) возникает ошибка: «Ошибка работы с Интернет: mark/reset not supported». Эта проблема специфична для устройств под управлением ОС Android и имеет под собой глубокие технические корни, связанные с реализацией сетевого стека Java.
В данной статье мы подробно разберем, почему возникает эта ошибка, проанализируем механизмы работы мобильной платформы и рассмотрим несколько способов решения проблемы — от простого обновления платформы до архитектурных изменений в коде обмена данными.
Давайте выясним причину возникновения исключения на уровне операционной системы. Ошибка mark/reset not supported является типичным исключением java.io.IOException. Она возникает в те моменты, когда сетевая библиотека Android пытается выполнить метод reset() для потока данных (InputStream), который не поддерживает установку меток или если размер данных превысил выделенный буфер.
Рассмотрим ситуацию подробнее: когда мобильное приложение 1С вызывает операцию Web-сервиса, оно формирует HTTP-запрос. Если на сервере (например, Apache или IIS) включена авторизация (особенно NTLM или Digest), происходит «рукопожатие»:
Чтобы отправить запрос повторно, системе нужно «отмотать» поток данных к началу. Если объем данных велик и они не поместились в оперативный буфер, метод reset() выдает ошибку, так как поток уже «ушел» вперед и вернуться к началу невозможно. Именно это мы и видим в сообщении об ошибке.
Проанализируем ситуацию с версиями платформы. Данная проблема была официально признана компанией 1С как баг (ошибка №10204781). Она была связана с некорректной работой внутреннего механизма повтора HTTP-запросов в мобильном клиенте именно для ОС Android.
Вариант исправления: необходимо обновить мобильную платформу. Ошибка была исправлена, начиная с версии 8.3.12.74. Если вы используете более ранние версии (например, 8.3.12.67, как в примере автора темы), вы неизбежно будете сталкиваться с этим ограничением при передаче данных объемом более 60-80 Кб.
Проверим работоспособность на примере кода, который часто используется для тестирования стабильности соединения:
Прокси = СформироватьПроксиНаКлиенте();
Если Прокси = Неопределено Тогда
Возврат "Ошибка инициализации прокси";
КонецЕсли;
Стр = "";
ГСЧ = Новый ГенераторСлучайныхЧисел;
Пока СтрДлина(Стр) < 80000 Цикл
Стр = Стр + ГСЧ.СлучайноеЧисло(1000, 1000000) + Символы.ПС;
КонецЦикла;
Дд = ПолучитьДвоичныеДанныеИзСтроки(Стр);
ДанныеДляПередачи = Новый ХранилищеЗначения(Дд);
Попытка
Ответ = Прокси.GetData(ДанныеДляПередачи);
Сообщить("Успешная передача: " + Ответ);
Исключение
Сообщить("Произошла ошибка: " + ОписаниеОшибки());
КонецПопытки;
После обновления платформы до актуального релиза (например, 8.3.12.93 или выше) данный код начнет отрабатывать стабильно, независимо от размера строки.
Если по каким-то причинам обновить платформу на всех устройствах пользователей невозможно, мы можем попытаться обойти проблему на уровне настроек инфраструктуры. Рассмотрим работу с Web-сервером Apache.
Как мы выяснили, ошибка часто провоцируется повторным запросом из-за авторизации 401. Чтобы этого избежать, можно использовать Basic-авторизацию и настроить клиент так, чтобы он отправлял учетные данные сразу, не дожидаясь запроса от сервера. В некоторых случаях помогает переход на анонимный доступ (если это допустимо политикой безопасности), так как в этом случае поток данных передается один раз и потребность в mark/reset отпадает.
Разберем еще один надежный метод, который помогает при работе в нестабильных мобильных сетях и обходит ограничения буферов — разделение больших данных на части (чанки). Вместо того чтобы передавать один объект ХранилищеЗначения размером 20 Мб, мы можем реализовать алгоритм передачи по частям.
Пошаговый алгоритм:
Этот метод не только решает проблему mark/reset not supported, но и делает обмен данными гораздо более устойчивым к разрывам связи, так как при сбое не нужно переотправлять все 20 Мб — достаточно повторить отправку одного маленького пакета.
Проанализируем альтернативный транспорт. В 1С помимо объектов WSПрокси можно использовать объект HTTPСоединение. При передаче данных через HTTPЗапрос и метод ОтправитьДляОбработки() (POST-запрос), платформа иногда использует другие механизмы работы с потоками, которые могут быть более устойчивы к данной ошибке в старых версиях Android.
Пример использования HTTPСоединение для передачи данных:
Соединение = Новый HTTPСоединение("myserver.ru", , Логин, Пароль);
Заголовок = Новый Соответствие();
Заголовок.Вставить("Content-Type", "application/octet-stream");
Запрос = Новый HTTPЗапрос("/v1/data/upload", Заголовок);
Запрос.УстановитьТелоИзДвоичныхДанных(Дд);
Ответ = Соединение.ОтправитьДляОбработки(Запрос);
Если Ответ.КодСостояния = 200 Тогда
Сообщить("Данные успешно отправлены");
КонецЕсли;
Стоит помнить, что при передаче ХранилищеЗначения через параметры Web-сервиса, 1С выполняет автоматическую сериализацию в формат Base64. Это увеличивает объем данных примерно на 33%. Если ваш файл весит 15 Мб, то по сети фактически будет передаваться около 20 Мб. При достижении определенных лимитов в памяти устройства Android это может приводить к непредсказуемому поведению сетевого стека. Рассмотрим возможность передачи чистых двоичных данных (Binary Data) или использование сжатия (Zip), чтобы минимизировать нагрузку на канал связи.
Подводя итог, мы можем выделить следующие рекомендации для устранения ошибки mark/reset not supported:
Соблюдение этих рекомендаций позволит вам создавать стабильные мобильные приложения, способные обмениваться большими объемами данных без ошибок сетевого взаимодействия.