Закрываем WebSocket, веб-сервис и HTTP-соединение 1С без зависших сеансов

· 9 мин

Содержание

У объектов HTTPСоединение и WSПрокси в 1С нет метода «Закрыть»: соединение закрывается, когда на объект не остаётся ссылок. Поэтому «закрыть» значит присвоить переменной Неопределено или дать ей выйти из области видимости. WebSocket, открытый из JavaScript в поле HTML-документа, закрывают вызовом ws.close(1000). Сеанс HTTP-сервиса с повторным использованием завершают заголовком IBSession: finish.

Под словом «WS-соединение» в 1С прячутся три разные вещи: канал WebSocket, вызов веб-сервиса SOAP и обычное HTTP-соединение. У каждой свой способ корректного закрытия и свои причины зависания. Механизм общий для платформы 8.3 и не зависит от конфигурации — в Бухгалтерии, ЗУП и УТ всё работает одинаково.

Какое соединение вы закрываете: WebSocket, веб-сервис или HTTP

Прежде чем писать код, определите тип канала. От этого зависит, что вообще можно закрыть.

Что этоЧем создаётся в 1СКак живётКак закрыть
WebSocket (ws://, wss://)JavaScript в поле HTML, внешняя компонента, система взаимодействияПостоянный канал, держится до явного закрытияclose() на стороне, которая открыла
Веб-сервис SOAPWSОпределения + WSПрокси, WS-ссылкаЗапрос — ответ, TCP может переиспользоватьсяУничтожить объект прокси
HTTP-запросHTTPСоединение + HTTPЗапросЗапрос — ответ, keep-alive внутри объектаУничтожить объект соединения
Сеанс на сервере 1СHTTP-сервис или веб-сервис с повторным использованием сеансовЖивёт до тайм-аута или явного завершенияIBSession: finish или тайм-аут

💡 Система взаимодействия (обсуждения, видеозвонки) тоже работает через WebSocket. Этим каналом управляет сама платформа. Закрыть его своим кодом нельзя и не нужно — проблемы с ним решают на стороне сервера взаимодействия и прокси.

У HTTP-сервиса 1С нет режима WebSocket-сервера. Обработчик получает HTTPСервисЗапрос и возвращает один HTTPСервисОтвет. Держать открытый канал внутри обработчика не получится: долгий цикл в нём просто занимает сеанс и рабочий процесс.

Закрытие соединения на стороне клиента 1С

HTTPСоединение: освободить объект

Пока объект HTTPСоединение жив, платформа может держать TCP-соединение открытым и использовать его для следующих запросов. Это удобно для серии вызовов, но плохо, если объект хранится в реквизите или глобальной переменной.

Соединение = Новый HTTPСоединение("api.example.ru", 443, , , , 30, Новый ЗащищенноеСоединениеOpenSSL);
Запрос = Новый HTTPЗапрос("/v1/status");
Запрос.Заголовки.Вставить("Connection", "close");

Ответ = Соединение.Получить(Запрос);
Если Ответ.КодСостояния <> 200 Тогда
	ЗаписьЖурналаРегистрации("Обмен.HTTP", УровеньЖурналаРегистрации.Ошибка, , ,
		СтрШаблон("Код ответа %1", Формат(Ответ.КодСостояния, "ЧГ=0")));
КонецЕсли;

Соединение = Неопределено; // соединение освобождается

Что здесь важно:

  • 🔹 шестой параметр конструктора — тайм-аут в секундах. Без него запрос может ждать ответа очень долго;
  • 🔹 заголовок Connection: close просит сервер закрыть TCP сразу после ответа. Нужен, когда сервер копит keep-alive соединения;
  • 🔹 Неопределено в конце — явный сигнал, что объект больше не нужен. В локальной переменной процедуры это произойдёт и само при выходе.

WSПрокси: то же правило

Для веб-сервиса SOAP схема та же. Создайте WSОпределения и WSПрокси, вызовите операцию, затем освободите оба объекта.

Определения = Новый WSОпределения("https://srv.example.ru/base/ws/exchange?wsdl", Логин, Пароль, , 30);
Прокси = Новый WSПрокси(Определения, "http://example.ru/exchange", "Exchange", "ExchangeSoap", , 30);
Прокси.Пользователь = Логин;
Прокси.Пароль = Пароль;

Результат = Прокси.GetStatus();

Прокси = Неопределено;
Определения = Неопределено;

⚠️ Не кладите WSПрокси в переменную модуля приложения «на всякий случай», чтобы не загружать WSDL каждый раз. Объект живёт весь сеанс, и вместе с ним висит соединение. Если WSDL грузится долго, сохраните его в файл и создавайте определения из файла.

WebSocket в поле HTML-документа

Встроенного клиента WebSocket в привычных объектах платформы (HTTPСоединение, WSПрокси) нет. Если в вашей версии платформы есть собственные объекты для WebSocket, найдите их в синтакс-помощнике поиском по слову WebSocket и закрывайте соединение их методами. Классический способ — страница с JavaScript в поле HTML-документа на форме.

Скрипт страницы хранит сокет в переменной и даёт функцию закрытия:

<script>
var ws = null;
function openSocket(url) {
  ws = new WebSocket(url);
  ws.onclose = function (e) {
    document.getElementById('state').value = e.code + ' ' + e.reason;
  };
}
function closeSocket() {
  if (ws && (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING)) {
    ws.close(1000, 'client exit');
  }
}
</script>
<input type="hidden" id="state">

Модуль формы вызывает её при закрытии:

&НаКлиенте
Процедура ПриЗакрытии(ЗавершениеРаботы)
	Попытка
		Элементы.ПолеHTML.Документ.defaultView.closeSocket();
	Исключение
		// страница уже выгружена — сокет закрыт браузером
	КонецПопытки;
КонецПроцедуры

Если форму закрыли без вызова close(), браузерный движок оборвёт TCP сам. Сервер увидит это как аварийное закрытие с кодом 1006 и может какое-то время считать клиента живым.

Сеансы HTTP-сервиса и веб-сервиса на сервере

Каждый вызов HTTP-сервиса или веб-сервиса 1С выполняется в сеансе информационной базы. Сколько он живёт, задают два свойства в конфигураторе — у HTTP-сервиса и у веб-сервиса они одинаковые.

СвойствоЗначениеЧто происходит
Повторное использование сеансовНе использоватьНа каждый запрос — новый сеанс, после ответа он завершается
ИспользоватьСеанс живёт между запросами, клиент передаёт cookie ibsession
Автоматически использоватьПлатформа сама берёт сеанс из пула
Продолжительность сеансасекундыСколько ждать следующего запроса, прежде чем завершить сеанс

Эти же параметры можно переопределить при публикации в файле default.vrd:

<httpServices publishByDefault="false">
  <service name="Exchange" rootUrl="exchange" enable="true"
           reuseSessions="autouse" sessionMaxAge="60"
           poolSize="10" poolTimeout="5"/>
</httpServices>

Как завершить сеанс явно: заголовок IBSession

В режиме «Использовать» клиент управляет сеансом сам. Первый запрос с заголовком IBSession: start открывает сеанс, сервер возвращает cookie ibsession. Дальше клиент шлёт этот cookie. Последний запрос отправляют с заголовком IBSession: finish — сервер завершает сеанс сразу, не дожидаясь тайм-аута.

Запрос = Новый HTTPЗапрос("/base/hs/exchange/done");
Запрос.Заголовки.Вставить("Cookie", "ibsession=" + ИдСеанса);
Запрос.Заголовки.Вставить("IBSession", "finish");
Ответ = Соединение.Получить(Запрос);

Если finish не отправлять, сеанс висит до истечения «Продолжительности сеанса». При сотне клиентов это сотня занятых сеансов и лицензий.

Где искать зависшие соединения

Консоль кластера и rac

Откройте консоль администрирования серверов 1С, кластер → Информационные базы → ваша база → Сеансы. Нужные строки видны по колонке приложения: «Web-сервис», «HTTP-сервис». Обращайте внимание на время начала и последней активности.

Из командной строки то же самое показывает rac:

rac cluster list
rac session list --cluster=<uuid кластера>

В выводе ищите app-id со значениями WSConnection и HTTPServiceConnection. Набор остальных фильтров и параметров смотрите в rac help session.

Из кода конфигурации

Быстрый подсчёт сеансов веб- и HTTP-сервисов на сервере:

&НаСервереБезКонтекста
Функция КоличествоСервисныхСеансов()
	Счетчик = 0;
	Для Каждого Сеанс Из ПолучитьСеансыИнформационнойБазы() Цикл
		Если Сеанс.ИмяПриложения = "WSConnection"
			Или Сеанс.ИмяПриложения = "HTTPServiceConnection" Тогда
			Счетчик = Счетчик + 1;
		КонецЕсли;
	КонецЦикла;
	Возврат Счетчик;
КонецФункции

Если число растёт с каждым обменом и не падает, сеансы не закрываются: нет IBSession: finish, слишком большая продолжительность сеанса или клиент держит соединение.

Принудительное завершение

Сеанс можно удалить в консоли кластера или командой rac session terminate с указанием кластера и сеанса.

Удаление сеанса обрывает текущий запрос: обмен, который он выполнял, завершится с ошибкой, незаписанные данные пропадут. Делайте это вне окна обмена и предупредите владельцев интеграции.

Диагностика: выберите симптом

Коды завершения WebSocket и ошибки соединения

Закрытие WebSocket — это обмен кадрами Close с кодом и текстом причины. Код видно в событии onclose (e.code). Основные значения по стандарту RFC 6455:

КодСмыслКто ставит
1000Нормальное закрытиеКлиент или сервер
1001Сторона уходит (закрыли страницу, сервер выключается)Браузер, сервер
1002Ошибка протоколаСервер
1006Обрыв без кадра CloseНикто: так браузер сообщает об аварийном разрыве
1008Нарушение политики (например, не прошла авторизация)Сервер
1011Внутренняя ошибка сервераСервер
3000–4999Свои коды приложенияВаш код

Из браузерного JavaScript в close() можно передать только 1000 или код от 3000 до 4999. Код 1006 вручную отправить нельзя — он означает, что соединение оборвалось без рукопожатия закрытия.

Для HTTPСоединение и WSПрокси ошибки приходят исключением. Тексты сетевых ошибок обычно на английском, например Timeout was reached, Couldn't connect to server, Failure when receiving data from the peer. Ловите их в Попытка … Исключение и пишите в журнал регистрации:

Попытка
	Ответ = Соединение.Получить(Запрос);
Исключение
	ЗаписьЖурналаРегистрации("Обмен.HTTP", УровеньЖурналаРегистрации.Ошибка, , ,
		ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
	Соединение = Неопределено;
	ВызватьИсключение;
КонецПопытки;

Сбросив соединение в Неопределено после ошибки, вы гарантируете, что повторная попытка откроет свежее TCP-соединение, а не полуразорванное.

Тайм-ауты: платформа, веб-сервер, прокси

Соединение рвётся там, где короче всего тайм-аут. Цепочка обычно такая: клиент → прокси (nginx) → веб-сервер с модулем 1С (Apache или IIS) → кластер 1С.

УровеньЧто настраиваетГде
Клиент 1СОжидание ответаПараметр «Таймаут» в HTTPСоединение, WSОпределения, WSПрокси
Сеанс сервисаЖизнь сеанса между запросами«Продолжительность сеанса», sessionMaxAge в default.vrd
Пул сеансовОжидание свободного сеансаpoolSize, poolTimeout в default.vrd
ApacheОжидание данных, keep-aliveTimeout, KeepAliveTimeout в httpd.conf
nginxТишина в каналеproxy_read_timeout (по умолчанию 60 секунд)
База 1СПассивные сеансы«Время засыпания пассивного сеанса», «Время завершения спящего сеанса» в свойствах базы в консоли

Для WebSocket через nginx нужны заголовки апгрейда, иначе соединение не установится:

location /ws/ {
    proxy_pass http://backend;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_read_timeout 600s;
}

💡 Не лечите обрывы бесконечным тайм-аутом. Лучше раз в 30–50 секунд слать с клиента короткое сообщение-пинг: канал не простаивает, а мёртвые соединения прокси закроет сам.

⚠️ Перезапуск Apache, IIS или nginx после правки конфигурации обрывает все текущие запросы к опубликованным базам. Для nginx используйте nginx -t, затем nginx -s reload — он применяет настройки без разрыва активных соединений.

Ошибки разработчиков при закрытии соединений

  • ❌ Соединение в реквизите формы или модуле приложения. Объект живёт весь сеанс, TCP висит. Создавайте соединение в процедуре, которая его использует.
  • ❌ Новый HTTPСоединение на каждый запрос в цикле. Обратная крайность: тысяча запросов — тысяча TCP-рукопожатий и TLS. Для серии запросов к одному серверу создайте один объект перед циклом и освободите после.
  • ❌ Режим «Использовать» без IBSession: finish. Сеансы и лицензии копятся до истечения продолжительности.
  • ❌ Долгая работа внутри обработчика HTTP-сервиса. Клиент отваливается по тайм-ауту, а сервер продолжает работу в сеансе. Отвечайте сразу и запускайте фоновое задание.
  • ❌ Попытка держать WebSocket в HTTP-сервисе циклом ожидания. Такой сервис занимает рабочий процесс и всё равно отдаёт один ответ.
  • ❌ Закрытие WebSocket кодом 1006 или 1001 из JavaScript. Браузер выбросит исключение: допустимы 1000 и 3000–4999.
  • ❌ Пустой Исключение без записи в журнал. Обрыв соединения проходит незаметно, а сеанс на другой стороне остаётся.

Частые вопросы

Почему в синтакс-помощнике у HTTPСоединение нет метода Закрыть?
Платформа управляет TCP сама. Соединение закрывается, когда объект уничтожен: переменная получила Неопределено или вышла из области видимости.
Закрывается ли сеанс 1С, если клиент оборвал соединение посреди запроса?
Нет, обработчик на сервере доработает до конца. Сеанс завершится по своим правилам: сразу при «Не использовать», по тайм-ауту при повторном использовании.
Можно ли закрыть сеанс HTTP-сервиса из самого обработчика?
Нет, сеансом управляет клиент: заголовок IBSession: finish или тайм-аут продолжительности сеанса.
Тратят ли сеансы веб-сервисов лицензии?
Да, сеанс веб-сервиса или HTTP-сервиса — клиентское подключение и требует клиентской лицензии. Зависшие сеансы могут исчерпать лицензии.
Какое значение продолжительности сеанса выбрать?
Чуть больше обычной паузы между запросами одного клиента. Для обмена пачкой запросов подойдёт 20–60 секунд. Большие значения только копят сеансы.