При интеграции платформы 1С:Предприятие с внешними веб-сервисами разработчики часто сталкиваются с важной архитектурной задачей: как правильно организовать процесс авторизации. Типичный сценарий — пользователю или фоновому заданию необходимо один раз авторизоваться в сервисе, а затем выполнять множество запросов для получения или отправки данных, не проходя процедуру авторизации повторно при каждом обращении. Для регулярной передачи данных между информационными базами по расписанию применяется программа автоматизации обмена через веб-сервисы, работающая без изменения конфигураций.
В этой статье мы подробно разберем, как устроена работа с сетевыми соединениями в платформе 1С, почему традиционная попытка «сохранить» объект соединения в памяти является ошибкой, и какие современные механизмы позволяют элегантно решить задачу сохранения сеанса. Для задач по циклическому получению данных из внешних источников часто применяется автоматический сбор файлов по протоколам HTTP и FTP.
Первая идея, которая часто возникает у начинающих разработчиков — создать один раз объект HTTPСоединение, поместить его в глобальную переменную, ПараметрыСеанса или во ВременноеХранилище, а затем использовать по мере необходимости. Однако этот подход неосуществим по нескольким фундаментальным причинам.
HTTPСоединение относится к типу данных, которые не могут быть сериализованы. Это значит, что его физически невозможно записать в базу данных, передать в параметры сеанса или поместить во временное хранилище для последующего извлечения на другом сервере или в другом сеансе.На самом деле, пытаться принудительно удерживать сам объект HTTPСоединение в памяти нет никакой технической необходимости. Платформа 1С:Предприятие 8.3 берет на себя управление низкоуровневыми сетевыми сокетами.
Когда в коде вызывается конструктор Новый HTTPСоединение, платформа не выполняет каждый раз медленное физическое TCP-соединение и TLS-рукопожатие с удаленным сервером. Вместо этого она обращается к системному пулу соединений. Если к данному хосту уже было открыто соединение и его таймаут (keep-alive) на стороне сервера еще не истек, 1С автоматически переиспользует существующий сетевой сокет.
Таким образом, пересоздавать объект HTTPСоединение при каждом запросе — это стандартная, быстрая и абсолютно безопасная операция. Наша главная задача заключается не в сохранении самого соединения, а в сохранении состояния авторизации (кук или токенов) и их последующей передаче в новые запросы.
Если вся цепочка действий (авторизация, отправка запроса, получение данных) выполняется внутри одного серверного вызова (в рамках выполнения одной процедуры или функции), то объект HTTPСоединение самостоятельно справляется с сохранением сессии.
Проанализируем ситуацию: созданный экземпляр HTTPСоединение автоматически отслеживает заголовки Set-Cookie, которые присылает удаленный веб-сервер в ответ на запрос авторизации. Все последующие запросы, отправленные через этот же самый экземпляр объекта, будут автоматически содержать эти куки в своих заголовках. Разработчику не нужно предпринимать никаких дополнительных действий.
Если запросы к веб-сервису разнесены во времени (например, когда пользователь нажимает разные кнопки на управляемой форме), нам необходимо сохранить полученные куки и вручную передавать их при каждом новом создании HTTPСоединение.
Разберем этот процесс по шагам:
Set-Cookie).ПараметрыСеанса или во ВременноеХранилище).HTTPЗапрос и принудительно подставляем сохраненные куки в заголовок Cookie.Рассмотрим подробнее пример кода, реализующий эту схему:
// Шаг 1. Авторизация и получение Cookies
Функция ВыполнитьАвторизациюИПолучитьКуки(АдресСервера, Логин, Пароль)
Соединение = Новый HTTPСоединение(АдресСервера, , , , , 15, Новый ЗащищенноеСоединениеOpenSSL());
Запрос = Новый HTTPЗапрос("/api/login");
// Формируем тело запроса для авторизации (например, JSON)
ДанныеАвторизации = Новый Структура;
ДанныеАвторизации.Вставить("username", Логин);
ДанныеАвторизации.Вставить("password", Пароль);
ЗаписьJSON = Новый ЗаписьJSON;
ЗаписьJSON.УстановитьСтроку();
ЗаписатьJSON(ЗаписьJSON, ДанныеАвторизации);
СтрокаТела = ЗаписьJSON.Закрыть();
Запрос.УстановитьТелоИзСтроки(СтрокаТела, КодировкаТекста.UTF8, ИспользованиеМаркераПоследовательностиБайтов.НеИспользовать);
Запрос.Заголовки.Вставить("Content-Type", "application/json");
Ответ = Соединение.ОтправитьДляОбработки(Запрос);
Если Ответ.КодСостояния = 200 Тогда
// Извлекаем куки из заголовков
СтрокаКук = Ответ.Заголовки.Получить("Set-Cookie");
Возврат СтрокаКук;
Иначе
ВызватьИсключение "Ошибка авторизации: " + Ответ.КодСостояния;
КонецЕсли;
КонецФункции
После получения строки кук мы должны сохранить ее. Если авторизация должна действовать на протяжении всего сеанса работы пользователя в системе, лучшим выбором будет использование ПараметрыСеанса (предварительно создав параметр с типом Строка). Если же куки нужны только в рамках работы конкретной формы, можно использовать ВременноеХранилище, связав его с уникальным идентификатором формы (УИД), чтобы данные автоматически очистились при закрытии формы.
При выполнении последующих запросов мы извлекаем сохраненную строку кук и внедряем ее в заголовок нового запроса:
// Шаг 2. Выполнение последующего запроса с сохраненными куками
Функция ПолучитьДанныеСервиса(АдресСервера, ПутьКРесурсу, СохраненныеКуки)
Соединение = Новый HTTPСоединение(АдресСервера, , , , , 15, Новый ЗащищенноеСоединениеOpenSSL());
Запрос = Новый HTTPЗапрос(ПутьКРесурсу);
// Подставляем сохраненные куки в заголовки нового запроса
Если ЗначениеЗаполнено(СохраненныеКуки) Тогда
Запрос.Заголовки.Вставить("Cookie", СохраненныеКуки);
КонецЕсли;
Ответ = Соединение.Получить(Запрос);
Возврат Ответ.ПолучитьТелоКакСтроку();
КонецФункции
Современным стандартом разработки веб-сервисов является использование авторизационных токенов вместо традиционных сессионных кук. Этот подход наиболее устойчив к кратковременным разрывам связи и масштабированию серверной инфраструктуры веб-сервиса. По этой технологии работает и бесплатная обработка для загрузки данных Wildberries по API, которая получает сведения о продажах комиссионеров в типовые конфигурации.
Процесс работы с токенами выглядит следующим образом:
access_token) и токен обновления (refresh_token).ПараметрыСеанса. Долгоживущий токен обновления (или учетные данные для повторного получения токена) сохраняется в базе данных — например, в безопасном хранилище данных или в защищенном регистре сведений.Authorization со спецификацией Bearer.refresh_token, сохраняет новый токен и повторяет целевой запрос без вывода ошибок пользователю.Посмотрим на пример формирования запроса с использованием токена:
Функция ВыполнитьЗапросСТокеном(АдресСервера, ПутьКРесурсу, ТокенДоступа)
Соединение = Новый HTTPСоединение(АдресСервера, , , , , 15, Новый ЗащищенноеСоединениеOpenSSL());
Запрос = Новый HTTPЗапрос(ПутьКРесурсу);
// Передаем токен в заголовке Authorization
Запрос.Заголовки.Вставить("Authorization", "Bearer " + ТокенДоступа);
Ответ = Соединение.Получить(Запрос);
Возврат Ответ;
КонецФункции
Как справедливо заметили участники форума, если авторизационные данные или куки требуются только на короткий промежуток времени (например, пока открыта форма обработки), можно успешно задействовать ВременноеХранилище.
При этом критически важно привязывать данные к уникальному идентификатору формы (УИД). Это гарантирует, что данные не будут удалены сборщиком мусора платформы раньше времени (по умолчанию время жизни данных во временном хранилище без привязки к форме составляет 20 минут), и будут автоматически очищены операционной системой при закрытии этой формы пользователем.
Проанализируем пример сохранения авторизационной структуры во временное хранилище:
// Сохранение авторизационных данных на сервере
АдресХранилища = ПоместитьВоВременноеХранилище(АвторизационныеДанные, УникальныйИдентификаторФормы);
Подводя итоги, сформулируем основные правила для разработчиков 1С при проектировании интеграций с веб-сервисами:
HTTPСоединение. Платформа самостоятельно оптимизирует сетевые сокеты через встроенный пул соединений. Вы можете смело вызывать Новый HTTPСоединение при каждом запросе.Set-Cookie ответа авторизации в заголовки Cookie последующих запросов.ПараметрыСеанса (для глобальных интеграций) или ВременноеХранилище с привязкой к УИД формы (для локальных задач).