Закрываем WebSocket, веб-сервис и HTTP-соединение 1С без зависших сеансов
Содержание
У объектов 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() на стороне, которая открыла |
| Веб-сервис SOAP | WSОпределения + 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-alive | Timeout, 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.
- ❌ Пустой
Исключениебез записи в журнал. Обрыв соединения проходит незаметно, а сеанс на другой стороне остаётся.